<?php
namespace Dataground;

use BadMethodCallException;
use DOMAttr;
use DOMDocument;
use DOMDocumentFragment;
use DOMElement;
use DOMNode;
use DOMNodeList;
use DOMText;
use Exception;
use InvalidArgumentException;
use SimpleXMLElement;
use XSLTCache;
use XSLTProcessor;

/**
 *
 * Copyright 2009 The SimpleDOM authors
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.

 */

/**
 * @package SimpleDOM
 * @version $Id: SimpleDOM.php 60 2010-04-01 06:34:58Z simpledom.dev $
 * @link    $URL: https://simpledom.googlecode.com/svn/trunk/SimpleDOM.php $
 */

/**
 * Alias for simplexml_load_file()
 *
 * @param $filename
 * @return    SimpleDOM
 */
function simpledom_load_file($filename)
{
    $args = func_get_args();

    if (isset($args[0]) && !isset($args[1])) {
        $args[1] = 'SimpleDOM';
    }

    return call_user_func_array('simplexml_load_file', $args);
}

/**
 * Alias for simplexml_load_string()
 *
 * Reported by goo...@svenpoeche.de, Nov 20, 2012
 *
 * @param $string
 * @return    SimpleDOM
 */
function simpledom_load_string($string)
{
    $args = func_get_args();

    if (isset($args[0]) && !isset($args[1])) {
        $args[1] = 'SimpleDOM';
    }

    return call_user_func_array('simplexml_load_string', $args);
}

/**
 * Alias for simplexml_import_dom()
 *
 * @return    SimpleDOM
 */
function simpledom_import_dom($string)
{
    $args = func_get_args();

    if (isset($args[0]) && !isset($args[1])) {
        $args[1] = 'SimpleDOM';
    }

    return call_user_func_array('simplexml_import_dom', $args);
}

/**
 * @package SimpleDOM
 */
class SimpleDOM extends SimpleXMLElement
{
    //=================================
    // Factories
    //=================================

    /**
     * Create a SimpleDOM object from a HTML string
     *
     * @param    string $source HTML source
     * @param    mixed &$errors Passed by reference. Will be replaced by an array of
     *                                    LibXMLError objects if applicable
     * @return    SimpleDOM
     */
    static public function loadHTML($source, &$errors = null)
    {
        return self::fromHTML('loadHTML', $source, $errors);
    }

    /**
     * Create a SimpleDOM object from a HTML file
     *
     * @param    string $filename Path/URL to HTML file
     * @param    mixed &$errors Passed by reference. Will be replaced by an array of
     *                                    LibXMLError objects if applicable
     * @return    SimpleDOM
     */
    static public function loadHTMLFile($filename, &$errors = null)
    {
        return self::fromHTML('loadHTMLFile', $filename, $errors);
    }

    //=================================
    // DOM stuff
    //=================================

    /** @ignore *
     * @param $name
     * @param $args
     * @throws BadMethodCallException
     * @return array|mixed
     */
    public function __call($name, $args)
    {
        $ret = null;

        $passthrough = array(
            // From DOMElement
            'getAttribute' => 'method',
            'getAttributeNS' => 'method',
            'getElementsByTagName' => 'method',
            'getElementsByTagNameNS' => 'method',
            'hasAttribute' => 'method',
            'hasAttributeNS' => 'method',
            'removeAttribute' => 'method',
            'removeAttributeNS' => 'method',
            'setAttribute' => 'method',
            'setAttributeNS' => 'method',
            // From DOMNode
            'appendChild' => 'insert',
            'insertBefore' => 'insert',
            'replaceChild' => 'insert',
            'cloneNode' => 'method',
            'getLineNo' => 'method',
            'hasAttributes' => 'method',
            'hasChildNodes' => 'method',
            'isSameNode' => 'method',
            'lookupNamespaceURI' => 'method',
            'lookupPrefix' => 'method',
            'normalize' => 'method',
            'removeChild' => 'method',
            'nodeName' => 'property',
            'nodeValue' => 'property',
            'nodeType' => 'property',
            'parentNode' => 'property',
            'childNodes' => 'property',
            'firstChild' => 'property',
            'lastChild' => 'property',
            'previousSibling' => 'property',
            'nextSibling' => 'property',
            'namespaceURI' => 'property',
            'prefix' => 'property',
            'localName' => 'property',
            'textContent' => 'property'
        );

        $dom = dom_import_simplexml($this);

        if (!isset($passthrough[$name])) {
            if (method_exists($dom, $name)) {
                throw new BadMethodCallException('DOM method ' . $name . '() is not supported');
            }

            if (property_exists($dom, $name)) {
                throw new BadMethodCallException('DOM property ' . $name . ' is not supported');
            }

            throw new BadMethodCallException('Undefined method ' . get_class($this) . '::' . $name . '()');
        }

        switch ($passthrough[$name]) {
            case 'insert':
                if (isset($args[0])
                    && $args[0] instanceof SimpleXMLElement
                ) {
                    $args[0] = $dom->ownerDocument->importNode(dom_import_simplexml($args[0]), true);
                }
            // no break; here

            case 'method':
                foreach ($args as &$arg) {
                    if ($arg instanceof SimpleXMLElement) {
                        $arg = dom_import_simplexml($arg);
                    }
                }
                unset($arg);

                $ret = call_user_func_array(array($dom, $name), $args);
                break;

            case 'property':
                $ret = $dom->$name;
                break;
        }

        if ($ret instanceof DOMText) {
            return $ret->textContent;
        }

        if ($ret instanceof DOMNode) {
            if ($ret instanceof DOMAttr) {
                /**
                 * Methods that affect attributes can't return the attributes themselves. Instead,
                 * we make them chainable
                 */
                return $this;
            }

            return simplexml_import_dom($ret, get_class($this));
        }

        if ($ret instanceof DOMNodeList) {
            $class = get_class($this);
            $list = array();
            $i = -1;

            while (++$i < $ret->length) {
                $node = $ret->item($i);
                $list[$i] = ($node instanceof DOMText) ? $node->textContent : simplexml_import_dom($node, $class);
            }

            return $list;
        }

        return $ret;
    }

    //=================================
    // DOM convenience methods
    //=================================

    /**
     * Add a new sibling before this node
     *
     * This is a convenience method. The same result can be achieved with
     *
     * $node->parentNode()->insertBefore($new, $node);
     *
     *
     * @param    SimpleXMLElement $new New node
     * @return    SimpleDOM                    The inserted node
     */
    public function insertBeforeSelf(SimpleXMLElement $new)
    {
        $tmp = dom_import_simplexml($this);
        $node = $tmp->ownerDocument->importNode(dom_import_simplexml($new), true);

        return simplexml_import_dom($this->insertNode($tmp, $node, 'before'), get_class($this));
    }

    /**
     * Add a new sibling after this node
     *
     * This is a convenience method. The same result can be achieved with
     *
     * $node->parentNode()->insertBefore($new, $node->nextSibling());
     *
     *
     * @param    SimpleXMLElement $new New node
     * @return    SimpleDOM                    The inserted node
     */
    public function insertAfterSelf(SimpleXMLElement $new)
    {
        $tmp = dom_import_simplexml($this);
        $node = $tmp->ownerDocument->importNode(dom_import_simplexml($new), true);

        return simplexml_import_dom($this->insertNode($tmp, $node, 'after'), get_class($this));
    }

    /**
     * Delete this node from document
     *
     * This is a convenience method. The same result can be achieved with
     *
     * $node->parentNode()->removeChild($node);
     *
     *
     * @throws BadMethodCallException
     * @return    void
     */
    public function deleteSelf()
    {
        $tmp = dom_import_simplexml($this);

        if ($tmp->isSameNode($tmp->ownerDocument->documentElement)) {
            throw new BadMethodCallException('deleteSelf() cannot be used to delete the root node');
        }

        $tmp->parentNode->removeChild($tmp);
    }

    /**
     * Remove this node from document
     *
     * This is a convenience method. The same result can be achieved with
     *
     * $node->parentNode()->removeChild($node);
     *
     *
     * @throws BadMethodCallException
     * @return    SimpleDOM        The removed node
     */
    public function removeSelf()
    {
        $tmp = dom_import_simplexml($this);

        if ($tmp->isSameNode($tmp->ownerDocument->documentElement)) {
            throw new BadMethodCallException('removeSelf() cannot be used to remove the root node');
        }

        $node = $tmp->parentNode->removeChild($tmp);
        return simplexml_import_dom($node, get_class($this));
    }

    /**
     * Replace this node
     *
     * This is a convenience method. The same result can be achieved with
     *
     * $node->parentNode()->replaceChild($new, $node);
     *
     *
     * @param    SimpleXMLElement $new New node
     * @return    SimpleDOM                    Replaced node on success
     */
    public function replaceSelf(SimpleXMLElement $new)
    {
        $old = dom_import_simplexml($this);
        $new = $old->ownerDocument->importNode(dom_import_simplexml($new), true);

        $node = $old->parentNode->replaceChild($new, $old);
        return simplexml_import_dom($node, get_class($this));
    }

    /**
     * Delete all elements matching a XPath expression
     *
     * @param    string $xpath XPath expression
     * @throws InvalidArgumentException
     * @return    integer            Number of nodes removed
     */
    public function deleteNodes($xpath)
    {
        if (!is_string($xpath)) {
            throw new InvalidArgumentException(
                'Argument 1 passed to deleteNodes() must be a string, ' . gettype($xpath) . ' given'
            );
        }

        $nodes = $this->_xpath($xpath);

        if (isset($nodes[0])) {
            $tmp = dom_import_simplexml($nodes[0]);

            if ($tmp->isSameNode($tmp->ownerDocument->documentElement)) {
                unset($nodes[0]);
            }
        }

        foreach ($nodes as $node) {
            $node->deleteSelf();
        }

        return count($nodes);
    }

    /**
     * Remove all elements matching a XPath expression
     *
     * @param    string $xpath XPath expression
     * @throws InvalidArgumentException
     * @return    array            Array of removed nodes on success or FALSE on failure
     */
    public function removeNodes($xpath)
    {
        if (!is_string($xpath)) {
            throw new InvalidArgumentException(
                'Argument 1 passed to removeNodes() must be a string, ' . gettype($xpath) . ' given'
            );
        }

        $nodes = $this->_xpath($xpath);

        if (isset($nodes[0])) {
            $tmp = dom_import_simplexml($nodes[0]);

            if ($tmp->isSameNode($tmp->ownerDocument->documentElement)) {
                unset($nodes[0]);
            }
        }

        $return = array();
        foreach ($nodes as $node) {
            $return[] = $node->removeSelf();
        }

        return $return;
    }

