Xataface  2.0alpha2
Xataface Application Framework
 All Data Structures Namespaces Files Functions Variables Groups Pages
ResultList.php
Go to the documentation of this file.
1 <?php
2 /*-------------------------------------------------------------------------------
3  * Xataface Web Application Framework
4  * Copyright (C) 2005-2008 Web Lite Solutions Corp (shannah@sfu.ca)
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  *-------------------------------------------------------------------------------
20  */
21 
22 /******************************************************************************
23  * File: Dataface/ResultList.php
24  * Author: Steve Hannah
25  * Created: September 3, 2005
26  * Description:
27  * Handles creation and display of a result list from an SQL database.
28  *
29  *****************************************************************************/
30 
31  import( 'Dataface/Table.php');
32 import('Dataface/QueryBuilder.php');
33 import('Dataface/Record.php');
34 import('Dataface/QueryTool.php');
39 
41  var $_db;
42  var $_columns;
43  var $_query;
44  var $_table;
45 
46  var $_results;
48 
49  var $_filterCols = array();
50 
51  function Dataface_ResultList( $tablename, $db='', $columns=array(), $query=array()){
53  $this->_tablename = $tablename;
54  if (empty($db) ) $db = $app->db();
55  $this->_db = $db;
56  $this->_columns = $columns;
57  if ( !is_array($columns) ) $this->_columns = array();
58  $this->_query = $query;
59  if( !is_array($query) ) $this->_query = array();
60 
61  $this->_table =& Dataface_Table::loadTable($tablename);
62  $fieldnames = array_keys($this->_table->fields(false,true));
63  $fields =& $this->_table->fields(false,true);
64 
65  if ( count($this->_columns)==0 ){
66 
67  foreach ($fieldnames as $field){
68  if ( @$fields[$field]['filter'] ) $this->_filterCols[] = $field;
69  if ( $fields[$field]['visibility']['list'] != 'visible') continue;
70  if ( $this->_table->isPassword($field) ) continue;
71  if ( isset( $fields[$field] ) and !preg_match('/blob/i', $fields[$field]['Type']) ){
72  $this->_columns[] = $field;
73  }
74  }
75 
76 
77  } else {
78 
79 
80  foreach ($fieldnames as $field){
81  if ( @$fields[$field]['filter'] ) $this->_filterCols[] = $field;
82  }
83  }
84 
85 
86  $this->_resultSet =& Dataface_QueryTool::loadResult($tablename, $db, $query);
87 
88  }
89 
90  function renderCell(&$record, $fieldname){
91  $del =& $record->_table->getDelegate();
92  if ( isset($del) and method_exists($del, $fieldname.'__renderCell') ){
93  $method = $fieldname.'__renderCell';
94  return $del->$method($record);
95  //return call_user_func(array(&$del, $fieldname.'__renderCell'), $record);
96  }
97  $field =& $record->_table->getField($fieldname);
98  $out = $record->preview($fieldname);
99  if ( !@$field['noEditInListView'] and @$field['noLinkFromListView'] and $record->checkPermission('edit', array('field'=>$fieldname) ) ){
100  $recid = $record->getId();
101  $out = '<span df:showlink="1" df:id="'.$recid.'#'.$fieldname.'" class="df__editable">'.$out.'</span>';
102  }
103  return $out;
104  }
105 
106  function renderRowHeader($tablename=null){
107  if ( !isset($tablename) ) $tablename = $this->_table->tablename;
108  $del =& $this->_table->getDelegate();
109  if ( isset($del) and method_exists($del, 'renderRowHeader') ){
110  return $del->renderRowHeader($tablename);
111  }
113  $appdel =& $app->getDelegate();
114  if ( isset($appdel) and method_exists($appdel,'renderRowHeader') ){
115  return $appdel->renderRowHeader($tablename);
116  }
117  return null;
118  }
119 
120  function renderRow(&$record){
121  $del =& $record->_table->getDelegate();
122  if ( isset($del) and method_exists($del, 'renderRow') ){
123  return $del->renderRow($record);
124  }
126  $appdel =& $app->getDelegate();
127  if ( isset($appdel) and method_exists($appdel,'renderRow') ){
128  return $appdel->renderRow($record);
129  }
130  return null;
131  }
132 
133  function &getResults(){
134  if ( !isset($this->_results) ){
135  /*
136  // It seems all dandy to only load the columns we need...but if the user
137  // is using a custom template we may need more columns.
138  // boo!!!
139  $columns = array_unique(
140  array_merge(
141  $this->_columns,
142  array_keys(
143  $this->_table->keys()
144  )
145  )
146  );
147  */
148 
149  $this->_resultSet->loadSet(null/*$columns*/,true,false,true);
150  $this->_results = new Dataface_RecordIterator($this->_tablename, $this->_resultSet->data());
151 
152  }
153  return $this->_results;
154 
155  }
156 
157  function toHtml(){
159  $query =& $app->getQuery();
160  if ( isset( $query['-sort']) ){
161  $sortcols = explode(',', trim($query['-sort']));
162  $sort_columns = array();
163  foreach ($sortcols as $sortcol){
164  $sortcol = trim($sortcol);
165  if (strlen($sortcol) === 0 ) continue;
166  $sortcol = explode(' ', $sortcol);
167  if ( count($sortcol) > 1 ){
168  $sort_columns[$sortcol[0]] = strtolower($sortcol[1]);
169  } else {
170  $sort_columns[$sortcol[0]] = 'asc';
171  }
172  break;
173  }
174  unset($sortcols); // this was just a temp array so we get rid of it here
175  } else {
176  $sort_columns = array();
177  }
178 
179  // $sort_columns should now be of the form [ColumnName] -> [Direction]
180  // where Direction is "asc" or "desc"
181 
182 
183 
184  if ( $this->_resultSet->found() > 0 ) {
185 
186 
187  if ( @$app->prefs['use_old_resultlist_controller'] ){
188  ob_start();
189  df_display(array(), 'Dataface_ResultListController.html');
190  $controller = ob_get_contents();
191  ob_end_clean();
192  }
193 
194 
195  ob_start();
196  //echo '<div style="clear: both"/>';
197  if ( !defined('Dataface_ResultList_Javascript') ){
198  define('Dataface_ResultList_Javascript',true);
200  $jt->import('Dataface/ResultList.js');
201 
202  //echo '<script language="javascript" type="text/javascript" src="'.DATAFACE_URL.'/js/Dataface/ResultList.js"></script>';
203  }
204 
205  if ( !@$app->prefs['hide_result_filters'] and count($this->_filterCols) > 0 ){
206  echo $this->getResultFilters();
207  }
208  unset($query);
209 
210  if ( @$app->prefs['use_old_resultlist_controller'] ){
211  echo '<div class="resultlist-controller" id="resultlist-controller-top">';
212 
213  echo $controller;
214  echo "</div>";
215  }
216 
217 
218 
219  $canSelect = false;
220  if ( !@$app->prefs['disable_select_rows'] ){
221  $canSelect = Dataface_PermissionsTool::checkPermission('select_rows',
223  }
224 
225 
226  $sq = $myq = $app->getQuery();
227  foreach ($sq as $sqk=>$sqv ){
228  if ( !$sqk or $sqk{0} == '-' ){
229  unset($sq[$sqk]);
230  }
231  }
232  if ( @$myq['-sort'] ) $sq['-sort'] = $myq['-sort'];
233  if ( @$myq['-skip'] ) $sq['-skip'] = $myq['-skip'];
234  if ( @$myq['-limit'] ) $sq['-limit'] = $myq['-limit'];
235 
236 
237  $sq = json_encode($sq);
239  $jt->import('list.js');
240  echo '
241  <table data-xataface-query="'.htmlspecialchars($sq).'" id="result_list" class="listing resultList resultList--'.$this->_tablename.'">
242  <thead>
243  <tr>';
244  if ( $canSelect){
245  echo '<th><input type="checkbox" onchange="toggleSelectedRows(this,\'result_list\');"></th>';
246  }
247 
248  if ( !@$app->prefs['disable_ajax_record_details'] ){
249  echo ' <th><!-- Expand record column --></th>
250  ';
251  }
252  echo '<th class="row-actions-header"></th>';
253  $results =& $this->getResults();
254  $perms = array();
255 
256 
257  foreach ($this->_columns as $key){
258  $cursor=$this->_resultSet->start();
259  $results->reset();
260  $perms[$key] = false;
261  while ( $results->hasNext() ){
262  $record = $results->next();
263  if ( $record->checkPermission('list', array("field"=>$key)) ){
264  $perms[$key] = true;
265  break;
266  }
267  }
268  }
269 
270  $numCols = 0;
271 
272  $rowHeaderHtml = $this->renderRowHeader();
273  if ( isset($rowHeaderHtml) ){
274  echo $rowHeaderHtml;
275  } else {
276 
277 
278 
279  foreach ($this->_columns as $key ){
280  if ( in_array($key, $this->_columns) ){
281  //if ( !($perms[$key] = Dataface_PermissionsTool::checkPermission('list', $this->_table, array('field'=>$key)) /*Dataface_PermissionsTool::view($this->_table, array('field'=>$key))*/) ) continue;
282  if ( !@$perms[$key] ) continue;
283  if ( isset($sort_columns[$key]) ){
284  $class = 'sorted-column-'.$sort_columns[$key];
285  $query = array();
286  $qs_columns = $sort_columns;
287  unset($qs_columns[$key]);
288  $sort_query = $key.' '.($sort_columns[$key] == 'desc' ? 'asc' : 'desc');
289  foreach ( $qs_columns as $qcolkey=> $qcolvalue){
290  $sort_query .= ', '.$qcolkey.' '.$qcolvalue;
291  }
292  } else {
293  $class = 'unsorted-column';
294  $sort_query = $key.' asc';
295  foreach ( $sort_columns as $scolkey=>$scolvalue){
296  $sort_query .= ', '.$scolkey.' '.$scolvalue;
297  }
298 
299  }
300  $sq = array('-sort'=>$sort_query);
301  $link = Dataface_LinkTool::buildLink($sq);
302  $numCols++;
303  $label = $this->_table->getFieldProperty('column:label', $key);
304  $legend = $this->_table->getFieldProperty('column:legend', $key);
305  if ( $legend ){
306  $legend = '<span class="column-legend">'.htmlspecialchars($legend).'</span>';
307  }
308 
309  $colType = $this->_table->getType($key);
310  $class .= ' coltype-'.$colType;
311  $cperms = $this->_table->getPermissions(array('field'=>$key));
312  if ( !$this->_table->isSearchable($key) or !@$cperms['find'] ){
313  $class .= ' unsearchable-column';
314  } else {
315  $class .= ' searchable-column';
316  }
317 
318  $class .= ' '.$this->getHeaderCellClass($key);
319 
320  if ( !$label ) $label = $this->_table->getFieldProperty('widget:label',$key);
321  echo "<th data-column=\"$key\" class=\"$class\"><a href=\"$link\">".htmlspecialchars($label)."</a> $legend</th>";
322  }
323  }
324  }
325  echo "</tr>
326  </thead>
327  <tbody>
328  ";
329 
330 
331  $cursor=$this->_resultSet->start();
332  $results->reset();
333  $baseQuery = array();
334  foreach ( $_GET as $key=>$value){
335  if ( strpos($key,'-') !== 0 ){
336  $baseQuery[$key] = $value;
337  }
338  }
339  $evenRow = false;
340  while ($results->hasNext() ){
341  $rowClass = $evenRow ? 'even' : 'odd';
342  $evenRow = !$evenRow;
343  $record =& $results->next();
344  $recperms = $record->getPermissions();
345 
346  if ( !@$recperms['view'] ){
347  $cursor++;
348  unset($record);
349  continue;
350  }
351  $rowClass .= ' '.$this->getRowClass($record);
352 
353 
354 
355  $query = array_merge( $baseQuery, array( "-action"=>"browse", "-relationship"=>null, "-cursor"=>$cursor++) );
356 
357  if ( @$recperms['link'] ){
358  if ( @$app->prefs['result_list_use_geturl'] ){
359  $link = $record->getURL('-action=view');
360  } else {
361 
362  $link = Dataface_LinkTool::buildLink($query).'&-recordid='.urlencode($record->getId());
363  }
364  } else {
365  $del =& $record->_table->getDelegate();
366  if ( $del and method_exists($del, 'no_access_link') ){
367  $link = $del->no_access_link($record);
368  } else {
369  $link = null;
370  }
371  }
372  $recordid = $record->getId();
373 
374 
375  echo "<tr class=\"listing $rowClass\">";
376  if ( $canSelect ) {
377  $permStr = array();
378  foreach ($recperms as $pk=>$pv){
379  if ( $pv ) $permStr[] = $pk;
380  }
381  $permStr = htmlspecialchars(implode(',', $permStr));
382  echo '<td class="checkbox-cell"><input class="rowSelectorCheckbox" xf-record-id="'.htmlspecialchars($recordid).'" id="rowSelectorCheckbox:'.htmlspecialchars($recordid).'" type="checkbox" data-xf-permissions="'.$permStr.'"></td>';
383  }
384 
385 
386 
387 
388  if ( !@$app->prefs['disable_ajax_record_details'] ){
389  echo '<td class="ajax-record-details-cell">';
390  echo '<script language="javascript" type="text/javascript"><!--
391  registerRecord(\''.addslashes($recordid).'\', '.$record->toJS(array()).');
392  //--></script>
393  <img src="'.DATAFACE_URL.'/images/treeCollapsed.gif" onclick="resultList.showRecordDetails(this, \''.addslashes($recordid).'\')"/>';
394 
395 
396  echo '</td>';
397  unset($at, $actions);
398  }
399 
400  $at =& Dataface_ActionTool::getInstance();
401  $actions = $at->getActions(array('category'=>'list_row_actions', 'record'=>&$record));
402  //print_r($actions);
403  echo '<td class="row-actions-cell">';
404  if ( count($actions)>0){
405  echo ' <span class="row-actions">';
406  foreach ($actions as $action){
407  echo '<a href="'.htmlspecialchars($action['url']).'" class="'.htmlspecialchars($action['class']).' '.(@$action['icon']?'with-icon':'').'" '.(@$action['icon']?' style="'.htmlspecialchars('background-image: url('.$action['icon'].')').'"':'').(@$action['target']?' target="'.htmlspecialchars($action['target']).'"':'').' title="'.htmlspecialchars(@$action['description']?$action['description']:$action['label']).'"><span>'.htmlspecialchars($action['label']).'</span></a> ';
408  }
409  echo '</span>';
410  }
411  echo '</td>';
412 
413 
414 
415  $rowContentHtml = $this->renderRow($record);
416  if ( isset($rowContentHtml) ){
417  echo $rowContentHtml;
418  } else {
419  //$expandTree=false; // flag to indicate when we added the expandTree button
420  //if ( @$app->prefs['enable_ajax_record_details'] === 0 ){
421  // $expandTree = true;
422  //}
423 
424  foreach ($this->_columns as $key){
425  $thisField =& $record->_table->getField($key);
426  if ( !$perms[$key] ) continue;
427 
428  $val = $this->renderCell($record, $key);
429  if ( $record->checkPermission('edit', array('field'=>$key)) and !$record->_table->isMetaField($key)){
430  $editable_class = 'df__editable_wrapper';
431  } else {
432  $editable_class = '';
433  }
434 
435  if ( !@$thisField['noLinkFromListView'] and $link and $val ){
436  $val = "<a href=\"$link\" class=\"unmarked_link\">".$val."</a>";
437  $editable_class = '';
438  } else {
439 
440  }
441 
442  if ( @$thisField['noEditInListView'] ) $editable_class='';
443 
444 
445  $cellClass = 'resultListCell resultListCell--'.$key;
446  $cellClass .= ' '.$record->table()->getType($key);
447  echo "<td id=\"td-".rand()."\" class=\"field-content $cellClass $rowClass $editable_class\">&nbsp;$val</td>";
448  unset($thisField);
449  }
450  }
451  echo "</tr>";
452 
453  echo "<tr class=\"listing $rowClass\" style=\"display:none\" id=\"{$recordid}-row\">";
454  if ( $canSelect ){
455  echo "<td><!--placeholder for checkbox col --></td>";
456  }
457  echo '<td><!-- placeholder for actions --></td>';
458  echo "<td colspan=\"".($numCols+1)."\" id=\"{$recordid}-cell\"></td>
459  </tr>";
460 
461  unset($record);
462  }
463  if ( @$app->prefs['enable_resultlist_add_row'] ){
464  echo "<tr id=\"add-new-row\" df:table=\"".htmlspecialchars($this->_table->tablename)."\">";
465  if ( $canSelect ) $colspan=2;
466  else $colspan = 1;
467  echo "<td colspan=\"$colspan\"><script language=\"javascript\">require(DATAFACE_URL+'/js/addable.js')</script><a href=\"#\" onclick=\"df_addNew('add-new-row');return false;\">".df_translate('scripts.GLOBAL.LABEL_ADD_ROW', "Add Row")."</a></td>";
468  foreach ( $this->_columns as $key ){
469  echo "<td><span df:field=\"".htmlspecialchars($key)."\"></span></td>";
470  }
471  echo "</tr>";
472  }
473  echo "</tbody>
474  </table>";
475  if ( $canSelect ){
476  echo '<form id="result_list_selected_items_form" method="post" action="'.df_absolute_url(DATAFACE_SITE_HREF).'">';
477  $app =& Dataface_Application::getInstance();
478  $q =& $app->getQuery();
479  foreach ( $q as $key=>$val){
480  if ( strlen($key)>1 and $key{0} == '-' and $key{1} == '-' ){
481  continue;
482  }
483  echo '<input type="hidden" name="'.urlencode($key).'" value="'.htmlspecialchars($val).'" />';
484  }
485  echo '<input type="hidden" name="--selected-ids" id="--selected-ids" />';
486  echo '<input type="hidden" name="-from" id="-from" value="'.$q['-action'].'" />';
487  echo '<input type="hidden" name="--redirect" value="'.base64_encode($app->url('')).'" />';
488  echo '</form>';
489 
490 
491  import('Dataface/ActionTool.php');
492  $at =& Dataface_ActionTool::getInstance();
493  $actions = $at->getActions(array('category'=>'selected_result_actions'));
494  if ( count($actions) > 0){
495  echo '<div id="selected-actions">'.df_translate('scripts.Dataface_ResultList.MESSAGE_WITH_SELECTED', "With Selected").': <ul class="selectedActionsMenu" id="result_list-selectedActionsMenu">';
496  foreach ($actions as $action){
497  $img = '';
498  if ( @$action['icon'] ){
499  $img = '<img src="'.$action['icon'].'"/>';
500  }
501 
502  if ( !@$action['onclick'] and !$action['url'] ){
503  $action['onclick'] = "return actOnSelected('result_list', '".@$action['name']."'".(@$action['confirm']?", function(){return confirm('".addslashes($action['confirm'])."');}":"").")";
504 
505  }
506 
507  echo <<<END
508  <li id="action-{$action['id']}"><a href="{$action['url']}" onclick="{$action['onclick']}" title="{$action['description']}">{$img}{$action['label']}</a></li>
509 END;
510  }
511 
512 
513  echo '</ul></div>';
514  }
515  }
516 
517  if ( @$app->prefs['use_old_resultlist_controller'] ){
518  echo '<div class="resultlist-controller" id="resultlist-controller-bottom">';
519 
520  echo $controller;
521  echo '</div>';
522  }
523 
524 
525  $out = ob_get_contents();
526  ob_end_clean();
527  } else {
528  if ( @$app->prefs['use_old_resultlist_controller'] ){
529  ob_start();
530  df_display(array(), 'Dataface_ResultListController.html');
531  $out = ob_get_contents();
532  ob_end_clean();
533  } else {
534  $out = '';
535  }
536  $out .= "<p style=\"clear:both\">".df_translate('scripts.GLOBAL.MESSAGE_NO_MATCH', "No records matched your request.")."</p>";
537  }
538 
539  return $out;
540  }
541 
542  function getRowClass(&$record){
543  $del =& $this->_table->getDelegate();
544  if ( isset($del) and method_exists($del, 'css__tableRowClass') ){
545  return $del->css__tableRowClass($record);
546  }
547  return '';
548  }
549 
550  function getHeaderCellClass($col){
551  $del =& $this->_table->getDelegate();
552  if ( isset($del) and method_exists($del, 'css__tableHeaderCellClass') ){
553  return $del->css__tableHeaderCellClass($col);
554  }
555  return '';
556  }
557 
558  function getResultFilters(){
559  ob_start();
560  $app =& Dataface_Application::getInstance();
561  $query =& $app->getQuery();
562 
563  echo '<div class="resultlist-filters">
564  <h3>'.df_translate('scripts.Dataface_ResultList.MESSAGE_FILTER_RESULTS', 'Filter Results').':</h3>
565  <script language="javascript"><!--
566 
567  function resultlist__updateFilters(col,select){
568  var currentURL = "'.$app->url('').'";
569  var currentParts = currentURL.split("?");
570  var currentQuery = "?"+currentParts[1];
571  var value = select.options[select.selectedIndex].value;
572  var regex = new RegExp(\'([?&])\'+col+\'={1,2}[^&]*\');
573  if ( currentQuery.match(regex) ){
574  if ( value ){
575  prefix = "=";
576  } else {
577  prefix = "";
578  }
579  currentQuery = currentQuery.replace(regex, \'$1\'+col+\'=\'+prefix+encodeURIComponent(value));
580  } else {
581  currentQuery += \'&\'+col+\'==\'+encodeURIComponent(value);
582  }
583  currentQuery = currentQuery.replace(/([&\?])-skip=[^&]+/, "$1");
584  window.location=currentParts[0]+currentQuery;
585  }
586  //--></script>
587  <ul>';
588 
589  $qb = new Dataface_QueryBuilder($this->_table->tablename, $query);
590  foreach ( $this->_filterCols as $col ){
591  $field =& $this->_table->getField($col);
592 
593  unset($vocab);
594  if ( isset($field['vocabulary']) ){
595  $vocab =& $this->_table->getValuelist($field['vocabulary']);
596 
597  } else {
598  $vocab=null;
599 
600  }
601 
602  echo '<li> '.htmlspecialchars($field['widget']['label']).' <select onchange="resultlist__updateFilters(\''.addslashes($col).'\', this);"><option value="">'.df_translate('scripts.GLOBAL.LABEL_ALL', 'All').'</option>';
603 
604  $res = df_query("select `$col`, count(*) as `num` ".$qb->_from()." ".$qb->_secure( $qb->_where(array($col=>null)) )." group by `$col`", null, true);
605  if ( !$res and !is_array($res)) trigger_error(mysql_error(df_db()), E_USER_ERROR);
606  if ( @$query[$col] and $query[$col]{0} == '=' ) $queryColVal = substr($query[$col],1);
607 
608  else $queryColVal = @$query[$col];
609 
610  //while ( $row = mysql_fetch_assoc($res) ){
611  foreach ($res as $row){
612  if ( isset($vocab) and isset($vocab[$row[$col]]) ){
613  $val = $vocab[$row[$col]];
614  } else {
615  $val = $row[$col];
616  }
617 
618  if ( $queryColVal == $row[$col] ) $selected = ' selected';
619  else $selected = '';
620  echo '<option value="'.htmlspecialchars($row[$col]).'"'.$selected.'>'.htmlspecialchars($val).' ('.$row['num'].')</option>';
621 
622  }
623  //@mysql_free_result($res);
624  echo '</select></li>';
625  }
626  echo '</ul></div>';
627  $out = ob_get_contents();
628  ob_end_clean();
629  return $out;
630 
631 
632  }
633 
634 
635 
636 
637 
638  }
639