Fixes PlSet (orders weren't working)
[platal.git] / classes / plfilter.php
1 <?php
2 /***************************************************************************
3 * Copyright (C) 2003-2010 Polytechnique.org *
4 * http://opensource.polytechnique.org/ *
5 * *
6 * This program is free software; you can redistribute it and/or modify *
7 * it under the terms of the GNU General Public License as published by *
8 * the Free Software Foundation; either version 2 of the License, or *
9 * (at your option) any later version. *
10 * *
11 * This program is distributed in the hope that it will be useful, *
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 * GNU General Public License for more details. *
15 * *
16 * You should have received a copy of the GNU General Public License *
17 * along with this program; if not, write to the Free Software *
18 * Foundation, Inc., *
19 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
20 ***************************************************************************/
21
22 // {{{ class PlLimit
23 class PlLimit
24 {
25 private $count = null;
26 private $from = null;
27
28 public function __construct($count = null, $from = null)
29 {
30 $this->count = $count;
31 $this->from = $from;
32 }
33
34 public function getSql()
35 {
36 if (!is_null($this->count) && $this->count != 0) {
37 if (!is_null($this->from) && $this->from != 0) {
38 return XDB::format('LIMIT {?}, {?}', (int)$this->from, (int)$this->count);
39 } else {
40 return XDB::format('LIMIT {?}', (int)$this->count);
41 }
42 }
43 return '';
44 }
45 }
46 // }}}
47
48 // {{{ class PlSqlJoin
49 class PlSqlJoin
50 {
51 private $mode;
52 private $table;
53 private $condition;
54
55 const MODE_LEFT = 'LEFT';
56 const MODE_RIGHT = 'RIGHT';
57 const MODE_INNER = 'INNER';
58
59 public function __construct($mode, $table, $condition)
60 {
61 if ($mode != self::MODE_LEFT && $mode != self::MODE_RIGHT && $mode != self::MODE_INNER) {
62 Platal::page()->kill("Join mode error: unknown mode $mode");
63 return;
64 }
65 $this->mode = $mode;
66 $this->table = $table;
67 $this->condition = $condition;
68 }
69
70 public function mode()
71 {
72 return $this->mode;
73 }
74
75 public function table()
76 {
77 return $this->table;
78 }
79
80 public function condition()
81 {
82 return $this->condition;
83 }
84
85 /** Replace all "metas" in the condition with their translation.
86 * $ME always becomes the alias of the table
87 * @param $key The name the joined table will have in the final query
88 * @param $joinMetas An array of meta => conversion to apply to the condition
89 */
90 public function replaceJoinMetas($key, $joinMetas = array())
91 {
92 $joinMetas['$ME'] = $key;
93 return str_replace(array_keys($joinMetas), $joinMetas, $this->condition);
94 }
95
96 /** Create a join command from an array of PlSqlJoin
97 * @param $joins The list of 'join' to convert into an SQL query
98 * @param $joinMetas An array of ('$META' => 'conversion') to apply to the joins.
99 */
100 public static function formatJoins(array $joins, array $joinMetas)
101 {
102 $str = '';
103 foreach ($joins as $key => $join) {
104 if (!($join instanceof PlSqlJoin)) {
105 Platal::page()->kill("Error: not a join: $join");
106 }
107 $mode = $join->mode();
108 $table = $join->table();
109 $str .= ' ' . $mode . ' JOIN ' . $table . ' AS ' . $key;
110 if ($join->condition() != null) {
111 $str .= ' ON (' . $join->replaceJoinMetas($key, $joinMetas) . ')';
112 }
113 $str .= "\n";
114 }
115 return $str;
116 }
117 }
118 // }}}
119
120 // {{{ class PlFilterOrder
121 abstract class PlFilterOrder
122 {
123 protected $desc = false;
124 public function __construct($desc = false)
125 {
126 $this->desc = $desc;
127 }
128
129 public function toggleDesc()
130 {
131 $this->desc = !$desc;
132 }
133
134 public function setDescending($desc = true)
135 {
136 $this->desc = $desc;
137 }
138
139 public function buildSort(PlFilter &$pf)
140 {
141 $sel = $this->getSortTokens($pf);
142 if (!is_array($sel)) {
143 $sel = array($sel);
144 }
145 if ($this->desc) {
146 foreach ($sel as $k => $s) {
147 $sel[$k] = $s . ' DESC';
148 }
149 }
150 return $sel;
151 }
152
153 abstract protected function getSortTokens(PlFilter &$pf);
154 }
155 // }}}
156
157 // {{{ class PFO_Random
158 class PFO_Random extends PlFilterOrder
159 {
160 private $seed = null;
161
162 public function __construct($seed = null, $desc = false)
163 {
164 parent::__construct($desc);
165 $this->seed = $seed;
166 }
167
168 protected function getSortTokens(PlFilter &$pf)
169 {
170 if ($this->seed == null) {
171 return 'RAND()';
172 } else {
173 return XDB::format('RAND({?})', $this->seed);
174 }
175 }
176 }
177 // }}}
178
179 // {{{ interface PlFilterCondition
180 interface PlFilterCondition
181 {
182 const COND_TRUE = 'TRUE';
183 const COND_FALSE = 'FALSE';
184
185 public function buildCondition(PlFilter &$pf);
186 }
187 // }}}
188
189 // {{{ class PFC_OneChild
190 abstract class PFC_OneChild implements PlFilterCondition
191 {
192 protected $child;
193
194 public function __construct(&$child = null)
195 {
196 if (!is_null($child) && ($child instanceof PlFilterCondition)) {
197 $this->setChild($child);
198 }
199 }
200
201 public function setChild(PlFilterCondition &$cond)
202 {
203 $this->child =& $cond;
204 }
205 }
206 // }}}
207
208 // {{{ class PFC_NChildren
209 abstract class PFC_NChildren implements PlFilterCondition
210 {
211 protected $children = array();
212
213 public function __construct()
214 {
215 $children = func_get_args();
216 foreach ($children as &$child) {
217 if (!is_null($child) && ($child instanceof PlFilterCondition)) {
218 $this->addChild($child);
219 }
220 }
221 }
222
223 public function addChild(PlFilterCondition &$cond)
224 {
225 $this->children[] =& $cond;
226 }
227
228 protected function catConds(array $cond, $op, $fallback)
229 {
230 if (count($cond) == 0) {
231 return $fallback;
232 } else if (count($cond) == 1) {
233 return $cond[0];
234 } else {
235 return '(' . implode(') ' . $op . ' (', $cond) . ')';
236 }
237 }
238 }
239 // }}}
240
241 // {{{ class PFC_True
242 class PFC_True implements PlFilterCondition
243 {
244 public function buildCondition(PlFilter &$uf)
245 {
246 return self::COND_TRUE;
247 }
248 }
249 // }}}
250
251 // {{{ class PFC_False
252 class PFC_False implements PlFilterCondition
253 {
254 public function buildCondition(PlFilter &$uf)
255 {
256 return self::COND_FALSE;
257 }
258 }
259 // }}}
260
261 // {{{ class PFC_Not
262 class PFC_Not extends PFC_OneChild
263 {
264 public function buildCondition(PlFilter &$uf)
265 {
266 $val = $this->child->buildCondition($uf);
267 if ($val == self::COND_TRUE) {
268 return self::COND_FALSE;
269 } else if ($val == self::COND_FALSE) {
270 return self::COND_TRUE;
271 } else {
272 return 'NOT (' . $val . ')';
273 }
274 }
275 }
276 // }}}
277
278 // {{{ class PFC_And
279 class PFC_And extends PFC_NChildren
280 {
281 public function buildCondition(PlFilter &$uf)
282 {
283 if (empty($this->children)) {
284 return self::COND_FALSE;
285 } else {
286 $true = self::COND_FALSE;
287 $conds = array();
288 foreach ($this->children as &$child) {
289 $val = $child->buildCondition($uf);
290 if ($val == self::COND_TRUE) {
291 $true = self::COND_TRUE;
292 } else if ($val == self::COND_FALSE) {
293 return self::COND_FALSE;
294 } else {
295 $conds[] = $val;
296 }
297 }
298 return $this->catConds($conds, 'AND', $true);
299 }
300 }
301 }
302 // }}}
303
304 // {{{ class PFC_Or
305 class PFC_Or extends PFC_NChildren
306 {
307 public function buildCondition(PlFilter &$uf)
308 {
309 if (empty($this->children)) {
310 return self::COND_TRUE;
311 } else {
312 $true = self::COND_TRUE;
313 $conds = array();
314 foreach ($this->children as &$child) {
315 $val = $child->buildCondition($uf);
316 if ($val == self::COND_TRUE) {
317 return self::COND_TRUE;
318 } else if ($val == self::COND_FALSE) {
319 $true = self::COND_FALSE;
320 } else {
321 $conds[] = $val;
322 }
323 }
324 return $this->catConds($conds, 'OR', $true);
325 }
326 }
327 }
328 // }}}
329
330 // {{{ class PlFilter
331 abstract class PlFilter
332 {
333 /** Filters objects matching the PlFilter
334 * @param $objects The objects to filter
335 * @param $limit The portion of the matching objects to show
336 */
337 public abstract function filter(array $objects, PlLimit &$limit);
338
339 public abstract function setCondition(PlFilterCondition &$cond);
340
341 public abstract function addSort(PlFilterOrder &$sort);
342
343 public abstract function getTotalCount();
344
345 /** Get objects, selecting only those within a limit
346 * @param $limit The portion of the matching objects to select
347 */
348 public abstract function get(PlLimit &$limit);
349
350 /** PRIVATE FUNCTIONS
351 */
352
353 /** List of metas to replace in joins:
354 * '$COIN' => 'pan.x' means 'replace $COIN with pan.x in the condition of the joins'
355 *
356 * "$ME" => "joined table alias" is always added to these.
357 */
358 protected $joinMetas = array();
359
360 protected $joinMethods = array();
361
362 /** Build the 'join' part of the query
363 * This function will call all methods declared in self::$joinMethods
364 * to get an array of PlSqlJoin objects to merge
365 */
366 protected function buildJoins()
367 {
368 $joins = array();
369 foreach ($this->joinMethods as $method) {
370 $joins = array_merge($joins, $this->$method());
371 }
372 return PlSqlJoin::formatJoins($joins, $this->joinMetas);
373 }
374
375 }
376 // }}}
377
378 ?>