You are here

FrxReportGenerator.inc in Forena Reports 6.2

Common functions used throughout the project but loaded in this file to keep the module file lean.

File

FrxReportGenerator.inc
View source
<?php

// $Id$

/**
 * @file
 * Common functions used throughout the project but loaded in this
 * file to keep the module file lean.
 */

// Include Report renderer.
require_once 'FrxReport.inc';
require_once 'FrxDataProvider.inc';
class FrxReportGenerator {
  public $app;
  private $repositories = array();
  static $instance = '';
  public $title;
  public $content;
  public static function instance() {
    global $forena_application_class;
    if (!self::$instance) {
      if (!$forena_application_class) {
        $forena_application_class = 'FrxDrupalApplication';
      }
      self::$instance = new FrxReportGenerator($forena_application_class);
    }
    return self::$instance;
  }
  public function __construct($application_class) {
    $app = $this->app = new $application_class();
  }

  /**
   * Get name from argument 1 or alterntaively from a file name
   *
   * @param unknown_type $name
   */
  public function report_desc($name = '') {
    $app = $this->app;

    // Get defined document types
    $doctypes = $this
      ->supported_doctypes();
    $parts = explode('.', $name);
    $name = '';
    if ($parts) {
      foreach ($parts as $part) {
        if (!isset($doctypes[$part])) {
          $name .= '/' . $part;
        }
        else {
          $desc['format'] = $part;
        }
      }
    }
    $desc['name'] = trim($name, '/');
    $report_path = $app
      ->configuration('report_repos');
    @(list($dir, $name_part) = explode('/', $name_part, -2));
    if (!$name_part) {
      $dir = '';
    }
    $desc['directory'] = $report_path . '/' . $dir;
    $desc['filename'] = $report_path . '/' . trim($name, ' /') . '.frx';
    $desc['link'] = 'reports/' . trim(str_replace('/', '.', $name), '.');
    $desc['exists'] = file_exists($desc['filename']);
    return $desc;
  }

  /**
   * Determines which css files need to be loaded.
   *
   * @param array $desc Report descriptor from forena_rport_desc
   * @param string $form The report "form" to be used.  From the report
   * @param string $format Document format that will be used for the report.
   * @return array A list of css files that should be applied to the report.
   */
  function report_css($desc, $form, $format = '') {
    $css_files = array();

    // First check for the form file
    $path = $this->app
      ->configuration('report_repos');
    if (file_exists($path . '/' . $form . '.css')) {
      $css_files[] = $path . '/' . $form . '.css';
    }
    if ($format && file_exists($path . '/' . $form . '-' . $format . '.css')) {
      $css_files[] = $path . '/' . $form . '-' . $format . '.css';
    }

    // Now check for a report specific file
    $base_file = $path . '/' . $desc['name'];
    if ($format && file_exists($base_file . '-' . $format . '.css')) {
      $css_files[] = $base_file . '-' . $format . '.css';
    }
    elseif (file_exists($base_file . '.css')) {
      $css_files[] = $base_file . '.css';
    }
    return $css_files;
  }

  /**
   * Determines which js files need to be loaded.
   *
   * @param array $desc Report descriptor from forena_rport_desc
   * @param string $form The report "form" to be used.  From the report
   * @param string $format Document format that will be used for the report.
   * @return array A list of css files that should be applied to the report.
   */
  function report_js($desc, $form, $format = '') {
    $js_files = array();

    // First check for the form file
    $path = $this->app
      ->configuration('report_repos');
    if (file_exists($path . '/' . $form . '.js')) {
      $js_files[] = $path . '/' . $form . '.js';
    }
    if ($format && file_exists($path . '/' . $form . '-' . $format . '.js')) {
      $js_files[] = $path . '/' . $form . '-' . $format . '.js';
    }

    // Now check for a report specific file
    $base_file = $path . '/' . $desc['name'];
    if ($format && file_exists($base_file . '-' . $format . '.js')) {
      $js_files[] = $base_file . '-' . $format . '.js';
    }
    elseif (file_exists($base_file . '.js')) {
      $js_files[] = $base_file . '.js';
    }
    return $js_files;
  }