    /**
     * Remove all elements matching a XPath expression
     *
     * @param    string $xpath XPath expression
     * @param    SimpleXMLElement $new Replacement node
     * @throws InvalidArgumentException
     * @return    array                        Array of replaced nodes on success or FALSE on failure
     */
    public function replaceNodes($xpath, SimpleXMLElement $new)
    {
        if (!is_string($xpath)) {
            throw new InvalidArgumentException(
                'Argument 1 passed to replaceNodes() must be a string, ' . gettype($xpath) . ' given'
            );
        }

        $nodes = array();
        foreach ($this->_xpath($xpath) as $node) {
            $nodes[] = $node->replaceSelf($new);
        }

        return $nodes;
    }

    /**
     * Copy all attributes from a node to current node
     *
     * @param    SimpleXMLElement $src Source node
     * @param    bool $overwrite If TRUE, overwrite existing attributes.
     *                                            Otherwise, ignore duplicate attributes
     * @return    SimpleDOM                        Current node
     */
    public function copyAttributesFrom(SimpleXMLElement $src, $overwrite = true)
    {
        $dom = dom_import_simplexml($this);

        foreach (dom_import_simplexml($src)->attributes as $attr) {
            if ($overwrite
                || !$dom->hasAttributeNS($attr->namespaceURI, $attr->nodeName)
            ) {
                $dom->setAttributeNS($attr->namespaceURI, $attr->nodeName, $attr->nodeValue);
            }
        }

        return $this;
    }

    /**
     * Clone all children from a node and add them to current node
     *
     * This method takes a snapshot of the children nodes then append them in order to avoid infinite
     * recursion if the destination node is a descendant of or the source node itself
     *
     * @param    SimpleXMLElement $src Source node
     * @param    bool $deep If TRUE, clone descendant nodes as well
     * @return    SimpleDOM                    Current node
     */
    public function cloneChildrenFrom(SimpleXMLElement $src, $deep = true)
    {
        $src = dom_import_simplexml($src);
        $dst = dom_import_simplexml($this);
        $doc = $dst->ownerDocument;

        $fragment = $doc->createDocumentFragment();
        foreach ($src->childNodes as $child) {
            $fragment->appendChild($doc->importNode($child->cloneNode($deep), $deep));
        }
        $dst->appendChild($fragment);

        return $this;
    }

    /**
     * Move current node to a new parent
     *
     * ATTENTION! using references to the old node will screw up the original document
     *
     * @param    SimpleXMLElement $dst Target parent
     * @return    SimpleDOM                    Current node
     */
    public function moveTo(SimpleXMLElement $dst)
    {
        return simplexml_import_dom(dom_import_simplexml($dst), get_class($this))->appendChild($this->removeSelf());
    }

    /**
     * Return the first node of the result of an XPath expression
     *
     * @param    string $xpath XPath expression
     * @return    mixed            SimpleDOM object if any node was returned, NULL otherwise
     */
    public function firstOf($xpath)
    {
        $nodes = $this->xpath($xpath);
        return (isset($nodes[0])) ? $nodes[0] : null;
    }

    //=================================
    // DOM extra
    //=================================

    /**
     * Insert a CDATA section
     *
     * @param    string $content CDATA content
     * @param    string $mode Where to add this node: 'append' to current node,
     *                                    'before' current node or 'after' current node
     * @return    SimpleDOM                Current node
     */
    public function insertCDATA($content, $mode = 'append')
    {
        $this->insert('CDATASection', $content, $mode);
        return $this;
    }

    /**
     * Insert a comment node
     *
     * @param    string $content Comment content
     * @param    string $mode Where to add this node: 'append' to current node,
     *                                    'before' current node or 'after' current node
     * @return    SimpleDOM                Current node
     */
    public function insertComment($content, $mode = 'append')
    {
        $this->insert('Comment', $content, $mode);
        return $this;
    }

    /**
     * Insert a text node
     *
     * @param    string $content CDATA content
     * @param    string $mode Where to add this node: 'append' to current node,
     *                                    'before' current node or 'after' current node
     * @return    SimpleDOM                Current node
     */
    public function insertText($content, $mode = 'append')
    {
        $this->insert('TextNode', $content, $mode);
        return $this;
    }

    /**
     * Insert raw XML data
     *
     * @param    string $xml XML to insert
     * @param    string $mode Where to add this tag: 'append' to current node,
     *                                'before' current node or 'after' current node
     * @throws BadMethodCallException
     * @throws InvalidArgumentException
     * @return    SimpleDOM            Current node
     */
    public function insertXML($xml, $mode = 'append')
    {
        $tmp = dom_import_simplexml($this);
        $fragment = $tmp->ownerDocument->createDocumentFragment();

        /**
         * Disable error reporting
         */
        $use_errors = libxml_use_internal_errors(true);

        if (!$fragment->appendXML($xml)) {
            libxml_use_internal_errors($use_errors);
            throw new InvalidArgumentException(libxml_get_last_error()->message);
        }
        libxml_use_internal_errors($use_errors);

        $this->insertNode($tmp, $fragment, $mode);

        return $this;
    }

    /**
     * Insert a Processing Instruction
     *
     * The content of the PI can be passed either as string or as an associative array.
     *
     * @param    string $target Target of the processing instruction
     * @param    string|array $data Content of the processing instruction
     * @param string $mode
     * @throws BadMethodCallException
     * @return    bool                        TRUE on success, FALSE on failure
     */
    public function insertPI($target, $data = null, $mode = 'before')
    {
        $tmp = dom_import_simplexml($this);
        $doc = $tmp->ownerDocument;

        if (isset($data)) {
            if (is_array($data)) {
                $str = '';
                foreach ($data as $k => $v) {
                    $str .= $k . '="' . htmlspecialchars($v) . '" ';
                }

                $data = substr($str, 0, -1);
            } else {
                $data = (string)$data;
            }

            $pi = $doc->createProcessingInstruction($target, $data);
        } else {
            $pi = $doc->createProcessingInstruction($target);
        }

        if ($pi !== false) {
            $this->insertNode($tmp, $pi, $mode);
        }

        return $this;
    }

    /**
     * Set several attributes at once
     *
     * @param    array $attr Attributes as name => value pairs
     * @param    string $ns Namespace for the attributes
     * @return    SimpleDOM            Current node
     */
    public function setAttributes(array $attr, $ns = null)
    {
        $dom = dom_import_simplexml($this);
        foreach ($attr as $k => $v) {
            $dom->setAttributeNS($ns, $k, $v);
        }
        return $this;
    }

    /**
     * Return the content of current node as a string
     *
     * Roughly emulates the innerHTML property found in browsers, although it is not meant to
     * perfectly match any specific implementation.
     *
     * @todo Write a test for HTML entities that can't be represented in the document's encoding
     *
     * @return    string            Content of current node
     */
    public function innerHTML()
    {
        $dom = dom_import_simplexml($this);
        $doc = $dom->ownerDocument;

        $html = '';
        foreach ($dom->childNodes as $child) {
            $html .= ($child instanceof DOMText) ? $child->textContent : $doc->saveXML($child);
        }

        return $html;
    }

    /**
     * Return the XML content of current node as a string
     *
     * @return    string            Content of current node
     */
    public function innerXML()
    {
        $xml = $this->outerXML();
        $pos = 1 + strpos($xml, '>');
        $len = strrpos($xml, '<') - $pos;
        return substr($xml, $pos, $len);
    }

    /**
     * Return the XML representing this node and its child nodes
     *
     * NOTE: unlike asXML() it doesn't return the XML prolog
     *
     * @return    string            Content of current node
     */
    public function outerXML()
    {
        $dom = dom_import_simplexml($this);
        return $dom->ownerDocument->saveXML($dom);
    }

    /**
     * Return all elements with the given class name
     *
     * Should work like DOM0's method
     *
     * @param    string $class Class name
     * @return    array                Array of SimpleDOM nodes
     */
    public function getElementsByClassName($class)
    {
        if (strpos($class, '"') !== false
            || strpos($class, "'") !== false
        ) {
            return array();
        }

        $xpath = './/*[contains(concat(" ", @class, " "), " ' . htmlspecialchars($class) . ' ")]';
        return $this->xpath($xpath);
    }

    /**
     * Test whether current node has given class
     *
     * @param    string $class Class name
     * @return    bool
     */
    public function hasClass($class)
    {
        return in_array($class, explode(' ', $this['class']));
    }

    /**
     * Add given class to current node
     *
     * @param    string $class Class name
     * @return    SimpleDOM            Current node
     */
    public function addClass($class)
    {
        if (!$this->hasClass($class)) {
            $current = (string)$this['class'];

            if ($current !== ''
                && substr($current, -1) !== ' '
            ) {
                $this['class'] .= ' ';
            }

            $this['class'] .= $class;
        }

        return $this;
    }

    /**
     * Remove given class from current node
     *
     * @param    string $class Class name
     * @return    SimpleDOM            Current node
     */
    public function removeClass($class)
    {
        while ($this->hasClass($class)) {
            $this['class'] = substr(str_replace(' ' . $class . ' ', ' ', ' ' . $this['class'] . ' '), 1, -1);
        }
        return $this;
    }

    //=================================
    // Utilities
    //=================================

    /**
     * Return the current element as a DOMElement
     *
     * @return    DOMElement
     */
    public function asDOM()
    {
        return dom_import_simplexml($this);
    }

    /**
     * Return the current node slightly prettified
     *
     * Elements will be indented, empty elements will be minified. The result isn't mean to be
     * perfect, I'm sure there are better prettifiers out there.
     *
     * @param    string $filepath If set, save the result to this file
     * @return    mixed                If $filepath is set, will return TRUE if the file was
     *                                succesfully written or FALSE otherwise. If $filepath isn't set,
     *                                it returns the result as a string
     */
    public function asPrettyXML($filepath = null)
    {
        /**
         * Dump and reload this node's XML with LIBXML_NOBLANKS.
         *
         * Also import it as a DOMDocument because some older of XSLTProcessor rejected
         * SimpleXMLElement as a source.
         */
        $xml = dom_import_simplexml(
            new SimpleXMLElement(
                $this->asXML()
            )
        );

        $xsl = new DOMDocument;
        $xsl->loadXML(
            '<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

	<xsl:output method="xml" indent="yes" />

	<xsl:template match="text()">

		<xsl:if test="not(normalize-space(.) = \'\' and contains(., \'
\'))">
			<xsl:value-of select="."/>
		</xsl:if>
	</xsl:template>

	<xsl:template match="node()">
		<xsl:copy>
			<xsl:copy-of select="@*" />
			<xsl:apply-templates />
		</xsl:copy>
	</xsl:template>

</xsl:stylesheet>'
        );

        $xslt = new XSLTProcessor;
        $xslt->importStylesheet($xsl);

        $result = trim($xslt->transformToXML($xml));

        if (isset($filepath)) {
            return (bool)file_put_contents($filepath, $result);
        }

        return $result;
    }

