CMS  Version 3.9
report_manager.inc
Go to the documentation of this file.
1 <?php
7 /**************************************************************
8 
9  Copyright (c) 2007-2010 Sonjara, Inc
10 
11  Permission is hereby granted, free of charge, to any person
12  obtaining a copy of this software and associated documentation
13  files (the "Software"), to deal in the Software without
14  restriction, including without limitation the rights to use,
15  copy, modify, merge, publish, distribute, sublicense, and/or sell
16  copies of the Software, and to permit persons to whom the
17  Software is furnished to do so, subject to the following
18  conditions:
19 
20  The above copyright notice and this permission notice shall be
21  included in all copies or substantial portions of the Software.
22 
23  Except as contained in this notice, the name(s) of the above
24  copyright holders shall not be used in advertising or otherwise
25  to promote the sale, use or other dealings in this Software
26  without prior written authorization.
27 
28  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
29  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
30  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
31  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
32  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
33  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
34  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
35  OTHER DEALINGS IN THE SOFTWARE.
36 
37 *****************************************************************/
38 
39 Fakoli::usingFeature("tree", "search_form", "data_view");
40 
119 {
121  var $title;
123  var $resultsPage = "custom_report_results";
124  var $editorPage = "custom_report";
125  var $target;
127  var $tables;
128  var $join;
129  var $report;
130  var $request;
132  var $limit;
133 
137  function ReportManager()
138  {
139  $this->selected = null;
140 
141  $this->report_id = 0;
142  $this->tables = array();
143  $this->request = $_REQUEST; // For config serialization
144  if ($this->request["custom_report_mode"] == "save")
145  {
146  $this->title = $this->request["custom_report_title"];
147  $this->description = $this->request["custom_report_description"];
148  }
149  }
150 
156  function __sleep()
157  {
158  $this->request = array_remove_keys($this->request, "custom_report_mode", "PHPSESSID", "__utma", "__utmb", "__utmc", "__utmz");
159  return array('report_id', 'title', 'description', 'request');
160  }
161 
167  function table($class, $title = "", $useOutputFilter = false)
168  {
169  $table = new ReportTable($class, $title, $this->request, $useOutputFilter);
170  $this->tables[] = $table;
171  return $table;
172  }
173 
181  static function deleteUser($user)
182  {
183  $pk = $user->getPrimaryKey();
184  $user_id = $user->$pk;
185 
186  trace("Component report_manager is deleting objects dependent on user_id {$user_id}", 3);
187 
188  $report = new CustomReport();
189  $report->delete("WHERE user_id={$user_id}");
190 
191  return $user;
192  }
193 
197  function drawForm()
198  {
199  global $script;
200 
201  $tableNavigator = new ReportTableNavigator($this->tables);
202  $filterNavigator = new ReportFilterNavigator($this->tables);
203 
204  $script .= $tableNavigator->writeScript();
205  $script .= $filterNavigator->writeScript();
206 
207  $action = $this->resultsPage ? $this->resultsPage : Fakoli::scriptName();
208  $target = $this->target ? " target='{$this->target}'" : "";
209 
210  $interstitial = $this->interstitialMessage ? " onsubmit='interstitial(\"".jsSafe($this->interstitialMessage).".\"); return true;'" : "";
211  $targetClass = CustomReportManager::getTarget($this);
212 
213  echo "<form method='POST' action='$action?report_id={$_GET['report_id']}&target={$targetClass}'$target id='custom_report'$interstitial>";
214  echo "<input type='hidden' name='report_id' value='{$this->report_id}'/>";
215  echo "<input type='hidden' name='custom_report_mode' value='search' id='custom_report_mode'/>";
216  echo "<input type='hidden' name='custom_report_title' value='' id='custom_report_title'/>";
217  echo "<input type='hidden' name='custom_report_description' value='' id='custom_report_description'/>";
218  $tableNavigator->drawView();
219  $filterNavigator->drawView();
220 
221 
222  $incomplete = ($this->request["__include_incomplete"] || $this->report_id == 0) ? " checked='checked'" : "";
223  $excel = $this->request["__excel"] ? " checked='checked'": "";
224 
225  echo "<div style='clear: left'><br/><input type='checkbox' name='__include_incomplete' value='1'$incomplete/> <strong>Include Incomplete Records in Results</strong>";
226  //echo "<br/><input type='checkbox' name='__excel' value='custom_report'$excel/> <strong>Output as Excel</strong>";
227  echo "<br/><br/><input type='submit' class='button' value='Generate Report'/>";
228  //echo "&nbsp;&nbsp;<button class='button' onclick='ReportManager.saveReport($this->report_id); return false;'>Save Report</button>";
229  echo "</div></form>";
230  }
231 
236  function getTable($class)
237  {
238  foreach($this->tables as $table)
239  {
240  if ($table->class == $class) return $table;
241  }
242 
243  return null;
244  }
245 
246  /*
247  * JDG 2/2012 avoid crash when user does not
248  * check any tables/columns - by default
249  * check first table and all its columns.
250  * If table is checked w/o any columns checked
251  * then select all columns in that table.
252  */
253  function populateSelected()
254  {
255  if ($this->request["custom_report_title"]) $this->title = $this->request["custom_report_title"];
256  if ($this->request["custom_report_description"]) $this->description = $this->request["custom_report_description"];
257 
258  foreach($this->tables as $table)
259  {
260  if ($this->request["table_{$table->class}"])
261  {
262  $this->setSelected($table);
263  }
264  $table->load($this->request);
265  }
266 
267  if(count($this->selected) == 0)
268  {
269  $this->setSelected($this->tables[0], false);
270  }
271 
272  }
273 
280  function setSelected($table, $checkRequest = true)
281  {
282  $table->selected = true;
283  $this->selected["table_{$table->class}"] = true;
284  //$this->constraint .= $table->getConstraint($first, $this->request);
285 
286  $colCount = 0;
287  foreach($table->columns as $column)
288  {
289  $col = "column_".codify($column->title);
290  if ($this->request[$col] || !$checkRequest)
291  {
292  $column->selected = true;
293  $this->selected[$col] = true;
294  $colCount ++;
295  }
296  }
297 
298  if($colCount == 0)
299  $this->setSelected($table, false);
300  }
301 
308  {
309  $first = true;
310  foreach($this->tables as $table)
311  if ($this->selected["table_{$table->class}"])
312  {
313  $this->constraint .= $table->getConstraint($first, $this->request);
314  $first = false;
315  }
316 
317  if($this->limit && is_numeric($this->limit))
318  {
319  $this->constraint .= " LIMIT {$this->limit}";
320  }
321 
322  //var_dump($this->request);
323  }
324 
325  function generateReport($inlineScript = false)
326  {
327  $t1 = microtime(true);
328 
329  $this->preSaveReport();
330 
331  global $script;
332 
333  if (!isset($this->selected))
334  {
335  $this->populateSelected();
336  }
337 
338  $this->generateConstraint();
339 
340  if ($_REQUEST["custom_report_mode"] == "save")
341  {
342  $this->save();
343  return;
344  }
345 
346  $joinClass = ($this->request['__include_incomplete'] == "1") ? LeftOuterJoin : InnerJoin;
347 
348  $this->join = new $joinClass;
349  $this->join->unique = true;
350  $constraint .= "";
351  $first = true;
352 
353  foreach($this->tables as $table)
354  {
355  if ($this->selected["table_{$table->class}"])
356  {
357  $table->createJoin($this);
358 
359 
360  $first = false;
361  }
362  }
363 
364  //echo "constraint {$this->constraint}<br>";
365  $results = $this->join->iteratedQuery($this->constraint);
366 
367  $report = new DataListView($results, "custom_report_results");
368 
369  foreach($this->tables as $table)
370  {
371  if ($this->selected["table_{$table->class}"])
372  {
373  $table->createColumns($this, $report);
374  }
375  }
376 
377  $report->dragColumnReorder = true;
378  $report->columnReorderCallback = "ReportManager.setColumnOrder";
379 
380  if ($this->columnOrder)
381  {
382  $report->setColumnOrder(explode("|",$this->columnOrder));
383  }
384 
385  $reportTitle = ($this->title) ? codify($this->title)."_".date("Y_m_d_Hi") : "custom_report_".date("Y_m_d_Hi");
386  //if(isset($this->request["__excel"]))
387  //{
388  $report->excelFile = $reportTitle . ".xls";
389  //}
390  /* to do - pdf output
391  else
392  {
393  $identifier = $this->request["identifier"];
394  $pdf = new PDF("/$identifier", false, true, $reportTitle . ".pdf");
395  $pdf->generate();
396  }
397  */
398  if ($inlineScript)
399  {
400  echo $report->writeScript();
401  }
402  else
403  {
404  $script .= $report->writeScript();
405  }
406 
407  $report->cssClass = "list small";
408  $report->hideExcelIcon = true;
409 
410  $excel = $report->getExcelLink();
411 
412  if($this->limit && count($results))
413  {
414  echo "<p class='report_limit'>Results are limited to the first {$this->limit} records.</p>\n";
415  }
416 
418 
419  $report->drawView();
420 
421  if (count($results))
422  {
424  }
425 
426  $t2 = microtime(true);
427  $elapsed = ($t2 - $t1);
428  echo "<p><em>Report generated in ".number_format($elapsed, 2)." seconds</em></p>";
429  }
430 
431  function isColumnSelected($column)
432  {
433  return $this->selected["column_".codify($column->title)];
434  }
435 
437  {
439  echo "<p><a class='button' href='#' onclick='go(\"{$url}?report_id={$this->report_id}&edit=1\"); return false;'><i class='fa-fw fas fa-pencil-alt'></i> Edit Report Settings</a></p>";
440  echo "<p><a class='button' href='#' onclick='ReportManager.updateReport(\"{$this->report_id}\"); return false;'><i class='fa-fw far fa-save'></i> Save Report</a>\n";
441  echo "&nbsp;&nbsp;<a class='button' href='#' onclick='ReportManager.exportToExcel(\"$excel\"); return false;'><i class='fa-fw far fa-file-excel'></i> Export to Excel</a>";
442  echo "\n&nbsp;&nbsp;<a class='button' href='#' onclick='ReportManager.createReportDialog();return false;'><i class='fa-fw fas fa-plus'></i> Create a New Report</a></p>\n";
443  }
444 
449  function preSaveReport()
450  {
451  global $user;
452 
453  if (!$this->report_id)
454  {
455  $sleeper = serialize($this);
456  $mgr = new UserManager();
457 
458  $this->report_id = "tmp-" .sha1($user->get($mgr->getUsernameField()) . $sleeper . now());
459  $_SESSION[$this->report_id] = $sleeper;
460  $_SESSION[$this->report_id . "-class"] = get_class($this);
461  }
462  }
463 
464  function save()
465  {
466  global $user;
467 
468  $report = new CustomReport();
469  $report->report_id = $this->report_id;
470  $report->title = $this->title;
471  $report->description = $this->description;
472  $report->user_id = $user->user_id;
473  $report->configuration = serialize($this);
474  $report->manager_class = get_class($this);
475  $report->column_order = $this->columnOrder;
476 
477  $report->save();
478 
479  $this->report_id = $report->report_id;
480  }
481 
482  static function load($report_id, $mode = "")
483  {
484  if (is_numeric($report_id))
485  {
487 
488  $proto = unserialize($report->configuration);
489  $cl = get_class($proto);
490 
491  if ($mode != "save")
492  {
493  //$_REQUEST = $proto->request;
494  }
495 
496  $instance = new $cl;
497  $instance->request = ($mode == "save" || $mode == "post") ? $_REQUEST : $proto->request;
498 
499  $instance->populateSelected();
500  $instance->columnOrder = $report->column_order;
501  $instance->report_id = $report_id;
502  return $instance;
503  }
504  else if (startsWith($report_id, "tmp-"))
505  {
506  $proto = unserialize($_SESSION[$report_id]);
507  $cl = get_class($proto);
508  $instance = new $cl;
509  $instance->request = $proto->request;
510  $instance->populateSelected();
511  $instance->columnOrder = $report->column_order;
512  $instance->report_id = 0;
513  return $instance;
514  }
515  else
516  {
517  throw new FakoliException("Invalid report id");
518  }
519  }
520 
521  function fromRequest()
522  {
523  $this->request = $_REQUEST;
524  $this->populateSelected();
525  }
526 }
527 
529 {
530  var $class;
531  var $proto;
532  var $fields;
533  var $title;
534  var $form;
536  var $params;
537  var $help;
539  var $columns = array();
540  var $footerColumns = array();
541  var $totalCallbacks = array();
542  var $outputFilter = null;
543 
544  function __construct($class, $title = "", $request = null, $outputFilter = false)
545  {
546  $this->class = $class;
547  $this->proto = new $class;
548 
549  if ($outputFilter)
550  {
551  $this->outputFilter = new InclusionFilter();
552  $this->outputFilter->add($this->proto->getPrimaryKey());
553  }
554 
555  $this->fields = array();
556  $this->title = $title ? $title : $this->proto->prettifyClassName();
557  $this->constraints = $constraints;
558 
559  if ($request) $this->proto->fromDataSet($request);
560 
561  $this->form = new SearchForm($this->proto);
562  $this->form->id = "custom_report";
563  // JDG 2/2012 - set params here to allow additional parameters
564  // to be set by the calling class
565  $this->params = $this->form->params; //new SearchParameters($this->proto);
566 
567  $this->excludedKeys = array();
568  $this->help = "";
569  }
570 
571  // Control Serialization ----
572 
573  function __sleep()
574  {
575  return array('class', 'fields', 'title', 'constraints', 'params', 'excludedKeys', 'columns', 'footerColumns', 'totalCallbacks');
576  }
577 
578  function __wakeup()
579  {
580  $this->proto = new $this->class;
581  $this->form = new SearchForm($this->proto);
582  }
583 
584  function load($request)
585  {
586  if ($request)
587  {
588  $this->proto->fromDataSet($request);
589  $this->params->fromArray($request);
590  $this->form->params->fromArray($request);
591  }
592  }
593 
594  function getFields() { return $this->fields; }
595 
596  // ----
597 
598  function column($title, $format, $sortable = true, $style = "", $type = null, $outputFields = null)
599  {
600  $this->columns[] = new ReportColumn($title, $format, $sortable, $style, $type, $outputFields);
601 
602  return $this;
603  }
604 
605  function excludeKeyFromJoin($key)
606  {
607  $this->excludedKeys[] = $key;
608  return $this;
609  }
610 
611  function getTitle()
612  {
613  return $this->title;
614  }
615 
616  function searchFields()
617  {
618  $filter = new InclusionFilter();
619  for($i = 0; $i < func_num_args(); ++$i)
620  {
621  // JDG 2/2012 - putting func_get_arg inside the explode crashes for me if not
622  $fieldMode = func_get_arg($i);
623  list($field, $searchMode) = explode(":", $fieldMode);
624  $filter->add($field);
625  if ($searchMode)
626  {
627  $this->form->setMatchingMode($searchMode, $field);
628  }
629  }
630 
631  $this->proto->filter = $filter;
632  return $this;
633  }
634 
635  function favoriteColumns()
636  {
637  $args = func_get_args();
638  $favorites = array_combine($args, $args);
639  foreach($this->columns as $column)
640  {
641  if (array_key_exists($column->title, $favorites))
642  {
643  $column->favorite = true;
644  }
645  }
646 
647  return $this;
648  }
649 
651  {
652  $this->constraints = $constraint;
653 
654  return $this;
655  }
656 
657  function getConstraint($first, $request)
658  {
659  $this->params->fromArray($request);
660  $constraint = $this->params->generateConstraint($first);
661 
662  if ($this->constraints)
663  {
664  if ($constraint)
665  {
666  $constraint .= " AND $constraint";
667  }
668  else
669  {
670  $constraint .= ($first ? "WHERE " : " AND ").$this->constraints;
671  }
672  }
673 
674  return $constraint;
675  }
676 
677  function configureForm($configurator = null)
678  {
679  if ($configurator) $configurator($this->form);
680  }
681 
689  function footerText($text = "", $style = "")
690  {
691  $column = new ReportFooterColumn($title, $text, "", $style);
692  $this->footerColumns[$title] = $column;
693 
694  return $column;
695  }
696 
704  function footerValue($title, $callback, $style = "")
705  {
706  $column = new FooterValueColumn($callback, $style);
707  $this->footerColumns[$title] = $column;
708 
709  return $column;
710  }
711 
719  function footerTotal($title, $field, $style = "text-align: right")
720  {
721  $column = new FooterTotalColumn($field, $template = "", $style);
722  $this->footerColumns[$title] = $column;
723 
724  return $column;
725  }
726 
727  function createJoin($mgr)
728  {
729  $mgr->join->add($this->class);
730  $this->filterOutput($mgr, $this->class);
731 
732  foreach($this->excludedKeys as $excluded)
733  {
734  $mgr->join->excludeKeyFromJoin($this->class, $excluded);
735  }
736  }
737 
739  {
740  if ($this->outputFilter)
741  {
742  foreach($this->columns as $column)
743  {
744  if ($mgr->isColumnSelected($column) && is_array($column->outputFields))
745  {
746  foreach($column->outputFields as $field)
747  {
748  $this->outputFilter->add($field);
749  }
750  }
751  }
752 
753  $mgr->join->setFilter($class, $this->outputFilter);
754  }
755  }
756 
758  {
759  foreach($this->columns as $column)
760  {
761  if ($mgr->isColumnSelected($column))
762  {
763  $report->column($column->title, $column->format, $column->sortable, $column->style, $column->type);
764 
765  if(array_key_exists($column->title, $this->footerColumns))
766  {
767  $footer = $this->footerColumns[$column->title];
768  $report->footerColumns[] = $footer;
769  if(get_class($footer) == FooterTotalColumn)
770  {
771  $report->totalCallbacks[] = array($footer, onStartRow);
772  }
773  }
774  else
775  {
776  $report->footerColumns[] = new FooterTextColumn("&nbsp;");
777  }
778  }
779  }
780  }
781 }
782 
784 {
785  var $title;
786  var $format;
787  var $style;
789  var $type;
791 
792  function ReportColumn($title, $format, $sortable = true, $style = "", $type = null, $outputFields = null)
793  {
794  $this->title = $title;
795  $this->format = $format;
796  $this->sortable = $sortable;
797  $this->style = $style;
798  $this->type = $type;
799  $this->outputFields = $outputFields;
800  }
801 }
802 ?>
$constraint
$table sortable
$user_id
$helpTree style
Definition: tree.inc:46
$table onStartRow
$siteTree target
Definition: site_map.inc:56
$filter
Definition: update.inc:44
$bookmark title
static getTarget($manager)
FakoliException is the base exception class for all Fakoli errors.
Definition: core.inc:53
static scriptName()
Returns the name of the currently executing script.
Definition: core.inc:1446
static usingFeature()
Uses the specified framework feature(s).
Definition: core.inc:388
ReportColumn($title, $format, $sortable=true, $style="", $type=null, $outputFields=null)
ReportManager provides a generic mechanism for implementing user configurable reports based on DataIt...
$columnOrder
Override the default column order.
$resultsPage
The page used to display the search results.
setSelected($table, $checkRequest=true)
Set the given table as selected.
$interstitialMessage
Optional interstitial message to display while report is being generated.
preSaveReport()
Alternative implementation - presave version of report is stored in session with a.
generateConstraint()
Generate the search constraint for the current report configuration.
__sleep()
Tidy up the request data and limit the fields returned when serializing a ReportManager.
writeResultsFormButtons($excel)
$target
The target window for the form results.
static deleteUser($user)
Respond to fired event DeleteUser.
getTable($class)
Retrieve the ReportTable object for the specified DataItem class.
$description
Longer description of the report.
$limit
Optional limit to number of records retrieved, depending on acceptable load times for results.
$report_id
ID of the saved report as persisted in the database.
generateReport($inlineScript=false)
table($class, $title="", $useOutputFilter=false)
Add a table to the report.
$editorPage
The page used to display the report editor.
$title
Title of the report.
ReportManager()
Creates a new ReportManager instance.
static load($report_id, $mode="")
isColumnSelected($column)
drawForm()
Draw the configuration/search form.
createColumns($mgr, $report)
filterOutput($mgr, $class)
__construct($class, $title="", $request=null, $outputFilter=false)
getConstraint($first, $request)
additionalConstraints($constraint)
footerText($text="", $style="")
Adds a text column to the table footer.
footerValue($title, $callback, $style="")
Adds a value column to the table footer.
excludeKeyFromJoin($key)
configureForm($configurator=null)
load($request)
footerTotal($title, $field, $style="text-align: right")
Sums the total for a column.
column($title, $format, $sortable=true, $style="", $type=null, $outputFields=null)
Provides the interface to the user model for the application.
global $user
$notes columns
Definition: edit.inc:66
$mode
$_SESSION["useMobile"]
Definition: override.inc:7
if(! $blog->published||! $blog->enable_rss_feed||!checkRole($blog->allow_read)) $url
Definition: rss.inc:58
$action
Definition: run.php:41
$report
Definition: save_report.inc:38