  /**
   * Clear session data because we want to reload.
   */
  public function clear_session() {
    if (isset($_SESSION['forena_access'])) {
      unset($_SESSION['forena_access']);
    }
  }

  /**
   * Check access control using the block in a data block.  In this case
   * public assess returns true.
   * @param $block  Repository block used to test access
   * @param $path   xpath to user right within xml data.
   * @param $access Access to test
   * @return unknown_type
   */
  function block_access($block, $path, $access, $cache = TRUE) {

    // PUBLIC always returns true.
    if ($access == 'PUBLIC') {
      return TRUE;
    }
    if (!isset($_SESSION['forena_access'])) {
      $_SESSION['forena_access'] = array();
    }
    if ($cache && isset($_SESSION['forena_access'][$block])) {
      $rights = $_SESSION['forena_access'][$block];
    }
    else {
      $rights = array();

      // Get the block from the repository
      $frxData = FrxData::instance();
      $frxData
        ->push(array(), 'frx-block-access');
      $data = $this
        ->invoke_data_provider($block, array(), NULL);
      $frxData
        ->pop();
      if ($data) {
        if (!$path) {
          $path = '*/*';
        }
        $nodes = $data
          ->xpath($path);
        if ($nodes) {
          foreach ($nodes as $node) {
            $rights[] = (string) $node;
          }
        }
        $_SESSION['forena_access'][$block] = $rights;
      }
    }
    foreach ($rights as $right) {
      if ($access == $right) {
        return TRUE;
      }
    }
    return FALSE;
  }

  // Putting this in a function to sandbox the repository settings
  function load_repository(&$repo) {

    // First determine if the class file exisits
    $path = @$repo['path'];
    $conf = array();
    if (file_exists($path . '/settings.php')) {

      // Override with repository specific data
      include $path . '/settings.php';
    }
    $repo = array_merge($conf, $repo);

    // $repo['user callback'] = $conf['user callback'];
    if (@(!is_object($repo['data']))) {
      $repo['data'] = $this
        ->load_provider($repo, $path);
    }

    //$repo['auth'] = __forena_load_auth($security_provider);
  }

  /**
   * Load the data provider class based on the class name.
   *
   * @param string $name
   * @return object The data provider object
   */
  function load_provider($conf, $repo_path) {
    @($name = isset($conf['data provider']) ? $conf['data provider'] : $conf['data_engine']);
    $this
      ->define_plugins();

    // Instantiate the path
    if (class_exists($name)) {
      $o = new $name($conf, $repo_path);
      return $o;
    }
    else {
      $this->app
        ->error('Data provider not found for ' . $conf['title']);
    }
  }

  /*
   * Get the repository data for an a repository name.
   * If no repository is specified the descriptors for all repositories are returned.
   */
  function repository($name = '') {
    static $repos = '';
    global $_forena_repositories;

    // Empty repository so we need to initialize
    if (!$repos) {

      // Build the default sample one
      $path = $this->app
        ->forena_path();
      $repos = array();

      // Load the repository list from the global settings.php file.
      $repos = $this->app
        ->repositories();
      if ($_forena_repositories) {
        array_merge($repos, $_forena_repositories);
      }
    }

    // Now determine if the object exists
    if ($name) {
      if (isset($repos[$name])) {
        if (@(!is_object($repos[$name]['data']))) {
          $this
            ->load_repository($repos[$name]);
        }
        return $repos[$name];
      }
      else {
        $this->app
          ->error('Undefined repository' . $name, "Undefined Repository: {$name}");
      }
    }
    else {
      return $repos;
    }
  }

  /**
   * Accessor function named more conveniently
   * Enter description here ...
   * @param unknown_type $block_name
   * @param unknown_type $parms
   */
  public function block_xml($block_name, $parms = array()) {
    return $this
      ->invoke_data_provider($block_name, $parms);
  }