    /**
     * Transform current node and return the result
     *
     * Will take advantage of {@link http://pecl.php.net/package/xslcache PECL's xslcache}
     * if available
     *
     * @param    string $filepath Path to stylesheet
     * @param    bool $use_xslcache If TRUE, use the XSL Cache extension if available
     * @return    string                    Result
     */
    public function XSLT($filepath, $use_xslcache = true)
    {
        if ($use_xslcache && extension_loaded('xslcache')) {
            $xslt = new XSLTCache;
            $xslt->importStylesheet($filepath);
        } else {
            $xsl = new DOMDocument;
            $xsl->load($filepath);

            $xslt = new XSLTProcessor;
            $xslt->importStylesheet($xsl);
        }

        return $xslt->transformToXML(dom_import_simplexml($this));
    }

    /**
     * Run an XPath query and sort the result
     *
     * This method accepts any number of arguments in a way similar to {@link
     * http://docs.php.net/manual/en/function.array-multisort.php array_multisort()}
     *
     *
     * // Retrieve all <x/> nodes, sorted by @foo ascending, @bar descending
     * $root->sortedXPath('//x', '@foo', '@bar', SORT_DESC);
     *
     * // Same, but sort @foo numerically and @bar as strings
     * $root->sortedXPath('//x', '@foo', SORT_NUMERIC, '@bar', SORT_STRING, SORT_DESC);
     *
     *
     * @param    string $xpath XPath expression
     * @return    SimpleDOM[]
     */
    public function sortedXPath($xpath)
    {
        $nodes = $this->xpath($xpath);
        $args = func_get_args();
        $args[0] =& $nodes;

        call_user_func_array(array(get_class($this), 'sort'), $args);

        return $nodes;
    }

    /**
     * Sort this node's children
     *
     * ATTENTION: text nodes are not supported. If current node has text nodes, they may be lost in
     * the process
     *
     * @return    SimpleDOM        This node
     */
    public function sortChildren()
    {
        $nodes = $this->removeNodes('*');
        $args = func_get_args();

        array_unshift($args, null);
        $args[0] =& $nodes;

        call_user_func_array(array(get_class($this), 'sort'), $args);

        foreach ($nodes as $node) {
            $this->appendChild($node);
        }

        return $this;
    }

    /**
     * Sort an array of nodes
     *
     * Note that nodes are sorted in place, nothing is returned
     *
     * @see sortedXPath
     *
     * @param    array &$nodes Array of SimpleXMLElement
     * @return    void
     */
    static public function sort(array &$nodes)
    {
        $args = func_get_args();
        unset($args[0]);

        $sort = array();
        $tmp = array();

        foreach ($args as $k => $arg) {
            if (is_string($arg)) {
                $tmp[$k] = array();

                if (preg_match('#^@?[a-z_0-9]+$#Di', $arg)) {
                    if ($arg[0] === '@') {
                        $name = substr($arg, 1);
                        foreach ($nodes as $node) {
                            $tmp[$k][] = (string)$node[$name];
                        }
                    } else {
                        foreach ($nodes as $node) {
                            $tmp[$k][] = (string)$node->$arg;
                        }
                    }
                } elseif (preg_match('#^current\\(\\)|text\\(\\)|\\.$#i', $arg)) {
                    /**
                     * If the XPath is current() or text() or . we use this node's textContent
                     */
                    foreach ($nodes as $node) {
                        $tmp[$k][] = dom_import_simplexml($node)->textContent;
                    }
                } else {
                    foreach ($nodes as $node) {
                        $_nodes = $node->xpath($arg);
                        $tmp[$k][] = (empty($_nodes)) ? '' : (string)$_nodes[0];
                    }
                }
            } else {
                $tmp[$k] = $arg;
            }

            /**
             * array_multisort() wants everything to be passed as reference so we have to cheat
             */
            $sort[] =& $tmp[$k];
        }

        $sort[] =& $nodes;

        call_user_func_array('array_multisort', $sort);
    }

    //=================================
    // Internal stuff
    //=================================

    /**#@+
     * @ignore
     * @param $xpath
     * @throws InvalidArgumentException
     * @return \SimpleXMLElement[]
     */
    protected function _xpath($xpath)
    {
        $use_errors = libxml_use_internal_errors(true);

        $nodes = $this->xpath($xpath);

        libxml_use_internal_errors($use_errors);

        if ($nodes === false) {
            throw new InvalidArgumentException('Invalid XPath expression ' . $xpath);
        }

        return $nodes;
    }

    protected function insert($type, $content, $mode)
    {
        $tmp = dom_import_simplexml($this);
        $method = 'create' . $type;

        $node = $tmp->ownerDocument->$method($content);
        return $this->insertNode($tmp, $node, $mode);
    }

    protected function insertNode(DOMNode $tmp, DOMNode $node, $mode)
    {
        if ($mode === 'before'
            || $mode === 'after'
        ) {
            if ($node instanceof DOMText
                || $node instanceof DOMElement
                || $node instanceof DOMDocumentFragment
            ) {
                if ($tmp->isSameNode($tmp->ownerDocument->documentElement)) {
                    throw new BadMethodCallException(
                        'Cannot insert a ' . get_class($node) . ' node outside of the root node'
                    );
                }
            }

            if ($mode === 'before') {
                return $tmp->parentNode->insertBefore($node, $tmp);
            }

            if ($tmp->nextSibling) {
                return $tmp->parentNode->insertBefore($node, $tmp->nextSibling);
            }

            return $tmp->parentNode->appendChild($node);
        }

        return $tmp->appendChild($node);
    }

    /**
     * NOTE: in order to support LSB, __CLASS__ would need to be replaced by get_called_class() and
     *        this method would need to be invoked via static:: instead of self::
     * @param $method
     * @param $arg
     * @param $errors
     * @return \SimpleXMLElement
     */
    static protected function fromHTML($method, $arg, &$errors)
    {
        $old = libxml_use_internal_errors(true);
        $cnt = count(libxml_get_errors());

        $dom = new DOMDocument;
        $dom->$method($arg);

        $errors = array_slice(libxml_get_errors(), $cnt);
        libxml_use_internal_errors($old);

        return simplexml_import_dom($dom, __CLASS__);
    }

    /**
     * Fixed version of addchild allows & (ampersand) in input
     * @param string $name
     * @param string $value
     * @param string $namespace
     * @return static
     */
    public function addChild($name, $value = null, $namespace = null)
    {
        if (is_object($value)) {
            return parent::addChild($name, $value, $namespace);
        } elseif ($value !== null) {
            $value = str_replace('&', '&', $value);
            return parent::addChild($name, $value, $namespace);
        } else {
            return parent::addChild($name, null, $namespace);
        }
    }

    /**
     * Get a node by attribute name and location in xpath
     *
     * @param string $strXpath
     * @param string $strName
     * @return static[] if node not found, otherwise the element
     */
    public function getNodeByName($strXpath, $strName)
    {
        $varResult = $this->xpath($strXpath . "[@name='" . $strName . "']");

        if ($varResult !== false) {
            // Get first element
            return reset($varResult);
        } else {
            return false;
        }
    }

    /**
     * Clear a data node
     *
     */
    public function clear()
    {
        dom_import_simplexml($this)->nodeValue = '';
    }

    /**
     * Get a single element by xpath
     * @param string $strXpathQuery
     * @throws Exception
     * @return static or NULL if not found
     */
    public function xpathSingle($strXpathQuery)
    {
        $arrResults = $this->xpath($strXpathQuery);

        if ($arrResults === false) {
            //throw new Exception('xpathElement returned an unspecified error on '.$strXpathQuery);
            return null;
        }

        if (is_array($arrResults) && count($arrResults) == 0) {
            return null;
        } else {
            if (count($arrResults) > 1) {
                throw new Exception(
                    'xpathSingle expects a single element as result, got ' . count($arrResults) . ' elements instead.'
                );
            } else {
                return current($arrResults);
            }
        }
    }

    /**
     * Get a nodevalue from an xpath query returning only one node (single)
     *
     * @param string $strXpathQuery
     * @return static the returning element, NULL if not found, FALSE on error
     */
    public function xpathValue($strXpathQuery)
    {

        $objResult = $this->xpathSingle($strXpathQuery);

        if ($objResult === false) {
            return false;
        } elseif ($objResult === null) {
            return null;
        } else {
            return (string)$objResult;
        }
    }

    /**
     * Execute xpath query and index by attribute
     *
     * @param string $strXpathQuery
     * @param string $strIndexAttribute
     * @return static[]
     */
    public function xpathByAttribute($strXpathQuery, $strIndexAttribute = 'name')
    {

        $arrOut = array();
        $objResult = $this->xpath($strXpathQuery);

        foreach ($objResult as $intIndex => $objNode) {
            $strIndex = (string)$objNode->attributes()->$strIndexAttribute;
            $arrOut[$strIndex] = $objResult[$intIndex];
        }

        return $arrOut;
    }

    /**
     *
     * @param string $strXpathQuery
     * @return int
     */
    public function xpathCount($strXpathQuery)
    {

        $arrElements = $this->xpath($strXpathQuery);

        if ($arrElements === false) {
            return 0;
        } else {
            return count($arrElements);
        }
    }

    /**
     * Build an array with references to subnodes of this xml element
     * Indexed by an urified index attribute (standard 'name')
     *
     * @param string $strNodeName name of the subnode
     * @param string $strIndexAttribute name of the attribute used as array index
     * @return static[]
     */
    public function buildReferenceLookupTable($strNodeName, $strIndexAttribute = 'name')
    {

        $arrOut = array();

        if (!isset($this->$strNodeName)) {
            return array();
        }

        foreach ($this->$strNodeName as $objNode) {
            $strIndex = (string)$objNode->attributes()->$strIndexAttribute;
            $arrOut[$strIndex] = $objNode;
        }

        return $arrOut;
    }

    /**
     * Get Parent Node
     *
     * @return static
     */
    public function getParent()
    {
        return current($this->xpath('parent::*'));
    }

