Updating functions_datastore.php
[bugdar.git] / includes / class_sort.php
1 <?php
2 /*=====================================================================*\
3 || ###################################################################
4 || # Bugdar
5 || # Copyright ©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 ©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 $db;
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->db = BSApp::$db;
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 = BSApp::$input->in['by'];
95 if (!$this->fetch_by_text(BSApp::$input->in['by']))
96 {
97 $this->sortkey = (isset(bugdar::$userinfo['defaultsortkey']) ? bugdar::$userinfo['defaultsortkey'] : bugdar::$options['defaultsortkey']);
98 }
99
100 $this->direction = BSApp::$input->in['as'];
101 if (!in_array($this->direction, array('asc', 'desc')))
102 {
103 $this->direction = (isset(bugdar::$userinfo['defaultsortas']) ? bugdar::$userinfo['defaultsortas'] : bugdar::$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 = " . bugdar::$userinfo['userid'] . " AND bug.product IN (" . fetch_on_bits('canviewownhidden') . "))" : "") . ")" .
123 ((bugdar::$options['hidestatuses'] OR isset(bugdar::$userinfo['hidestatuses'])) ? "
124 AND bug.status NOT IN (" . (bugdar::$userinfo['hidestatuses'] != '' ? bugdar::$userinfo['hidestatuses'] : bugdar::$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 default:
184 if (substr($this->sortkey, 0, 6) != 'custom')
185 {
186 return;
187 }
188
189 $query = "
190 SELECT bug.*, vote.votefor, vote.voteagainst FROM " . TABLE_PREFIX . "bug AS bug
191 LEFT JOIN " . TABLE_PREFIX . "vote AS vote
192 ON (bug.bugid = vote.bugid)
193 WHERE $basewhere" .
194 (is_array($where) ? "
195 AND " . implode("\nAND ", $where) : "") . "
196 ORDER BY {$this->sortkey} " . strtoupper($this->direction) . ", " . $querykeys['lastpost'] . " " . strtoupper($this->direction) . ($limit ? "
197 LIMIT $limit" : "");
198 }
199
200 return $query;
201 }
202
203 // ###################################################################
204 /**
205 * Returns the display text for a given sort order key
206 *
207 * @access public static
208 *
209 * @param string Sort order key, or FALSE for the array
210 * @param bool Permission check the custom fields?
211 *
212 * @return mixed Display text if param is string, or array of all key=>text if param is NULL
213 */
214 function fetch_by_text($key, $doPerm = true)
215 {
216 global $bugsys;
217
218 $keys = array(
219 'lastpost' => T('Last Post Time'),
220 'bugid' => T('Bug ID'),
221 'summary' => T('Summary'),
222 'reporter' => T('Reporter'),
223 'product' => T('Product'),
224 'component' => T('Component'),
225 'version' => T('Version'),
226 'status' => T('Status'),
227 'resolution' => T('Resolution'),
228 'priority' => T('Priority'),
229 'severity' => T('Severity'),
230 'votes' => T('Votes'),
231 'assignedto' => T('Assigned To')
232 );
233
234 $fields = self::_fetchCustomFields($doPerm);
235 foreach ($fields AS $field)
236 {
237 $keys['custom' . $field['fieldid']] = $field['name'];
238 }
239
240 if ($key === false)
241 {
242 return $keys;
243 }
244 else
245 {
246 return $keys["$key"];
247 }
248 }
249
250 // ###################################################################
251 /**
252 * Returns the display text for a given sort order direction
253 *
254 * @access public static
255 *
256 * @param string Sort direction, or FALSE for the array
257 *
258 * @return mixed Display text if param is string, or array of all key=>text if param is NULL
259 */
260 function fetch_as_text($key)
261 {
262 global $lang;
263
264 $keys = array(
265 'desc' => T('Descending'),
266 'asc' => T('Ascending')
267 );
268
269 if ($key === false)
270 {
271 return $keys;
272 }
273 else
274 {
275 return $keys["$key"];
276 }
277 }
278
279 // ###################################################################
280 /**
281 * Returns a multi-dimensional array with sort by keys indexing arrays
282 * with 'image' and 'href' keys that store the values from
283 * fetch_sort_image() and fetch_sort_link(), respectively
284 *
285 * @access public
286 *
287 * @param string Extra GET parameters to pass to fetch_sort_link()
288 *
289 * @return array Array as described above
290 */
291 function fetch_display_array($params = null)
292 {
293 $return = $this->fetch_by_text(false);
294
295 foreach ($return AS $key => $nil)
296 {
297 $return["$key"] = array('image' => ($this->sortkey == $key ? $this->fetch_sort_image() : ''), 'href' => $this->fetch_sort_link($key, $params, true));
298 }
299
300 return $return;
301 }
302
303 // ###################################################################
304 /**
305 * Returns the entire <img> tag for the sort arrow
306 *
307 * @access public
308 *
309 * @return string HTML <img> tag
310 */
311 function fetch_sort_image()
312 {
313 return '<img src="templates/images/arrow_' . $this->fetch_sort_direction() . '.gif" alt="" style="vertical-align: top; border: none" />';
314 }
315
316 // ###################################################################
317 /**
318 * Returns the href value for an <a> tag by generating all the necessary
319 * bits and concat'ing it onto an extra string of GETs (optional)
320 *
321 * @access public
322 *
323 * @param string Sorting key
324 * @param string Additional GET parameters
325 * @param bool Highlight the current sortkey if that's passed?
326 *
327 * @return string HREF
328 */
329 function fetch_sort_link($key, $params = null, $highlight = false)
330 {
331 if ($params)
332 {
333 $params .= '&amp;';
334 }
335
336 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());
337 }
338
339 // ###################################################################
340 /**
341 * Returns the OPPOSITE direction to sort when you click on a link
342 *
343 * @access public
344 *
345 * @return string Either asc or desc
346 */
347 function fetch_opposite_sort_direction()
348 {
349 if ($this->direction == 'asc')
350 {
351 return 'desc';
352 }
353 else
354 {
355 return 'asc';
356 }
357 }
358
359 // ###################################################################
360 /**
361 * Returns the current sorted direction for the image path
362 *
363 * @access public
364 *
365 * @return string Either asc or desc
366 */
367 function fetch_sort_direction()
368 {
369 return $this->direction;
370 }
371
372 // ###################################################################
373 /**
374 * Returns the HTML code for bug listing table column headers
375 *
376 * @param bool Include the sort links/image?
377 * @param string Additional GET params to pass to fetch_sort_link()
378 *
379 * @return string HTML code
380 */
381 function constructColumnHeaders($sortable, $params = null)
382 {
383 $this->_processColumns();
384
385 $names = self::fetch_by_text(false);
386
387 $output = '';
388 foreach ($this->columns AS $columns)
389 {
390 $build = array();
391 foreach ($columns AS $column)
392 {
393 $build[] = ($sortable ? '<a href="' . $this->fetch_sort_link($column, $params, true) . '">' . $names[$column] . '</a>' : $names[$column]);
394 }
395 $image = ((in_array($this->sortkey, $columns) AND $sortable) ? $this->fetch_sort_image() : '');
396 $name = implode(' / ', $build);
397
398 $tpl = new BSTemplate('list_head');
399 $tpl->vars = array(
400 'name' => $name,
401 'image' => $image
402 );
403 $output .= $tpl->evaluate()->getTemplate();
404 }
405
406 return $output;
407 }
408
409 // ###################################################################
410 /**
411 * Returns the HTML code for a row of data for the bug listing
412 *
413 * @param array Bug data array
414 * @param string Additional link params
415 *
416 * @return string Row HTML
417 */
418 function constructRow($bug, $params = null)
419 {
420 global $bugsys;
421
422 $this->_processColumns();
423
424 foreach ($this->columns AS $columns)
425 {
426 if (sizeof($columns) > 1)
427 {
428 $build = array();
429 foreach ($columns AS $column)
430 {
431 $build[] = $this->_processDataForColumn($bug, $column, $params, true);
432 }
433 $data = "\n\t\t" . implode("\n\t\t", $build) . "\n\t";
434 }
435 else
436 {
437 $data = $this->_processDataForColumn($bug, $columns[0], $params, false);
438 }
439 $fields .= "\n\t<td>$data</td>";
440 }
441
442 $tpl = new BSTemplate('trackerhome_bits');
443 $tpl->vars = array(
444 'bug' => $bug,
445 'fields'=> $fields
446 );
447 return $tpl->evaluate()->getTemplate();
448 }
449
450 // ###################################################################
451 /**
452 * Handler for special-case column data
453 *
454 * @param array Bug data
455 * @param string Column name
456 * @param string Additional URL params
457 * @param bool Will this column have multiple data sets?
458 *
459 * @return string Processed column data
460 */
461 function _processDataForColumn($bug, $column, $params = null, $multi = false)
462 {
463 $open = ($multi ? '<div>' : '');
464 $close = ($multi ? '</div>' : '');
465 switch ($column)
466 {
467 case 'summary':
468 return $open . '<a href="showreport.php?bugid=' . $bug['bugid'] . $params . '">' . $bug['summary'] . '</a>' . $close;
469 case 'reporter':
470 return $open . ($bug['userid'] ? $bug['username'] : T('Guest')) . $close;
471 case 'lastpost':
472 return "\n\t\t<div>" . $bug['lastposttime'] . "</div>\n\t\t<div>" . T('by') . ' ' . ($bug['lastpost'] ? $bug['lastpost'] : T('Guest')) . "</div>\n\t";
473 case 'votes':
474 return "\n\t\t<div>" . T('For:') . ' ' . $bug['votefor'] . "</div>\n\t\t<div>" . T('Against:') . ' ' . $bug['voteagainst'] . "</div>\n\t";
475 default:
476 return $open . $bug["$column"] . $close;
477 }
478 }
479
480 // ###################################################################
481 /**
482 * Sets up $this->columns so that the data can be processed more
483 * easily
484 */
485 function _processColumns()
486 {
487 if (is_array($this->columns))
488 {
489 return;
490 }
491
492 $columns = self::fetch_by_text(false);
493
494 $array = ((bugdar::$userinfo['userid'] AND is_array(bugdar::$userinfo['columnoptions'])) ? bugdar::$userinfo['columnoptions'] : bugdar::$options['columnoptions']);
495
496 foreach ($array AS $column => $position)
497 {
498 // the column doesn't exist, or more likely, we don't have permission to view it
499 if (!isset($columns[$column]))
500 {
501 continue;
502 }
503 if ($position > 0)
504 {
505 $this->columns["$position"][] = $column;
506 }
507 }
508
509 ksort($this->columns);
510 }
511
512 // ###################################################################
513 /**
514 * Returns an array of all the custom fields that the current user
515 * has permission to use
516 *
517 * @param boolean Ignore permissions?
518 *
519 * @return array
520 */
521 private static function _fetchCustomFields($doPerm = true)
522 {
523 global $bugsys;
524 static $fields = array(), $fieldsPerm = array();
525
526 if ($doPerm AND !empty($fieldsPerm))
527 {
528 return $fieldsPerm;
529 }
530 else if (!$doPerm AND !empty($fields))
531 {
532 return $fields;
533 }
534
535 if ($doPerm)
536 {
537 $fields_fetch = BSApp::$db->query("
538 SELECT bugfield.*, MAX(permission.mask) AS mask
539 FROM " . TABLE_PREFIX . "bugfield AS bugfield
540 LEFT JOIN " . TABLE_PREFIX . "bugfieldpermission AS permission
541 ON (bugfield.fieldid = permission.fieldid)
542 WHERE (permission.mask = 2 OR permission.mask = 1)
543 AND permission.usergroupid IN (" . bugdar::$userinfo['usergroupid'] . (sizeof(bugdar::$userinfo['groupids']) != 0 ? ',' . implode(',', bugdar::$userinfo['groupids']) : '') . ")
544 GROUP BY (bugfield.fieldid)
545 ");
546 }
547 else
548 {
549 $fields_fetch = BSApp::$db->query("SELECT * FROM " . TABLE_PREFIX . "bugfield");
550 }
551
552 foreach ($fields_fetch as $field)
553 {
554 if ($doPerm)
555 {
556 $fieldsPerm[$field['fieldid']] = $field;
557 }
558 else
559 {
560 $fields[$field['fieldid']] = $field;
561 }
562 }
563 return $fields;
564 }
565 }
566
567 /*=====================================================================*\
568 || ###################################################################
569 || # $HeadURL$
570 || # $Id$
571 || ###################################################################
572 \*=====================================================================*/
573 ?>