<?php
/*
* OPEN POWER LIBS <http://www.invenzzia.org>
*
* This file is subject to the new BSD license that is bundled
* with this package in the file LICENSE. It is also available through
* WWW at this URL: <http://www.invenzzia.org/license/new-bsd>
*
* Copyright (c) Invenzzia Group <http://www.invenzzia.org>
* and other contributors. See website for details.
*
*/
abstract class Opt_Instruction_BaseSection extends Opt_Instruction_Loop
{
static private $_sections = array();
static private $_stack;
/**
* Initializes the section processor.
*
* @param Opt_Compiler_Class $compiler The compiler object
*/
public function __construct($compiler)
{
parent::__construct($compiler);
if(!is_object(self::$_stack))
{
self::$_stack = new SplStack;
}
} // end __construct();
/**
* Resets the processor state.
*/
public function reset()
{
if(sizeof(self::$_sections) > 0)
{
self::$_stack = new SplStack;
self::$_sections = array();
}
} // end reset();
/**
* Returns the information record about the specified section.
*
* @static
* @param String $name Section ID
* @return Array The information record or NULL, if the section is not found.
*/
static public function getSection($name)
{
if(!isset(self::$_sections[$name]))
{
return NULL;
}
return self::$_sections[$name];
} // end getSection();
/**
* Returns the currently parsed section record.
*
* @static
* @return Array The information record or NULL, if no sections are active.
*/
static public function getLastSection()
{
return self::getSection(self::$_stack->top());
} // end getLastSection();
/**
* Returns the number of active sections.
*
* @static
* @return Int The number of sections on the stack.
*/
static public function countSections()
{
return self::$_stack->count();
} // end countSections();
/**
* Creates a new section record, using the information from the specified node.
* If the node is not a valid OPT instruction, the method scans the ancestors
* to find a free opt:show node.
*
* The method can also initialize the attributed section, if we provide the
* opt:section attribute object as the second argument.
*
* The method initializes also the neighbourhood of the section by parsing the
* opt:show tag, if necessary and creating the enter condition. Note that it
* does not start the section - the record is neither put onto the section stack
* nor opt:use integration is not made. You have to use _sectionStart() in order
* to fully initialize the section.
*
* @param Opt_Xml_Element $node
* @param Opt_Xml_Attribute $attr optional
* @param Array $extraAttributes optional
* @return Array The section record.
*/
protected function _sectionCreate(Opt_Xml_Element $node, $attr = NULL, $extraAttributes = NULL)
{
/* First, we need to determine, whether the section is associated with
* opt:show. In case of tag sections, this is done only if the node
* does not contain any section attributes.
*/
$show = NULL;
if($attr instanceof Opt_Xml_Attribute)
{
$show = $this->_findShowNode($node, $attr->getValue());
if(!is_null($show))
{
// In this case we can obtain the attributes from opt:show.
$section = $this->_extractSectionAttributes($show, $extraAttributes);
$section['show'] = $show;
$section['node'] = $node;
$section['attribute'] = $attr;
}
else
{
// Generate a default section record, using the $attr value and
// optionally checking for the "separator" attribute in the current node.
$_params = array(
'separator' => array(0 => self::OPTIONAL, self::EXPRESSION, NULL),
);
$this->_extractAttributes($node, $_params);
$section = array(
'name' => $attr->getValue(),
'order' => 'asc',
'parent' => NULL,
'datasource' => NULL,
'display' => NULL,
'separator' => $_params['separator'],
'show' => null,
'node' => $node,
'attribute' => $attr
);
}
}
else
{
if(is_null($node->getAttribute('name')))
{
// We must look for opt:show
$show = $this->_findShowNode($node);
if(is_null($show))
{
throw new Opt_AttributeNotDefined_Exception('name', $node->getXmlName());
}
$section = $this->_extractSectionAttributes($show, $extraAttributes);
$section['show'] = $show;
$section['node'] = $node;
$section['attribute'] = null;
}
else
{
$section = $this->_extractSectionAttributes($node, $extraAttributes);
$section['show'] = null;
$section['node'] = $node;
$section['attribute'] = null;
}
}
$this->_validateSection($section);
if(is_null($section['show']))
{
$this->_createShowCondition($node, $section);
}
elseif(is_null($section['show']->get('priv:initialized')))
{
$this->_createShowCondition($section['show'], $section);
}
// A faster way to obtain the section name associated with this section.
$node->set('priv:section', $section['name']);
return $section;
} // end _sectionCreate();
/**
* Starts the section by putting its record on a section stack.
*
* @param Array $section The section record.
*/
protected function _sectionStart(Array &$section)
{
self::_addSection($section);
if(!is_null($section['node']->get('call:use')))
{
$this->_compiler->setConversion('##simplevar_'.$section['node']->get('call:use'), $section['name']);
}
// Populate the debug console.
if($this->_tpl->debugConsole)
{
if(isset($section['datasource']))
{
$parent = '<em>Datasource</em>';
}
elseif(!is_null($section['parent']))
{
$parent = $section['parent'];
}
else
{
$parent = '-';
}
Opt_Support::addSection($section['name'], $parent, (string)$section['format'], $section['node']->getXmlName());
}
} // end _sectionStart();
/**
* Finalizes the section associated with the specified XML node. If the
* node does not contain any valid section information, it generates
* an exception.
*
* @param Opt_Xml_Element $node The section node
*/
protected function _sectionEnd(Opt_Xml_Element $node)
{
$section = $node->get('priv:section');
if(!is_array($section))
{
$section = self::getSection($section);
}
if(!is_null($node->get('call:use')))
{
$this->_compiler->unsetConversion('##simplevar_'.$node->get('priv:section'));
}
self::_removeSection($section['name']);
} // end _sectionEnd();
/**
* Adds the show condition PHP code to the specified node, using the
* information from the section record.
*
* @internal
* @param Opt_Xml_Element $node The node, where to add the show condition.
* @param Array &$section The section info record
*/
private function _createShowCondition(Opt_Xml_Element $node, &$section)
{
// First, try to check for the call:use tag variable.
if($node->get('call:use') !== NULL)
{
$section['node']->set('call:use', $node->get('call:use'));
}
// Deal with the data formats
$format = $section['format'];
$format->assign('section', $section);
$request = $format->property('section:anyRequests');
if(!is_null($request))
{
// Send the requested data, if the data format needs any.
switch($request)
{
case 'ancestorNames':
self::$_stack->setIteratorMode(SplDoublyLinkedList::IT_MODE_LIFO | SplDoublyLinkedList::IT_MODE_KEEP);
$list = array();
$parent = $section['parent'];
foreach(self::$_stack as $up)
{
if($up == $parent)
{
$parent = self::$_sections[$up]['parent'];
$list[] = self::$_sections[$up]['name'];
}
}
$format->assign('requestedData', array_reverse($list));
break;
case 'ancestorNumbers':
self::$_stack->setIteratorMode(SplDoublyLinkedList::IT_MODE_LIFO | SplDoublyLinkedList::IT_MODE_KEEP);
$list = array();
$parent = $section['parent'];
$iteration = self::$_stack->count();
foreach(self::$_stack as $up)
{
if($up == $parent)
{
$parent = self::$_sections[$up]['parent'];
$list[] = $iteration;
}
$iteration--;
}
$format->assign('requestedData', array_reverse($list));
break;
}
}
$code = $format->get('section:init');
if(is_null($section['display']))
{
$code .= ' if('.$format->get('section:isNotEmpty').'){ ';
}
else
{
$code .= ' if('.$format->get('section:isNotEmpty').' && '.$section['display'].'){ ';
}
$code .= $format->get('section:started');
$node->addBefore(Opt_Xml_Buffer::TAG_BEFORE, $code);
$code = $format->get('section:finished').' } '.$format->get('section:done');
$node->addAfter(Opt_Xml_Buffer::TAG_AFTER, $code);
$node->set('priv:initialized', true);
} // end _createShowCondition();
/**
* Finds the nearest free opt:show node in the ascentors of the specified node.
*
* @internal
* @param Opt_Xml_Element $item The current node.
* @param String $name optional The name that opt:show must match.
* @return Opt_Xml_Element The opt:show node or NULL if not found.
*/
private function _findShowNode(Opt_Xml_Element $item, $name = null)
{
if(!is_null($name))
{
// The section names must also match!
while(!is_null($item = $item->getParent()))
{
if($item instanceof Opt_Xml_Element)
{
if($item->getXmlName() == 'opt:show')
{
$nameAttr = $item->getAttribute('name');
if(!is_null($nameAttr) && $nameAttr->getValue() == $name)
{
return $item;
}
}
}
}
}
else
{
// Here we do not need to check, whether the name in opt:show matches the argument.
while(!is_null($item = $item->getParent()))
{
if($item instanceof Opt_Xml_Element)
{
if($item->getXmlName() == 'opt:show')
{
return $item;
}
}
}
}
return NULL;
} // end _findShowNode();
/**
* The section parameter parsing is used in several places, so the
* code was moved to a separate method.
*
* @internal
* @param Opt_Xml_Element $node Parse the attributes from this node.
* @param Array $extraAttributes=NULL Extra section attributes
* @return Array The extracted attributes.
*/
private function _extractSectionAttributes(Opt_Xml_Element $node, $extraAttributes = NULL)
{
$params = array(
'name' => array(0 => self::REQUIRED, self::ID),
'parent' => array(0 => self::OPTIONAL, self::ID_EMP, NULL),
'datasource' => array(0 => self::OPTIONAL, self::EXPRESSION, NULL),
'order' => array(0 => self::OPTIONAL, self::ID, 'asc'),
'display' => array(0 => self::OPTIONAL, self::EXPRESSION, NULL),
'separator' => array(0 => self::OPTIONAL, self::EXPRESSION, NULL),
);
// The instruction may add some extra attributes.
if(!is_null($extraAttributes))
{
$params = array_merge($params, $extraAttributes);
}
$this->_extractAttributes($node, $params);
return $params;
} // end _extractSectionAttributes();
/**
* Validates the section record, determines the section parent and
* the data format.
*
* @internal
* @param Array &$section The section record.
*/
private function _validateSection(Array &$section)
{
// Verify the value of the "order" attribute.
if($section['order'] != 'asc' && $section['order'] != 'desc')
{
throw new Opt_InvalidAttributeType_Exception('order', $node->getXmlName(), '"asc" or "desc"');
}
// Determine the parent of the specified section.
// if the attribute is not set, the default behaviour is to find the nearest
// top-level and active section and link to it. Otherwise we must check if
// the chosen section exists and is active.
// Note that "parent" is ignored when we set "datasource"
if(is_null($section['parent']))
{
if(self::$_stack->count() > 0)
{
$section['parent'] = self::$_stack->top();
}
}
elseif($section['parent'] != '')
{
if(is_null(self::getSection($section['parent'])))
{
$exception = new Opt_SectionNotFound_Exception('parent', $section['parent']);
$sections = array();
self::$_stack->setIteratorMode(SplDoublyLinkedList::IT_MODE_LIFO | SplDoublyLinkedList::IT_MODE_KEEP);
foreach(self::$_stack as $up)
{
$sections[] = $up;
}
$exception->setData($sections);
throw $exception;
}
}
else
{
// For the situation, if we had 'parent=""' in the template.
$section['parent'] = null;
}
$section['nesting'] = self::countSections() + 1;
// Now we need to obtain the information about the data format.
$section['format'] = $this->_compiler->getFormat($section['name']);
if(!$section['format']->supports('section'))
{
throw new Opt_FormatNotSupported_Exception($section['format']->getName(), 'section');
}
} // end _validateSection();
/**
* Adds the new section record to the stack.
*
* @static
* @internal
* @param Array $info The section record.
*/
static private function _addSection(Array $info)
{
if(isset(self::$_sections[$info['name']]))
{
throw new Opt_SectionExists_Exception('adding section', $info['name']);
}
self::$_sections[$info['name']] = $info;
self::$_stack->push($info['name']);
} // end _addSectionInfo();
/**
* Removes the specified section from the stack. The name
* is provided to check, if the order of the closing is
* valid.
*
* @static
* @internal
* @param String $name The section name.
*/
static private function _removeSection($name)
{
if(self::$_stack->count() == 0)
{
throw new Opt_ObjectNotExists_Exception('section', $name);
}
$name2 = self::$_stack->pop();
if($name != $name2)
{
throw new Opl_Debug_Generic_Exception('OPT: Invalid section name thrown from the stack. Expected: '.$name.'; Actual: '.$name2);
}
self::$_sections[$name]['format']->resetVars();
unset(self::$_sections[$name]);
} // end _removeSection;
/**
* A dummy opt:show processor that actually does nothing, because
* it must wait for the right section instruction. If such instruction
* will not appear, the node will be parsed in the postprocessing.
*
* @internal
* @param Opt_Xml_Node $node The node
*/
protected function _processShow(Opt_Xml_Node $node)
{
$node->set('postprocess', true);
$this->_sortSectionContents($node, 'opt', 'showelse');
$this->_process($node);
} // end _processShow();
/**
* Finalizes the opt:show attribute. If there was no section in opt:show,
* it initializes a section for a moment just to generate the condition,
* but does not add it to the stack.
*
* @param Opt_Xml_Node $node The node.
*/
protected function _postprocessShow(Opt_Xml_Node $node)
{
if(!is_null($node->get('priv:initialized')))
{
return;
}
$section = $this->_extractSectionAttributes($node, null);
$section['show'] = $node;
$section['node'] = null;
$section['attribute'] = null;
$this->_validateSection($section);
$this->_createShowCondition($node, $section);
} // end _postprocessShow();
/**
* An utility method that cleans the contents of the section node, by
* moving the alternative section (opt:sectionelse etc. tags) code to the end
* of the children list.
*
* In the parameters, we must specify the name and the namespace of the
* tags that will be treated as the alternative content tags.
*
* @param Opt_Xml_Element $node The section node
* @param String $ns The namespace
* @param String $name The alternative section content tag name
*/
protected function _sortSectionContents(Opt_Xml_Element $node, $ns, $name)
{
$else = $node->getElementsByTagNameNS($ns, $name, false);
if(sizeof($else) == 1)
{
if(!$node->hasAttributes())
{
throw new Opt_InstructionTooManyItems_Exception($ns.':'.$name, $node->getXmlName(), 'Zero');
}
$node->bringToEnd($else[0]);
}
elseif(sizeof($else) > 1)
{
throw new Opt_InstructionTooManyItems_Exception($ns.':'.$name, $node->getXmlName(), 'Zero or one');
}
} // end _locateElse();
/**
* Processes the system variable $sys for the sections.
*
* @param Array $opt The system variable call splitted into separate identifiers.
* @return String The output PHP code.
*/
public function processSystemVar($opt)
{
if(sizeof($opt) < 4)
{
throw new Opt_SysVariableLength_Exception('$'.implode('.',$opt), 'short');
}
// Determine the section
$section = self::getSection($opt[2]);
if($section === null)
{
throw new Opt_SectionNotFound_Exception('OPT variable $'.implode('.',$opt), $opt[2]);
}
switch($opt[3])
{
case 'count':
return $section['format']->get('section:count');
case 'iterator':
return $section['format']->get('section:iterator');
case 'size':
return $section['format']->get('section:size');
case 'first':
return $section['format']->get('section:isFirst');
case 'last':
return $section['format']->get('section:isLast');
case 'extreme':
return $section['format']->get('section:isExtreme');
default:
$result = $this->_processSystemVar($opt);
if(is_null($result))
{
throw new Opt_SysVariableUnknown_Exception('$'.implode('.',$opt));
}
return $result;
}
} // end processSystemVar();
/**
* Allows the sections to handle specific uses of $sys special variable.
*
* @param Array $opt The system variable call splitted into separate identifiers.
* @return String The output PHP code.
*/
protected function _processSystemVar($opt)
{
return NULL;
} // end _processSystemVar();
} // end Opt_Instruction_BaseSection;