    /**
     * Get an attribute by name
     *
     * @param string $strName
     * @param bool $varDefault
     * @return bool|string
     */
    public function getAttribute($strName, $varDefault = false)
    {
        if (isset($this->attributes()->$strName) && (((string)$this->attributes()->$strName) !== '')) {
            return (string)$this->attributes()->$strName;
        } else {
            return $varDefault;
        }
    }

    /**
     *  Get a boolean attribute value (value must either be 'true' or 'false')
     * @param $strName
     * @param bool $blnDefault
     * @throws Exception
     * @return bool
     */
    public function getBooleanAttribute($strName, $blnDefault = false)
    {
        $strValue = $this->getAttribute($strName, $blnDefault ? 'true' : 'false');
        if (!in_array($strValue, array('true', 'false'))) {
            throw new Exception(
                'Boolean attribute illegal value: "' . $strValue . '" valid values are "true" or "false"'
            );
        } else {
            return (($strValue == 'true') ? true : false);
        }
    }

    /**
     * Get an node by name
     *
     * @param string $strName
     * @param bool $varDefault
     * @return bool|string
     */
    public function getNode($strName, $varDefault = false)
    {
        $varResult = $this->$strName;

        if (($varResult !== false) && ((string)$varResult !== '')) {
            // Get first element
            return (string)$varResult;
        } else {
            return $varDefault;
        }
    }

    /**
     * Set (or create) Attribute
     *
     * @param string $strName
     * @param string $strValue
     * @return string
     */
    public function setAttribute($strName, $strValue)
    {
        if (!isset($this->attributes()->$strName)) {
            $this->addAttribute($strName, $strValue);
            return $strName;
        } else {
            $this->attributes()->$strName = $strValue;
            return $this->attributes()->$strName;
        }
    }

    /**
     * Propagate (copy) a boolean attribute (source) to all parents above it as (destination) attribute
     * Default logical operator between current parent value and source value is AND
     * so any parent (destination) attribute must be TRUE (considdered default) in order to alter it from source
     * @param $strSource
     * @param $strDestination
     * @param null $strStopAtNode
     * @param string $strMode
     * @throws Exception
     */
    public function propagateBooleanAttribute($strSource, $strDestination, $strStopAtNode = null, $strMode = 'AND')
    {
        $blnValue = false;

        // Is there a parent ?
        if (($this->getParent() !== false) && ($this->getParent()->getName() !== $strStopAtNode)) {

            if ($strMode == 'AND') {
                $blnValue = ($this->getBooleanAttribute($strSource) && $this->getParent()->getBooleanAttribute(
                        $strDestination,
                        true
                    ));
            } elseif ($strMode == 'OR') {
                $blnValue = ($this->getBooleanAttribute($strSource) || $this->getParent()->getBooleanAttribute(
                        $strDestination,
                        true
                    ));
            }

            // Set it
            $this->getParent()->setAttribute($strDestination, $blnValue ? 'true' : 'false');
            $this->getParent()->propagateBooleanAttribute($strDestination, $strDestination, $strStopAtNode, $strMode);
        }
    }

    /**
     * Set (or create) Node
     *
     * @param string $strName
     * @param string $varValue
     * @return null|\static|\static[]
     */
    public function setNode($strName, $varValue = null)
    {
        if (!isset($this->children()->$strName)) {
            if ($varValue === null) {
                return $this->addChild($strName);
            } else {
                return $this->addChild($strName, $varValue);
            }
        } else {
            if ($varValue === null) {
                unset($this->children()->$strName);
                return null;
            } else {
                $this->children()->$strName = (string)$varValue;
                return $this->children()->$strName;
            }
        }
    }

    /**
     * Remove a node
     *
     * @param $name
     */
    public function removeNode($name) {
        unset($this->children()->$name);
    }

    /**
     *
     * http://stackoverflow.com/questions/2356144/php-duplicate-xml-node-using-simple-xml
     *
     * public function copyChild($objSource, $objDestination) {
     *
     * $dom_thing = dom_import_simplexml($thing);
     * $dom_node  = dom_import_simplexml($thing->node);
     * $dom_new   = $dom_thing->appendChild($dom_node->cloneNode(TRUE));
     * $new_node  = simplexml_import_dom($dom_new);
     * }
     * */

    /**
     *    Add missing nodes and attributes to an element from a strucure element
     * Had to be written like this because clone does not work on SimpleXmlElements
     *
     * @param $objStructure
     * @param $objElement
     */
    public function addMissing($objStructure, &$objElement)
    {
        // Set missing attributes
        foreach ($objStructure->attributes() as $strName => $objAttribute) {
            if (!isset($objElement->attributes()->$strName)) {
                $objElement->addAttribute($strName, (string)$objAttribute);
            }
        }

        // Add missing nodes
        foreach ($objStructure->children() as $strName => $varNode) {
            if (count($varNode) > 0) {
                // Due to a PHP bug clone does not work
                $objNode = $objElement->addChild($strName);
                $this->addMissing($varNode, $objNode);
            } else {
                $objElement->addChild($strName, (string)$varNode);
            }
        }
    }

    /**
     * Dump pretty XML
     */
    public function dump()
    {
        $dom = new DOMDocument('1.0');
        $dom->preserveWhiteSpace = false;
        $dom->formatOutput = true;
        $dom->loadXML($this->asXML());
        return $dom->saveXML();
    }

    /**
     * Return a string to be used as xpath attribute search string.
     * This function is necessary because xpath can not by default find strings which contain a single AND a double quote.
     * Usage:
     *
     *
     *     $object->xpath('element[\@name='.getXpathStringForAttributeValue('test').']');
     *
     *
     * @param $attributeValue
     * @return string
     */
    public function getXpathStringForAttributeValue($attributeValue)
    {
        $hasApos = strpos($attributeValue, "'");
        $hasQuote = strpos($attributeValue, '"');

        if ($hasApos === false) {
            return "'" . $attributeValue . "'";
        }
        if ($hasQuote === false) {
            return '"' . $attributeValue . '"';
        }

        $result = 'concat(';
        $currentArgument = '';
        for ($pos = 0; $pos < strlen($attributeValue); $pos++) {
            switch (substr($attributeValue, $pos, 1)) {
                case "'":
                    $result .= '"';
                    $result .= $currentArgument;
                    $result .= "'\",";
                    $currentArgument = '';
                    break;
                case '"':
                    $result .= "'";
                    $result .= $currentArgument;
                    $result .= "\"',";
                    $currentArgument = '';
                    break;
                default:
                    $currentArgument .= substr($attributeValue, $pos, 1);
                    break;
            }
        }
        if (strlen($currentArgument) === 0) {
            $result = substr($result, 0, -1) . ')';
        } else {
            $result .= "'";
            $result .= $currentArgument;
            $result .= "')";
        }
        return $result;
    }

    /**#@-*/
}


<?php
/**
 * BSD 3-Clause License
 *
 * Copyright (c) 2019, TASoft Applications
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 *
 *  Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 *  Neither the name of the copyright holder nor the names of its
 *   contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

namespace Skyline\Compiler;

use Skyline\Compiler\CompilerConfiguration as CC;
use Skyline\Compiler\Context\Code\SourceCodeManager;
use Skyline\Compiler\Context\FileCache\FileCacheInterface;
use Skyline\Compiler\Context\FileCache\LocalFileCache;
use Skyline\Compiler\Context\Logger\LoggerInterface;
use Skyline\Compiler\Context\Logger\OutputLogger;
use Skyline\Compiler\Context\ValueCache\ValueCache;
use Skyline\Compiler\Context\ValueCache\ValueCacheInterface;
use Skyline\Compiler\Exception\CompilerException;
use Skyline\Compiler\Project\Attribute\AttributeInterface;
use Skyline\Compiler\Project\Attribute\CompilerContextParameterCollection;
use Skyline\Compiler\Project\Attribute\SearchPathCollection;
use Skyline\Compiler\Project\ProjectInterface;
use Skyline\Kernel\Service\Error\AbstractErrorHandlerService;
use TASoft\Collection\DependencyCollection;
use TASoft\Config\Config;
use Throwable;

class CompilerContext
{
    /** @var ProjectInterface */
    private $project;

    /** @var LoggerInterface */
    private $logger;

    /** @var ValueCacheInterface */
    private $valueCache;

    /** @var FileCacheInterface */
    private $fileCache;

    /** @var Config */
    private $configuration;

    /** @var SourceCodeManager */
    private $sourceCodeManager;

    /** @var array  */
    private $compilers = [];

    /** @var CompilerInterface[] */
    private $orderedCompilers;

    /** @var CompilerContextParameterCollection */
    private $contextParameters;

    /**
     * CompilerContext constructor.
     * @param ProjectInterface $project
     */
    public function __construct(ProjectInterface $project)
    {
        $this->project = $project;
    }

    /**
     * Makes a compiler ready to execute
     *
     * @param CompilerInterface|CompilerFactoryInterface $compiler
     */
    public function addCompiler($compiler) {
        if($compiler instanceof CompilerFactoryInterface || $compiler instanceof CompilerInterface) {
            $this->compilers[] = $compiler;
            $this->orderedCompilers = NULL;
        } else
            throw new CompilerException("Can only add objects that implement CompilerInterface or CompilerFactoryInterface to context");
    }

    /**
     * Removes a compiler from list if exists
     *
     * @param $compiler
     */
    public function removeCompiler($compiler) {
        if(($idx = array_search($compiler, $this->compilers)) !== false) {
            unset($this->compilers[$idx]);
            $this->orderedCompilers = NULL;
        }
    }

    /**
     * @return LoggerInterface
     */
    public function getLogger(): LoggerInterface
    {
        if(!$this->logger)
            $this->logger = new OutputLogger();
        return $this->logger;
    }

    /**
     * @param LoggerInterface $logger
     */
    public function setLogger(LoggerInterface $logger): void
    {
        $this->logger = $logger;
    }

    /**
     * @return FileCacheInterface
     */
    public function getFileCache(): FileCacheInterface
    {
        if(!$this->fileCache) {
            $fn = CC::get($this->getConfiguration(), CC::COMPILER_CACHE_FILENAME);
            $this->fileCache = new LocalFileCache($fn);
        }
        return $this->fileCache;
    }

    /**
     * @param FileCacheInterface $fileCache
     */
    public function setFileCache(FileCacheInterface $fileCache): void
    {
        $this->fileCache = $fileCache;
    }

    /**
     * @return ValueCacheInterface
     */
    public function getValueCache(): ValueCacheInterface
    {
        if(!$this->valueCache)
            $this->valueCache = new ValueCache();
        return $this->valueCache;
    }

    /**
     * @param ValueCacheInterface $valueCache
     */
    public function setValueCache(ValueCacheInterface $valueCache): void
    {
        $this->valueCache = $valueCache;
    }

    /**
     * Returns the project.
     * While compiling, it never returns NULL!
     *
     * @return ProjectInterface|null
     */
    public function getProject(): ?ProjectInterface
    {
        return $this->project;
    }

    /**
     * @return Config
     */
    public function getConfiguration(): Config
    {
        if(!$this->configuration)
            $this->configuration = new Config();
        return $this->configuration;
    }

    /**
     * @param Config $configuration
     */
    public function setConfiguration(Config $configuration): void
    {
        $this->configuration = $configuration;
    }

    /**
     * Returns the Skyline CMS Application data directory (absolute)
     * @return string
     */
    public function getSkylineAppDataDirectory() {
        $skylineTarget = CC::get($this->getConfiguration(), CC::SKYLINE_APP_DATA_DIR);
        $projDir = $this->getProject()->getProjectRootDirectory();
        return "$projDir/$skylineTarget/";
    }

    /**
     * Returns the Skyline CMS Application data directory (absolute)
     * @return string
     */
    public function getSkylinePublicDataDirectory() {
        $skylineTarget = CC::get($this->getConfiguration(), CC::SKYLINE_PUBLIC_DATA_DIR);
        $projDir = $this->getProject()->getProjectRootDirectory();
        return "$projDir/$skylineTarget/";
    }

    /**
     *  Returns the required Skyline CMS Application data sub directory
     *
     * @param string $dirName
     * @return string
     * @see CompilerConfiguration::SKYLINE_DIR_* constants
     */
    public function getSkylineAppDirectory(string $dirName) {
        $name = CC::get([], $dirName);
        if($name) {
            return $this->getSkylineAppDataDirectory() . "/$name";
        }
        return NULL;
    }

    /**
     * Obtaining search paths from project
     *
     * @param string $name
     * @return array
     */
    public function getProjectSearchPaths(string $name): array {
        $srcPaths = $this->getProject()->getAttribute(AttributeInterface::SEARCH_PATHS_ATTR_NAME);
        if($srcPaths instanceof SearchPathCollection) {
            return $srcPaths->getSearchPaths($name) ?? [];
        }
        return [];
    }

    /**
     * Resolves the compilers against their dependencies
     *
     * @return array
     */
    public function getOrganizedCompilers() {
        if(NULL === $this->orderedCompilers) {
            $depCollection = new DependencyCollection(false);
            $depCollection->setAcceptsDuplicates(false);

            foreach($this->compilers as $compiler) {
                if($compiler instanceof CompilerInterface) {
                    $id = $compiler->getCompilerID();
                    $deps = $compiler->getDependsOnCompilerIDs();
                    if($deps)
                        $depCollection->add($id, $compiler, $deps);
                    else
                        $depCollection->add($id, $compiler);
                }
                elseif($compiler instanceof CompilerFactoryInterface) {
                    $compiler->registerCompilerInstances($depCollection, $this);
                }
            }

            $this->orderedCompilers = $depCollection->getOrderedElements();
        }
        return $this->orderedCompilers;
    }

    /**
     * Main compile command.
     * Call this function to resolve any compiler factories and dependents.
     * Then every compiler is called to compile its stuff
     *
     * The compilation does not throw any error or notification. Everything is redirected to the logger.
     * To handle errors, use a different logger.
     *
     * @param callable $validator
     */
    public function compile(callable $validator = NULL) {
        if(!($project = $this->getProject())) {
            $project = CC::get($this->getConfiguration(), CC::COMPILER_PROJECT);
            if(!$project)
                throw new CompilerException("Compilation without project settings is not possible");
        }

        $this->project = $project;

        try {
            set_error_handler(function($code, $msg, $file, $line) {
                switch(AbstractErrorHandlerService::detectErrorLevel($code)) {
                    case AbstractErrorHandlerService::NOTICE_ERROR_LEVEL: return $this->getLogger()->logNotice($msg, [$file, $line]);
                    case AbstractErrorHandlerService::DEPRECATED_ERROR_LEVEL:
                    case AbstractErrorHandlerService::WARNING_ERROR_LEVEL: return $this->getLogger()->logWarning($msg, [$file, $line]);
                    default: return $this->getLogger()->logError($msg, [$file, $line]);
                }
            });

            /** @var CompilerInterface $compiler */
            foreach($this->getOrganizedCompilers() as $compiler) {
                if(!$validator || $validator($compiler))
                    $compiler->compile($this);
            }
        } catch (Throwable $throwable) {
            $this->getLogger()->logException($throwable);
        } finally {
            restore_error_handler();
        }
    }

    /**
     * @return SourceCodeManager
     */
    public function getSourceCodeManager(): SourceCodeManager
    {
        if(!$this->sourceCodeManager)
            $this->sourceCodeManager = new SourceCodeManager($this);
        return $this->sourceCodeManager;
    }

    /**
     * @param SourceCodeManager $sourceCodeManager
     */
    public function setSourceCodeManager(SourceCodeManager $sourceCodeManager): void
    {
        $this->sourceCodeManager = $sourceCodeManager;
    }

    /**
     * @return CompilerContextParameterCollection
     */
    public function getContextParameters(): CompilerContextParameterCollection
    {
        if(!$this->contextParameters)
            $this->contextParameters = new CompilerContextParameterCollection("context");
        return $this->contextParameters;
    }

    /**
     * @param CompilerContextParameterCollection $contextParameters
     */
    public function setContextParameters(CompilerContextParameterCollection $contextParameters): void
    {
        $this->contextParameters = $contextParameters;
    }

    /**
     * If returned true, all compilers should store absolute linked file references
     *
     * @return bool
     */
    public function useZeroLinks(): bool {
        return CC::get($this->getConfiguration(), CC::COMPILER_ZERO_LINKS);
    }
}


<?php

/*
 * Chill is a software for social workers
 *
 * Copyright (C) 2014-2015, Champs Libres Cooperative SCRLFS,
 * <http://www.champs-libres.coop>, <info@champs-libres.coop>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

namespace Chill\CustomFieldsBundle\Service;

use Doctrine\ORM\EntityManagerInterface;
use Chill\CustomFieldsBundle\Service\CustomFieldProvider;
use Chill\CustomFieldsBundle\Entity\CustomFieldsGroup;
use Chill\CustomFieldsBundle\Entity\CustomField;

/**
 * Helpers for manipulating custom fields.
 *
 * Herlpers for getting a certain custom field, for getting the raw value
 * of a custom field and for rendering the value of a custom field.
 *
 * @author Julien Fastré <julien.fastre@champs-libres.coop>
 *
 */
class CustomFieldsHelper
{
    /** @var EntityManagerInterface $em The entity manager */
    private $em;

    /** @var CustomFieldProvider $provider Provider of all the declared custom
     * fields */
    private $provider;

    /**  @var array $cache Matrix to cache all the custom fields. The array
     * is indexed by the EntityClass then the slug  */
    private $cache = array();

    /**
     * Constructor
     *
     * @param EntityManagerInterface $em The entity manager
     * @param CustomFieldProvider $provider The customfield provider that
     * contains all the declared custom fields
     */
    public function __construct(EntityManagerInterface $em,
        CustomFieldProvider $provider)
    {
        $this->em = $em;
        $this->provider = $provider;
    }

    /**
     * Set in cache all the custom fields of a given class containing some
     * custom fields.
     *
     * @param object|string $class The given class.
     * @todo check if this fucntions has to call _cacheCustomFieldsGroup instead of
     * _cacheCustomFields ?
     */
    private function _cacheCustomFields($class)
    {
        $customFieldsGroups = $this->em->getRepository('ChillCustomFieldsBundle:CustomFieldsGroup')
            ->findBy(array('entity' => (is_string($class)) ? $class : get_class($class)));

        if (!$customFieldsGroups) {
            throw CustomFieldsHelperException::customFieldsGroupNotFound((is_string($class)) ? $class : get_class($class));
        }

        foreach ($customFieldsGroup as $cfGroup) {
            $this->_cacheCustomFields($cfGroup);
        }
    }

    /**
     * Set in cache of the custom fields of a customfield Group.
     *
     * @param CustomFieldsGroup $group The given CustomFieldsGroup
     */
    private function _cacheCustomFieldsGroup(CustomFieldsGroup $group)
    {
        foreach ($group->getCustomFields() as $field) {
            $this->cache[$group->getEntity()][$field->getSlug()] = $field;
        }
    }

    /**
     * Return a requested customField
     *
     * @param object|string $class The requested class
     * @param string $slug The slug. BEWARE If the slug is null, throw a proper CustomFieldsHelperException
     * @return CustomField The requested CustomField
     * @throws CustomFieldsHelperException if $slug is null
     */
    public function getCustomField($class, $slug = null)
    {
        if (!$slug) {
            throw CustomFieldsHelperException::slugIsMissing();
        }

        $resolveClass = (is_string($class)) ? $class : get_class($class);
        if (!$this->cache[$resolveClass][$slug]) {
            $this->_cacheCustomFields($resolveClass);
        }

        return $this->cache[$resolveClass][$slug];
    }

    /**
     * Return the stored/raw value of a custom field.
     *
     * The method return null if the slug is not recorded.
     *
     * @param array $fields  the **raw** array, as stored in the db
     * @param object|string $class
     * @param string $slug
     * @return mixed|null The value or null if the slug is not recorded
     */
    private function getCustomFieldValue(array $fields, $class, $slug)
    {
        return (isset($fields[$slug])) ? $this->provider
            ->getCustomFieldByType($this->getCustomField($class, $slug)->getType())
            ->deserialize($fields[$slug])
            : null;
    }

    public function isEmptyValue(array $fields, $classOrCustomField, $slug = null)
    {
        $customField = ($classOrCustomField instanceof CustomField) ? $classOrCustomField : $this->getCustomField($classOrCustomField, $slug);
        $slug = $customField->getSlug();
        $rawValue = (isset($fields[$slug])) ? $fields[$slug] : null;

        $customFieldType =  $this->provider->getCustomFieldByType($customField->getType());

        return $customFieldType->isEmptyValue($rawValue, $customField);
    }