  /**
   * Extract the data by running a block
   *
   * @param unknown_type $data_block
   * @param unknown_type $parameters
   * @param string $clause order / where cause
   * @return unknown
   */
  function invoke_data_provider($data_block, $parameters = array(), $clause = '') {
    list($provider, $block) = explode('/', $data_block, 2);

    // Get the data
    $repos = $this
      ->repository($provider);
    if (isset($repos['enabled']) && !$repos['enabled']) {
      return '';
    }
    if (isset($repos['user callback'])) {
      $user_fn = $repos['user callback'];
      if (is_callable($user_fn)) {
        $current_user = $user_fn();
        FrxData::instance()
          ->setValue('current_user', $current_user);
      }
    }
    if ($repos['data']) {
      $provider = $repos['data'];
      $access = TRUE;
      if (method_exists($provider, 'data')) {
        $xml = $provider
          ->data($block, $parameters, $clause);
      }
      return $xml;
    }
  }

  /**
   * Returns an array of information about the data block
   * @param $data_block
   * @return unknown_type
   */
  function forena_load_block($data_block, $clause = '') {
    @(list($provider, $block) = explode('/', $data_block, 2));

    // Get the data
    $repos = forena_repository($provider);
    if (isset($repos['data'])) {
      $provider = $repos['data'];
      if (method_exists($provider, 'load_block')) {
        $block_info = $provider
          ->load_block($block, $clause);
      }
      return $block_info;
    }
  }

  /**
   * Load the formatters for all initialized repositories.
   *
   */
  function get_formatter($fkey) {

    // Get all repositories
    $repos = $this
      ->repository();
    $formats = array();
    foreach ($repos as $k => $r) {
      $provider = isset($r['data']) ? $r['data'] : NULL;
      if ($provider && method_exists($provider, 'formats')) {
        $f = $provider
          ->formats();
        if (isset($f[$fkey]) && method_exists($provider, $fkey)) {

          // We found an object with the advertised method return it
          return $provider;
        }
      }
    }

    //Did not find the formater in the data provider

    //Look to see if it's in a control class
    $controls = $this
      ->define_controls();
    foreach ($controls as $k => $r) {
      $provider = $r;
      if ($provider && method_exists($provider, 'formats')) {
        $f = $provider
          ->formats();
        if (isset($f[$fkey]) && method_exists($provider, $fkey)) {

          // We found an object with the advertised method return it
          return $provider;
        }
      }
    }
    return $formats;
  }

  /**
   * Accepts the name of a file
   *
   * Returns a xml object of the file.
   *
   */
  function get_report($report_name, $data = array()) {
    $r = null;
    $this
      ->alter_parameters($report_name, $data);
    if ($report_name) {
      $r_text = $this->app
        ->load_report($report_name);
      if ($r_text) {
        $r = new FrxReport($r_text, $data);
      }
    }
    return $r;
  }
  function alter_parameters($report_name, &$data) {
    if (method_exists($this->app, 'alter_parameters')) {
      $this->app
        ->alter_parameters($report_name, $data);
    }
  }

  /**
   * Enter description here...
   *
   * @param simplexml $xml
   * @param string $tag
   * @return string
   */
  function inner_xml($xml, $tag) {
    if (is_object($xml) && is_object($xml->{$tag})) {
      $xml_data = $xml->{$tag}
        ->asXML();
      $xml_data = preg_replace("/<\\/?" . $tag . "(.|\\s)*?>/", "", $xml_data);
    }
    return $xml_data;
  }

  /**
   * Accepts the name of the html tag, and the string the tag is in.
   *
   * Returns the string within the html tag name
   *
   */
  function get_html($tag, $r_text) {
    $open = strpos($r_text, $tag);
    $close = strpos($r_text, '>', $open);
    $next = strpos($r_text, '<', $close + 1);
    $str = substr($r_text, $close + 1, $next - ($close + 1));
    return $str;
  }
  function format_data($value, $format, $format_str, $teng = '') {
    $fo = $this
      ->get_formatter($format);
    if ($fo) {
      $ret = $fo
        ->{$format}($value, $format_str, $teng);
    }
    else {
      $ret = $value;
    }
    return $ret;
  }

  /**
   * Returns an array of information about the data block
   * @param $data_block
   * @return unknown_type
   */
  function load_block($data_block, $clause = '') {
    list($provider, $block) = explode('/', $data_block, 2);

    // Get the data
    $repos = $this
      ->repository($provider);
    if ($repos['data']) {
      $provider = $repos['data'];
      if (method_exists($provider, 'load_block')) {
        $block_info = $provider
          ->load_block($block, $clause);
      }
      return $block_info;
    }
  }

