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 ROW_NUMBER, // The field identifier can be either:
51 // ROW_NUMBER - The number of each row will be displayed
52 // SQL_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', SQL_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 SQL_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 SQL_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 $_query_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 * SQL_TITLE, SQL_NAME and 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('SQL_TITLE', 0);
175 define('SQL_NAME', 1);
176 define('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: 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 === 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->_query_arr[] = array(
237 $this->_total_rank += $rank;
240 function setCallback( $title, $name, $function, $rank, $total )
242 $this->_query_arr[] = array(
243 'type' => 'callback',
250 $this->_total_rank += $rank;
254 * Sets the callback function that gets called when formatting a row
257 * @param callback $function Any callable function, method or lambda
260 function setRowFunction( $callback )
262 $this->_function = $callback;
266 * Sets the default sort field
269 * @param string $field The field name to sort
270 * @param string $direction 'asc' for ascending order and 'desc' for descending order
273 function setDefaultSort( $field, $direction = 'desc' )
275 $this->_def_sort_arr = array('field' => $field, 'direction' => $direction);
279 * Appends a single result to the list
282 * @param array $result A single result that will be appended to the rest
285 function addResult( $result )
287 $this->_preset_rows[] = $result;
291 * Appends several results to the list
294 * @param array $result An array of result that will be appended to the rest
297 function addResultArray( $arr )
299 $this->_preset_rows = array_merge($this->_preset_rows, $arr);
303 * Gets the total number of results from a query
306 * @param string $sql The query
307 * @return int Total number of rows
310 function _getTotal( $param )
312 if ($param['type'] == 'callback') {
313 return $param['total'];
316 $sql = $param['sql'];
319 if (is_array($sql)) {
320 $sql['mysql'] = preg_replace('/SELECT.*FROM/is', 'SELECT COUNT(*) FROM', $sql['mysql']);
321 $sql['mssql'] = preg_replace('/SELECT.*FROM/is', 'SELECT COUNT(*) FROM', $sql['mssql']);
324 $sql = preg_replace('/SELECT.*FROM/is', 'SELECT COUNT(*) FROM', $sql);
326 $result = DB_query($sql);
327 $num_rows = DB_numRows($result);
328 if ($num_rows <= 1) {
329 $B = DB_fetchArray($result, true);
332 return $num_rows ? $num_rows : 0;
336 * Calculates the offset and limits for each query based on
337 * the number of rows to be displayed per query per page.
340 * @param array $totals The total number of results per query
341 * @return array The offsets and limits for a given page
344 function _getLimits( $totals )
346 $order = range(0, count($totals)-1);
347 array_multisort($totals, $order);
348 $fin = array('total' => 0, 'offset' => 0, 'limit' => 0);
349 $fin = array_fill(0, count($totals), $fin);
351 for ($p = 0; $p < $this->_page; $p++)
354 for ($q = 0; $q < count($totals); $q++)
356 $fin[$q]['offset'] = $fin[$q]['offset'] + $fin[$q]['limit'];
357 $extra_pp = $extra + $totals[$q]['pp'];
358 if ($extra_pp - $totals[$q]['total'] >= 0)
360 $fin[$q]['limit'] = $totals[$q]['total'];
361 $extra = $extra_pp - $totals[$q]['total'];
362 $totals[$q]['total'] = 0;
364 else if ($totals[$q]['total'] - $extra_pp >= 0)
366 $fin[$q]['limit'] = $extra_pp;
367 $totals[$q]['total'] = $totals[$q]['total'] - $extra_pp;
372 $fin[$q]['limit'] = $totals[$q]['pp'];
373 $totals[$q]['total'] = $totals[$q]['total'] - $totals[$q]['pp'];
376 array_multisort($totals, $order, $fin);
379 array_multisort($order, $fin);
385 * Executes pre set queries
388 * @return array The results found
391 function ExecuteQueries()
393 // Get the details for sorting the list
394 $this->_sort_arr['field'] = isset($_GET['order']) ? COM_applyFilter($_GET['order']) : $this->_def_sort_arr['field'];
395 if (isset($_GET['direction']))
396 $this->_sort_arr['direction'] = $_GET['direction'] == 'asc' ? 'asc' : 'desc';
398 $this->_sort_arr['direction'] = $this->_def_sort_arr['direction'];
400 if (is_numeric($this->_sort_arr['field']))
402 $ord = $this->_def_sort_arr['field'];
403 $this->_sort_arr['field'] = SQL_TITLE;
407 $ord = $this->_sort_arr['field'];
409 $order_sql = ' ORDER BY "' . addslashes($ord) . '" ' . strtoupper($this->_sort_arr['direction']);
411 $this->_page = isset($_GET['page']) ? COM_applyFilter($_GET['page'], true) : 1;
412 if (isset($_GET['results'])) {
413 $this->_per_page = COM_applyFilter($_GET['results'], true);
416 $rows_arr = $this->_preset_rows;
417 $this->_total_found = count($this->_preset_rows);
419 // When the preset rows exceed per_page bail early
420 if ($this->_total_found > $this->_per_page)
421 return array_slice($rows_arr, 0, $this->_per_page);
423 // Calculate the limits for each query
424 $num_query_results = $this->_per_page - $this->_total_found;
425 $pp_total = $this->_total_found;
427 for ($i = 0; $i < count($this->_query_arr); $i++)
429 $limits[$i]['total'] = $this->_getTotal($this->_query_arr[$i]);
430 $limits[$i]['pp'] = round(($this->_query_arr[$i]['rank'] / $this->_total_rank) * $num_query_results);
431 $this->_total_found += $limits[$i]['total'];
432 $pp_total += $limits[$i]['pp'];
434 if ($pp_total < $this->_per_page) {
435 $limits[0]['pp'] += $this->_per_page - $pp_total;
436 } else if ($this->_per_page < $pp_total) {
437 $limits[0]['pp'] -= $pp_total - $this->_per_page;
439 $limits = $this->_getLimits($limits);
441 // Execute each query in turn
442 for ($i = 0; $i < count($this->_query_arr); $i++)
444 if ($limits[$i]['limit'] <= 0) {
448 // This is a callback function
449 if ($this->_query_arr[$i]['type'] == 'callback')
451 if (is_callable($this->_query_arr[$i]['func']))
453 $callback_rows = call_user_func_array($this->_query_arr[$i]['func'], array($limits[$i]['offset'], $limits[$i]['limit']));
455 foreach ($callback_rows as $row)
458 $col[SQL_TITLE] = $this->_query_arr[$i]['title'];
459 $col[SQL_NAME] = $this->_query_arr[$i]['name'];
461 foreach ($this->_fields as $field)
463 if (!is_numeric($field['name']) && $field['name'][0] != '_') {
464 if (empty($row[ $field['name'] ])) {
465 $col[ $field['name'] ] = 'LF_NULL';
467 $col[ $field['name'] ] = $row[ $field['name'] ];
472 // Need to call the format function before and after
473 // sorting the results.
474 if (is_callable($this->_function)) {
475 $col = call_user_func_array($this->_function, array(true, $col));
484 // This is an SQL query, so execute it and format the results
485 $limit_sql = " LIMIT {$limits[$i]['offset']},{$limits[$i]['limit']}";
487 if (is_array($this->_query_arr[$i]['sql']))
489 $this->_query_arr[$i]['sql']['mysql'] .= $order_sql . $limit_sql;
490 $this->_query_arr[$i]['sql']['mssql'] .= $order_sql . $limit_sql;
494 $this->_query_arr[$i]['sql'] .= $order_sql . $limit_sql;
497 $result = DB_query($this->_query_arr[$i]['sql']);
499 while ($A = DB_fetchArray($result))
502 $col[SQL_TITLE] = $this->_query_arr[$i]['title'];
503 $col[SQL_NAME] = $this->_query_arr[$i]['name'];
505 foreach ($this->_fields as $field)
507 if (!is_numeric($field['name']) && $field['name'][0] != '_') {
508 if (empty($A[ $field['name'] ])) {
509 $col[ $field['name'] ] = 'LF_NULL';
511 $col[ $field['name'] ] = $A[ $field['name'] ];
516 // Need to call the format function before and after
517 // sorting the results.
518 if (is_callable($this->_function)) {
519 $col = call_user_func_array($this->_function, array(true, $col));
526 // Sort the final array
527 $direction = $this->_sort_arr['direction'] == 'asc' ? SORT_ASC : SORT_DESC;
529 foreach ($rows_arr as $sortarray) {
530 $tmp = strip_tags($sortarray[ $this->_sort_arr['field'] ]);
531 $column[] = ($tmp == 'LF_NULL' ? 0 : $tmp);
533 array_multisort($column, $direction, $rows_arr);
539 * Generates the HTML code based on the preset style
542 * @param array $rows_arr The rows to display in the list
543 * @param string $title The title of the list
544 * @param string $list_top HTML that will appear before the list is printed
545 * @param string $list_bottom HTML that will appear after the list is printed
546 * @param boolean $show_sort True to enable column sorting, false to disable
547 * @param boolean $show_limit True to show page limits, false to hide
548 * @return string HTML output
551 function getFormattedOutput( $rows_arr, $title, $list_top = '', $list_bottom = '', $show_sort = true, $show_limit = true )
553 global $_CONF, $_IMAGE_TYPE, $LANG_ADMIN, $LANG09;
555 // get all template fields.
556 $list_templates = new Template($_CONF['path_layout'] . 'lists/' . $this->_style);
557 $list_templates->set_file (array (
558 'list' => 'list.thtml',
559 'limit' => 'page_limit.thtml',
560 'sort' => 'page_sort.thtml',
561 'row' => 'item_row.thtml',
562 'field' => 'item_field.thtml'
565 // insert std. values into the template
566 $list_templates->set_var('xhtml', XHTML);
567 $list_templates->set_var('site_url', $_CONF['site_url']);
568 $list_templates->set_var('layout_url', $_CONF['layout_url']);
570 if (count($rows_arr) == 0)
572 $list_templates->set_var('show_sort', 'display:none;');
573 $list_templates->set_var('show_limit', 'display:none;');
574 $list_templates->set_var('message', $LANG_ADMIN['no_results']);
575 $list_templates->set_var('list_top', $list_top);
576 $list_templates->set_var('list_bottom', $list_bottom);
577 $list_templates->parse('output', 'list');
579 // No results to show so quickly print a message and exit
581 if (!empty($title)) {
582 $retval .= COM_startBlock($title, '', COM_getBlockTemplate('_admin_block', 'header'));
584 $retval .= $list_templates->finish($list_templates->get_var('output'));
585 if (!empty($title)) {
586 $retval .= COM_endBlock(COM_getBlockTemplate('_admin_block', 'footer'));
592 // Draw the page limit select box
595 foreach ($this->_page_limits as $key => $val)
597 $text = is_numeric($key) ? sprintf($LANG09[67], $val) : $key;
598 $href = $this->_page_url . "order={$this->_sort_arr['field']}&" .
599 "direction={$this->_sort_arr['direction']}&results=$val";
601 $selected = $this->_per_page == $val ? ' selected="selected"' : '';
603 $list_templates->set_var('limit_text', $text);
604 $list_templates->set_var('limit_href', $href);
605 $list_templates->set_var('limit_selected', $selected);
606 $list_templates->parse('page_limit', 'limit', true);
611 $list_templates->set_var('show_limit', 'display:none;');
614 // Create how to display the sort field
615 if ($this->_style == 'table')
617 $arrow = $this->_sort_arr['direction'] == 'asc' ? 'bararrowdown' : 'bararrowup';
618 $sort_selected = "{$_CONF['layout_url']}/images/$arrow.$_IMAGE_TYPE";
619 $sort_selected = ' ' . COM_createImage($sort_selected, $arrow);
624 $sort_selected = ' selected="selected"';
625 $sort_text = $LANG09[68].' ';
627 $list_templates->set_var('show_sort', 'display:none;');
631 $list_templates->set_var('sort_text', "$sort_text...");
632 $list_templates->set_var('sort_href', "");
633 $list_templates->set_var('sort_selected', $sort_selected);
634 $list_templates->parse('page_sort', 'sort', true);
637 // Draw the sorting select box/table headings
638 foreach ($this->_fields as $field)
640 if ($field['display'] == true && $field['title'] != '')
642 $text = $sort_text . $field['title'];
645 if ($show_sort && $field['sort'] != false)
647 $direction = $this->_def_sort_arr['direction'];
649 // Show the sort arrow
650 if ($this->_sort_arr['field'] === $field['name']) {
651 //$selected = $sort_selected;
652 $direction = $this->_sort_arr['direction'] == 'asc' ? 'desc' : 'asc';
653 $text .= " ($direction)";
656 $href = $this->_page_url . "results={$this->_per_page}&" .
657 "order={$field['name']}&direction=$direction";
659 if ($this->_style == 'table') {
660 $text = "<a href=\"$href\">$text</a>";
665 $list_templates->set_var('sort_text', $text);
666 $list_templates->set_var('sort_href', $href);
667 $list_templates->set_var('sort_selected', $selected);
668 $list_templates->parse('page_sort', 'sort', true);
672 $offset = ($this->_page-1) * $this->_per_page;
674 $list_templates->set_var('show_message', 'display:none;');
676 // Run through all the results
678 foreach ($rows_arr as $row)
680 if (is_callable($this->_function)) {
681 $row = call_user_func_array($this->_function, array(false, $row));
684 foreach ($this->_fields as $field)
686 if ($field['display'] == true)
689 if ($field['name'] == ROW_NUMBER) {
690 $fieldvalue = $r + $offset;
691 } else if (!empty($row[ $field['name'] ])) {
692 $fieldvalue = $row[ $field['name'] ];
695 if ($fieldvalue != 'LF_NULL') {
696 $fieldvalue = sprintf($field['format'], $fieldvalue, $field['title']);
699 $list_templates->set_var('field_text', $fieldvalue);
700 $list_templates->parse('item_field', 'field', true);
707 $list_templates->set_var('cssid', ($r % 2) + 1);
708 $list_templates->parse('item_row', 'row', true);
709 $list_templates->clear_var('item_field');
712 // Print page numbers
713 $page_url = $this->_page_url . 'order=' . $this->_sort_arr['field'] . '&direction=' . $this->_sort_arr['direction'] . '&results=' . $this->_per_page;
714 $num_pages = ceil($this->_total_found / $this->_per_page);
715 if ($num_pages > 1) {
716 $list_templates->set_var('google_paging', COM_printPageNavigation($page_url, $this->_page, $num_pages, 'page=', false, '', ''));
718 $list_templates->set_var('google_paging', '');
721 $list_top = sprintf($list_top, $offset+1, $r+$offset-1, $this->_total_found);
722 $list_templates->set_var('list_top', $list_top);
723 $list_templates->set_var('list_bottom', $list_bottom);
725 $list_templates->parse('output', 'list');
727 // Do the actual output
730 if (!empty($title)) {
731 $retval .= COM_startBlock($title, '', COM_getBlockTemplate('_admin_block', 'header'));
734 $retval .= $list_templates->finish($list_templates->get_var('output'));
736 if (!empty($title)) {
737 $retval .= COM_endBlock(COM_getBlockTemplate('_admin_block', 'footer'));