    /**
     * Render the value of a custom field
     *
     * @param array $fields the **raw** array, as stored in the db
     * @param CustomField|object|string $classOrCustomField the object OR the get_class($object) string OR The CustomField
     * @param string $documentType The type of document in which the rendered value is displayed ('html' or 'csv').
     * @param string $slug The slug of the custom field to render.
     * @param boolean $showIfEmpty If the widget must be rendered if the value is empty. An empty value is all values described as http://php.net/manual/fr/function.empty.php, except `FALSE`
     * @throws CustomFieldsHelperException if slug is missing
     * @return The representation of the value the customField.
     */
    public function renderCustomField(array $fields, $classOrCustomField, $documentType='html', $slug = null, $showIfEmpty = true)
    {
        $customField = ($classOrCustomField instanceof CustomField) ? $classOrCustomField : $this->getCustomField($classOrCustomField, $slug);
        $slug = $customField->getSlug();
        $rawValue = (isset($fields[$slug])) ? $fields[$slug] : null;
        $customFieldType =  $this->provider->getCustomFieldByType($customField->getType());

        return $customFieldType->render($rawValue, $customField, $documentType);
    }
}


<?php

namespace Tokenly\BitcoinAddressLib;

use Exception;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\ServiceProvider;
use Tokenly\BitcoinAddressLib\BitcoinAddressGenerator;

/*
* BitcoinAddressServiceProvider
*/
class BitcoinAddressServiceProvider extends ServiceProvider
{

    public function boot()
    {
    }

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->bindConfig();

        $this->app->bind('Tokenly\BitcoinAddressLib\BitcoinAddressGenerator', function($app) {
            $seed = Config::get('bitcoin-address-lib.seed');
            if (!$seed) { throw new Exception("A seed value is required for the bitcoin address generator", 1); }
            $generator = new BitcoinAddressGenerator($seed);
            return $generator;
        });
    }

    protected function bindConfig()
    {
        // simple config
        $config = [
            'bitcoin-address-lib.seed' => env('BITCOIN_MASTER_KEY'),
        ];

        // set the laravel config
        Config::set($config);
    }

}


<?php

namespace NodePub\Common;

use Symfony\Component\Finder\Finder;

trait SourceDirectoryAwareTrait
{
    /**
     * @var array
     */
    protected $sourceDirs = array();

    /**
     * Adds a directory to the array of sources that will
     * be searched for themes to load.
     */
    public function addSource($sourcePath, $useStrict=true)
    {
        if (is_link($sourcePath)) {
            return $this->addSource(realpath($sourcePath), $useStrict);
        }

        if (is_dir($sourcePath)) {
            $this->sourceDirs[] = $sourcePath;
        } elseif (true === $useStrict) {
            throw new \Exception(sprintf('Path {%s} is not a readable directory', $sourcePath));
        }

        return $this;
    }

    /**
     * Searches source directories for criteria in given Finder
     *
     * @return Finder
     */
    public function findInSourceDirs(Finder $finder)
    {
        foreach ($this->sourceDirs as $dir) {
            $finder->in($dir);
        }

        return $finder;
    }
}


<?php
/**
 * This file is part of the RedKite CMS Application and it is distributed
 * under the GPL LICENSE Version 2.0. To use this application you must leave
 * intact this copyright notice.
 *
 * Copyright (c) RedKite Labs <info@redkite-labs.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * For extra documentation and help please visit http://www.redkite-labs.com
 *
 * @license    GPL LICENSE Version 2.0
 *
 */

namespace RedKiteCms\Rendering\PageRenderer;

use RedKiteCms\Bridge\Dispatcher\Dispatcher;
use RedKiteCms\Content\Block\BaseBlock;
use RedKiteCms\Content\PageCollection\PagesCollectionParser;
use RedKiteCms\EventSystem\Event\Render\PageRenderedEvent;
use RedKiteCms\EventSystem\Event\Render\SlotsRenderedEvent;
use RedKiteCms\EventSystem\Event\Render\SlotsRenderingEvent;
use RedKiteCms\EventSystem\RenderEvents;
use RedKiteCms\FilesystemEntity\Page;
use RedKiteCms\FilesystemEntity\Slot;

/**
 * Class PageRendererBackend is the object assigned to render a page for the CMS backend
 *
 * @author  RedKite Labs <webmaster@redkite-labs.com>
 * @package RedKiteCms\Rendering\PageRenderer
 */
class PageRendererBackend
{
    /**
     * @type \Twig_Environment
     */
    private $templating;
    /**
     * @type \RedKiteCms\Content\PageCollection\PagesCollectionParser
     */
    private $pagesParser;
    /**
     * @var array
     */
    private $slots = array();
    /**
     * @var string
     */
    protected $environment = "Backend";

    /**
     * Constructor
     *
     * @param \Twig_Environment $templating
     * @param \RedKiteCms\Content\PageCollection\PagesCollectionParser $pagesParser
     */
    public function __construct(\Twig_Environment $templating, PagesCollectionParser $pagesParser)
    {
        $this->templating = $templating;
        $this->pagesParser = $pagesParser;
    }

    /**
     * @return array
     */
    public function getSlots()
    {
        return $this->slots;
    }

    /**
     * Renders the page slots from a Page entity
     * @param \RedKiteCms\FilesystemEntity\Page $page
     * @param array $options
     *
     * @return array
     */
    public function render(Page $page, array $options = array())
    {
        $slots = $page->getPageSlots();
        $this->slots = $this->renderSlots($page, $slots, $options);

        $event = Dispatcher::dispatch(RenderEvents::PAGE_RENDERED . '.' . strtolower($page->getPageName()), new PageRenderedEvent($page));

        return $event->getPage();
    }

    /**
     * Render the cms blocks
     *
     * @param array $blocks
     * @param $username
     * @param array $options
     *
     * @return string
     */
    public function renderCmsBlocks(array $blocks, $username, array $options = array())
    {
        $tmp = array();
        foreach ($blocks as $block) {
            $tmp[] = $this->renderCmsBlock($block, $username, $options);
        }

        return implode("\n", $tmp);
    }

    /**
     * Renders the given block
     *
     * @param \RedKiteCms\Content\Block\BaseBlock $block
     * @param $username
     * @param array $options
     *
     * @return string
     */
    public function renderCmsBlock(BaseBlock $block, $username, array $options = array())
    {
        $blockTemplate = $this->fetchTemplateBlock($block);
        if ($blockTemplate == "") {
            return "";
        }

        $permalinks = $this->pagesParser
            ->contributor($username)
            ->parse()
            ->permalinksByLanguage();

        $options = array_merge(
            array(
                'block' => $block,
                'permalinks' => $permalinks,
            ),
            $options
        );

        return $this->templating->render($blockTemplate, $options);
    }

    /**
     * Renders the slots
     *
     * @param \RedKiteCms\FilesystemEntity\Page $page
     * @param array $slots
     * @param array $options
     *
     * @return array
     */
    protected function renderSlots(Page $page, array $slots, array $options = array())
    {
        $renderedSlots = array();
        $slots = $this->dispatchSlotsEvent(RenderEvents::SLOTS_RENDERING, $page, $slots);
        foreach ($slots as $slotName => $slot) {
            if (is_string($slot)) {
                $renderedSlots[$slotName] = $slot;
                continue;
            }

            if (!$slot instanceof Slot) {
                continue;
            }

            $blocks = $slot->getEntitiesInUse();
            $renderedSlots[$slotName] = $this->templating->render(
                'RedKiteCms/Resources/views/Slot/slot.html.twig',
                array(
                    'options' => $options,
                    'slotname' => $slotName,
                    'data' => rawurlencode("[" . implode(",", $blocks)) . "]",
                    'next' => $slot->getNext(),
                )
            );
        }

        return $slots = $this->dispatchSlotsEvent(RenderEvents::SLOTS_RENDERED, $page, $renderedSlots);
    }

    /**
     * Generates the events based on the current page.
     *
     * RedKite CMS will generate four events:
     *
     * 1. [ Base render name ] This event is used to change a slot content for the entire site
     * 2. [ Base render name ].[ Language ] This event is used to change a slot content for the event language
     * 3. [ Base render name ].[ Page ] This event is used to change a slot content for the event page collection
     * 4. [ Base render name ].[ Language ].[ Page ] This event is used to change a slot content for the event page and
     * language
     *
     * @param $baseEventName
     * @param \RedKiteCms\FilesystemEntity\Page $page
     *
     * @return array
     */
    protected function generateEventNames($baseEventName, Page $page)
    {
        $pageName = $page->getPageName();
        $language = $page->getCurrentLanguage();

        return array(
            $baseEventName,
            $baseEventName . '.' . $language,
            $baseEventName . '.' . $pageName,
            $baseEventName . '.' . $language . '.' . $pageName,
        );
    }

    /**
     * Dispatches the events
     *
     * @param $baseEventName
     * @param \RedKiteCms\FilesystemEntity\Page $page
     * @param array $slots
     *
     * @return array
     */
    protected function dispatchSlotsEvent($baseEventName, Page $page, array $slots)
    {
        $eventNames = $this->generateEventNames($baseEventName, $page);
        $event = new SlotsRenderingEvent($slots);
        foreach($eventNames as $eventName) {
            $event = Dispatcher::dispatch($eventName, $event);
        }

        return $event->getSlots();
    }

    /**
     * Decodes the given blocks
     * @param $values
     *
     * @return array
     */
    protected function decodeBlocks($values)
    {
        $result = array();
        foreach ($values as $value) {
            $block = json_decode($value, true);
            $result[] = $block["name"];
        }

        return $result;
    }

    protected function fetchTemplateBlock(BaseBlock $block)
    {
        preg_match('/RedKiteCms\\\\Block\\\\([^\\\\]+)\\\\Core/',  get_class($block), $matches);
        $pluginName = $matches[1];
        $blockName = $block->getType();

        return sprintf('%s/Resources/views/%s/%s.html.twig', $pluginName, $this->environment, strtolower($blockName));
    }
}


<?php namespace ForeverPHP\Routing;

use ForeverPHP\Core\{App, Settings, Setup};
use ForeverPHP\Core\Exceptions\{AppException, RouterException};
use ForeverPHP\Core\Facades\{Redirect, Request, Storage};
use ForeverPHP\Http\Response;
use ForeverPHP\Session\SessionManager;
use ForeverPHP\View\{Context, View};

/**
 * Almacena todas las rutas en una matriz para luego ejecutar la ruta
 * solicitada.
 *
 * @author      Daniel Nuñez S. <dnunez@pointers.cl>
 * @since       Version 1.0.0
 */
class Router
{
    private $uriBase = '/';
    private $routes = array();
    private $complexRoutes = array();
    private $nameForRoute = null;

    private $gets = array();
    private $posts = array();

    /**
     * Contiene la instancia singleton de Router.
     *
     * @var \ForeverPHP\Routing\Router
     */
    private static $instance;