  /**
   * Load a block file form disk and introspect the comments to determine security
   * Return a structured array based on this.
   *
   * @param unknown_type $filepath
   * @param unknown_type $comment
   */
  function load_block_file($filepath) {
    if (!file_exists($filepath)) {
      return '';
    }
    $block_data = file_get_contents($filepath);
    return $block_data;
  }

  /**
   * Loads all of the include files that
   */
  function define_plugins($class = '') {
    $plugins = $this->app
      ->plugins();
    foreach ($plugins as $p) {
      if ($class == '' || $class == $p['class']) {
        if ($p['file']) {
          include_once trim($p['file'], '/');
        }
      }
    }
  }

  /**
   * returns a list of instances of the control classes
   * if there wasn't a class, returns an empty string.
   */
  function define_controls($class = '') {
    static $instances = '';
    if (!$instances) {
      $classes = $this->app
        ->controls();
      foreach ($classes as $c) {
        if ($class == '' || $class == $c['class']) {
          if ($c['file']) {
            include_once $c['file'];
            if (class_exists($c['class'])) {
              $instances[$c['class']] = new $c['class']();
            }
          }
        }
      }
    }
    if ($class) {
      return @$instances[$class];
    }
    return $instances;
  }

  /**
   * Gets the correct format function
   * to render the document and returns an
   * object of that class.
   *
   * If it fails it returns a 0;
   */
  function get_doctypes($fkey) {
    $controls = $this
      ->define_controls();
    foreach ($controls as $k => $r) {
      $provider = $r;
      if ($provider && method_exists($provider, 'doc_types')) {
        $f = $provider
          ->doc_types();
        if (isset($f[$fkey]) && method_exists($provider, $f[$fkey])) {

          // We found an object with the advertised method return it
          return $provider;
        }
      }
    }
    return 0;
  }

  /**
   *
   * @return returns an array of supported document format extensions
   *
   */
  function supported_doctypes() {
    $controls = $this
      ->define_controls();
    $supported_doctypes = array();
    if ($controls) {
      foreach ($controls as $k => $r) {
        $provider = $r;
        if ($provider && method_exists($provider, 'doc_types')) {
          $f = $provider
            ->doc_types();
          $supported_doctypes = array_merge($supported_doctypes, $f);
        }
      }
    }
    $temp = array_keys($supported_doctypes);
    $temp[] = 'web';
    if ($supported_doctypes) {
      $supported_doctypes = array_combine($temp, $temp);
    }
    return $supported_doctypes;
  }

  /**
   * Returns an object of the template class
   * that has a method named templates.
   *
   * If it fails it returns a 0;
   */
  function get_templates($fkey) {
    $templates = $this
      ->supported_templates();
    if (class_exists($fkey)) {
      return new $fkey();
    }
  }

  /**
   *
   * @return returns an array of supported templates
   *
   */
  function supported_templates() {
    require_once 'templates/FrxTemplate.inc';
    require_once 'templates/FrxTable.inc';
    require_once 'templates/FrxFieldTable.inc';
    require_once 'templates/FrxEmailMerge.inc';
    require_once 'templates/FrxGraphTemplate.inc';
    require_once 'templates/FrxRptInclude.inc';
    $templates = array(
      'FrxTable' => 'Table',
      'FrxTemplate' => 'Document',
      'FrxEmailMerge' => 'Email Merge',
      'FrxFieldTable' => 'Fields',
      'FrxGraphTemplate' => 'Graph or Chart',
    );
    return $templates;
  }
  function supported_formats() {
    $controls = $this
      ->define_controls();
    $supported_formats = array();
    $f = array();
    foreach ($controls as $k => $r) {
      $provider = $r;
      if ($provider && method_exists($provider, 'formats')) {
        $f = $provider
          ->formats();
        $supported_formats = array_merge($supported_formats, $f);
      }
    }
    return $supported_formats;
  }

