Commit 38d48ab2 authored by metzlerd's avatar metzlerd
Browse files

Major updates to support renderers that allow reapplication of

templates. 
parent f550cebb
......@@ -20,6 +20,23 @@ class Frx {
return $parms;
}
/**
* Temporary dom object used for for fragment importing and manipulation.
* We use a singleton here to reduce the memory footprint.
* @return DOMDocument
*/
public static function tempDOM() {
static $o = '';
if (!$o) {
$o= new DOMDocument('1.0', 'UTF-8');
$o->load('<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
<!ENTITY nbsp "&#160;">
]>');
}
return $o;
}
/**
* Skin Factory
* @return FrxSkin
......
......@@ -28,7 +28,7 @@ class FrxEditor {
]>';
public $xmlns = 'urn:FrxReports';
private $field_ids;
private $xpq;
public $xpq;
......@@ -45,7 +45,7 @@ class FrxEditor {
$dom->preserveWhiteSpace = TRUE;
$this->load($report_name);
$this->frxReport = new FrxReport();
$this->frxReport->setReport($this->dom);
$this->frxReport->setReport($this->dom, $this->xpq);
$cache = forena_load_cache($this->frxReport->rpt_xml);
if (isset($cache['access'])) $this->access = forena_check_all_access($cache['access']);
}
......@@ -112,6 +112,7 @@ class FrxEditor {
libxml_use_internal_errors();
try {
@$dom->loadXML($r_text);
$this->xpq = new DOMXPath($dom);
}
catch (Exception $e) {
forena_error('Invalid or malformed report document', '<pre>' .
......@@ -124,6 +125,8 @@ class FrxEditor {
$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);
return $r_text;
......@@ -689,9 +692,9 @@ class FrxEditor {
}
if ($template_class) {
$c = Frx::Template($template_class);
if ($c && method_exists($c, 'getConfig')) {
$c->initReportNode($node, $$this->frxReport);
$config = array_merge($config,$c->getConfig());
if ($c && method_exists($c, 'scrapeConfig')) {
$c->initReportNode($node, $this->frxReport);
$config = array_merge($config,$c->scrapeConfig());
}
}
return $template_class;
......@@ -763,7 +766,7 @@ class FrxEditor {
$b = Frx::BlockEditor($block_name, $this->frxReport->block_edit_mode);
$data= $b->data($this->parms);
$this->addParameters($b->tokens());
$this->frxReport->setReport($this->dom);
$this->frxReport->setReport($this->dom, $this->xpq);
$c = Frx::Template($template_class);
if ($c) {
$c->initReportNode($node, $this->frxReport);
......@@ -802,7 +805,7 @@ class FrxEditor {
$b = Frx::BlockEditor($block_name, $this->frxReport->block_edit_mode);
$data= $b->data($this->parms);
$this->addParameters($b->tokens());
$this->frxReport->setReport($this->dom);
$this->frxReport->setReport($this->dom, $this->xpq);
$c = Frx::Template($template_class);
if ($c) {
$c->initReportNode($node, $this->frxReport);
......@@ -994,6 +997,15 @@ class FrxEditor {
return $form;
}
public function templateConfigFormValidate($class, &$config) {
$c = Frx::Template($class);
$errors = array();
if ($c && method_exists($c, 'configValidate')) {
$errors = $c->configValidate($config);
}
return $errors;
}
/**
* Generate the list of possible templates.
*/
......
......@@ -37,6 +37,7 @@ class FrxReport {
public $dom;
public $format;
public $link_mode = '';
public $xpathQuery;
public $preview_mode = FALSE; // This will make the report renderer put in editing controls when TRUE
......@@ -60,7 +61,7 @@ class FrxReport {
return;
}
if (!$success) return;
$this->setReport($dom);
$this->setReport($dom, $this->xpathQuery);
}
......@@ -81,9 +82,10 @@ class FrxReport {
* Sets the report.
* @param DOMDocument $dom
*/
public function setReport(DOMDocument $dom) {
public function setReport(DOMDocument $dom, DOMXPath $xpq) {
$this->dom = $dom;
$dom->formatOutput = TRUE;
$this->xpathQuery = $xpq;
$rpt_xml = $this->rpt_xml = simplexml_import_dom($this->dom->documentElement);
$this->missing_parms = FALSE;
......
......@@ -548,11 +548,12 @@ function forena_report_data_block_form($formid, &$form_state, $report_name, $act
$id = isset($form_state['storage']['id']) ? $form_state['storage']['id'] : $id;
$template_class = isset($form_state['values']['template_class']) ? $form_state['values']['template_class'] : 'FrxTable';
if (isset($form_state['values']['config'])) {
$config = $form_state['values']['config'];
$config = array_merge($form_state['storage']['config'], $form_state['values']['config']);
}
elseif ($id) {
$template_class = $r->scrapeBlockConfig($id, $config);
}
$form_state['storage']['config'] = $config;
//The default submit handler
//If someone presses enter, this handler will execute
$form['action'] = array(
......@@ -630,6 +631,7 @@ function forena_report_data_block_form($formid, &$form_state, $report_name, $act
$parms = $form_state['storage']['parms'];
$r = Frx::Editor('__report_block_preview__');
$r->setEditorParms($parms);
//$r->templateConfigFormValidate($template_class, $form_state['values']['config'], 'config][');
$r->addBlock($block_name, $template_class, $config);
$r->update();
$preview = $r->preview($parms);
......@@ -646,6 +648,15 @@ function forena_report_data_block_form($formid, &$form_state, $report_name, $act
return $form;
}
function forena_report_data_block_form_validate($form, &$form_state) {
$r = Frx::Editor($form_state['values']['report_name']);
$template_class = $form_state['values']['template_class'];
$errors = $r->templateConfigFormValidate($template_class, $form_state['values']['config'], 'template][config][');
if ($errors) foreach ($errors as $key => $message) {
form_set_error($key, $message);
}
}
/**
* Ajax callback to refresh the template data
* @param unknown $form
......@@ -665,6 +676,7 @@ function forena_report_edit_template_submit($form, &$form_state){
extract($form_state['values']);
$r = Frx::Editor($report_name);
$parms = $form_state['storage']['parms'];
$config = array_merge($form_state['storage']['config'], $config);
$r = Frx::Editor($report_name);
$r->setEditorParms($parms);
$r->applyTemplate($id, $template_class, $config);
......@@ -675,6 +687,7 @@ function forena_report_edit_template_submit($form, &$form_state){
function forena_report_add_template_submit($form, &$form_state) {
extract($form_state['values']);
$parms = $form_state['storage']['parms'];
$config = array_merge($form_state['storage']['config'], $config);
$r = Frx::Editor($report_name);
$r->setEditorParms($parms);
Frx::Editor($report_name)->addBlock($block_name, $template_class, (array)$config, $id);
......
......@@ -5,8 +5,26 @@
*/
class FrxEmailMerge extends FrxRenderer {
public $templateName = 'Mail Merge';
public $email_input_format;
public function __construct() {
$this->email_input_format = variable_get('forena_email_input_format', 'full_text');
}
public function scrapeConfig() {
$config=array();
// $this->extractTemplateHTML($this->reportDocDomNode, $config, array('table'));
$config['from'] = $this->extractXPathInnerHTML("//*[@class='email-header-from']", $this->reportDocDomNode);
$config['to'] = $this->extractXPathInnerHTML("//*[@class='email-header-to']", $this->reportDocDomNode);
$config['cc'] = $this->extractXPathInnerHTML("//*[@class='email-header-cc']", $this->reportDocDomNode);
$config['bcc'] = $this->extractXPathInnerHTML("//*[@class='email-header-bcc']", $this->reportDocDomNode);
$config['subject'] = $this->extractXPathInnerHTML("//*[@class='email-header-sbuject']", $this->reportDocDomNode);
$config['body'] = $this->extractXPathInnerHTML("//*[@class='email-body']", $this->reportDocDomNode);
return $config;
}
public function configForm($config, $xml='') {
$form_ctl['from'] = array(
'#type' => 'textfield',
'#title' => t('From'),
......@@ -26,14 +44,20 @@ class FrxEmailMerge extends FrxRenderer {
);
$form_ctl['body'] = array(
'#type' => 'textarea',
'#type' => 'text_format',
'#title' => t('Message'),
'#default_value' => @$config['body'],
);
return $form_ctl;
}
public function configValidate(&$config) {
$this->validateTextFormats($config, array('body'));
}
public function generate($xml, $config) {
$from = $this->extract('from', $config);
$to = $this->extract('to', $config);
......
......@@ -10,6 +10,9 @@ class FrxFieldTable extends FrxRenderer {
$config['class'] = 'FrxFieldTable';
$div = $this->blockDiv($config);
$columns = $this->columns($xml);
// PUt on the header
$this->removeChildrenExcept($div, array('table'));
$this->addFragment($div, $config['header']);
$table = $this->setFirstNode($div, 4, 'table');
$this->removeChildren($table);
$r=0;
......
......@@ -19,6 +19,8 @@ class FrxRenderer {
public $columns;
public $numeric_columns;
public $xmlns = 'urn:FrxReports';
public $dom; // Reports root document node.
public $xpathQuery;
public function initReportNode(DOMElement $domNode, FrxReport $frxReport) {
......@@ -31,17 +33,16 @@ class FrxRenderer {
$this->format = $this->frxReport->format;
$skin = Frx::Data()->getContext('skin');
$this->settings = isset($skin['FrxReport']) ? $skin['FrxReport'] : array();
$this->reportDocNode->getName();
$this->htmlAttributes = $node->attributes();
$this->id = (string)$this->htmlAttributes['id'];
$this->frxAttributes = $node->attributes(FRX_NS);
$this->xpathQuery = $this->frxReport->xpathQuery;
}
public function replaceTokens($text, $raw_mode=FALSE) {
return $this->teng->replace($text, $raw_mode);
}
/**
* Returns true if a node has no children, no attributes (or empty values
* in it's attributes), and no text content
......@@ -379,12 +380,20 @@ class FrxRenderer {
* Standard php array containing merged attributes
* Enter description here ...
*/
public function mergedAttributes() {
public function mergedAttributes($node = NULL) {
if ($node) {
$frx_attributes = $node->attributes($this->xmlns);
$html_attributes = $node->attributes();
}
else {
$frx_attributes = $this->frxAttributes;
$html_attributes = $this->htmlAttributes;
}
$attributes = array();
if (isset($this->frxAttributes)) foreach ($this->frxAttributes as $key => $data) {
if ($frx_attributes) foreach ($frx_attributes as $key => $data) {
FrxRenderer::addAttributes($attributes, $key, $data);
}
if (isset($this->htmlAttributes)) foreach ($this->htmlAttributes as $key => $data) {
if ($html_attributes) foreach ($html_attributes as $key => $data) {
FrxRenderer::addAttributes($attributes, $key, $data);
}
$skin_data = Frx::Data()->getContext('skin');
......@@ -444,24 +453,45 @@ class FrxRenderer {
*/
public function configForm($config, $xml='') {
$form_ctl = array();
$form_ctl['heading'] = array(
'#type' => 'textfield',
'#title' => t('Heading'),
'#default_value' => @$config['heading'],
);
$form_ctl['description'] = array(
'#type' => 'textarea',
'#title' => t('Description'),
'#rows' => 2,
'#default_value' => @$config['description'],
'#ajax' => $this->configAjax('blur')
$form_ctl['header'] = array(
'#type' => 'text_format',
'#title' => t('Header'),
'#rows' => 5,
'#default_value' => @$config['header'],
// '#ajax' => $this->configAjax('blur')
);
return $form_ctl;
}
public function configValidate(&$config) {
return $this->validateTextFormats($config, array('header', 'footer'));
}
public function validateTextFormats(&$config, $elements) {
$dom = Frx::tempDOM();
$frag = $dom->createDocumentFragment();
$errors = array();
foreach ($elements as $element) if (isset($config[$element])) {
$config[$element] = $config[$element]['value'];
if ($config[$element]) {
@$frag->appendXML($config[$element]);
if (!isset($frag->firstChild)) {
$errors[$element] = t('Invalid XHTML in %s', array('%s' => $element));
}
else {
$this->removeChildren($frag);
}
}
}
return $errors;
}
public function scrapeConfig() {
$content = array();
$this->extractTemplateHTML($this->reportDocDomNode, $content);
}
public function configAjax($event='') {
$ajax = array(
'callback' => 'forena_template_callback',
......@@ -488,6 +518,26 @@ class FrxRenderer {
return $node;
}
function addFragment(DOMNode $node, $xml_string, $ctl_name = 'Header') {
if ($xml_string) {
$frag = $this->dom->createDocumentFragment();
try {
@$frag->appendXML($xml_string);
}
catch (Exception $e) {
return;
}
if (isset($frag->firstChild)) {
$node->appendChild($frag);
}
else {
drupal_set_message(t('Invalid XHTML in %s.', array('%s' => $ctl_name)), 'error', FALSE);
}
}
}
public function columns($xml, $path='/*/*') {
//create an array of columns
if (!is_object($xml)) return array();
......@@ -687,4 +737,80 @@ class FrxRenderer {
return $values;
}
public function removeChildrenExcept(DOMNode $node, $tags = array('table')) {
foreach ($node->childNodes as $child) {
if ($child->nodeType != XML_ELEMENT_NODE || array_search($child->tagName, $tags)===FALSE) {
$this->removeChildren($child);
$node->removeChild($child);
}
}
}
/**
* Get the textual representations of html for the configuration engine.
*/
public function extractTemplateHTML(DOMNode $node, &$content, $tags = array()) {
$cur_section = 'header';
if (!$content) $content = array('header' => '', 'content' => '', 'footer' => '');
if (!$tags) $cur_section = 'content';
foreach ($node->childNodes as $child) {
switch ($child->nodeType) {
case XML_ELEMENT_NODE:
if (array_search($child->tagName, $tags)!==FALSE) {
$cur_section = 'content';
}
elseif ($tags && $cur_section == 'content') {
$cur_section = 'footer';
}
$content[$cur_section] .= $this->dom->saveXML($child);
break;
case XML_TEXT_NODE:
case XML_ENTITY_REF_NODE:
case XML_ENTITY_NODE:
$content[$cur_section] .= $child->textContent;
break;
case XML_COMMENT_NODE:
$content[$cur_section] .= '<!--' . $child->data . '-->';
break;
}
}
}
public function extractXPathInnerHTML($query, DOMNode $context) {
$result = $this->xpathQuery->query($query, $context);
$length = $result->length;
$content = array('content' => '');
for ($i=0; $i<$length; $i++) {
$this->extractTemplateHTML($result->item($i), $content, array());
}
return $content['content'];
}
/**
* Puts attributes back in array format prior to rendering.
* @param unknown $attributes
*/
public function arrayAttributes($attributes) {
$remove_attrs = array();
foreach ($attributes as $key => $value) {
if (is_array($value)) {
$i=0;
foreach($value as $idx => $v) {
$i++;
$new_key = $key . '_' . trim($i);
$attributes[$new_key] = (string)$v;
}
$remove_attrs [] = $key;
}
}
foreach ($remove_attrs as $key) {
unset($attributes[$key]);
}
return $attributes;
}
}
\ No newline at end of file
......@@ -9,14 +9,30 @@
class FrxSVGGraph extends FrxRenderer {
private $graph;
private $links;
public $templateName = 'SVG Graph or Chart';
public $templateName = 'SVG Graph';
public $xy_data = FALSE;
function __construct() {
public function __construct() {
$library = forena_library_file('SVGGraph');
require_once $library;
}
public function scrapeConfig() {
$nodes = $this->reportDocDomNode->getElementsByTagName('svg');
if ($nodes->length) {
$svg = $nodes->item(0);
$svg = simplexml_import_dom($svg);
$config = $this->mergedAttributes($svg);
$graph_type = isset($config['type']) ? strtolower($config['type']) : 'bargraph';
$types = $this->graphTypes();
$types = array_change_key_case($types);
$config['type'] = $types[$graph_type]['type'];
$config['style'] = $types[$graph_type]['style'];
}
return $config;
}
public function render() {
// Get data from source
......@@ -207,6 +223,11 @@ class FrxSVGGraph extends FrxRenderer {
$xvalues = @$graph_types[$type]['xaxis'];
$num_series = isset($graph_types[$style]['series']) ? $graph_types[$style]['series'] : 1;
$types = $graph_options['types'];
$form_ctl['xpath'] = array(
'#type' => 'textfield',
'#title' => ('xpath'),
'#default_value' => @$config['xpath'],
);
$form_ctl['type'] = array(
'#type' => 'select',
......@@ -283,21 +304,18 @@ class FrxSVGGraph extends FrxRenderer {
}
public function generate($xml, $config) {
$media = $this->extract('media', $config);
$media = $media ? $media : 'FrxSVGGraph';
$config['class'] = get_class($this);
$media = 'FrxSVGGraph';
$div = $this->blockDiv($config);
if ($media == 'FrxSVGGraph' && $config) {
if ($config) {
// Clean series
//if (count($config['series'])==1) $config['series'] = $config['series'][1];
//if (count($config['seriesx'])==1) $config['seriesx'] = $config['seriesx'][1];
foreach ($config['series'] as $i => $series) if (!$series) unset($config['series'][$i]);
// Clean colors
if (isset($config['colors'])) foreach ($config['colors'] as $i => $color) if (!$color) unset($color[$i]);
$type = $this->extract('type', $config);
if (!$type) $type = 'Bar Graph';
$xpath = $this->extract('xpath', $config);
$clause = $this->extract('clause', $config);
$gen_table = $this->extract('gen_table', $config);
$style = $this->extract('style', $config);
if (!$style) $style = 'BarGraph';
......@@ -307,14 +325,12 @@ class FrxSVGGraph extends FrxRenderer {
$style = $styles[0];
}
if ($style) $type = $style;
$frxattrs = $this->arrayAttributes($config);
$frxattrs['renderer'] = 'FrxSVGGraph';
$frxattrs['type'] = $type;
$frxparms = array(
'type' => $type,
'renderer' => 'FrxSVGGraph',
'xpath' => $xpath,
);
$frxparms = array_merge($config, $frxparms);
$svg = $this->addNode($div, 2, 'svg', NULL, NULL, $frxparms);
$svg = $this->setFirstNode($div, 2, 'svg', NULL, NULL, $frxattrs);
if ($gen_table) {
$columns = $this->columns($xml);
$table = $this->addNode($div, 4, 'table');
......
......@@ -5,6 +5,13 @@
*/
class FrxTable extends FrxRenderer {
public $templateName = 'Simple Table';
public function scrapeConfig() {
$config=array();
$this->extractTemplateHTML($this->reportDocDomNode, $config, array('table'));
return $config;
}
public function generate($xml, $config) {
$block = @$config['block'];
......@@ -15,6 +22,11 @@ class FrxTable extends FrxRenderer {
}
$config['class'] = @$config['class'] ? $config['class'] . ' FrxTable' : 'FrxTable';
$div = $this->blockDiv($config);
// PUt on the header
$this->removeChildrenExcept($div, array('table'));
$this->addFragment($div, $config['header']);
$columns = $this->columns($xml);
$attrs = array('foreach' => '*');
......
......@@ -20,7 +20,7 @@
</frx:fields>
</head>
<body>
<div frx:block="drupal/active_users" id="forena-1" frx:foreach="*">
<div frx:block="drupal/active_users" id="forena-1" frx:foreach="*" class="FrxEmailMerge">
<div class="email-document">
<div>
<table>
......
......@@ -17,7 +17,7 @@
the detail report. In this case state. You can override these by
passing in a where clause in the query.</p>
</div>
<div frx:block="sampledb/users_by_state" id="forena-1"
<div frx:block="sampledb/users_by_state" id="forena-1" class="FrxSVGGraph"
frx:clause="order by name">
<div frx:foreach="*" id="state">
<h3>{name}</h3>
......
......@@ -24,7 +24,7 @@
<p>This report links back to the sample report 2. You can begin
to see the possibilities for inter-connecting reports</p>
</div>
<div frx:block="sampledb/users_by_state" id="forena-1">
<div frx:block="sampledb/users_by_state" id="forena-1" class="FrxSVGGraph">
<svg id="state-chart"
frx:renderer="FrxSVGGraph" frx:type="bargraph"
frx:xpath="*[total&gt;10000]"
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment