Fix DB_MySQL_PDO::escape_binary().
[bugdar.git] / includes / class_sort.php
1 <?php
2 /*=====================================================================*\
3 || ###################################################################
4 || # Bugdar
5 || # Copyright (c)2002-2007 Blue Static
6 || #
7 || # This program is free software; you can redistribute it and/or modify
8 || # it under the terms of the GNU General Public License as published by
9 || # the Free Software Foundation; version 2 of the License.
10 || #
11 || # This program is distributed in the hope that it will be useful, but
12 || # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 || # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 || # more details.
15 || #
16 || # You should have received a copy of the GNU General Public License along
17 || # with this program; if not, write to the Free Software Foundation, Inc.,
18 || # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
19 || ###################################################################
20 \*=====================================================================*/
21
22 /**
23 * Bug Listing Sorter
24 *
25 * This class is used to sort bugs based on user-sent options and variables.
26 *
27 * @author Blue Static
28 * @copyright Copyright (c)2002 - 2007, Blue Static
29 * @version $Revision$
30 * @package Bugdar
31 *
32 */
33 class ListSorter
34 {
35 /**
36 * Bugsys registry
37 * @var object
38 * @access private
39 */
40 var $registry;
41
42 /**
43 * Page name
44 * @var string
45 * @access public
46 */
47 var $page = '';
48
49 /**
50 * Current sort key
51 * @var string
52 * @access private
53 */
54 var $sortkey = '';
55
56 /**
57 * Current sort direction
58 * @var string
59 * @access private
60 */
61 var $direction = '';
62
63 /**
64 * Column array for table heads
65 * @var array
66 */
67 var $columns;
68
69 // ###################################################################
70 /**
71 * Constructor: set the page name
72 *
73 * @access public
74 *
75 * @param string File name without the .php extension
76 */
77 function ListSorter($page)
78 {
79 global $bugsys;
80 $this->registry = $bugsys;
81 $this->page = $page;
82 $this->process_incoming();
83 }
84
85 // ###################################################################
86 /**
87 * Processes the incoming variables and then sets all the sort order
88 * information appropriately
89 *
90 * @access private
91 */
92 function process_incoming()
93 {
94 $this->sortkey = $this->registry->in['by'];
95 if (!$this->fetch_by_text($this->registry->in['by']))
96 {
97 $this->sortkey = (isset($this->registry->userinfo['defaultsortkey']) ? $this->registry->userinfo['defaultsortkey'] : $this->registry->options['defaultsortkey']);
98 }
99
100 $this->direction = $this->registry->in['as'];
101 if (!in_array($this->direction, array('asc', 'desc')))
102 {
103 $this->direction = (isset($this->registry->userinfo['defaultsortas']) ? $this->registry->userinfo['defaultsortas'] : $this->registry->options['defaultsortas']);
104 }
105 }
106
107 // ###################################################################
108 /**
109 * Fetch a SQL query to gather bugs with the sort filters applied
110 *
111 * @access public
112 *
113 * @param string Additional WHERE clauses in an array
114 * @param string A LIMIT clause
115 *
116 * @return string Compiled SQL query
117 */
118 function fetch_sql_query($where = null, $limit = null)
119 {
120 // this WHERE clause is used for all the queries
121 $basewhere = "bug.product IN (" . fetch_on_bits('canviewbugs') . ")
122 AND (!bug.hidden OR (bug.hidden AND bug.product IN (" . fetch_on_bits('canviewhidden') . "))" . (can_perform('canviewownhidden') ? " OR (bug.hidden AND bug.userid = " . $this->registry->userinfo['userid'] . " AND bug.product IN (" . fetch_on_bits('canviewownhidden') . "))" : "") . ")" .
123 (($this->registry->options['hidestatuses'] OR isset($this->registry->userinfo['hidestatuses'])) ? "
124 AND bug.status NOT IN (" . ($this->registry->userinfo['hidestatuses'] != '' ? $this->registry->userinfo['hidestatuses'] : $this->registry->options['hidestatuses']) . ")" : "");
125
126 // remap the sort keys to be actual SQL fields
127 $querykeys = array(
128 'bugid' => 'bugid',
129 'summary' => 'summary',
130 'reporter' => 'userid',
131 'lastpost' => (can_perform('canviewhidden') ? "lastposttime" : "hiddenlastposttime"),
132 'assignedto'=> 'assignedto'
133 );
134
135 switch ($this->sortkey)
136 {
137 case 'bugid':
138 case 'summary':
139 case 'reporter':
140 case 'lastpost':
141 case 'assignedto':
142 $query = "
143 SELECT bug.*, vote.votefor, vote.voteagainst FROM " . TABLE_PREFIX . "bug AS bug
144 LEFT JOIN " . TABLE_PREFIX . "vote AS vote
145 ON (bug.bugid = vote.bugid)
146 WHERE $basewhere" .
147 (is_array($where) ? "
148 AND " . implode("\nAND ", $where) : "") . "
149 ORDER BY " . $querykeys[ $this->sortkey ] . " " . strtoupper($this->direction) . ($this->sortkey != 'lastpost' ? ", " . $querykeys['lastpost'] . " " . strtoupper($this->direction) : "") . ($limit ? "
150 LIMIT $limit" : "");
151 break;
152 case 'product':
153 case 'component':
154 case 'version':
155 case 'status':
156 case 'resolution':
157 case 'priority':
158 case 'severity':
159 $key = ($this->sortkey != 'component' ? $this->sortkey : 'product');
160 $query = "
161 SELECT $key.*, bug.*, vote.votefor, vote.voteagainst FROM " . TABLE_PREFIX . "$key AS $key
162 RIGHT JOIN " . TABLE_PREFIX . "bug AS bug
163 ON (bug.$key = $key.{$key}id)
164 LEFT JOIN " . TABLE_PREFIX . "vote AS vote
165 ON (bug.bugid = vote.bugid)
166 WHERE $basewhere" .
167 (is_array($where) ? "
168 AND " . implode("\nAND ", $where) : "") . "
169 ORDER BY $key.displayorder " . strtoupper($this->direction) . ", bug.$querykeys[lastpost] " . strtoupper($this->direction) . ($limit ? "
170 LIMIT $limit" : "");
171 break;
172 case 'votes':
173 $query = "
174 SELECT bug.*, vote.votefor, vote.voteagainst FROM " . TABLE_PREFIX . "bug AS bug
175 LEFT JOIN " . TABLE_PREFIX . "vote AS vote
176 ON (bug.bugid = vote.bugid)
177 WHERE $basewhere" .
178 (is_array($where) ? "
179 AND " . implode("\nAND ", $where) : "") . "
180 ORDER BY vote.votefor " . strtoupper($this->direction) . ", vote.voteagainst " . strtoupper($this->fetch_opposite_sort_direction()) . ", bug.$querykeys[lastpost] " . strtoupper($this->direction) . ($limit ? "
181 LIMIT $limit" : "");
182 break;
183 }
184
185 return $query;
186 }
187
188 // ###################################################################
189 /**
190 * Returns the display text for a given sort order key
191 *
192 * @access public static
193 *
194 * @param string Sort order key, or FALSE for the array
195 *
196 * @return mixed Display text if param is string, or array of all key=>text if param is NULL
197 */
198 function fetch_by_text($key)
199 {
200 global $lang;
201
202 $keys = array(
203 'lastpost' => T('Last Post Time'),
204 'bugid' => T('Bug ID'),
205 'summary' => T('Summary'),
206 'reporter' => T('Reporter'),
207 'product' => T('Product'),
208 'component' => T('Component'),
209 'version' => T('Version'),
210 'status' => T('Status'),
211 'resolution' => T('Resolution'),
212 'priority' => T('Priority'),
213 'severity' => T('Severity'),
214 'votes' => T('Votes'),
215 'assignedto' => T('Assigned To')
216 );
217
218 if ($key === false)
219 {
220 return $keys;
221 }
222 else
223 {
224 return $keys["$key"];
225 }
226 }
227
228 // ###################################################################
229 /**
230 * Returns the display text for a given sort order direction
231 *
232 * @access public static
233 *
234 * @param string Sort direction, or FALSE for the array
235 *
236 * @return mixed Display text if param is string, or array of all key=>text if param is NULL
237 */
238 function fetch_as_text($key)
239 {
240 global $lang;
241
242 $keys = array(
243 'desc' => T('Descending'),
244 'asc' => T('Ascending')
245 );
246
247 if ($key === false)
248 {
249 return $keys;
250 }
251 else
252 {
253 return $keys["$key"];
254 }
255 }
256
257 // ###################################################################
258 /**
259 * Returns a multi-dimensional array with sort by keys indexing arrays
260 * with 'image' and 'href' keys that store the values from
261 * fetch_sort_image() and fetch_sort_link(), respectively
262 *
263 * @access public
264 *
265 * @param string Extra GET parameters to pass to fetch_sort_link()
266 *
267 * @return array Array as described above
268 */
269 function fetch_display_array($params = null)
270 {
271 $return = $this->fetch_by_text(false);
272
273 foreach ($return AS $key => $nil)
274 {
275 $return["$key"] = array('image' => ($this->sortkey == $key ? $this->fetch_sort_image() : ''), 'href' => $this->fetch_sort_link($key, $params, true));
276 }
277
278 return $return;
279 }
280
281 // ###################################################################
282 /**
283 * Returns the entire <img> tag for the sort arrow
284 *
285 * @access public
286 *
287 * @return string HTML <img> tag
288 */
289 function fetch_sort_image()
290 {
291 return '<img src="templates/images/arrow_' . $this->fetch_sort_direction() . '.gif" alt="" style="vertical-align: top; border: none" />';
292 }
293
294 // ###################################################################
295 /**
296 * Returns the href value for an <a> tag by generating all the necessary
297 * bits and concat'ing it onto an extra string of GETs (optional)
298 *
299 * @access public
300 *
301 * @param string Sorting key
302 * @param string Additional GET parameters
303 * @param bool Highlight the current sortkey if that's passed?
304 *
305 * @return string HREF
306 */
307 function fetch_sort_link($key, $params = null, $highlight = false)
308 {
309 if ($params)
310 {
311 $params .= '&amp;';
312 }
313
314 return $this->page . '.php?' . $params . 'by=' . $key . '&amp;as=' . (($this->sortkey == $key AND $highlight) ? $this->fetch_opposite_sort_direction() . '" class="select' : $this->fetch_sort_direction());
315 }
316
317 // ###################################################################
318 /**
319 * Returns the OPPOSITE direction to sort when you click on a link
320 *
321 * @access public
322 *
323 * @return string Either asc or desc
324 */
325 function fetch_opposite_sort_direction()
326 {
327 if ($this->direction == 'asc')
328 {
329 return 'desc';
330 }
331 else
332 {
333 return 'asc';
334 }
335 }
336
337 // ###################################################################
338 /**
339 * Returns the current sorted direction for the image path
340 *
341 * @access public
342 *
343 * @return string Either asc or desc
344 */
345 function fetch_sort_direction()
346 {
347 return $this->direction;
348 }
349
350 // ###################################################################
351 /**
352 * Returns the HTML code for bug listing table column headers
353 *
354 * @param bool Include the sort links/image?
355 * @param string Additional GET params to pass to fetch_sort_link()
356 *
357 * @return string HTML code
358 */
359 function constructColumnHeaders($sortable, $params = null)
360 {
361 $this->_processColumns();
362
363 $output = '';
364 foreach ($this->columns AS $columns)
365 {
366 $build = array();
367 foreach ($columns AS $column)
368 {
369 $build[] = ($sortable ? '<a href="' . $this->fetch_sort_link($column, $params, true) . '">' . $this->registry->columnNames["$column"] . '</a>' : $this->registry->columnNames["$column"]);
370 }
371 $image = ((in_array($this->sortkey, $columns) AND $sortable) ? $this->fetch_sort_image() : '');
372 $name = implode(' / ', $build);
373 eval('$output .= "' . $this->registry->template->fetch('list_head') . '";');
374 }
375
376 return $output;
377 }
378
379 // ###################################################################
380 /**
381 * Returns the HTML code for a row of data for the bug listing
382 *
383 * @param array Bug data array
384 * @param string Additional link params
385 *
386 * @return string Row HTML
387 */
388 function constructRow($bug, $params = null)
389 {
390 global $bugsys;
391
392 $this->_processColumns();
393
394 foreach ($this->columns AS $columns)
395 {
396 if (sizeof($columns) > 1)
397 {
398 $build = array();
399 foreach ($columns AS $column)
400 {
401 $build[] = $this->_processDataForColumn($bug, $column, $params, true);
402 }
403 $data = "\n\t\t" . implode("\n\t\t", $build) . "\n\t";
404 }
405 else
406 {
407 $data = $this->_processDataForColumn($bug, $columns[0], $params, false);
408 }
409 $fields .= "\n\t<td>$data</td>";
410 }
411
412 eval('$output = "' . $this->registry->template->fetch('trackerhome_bits') . '";');
413 return $output;
414 }
415
416 // ###################################################################
417 /**
418 * Handler for special-case column data
419 *
420 * @param array Bug data
421 * @param string Column name
422 * @param string Additional URL params
423 * @param bool Will this column have multiple data sets?
424 *
425 * @return string Processed column data
426 */
427 function _processDataForColumn($bug, $column, $params = null, $multi = false)
428 {
429 $open = ($multi ? '<div>' : '');
430 $close = ($multi ? '</div>' : '');
431 switch ($column)
432 {
433 case 'summary':
434 return $open . '<a href="showreport.php?bugid=' . $bug['bugid'] . $params . '">' . $bug['summary'] . '</a>' . $close;
435 case 'reporter':
436 return $open . ($bug['userid'] ? $bug['username'] : T('Guest')) . $close;
437 case 'lastpost':
438 return "\n\t\t<div>" . $bug['lastposttime'] . "</div>\n\t\t<div>" . T('by') . ' ' . ($bug['lastpost'] ? $bug['lastpost'] : T('Guest')) . "</div>\n\t";
439 case 'votes':
440 return "\n\t\t<div>" . T('For:') . ' ' . $bug['votefor'] . "</div>\n\t\t<div>" . T('Against:') . ' ' . $bug['voteagainst'] . "</div>\n\t";
441 default:
442 return $open . $bug["$column"] . $close;
443 }
444 }
445
446 // ###################################################################
447 /**
448 * Sets up $this->columns so that the data can be processed more
449 * easily
450 */
451 function _processColumns()
452 {
453 if (is_array($this->columns))
454 {
455 return;
456 }
457
458 $array = (($this->registry->userinfo['userid'] AND is_array($this->registry->userinfo['columnoptions'])) ? $this->registry->userinfo['columnoptions'] : $this->registry->options['columnoptions']);
459
460 foreach ($array AS $column => $position)
461 {
462 if ($position > 0)
463 {
464 $this->columns["$position"][] = $column;
465 }
466 }
467
468 ksort($this->columns);
469 }
470 }
471
472