  /**
   *
   * @param unknown_type $format: The extension of the document to be rendered
   * @param unknown_type $output: The string of the page to be displayed
   * @return: The document in the requested format. Returns a string if not
   * able to format.
   */
  function generate_doc($format, $output, $options = array(), $print = TRUE) {
    $doc = $this
      ->get_doctypes($format);
    if ($doc) {
      $all_methods = $doc
        ->doc_types();
      $method = $all_methods[$format];
      $ret = $doc
        ->{$method}($output, $options);
    }
    if ($ret && $print) {

      // If an object was found, set the appropriate mime type (header doctype).
      //   $flen = strlen($ret);
      switch ($format) {
        case 'doc':
          header('Content-Type: application/msword');
          header('Cache-Control:');
          header('Pragma:');
          header("Cache-Control: must-revalidate");
          break;
        case 'pdf':
          header('Content-Type: application/pdf');
          header('Cache-Control:');
          header('Pragma:');
          header("Cache-Control: must-revalidate");
          header("Content-Disposition: attachment; filename=report.pdf");
          break;
        case 'xls':
          header('Content-Type: application/msexcel');
          header('Cache-Control:');
          header('Pragma:');
          header("Cache-Control: must-revalidate");
          header("Content-Disposition: attachment; filename=report.xls");
          break;
        case 'csv':
          header('Content-Type: application/csv');
          header('Cache-Control:');
          header('Pragma:');
          header("Cache-Control: must-revalidate");
          header("Content-Disposition: attachment; filename=report.csv");
          break;
        case 'svg':
          header('Content-Type: image/svg+xml');
          header('Cache-Control:');
          header('Pragma:');
          header('Cache-Control: must-revalidate');
          break;
      }

      // Print the output.
    }
    return $ret;
  }

  /**
   * Load and render a report based on a drupal path.
   * In this function the arglist is used to get the full path to the report.
   *
   * @return unknown
   */
  function report($name_in, $parms = array(), $print = TRUE) {
    $output = '';
    $desc = $this
      ->report_desc($name_in);
    $name = $desc['name'];
    $format = isset($desc['format']) ? $desc['format'] : '';
    $filename = $desc['filename'];
    $css_files = array();
    $js_files = array();

    // Determine the data to get.
    if (!$parms) {
      $parms = array_merge($_GET, $_POST);
      unset($parms['q']);
    }
    else {
      $parms = (array) $parms;
    }
    if ($name) {
      $r = $this
        ->get_report($name, $parms);
      if (!$r || !$r->rpt_xml) {
        $this->app
          ->error('Could not load report. Check for invalid XML.');
        return '';
      }

      //check for default parameters
      $r_params = $r->parameters;
      $reload_params = FALSE;
      $missing_parms = FALSE;

      //Set default Parameters
      if ($r_params) {

        //put default parameters in parms array
        foreach ($r_params as $key => $parm) {
          if ((@$parms[$key] == '' || @$parms[$key] == array()) && @$parm['value']) {
            $value = (string) $parm['value'];
            if (strpos($value, '|') !== FALSE) {
              $value = explode('|', $value);
            }
            $parms[$key] = $value;
            $reload_params = TRUE;
          }

          //do not show report if a required parameter does not have a value

          //force the user to input a parameter
          if (@(!$parms[$key]) && @strcmp($parm['require'], "1") == 0 && !$parm['value']) {
            $missing_parms = TRUE;
          }
        }
      }

      //Reload report if parameters were missing
      if ($reload_params) {
        $r = $this
          ->get_report($name, $parms);
      }
      $form = isset($r->options['form']) ? $r->options['form'] : '';
      $rpt_xml = $r->rpt_xml;

      // Default the form
      if (!$form) {
        $form = $this->app
          ->configuration('default_form');
      }
      $this->form = $form;
      if (!$missing_parms) {
        $output .= $r
          ->render($format);
        $css_files = $this
          ->report_css($desc, $form, $format);
        $js_files = $this
          ->report_js($desc, $form, $format);
      }

      //put title on top of report
      $title = (string) $r->title;
      $this->title = $title;
      if ($format && $format != 'web') {

        //a format was requested
        $header = '<h1>' . $title . '</h1>';
        $output = $header . $output;
        $css_text = '';
        $r_text = '';
        if ($css_files) {
          foreach ($css_files as $css_file) {
            $css_text .= file_get_contents($css_file);
          }
        }
        $options = array(
          'css' => $css_text,
          'docname' => str_replace(' ', '_', $title),
          'xml' => $r_text,
          'title' => $title,
        );
        $output = $this
          ->generate_doc($format, $output, $options, $print);
        if ($format != 'email' && $print) {
          print $output;
        }
        else {
          return $output;
        }
        exit;
      }
      else {

        //Creating links for downloadable documents.

        //build querystring for document href
        $q = '';
        foreach ($parms as $key => $value) {
          $q .= "&" . $key . '=' . $value;
        }
        $q = trim($q, '&');

        //Building the document links

        //@TODO: Move this to better location
        $rpt_xml = $r->rpt_xml;
        $nodes = $rpt_xml
          ->xpath('//frx:docgen/frx:doc');
        $div = '<div class="doclinks">';
        $default_doctypes = $this->app
          ->configuration('doc_defaults', array());
        if (!$missing_parms) {
          if (!$nodes) {

            //show the default.
            if ($default_doctypes) {
              foreach ($default_doctypes as $value) {
                if (is_object($this
                  ->get_doctypes($value))) {
                  $div .= $this
                    ->link(strtoupper($value) . ' ', 'report_doc/' . $name_in . '.' . $value, array(
                    'query' => $parms,
                    'class' => 'doclinks',
                  ));
                }
              }
            }
          }
          else {

            //There were nodes. show the prefered doc types
            $doctypes = $this
              ->supported_doctypes();
            foreach ($nodes as $value) {
              $arr = $value
                ->attributes();
              $type = (string) $arr['type'];
              if (@$doctypes[$type]) {
                if (is_object($this
                  ->get_doctypes($type))) {
                  $div .= $this
                    ->link(strtoupper($type) . ' ', 'report_doc/' . $name_in . '.' . $type, array(
                    'query' => $parms,
                    'class' => 'doclinks',
                  ));
                }
              }
            }
          }
        }
        $div .= '</div>';
        $output = $div . $this->app
          ->theme($r, $title, $format);
        $this->app
          ->add_css($this->app
          ->forena_path() . '/forena.css');
        if ($css_files) {
          foreach ($css_files as $css_file) {
            $this->app
              ->add_css($css_file);
          }
        }
        if ($js_files) {
          foreach ($js_files as $js_file) {
            $this->app
              ->add_js($js_file);
          }
        }
        return $output;
      }
    }
    else {
      $this->app
        ->not_found($name);
    }
  }

