class/ReportController.php
<?php
namespace Homestead;
use \Homestead\Exception\DatabaseException;
use \PHPWS_Error;
use \PHPWS_DB;
// Location to save the generated report files
// I'd rather this be a private static or class const, but you can't
// calculate the path dynamically that way.
define('HMS_REPORT_PATH', PHPWS_SOURCE_DIR . 'files/hms_reports/');
/**
* ReportController - Central report controller. Provides much of the functionality
* needed to setup, execute, and save reports.
*
* @author jbooker
* @package HMS
*/
abstract class ReportController {
// The report object we're controlling/wraping.
protected $report;
// The full path name for the output file of this report (without an extension)
// This is computed at run-time based on the report's completion date so that all output
// files from the same report have the same timestamp.
protected $fileName;
// Local storage of view objects, can be null if not implemented by the report.
protected $htmlView;
protected $pdfView;
protected $csvView;
/**
* Constructor
* If a report is passed in, that report is used. Otherwise,
* a new instance of the appropriate report (based on this controller's
* class name) will be instantiated.
*
* @param Report $report Optional Report object to use.
*/
public function __construct(Report $report = null)
{
if(isset($report) && !is_null($report)){
$this->report = $report;
}else{
$this->report = $this->getReportInstance();
}
}
/**
* Returns the class name of the report this object wraps,
* based on this controller's class name.
*
* @return String report's class name
*/
public function getReportClassName()
{
return preg_replace('/(.+\\\)(.+\\\)(.+)(\\\.+)/', '$3', get_class($this));
}
/**
* Returns a new instance of of the given report name.
*
* @return Report New instnace of this controller's report.
*/
private function getReportInstance()
{
$name = $this->getReportClassName();
$className = '\\Homestead\\Report\\' . $name . '\\' . $name;
return new $className;
}
/**
* Initalizes the report object we're wrapping. Sets creation dates/users.
*
* @param int $scheduledExecTime Unix timestamp of the time this report should be executed.
*/
public function newReport($scheduledExecTime)
{
$this->report = $this->getReportInstance();
$this->report->setCreatedBy(UserStatus::getUsername());
$this->report->setCreatedOn(time());
$this->report->setScheduledExecTime($scheduledExecTime);
$this->report->setBeganTimestamp(null);
$this->report->setCompletedTimestamp(null);
}
/**
* Returns the friendly name of the report we're wrapping. It's a shortcut
* method for $this->report->getFriendlyName().
*
* @return String This report's friendly name.
*/
public function getFriendlyName()
{
return $this->report->getFriendlyName();
}
/**
* Responsible for returning a View to be used on the ReportListView. Can be
* overridden to provide a custom menu item view, but must return an objects
* that extends the View class.
*
* @return View - View object to use as the report's menu item
*/
public function getMenuItemView()
{
$this->loadLastExec();
$view = new ReportMenuItemView($this->report, $this->getReportClassName());
return $view;
}
/**
* Returns a string of HTML that describes the report (what it does, etc).
*
* Default implementation looks for a file in hms/templates/admin/reports/<reportName>Desc.tpl
*
* @return String String of HTML for report's description, or null if the default file wasn't found.
*/
public function getDescription()
{
$fileName = PHPWS_SOURCE_DIR . 'mod/hms/templates/admin/reports/' . $this->getReportClassName() . 'Desc.tpl';
if(file_exists($fileName)){
return file_get_contents($fileName);
}else{
return null;
}
}
/**
*
*/
public function getSyncSetupView()
{
//TODO, and make the details view use this
}
/**
* Returns the ReportSetupView to show the UI for running
* this report in the background.
*
* The default implementation (for iSyncReport) expects a class named
* '<reportName>SetupView.php, which extends ReportSetupView to be in
* the report's class directory. This functionality can be overridden
* by each report to return a different object, but it must return an
* object of type ReportSetupView.
*
*
* @see iSyncReport
* @see ReportSetupView
* @param bool $datePicker Whether or not the interface should show a date picker
* @return ReportSetupView The ReportSetupView responsible for showing the UI to run this report.
*/
public function getAsyncSetupView()
{
$view = new ReportSetupView($this->report);
$view->setLinkText('Run in background');
$view->setDialogId('reportBgDialog');
$view->setRunNow(true);
$view->setFormId('report-setup-form-bg');
return $view;
}
/**
* Returns the ReportSetupView to show the UI for scheduling
* the report.
*
* Default implementation just calls the getAsyncSetupView
* method with the datePicker = true parameter.
*
* @see getAsyncSetupView
* @return ReportSetupView - ReportSetupView for scheudling this report.
*/
public function getSchedSetupView()
{
$view = $this->getAsyncSetupView();
$view->setLinkText('Schedule run');
$view->setDialogId('reportSchedDialog');
$view->useDatePicker(true);
$view->setRunNow(false);
$view->setFormId('report-setup-form-sched');
return $view;
}
/**
* Default implementation for the iSyncReport interface.
* This can be overridden in a sub-class to provide a custom
* execution command. It must return a valid Command object
* to run the report synchronously.
*
* @see iSyncReport
* @return Command A Command object to run this report synchronously.
*/
public function getSyncExecCmd()
{
$cmd = CommandFactory::getCommand('ExecReportSync');
$cmd->setReportClass($this->getReportClassName());
return $cmd;
}
/**
* Sets the parameters for this report. Must be implemented
* by each controller. This method must take the values
* in the passed-in array and assign them to the proper
* member variables withthin the report.
*
* @param array $params
*/
public abstract function setParams(Array $params);
/**
* @return Array An Array of key=>value parameters for this report
*/
public abstract function getParams();
/**
* Shortcut method to save the report object that this controller contains.
*/
public function saveReport()
{
return $this->report->save();
}
/**
* Get's the file name from the Report this controller is wrapping and
* prepends the configured filesystem path. The file name that's generated
* will use the current date/time at the time this method is called.
*/
public function getFileName()
{
// Ask the report for it's file name
$this->fileName = HMS_REPORT_PATH . $this->report->getFileName();
}
/**
* Responsible for starting the execution of this controller's
* report, then getting and saving each of the implemented views.
*
* This is a default implementation, and could be overridden if necessary.
*
*/
public function generateReport()
{
// Set the start time
$this->report->setBeganTimestamp(time());
// Save that timestamp
$this->report->save();
// Execute the report
$this->report->execute();
/*
* Generate the report's full pathname (with a file extension),
* so that each output file type will have the same file name.
*/
$this->getFileName();
/*
* For each of the views we have, check to see if this controller
* implements the necessary interface. If so, call those methods
* to generate and save the view.
*/
// HTML
if($this instanceof iHtmlReportView){
$this->htmlView = $this->getHtmlView();
// Save the HTML output
$this->saveHtmlOutput($this->htmlView);
}
// PDF
if($this instanceof iPdfReportView){
// Commented out until we find a HTML->PDF converter that doesn't suck
//$this->pdfView = $this->getPdfView();
//Save the PDF output
//$this->savePdfOutput($this->pdfView);
}
// CSV
if($this instanceof iCsvReportView){
$this->csvView = $this->getCsvView();
$this->saveCsvOutput($this->csvView);
}
// Set the completion time
$this->report->setCompletedTimestamp(time());
// Save the report to save the completed timestamp
$this->report->save();
}
/**
* Default implementation of the iHtmlReportView interface. Returns
* a HTML view based on the report's class name in the form of:
* report/<reportName>/<reportName>HtmlView.php
*
* The generated class name must extend ReportHtmlView.
*
* @see iHtmlReportView
* @return ReportHtmlView
*/
public function getHtmlView()
{
$name = $this->getReportClassName();
$className = '\\Homestead\\Report\\' . $name .'\\' . $name . "HtmlView";
return new $className($this->report);
}
/**
* Default implementation for the iHtmlReportView interface. Responsible
* for appending a file extention to the file name, getting the output
* from the provided view, saving that output to a file, and storing the
* finished file name in the Report object.
*
* Can be overrriden if custom behavior is necessary.
*
* @see iHtmlReportView
* @param ReportHtmlView $htmlView
*/
public function saveHtmlOutput(ReportHtmlView $htmlView)
{
// Add the proper extension
$fileName = $this->fileName . '.html';
$fileResult = file_put_contents($fileName, $htmlView->show());
if($fileResult === FALSE){
//TODO throw exception
}
// Save the file name to the report
$this->report->setHtmlOutputFilename($fileName);
$this->report->save();
}
/**
* Default implementation for the iPdfReportView interface. Responsible
* for returning an object which extends the ReportPdfView class.
*
* This implementation expects a htmlView to exist and attempts to convert
* it to PDF. Override this to provide your own ReportPdfView
* implementation which generates a custom PDF for this controller's report.
*
* @see iHtmlReportView
* @return ReportPdfView
*/
public function getPdfView()
{
//TODO Check to make sure a HtmlView actually exists
$pdfView = new ReportPdfViewFromHtml($this->report, $this->htmlView);
return $pdfView;
}
/**
* Default implementation for the iPdfReportView interface. Responsible
* for appending a file extention to the file name, getting the output
* from the provided view, saving that output to a file, and storing the
* finished file name in the Report object.
*
* Can be overrriden if custom behavior is necessary.
*
* @see iHtmlReportView
* @param ReportPdfView $pdfView
*/
public function savePdfOutput(ReportPdfView $pdfView)
{
// Add the proper extension
$fileName = $this->fileName . '.pdf';
$pdfView->render();
$fileResult = file_put_contents($fileName, $pdfView->getPdfContent());
// Save the file name to the report
$this->report->setPdfOutputFilename($fileName);
$this->report->save();
}
/**
* Default implementation for the iCsvReportView interface. Responsible for
* returning an object which extends the ReportCsvView class.
*
* By default, it returns an instance of ReportCsvView. This can be overridden
* to return a custom view (but it must extend ReportCsvView). This view
* provides a default implementation of the iCsvReport interface to convert
* report data to the csv format from an array.
*
* @see iHtmlCsvView
* @return ReportCsvView
*/
public function getCsvView(){
$csvView = new ReportCsvView($this->report);
return $csvView;
}
/**
* Default implementation for the iCsvReportView interface. Responsible
* for appending a file extention to the file name, getting the output
* from the provided view, saving that output to a file, and storing the
* finished file name in the Report object.
*
* Can be overrriden if custom behavior is necessary.
*
* @see iHtmlCsvView
* @param ReportCsvView $csvView
*/
public function saveCsvOutput(ReportCsvView $csvView)
{
// Add the proper extension
$fileName = $this->fileName . '.csv';
$fileResult = file_put_contents($fileName, $csvView->getOutput());
// Save the file name to the report
$this->report->setCsvOutputFilename($fileName);
$this->report->save();
}
/**
* Shortcut method to return the Command for the default
* viewing method for this report. This can be overridden
* to provide custom behavior, or to overrride the report's
* requested behavior.
*
* @return Command Default command for viewing this report's output
*/
public function getDefaultOutputViewCmd()
{
return $this->report->getDefaultOutputViewCmd();
}
/**
* Loads the report instance for the last execution performed.
* The loaded object will contain a null ID if the report has
* never been executed. Returns true/false on success/failure.
*
* @throws DatabaseException
* @return boolean
*/
public function loadLastExec()
{
$db = new PHPWS_DB('hms_report');
$db->addWhere('report', $this->getReportClassName());
$db->addWhere('completed_timestamp', 'NULL', 'IS NOT');
$db->addOrder('completed_timestamp DESC');
$db->setLimit(1);
$result = $db->loadObject($this->report);
if(PHPWS_Error::logIfError($result)){
throw new DatabaseException($result->toString());
}
return true;
}
/**
* Returns the report instance that this controller is managing.
*
* @return Report
*/
public function getReport()
{
return $this->report;
}
/**
* Saves the parameters from this report to the database.
*
* @throws DatabaseException
*/
public function saveParams()
{
$params = $this->getParams();
if(empty($params)){
return;
}
$db = new PHPWS_DB('hms_report_param');
foreach($params as $key=>$value){
$db->reset();
$db->addValue('report_id', $this->report->getId());
$db->addValue('param_name', $key);
$db->addValue('param_value', $value);
$result = $db->insert();
if(PHPWS_Error::logIfError($result)){
throw new DatabaseException($result->toString());
}
}
}
/**
* Loads the parameters for this report from the database.
*
* @throws DatabaseException
*/
public function loadParams()
{
$db = new PHPWS_DB('hms_report_param');
$db->addWhere('report_id', $this->report->getId());
$results = $db->select();
if(PHPWS_Error::logIfError($results)){
throw new DatabaseException($results->toString());
}
if(is_null($results) || empty($results)){
echo 'empty params!';
return;
}
$params = array();
foreach($results as $result){
$params[$result['param_name']] = $result['param_value'];
}
$this->setParams($params);
}
}