Dont display sort by author when searching by author (feature request #0000910)
3 /* Reminder: always indent with 4 spaces (no tabs). */
4 // +---------------------------------------------------------------------------+
6 // +---------------------------------------------------------------------------+
7 // | listfactory.class.php |
9 // | This class allows personalised lists or tables to be easily generated |
10 // | from arrays or SQL statements. It will also supports the sorting and |
11 // | paging of results. |
12 // +---------------------------------------------------------------------------+
13 // | Copyright (C) 2000-2009 by the following authors: |
15 // | Authors: Sami Barakat - s.m.barakat AT gmail DOT com |
16 // +---------------------------------------------------------------------------+
18 // | This program is free software; you can redistribute it and/or |
19 // | modify it under the terms of the GNU General Public License |
20 // | as published by the Free Software Foundation; either version 2 |
21 // | of the License, or (at your option) any later version. |
23 // | This program is distributed in the hope that it will be useful, |
24 // | but WITHOUT ANY WARRANTY; without even the implied warranty of |
25 // | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
26 // | GNU General Public License for more details. |
28 // | You should have received a copy of the GNU General Public License |
29 // | along with this program; if not, write to the Free Software Foundation, |
30 // | Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
32 // +---------------------------------------------------------------------------+
34 if (strpos(strtolower($_SERVER['PHP_SELF']), 'listfactory.class.php') !== false) {
35 die('This file can not be used on its own.');
40 // Initiate an instance of the class with the URL of the current page
41 $url = $_SERVER['PHP_SELF'];
42 $obj = new ListFactory($url);
44 // Set up some hidden fields that will be used to help format the data later on
45 $obj->setField('ID', 'id', false);
47 // Set up the fields that will be seen by the user
49 '#', // Title of the field
50 LF_ROW_NUMBER, // The field identifier can be either:
51 // LF_ROW_NUMBER - The number of each row will be displayed
52 // LF_SOURCE_TITLE - The title given the the SQL query will be displayed
53 // <string> - SQL column name
54 true, // Enables the field
55 true, // The field can be sorted
56 '<b>%d.</b>' // Formats the data
58 $obj->setField('Type', LF_SOURCE_TITLE, true, true, '<b>%s</b>');
59 $obj->setField('Title', 'title');
60 $obj->setField('Text', 'text');
61 $obj->setField('Date', 'date');
63 // Set the default field to sort by
64 $obj->setDefaultSort('date');
66 // Set the style of output
67 $obj->setStyle('table');
69 // Sets the call back function to add any extra formatting to the fields
70 $obj->setRowFunction('test_list_func');
72 // Set up some queries to execute
73 $sql = 'SELECT sid AS id, title, introtext AS text, date FROM stories';
75 'Story', // The name given to the query which will be displayed in the LF_SOURCE_TITLE field (optional)
77 $sql, // The SQL string without the LIMIT or ORDER BY clauses. Notice the column names match the field identifiers
78 5 // The rank of the query, 5 highest = more results, 1 lowest = least results
80 $sql = 'SELECT cid AS id, title, comment AS text, date FROM comments';
81 $obj->setQuery('Comment', 'comment', $sql, 2);
83 // Append some extra rows to the output
84 // Note: the array must match the field identifier names stated previously
87 LF_SOURCE_TITLE => 'Extra Row',
88 'title' => 'An extra row example',
89 'text' => 'With some really really really long text.....<b>and HTML</b>',
90 'date' => '2008-07-08 03:00:00'
92 // Add the extra row, notice it is not automatically passed to the row function
93 $obj->addResult($extra_row);
95 // Prints out the list
96 $results = $obj->ExecuteQueries();
97 $title = 'Test ListFactory';
98 $text = 'Showing %d - %d of %d results.';
99 $retval = $obj->getFormattedOutput($results, $title, $text);
102 // This function is called by the ListFactory to provide furthur formatting of the results.
103 function test_list_func($preSort, $row)
107 // extract any further information from the results.
108 // such as converting user ID's to user names
112 // Create a link from the title and id
113 $row['title'] = '<a href="http://www.geeklog.net/list_test.php?id='.$row['id'].'">'.$row['title'].'</a>';
115 // Shorten the text and strip any HTML tags
116 $row['text'] = substr(strip_tags($row['text']), 0, 20);
119 // Return the reformatted row
126 * Geeklog List Factory Class
128 * @author Sami Barakat, s.m.barakat AT gmail DOT com
134 var $_fields = array();
135 var $_sources_arr = array();
136 var $_total_rank = 0;
137 var $_sort_arr = array();
138 var $_def_sort_arr = array();
141 var $_page_limits = array();
143 var $_preset_rows = array();
145 var $_style = 'table';
150 * Sets up private url variable and defines the
151 * LF_SOURCE_TITLE, LF_SOURCE_NAME and LF_ROW_NUMBER constants.
154 * @param string $url The URL of the page the table appears on
155 * @param array $limits The avaliable page limits
156 * @param int $per_page The default number or rows per page
159 function ListFactory( $url, $limits = '10,15,20,25,30,35', $per_page = 20 )
161 $url .= (strpos($url,'?') === false ? '?' : '&');
162 $this->_page_url = $url;
163 $this->_style = 'table';
164 $this->_per_page = $per_page;
166 if (is_string($limits)) {
167 $this->_page_limits = explode(',', $limits);
168 } else if (is_array($limits)) {
169 $this->_page_limits = $limits;
171 $this->_page_limits = array(10, 15, 20, 25, 30, 35);
174 define('LF_SOURCE_TITLE', 0);
175 define('LF_SOURCE_NAME', 1);
176 define('LF_ROW_NUMBER', 2);
180 * Determins which set of templates to load when formatting the output
183 * @param string $style Either 'table' or 'inline'
186 function setStyle( $style )
188 $this->_style = $style;
192 * Sets a field in the list.
194 * Note: LF_ROW_NUMBER cannot be sorted
197 * @param string $title The title of the field which is displayed to the user
198 * @param string $name The local name given to the field
199 * @param boolean $display True if the field is to be displayed to the user otherwise false
200 * @param boolean $sort True if the field can be sorted otherwise false
201 * @param string $format The format string with one type specifier
204 function setField( $title, $name, $display = true, $sort = true, $format = '%s' )
206 if ($name === LF_ROW_NUMBER) {
209 $this->_fields[] = array(
212 'display' => $display,
219 * Sets the SQL query that will generate rows
222 * @param string $title The text that's displayed to the user
223 * @param string $name The local name given to the query
224 * @param string $sql The SQL string without the ORDER BY or LIMIT clauses
225 * @param int $rank The rating that determins how many results will be returned
228 function setQuery( $title, $name, $sql, $rank )
230 $this->_sources_arr[] = array(
237 $this->_total_rank += $rank;
241 * Sets a callback function that provides another source for results.
243 * The function will be passed two parameters, $offset and $limit,
244 * which will determine how many results are requested. The callback
245 * function should then return a multidimensional array containing
246 * the results. This provides an alternative to the setQuery()
247 * function as results can be sourced from anywhere.
250 * @param string $title The text that's displayed to the user
251 * @param string $name The local name given to the query
252 * @param string $function Any callable function, method or lambda
253 * @param int $rank The rating that determins how many results will be returned
254 * @param int $total The total number of results that are avaliable
257 function setCallback( $title, $name, $callback, $rank, $total )
259 $this->_sources_arr[] = array(
260 'type' => 'callback',
267 $this->_total_rank += $rank;
271 * Sets the callback function thats called on every row for styling
275 * @param callback $function Any callable function, method or lambda
278 function setRowFunction( $callback )
280 $this->_function = $callback;
284 * Sets the default sort field
287 * @param string $field The field name to sort
288 * @param string $direction 'asc' for ascending order and 'desc' for descending order
291 function setDefaultSort( $field, $direction = 'desc' )
293 $this->_def_sort_arr = array('field' => $field, 'direction' => $direction);
297 * Appends a single result to the list
300 * @param array $result A single result that will be appended to the rest
303 function addResult( $result )
305 $this->_preset_rows[] = $result;
309 * Appends several results to the list
312 * @param array $result An array of result that will be appended to the rest
315 function addResultArray( $arr )
317 $this->_preset_rows = array_merge($this->_preset_rows, $arr);
321 * Gets the total number of results from source item, either an sql
322 * query or a callback function.
325 * @param array $source The source we are currently working with
326 * @return int Total number of rows
329 function _getTotal( $source )
331 if ($source['type'] == 'callback') {
332 return $source['total'];
335 $sql = $source['sql'];
338 if (is_array($sql)) {
339 $sql['mysql'] = preg_replace('/SELECT.*FROM/is', 'SELECT COUNT(*) FROM', $sql['mysql']);
340 $sql['mssql'] = preg_replace('/SELECT.*FROM/is', 'SELECT COUNT(*) FROM', $sql['mssql']);
343 $sql = preg_replace('/SELECT.*FROM/is', 'SELECT COUNT(*) FROM', $sql);
345 $result = DB_query($sql);
346 $num_rows = DB_numRows($result);
347 if ($num_rows <= 1) {
348 $B = DB_fetchArray($result, true);
351 return $num_rows ? $num_rows : 0;
355 * Calculates the offset and limits for each query based on
356 * the number of rows to be displayed per query per page.
359 * @param array $totals The total number of results per query
360 * @return array The offsets and limits for a given page
363 function _getLimits( $totals )
365 $order = range(0, count($totals)-1);
366 array_multisort($totals, $order);
367 $fin = array('total' => 0, 'offset' => 0, 'limit' => 0);
368 $fin = array_fill(0, count($totals), $fin);
370 for ($p = 0; $p < $this->_page; $p++)
373 for ($q = 0; $q < count($totals); $q++)
375 $fin[$q]['offset'] = $fin[$q]['offset'] + $fin[$q]['limit'];
376 $extra_pp = $extra + $totals[$q]['pp'];
377 if ($extra_pp - $totals[$q]['total'] >= 0)
379 $fin[$q]['limit'] = $totals[$q]['total'];
380 $extra = $extra_pp - $totals[$q]['total'];
381 $totals[$q]['total'] = 0;
383 else if ($totals[$q]['total'] - $extra_pp >= 0)
385 $fin[$q]['limit'] = $extra_pp;
386 $totals[$q]['total'] = $totals[$q]['total'] - $extra_pp;
391 $fin[$q]['limit'] = $totals[$q]['pp'];
392 $totals[$q]['total'] = $totals[$q]['total'] - $totals[$q]['pp'];
395 array_multisort($totals, $order, $fin);
398 array_multisort($order, $fin);
404 * Applies styling to each row and adds extra meta details that are
405 * used else where in the ListFactory.
408 * @param array $row_arr A single results row
409 * @param array $source The source we are currently working with
410 * @return array The row with styling applied and extra meta details
413 function _fillrow( $row_arr, $source )
416 $col[LF_SOURCE_TITLE] = $source['title'];
417 $col[LF_SOURCE_NAME] = $source['name'];
419 foreach ($this->_fields as $field)
421 if (!is_numeric($field['name']) && $field['name'][0] != '_') {
422 if (empty($row_arr[ $field['name'] ])) {
423 $col[ $field['name'] ] = 'LF_NULL';
425 $col[ $field['name'] ] = $row_arr[ $field['name'] ];
430 // Need to call the format function before and after
431 // sorting the results.
432 if (is_callable($this->_function)) {
433 $col = call_user_func_array($this->_function, array(true, $col));
440 * Executes pre set queries
443 * @return array The results found
446 function ExecuteQueries()
448 // Get the details for sorting the list
449 $this->_sort_arr['field'] = isset($_GET['order']) ? COM_applyFilter($_GET['order']) : $this->_def_sort_arr['field'];
450 if (isset($_GET['direction']))
451 $this->_sort_arr['direction'] = $_GET['direction'] == 'asc' ? 'asc' : 'desc';
453 $this->_sort_arr['direction'] = $this->_def_sort_arr['direction'];
455 if (is_numeric($this->_sort_arr['field']))
457 $ord = $this->_def_sort_arr['field'];
458 $this->_sort_arr['field'] = LF_SOURCE_TITLE;
462 $ord = $this->_sort_arr['field'];
464 $order_sql = ' ORDER BY "' . addslashes($ord) . '" ' . strtoupper($this->_sort_arr['direction']);
466 $this->_page = isset($_GET['page']) ? COM_applyFilter($_GET['page'], true) : 1;
467 if (isset($_GET['results'])) {
468 $this->_per_page = COM_applyFilter($_GET['results'], true);
471 $rows_arr = $this->_preset_rows;
472 $this->_total_found = count($this->_preset_rows);
474 // When the preset rows exceed per_page bail early
475 if ($this->_total_found > $this->_per_page)
476 return array_slice($rows_arr, 0, $this->_per_page);
478 // Calculate the limits for each query
479 $num_query_results = $this->_per_page - $this->_total_found;
480 $pp_total = $this->_total_found;
482 for ($i = 0; $i < count($this->_sources_arr); $i++)
484 $limits[$i]['total'] = $this->_getTotal($this->_sources_arr[$i]);
485 $limits[$i]['pp'] = round(($this->_sources_arr[$i]['rank'] / $this->_total_rank) * $num_query_results);
486 $this->_total_found += $limits[$i]['total'];
487 $pp_total += $limits[$i]['pp'];
489 if ($pp_total < $this->_per_page) {
490 $limits[0]['pp'] += $this->_per_page - $pp_total;
491 } else if ($this->_per_page < $pp_total) {
492 $limits[0]['pp'] -= $pp_total - $this->_per_page;
494 $limits = $this->_getLimits($limits);
496 // Retrieve the results from each source in turn
497 for ($i = 0; $i < count($this->_sources_arr); $i++)
499 if ($limits[$i]['limit'] <= 0) {
503 // This is a callback function
504 if ($this->_sources_arr[$i]['type'] == 'callback')
506 if (is_callable($this->_sources_arr[$i]['func']))
508 $callback_rows = call_user_func_array(
509 $this->_sources_arr[$i]['func'],
510 array($limits[$i]['offset'],
511 $limits[$i]['limit'])
514 foreach ($callback_rows as $row) {
515 $rows_arr[] = $this->_fillrow($row, $this->_sources_arr[$i]);
518 COM_errorLog('ListFactory: A callback function was set for "'.
519 $this->_sources_arr[$i]['name'].'", but it could not be found.');
524 // This is an SQL query, so execute it and format the results
525 $limit_sql = " LIMIT {$limits[$i]['offset']},{$limits[$i]['limit']}";
527 if (is_array($this->_sources_arr[$i]['sql'])) {
528 $this->_sources_arr[$i]['sql']['mysql'] .= $order_sql . $limit_sql;
529 $this->_sources_arr[$i]['sql']['mssql'] .= $order_sql . $limit_sql;
531 $this->_sources_arr[$i]['sql'] .= $order_sql . $limit_sql;
534 $result = DB_query($this->_sources_arr[$i]['sql']);
535 while ($A = DB_fetchArray($result)) {
536 $rows_arr[] = $this->_fillrow($A, $this->_sources_arr[$i]);
540 // Sort the final array
541 $direction = $this->_sort_arr['direction'] == 'asc' ? SORT_ASC : SORT_DESC;
543 foreach ($rows_arr as $sortarray) {
544 $tmp = strip_tags($sortarray[ $this->_sort_arr['field'] ]);
545 $column[] = ($tmp == 'LF_NULL' ? 0 : $tmp);
547 array_multisort($column, $direction, $rows_arr);
553 * Generates the HTML code based on the preset style
556 * @param array $rows_arr The rows to display in the list
557 * @param string $title The title of the list
558 * @param string $list_top HTML that will appear before the list is printed
559 * @param string $list_bottom HTML that will appear after the list is printed
560 * @param boolean $show_sort True to enable column sorting, false to disable
561 * @param boolean $show_limit True to show page limits, false to hide
562 * @return string HTML output
565 function getFormattedOutput( $rows_arr, $title, $list_top = '', $list_bottom = '', $show_sort = true, $show_limit = true )
567 global $_CONF, $_IMAGE_TYPE, $LANG_ADMIN, $LANG09;
569 // get all template fields.
570 $list_templates = new Template($_CONF['path_layout'] . 'lists/' . $this->_style);
571 $list_templates->set_file (array (
572 'list' => 'list.thtml',
573 'limit' => 'page_limit.thtml',
574 'sort' => 'page_sort.thtml',
575 'row' => 'item_row.thtml',
576 'field' => 'item_field.thtml'
579 // insert std. values into the template
580 $list_templates->set_var('xhtml', XHTML);
581 $list_templates->set_var('site_url', $_CONF['site_url']);
582 $list_templates->set_var('layout_url', $_CONF['layout_url']);
584 if (count($rows_arr) == 0)
586 $list_templates->set_var('show_sort', 'display:none;');
587 $list_templates->set_var('show_limit', 'display:none;');
588 $list_templates->set_var('message', $LANG_ADMIN['no_results']);
589 $list_templates->set_var('list_top', $list_top);
590 $list_templates->set_var('list_bottom', $list_bottom);
591 $list_templates->parse('output', 'list');
593 // No results to show so quickly print a message and exit
595 if (!empty($title)) {
596 $retval .= COM_startBlock($title, '', COM_getBlockTemplate('_admin_block', 'header'));
598 $retval .= $list_templates->finish($list_templates->get_var('output'));
599 if (!empty($title)) {
600 $retval .= COM_endBlock(COM_getBlockTemplate('_admin_block', 'footer'));
606 // Draw the page limit select box
609 foreach ($this->_page_limits as $key => $val)
611 $text = is_numeric($key) ? sprintf($LANG09[67], $val) : $key;
612 $href = $this->_page_url . "order={$this->_sort_arr['field']}&" .
613 "direction={$this->_sort_arr['direction']}&results=$val";
615 // Prevent displaying too many limit items
616 if ($this->_total_found <= $val)
618 // If this is the last item, chances are its going to be selected
619 $selected = $this->_per_page >= $val ? ' selected="selected"' : '';
620 $list_templates->set_var('limit_href', $href);
621 $list_templates->set_var('limit_text', $text);
622 $list_templates->set_var('limit_selected', $selected);
623 $list_templates->parse('page_limit', 'limit', true);
628 $selected = $this->_per_page == $val ? ' selected="selected"' : '';
629 $list_templates->set_var('limit_text', $text);
630 $list_templates->set_var('limit_href', $href);
631 $list_templates->set_var('limit_selected', $selected);
632 $list_templates->parse('page_limit', 'limit', true);
635 $list_templates->set_var('show_limit', 'display:none;');
640 $list_templates->set_var('show_limit', 'display:none;');
643 // Create how to display the sort field
644 if ($this->_style == 'table')
646 $arrow = $this->_sort_arr['direction'] == 'asc' ? 'bararrowdown' : 'bararrowup';
647 $sort_selected = "{$_CONF['layout_url']}/images/$arrow.$_IMAGE_TYPE";
648 $sort_selected = ' ' . COM_createImage($sort_selected, $arrow);
654 $sort_text = $LANG09[68].' ';
656 $list_templates->set_var('show_sort', 'display:none;');
660 // Draw the sorting select box/table headings
661 foreach ($this->_fields as $field)
663 if ($field['display'] == true && $field['title'] != '')
665 $text = $sort_text . $field['title'];
669 if ($this->_style == 'inline' && $show_sort && $field['sort'] != false)
671 $direction = $this->_def_sort_arr['direction'];
673 // Show the sort arrow
674 if ($this->_sort_arr['field'] === $field['name'])
676 // Add drop down item for current sort order
677 $list_templates->set_var('sort_text', $text.' ('.$this->_sort_arr['direction'].')');
678 $list_templates->set_var('sort_href', '');
679 $list_templates->set_var('sort_selected', ' selected="selected"');
680 $list_templates->parse('page_sort', 'sort', true);
682 // Set up the sort order for the opposite direction
683 $direction = $this->_sort_arr['direction'] == 'asc' ? 'desc' : 'asc';
684 $text .= " ($direction)";
686 $href = $this->_page_url . "results={$this->_per_page}&" .
687 "order={$field['name']}&direction=$direction";
690 $list_templates->set_var('sort_text', $text);
691 $list_templates->set_var('sort_href', $href);
692 $list_templates->set_var('sort_selected', '');
693 $list_templates->parse('page_sort', 'sort', true);
695 else if ($this->_style == 'table')
697 if ($show_sort && $field['sort'] != false)
699 $text = "<a href=\"$href\">$text</a>";
701 if ($this->_sort_arr['field'] === $field['name']) {
702 $selected = $sort_selected;
705 $href = $this->_page_url . "results={$this->_per_page}&" .
706 "order={$field['name']}&direction=$direction";
709 $list_templates->set_var('sort_text', $text);
710 $list_templates->set_var('sort_href', $href);
711 $list_templates->set_var('sort_selected', $selected);
712 $list_templates->parse('page_sort', 'sort', true);
717 $offset = ($this->_page-1) * $this->_per_page;
719 $list_templates->set_var('show_message', 'display:none;');
721 // Run through all the results
723 foreach ($rows_arr as $row)
725 if (is_callable($this->_function)) {
726 $row = call_user_func_array($this->_function, array(false, $row));
729 foreach ($this->_fields as $field)
731 if ($field['display'] == true)
734 if ($field['name'] == LF_ROW_NUMBER) {
735 $fieldvalue = $r + $offset;
736 } else if (!empty($row[ $field['name'] ])) {
737 $fieldvalue = $row[ $field['name'] ];
740 if ($fieldvalue != 'LF_NULL') {
741 $fieldvalue = sprintf($field['format'], $fieldvalue, $field['title']);
744 $list_templates->set_var('field_text', $fieldvalue);
745 $list_templates->parse('item_field', 'field', true);
747 // Write an empty field
748 $list_templates->set_var('field_text', ' ');
749 $list_templates->parse('item_field', 'field', true);
756 $list_templates->set_var('cssid', ($r % 2) + 1);
757 $list_templates->parse('item_row', 'row', true);
758 $list_templates->clear_var('item_field');
761 // Print page numbers
762 $page_url = $this->_page_url.'order='.$this->_sort_arr['field'] .
763 '&direction='.$this->_sort_arr['direction'].'&results='.$this->_per_page;
764 $num_pages = ceil($this->_total_found / $this->_per_page);
765 if ($num_pages > 1) {
766 $list_templates->set_var('google_paging', COM_printPageNavigation($page_url, $this->_page, $num_pages, 'page=', false, '', ''));
768 $list_templates->set_var('google_paging', '');
771 $list_top = sprintf($list_top, $offset+1, $r+$offset-1, $this->_total_found);
772 $list_templates->set_var('list_top', $list_top);
773 $list_templates->set_var('list_bottom', $list_bottom);
775 $list_templates->parse('output', 'list');
777 // Do the actual output
780 if (!empty($title)) {
781 $retval .= COM_startBlock($title, '', COM_getBlockTemplate('_admin_block', 'header'));
784 $retval .= $list_templates->finish($list_templates->get_var('output'));
786 if (!empty($title)) {
787 $retval .= COM_endBlock(COM_getBlockTemplate('_admin_block', 'footer'));