  /*
   * Recieves a datablock and returns an array of values from the data block.
   * @data_block: name of the data block to be invoked for values
   * @field: Specific field name within the data block. The values returned will only
   * come from this field.
   * @params: filtering for the data block
   * @where_clause: Where clause for data block to be filtered against.
   *
   */
  function data_block_params($data_block, $field, $label, $params = array(), $clause = '') {
    FrxData::instance()
      ->push($params);
    $xml = $this
      ->invoke_data_provider($data_block, $params, $clause);
    FrxData::instance()
      ->pop();
    $list = array();
    if ($xml) {
      $path = $field ? $field : '*[1]';
      $label_path = $label ? $label : '*[2]';

      //walk through the $xml.
      $rows = $xml
        ->xpath('*');
      if ($rows) {
        foreach ($rows as $row) {
          $value = $row
            ->xpath($path);
          $label_field = $row
            ->xpath($label_path);
          $label_value = $label_field ? (string) $label_field[0] : (string) $value[0];
          $list[(string) $value[0]] = (string) $label_value;
        }
      }
    }
    return $list;
  }
  public function debug($short_message = '', $log = '') {
    $this->app
      ->debug($short_message, $log);
  }
  public function error($short_message = '', $log = '') {
    $this->app
      ->error($short_message, $log);
  }
  public function configuration($name) {
    return $this->app
      ->configuration($name);
  }
  public function report_path() {
    return $this->app
      ->configuration('report_repos');
  }
  public function link($title, $path, $options = array()) {
    return $this->app
      ->link($title, $path, $options);
  }
  public function url($path, $options = array()) {
    return $this->app
      ->url($path, $options);
  }

}

Classes

Namesort descending Description
FrxReportGenerator