| rev |
line source |
|
arpad@68
|
1 <?php
|
|
arpad@68
|
2 /*
|
|
arpad@76
|
3 * dWing - a cms aimed to be as bleeding edge as possible
|
|
arpad@76
|
4 * Copyright (C) 2007-2008 Arpad Borsos <arpad.borsos@googlemail.com>
|
|
arpad@76
|
5 *
|
|
arpad@76
|
6 * This program is free software: you can redistribute it and/or modify
|
|
arpad@76
|
7 * it under the terms of the GNU General Public License as published by
|
|
arpad@76
|
8 * the Free Software Foundation, either version 3 of the License, or
|
|
arpad@76
|
9 * (at your option) any later version.
|
|
arpad@76
|
10 *
|
|
arpad@76
|
11 * This program is distributed in the hope that it will be useful,
|
|
arpad@76
|
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
arpad@76
|
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
arpad@76
|
14 * GNU General Public License for more details.
|
|
arpad@76
|
15 *
|
|
arpad@76
|
16 * You should have received a copy of the GNU General Public License
|
|
arpad@76
|
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
arpad@76
|
18 */
|
|
arpad@165
|
19
|
|
arpad@165
|
20 // TODO: active.php may not be the best file name to house these classes
|
|
arpad@165
|
21 // the autoload handler would miss JSONable for sure
|
|
arpad@165
|
22
|
|
arpad@165
|
23 /**
|
|
arpad@165
|
24 * Interface for ActiveRecord Classes
|
|
arpad@165
|
25 */
|
|
arpad@165
|
26 interface ActiveRecord
|
|
arpad@165
|
27 {
|
|
arpad@165
|
28 /**
|
|
arpad@165
|
29 * Interfaces to not allow member variables, but please make sure to define
|
|
arpad@165
|
30 * a public $id member.
|
|
arpad@165
|
31 */
|
|
arpad@165
|
32 //public $id;
|
|
arpad@165
|
33 /**
|
|
arpad@169
|
34 * An ActiveRecord class should have a constructor which takes an ID and
|
|
arpad@169
|
35 * fetches the corresponding Object
|
|
arpad@169
|
36 */
|
|
arpad@169
|
37 //public function __construct($aId)
|
|
arpad@169
|
38 /**
|
|
arpad@165
|
39 * Saves the object into the Database
|
|
arpad@165
|
40 * Creates a new transaction and commits it when $aUseTransaction is true
|
|
arpad@165
|
41 */
|
|
arpad@165
|
42 public function save($aUseTransaction = false);
|
|
arpad@165
|
43 /**
|
|
arpad@165
|
44 * Deletes the object from the Database
|
|
arpad@165
|
45 * Creates a new transaction and commits it when $aUseTransaction is true
|
|
arpad@165
|
46 */
|
|
arpad@165
|
47 public function delete($aUseTransaction = false);
|
|
arpad@169
|
48 /**
|
|
arpad@169
|
49 * This function assigns $aData passed in to the object
|
|
arpad@169
|
50 */
|
|
arpad@169
|
51 public function assignData($aData);
|
|
arpad@165
|
52 }
|
|
arpad@165
|
53
|
|
arpad@165
|
54 /**
|
|
arpad@165
|
55 * Interface for Objects that can be JSON-ified
|
|
arpad@165
|
56 */
|
|
arpad@165
|
57 interface JSONable
|
|
arpad@165
|
58 {
|
|
arpad@165
|
59 /**
|
|
arpad@165
|
60 * Turns the object into either a JSON String when $aEncode is true or
|
|
arpad@165
|
61 * returns a associative array that can be turned into JSON via json_encode()
|
|
arpad@165
|
62 * Also includes ContentProvider Children when $aIncludeChildren is true
|
|
arpad@165
|
63 */
|
|
arpad@165
|
64 public function toJSON($aEncode = true, $aIncludeChildren = false);
|
|
arpad@165
|
65 }
|
|
arpad@76
|
66
|
|
arpad@76
|
67 /*
|
|
arpad@165
|
68 * TODO:
|
|
arpad@165
|
69 * fetch data on __get and use IFNULL() to not overwrite the record with empty data
|
|
arpad@165
|
70 */
|
|
arpad@165
|
71
|
|
arpad@165
|
72 /**
|
|
arpad@165
|
73 * Abstract Base implementation of ActiveRecord
|
|
arpad@165
|
74 */
|
|
arpad@165
|
75 /*
|
|
arpad@68
|
76 Used like this:
|
|
arpad@68
|
77
|
|
arpad@174
|
78 class News extends ActiveItem
|
|
arpad@68
|
79 {
|
|
arpad@68
|
80 //protected $tableName = 'news';
|
|
arpad@68
|
81 protected $primaryKey = 'news_id';
|
|
arpad@68
|
82 protected $definition = array('title' => 'required', 'text' => 'html',
|
|
arpad@110
|
83 'user_id' => 'user', 'time' => 'time', 'fancyurl' => 'value');
|
|
arpad@68
|
84 }
|
|
arpad@174
|
85 class Comment extends ActiveItem
|
|
arpad@68
|
86 {
|
|
arpad@68
|
87 protected $tableName = 'comments';
|
|
arpad@110
|
88 protected $definition = array('text' => 'html', 'user_id' => 'user',
|
|
arpad@68
|
89 'time' => 'time', 'content_id' => 'required', 'content_type' => 'required');
|
|
arpad@68
|
90 }
|
|
arpad@165
|
91 */
|
|
arpad@174
|
92 abstract class ActiveItem implements ActiveRecord, JSONable, ContentItem
|
|
arpad@68
|
93 {
|
|
arpad@68
|
94 private static $statements = array();
|
|
arpad@68
|
95 protected $primaryKey = 'id';
|
|
arpad@68
|
96 protected $data = array();
|
|
arpad@68
|
97 protected $className;
|
|
arpad@68
|
98 protected $tableName;
|
|
arpad@68
|
99 public $id;
|
|
arpad@174
|
100
|
|
arpad@174
|
101 /**
|
|
arpad@174
|
102 * Until we require PHP5.3, just reimplement this method, returning the
|
|
arpad@174
|
103 * correct ContentType
|
|
arpad@174
|
104 */
|
|
arpad@174
|
105 public static function ContentType()
|
|
arpad@174
|
106 {
|
|
arpad@174
|
107 return 0;
|
|
arpad@174
|
108 }
|
|
arpad@68
|
109
|
|
arpad@68
|
110 public function __construct($aData = null)
|
|
arpad@98
|
111 {
|
|
arpad@68
|
112 $this->className = get_class($this);
|
|
arpad@68
|
113 if(empty($this->tableName))
|
|
arpad@68
|
114 $this->tableName = strtolower($this->className);
|
|
arpad@159
|
115 $this->tableName = Core::$prefix.$this->tableName;
|
|
arpad@116
|
116
|
|
arpad@116
|
117 if($aData == null)
|
|
arpad@116
|
118 return;
|
|
arpad@68
|
119
|
|
arpad@68
|
120 if(is_array($aData))
|
|
arpad@68
|
121 {
|
|
arpad@94
|
122 // primary key set: fetch the old record and overwrite the data with the new one
|
|
arpad@94
|
123 // problem: I want to create Objects from a fetchAll query that don't re-fetch
|
|
arpad@94
|
124 // themselves
|
|
arpad@121
|
125 if(!empty($aData[$this->primaryKey]) || !empty($aData['id']))
|
|
arpad@121
|
126 {
|
|
arpad@121
|
127 if(!empty($aData[$this->primaryKey]))
|
|
arpad@121
|
128 $this->id = (int)$aData[$this->primaryKey];
|
|
arpad@121
|
129 else if(!empty($aData['id']))
|
|
arpad@121
|
130 $this->id = (int)$aData['id'];
|
|
arpad@94
|
131 $this->fetchData();
|
|
arpad@94
|
132 $this->data = array_merge($this->data, $aData);
|
|
arpad@94
|
133 unset($this->data[$this->primaryKey]);
|
|
arpad@94
|
134 }
|
|
arpad@94
|
135 // else: write the data so it can be saved afterwards
|
|
arpad@94
|
136 else
|
|
arpad@94
|
137 {
|
|
arpad@94
|
138 $this->data = $aData;
|
|
arpad@129
|
139 }
|
|
arpad@129
|
140 return;
|
|
arpad@68
|
141 }
|
|
arpad@121
|
142 if(is_numeric($aData)) // We have an Id
|
|
arpad@68
|
143 {
|
|
arpad@94
|
144 $this->id = (int)$aData;
|
|
arpad@94
|
145 $this->fetchData();
|
|
arpad@116
|
146 }
|
|
arpad@116
|
147 else if(in_array('fancyurl', $this->definition)) // We have a fancyurl
|
|
arpad@116
|
148 {
|
|
arpad@116
|
149 $childClass = $this->className;
|
|
arpad@116
|
150 if(empty(self::$statements[$childClass]['fancyurl']))
|
|
arpad@116
|
151 {
|
|
arpad@116
|
152 self::$statements[$childClass]['fancyurl'] =
|
|
arpad@116
|
153 Core::$db->prepare('SELECT * FROM '.$this->tableName.' WHERE
|
|
arpad@116
|
154 fancyurl=:fancyurl;'); // TODO: do not hardcore the fancyurl name
|
|
arpad@116
|
155 }
|
|
arpad@116
|
156 $statement = self::$statements[$childClass]['fancyurl'];
|
|
arpad@116
|
157 $statement->bindValue(':fancyurl', Utils::fancyUrl($aData), PDO::PARAM_STR);
|
|
arpad@116
|
158 $statement->execute();
|
|
arpad@116
|
159 $this->data = $statement->fetch(PDO::FETCH_ASSOC);
|
|
arpad@116
|
160 if(!empty($this->data))
|
|
arpad@116
|
161 {
|
|
arpad@116
|
162 $this->id = $this->data[$this->primaryKey];
|
|
arpad@116
|
163 unset($this->data[$this->primaryKey]);
|
|
arpad@116
|
164 }
|
|
arpad@68
|
165 }
|
|
arpad@68
|
166 }
|
|
arpad@94
|
167 protected function fetchData()
|
|
arpad@68
|
168 {
|
|
arpad@68
|
169 $childClass = $this->className;
|
|
arpad@68
|
170 if(empty(self::$statements[$childClass]['read']))
|
|
arpad@68
|
171 {
|
|
arpad@68
|
172 self::$statements[$childClass]['read'] =
|
|
arpad@106
|
173 Core::$db->prepare('SELECT * FROM '.$this->tableName.' WHERE '.
|
|
arpad@68
|
174 $this->primaryKey.'=:id;');
|
|
arpad@68
|
175 }
|
|
arpad@68
|
176 if(empty($this->data))
|
|
arpad@68
|
177 {
|
|
arpad@68
|
178 $statement = self::$statements[$childClass]['read'];
|
|
arpad@68
|
179 $statement->bindValue(':id', $this->id, PDO::PARAM_INT);
|
|
arpad@68
|
180 $statement->execute();
|
|
arpad@68
|
181 $this->data = $statement->fetch(PDO::FETCH_ASSOC);
|
|
arpad@94
|
182 if(empty($this->data))
|
|
arpad@94
|
183 unset($this->id);
|
|
arpad@68
|
184 }
|
|
arpad@94
|
185 }
|
|
arpad@94
|
186 public function assignData($aData)
|
|
arpad@121
|
187 {
|
|
arpad@94
|
188 $this->data = array_merge($this->data, $aData);
|
|
arpad@94
|
189 }
|
|
arpad@94
|
190 public function __get($aVarName)
|
|
arpad@98
|
191 {
|
|
arpad@169
|
192 $this->fetchData();
|
|
arpad@169
|
193 if(!isset($this->data[$aVarName]))
|
|
arpad@169
|
194 {
|
|
arpad@169
|
195 // TODO: is it really good to hardcore 'user_id' here?
|
|
arpad@169
|
196 if($aVarName == 'user' && isset($this->definition['user_id']))
|
|
arpad@169
|
197 {
|
|
arpad@169
|
198 $this->data['user'] = $user = Users::getUser($this->data['user_id']);
|
|
arpad@169
|
199 return $user;
|
|
arpad@169
|
200 }
|
|
arpad@171
|
201 $singular = Utils::makeSingular($aVarName);
|
|
arpad@171
|
202 if(!Utils::isSingular($aVarName) && class_exists($singular) &&
|
|
arpad@171
|
203 Utils::doesImplement($singular, 'ContentProvider'))
|
|
arpad@171
|
204 {
|
|
arpad@171
|
205 $this->data[$aVarName] = $obj =
|
|
arpad@171
|
206 call_user_func(array($singular, 'getAllFor'), $this);
|
|
arpad@171
|
207 return $obj;
|
|
arpad@171
|
208 }
|
|
arpad@171
|
209 if(Utils::isSingular($aVarName) && class_exists($aVarName) &&
|
|
arpad@169
|
210 Utils::doesImplement($aVarName, 'ContentProviderSingle'))
|
|
arpad@169
|
211 {
|
|
arpad@169
|
212 $this->data[$aVarName] = $obj =
|
|
arpad@169
|
213 call_user_func(array($aVarName, 'getFor'), $this);
|
|
arpad@169
|
214 return $obj;
|
|
arpad@169
|
215 }
|
|
arpad@98
|
216 return null;
|
|
arpad@169
|
217 }
|
|
arpad@169
|
218 return $this->data[$aVarName];
|
|
arpad@94
|
219 }
|
|
arpad@94
|
220 public function __isset($aVarName)
|
|
arpad@94
|
221 {
|
|
arpad@173
|
222 return $this->__get($aVarName) != null;
|
|
arpad@68
|
223 }
|
|
arpad@68
|
224 public function __set($aVarName, $aValue)
|
|
arpad@98
|
225 {
|
|
arpad@98
|
226 if(!isset($this->definition[$aVarName]) && $aVarName != $this->primaryKey)
|
|
arpad@98
|
227 return;
|
|
arpad@98
|
228 if($aVarName == $this->primaryKey)
|
|
arpad@98
|
229 $this->id = $aValue;
|
|
arpad@98
|
230 else
|
|
arpad@98
|
231 $this->data[$aVarName] = $aValue;
|
|
arpad@68
|
232 }
|
|
arpad@165
|
233 public function save($aUseTransaction = false)
|
|
arpad@68
|
234 {
|
|
arpad@173
|
235 if($aUseTransaction)
|
|
arpad@175
|
236 Core::$db->beginTransaction();
|
|
arpad@68
|
237 $childClass = $this->className;
|
|
arpad@68
|
238 if(empty($this->id))
|
|
arpad@68
|
239 {
|
|
arpad@68
|
240 if(empty(self::$statements[$childClass]['create']))
|
|
arpad@68
|
241 {
|
|
arpad@68
|
242 $query = 'INSERT INTO '.$this->tableName.' SET ';
|
|
arpad@68
|
243 $colDefs = array();
|
|
arpad@68
|
244 foreach($this->definition as $column => $options)
|
|
arpad@68
|
245 {
|
|
arpad@68
|
246 $colDefs[] = $column.'=:'.$column;
|
|
arpad@68
|
247 }
|
|
arpad@68
|
248 $query.= implode(', ', $colDefs).';';
|
|
arpad@106
|
249 self::$statements[$childClass]['create'] = Core::$db->prepare($query);
|
|
arpad@68
|
250 }
|
|
arpad@68
|
251 $statement = self::$statements[$childClass]['create'];
|
|
arpad@68
|
252 }
|
|
arpad@68
|
253 else
|
|
arpad@68
|
254 {
|
|
arpad@68
|
255 if(empty(self::$statements[$childClass]['update']))
|
|
arpad@68
|
256 {
|
|
arpad@68
|
257 $query = 'UPDATE '.$this->tableName.' SET ';
|
|
arpad@68
|
258 $colDefs = array();
|
|
arpad@68
|
259 foreach($this->definition as $column => $options)
|
|
arpad@68
|
260 {
|
|
arpad@68
|
261 $colDefs[] = $column.'=:'.$column;
|
|
arpad@94
|
262 //$colDefs[] = $column.'=IFNULL(:'.$column.','.$column.')';
|
|
arpad@68
|
263 }
|
|
arpad@68
|
264 $query.= implode(', ', $colDefs).' WHERE '.$this->primaryKey.'=:id;';
|
|
arpad@106
|
265 self::$statements[$childClass]['update'] = Core::$db->prepare($query);
|
|
arpad@68
|
266 }
|
|
arpad@68
|
267 $statement = self::$statements[$childClass]['update'];
|
|
arpad@68
|
268 $statement->bindValue(':id', $this->id, PDO::PARAM_INT);
|
|
arpad@68
|
269 }
|
|
arpad@68
|
270 foreach($this->definition as $column => $options)
|
|
arpad@68
|
271 {
|
|
arpad@68
|
272 switch($options)
|
|
arpad@68
|
273 {
|
|
arpad@98
|
274 case 'user':
|
|
arpad@121
|
275 if(!isset($this->data[$column]))
|
|
arpad@121
|
276 $this->data[$column] = Core::$user->id;
|
|
arpad@121
|
277 $statement->bindValue(':'.$column, $this->data[$column], PDO::PARAM_INT);
|
|
arpad@98
|
278 break;
|
|
arpad@68
|
279 case 'time':
|
|
arpad@121
|
280 if(!isset($this->data[$column]))
|
|
arpad@121
|
281 $this->data[$column] = time();
|
|
arpad@121
|
282 $statement->bindValue(':'.$column, $this->data[$column], PDO::PARAM_INT);
|
|
arpad@98
|
283 break;
|
|
arpad@98
|
284 case 'html':
|
|
arpad@121
|
285 $this->data[$column] = isset($this->data[$column]) ?
|
|
arpad@121
|
286 Utils::purify($this->data[$column]) : '';
|
|
arpad@121
|
287 $statement->bindValue(':'.$column, $this->data[$column], PDO::PARAM_STR);
|
|
arpad@68
|
288 break;
|
|
arpad@68
|
289 case 'required':
|
|
arpad@68
|
290 if(empty($this->data[$column]))
|
|
arpad@121
|
291 throw new Exception(printf(l10n::_('%s was empty'), $column));
|
|
arpad@121
|
292 $statement->bindValue(':'.$column, $this->data[$column], PDO::PARAM_STR);
|
|
arpad@98
|
293 break;
|
|
arpad@121
|
294 default:
|
|
arpad@121
|
295 if(!isset($this->data[$column]))
|
|
arpad@121
|
296 $this->data[$column] = '';
|
|
arpad@121
|
297 $statement->bindValue(':'.$column, $this->data[$column], PDO::PARAM_STR);
|
|
arpad@68
|
298 }
|
|
arpad@68
|
299 }
|
|
arpad@68
|
300 if(!$statement->execute())
|
|
arpad@173
|
301 throw new Exception($statement->errorInfo[2]);
|
|
arpad@173
|
302 if($aUseTransaction)
|
|
arpad@173
|
303 Core::$db->commit();
|
|
arpad@173
|
304 $return = empty($this->id) ? ($this->id = Core::$db->lastInsertId()) : true;
|
|
arpad@173
|
305 if($aUseTransaction)
|
|
arpad@173
|
306 Core::$db->commit();
|
|
arpad@173
|
307 return $return;
|
|
arpad@68
|
308 }
|
|
arpad@165
|
309 public function delete($aUseTransaction = false)
|
|
arpad@165
|
310 {
|
|
arpad@173
|
311 if($aUseTransaction)
|
|
arpad@175
|
312 Core::$db->beginTransaction();
|
|
arpad@68
|
313 $childClass = $this->className;
|
|
arpad@68
|
314 if(empty(self::$statements[$childClass]['delete']))
|
|
arpad@68
|
315 {
|
|
arpad@68
|
316 self::$statements[$childClass]['delete'] =
|
|
arpad@106
|
317 Core::$db->prepare('DELETE FROM '.$this->tableName.' WHERE '.
|
|
arpad@68
|
318 $this->primaryKey.'=:id;');
|
|
arpad@68
|
319 }
|
|
arpad@68
|
320 $statement = self::$statements[$childClass]['delete'];
|
|
arpad@68
|
321 $statement->bindValue(':id', $this->id, PDO::PARAM_INT);
|
|
arpad@94
|
322 if($return = $statement->execute())
|
|
arpad@174
|
323 {
|
|
arpad@174
|
324 Core::deleteEverythingFor($this);
|
|
arpad@94
|
325 $this->data = array();
|
|
arpad@94
|
326 unset($this->id);
|
|
arpad@94
|
327 }
|
|
arpad@173
|
328 if($aUseTransaction)
|
|
arpad@173
|
329 Core::$db->commit();
|
|
arpad@94
|
330 return $return;
|
|
arpad@112
|
331 }
|
|
arpad@112
|
332 // Maybe this is not the best place for toJSON()
|
|
arpad@165
|
333 public function toJSON($aEncode = true, $aIncludeChildren = false)
|
|
arpad@112
|
334 {
|
|
arpad@165
|
335 // TODO: make use of $aEncode and $aIncludeChildren
|
|
arpad@112
|
336 if(empty($this->id))
|
|
arpad@112
|
337 return 'false';
|
|
arpad@121
|
338 $displayArray = array('id' => $this->id);
|
|
arpad@125
|
339 foreach($this->definition as $column => $option)
|
|
arpad@125
|
340 {
|
|
arpad@125
|
341 if($option == 'user')
|
|
arpad@125
|
342 {
|
|
arpad@125
|
343 $user = Users::getUser($this->data[$column]);
|
|
arpad@125
|
344 $displayArray['user'] = array('id' => $user->id, 'nick' => $user->nick);
|
|
arpad@125
|
345 }
|
|
arpad@125
|
346 else
|
|
arpad@125
|
347 $displayArray[$column] = $this->data[$column];
|
|
arpad@125
|
348 }
|
|
arpad@112
|
349 return json_encode($displayArray);
|
|
arpad@68
|
350 }
|
|
arpad@68
|
351 }
|
|
arpad@94
|
352 ?>
|