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('title' => $title, 'name' => $name, 'display' => $display, 'sort' => $sort, 'format' => $format);
213 * Sets the SQL query that will generate rows
216 * @param string $title The text that's displayed to the user
217 * @param string $name The local name given to the query
218 * @param string $sql The SQL string without the ORDER BY or LIMIT clauses
219 * @param int $rank The rating that determins how many results will be returned
222 function setQuery( $title, $name, $sql, $rank )
224 $this->_query_arr[] = array('title' => $title, 'name' => $name, 'sql' => $sql, 'rank' => $rank);
225 $this->_total_rank += $rank;
229 * Sets the callback function that gets called when formatting a row
232 * @param callback $function Any callable function, method or lambda
235 function setRowFunction( $callback )
237 $this->_function = $callback;
241 * Sets the default sort field
244 * @param string $field The field name to sort
245 * @param string $direction 'asc' for ascending order and 'desc' for descending order
248 function setDefaultSort( $field, $direction = 'desc' )
250 $this->_def_sort_arr = array('field' => $field, 'direction' => $direction);
254 * Appends a single result to the list
257 * @param array $result A single result that will be appended to the rest
260 function addResult( $result )
262 $this->_preset_rows[] = $result;
266 * Appends several results to the list
269 * @param array $result An array of result that will be appended to the rest
272 function addResultArray( $arr )
274 $this->_preset_rows = array_merge($this->_preset_rows, $arr);
278 * Gets the total number of results from a query
281 * @param string $sql The query
282 * @return int Total number of rows
285 function _numRows( $sql )
289 $sql['mysql'] = preg_replace('/SELECT.*FROM/is', 'SELECT COUNT(*) FROM', $sql['mysql']);
290 $sql['mssql'] = preg_replace('/SELECT.*FROM/is', 'SELECT COUNT(*) FROM', $sql['mssql']);
294 $sql = preg_replace('/SELECT.*FROM/is', 'SELECT COUNT(*) FROM', $sql);
296 $result = DB_query($sql);
297 $num_rows = DB_numRows($result);
300 $B = DB_fetchArray($result, true);
303 return $num_rows ? $num_rows : 0;
307 * Calculates the offset and limits for each query based on
308 * the number of rows to be displayed per query per page.
311 * @param array $totals The total number of results per query
312 * @return array The offsets and limits for a given page
315 function _getLimits( $totals )
317 $order = range(0, count($totals)-1);
318 array_multisort($totals, $order);
319 $fin = array('total' => 0, 'offset' => 0, 'limit' => 0);
320 $fin = array_fill(0, count($totals), $fin);
322 for ($p = 0; $p < $this->_page; $p++)
325 for ($q = 0; $q < count($totals); $q++)
327 $fin[$q]['offset'] = $fin[$q]['offset'] + $fin[$q]['limit'];
328 $extra_pp = $extra + $totals[$q]['pp'];
329 if ($extra_pp - $totals[$q]['total'] >= 0)
331 $fin[$q]['limit'] = $totals[$q]['total'];
332 $extra = $extra_pp - $totals[$q]['total'];
333 $totals[$q]['total'] = 0;
335 else if ($totals[$q]['total'] - $extra_pp >= 0)
337 $fin[$q]['limit'] = $extra_pp;
338 $totals[$q]['total'] = $totals[$q]['total'] - $extra_pp;
343 $fin[$q]['limit'] = $totals[$q]['pp'];
344 $totals[$q]['total'] = $totals[$q]['total'] - $totals[$q]['pp'];
347 array_multisort($totals, $order, $fin);
350 array_multisort($order, $fin);
356 * Executes pre set queries
359 * @return array The results found
362 function ExecuteQueries()
364 // Get the details for sorting the list
365 $this->_sort_arr['field'] = isset($_GET['order']) ? COM_applyFilter($_GET['order']) : $this->_def_sort_arr['field'];
366 if (isset($_GET['direction']))
367 $this->_sort_arr['direction'] = $_GET['direction'] == 'asc' ? 'asc' : 'desc';
369 $this->_sort_arr['direction'] = $this->_def_sort_arr['direction'];
371 if (is_numeric($this->_sort_arr['field']))
373 $ord = $this->_def_sort_arr['field'];
374 $this->_sort_arr['field'] = SQL_TITLE;
378 $ord = $this->_sort_arr['field'];
380 $order_sql = ' ORDER BY "' . addslashes($ord) . '" ' . strtoupper($this->_sort_arr['direction']);
382 $this->_page = isset($_GET['page']) ? COM_applyFilter($_GET['page'], true) : 1;
383 if (isset($_GET['results'])) {
384 $this->_per_page = COM_applyFilter($_GET['results'], true);
387 $rows_arr = $this->_preset_rows;
388 $this->_total_found = count($this->_preset_rows);
390 // When the preset rows exceed per_page bail early
391 if ($this->_total_found > $this->_per_page)
392 return array_slice($rows_arr, 0, $this->_per_page);
394 // Calculate the limits for each query
395 $num_query_results = $this->_per_page - $this->_total_found;
396 $pp_total = $this->_total_found;
398 for ($i = 0; $i < count($this->_query_arr); $i++)
400 $limits[$i]['total'] = $this->_numRows($this->_query_arr[$i]['sql']);
401 $limits[$i]['pp'] = round(($this->_query_arr[$i]['rank'] / $this->_total_rank) * $num_query_results);
402 $this->_total_found += $limits[$i]['total'];
403 $pp_total += $limits[$i]['pp'];
405 if ($pp_total < $this->_per_page) {
406 $limits[0]['pp'] += $this->_per_page - $pp_total;
407 } else if ($this->_per_page < $pp_total) {
408 $limits[0]['pp'] -= $pp_total - $this->_per_page;
410 $limits = $this->_getLimits($limits);
412 // Execute each query in turn
413 for ($i = 0; $i < count($this->_query_arr); $i++)
415 if ($limits[$i]['limit'] <= 0) {
418 $limit_sql = " LIMIT {$limits[$i]['offset']},{$limits[$i]['limit']}";
420 if (is_array($this->_query_arr[$i]['sql']))
422 $this->_query_arr[$i]['sql']['mysql'] .= $order_sql . $limit_sql;
423 $this->_query_arr[$i]['sql']['mssql'] .= $order_sql . $limit_sql;
427 $this->_query_arr[$i]['sql'] .= $order_sql . $limit_sql;
430 $result = DB_query($this->_query_arr[$i]['sql']);
432 while ($A = DB_fetchArray($result))
435 $col[SQL_TITLE] = $this->_query_arr[$i]['title'];
436 $col[SQL_NAME] = $this->_query_arr[$i]['name'];
438 foreach ($this->_fields as $field)
440 if (!is_numeric($field['name']) && $field['name'][0] != '_') {
441 if (empty($A[ $field['name'] ])) {
442 $col[ $field['name'] ] = 'LF_NULL';
444 $col[ $field['name'] ] = $A[ $field['name'] ];
449 // Need to call the format function before and after
450 // sorting the results.
451 if (is_callable($this->_function)) {
452 $col = call_user_func_array($this->_function, array(true, $col));
459 // Sort the final array
460 $direction = $this->_sort_arr['direction'] == 'asc' ? SORT_ASC : SORT_DESC;
462 foreach ($rows_arr as $sortarray) {
463 $tmp = strip_tags($sortarray[ $this->_sort_arr['field'] ]);
464 $column[] = ($tmp == 'LF_NULL' ? 0 : $tmp);
466 array_multisort($column, $direction, $rows_arr);
472 * Generates the HTML code based on the preset style
475 * @param array $rows_arr The rows to display in the list
476 * @param string $title The title of the list
477 * @param string $list_top HTML that will appear before the list is printed
478 * @param string $list_bottom HTML that will appear after the list is printed
479 * @param boolean $show_sort True to enable column sorting, false to disable
480 * @param boolean $show_limit True to show page limits, false to hide
481 * @return string HTML output
484 function getFormattedOutput( $rows_arr, $title, $list_top = '', $list_bottom = '', $show_sort = true, $show_limit = true )
486 global $_CONF, $_IMAGE_TYPE, $LANG_ADMIN, $LANG09;
488 // get all template fields.
489 $list_templates = new Template($_CONF['path_layout'] . 'lists/' . $this->_style);
490 $list_templates->set_file (array (
491 'list' => 'list.thtml',
492 'limit' => 'page_limit.thtml',
493 'sort' => 'page_sort.thtml',
494 'row' => 'item_row.thtml',
495 'field' => 'item_field.thtml'
498 // insert std. values into the template
499 $list_templates->set_var('xhtml', XHTML);
500 $list_templates->set_var('site_url', $_CONF['site_url']);
501 $list_templates->set_var('layout_url', $_CONF['layout_url']);
503 if (count($rows_arr) == 0)
505 $list_templates->set_var('show_sort', 'display:none;');
506 $list_templates->set_var('show_limit', 'display:none;');
507 $list_templates->set_var('message', $LANG_ADMIN['no_results']);
508 $list_templates->set_var('list_top', $list_top);
509 $list_templates->set_var('list_bottom', $list_bottom);
510 $list_templates->parse('output', 'list');
512 // No results to show so quickly print a message and exit
514 if (!empty($title)) {
515 $retval .= COM_startBlock($title, '', COM_getBlockTemplate('_admin_block', 'header'));
517 $retval .= $list_templates->finish($list_templates->get_var('output'));
518 if (!empty($title)) {
519 $retval .= COM_endBlock(COM_getBlockTemplate('_admin_block', 'footer'));
525 // Draw the page limit select box
528 foreach ($this->_page_limits as $key => $val)
530 $text = is_numeric($key) ? sprintf($LANG09[67], $val) : $key;
531 $href = $this->_page_url . "order={$this->_sort_arr['field']}&" .
532 "direction={$this->_sort_arr['direction']}&results=$val";
534 $selected = $this->_per_page == $val ? ' selected="selected"' : '';
536 $list_templates->set_var('limit_text', $text);
537 $list_templates->set_var('limit_href', $href);
538 $list_templates->set_var('limit_selected', $selected);
539 $list_templates->parse('page_limit', 'limit', true);
544 $list_templates->set_var('show_limit', 'display:none;');
547 // Create how to display the sort field
548 if ($this->_style == 'table')
550 $arrow = $this->_sort_arr['direction'] == 'asc' ? 'bararrowdown' : 'bararrowup';
551 $sort_selected = "{$_CONF['layout_url']}/images/$arrow.$_IMAGE_TYPE";
552 $sort_selected = ' ' . COM_createImage($sort_selected, $arrow);
557 $sort_selected = ' selected="selected"';
558 $sort_text = $LANG09[68].' ';
560 $list_templates->set_var('show_sort', 'display:none;');
564 $list_templates->set_var('sort_text', "$sort_text...");
565 $list_templates->set_var('sort_href', "");
566 $list_templates->set_var('sort_selected', $sort_selected);
567 $list_templates->parse('page_sort', 'sort', true);
570 // Draw the sorting select box/table headings
571 foreach ($this->_fields as $field)
573 if ($field['display'] == true && $field['title'] != '')
575 $text = $sort_text . $field['title'];
578 if ($show_sort && $field['sort'] != false)
580 $direction = $this->_def_sort_arr['direction'];
582 // Show the sort arrow
583 if ($this->_sort_arr['field'] === $field['name']) {
584 //$selected = $sort_selected;
585 $direction = $this->_sort_arr['direction'] == 'asc' ? 'desc' : 'asc';
586 $text .= " ($direction)";
589 $href = $this->_page_url . "results={$this->_per_page}&" .
590 "order={$field['name']}&direction=$direction";
592 if ($this->_style == 'table') {
593 $text = "<a href=\"$href\">$text</a>";
598 $list_templates->set_var('sort_text', $text);
599 $list_templates->set_var('sort_href', $href);
600 $list_templates->set_var('sort_selected', $selected);
601 $list_templates->parse('page_sort', 'sort', true);
605 $offset = ($this->_page-1) * $this->_per_page;
607 $list_templates->set_var('show_message', 'display:none;');
609 // Run through all the results
611 foreach ($rows_arr as $row)
613 if (is_callable($this->_function)) {
614 $row = call_user_func_array($this->_function, array(false, $row));
617 foreach ($this->_fields as $field)
619 if ($field['display'] == true)
622 if ($field['name'] == ROW_NUMBER) {
623 $fieldvalue = $r + $offset;
624 } else if (!empty($row[ $field['name'] ])) {
625 $fieldvalue = $row[ $field['name'] ];
628 if ($fieldvalue != 'LF_NULL') {
629 $fieldvalue = sprintf($field['format'], $fieldvalue, $field['title']);
632 $list_templates->set_var('field_text', $fieldvalue);
633 $list_templates->parse('item_field', 'field', true);
640 $list_templates->set_var('cssid', ($r % 2) + 1);
641 $list_templates->parse('item_row', 'row', true);
642 $list_templates->clear_var('item_field');
645 // Print page numbers
646 $page_url = $this->_page_url . 'order=' . $this->_sort_arr['field'] . '&direction=' . $this->_sort_arr['direction'] . '&results=' . $this->_per_page;
647 $num_pages = ceil($this->_total_found / $this->_per_page);
648 if ($num_pages > 1) {
649 $list_templates->set_var('google_paging', COM_printPageNavigation($page_url, $this->_page, $num_pages, 'page=', false, '', ''));
651 $list_templates->set_var('google_paging', '');
654 $list_top = sprintf($list_top, $offset+1, $r+$offset-1, $this->_total_found);
655 $list_templates->set_var('list_top', $list_top);
656 $list_templates->set_var('list_bottom', $list_bottom);
658 $list_templates->parse('output', 'list');
660 // Do the actual output
663 if (!empty($title)) {
664 $retval .= COM_startBlock($title, '', COM_getBlockTemplate('_admin_block', 'header'));
667 $retval .= $list_templates->finish($list_templates->get_var('output'));
669 if (!empty($title)) {
670 $retval .= COM_endBlock(COM_getBlockTemplate('_admin_block', 'footer'));