    public function Router()
    {
        //
    }

    /**
     * Obtiene o crea la instancia singleton de Router.
     *
     * @return \ForeverPHP\Routing\Router
     */
    public static function getInstance()
    {
        if (is_null(static::$instance)) {
            static::$instance = new static();
        }

        return static::$instance;
    }

    private function addSlash(string $route)
    {
        $url = $route;

        if (strlen($url) == 0) {
            $url = '/';
        } else {
            // Busca el ultimo slash si no esta se agrega
            if ($url[strlen($url) - 1] != '/') {
                $url .= '/';
            }
        }

        return $url;
    }

    private function removeSlash(string $route)
    {
        $url = $route;

        if ($url[strlen($url) - 1] == '/') {
            $url = rtrim($url, '/');
        }

        return $url;
    }

    private function getUriBase()
    {
        $this->uriBase = str_replace(basename($_SERVER['SCRIPT_NAME']), '', $_SERVER['SCRIPT_NAME']);
    }

    private function parseRoute(string $route, array &$paramsUrl)
    {
        $newRoute = $route;
        $matches = array();

        // Patrones de busqueda
        $regexFindParam = "/{([0-9A-Za-z\-_]*)([?])?}/";
        $regexParamRequire = "/{([0-9A-Za-z\-_]*)}/";
        $regexParamNoRequire = "/{([0-9A-Za-z\-_]*)\?}/";

        // Patrones de reemplazo
        $replaceParamRequire = "([0-9a-zA-Z\-_]+)";
        $replaceParamNoRequire = "([0-9a-zA-Z\-_]*)";

        preg_match_all($regexFindParam, $route, $matches, PREG_SET_ORDER);

        if (count($matches) > 0) {
            foreach ($matches as $match) {
                $paramsUrl[$match[1]] = count($match) == 3 ? false : true;
            }

            // Reemplazo los parametros requiridos y no requeridos
            $newRoute = preg_replace($regexParamRequire, $replaceParamRequire, $route);
            $newRoute = preg_replace($regexParamNoRequire, $replaceParamNoRequire, $newRoute);

            // Le agrego esta pequeña expresion regular al final para no tener problemas con el slash
            $newRoute .= '[/]?';
        } else {
            $newRoute = $this->addSlash($newRoute);
        }

        return $newRoute;
    }

    /*
     * ABSOLETA: Se eliminara esta version ya que ahora habra un directorio con rutas.
     */
    /**
     * Carga las rutas propias de una aplicación.
     *
     * @param  string $appName
     */
    public function fromApp(string $appName)
    {
        require_once APPS_ROOT . DS . $appName . DS . 'approutes.php';
    }

    public function get()
    {
        //
    }

    public function post()
    {
        //
    }

    /*
     * ESTA FUNCION QUEDARA OBSOLETA SIENDO REEMPLAZADA POR get, post, ENTRE OTRAS, ESTA FUNCIONA AUN NO SE ELIMINARA
     * POR TEMAS DE COMPATIBILIDAD PERO SE MODIFICARA, Y ESTA SEGUN EL METODO DE LLAMADA DE LA RUTA LLAMARA A LA FUNCION
     * get, post, etc SEGUN SEA NECESARIO
     */
    public function add(string $route, string $view, $middlewares = null)
    {
        $app = null;        // Aplicacion donde esta la vista
        $v = null;          // Vista a buscar
        $method = 'run';  // Metodo por defecto a ejecutar

        // Se valida si es una vista a ejecutar o una funcion anonima
        if (is_string($view)) {
            if (!strpos($view, '@')) {
                throw new RouterException("Revise la ruta ($route) al parecer la ruta a la vista no esta correctamente escrita.");
            }

            // Dividir la vista en app, vista y funcion si es que esta definida
            $view = explode('@', $view);
            $app = $view[0];
            $v = $view[1];

            // Valida si la vista trae un metodo a ejecutar
            if (count($view) == 3) {
                $method = $view[2];
            }
        } elseif (is_array($view)) {
            /*
             * Definicion de una vista con matriz.
             *
             * $view = array(
             *     'name' => 'nombre_vista',
             *     'app.vista' o 'view' => 'app.view.function' o function() {}
             * );
             */

            // Si el numero de items de la matriz es menor a dos no es valido
            if (count($view) < 2) {
                throw new RouterException('Definición de vista invalida.');
            }

            if (!array_key_exists('name', $view)) {
                throw new RouterException("No existe el atributo 'name' de la vista en la matriz.");
            }

            /*
             * Almacena temporalmente el nombre de la ruta para que al volver a
             * llamar a add se agregue el nombre a la ruta.
             */
            $this->nameForRoute = $view['name'];

            // Vuelve a llamar a add para agregar la ruta
            return $this->add($route, $view[0], $middlewares);
        } else {
            $method = $view;
        }

        // Se valida si la ruta trae parametros por ruta
        $paramsUrl = array();
        $route = $this->parseRoute($route, $paramsUrl);

        // Matriz con el contenido de la ruta
        $routeContent = array(
            'app' => $app,
            'view' => $v,
            'method' => $method,
            'paramsUrl' => $paramsUrl,
            'name' => $this->nameForRoute,
            'middlewares' => $middlewares
        );

        // Valida si es ruta normal o compleja
        if (count($paramsUrl) == 0) {
            $this->routes[$route] = $routeContent;
        } else {
            $this->complexRoutes[$route] = $routeContent;
        }
    }

    /**
     * Obtiene la ruta actual
     *
     * @return string
     */
    public function getRoute(): string
    {
        $uri = $_SERVER['REQUEST_URI'];

        // Extraigo la URI base si esta es diferente a /
        if ($this->uriBase != '/') {
            $uri = str_replace($this->uriBase, '/', $uri);
        }

        // Busco el caracter ? por si se pasaron parametros por GET
        $pos = strpos($uri, '?');

        if ($pos !== false) {
            $uri = substr($uri, 0, $pos);
        }

        // Agrega un slash a uri para evitar error en la busqueda de ultimo slash
        //$uri = $this->addSlash($uri);

        // Retorno la ruta correcta
        return $uri;
    }

    /**
     * Obtiene el nombre de la ruta actual si es que se le definio uno.
     *
     * @return string
     */
    public function getRouteName(): string
    {
        return $this->nameForRoute;
    }

    private function loadParamsRoute(string &$route, string &$routeContent)
    {
        $noMatch = true; // Indica si hay o no coincidencias de ruta

        if (count($this->complexRoutes) > 0) {
            foreach ($this->complexRoutes as $complexRoute => $_routeContent) {
                $regex = '#^' . $complexRoute . '$#m';

                // Busca los parametreos dentro de la ruta
                preg_match_all($regex, $route, $matches, PREG_SET_ORDER);

                if (count($matches) > 0) {
                    $paramsUrl = array();
                    $i = 0; // Indice del parametro

                    // Se saca el primer elemento de las coincidencia ya que no se usara
                    $matches = $matches[0];
                    array_shift($matches);

                    // Se genera la matriz con los parametros para el request
                    foreach ($_routeContent['paramsUrl'] as $paramKey => $paramValue) {
                        if ($matches[$i] != null && $matches[$i] != 'undefined') {
                            $paramsUrl[$paramKey] = $matches[$i];
                        }

                        $i++;
                    }

                    // Se valida que realmente que vayan parametros en $paramsUrl
                    if (count($paramsUrl) > 0) {
                        Request::register($paramsUrl);
                    }

                    if ($_routeContent['app'] != null) {
                        $routeContent = $_routeContent;
                    } else {
                        $routeContent = $_routeContent['function'];
                    }

                    /*
                     * Devuelvo la ruta compleja para obtener su nombre y
                     * decoradores.
                     */
                    $route = $_routeContent;

                    $noMatch = false;
                    break;
                }
            }

            // Si no hay coincidencias se muestra la vista por defecto del framework
            if ($noMatch) {
                return false;
            }

            return true;
        } else {
            return false;
        }
    }

    private function notView()
    {
        if (Settings::getInstance()->inDebug()) {
            Context::getInstance()->set('exception', 'Framework MVT');
            Context::getInstance()->set('details', 'Hurra ForeverPHP esta corriendo, ahora genera una vista.');

            // Le indico a la vista que haga render usando los templates del framework
            Settings::getInstance()->set('ForeverPHPTemplate', true);

            $response = new Response();
            $response->render('exception')->make();
        } else {
            // Si esta en produccion muestra un error 404
            //(new Redirect)->error(404);
            Redirect::error(404);
        }
    }

    private function runFunction($function)
    {
        if (!is_string($function)) {
            // Ejecuta la funcion anonima
            $returnValue = call_user_func($function);

            /*
             * Valida si el valor de retorno de la funcion, es un objeto que
             * implemente ResponseInterface
             */
            if ($returnValue instanceof \ForeverPHP\Http\ResponseInterface) {
                $returnValue->make();
            }
        } else {
            $this->notView();
        }
    }

    private function setHeadersToResponse()
    {
        if (SessionManager::getInstance()->exists('headersInRedirect', 'redirect')) {
            $redirectPath = SessionManager::getInstance()->get('redirectPath', 'redirect');
            $requestURI = $_SERVER['REQUEST_URI'];

            if ($redirectPath == $requestURI) {
                $headers = SessionManager::getInstance()->get('headersInRedirect', 'redirect');

                if ($headers != false) {
                    foreach ($headers as $key => $value) {
                        header($key . ': ' . $value);
                    }
                }
            } else {
                SessionManager::getInstance()->set('headersInRedirect', false, 'redirect');
            }
        }
    }

    /**
     * Ejecuta la ruta solicitada
     */
    public function run()
    {
        // Obtiene la Url base
        $this->getUriBase();

        // Obtiene la ruta actual
        $route = $this->addSlash($this->getRoute());

        // Valida que tipo de ruta se solicito
        $routeContent = null;
        $appName = null;

        if (array_key_exists($route, $this->routes)) {
            $route = $this->routes[$route];

            if ($route['app'] != null) {
                $appName = $route['app'];
                $routeContent = $route;
            } else {
                $routeContent = $route['function'];
            }

            // Registro los parametros pasados en la solicitud
            Request::register();
        } else {
            // Se remueve el ultimo slash ya que rutas complejas no lo requieren
            //$route = self::remove_slash($route);
            if (!$this->loadParamsRoute($route, $routeContent)) {
                $routeContent = null;
            }
        }

        // Agrega las cabeceras a la respuesta de existir
        $this->setHeadersToResponse();

        /*
         * Almacena el nombre de la ruta actual si es que lo tiene, dependiendo
         * si es una ruta normal o compleja y si es funcion o vista.
         */
        if (is_array($route)) {
            $this->nameForRoute = $route['name'];
        } elseif (is_array($routeContent)) {
            $this->nameForRoute = $routeContent['name'];
        }

        /*
         * NOTA: Los decoradores solo pueden ser utilizados en rutas con vistas
         *       no en rutas con funciones anonimas.
         */

        if (is_array($routeContent)) {
            if ($appName == null) {
                $appName = $routeContent['app'];
            }

            // Primero se verifica que la aplicacion este agregada en la configuracion
            $app = App::getInstance();
            if ($app->exists($appName)) {
                $app->load($appName);

                // Cargo el autoload.php, archivo opcional
                $autoloadPath = APPS_ROOT . DS . 'autoload.php';

                if (Storage::exists($autoloadPath)) {
                    include_once $autoloadPath;
                }

                // Ejecuta los decoradores de la ruta si es que hay
                $middlewares = $routeContent['middlewares'];

                if (!is_null($middlewares)) {
                    foreach ($middlewares as $middleware) {
                        $returnValue = $app->getMiddleware($middleware);

                        // Valida si el decorador retorna un Response
                        if ($returnValue instanceof \ForeverPHP\Http\ResponseInterface) {
                            $app->run($returnValue);
                        }
                    }
                }

                $app->run($routeContent);
            } else {
                throw new AppException("La aplicación ($appName) a la que pertenece la vista no esta " .
                    "cargada en settings.php.");
            }
        } elseif (is_callable($routeContent)) {
            $this->runFunction($routeContent);
        } else {
            $this->notView();
        }
    }
}


<?php
/**
 * User: Alex Gusev <alex@flancer64.com>
 */

namespace Praxigento\BonusHybrid\Service\Calc\Value\Ov;

use Praxigento\BonusHybrid\Config as Cfg;
use Praxigento\BonusHybrid\Repo\Data\Downline as EBonDwnl;

/**
 * Calculate OV on the compressed downline tree.
 *
 * @deprecated use \Praxigento\BonusHybrid\Service\Calc\Bonus\Z\Proc\Ov
 */
class Calc
{
    /** @var \Praxigento\Downline\Api\Helper\Tree */
    private $hlpDwnlTree;
    /** @var \Praxigento\BonusHybrid\Service\Calc\Z\Helper\GetCustomersIds */
    private $hlpSignUpDebitCust;
    /** @var \Praxigento\Core\Api\App\Logger\Main */
    private $logger;
    /** @var \Praxigento\BonusHybrid\Repo\Dao\Downline */
    private $daoBonDwnl;

    public function __construct(
        \Praxigento\Core\Api\App\Logger\Main $logger,
        \Praxigento\BonusHybrid\Service\Calc\Z\Helper\GetCustomersIds $hlpSignUpDebitCust,
        \Praxigento\Downline\Api\Helper\Tree $hlpDwnlTree,
        \Praxigento\BonusHybrid\Repo\Dao\Downline $daoBonDwnl
    )
    {
        $this->logger = $logger;
        $this->hlpSignUpDebitCust = $hlpSignUpDebitCust;
        $this->hlpDwnlTree = $hlpDwnlTree;
        $this->daoBonDwnl = $daoBonDwnl;
    }

    /**
     * Calculate OV for the downline tree.
     *
     * @param int $calcId
     * @return EBonDwnl[] updated tree (with OV)
     */
    public function exec($calcId)
    {
        $result = [];
        /* collect additional data */
        $dwnlCompress = $this->daoBonDwnl->getByCalcId($calcId);
        /* create maps to access data */
        $mapById = $this->hlpDwnlTree->mapById($dwnlCompress, EBonDwnl::A_CUST_REF);
        $mapDepth = $this->hlpDwnlTree->mapByTreeDepthDesc($dwnlCompress, EBonDwnl::A_CUST_REF, EBonDwnl::A_DEPTH);
        $mapTeams = $this->hlpDwnlTree->mapByTeams($dwnlCompress, EBonDwnl::A_CUST_REF, EBonDwnl::A_PARENT_REF);
        $signupDebitCustomers = $this->hlpSignUpDebitCust->exec();
        /**
         * Scan downline by level from bottom to top
         */
        foreach ($mapDepth as $depth => $levelCustomers) {
            $this->logger->debug("Process level #$depth of the downline tree.");
            /* ... then scan customers on each level */
            foreach ($levelCustomers as $custId) {
                /** @var EBonDwnl $entity */
                $entity = $mapById[$custId];
                $ov = $entity->getPv(); // initial OV equals to customer's own PV
                $isSignUpDebit = in_array($custId, $signupDebitCustomers);
                if ($isSignUpDebit) {
                    /* add written-off PV if customer was qualified to Sign Up Debit bonus */
                    $ov += Cfg::SIGNUP_DEBIT_PV;
                }
                if (isset($mapTeams[$custId])) {
                    /* add OV from front team members */
                    $team = $mapTeams[$custId];
                    foreach ($team as $memberId) {
                        /** @var EBonDwnl $member */
                        $member = $result[$memberId];
                        $memberOv = $member->getOv();
                        $ov += $memberOv;
                    }
                }
                $entity->setOv($ov);
                $result[$custId] = $entity;
            }
        }
        unset($mapPv);
        unset($mapTeams);
        unset($mapDepth);

        return $result;
    }

}


<?php

/*
 * This file is part of the P13/DateTime package.
 *
 * (c) Wagner Sicca <wssicca@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace p13\datetime;

use \DateTimeZone;

/**
 * Extensão da classe DateTime que possui métodos
 * relacionados somente à data, ignorando o tempo
 *
 * @author Wagner Sicca <wssicca@gmail.com>
 * @namespace p13\datetime
 * @package p13\datetime
 */
class Date extends DateTime implements ExtensionInterface
{

    /**
     * Retorna a data no formato d/m/Y
     * @return string
     */
    public function __toString()
    {
        return $this->format('br');
    }

    /**
     * Faz o "cast" de \DateTime para p13\datetime\Date
     * @param \DateTime $date
     * @return Date
     */
    public static function cast($date)
    {
        return $date instanceof self ?
                $date :
                new self($date->format(self::ISO8601), $date->getTimezone());
    }

    /**
     * Returns new DateTime object formatted according to the specified format
     * @param string $format
     * @param string $time
     * @param \DateTimeZone $object
     * @return Date
     * @static
     */
    public static function createFromFormat($format, $time, $object = null)
    {
        if (empty($object)) {
            $object = new DateTimeZone('America/Sao_Paulo');
        }
        return self::cast(parent::createFromFormat($format, $time, $object));
    }

    /**
     * Estende  a função DateTime::format() adicionando dois novos formatos:
     * - 'br' imprime a data no formato "d/m/Y"
     * - 'br_extenso' imprime a data no formato "1 de abril de 2015" (ABNT NBR 5892)
     * Demais casos serão passados para a função original
     * @param string $format
     */
    public function format($format)
    {
        $formato_br = 'br';
        $formato_br_extenso = 'br\\_extenso';

        if (
                preg_match("/{$formato_br}/", $format) == 0 ||
                preg_match("/\\\\{$formato_br}/", $format) == 1
        ) {
            return parent::format($format);
        } else if (preg_match("/{$formato_br_extenso}/", $format)) {
            setlocale(LC_ALL, "pt_BR", "pt_BR.iso-8859-1", "pt_BR.utf-8", "portuguese");
            date_default_timezone_set('America/Sao_Paulo');
            return preg_replace(
                    "/{$formato_br_extenso}/", strftime('%d de %B de %Y', $this->getTimestamp()), $format
            );
        } else {
            return parent::format('d/m/Y');
        }
    }

}


<?php
/**
 * This file is part of Formular.
 *
 * (c) Philippe Gerber
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace bigwhoop\Formular\TemplateFactory;
use bigwhoop\Formular\Template\FileTemplate;

/**
 * @author Philippe Gerber <philippe@bigwhoop.ch>
 */
class FileBasedFactory implements TemplateFactoryInterface
{
    /** @var array */
    private $templatesPaths = [];

    /** @var array */
    private $templatesMap = [];

    /** @var null|string */
    private $defaultNamespace = null;

    /**
     * {@inheritdoc}
     */
    public function setDefaultNamespace($namespace)
    {
        $this->defaultNamespace = $namespace;
        return $this;
    }

    /**
     * @param string $path
     * @param string|null $namespace
     * @return $this
     */
    public function addTemplatesPath($path, $namespace = null)
    {
        $this->templatesPaths[$path] = $namespace;
        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function createTemplate($name)
    {
        $templatePath = $this->getTemplatePath($name);
        $template = new FileTemplate($templatePath);
        $template->setTemplateFactory($this);
        return $template;
    }

    /**
     * Returns the path to a given template.
     *
     * The template name can contain a namespace like "template@ns" in case there are templates with the same name.
     *
     * @param string $template
     * @return string
     * @throws \RuntimeException
     */
    private function getTemplatePath($template)
    {
        if (strpos($template, '@') === false && !empty($this->defaultNamespace)) {
            $template .= '@' . $this->defaultNamespace;
        }

        $map = $this->buildTemplatesMap();

        if (!array_key_exists($template, $map)) {
            throw new \RuntimeException("A template by the name '$template' does not exist. The following templates are available: " . join(', ', array_keys($map)));
        }
        return $map[$template];
    }

    /**
     * Builds a map of template names to paths.
     *
     * @return array
     * @throws \OverflowException|\RuntimeException
     */
    private function buildTemplatesMap()
    {
        if (!empty($this->templatesMap)) {
            return $this->templatesMap;
        }

        $this->templatesMap = [];
        foreach ($this->templatesPaths as $templatesPath => $templatesNamespace) {
            if (!is_readable($templatesPath)) {
                throw new \RuntimeException("Templates path '$templatesPath' does not exist or is not readable.");
            }

            foreach (glob($templatesPath . '/*.phtml') as $templatePath) {
                $template = pathinfo($templatePath, PATHINFO_FILENAME);
                if ($templatesNamespace !== null) {
                    $template .= '@' . $templatesNamespace;
                }
                if (array_key_exists($template, $this->templatesMap)) {
                    throw new \OverflowException("Can't import template '$template' from '$templatePath' as a template with the same name already exists at '{$this->templatesMap[$template]}'. You may want to use namespaces.");
                }
                $this->templatesMap[$template] = $templatePath;
            }
        }
        return $this->templatesMap;
    }
}