forena-8.x-1.x-dev/src/Editor/ReportEditor.php

src/Editor/ReportEditor.php
<?php
/**
 * @file ReportEditor.inc
 * Wrapper XML class for working with DOM object.
 * It provides helper
 * Enter description here ...
 * @author metzlerd
 *
 */
namespace Drupal\forena\Editor;
use Drupal\forena\FrxAPI;
use Drupal\forena\Menu;
use \DOMDocument;
use \DOMXPath;
use \Drupal\forena\Report;
use Drupal\forena\Template\TemplateBase;

class ReportEditor {
  use FrxAPI;

  public $dom;
  public $document_root;
  /** @var  \SimpleXMLElement */
  public $simplexml;
  public $title;
  public $report_name;
  public $report_link;
  public $frx_attributes;
  public $cache;
  public $frxReport;
  public $desc;
  public $parms = array(); // Current editor parameters.
  public $access = TRUE; // The user has access to the report.
  public $doc_prefix = '<?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE root [
    <!ENTITY nbsp "&#160;">
    ]>';
  public $xmlns = 'urn:FrxReports';
  private $field_ids;
  public $xpq;



  /**
   * Construct the editor
   * @param string $report_name name of report to load
   */
  public function __construct($report_name) {
    $this->dom = new DOMDocument('1.0', 'UTF-8');
    $this->edit = TRUE;
    $dom = $this->dom;
    $dom->formatOutput = TRUE;
    $dom->preserveWhiteSpace = TRUE;
    $this->frxReport = new Report();
    $this->load($report_name, $this->edit);
  }

  /**
   * Report the root element
   */
  public function asXML() {
    $this->dom->formatOutput = TRUE;
    return  $this->doc_prefix . $this->dom->saveXML($this->dom->documentElement);
  }

  /**
   * Rename report.
   * @param $name
   *   New report name
   */
  public function rename($name) {
    $old_name = $this->report_name;
    $this->report_name = $name;
    $this->report_link = 'reports/' . str_replace('/', '.', $name);
    unset($_SESSION['forena_report_editor'][$old_name]);
    $this->update();
  }

  /**
   * Save data away in the session state.
   */
  public function update() {
    $_SESSION['forena_report_editor'][$this->report_name] =
      $this->doc_prefix . $this->dom->saveXML($this->dom->documentElement);
  }

  public function cancel() {
    unset($_SESSION['forena_report_editor'][$this->report_name]);
    drupal_get_messages('warning');
  }

  /**
   * @param $report
   * @param bool $edit
   * @return Report
   */
  public function loadReport($report, $edit = FALSE) {
    $desc = Menu::instance()->parseURL($report);
    $r_text='';
    $report_name = $desc['name'];

    // Load the latest copy of the report editor
    if ($report_name) {
      if (isset($_SESSION['forena_report_editor'][$report_name]) && $edit) {
        $r_text = $_SESSION['forena_report_editor'][$report_name];
        drupal_set_message(t('All changes are stored temporarily.  Click Save to make your changes permanent.  Click Cancel to discard your changes.'), 'warning', FALSE);
      }
      else {
        $filename = $report_name . '.frx';
        $r_text = $this->reportFileSystem()->contents($filename);
      }
    }

    if (!$r_text) {
      $m_path = drupal_get_path('module', 'forena');
      $r_text = file_get_contents($m_path . '/default.frx');
    }

    $dom = new DOMDocument();
    libxml_use_internal_errors();
    try {
      @$dom->loadXML($r_text);
    }
    catch (\Exception $e) {
      $this->error('Invalid or malformed report document', '<pre>' .
        $e->getMessage() . $e->getTraceAsString() . '</pre>');
    }

    if (!$this->dom->documentElement) {
      $this->error(t('Invalid or malformed report document'));
      return null;
    }

    // @TODO:  This should load the report and not worry about
    $frxReport = new Report($r_text);

    simplexml_import_dom($dom);
    $dom->formatOutput = TRUE;
    // Try to make sure garbage collection happens.
    $xpq = new DOMXPath($dom);
    $xpq->registerNamespace('frx', $this->xmlns);
    // Make sure document header is reparsed.
    // $frxReport->setReport($dom, $xpq, $edit);
    return $frxReport;
  }

  /**
   * Load report from file system
   * @param string $report_name
   * @param bool $edit
   *   Indicates whether we are preferentially loading from session.
   * @return string
   */
  public function load($report_name, $edit=TRUE) {
    $this->desc = Menu::instance()->parseURL($report_name);
    $r_text='';
    $dom = $this->dom;
    $this->report_name = $report_name = $this->desc['name'];
    $this->report_link = 'reports/' . str_replace('/', '.', $this->desc['base_name']);
    // Load the latest copy of the report editor
    if ($report_name) {
      if (isset($_SESSION['forena_report_editor'][$report_name]) && $edit) {
        $r_text = $_SESSION['forena_report_editor'][$report_name];
        drupal_set_message(t('All changes are stored temporarily.  Click Save to make your changes permanent.  Click Cancel to discard your changes.'), 'warning', FALSE);
      }
      else {
        $filename = $report_name . '.frx';
        $r_text = $this->reportFileSystem()->contents($filename);
      }
    }
    if (!$r_text) {
      $m_path = drupal_get_path('module', 'forena');
      $r_text = file_get_contents($m_path . '/default.frx');
    }

    libxml_use_internal_errors();
    try {
      @$dom->loadXML($r_text);
      $this->xpq = new DOMXPath($dom);
    }
    catch (\Exception $e) {
      $this->error('Invalid or malformed report document', '<pre>' .
        $e->getMessage() . $e->getTraceAsString() . '</pre>');
    }
    if (!$this->dom->documentElement) {
      $this->error(t('Invalid or malformed report document'));
      return '';
    }

    $this->verifyHeaderElements();
    $tnodes = $dom->getElementsByTagName('title');
    if ($tnodes->length) $this->title = $tnodes->item(0)->textContent;
    $this->document_root = $dom->documentElement;
    $this->simplexml = simplexml_import_dom($dom);
    $dom->formatOutput = TRUE;
    // Try to make sure garbage collection happens.
    unset($this->xpq);
    $this->xpq = new DOMXPath($dom);
    $this->xpq->registerNamespace('frx', $this->xmlns);
    // Make sure document header is reparsed.
    // @TODO: Figure out whether we need to reparse?
    // $this->frxReport->setReport($this->dom, $this->xpq, $this->edit);
     $cache = forena_load_cache($this->frxReport->rpt_xml);
    if (isset($cache['access'])) $this->access = forena_check_all_access($cache['access']);
    if (!$edit) $this->cache = $this->reportFileSystem()->getMetaData($report_name . '.frx');
    return $r_text;
  }

  /**
   * Save report
   */
  public function save() {
    $this->cleanup_ids();
    unset($_SESSION['forena_report_editor'][$this->report_name]);
    forena_save_report($this->report_name, $this->asXML(), TRUE);
    drupal_set_message(t('Your report, %s has been saved.', array('%s' => $this->report_name)));
    drupal_get_messages('warning');
    $cid = 'forena:report:' . $this->report_name . '%';
    // Remove cache entries
    db_delete('cache')
      ->condition('cid', $cid, 'LIKE')
      ->execute();
    // @TODO: Figure out how to rebuid or invalidate MENUS
    //menu_rebuild();
  }

  public function delete() {
    $filepath = $this->report_name . '.frx';
    $this->reportFileSystem()->delete($filepath);
  }

  /**
   * Set the value of an element within the report
   * @param String $xpath
   *   Xpath to element being saved
   * @param string $value
   *   Value to be saved.
   * @return bool
   *   Indicates whether the set was successful.
   */
  public function setValue($xpath, $value) {
    /** @var \SimpleXMLElement $xml */
    $xml = $this->simplexml;
    $i = strrpos($xpath, '/');
    $path = substr($xpath, 0, $i);
    $key = substr($xpath, $i+1);
    $nodes = $xml->xpath($path);
    if ($nodes) {
      // if the last part of the xpath is a key then assume the key
      if (strpos($key, '@')===0) {
        $key = trim($key, '@');
        if (is_null($value)) {
          unset($nodes[0][$key]);
        }
        else {
          $nodes[0][$key] = $value;
        }
      }
      // We must be refering to the text element of a node.
      else {
        if (is_null($value)) {
          unset($nodes[0]->$key);
        }
        else {
          $nodes[0]->$key = $value;
        }
      }
      return TRUE;
    }
    else {
      return FALSE;
    }
  }

  /**
   * Set the value of the body of the report
   * Will parse and set the value of the body of the report
   * using XML
   * @param string $body
   *   String represnetation of html body
   */
  public function setBody($body) {
    $dom = $this->dom;
    $nodes = $dom->getElementsByTagName('body');
    $cur_body = $nodes->item(0);
    // Make sure that we have a body tag.
    if (strpos($body, '<body')===FALSE) {
      $body = '<body>' . $body . '</body>';
    }
    // Attempt to parse the xml
    $body_doc = new DOMDocument('1.0', 'UTF-8');
    $body_xml = $this->doc_prefix . '<html xmlns:frx="' . $this->xmlns . '">' . $body . '</html>';
    try {
      $body_doc->loadXML($body_xml);
      $new_body = $dom->importNode($body_doc->getElementsByTagName('body')->item(0), TRUE);
      $parent = $cur_body->parentNode;
      $parent->replaceChild($new_body, $cur_body);
    }
    catch (\Exception $e) {

      $this->error('Malformed report body', '<pre>' . $e->getMessage() .
      $e->getTraceAsString() . '</pre>');
    }
    // If there are no frx attributes in the body then replace them with the old values.
    $frx_nodes = $this->frxReport->rpt_xml->xpath('body//*[@frx:*]');
    if (!$frx_nodes) {
      //@TODO: Figure out how to save all the report attbitues by id.
      //$this->frxReport->save_attributes_by_id();
    }
  }
  /**
   * Makes sure that the normal header elements for a report are there.
   * @param array $required_elements
   *   List of elements to ensure are in the document.
   */
  public function verifyHeaderElements($required_elements = array()) {
    if (!$required_elements) $required_elements = array(
      'category',
      'options',
      'fields',
      'parameters',
      'docgen',
    );
    $dom = $this->dom;
    if (!$this->dom->documentElement) {
      drupal_set_message('error', 'error');
      return;
    }

    $head = $dom->getElementsByTagName('head')->item(0);
    if (!$head)  {
      $head = $dom->createElement('head');
       $dom->documentElement->appendChild($head);
    }
    // Make sure the report title exists.
    if ($dom->getElementsByTagName('title')->length==0) {
      $n = $dom->createElement('title');
      $head->appendChild($n);
    }
    // Make sure each of these exists in the header
    foreach ($required_elements as $tag) {
      if ($dom->getElementsByTagNameNS($this->xmlns, $tag)->length == 0 ) {
        $n = $dom->createElementNS($this->xmlns, $tag);
        $head->appendChild($n);
      }
    }
  }

  /**
   * Genreal utility for setting data in the header of a reprot
   *
   * @param string $parent Name of parent element
   * @param string $element Name of child element
   * @param array $element_array Data containing the elements
   * @param array $attributes array of attribute names to set
   * @param string $element_field name of field containing node data
   * @param string $id_field name of field containint node id
   */
  public function setFrxHeader($parent, $element, $element_array, $attributes, $element_field='', $id_field = 'id') {
    $dom = $this->dom;
    /** @var \DOMXPath $xpq */
    $xpq = $this->xpq;
    $this->verifyHeaderElements(array($parent));
    $pnode = $dom->getElementsByTagNameNS($this->xmlns, $parent)->item(0);

    // Iterate through all child arrays in the header
    $tnode = $dom->createTextNode("\n");
    $pnode->appendChild($tnode);
    foreach ($element_array as $element_data) {
      $id = @$element_data[$id_field];
      $path = '//frx:' . $parent . '/frx:' . $element . '[@' . $id_field . '="' . $id . '"]';
      $nodes = $xpq->query($path);
      $value = NULL;
      if ($element && isset($element_data[$element_field])) {
        $value = $element_data[$element_field];
      }

      $node = $dom->createElementNS($this->xmlns, $element, trim($value, "|"));
      if ($nodes->length == 0) {
        $tnode = $dom->createTextNode("  ");
        $pnode->appendChild($tnode);        $pnode->appendChild($node);
        $tnode = $dom->createTextNode("\n");
        $pnode->appendChild($tnode);
      }
      else {
        $src_node = $nodes->item(0);
        $pnode->replaceChild($node, $src_node);
      }

      foreach ($attributes as $attribute) {
        if (!empty($element_data[$attribute])) {
          $node->setAttribute($attribute, $element_data[$attribute]);
        }
        else {
          if ($node->hasAttribute( $attribute)) {
            $node->removeAttribute($attribute);
          }
        }
      }
    }



  }

  /**
   * Builds the fields from an array of elements.
   * Enter description here ...
   * @param $fieldElements
   */
  public function setFields($fieldElements) {
    $this->verifyHeaderElements(array('fields'));
    $this->setFrxHeader('fields', 'field',
    $fieldElements,
    array('id', 'link', 'format', 'format-string', 'target', 'rel', 'class', 'add-query', 'calc', 'context'),
      'default');

  }

  /**
   * Makes sure specific document types are asserted in the report document.
   * @param array $types
   *   Document types to assert.
   */
  public function ensureDocGen($types) {
    $types = array_combine($types, $types);
    $doctypes = $this->getDocgen();
    $doctypes = array_merge($types, $doctypes);
    $this->setDocgen($doctypes);
  }


  /**
   * Gets the array of selected document types or default if they are present.
   * @return array
   */
  public function getDocgen() {

    //build the options and default list
    $nodes = $this->simplexml->head->xpath('//frx:doc');
    if ($nodes)  {
      $doctypes = array();
      foreach($nodes as $doc) {
        $doctypes[] = (string) $doc['type'];
      }
    }
    else {
        $doctypes = \Drupal::config('forena.settings')->get('doc_formats');
    }

    // Verify that they are not disabled
    $supported_types = array_keys($this->documentManager()->getDocTypes());
    $doctypes = array_intersect($doctypes, $supported_types);
    $doctypes = array_combine($doctypes, $doctypes);
    return $doctypes;
  }

  /**
   * Set document generation types that apply to this report.
   * Enter description here ...
   * @param array $docgenElements
   */
  public function setDocgen($doctypes) {
    $docgenElements = array();
    if ($selected = array_filter($doctypes)) {
      if ($selected) foreach ($selected as $key => $value) {
        if ($value) $docgenElements[] = array('type' => $key);
      }
    }

    $dom = $this->dom;
    $newDocs = $dom->createElementNS($this->xmlns, 'docgen');
    $this->verifyHeaderElements(array('docgen'));
    $dnode = $dom->getElementsByTagNameNS($this->xmlns, 'docgen')->item(0);
    $p = $dnode->parentNode;
    $p->replaceChild($newDocs, $dnode);
    $this->setFrxHeader('docgen', 'doc',
      $docgenElements,
      array('type'),
      NULL,
      'type'
      );
  }

  /**
   * Set report parameters
   * Enter description here ...
   * @param array $parmElements array
   */
  public function setParameters($parmElements) {
    $dom = $this->dom;
    $newParms = $dom->createElementNS($this->xmlns, 'parameters');
    $this->verifyHeaderElements(array('parameters'));
    $fnode = $dom->getElementsByTagNameNS($this->xmlns, 'parameters')->item(0);
    $p = $fnode->parentNode;
    $p->replaceChild($newParms, $fnode);

    $this->setFrxHeader('parameters', 'parm',
    $parmElements,
    array('id', 'label', 'require', 'desc', 'data_source', 'data_field', 'label_field', 'type', 'class', 'options'),
      'default');
  }


  public function addParameters($parms_to_add) {
    $parms_to_add = (array)$parms_to_add;
    foreach ($parms_to_add as $parm) {
      $parms= array();
      $parms[$parm] = array('id' => $parm );
      $this->setFrxHeader('parameters', 'parm',
      $parms,
      array('id', 'label', 'require', 'desc', 'data_source', 'data_field', 'type'),
        'default');
    }
  }

  /**
   * Set the report title
   * @param String $title
   */
  public function setTitle($title) {
    $dom = $this->dom;
    $head = $dom->getElementsByTagName('head')->item(0);
    $tnode = $dom->getElementsByTagName( 'title')->item(0);
    $node = $dom->createElement( 'title', $title);
    $head->replaceChild($node, $tnode);
  }

  /**
   * Set the report category
   * Enter description here ...
   * @param string $category
   *   Category for listing the report in.
   */
  public function setCategory($category) {
    $dom = $this->dom;
    $this->verifyHeaderElements(array('category'));
    $head = $dom->getElementsByTagName('head')->item(0);
    $cnode = $dom->getElementsByTagNameNS($this->xmlns, 'category')->item(0);
    $node = $dom->createElementNS($this->xmlns, 'category', $category);
    $head->replaceChild($node, $cnode);
  }

  public function getCategory() {
    $dom = $this->dom;
    $this->verifyHeaderElements(array('category'));
    $cnode = $dom->getElementsByTagNameNS($this->xmlns, 'category')->item(0);
    return $cnode->textContent;
  }

  /**
   * Retrieve options element in array form
   */
  public function getOptions() {
    $dom = $this->dom;
    $this->verifyHeaderElements(array('options'));
    $opts = $dom->getElementsByTagNameNS($this->xmlns, 'options')->item(0);
    $ret = array();
    // Simplexml is easier to work with
    $options = simplexml_import_dom($opts);
    foreach ($options->attributes() as $key => $value) {
      $ret[(string)$key] = (string)$value;
    }
    return $ret;
  }

  /**
   * Set the options list for the report
   * Enter description here ...
   * @param array $option_data
   */
  public function setOptions($option_data) {
    $dom = $this->dom;
    $this->verifyHeaderElements(array('options'));
    /** @var \DOMElement $options */
    $options = $dom->getElementsByTagNameNS($this->xmlns, 'options')->item(0);
    foreach ($option_data as $key => $value) {
      if ($value) {
        $options->setAttribute($key, $value);
      }
      else {
        if ($options->hasAttribute($key)) {
          $options->removeAttribute($key);
        }
      }
    }
  }

  /*
   * Retrieve menu data for the report
   */
  public function getMenu() {
    $dom = $this->dom;
    $this->verifyHeaderElements(array('menu'));
    $opts = $dom->getElementsByTagNameNS($this->xmlns, 'menu')->item(0);
    $ret = array();
    // Simplexml is easier to work with
    $options = simplexml_import_dom($opts);
    foreach ($options->attributes() as $key => $value) {
      $ret[(string)$key] = (string)$value;
    }
    return $ret;

  }

  /*
   * Retrieve menu data for the report
  */
  public function getCache() {
    $dom = $this->dom;
    $this->verifyHeaderElements(array('cache'));
    $opts = $dom->getElementsByTagNameNS($this->xmlns, 'cache')->item(0);
    $ret = array();
    // Simplexml is easier to work with
    $options = simplexml_import_dom($opts);
    foreach ($options->attributes() as $key => $value) {
      $ret[(string)$key] = (string)$value;
    }
    return $ret;

  }

  /*
   * Set menu data for the report
   * @param $menu_data array of key values for menu options.
   */
  public function setMenu($menu_data) {
    $dom = $this->dom;
    $this->verifyHeaderElements(array('menu'));
    /** @var \DOMElement $options */
    $options = $dom->getElementsByTagNameNS($this->xmlns, 'menu')->item(0);
    foreach ($menu_data as $key => $value) {
      if ($value) {
        $options->setAttribute($key, $value);
      }
      else {
        if ($options->hasAttribute($key)) {
          $options->removeAttribute($key);
        }
      }
    }
  }

  /*
   * Set Cache data for the report
  * @param $cache_data array of key values for menu options.
  */
  public function setCache($data) {
    $dom = $this->dom;
    $this->verifyHeaderElements(array('cache'));
    /** @var \DOMElement $options */
    $options = $dom->getElementsByTagNameNS($this->xmlns, 'cache')->item(0);
    foreach ($data as $key => $value) {
      if ($value) {
        $options->setAttribute($key, $value);
      }
      else {
        if ($options->hasAttribute($key)) {
          $options->removeAttribute($key);
        }
      }
    }
  }


  /*
   * Set CSS Style Data
   * @param $menu_data array of key values for menu options.
   */
  public function setStyle($css) {
    $dom = $this->dom;
    //$this->verifyHeaderElements(array('menu'));
    $head = $dom->getElementsByTagName('head')->item(0);
    $nodes = $dom->getElementsByTagName('style');
    $style = $dom->createElement('style');
    $style->appendChild(new \DOMText($css));
    if ($nodes->length==0) {
      $head->appendChild($style);
    }
    else {
      $head->replaceChild($style, $nodes->item(0));
    }

  }

  public function removeParm($id) {
    $dom = $this->dom;
    /** @var DOMXPath $xpq */
    $xpq = $this->xpq;

    $pnode = $dom->getElementsByTagNameNS($this->xmlns, 'parameters')->item(0);
    $path = '//frx:parameters/frx:parm[@id="' . $id . '"]';

    $nodes = $xpq->query($path);
    if ($nodes->length) {
      foreach ($nodes as $node) $pnode->removeChild($node);
    }
  }

  /**
   * Make sure all xml elements have ids
   */
  private function parse_ids() {
    $i=0;
    if ($this->simplexml) {
      $this->simplexml->registerXPathNamespace('frx', Report::FRX_NS);
      $frx_attributes = array();
      $frx_nodes = $this->simplexml->xpath('body//*[@frx:*]');

      if ($frx_nodes) foreach ($frx_nodes as $node) {
        $attr_nodes = $node->attributes(Report::FRX_NS);
        if ($attr_nodes) {
          // Make sure every element has an id
          $i++;
          $id = 'forena-' . $i;

          if (!(string)$node['id']) {
            $node->addAttribute('id', $id);

          }
          else {
            if (strpos((string)$node['id'], 'forena-')===0) {
              // Reset the id to the numerically generated one
              $node['id'] = $id;
            }
            else {
              // Use the id of the element
              $id = (string)$node['id'];
            }
          }

          // Save away the frx attributes in case we need them later.
          $attr_nodes = $node->attributes(Report::FRX_NS);
          $attrs = array();
          if ($attr_nodes) foreach ($attr_nodes as $key => $value) {
            $attrs[$key] = (string)$value;
          }
          // Save away the attributes
          $frx_attributes[$id] = $attrs;
        }
      }

      $this->frx_attributes = $frx_attributes;
    }
  }

  /**
   * Removes the attributes associated with forena-# that are added by forena.
   * There is no real reason to persist them as they can be added on later and they
   * are only created for wysiwyg compatibility.
   */
  private function cleanup_ids() {
    if ($this->simplexml) {
      $this->simplexml->registerXPathNamespace('frx', Report::FRX_NS);
      $frx_nodes = $this->simplexml->xpath('body//*[@frx:*]');

      if ($frx_nodes) foreach ($frx_nodes as $node) {
        $attr_nodes = $node->attributes(Report::FRX_NS);
        if ($attr_nodes) {
          if ((string)$node['id'] && strpos($node['id'], 'forena-')===0) {
            unset($node['id']);

          }
        }
      }
    }
  }

  /**
   * Get the attributes by
   *
   * @return array Attributes
   *
   * This function will return an array for all of the frx attributes defined in the report body
   * These attributes can be saved away and added back in later using.
   */
  public function get_attributes_by_id() {
    $this->parse_ids();
    return $this->frx_attributes;
  }

  /**
   * Save attributes based on id match
   *
   * @param array $attributes
   *
   * The attributes array should be of the form
   * array( element_id => array( key1 => value1, key2 => value2)
   * The function restores the attributes based on the element id.
   */
  public function save_attributes_by_id($attributes) {
    $rpt_xml = $this->simplexml;
    if ($attributes) foreach ($attributes as $id => $att_list) {
      $id_search_path = 'body//*[@id="' . $id . '"]';
      $fnd = $rpt_xml->xpath($id_search_path);
      if ($fnd) {
        $node = $fnd[0];

        // Start attribute replacement
        $frx_attributes = $node->attributes(Report::FRX_NS);

        foreach ($att_list as $key => $value) {
          if (!$frx_attributes[$key]) {
            if ($value) $node['frx:' . $key] = $value;
          }
          else {
            unset($frx_attributes[$key]);
            if ($value) $node['frx:' . $key] = $value;
          }
        }
      }
    }
  }

  /**
   * Delete a node based on id
   * @param string $id
   * @return ReportEditor
   */
  public function deleteNode($id) {
    $path = 'body//*[@id="' . $id . '"]';
    $nodes = $this->simplexml->xpath($path);
    if ($nodes) {
      $node = $nodes[0];
      $dom=dom_import_simplexml($node);
      $dom->parentNode->removeChild($dom);
    }
    return $this;
  }

  /**
   * Scrape Data block configuration
   * This tries to introspect the frx:block configuration based
   * on the child nodes in the report by calling the
   * getConfig method on the block.
   *
   * @param string $id
   *   Extract configuration from a template.
   * @return TemplateBase
   */
  public function scrapeBlockConfig($id, &$config) {
    $template_class = "FrxMergeDocument";
    $path = "body//*[@id='$id']";
    $nodes = $this->simplexml->xpath($path);
    if ($nodes)  {
      $node = dom_import_simplexml($nodes[0]);
    }
    else {
      drupal_set_message(t('Could not find %s in report', array('%s' => $id)), 'error');
      return '';
    }

    // $block_name = $node->getAttributeNS($this->xmlns, 'block');
    $class = $node->getAttribute("class");
    $templates = $this->templateOptions();
    $config['id'] = $id;
    foreach ($templates as $tclass => $desc) {
      if (strpos($class, $tclass)!==FALSE) {
        $template_class = $tclass;
        break;
      }
    }
    if ($template_class) {
      //@TODO:  Figure out how to generate templates
      /** @var TemplateBase $c */
      $c = FrxAPI::Template($template_class);
      $config['class'] = $template_class;
      if ($c && method_exists($c, 'scrapeConfig')) {
        $c->initReportNode($node, $this->frxReport);
        $config = array_merge($config,$c->scrapeConfig());
      }
    }
    return $template_class;
  }



  /**
   * Apply a template based on the block id.
   * @param string  $id
   * @param string $class
   * @param array $config
   */
  public function applyTemplate($id, $template_class, $config=array()) {
    $path = "body//*[@id='$id']";
    $nodes = $this->simplexml->xpath($path);
    if ($nodes) {
       $node = dom_import_simplexml($nodes[0]);
    }
    else {
      drupal_set_message(t('Could not find %s in report', array('%s' => $id)), 'error');
      return; 
    }

    $block_name = $node->getAttributeNS($this->xmlns, 'block');
    $class = $node->getAttribute("class");

    $config['block'] = $block_name;

    $data= FrxAPI::BlockEditor($block_name)->data($this->parms);
    $c = FrxAPI::Template($template_class);
    if ($c) {
      $c->initReportNode($node, $this->frxReport);
      if (strpos($class, $template_class)===FALSE) {
        $c->resetTemplate();
      }
      $c->generate($data, $config);
    }
    else {
      drupal_set_message(t('Could not find template %s', array('%s' => $template_class)), 'error');
    }
  }

  public function setEditorParms($parms) {
    $this->parms = $parms;
  }

  /**
   * Add a data blcok
   * @param string $block
   *   Block to provide the data
   * @param string $class
   *   Template class to use as configuration
   * @param string $id
   *   ID of the section to insert.
   * @return ReportEditor
   */
  public function addBlock($block_name, $template_class, &$config,  $id='') {
    if (!$template_class) $template_class = 'FrxTable';
    $block_name = str_replace('.', '/', $block_name);
    if ($id) {
      $path = "body//*[@id='$id']";
      $nodes = $this->simplexml->xpath($path);
      if ($nodes) {
        $pnode = dom_import_simplexml($nodes[0]);
        $node = $this->dom->createElement('div');
        $pnode->appendChild($node);
      }
      else {
        drupal_set_message(t('Could not find %s in report', array('%s' => $id)), 'error');
        return NULL; 
      }
    }
    else {
      $nodes = $this->dom->getElementsByTagName('body');
      $pnode = $nodes->item(0);
      $node = $this->dom->createElement('div');
      $pnode->appendChild($node);
    }
    $this->frxReport->setReport($this->dom, $this->xpq);
    $config['block'] = $block_name;
    $b = FrxAPI::BlockEditor($block_name, $this->frxReport->block_edit_mode);
    $data= $b->data($this->parms);
    $this->addParameters($b->tokens());
    $c = FrxAPI::Template($template_class);
    if ($c) {
      $c->initReportNode($node, $this->frxReport);
      $c->generate($data, $config);
    }
    else {
      drupal_set_message(t('Could not find template %s', array('%s' => $template_class)), 'error');
    }
    return $this;
  }


  /**
   * Insert a data block before a node.
   * @param string $block_name
   *   Name of block to prepend
   * @param string $template_class
   *   Class of template to use.
   * @param string $id
   *   ID of element to prepend on.
   * @return ReportEditor
   */
  public function prependBlock($block_name, $template_class='FrxTable', $config=array(),  $id) {
    $block_name = str_replace('.', '/', $block_name);
    $path = "body//*[@id='$id']";
    $nodes = $this->simplexml->xpath($path);
    if ($nodes) {
       $target = dom_import_simplexml($nodes[0]);
    }
    else {
      drupal_set_message(t('Could not find %s in report', array('%s' => $id)), 'error');
      return NULL; 
    }

    $node = $this->dom->createElement('div');
    $pnode = $target->parentNode;
    $pnode->insertBefore($node, $target);
    $config['block'] = $block_name;
    $b = FrxAPI::BlockEditor($block_name, $this->frxReport->block_edit_mode);
    $data= $b->data($this->parms);
    $this->addParameters($b->tokens());
    $this->frxReport->setReport($this->dom, $this->xpq);
    $c = FrxAPI::Template($template_class);
    if ($c) {
      $c->initReportNode($node, $this->frxReport);
      $c->generate($data, $config);
    }
    else {
      drupal_set_message(t('Could not find template %s', array('%s' => $template_class)), 'error');
    }
    return $this;
  }


  public function preview($parms = array()) {
    $r = $this->frxReport;
    if(strpos($this->report_name, '__') !== 0) $r->preview_mode = TRUE;
    $content = $this->report($parms, TRUE);
    return $content;
  }


  public function fieldLink($id, $value) {
    $o = '';
    if (!isset($this->field_ids[$id])) {
      $m_path = drupal_get_path('module', 'forena');
      $report_link = $this->report_link;
      $image = array(
          'path' => url("$m_path/icons/cog.svg"),
          'alt' => t('Configure'),
          'title' => t('Configure'),
          'class' => 'forena-field-config'
      );
      $image = theme('image', $image);
      $id = urlencode($id);
      $o= l($image, "$report_link/edit/edit-field/$id", array('html' => TRUE, 'attributes' => array( 'class' => 'forena-field-config')));
      $this->field_ids[$id] = 1;
    }
    return $o;

  }

  /**
   * Add foreach section links to blocks.
   * @param string $block_name
   * @param string $id
   * @param string $context
   * @return string
   */
  public function foreachLinks($block_name, $id='', $context='') {
    $o = '';
    $report_name = $this->report_name;
    // Add the block or ID link
    $o .= '<div class="forena-edit-links">'
        . $this->l_icon("reports/$report_name/edit/select-data/add-data/$id", 'plus.svg', 'Add Detail', null, t("Data"))
        . "</div>";
    return $o;
  }


  public function l_icon($link, $name, $alt, $context=array(), $label="") {
    $path = $name=='configure.png' ? 'misc' : drupal_get_path('module', 'forena') . '/icons';

    $image = array(
      'path' => file_create_url("$path/$name"),
    	'alt' => t($alt),
      'title' => t($alt),
    );
    $image = theme('image', $image);
    $options = array('query' => $context, 'html' => TRUE);
    return l($image . $label, $link, $options);
  }


  public function blockLinks($block_name, $frx, $attrs, $id='', $context = array()) {
    $o = '';
    if (!$context) $context = array();
    $parms = FrxAPI::Data()->getContext('parm');
    if ($parms && is_array($context)) $context = array_merge($parms, $context);
    $class = @(string)$attrs['class'];
    $frx_class = strpos($class, 'FrxAPI') !== FALSE;
    if ($frx_class) {
      $frx_class = FALSE;
      foreach ($this->templateOptions() as $key => $label) {
        if(strpos($class, $key) !==FALSE) $frx_class = TRUE;
      }
    }


    $block_tag = (string)$frx['block'];

    if ($frx_class || $block_tag ) {

      $block_label = (string)$frx['block'] ? $block_name : '#' . $id;
      $block_link = str_replace('/', '.', $block_name);
      $report_name = str_replace('/', '.' , $this->desc['base_name']);
      $b = $this->dataManager()->loadBlock($block_name);
      $options = array();
      if ($context) $options['query'] = $context;

      // Add the prepend link.
      if ($block_tag) {
        // If we have a block tag we're going to prepend another data block?
        $o .= '<div class="forena-edit-links">' . $this->l_icon("reports/$report_name/edit/select-data/prepend-data/$id", 'plus.svg', 'Insert Data', null, t("Data")).  "</div>";
      }
      else {
        //$o .= '<div class="forena-edit-links">' . $this->l_icon("reports/$report_name/edit/prepend-section/$block_link/$id", 'plus.svg', 'Add Data').  "</div>";
      }
      // Add the block or ID link
      $o .= '<div class="forena-edit-links">'
         . l($block_label, "reports/$report_name/edit/edit-data/$block_link/$id", $options )
         . ' ' . $this->l_icon("reports/$report_name/edit/delete-data/$id", 'minus.svg', 'Remove Data',  $context)
         . "</div>";
    }
    return $o;

  }

  /**
   * Generate the configuration form for the template for a class.
   * @param string $class
   *   Class implementing template obejct. 
   * @param array $config
   *   Configuration of the template. 
   * @return array 
   *   Form elements representing configuration. 
   */
  public function templateConfigForm($class, $config) {
    $form  = array();
    $c = FrxAPI::Template($class);
    if ($c && method_exists($c, 'configForm')) {
      $form = $c->configForm($config);
    }
    return $form;
  }

  public function templateConfigFormValidate($class, &$config) {
    $c = FrxAPI::Template($class);
    $errors = array();
    if ($c && method_exists($c, 'configValidate')) {
      $errors = $c->configValidate($config);
    }
    return $errors;
  }

  /**
   * Generate the list of possible templates.
   */
  public function templateOptions() {
     $controls = FrxAPI::Controls();
     $templates = array();
     foreach ($controls as $control) {
       if (method_exists($control, 'generate') && isset($control->templateName)) {
         $templates[get_class($control)] = $control->templateName;
       }
     }
     asort($templates);
     return $templates;
  }

  public function editorLinks() {
    $o = '';
    $report_link = $this->report_link;
    if (!$this->edit && forena_user_access_check('design any report')) {
    // Add the block or ID link
      $o .= '<div class="forena-editor-links">'
          . $this->l_icon("$report_link/edit", 'pencil.svg', 'Edit', (array)FrxAPI::Data()->getContext('parm'));
      if (\Drupal::moduleHandler()->moduleExists('locale')) $o .= $this->l_icon("$report_link/translations", 'file.svg', 'Translations');
      if (!$this->cache->include) $o .= $this->l_icon("$report_link/delete", 'minus.svg', 'Delete');
      $o .= "</div>";
    }
    return $o;
  }

  public function documentLinks() {
    $doctypes = array_keys($this->documentManager()->getDocTypes());
    $links = array();
    $r = $this->frxReport;

    $formats = $r->formats ? $r->formats : array_filter(
      \Drupal::config('forena.settings')->get('doc_defaults')
    );
    $parms = $this->getDataContext('parm');
    foreach ($doctypes as $ext) {
      if (array_search($ext, $formats) !== FALSE) {
        $links[] = array('title' => strtoupper($ext), 'href' => $this->report_link . ".$ext", 'query' => $parms);
      }
    }
    if ($links) return array(
        '#theme' => 'links',
        '#links' => $links,
        '#attributes' => array('class' => array('forena-doclinks')));
    return '';
  }

  /**
   * Allow modules to alter the parameters of a report.
   * @param string $report_name
   * @param array $parms
   */
  function alterParameters(&$parms) {
    drupal_alter('forena_parameters', $this->report_name, $parms );
  }
}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc