<?php

namespace Understory;

use Timber;

abstract class CustomPostType implements DelegatesMetaDataBinding, Registerable, Registry, Composition
{
    private $post;
    private $registry = [];
    private $config;
    private $closureProp = [];

    /**
     * If we are supplied a post then we try to instantiate as the correct
     * custom post type, otherwise call the normal constructor
     *
     * @param \Timber\Post|int $post \Timber\Post or post id to initialize at \Timber\Post
     */
    public function __construct($post = null)
    {
        if ($post) {
            $this->setPost($post);
        }
    }

    /**
     * Set and Init Post
     * If nothing is passed, WordPress's current post will try to be determined
     * @param mixed $post Post, Post ID or null
     */
    public function setPost($post = null)
    {
        $this->setMetaDataBinding(new Post($post));
        $this->getConfig();
    }

    public function autobind()
    {
        $this->setPost();
        return $this;
    }

    public function getPostType()
    {
        return $this->getConfig()->getPostTypeName();
    }

    protected function configure(PostTypeBuilder $postTypeBuilder)
    {
        return $postTypeBuilder;
    }

    public function getConfig()
    {
        if (!$this->config) {
            $this->config = $this->generateBuilder();
        }

        return $this->config;
    }

    private function generateBuilder()
    {
        $cptName = $this->generateCustomPostTypeName();
        return $this->configure(new PostTypeBuilder($cptName));
    }

    public function addToRegistry($key, Registerable $registerable)
    {
        $this->registry[$key] = $registerable;
    }

    public function registerItemsInRegistry()
    {
        foreach ($this->registry as $registerable) {
            if ($registerable instanceof CustomTaxonomy) {
                $this->registerTaxonomy($registerable);
                continue;
            }
            $registerable->register();
        }
    }

    public function has($property, $value, $many = false)
    {
        if ($value instanceof Sequential) {
            $value->setSequentialPosition($this->getNextInSequence(), $this);
        }

        if ($value instanceof Registerable) {
            $this->addToRegistry($property, $value);
        }

        if ($value instanceof DelegatesMetaDataBinding) {
            $value->setMetaDataBinding($this);
        }

        if ($value instanceof BelongsToPost) {
            $this->setClosureProperty($property, $value->belongsToPost($this, $many));
        }

        $this->setProperty($property, $value);
    }

    public function hasMany($property, $value)
    {
        $this->has($property, $value, $many = true);
    }

    public function hasOne($property, $value)
    {
        $this->has($property, $value);
    }

    /**
     * @param string $property
     * @param mixed $value
     */
    private function setProperty($property, $value)
    {
        $reflection = new \ReflectionObject($this);
        if (
            !isset($this->closureProp[$property])
            && (
                !$reflection->hasProperty($property)
                || !$reflection->getProperty($property)->isPrivate()
            )
        ){
            $this->$property = $value;
        }
    }

    private function setClosureProperty($property, $value)
    {
        if ($value instanceof \Closure) {
            $this->closureProp[$property] = $value;
        }
    }

    private $sequence = 1;
    private function getNextInSequence()
    {
        return $this->sequence++;
    }

    public function register()
    {
        if (!$this->modifyRegisteredPostType()) {
            register_post_type(
                $this->getPostType(),
                $this->getConfig()->build()
            );
        }

        $this->registerRevisionLimit();
        $this->registerItemsInRegistry();
    }

    private function modifyRegisteredPostType()
    {
        global $wp_post_types;

        if (isset($wp_post_types[$this->getPostType()])) {
            // Convert associative arrays to a stdobject 1 level deep
            $config = json_decode(json_encode($this->getConfig()->build()), false);
            $wp_post_types[$this->getPostType()] = $config;
            return true;
        }

        return false;
    }

    private function registerRevisionLimit()
    {
        add_filter('wp_revisions_to_keep', function($num, $post) {
            if ($post->post_type === $this->getPostType()) {
                return $this->getConfig()->getRevisionLimit();
            }
            return $num;
        }, 10, 2 );
    }

    /**
     * Registers a Taxonomy for this PostType with WordPress
     * @param CustomTaxonomy $taxonomy
     * @return bool
     */
    public function registerTaxonomy(CustomTaxonomy $taxonomy)
    {
        return register_taxonomy_for_object_type(
            $taxonomy->getTaxonomy(),
            $this->getPostType()
        );
    }

    /**
     * Implentation of HasMetaData->getMetaValue
     *
     * @param  string $key Key for the meta field
     * @return string Value of the meta field
     */
    public function getMetaValue($key)
    {
        return $this->getMetaDataBinding()->getMetaValue($key);
    }

    /**
     * Implentation of HasMetaData->setMetaValue
     *
     * @param  string $key Key for the meta field
     * @param  string $value Value for the meta field
     */
    public function setMetaValue($key, $value)
    {
        $this->getMetaDataBinding()->setMetaValue($key, $value);
    }

    public function getBindingName()
    {
        return $this->getPostType();
    }

    /**
     * @return Post [description]
     */
    public function getMetaDataBinding()
    {
        if (!isset($this->post)) {
            $this->autobind();
        }

        return $this->post;
    }

    public function setMetaDataBinding(MetaDataBinding $binding)
    {
        $this->post = $binding;
    }

    public function findAll($args = [])
    {
        $args = array_merge([
            'posts_per_page' => '-1',
            'post_type' => $this->getPostType()
        ], $args);

        return \Timber\PostGetter::get_posts($args, get_called_class());
    }

    public function __isset($property)
    {
        if (isset($this->closureProp[$property])) {
            return true;
        }
        return isset($this->getMetaDataBinding()->$property);
    }

    public function __get($property)
    {
        if ($this->hasClosureProp($property)) {
            return $this->getClosureProp($property);
        }

        if (method_exists($this, 'get'.$property)) {
            return call_user_func_array([$this, 'get'.$property], []);
        }

        return $this->getMetaDataBinding()->$property;
    }

    public function __call($method, $args)
    {
        if ($this->hasClosureProp($method)) {
            return $this->getClosureProp($method);
        }

        return call_user_func_array(
            [
                $this->getMetaDataBinding(),
                $method,
            ],
            $args
        );
    }

    private function hasClosureProp($property)
    {
        return isset($this->closureProp[$property]);
    }

    private function getClosureProp($property)
    {
        if ($this->hasClosureProp($property)) {
            $closure = $this->closureProp[$property];
            return $closure();
        }
    }

    /**
     * Overwrite to change the way the custom post type name is determined.
     * This value is fed into the PostTypeBuilder. If an existing post type of
     * the same name exist, it will load the existing configuration to allow
     * modification.
     *
     * @return string
     */
    protected function generateCustomPostTypeName()
    {
        $matches = [];
        preg_match('@(\\\\)?([\w]+)$@', get_called_class(), $matches);
        $cptName = strtolower(
            preg_replace('/(?<=\\w)(?=[A-Z])/', "-$1", $matches[2])
        );
        return $cptName;
    }
}


<?php

/*
 * This file is part of the TomahawkPHP package.
 *
 * (c) Tom Ellis
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Tomahawk\Templating\Twig\Extension;

use Tomahawk\Url\UrlGenerator;
use Tomahawk\Url\UrlGeneratorInterface;

class UrlExtension extends \Twig_Extension
{
    /**
     * @var UrlGeneratorInterface
     */
    protected $urlGenerator;

    /**
     * Construct
     *
     * @param UrlGeneratorInterface $urlGenerator
     */
    public function __construct(UrlGeneratorInterface $urlGenerator)
    {
        $this->urlGenerator = $urlGenerator;
    }

    /**
     * Returns a list of functions to add to the existing list.
     *
     * @return array An array of functions
     */
    public function getFunctions()
    {
        return array(
            new \Twig_SimpleFunction('base_url', array($this, 'getBaseUrl')),
            new \Twig_SimpleFunction('current_url', array($this, 'getCurrentUrl')),
            new \Twig_SimpleFunction('asset_url', array($this, 'asset')),
            new \Twig_SimpleFunction('url_to', array($this, 'to')),
            new \Twig_SimpleFunction('secure_url_to', array($this, 'secureTo')),
            new \Twig_SimpleFunction('route', array($this, 'route')),
        );
    }

    /**
     * @return string
     */
    public function getBaseUrl()
    {
        return $this->urlGenerator->getBaseUrl();
    }

    /**
     * @return mixed|string
     */
    public function getCurrentUrl()
    {
        return $this->urlGenerator->getCurrentUrl();
    }

    /**
     * URL to an asset
     *
     * @param $path
     * @param array $extra
     * @param bool $secure
     * @return string
     */
    public function asset($path, array $extra = array(), $secure = false)
    {
        return $this->urlGenerator->asset($path, $extra, $secure, true);
    }

    /**
     * @param string $path
     * @param array $extra
     * @param bool $secure
     * @param bool $asset
     * @return mixed|string
     */
    public function to($path = '', array $extra = array(), $secure = false, $asset = false)
    {
        return $this->urlGenerator->to($path, $extra, $secure, $asset);
    }

    /**
     * @param string $url
     * @param array $extra
     * @param bool $secure
     * @param bool $asset
     * @return string
     */
    public function secureTo($url = '', array $extra = array(), $secure = true, $asset = false)
    {
        return $this->urlGenerator->secureTo($url, $extra, $secure, $asset);
    }

    /**
     * @param $name
     * @param array $data
     * @return mixed|string
     */
    public function route($name, $data = array())
    {
        return $this->urlGenerator->route($name, $data, UrlGenerator::ABSOLUTE_URL);
    }

    /**
     * Returns the name of the extension.
     *
     * @return string The extension name
     */
    public function getName()
    {
        return 'url';
    }
}


<?php

/*
 * This file is part of the TomahawkPHP package.
 *
 * (c) Tom Ellis
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Tomahawk\Bundle\MonologBundle\DependencyInjection;

use Monolog\Logger;
use Monolog\Formatter\LineFormatter;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Handler\FingersCrossedHandler;
use Tomahawk\DependencyInjection\ContainerInterface;
use Tomahawk\DependencyInjection\ServiceProviderInterface;
use Tomahawk\Bundle\MonologBundle\Builder\HandlerBuilder;

class MonologServiceProvider implements ServiceProviderInterface
{
    /**
     * Default handlers
     *
     * @var array
     */
    private $defaultHandlers = [
        'fingers_crossed' => 'monolog.handler.fingers_crossed',
        'rotating_file'   => 'monolog.handler.rotating_file',
        'stream'          => 'monolog.handler.stream',
    ];

    /**
     * Registers services on the given container.
     *
     * This method should only be used to configure services and parameters.
     * It should not get services.
     *
     * @param ContainerInterface $container An Container instance
     */
    public function register(ContainerInterface $container)
    {
        $self = $this;

        $container->set('monolog.handler.builder', function(ContainerInterface $c) {

            return new HandlerBuilder($c['config'], $c, $this->defaultHandlers);
        });

        // Register Monolog handlers

        $container->set('monolog.handler.fingers_crossed', function(ContainerInterface $c) use($self) {

            $config = $c['config'];

            $actionLevel = $config->get('logging.action_level', Logger::ERROR);
            $actionLevel = $self->levelToMonologConst($actionLevel);

            $actionHandlerName = $config->get('logging.action_handler');
            $actionHandler = $c['monolog.handler.builder']->build($actionHandlerName);

            $handler = new FingersCrossedHandler(
                $actionHandler,
                $actionLevel
            );

            return $handler;
        });

        $container->set('monolog.handler.rotating_file', function(ContainerInterface $c) use($self) {

            $config = $c['config'];
            $kernel = $c['kernel'];

            $maxFiles = $config->get('logging.max_files', 10);
            $logLevel = $config->get('logging.level', Logger::ERROR);
            $logLevel = $self->levelToMonologConst($logLevel);

            $stream = sprintf('%s/%s.log', $config->get('logging.path'), $kernel->getEnvironment());

            $formatter = new LineFormatter(null, null, true, true);
            $formatter->includeStacktraces();

            $handler = new RotatingFileHandler($stream, $maxFiles, $logLevel);
            $handler->setFormatter($formatter);

            return $handler;
        });

        $container->set('monolog.handler.stream', function(ContainerInterface $c) use($self) {

            $config = $c['config'];
            $kernel = $c['kernel'];

            $logLevel = $config->get('logging.level', Logger::ERROR);
            $logLevel = $self->levelToMonologConst($logLevel);

            $stream = sprintf('%s/%s.log', $config->get('logging.path'), $kernel->getEnvironment());

            $formatter = new LineFormatter(null, null, true, true);
            $formatter->includeStacktraces();

            $handler = new StreamHandler($stream, $logLevel);

            $handler->setFormatter($formatter);

            return $handler;
        });

        $container->set('Monolog\Handler\HandlerInterface', function(ContainerInterface $c) {
            return $c['monolog.handler.builder']->build($c['config']->get('logging.handler'));
        });

        $container->set('Psr\Log\LoggerInterface', function(ContainerInterface $c) {

            // Create a log channel
            $log = new Logger('application');
            $log->pushHandler($c['monolog.handler']);

            return $log;
        });

        $container->addAlias('monolog.handler', 'Monolog\Handler\HandlerInterface');
        $container->addAlias('monolog', 'Psr\Log\LoggerInterface');
        $container->addAlias('logger', 'Psr\Log\LoggerInterface');

    }

    /**
     * Convert level to mongo constant
     *
     * @param $level
     * @return int|mixed
     */
    private function levelToMonologConst($level)
    {
        return is_int($level) ? $level : constant('Monolog\Logger::' . strtoupper($level));
    }
}


<?php

/*
 * This file is part of the Doctrine Bundle
 *
 * The code was originally distributed inside the Symfony framework.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 * (c) Doctrine Project, Benjamin Eberlei <kontakt@beberlei.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Tomahawk\Bundle\DoctrineBundle\Command;

use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Doctrine\ORM\Tools\EntityRepositoryGenerator;
use Tomahawk\Bundle\DoctrineBundle\Mapping\DisconnectedMetadataFactory;

/**
 * Generate entity classes from mapping information
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Jonathan H. Wage <jonwage@gmail.com>
 */
class GenerateEntitiesDoctrineCommand extends DoctrineCommand
{
    /**
     * {@inheritDoc}
     */
    protected function configure()
    {
        $this
            ->setName('doctrine:generate:entities')
            ->setAliases(array('generate:doctrine:entities'))
            ->setDescription('Generates entity classes and method stubs from your mapping information')
            ->addArgument('name', InputArgument::REQUIRED, 'A bundle name, a namespace, or a class name')
            ->addOption('path', null, InputOption::VALUE_REQUIRED, 'The path where to generate entities when it cannot be guessed')
            ->addOption('no-backup', null, InputOption::VALUE_NONE, 'Do not backup existing entities files.')
            ->setHelp(<<<EOT
The <info>doctrine:generate:entities</info> command generates entity classes
and method stubs from your mapping information:

You have to limit generation of entities:

* To a bundle:

  <info>php app/hatchet doctrine:generate:entities MyCustomBundle</info>

* To a single entity:

  <info>php app/hatchet doctrine:generate:entities MyCustomBundle:User</info>
  <info>php app/hatchet doctrine:generate:entities MyCustomBundle/Entity/User</info>

* To a namespace

  <info>php app/hatchet doctrine:generate:entities MyCustomBundle/Entity</info>

If the entities are not stored in a bundle, and if the classes do not exist,
the command has no way to guess where they should be generated. In this case,
you must provide the <comment>--path</comment> option:

  <info>php app/hatchet doctrine:generate:entities Blog/Entity --path=src/</info>

By default, the unmodified version of each entity is backed up and saved
(e.g. Product.php~). To prevent this task from creating the backup file,
pass the <comment>--no-backup</comment> option:

  <info>php app/hatchet doctrine:generate:entities Blog/Entity --no-backup</info>

<error>Important:</error> Even if you specified Inheritance options in your
XML or YAML Mapping files the generator cannot generate the base and
child classes for you correctly, because it doesn't know which
class is supposed to extend which. You have to adjust the entity
code manually for inheritance to work!

EOT
        );
    }

    /**
     * {@inheritDoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $manager = new DisconnectedMetadataFactory($this->getContainer()->get('doctrine'));

        try {
            $bundle = $this->getApplication()->getKernel()->getBundle($input->getArgument('name'));

            $output->writeln(sprintf('Generating entities for bundle "<info>%s</info>"', $bundle->getName()));
            $metadata = $manager->getBundleMetadata($bundle);
        } catch (\InvalidArgumentException $e) {
            $name = strtr($input->getArgument('name'), '/', '\\');

            if (false !== $pos = strpos($name, ':')) {
                $name = $this->getContainer()->get('doctrine')->getEntityNamespace(substr($name, 0, $pos)).'\\'.substr($name, $pos + 1);
            }

            if (class_exists($name)) {
                $output->writeln(sprintf('Generating entity "<info>%s</info>"', $name));
                $metadata = $manager->getClassMetadata($name, $input->getOption('path'));
            } else {
                $output->writeln(sprintf('Generating entities for namespace "<info>%s</info>"', $name));
                $metadata = $manager->getNamespaceMetadata($name, $input->getOption('path'));
            }
        }

        $generator = $this->getEntityGenerator();

        $backupExisting = !$input->getOption('no-backup');
        $generator->setBackupExisting($backupExisting);

        $repoGenerator = new EntityRepositoryGenerator();
        foreach ($metadata->getMetadata() as $m) {
            if ($backupExisting) {
                $basename = substr($m->name, strrpos($m->name, '\\') + 1);
                $output->writeln(sprintf('  > backing up <comment>%s.php</comment> to <comment>%s.php~</comment>', $basename, $basename));
            }
            // Getting the metadata for the entity class once more to get the correct path if the namespace has multiple occurrences
            try {
                $entityMetadata = $manager->getClassMetadata($m->getName(), $input->getOption('path'));
            } catch (\RuntimeException $e) {
                // fall back to the bundle metadata when no entity class could be found
                $entityMetadata = $metadata;
            }

            $output->writeln(sprintf('  > generating <comment>%s</comment>', $m->name));
            $generator->generate(array($m), $entityMetadata->getPath());

            if ($m->customRepositoryClassName && false !== strpos($m->customRepositoryClassName, $metadata->getNamespace())) {
                $repoGenerator->writeEntityRepositoryClass($m->customRepositoryClassName, $metadata->getPath());
            }
        }
    }
}


<?php

/*
 * This file is part of the TomahawkPHP package.
 *
 * (c) Tom Ellis
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Tomahawk\CommandBus;

use Tomahawk\DependencyInjection\ContainerInterface;

class CommandHandlerResolver implements CommandHandlerResolverInterface
{
    protected $container;

    /**
     * @param ContainerInterface $container
     */
    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }

    /**
     * Resolve handler for command
     *
     * @param CommandInterface $command
     * @return CommandHandlerInterface
     */
    public function resolve(CommandInterface $command)
    {
        $handlerClass = $this->getHandlerClass($command);

        try {
            $handler = $this->container->get($handlerClass);
            return $handler;
        }
        catch(\Exception $e) {
            return null;
        }
    }

    /**
     * Get command handler class name
     *
     * @param CommandInterface $command
     * @return string
     */
    protected function getHandlerClass(CommandInterface $command)
    {
        $class = get_class($command);

        return $class .'Handler';
    }
}


<?php
namespace CFDIReader\PostValidations\Validators;

use CFDIReader\CFDIReader;
use CFDIReader\PostValidations\Issues;
use CFDIReader\PostValidations\IssuesTypes;
use CFDIReader\PostValidations\Messages;
use CFDIReader\PostValidations\ValidatorInterface;
use SimpleXMLElement;

/**
 * Decorator class to write Validators using some protected helper methods
 * If you want to use this class as template please consider add your
 * validator to this project.
 *
 * This class is not supposed to use outside
 *
 * @access private
 */
abstract class AbstractValidator implements ValidatorInterface
{
    /**
     * @var Messages
     */
    protected $errors;

    /**
     * @var Messages
     */
    protected $warnings;

    /**
     * @deprecated since version 2.4.0, use $cfdi->node and $cfdi->attribute instead
     *
     * @var SimpleXMLElement
     */
    protected $comprobante;

    /**
     * Configure this helper class
     * @param CFDIReader $cfdi
     * @param Issues $issues
     */
    protected function setup(CFDIReader $cfdi, Issues $issues)
    {
        $this->errors = $issues->messages(IssuesTypes::ERROR);
        $this->warnings = $issues->messages(IssuesTypes::WARNING);
        $this->comprobante = $cfdi->comprobante();
    }

    /**
     * Get a numeric value from a decimal
     * @param string $input
     * @return float
     */
    protected function value($input)
    {
        return floatval($input);
    }

    /**
     * Compare two numbers using a delta abs(n - m) <= d
     * @param float $first
     * @param float $second
     * @param float|null $delta
     * @return bool
     */
    protected function compare($first, $second, $delta = null)
    {
        if (null === $delta) {
            $delta = $this->compareDelta();
        }
        return (abs($first - $second) <= $delta);
    }

    /**
     * @return float
     */
    protected function compareDelta()
    {
        return 0.001;
    }

    /**
     * Compute the sum of a collection of nodes considering an attribute
     * @param SimpleXMLElement $collection
     * @param string $attribute
     * @return float
     */
    protected function sumNodes(SimpleXMLElement $collection = null, $attribute = '')
    {
        if (null === $collection) {
            return 0;
        }
        $sum = 0;
        if ('' === $attribute) {
            foreach ($collection as $node) {
                $sum = $sum + $this->value($node);
            }
        } else {
            foreach ($collection as $node) {
                $sum = $sum + $this->value($node[$attribute]);
            }
        }
        return $sum;
    }
}


<?php

/*
 * This file is part of the Doctrine Bundle
 *
 * The code was originally distributed inside the Symfony framework.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 * (c) Doctrine Project, Benjamin Eberlei <kontakt@beberlei.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Tomahawk\Bundle\DoctrineBundle\Command;

use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Doctrine\DBAL\DriverManager;

/**
 * Database tool allows you to easily drop and create your configured databases.
 *
 * Based on the CreateDatabaseDoctrineCommand from the Symfony DoctrineBundle
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Jonathan H. Wage <jonwage@gmail.com>
 */
class CreateDatabaseDoctrineCommand extends DoctrineCommand
{
    /**
     * {@inheritDoc}
     */
    protected function configure()
    {
        $this
            ->setName('doctrine:database:create')
            ->setDescription('Creates the configured databases')
            ->addOption('shard', null, InputOption::VALUE_REQUIRED, 'The shard connection to use for this command')
            ->addOption('connection', null, InputOption::VALUE_OPTIONAL, 'The connection to use for this command')
            ->addOption('if-not-exists', null, InputOption::VALUE_NONE, 'Don\'t trigger an error, when the database already exists')
            ->setHelp(<<<EOT
The <info>doctrine:database:create</info> command creates the default
connections database:

<info>php app/hatchet doctrine:database:create</info>

You can also optionally specify the name of a connection to create the
database for:

<info>php app/hatchet doctrine:database:create --connection=default</info>
EOT
        );
    }

    /**
     * {@inheritDoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $connectionName = $input->getOption('connection');
        if (empty($connectionName) === true) {
            $connectionName = $this->container->get('doctrine')->getDefaultConnectionName();
        }
        $connection = $this->getDoctrineConnection($connectionName);
        $ifNotExists = $input->getOption('if-not-exists');
        $params = $connection->getParams();
        if (isset($params['master'])) {
            $params = $params['master'];
        }
        // Cannot inject `shard` option in parent::getDoctrineConnection
        // cause it will try to connect to a non-existing database
        if (isset($params['shards'])) {
            $shards = $params['shards'];
            // Default select global
            $params = array_merge($params, $params['global']);
            unset($params['global']['dbname']);
            if ($input->getOption('shard')) {
                foreach ($shards as $i => $shard) {
                    if ($shard['id'] === (int)$input->getOption('shard')) {
                        // Select sharded database
                        $params = array_merge($params, $shard);
                        unset($params['shards'][$i]['dbname'], $params['id']);
                        break;
                    }
                }
            }
        }
        $hasPath = isset($params['path']);
        $name = $hasPath ? $params['path'] : (isset($params['dbname']) ? $params['dbname'] : false);
        if (!$name) {
            throw new \InvalidArgumentException("Connection does not contain a 'path' or 'dbname' parameter and cannot be dropped.");
        }
        // Need to get rid of _every_ occurrence of dbname from connection configuration and we have already extracted all relevant info from url
        unset($params['dbname'], $params['path'], $params['url']);
        $tmpConnection = DriverManager::getConnection($params);
        $shouldNotCreateDatabase = $ifNotExists && in_array($name, $tmpConnection->getSchemaManager()->listDatabases());
        // Only quote if we don't have a path
        if (!$hasPath) {
            $name = $tmpConnection->getDatabasePlatform()->quoteSingleIdentifier($name);
        }
        $error = false;
        try {
            if ($shouldNotCreateDatabase) {
                $output->writeln(sprintf('<info>Database <comment>%s</comment> for connection named <comment>%s</comment> already exists. Skipped.</info>', $name, $connectionName));
            } else {
                $tmpConnection->getSchemaManager()->createDatabase($name);
                $output->writeln(sprintf('<info>Created database <comment>%s</comment> for connection named <comment>%s</comment></info>', $name, $connectionName));
            }
        } catch (\Exception $e) {
            $output->writeln(sprintf('<error>Could not create database <comment>%s</comment> for connection named <comment>%s</comment></error>', $name, $connectionName));
            $output->writeln(sprintf('<error>%s</error>', $e->getMessage()));
            $error = true;
        }
        $tmpConnection->close();
        return $error ? 1 : 0;
    }
}


<?php

/*
 * This file is part of the TomahawkPHP package.
 *
 * (c) Tom Ellis
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Tomahawk\Bundle\FrameworkBundle;

use Tomahawk\Bundle\FrameworkBundle\EventListener\SessionListener;
use Tomahawk\HttpKernel\Bundle\Bundle;
use Tomahawk\Config\ConfigInterface;
use Tomahawk\Bundle\FrameworkBundle\EventListener\CookieListener;
use Tomahawk\Bundle\FrameworkBundle\EventListener\StringResponseListener;
use Tomahawk\Bundle\FrameworkBundle\DependencyInjection\AuthenticationServiceProvider;
use Tomahawk\Bundle\FrameworkBundle\DependencyInjection\CommandBusServiceProvider;
use Tomahawk\Bundle\FrameworkBundle\DependencyInjection\ConfigServiceProvider;
use Tomahawk\Bundle\FrameworkBundle\DependencyInjection\RoutingServiceProvider;
use Tomahawk\Bundle\FrameworkBundle\DependencyInjection\SessionServiceProvider;
use Tomahawk\Bundle\FrameworkBundle\DependencyInjection\TranslatorServiceProvider;
use Tomahawk\Bundle\FrameworkBundle\DependencyInjection\CacheServiceProvider;
use Tomahawk\Bundle\FrameworkBundle\DependencyInjection\TemplatingServiceProvider;
use Tomahawk\Bundle\FrameworkBundle\DependencyInjection\FrameworkServiceProvider;
use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpKernel\EventListener\SaveSessionListener;

class FrameworkBundle extends Bundle
{
    public function boot()
    {
        ErrorHandler::register(null, false)->throwAt(0, true);

        $this->container->register(new FrameworkServiceProvider());

        $this->container->register(new AuthenticationServiceProvider());

        $this->container->register(new CacheServiceProvider());

        $this->container->register(new CommandBusServiceProvider());

        $this->container->register(new ConfigServiceProvider());

        $this->container->register(new TemplatingServiceProvider());

        $this->container->register(new TranslatorServiceProvider());

        $this->container->register(new RoutingServiceProvider());

        $this->container->register(new SessionServiceProvider());

        if ($trustedProxies = $this->getConfig()->get('kernel.trusted_proxies')) {
            Request::setTrustedProxies($trustedProxies);
        }

        if ($this->getConfig()->get('kernel.http_method_override')) {
            Request::enableHttpMethodParameterOverride();
        }

        if ($trustedHosts = $this->getConfig()->get('kernel.trusted_hosts')) {
            Request::setTrustedHosts($trustedHosts);
        }
    }

    /**
     * Register any events for the bundle
     *
     * This is called after all bundles have been boot so you get access
     * to all the services
     *
     * @param EventDispatcherInterface $dispatcher
     */
    public function registerEvents(EventDispatcherInterface $dispatcher)
    {
        $dispatcher->addSubscriber($this->container->get('route_listener'));

        $dispatcher->addSubscriber($this->container->get('locale_listener'));

        $dispatcher->addSubscriber(new CookieListener($this->container));

        $dispatcher->addSubscriber(new StringResponseListener());

        $dispatcher->addSubscriber(new SessionListener($this->container));

        $dispatcher->addSubscriber(new SaveSessionListener());
    }

    /**
     * @return ConfigInterface
     */
    public function getConfig()
    {
        return $this->container->get('config');
    }

}


<?php

/*
 * This file is part of the TomahawkPHP package.
 *
 * (c) Tom Ellis
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Tomahawk\Session\Bag;

class OldInputBag implements OldInputBagInterface
{
    private $name = '__old';

    /**
     * Old input
     *
     * @var array
     */
    private $oldInput = array();

    /**
     * The storage key for flashes in the session.
     *
     * @var string
     */
    private $storageKey;

    /**
     * Constructor.
     *
     * @param string $storageKey The key used to store flashes in the session.
     */
    public function __construct($storageKey = '_th_old_input')
    {
        $this->storageKey = $storageKey;
    }

    /**
     * Registers an input value
     *
     * @param $name
     * @param $value
     */
    public function set($name, $value)
    {
        $this->oldInput[$name] = $value;
    }

    /**
     * Get input value for a given field
     *
     * @param $name
     * @param null $default
     * @return array
     */
    public function peek($name, $default = null)
    {
        return $this->has($name) ? $this->oldInput[$name] : $default;
    }

    /**
     * Gets all input values
     *
     * @return array
     */
    public function peekAll()
    {
        return $this->oldInput;
    }

    /**
     * Gets and clears input from the stack.
     *
     * @param string $name
     * @param null $default Default value if $name does not exist.
     *
     * @return array
     */
    public function get($name, $default = null)
    {
        if ( ! $this->has($name)) {
            return $default;
        }

        $return = $this->oldInput[$name];

        unset($this->oldInput[$name]);

        return $return;
    }

    /**
     * Gets and clears input from the stack.
     *
     * @return array
     */
    public function all()
    {
        $return = $this->peekAll();
        $this->oldInput = array();

        return $return;
    }

    /**
     * Sets all input values.
     *
     * @param array $input
     */
    public function setAll(array $input)
    {
        $this->oldInput = $input;
    }

    /**
     * Has value for a given input name?
     *
     * @param string $name
     *
     * @return bool
     */
    public function has($name)
    {
        return array_key_exists($name, $this->oldInput) && $this->oldInput[$name];
    }

    /**
     * Returns a list of all defined types.
     *
     * @return array
     */
    public function keys()
    {
        return array_keys($this->oldInput);
    }

    /**
     * Gets this bag's name.
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Initializes the Bag.
     *
     * @param array $oldInput
     */
    public function initialize(array &$oldInput)
    {
        $this->oldInput = &$oldInput;
    }

    /**
     * Gets the storage key for this bag.
     *
     * @return string
     */
    public function getStorageKey()
    {
        return $this->storageKey;
    }

    /**
     * Clears out data from bag.
     *
     * @return mixed Whatever data was contained.
     */
    public function clear()
    {
        return $this->all();
    }
}


<?php

/*
 * This file is part of the TomahawkPHP package.
 *
 * (c) Tom Ellis
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Tomahawk\Html;

/**
 * Class HtmlBuilder
 *
 * @package Tomahawk\Html
 */
class HtmlBuilder implements HtmlBuilderInterface
{

    /**
     * @param $value
     * @return string
     */
    public function entities($value)
    {
        return htmlentities($value, ENT_QUOTES, 'UTF-8', false);
    }

    /**
     * @param $url
     * @param array $attributes
     * @return string
     */
    public function script($url, array $attributes = [])
    {
        return sprintf('', $url, $this->attributes($attributes));
    }

    /**
     * @param $url
     * @param array $attributes
     * @return string
     */
    public function style($url, array $attributes = [])
    {
        return sprintf('<link rel="stylesheet" type="text/css" href="%s"%s>', $url, $this->attributes($attributes));
    }

    /**
     * @param $url
     * @param $text
     * @param array $attributes
     * @return string
     */
    public function link($url, $text, array $attributes = [])
    {
        return sprintf('%s', $url, $this->attributes($attributes), $text);
    }

    /**
     * @param array $attributes
     * @return string
     */
    public function attributes(array $attributes)
    {
        $html = [];

        // For numeric keys we will assume that the key and the value are the same
        // as this will convert HTML attributes such as "required" to a correct
        // form like required="required" instead of using incorrect numerics.
        foreach ($attributes as $key => $value) {
            $element = $this->attributeElement($key, $value);

            if ( ! is_null($element)) {
                $html[] = $element;
            }
        }

        return count($html) > 0 ? ' '.implode(' ', $html) : '';
    }

    /**
     * Build a single attribute element.
     *
     * @param  string  $key
     * @param  string  $value
     * @return string
     */
    public function attributeElement($key, $value)
    {
        if (is_numeric($key)) {
            $key = $value;
        }

        if ( ! is_null($value)) {
            return $key.'="'.$this->entities($value).'"';
        }
    }
}


<?php

namespace Understory;

use Timber;

abstract class CustomTaxonomy implements DelegatesMetaDataBinding, Registerable, Registry, Composition, Sequential, BelongsToPost
{
    /**
     * @var string
     */
    private $taxonomy;

    private $registry = [];
    private $config;

    public function __construct($tid = null)
    {
        if ($tid) {
            $this->setTerm($tid);
        }
    }

    public function setTerm($tid = null)
    {
        $this->setMetaDataBinding(new Term($tid, $this->getTaxonomy()));
        $this->getConfig();
    }

    public function autobind()
    {
        $this->setTerm();
        return $this;
    }

    public function getTaxonomy()
    {
        return $this->getConfig()->getTaxonomyName();
    }

    protected function configure(TaxonomyBuilder $taxonomyBuilder)
    {
        return $taxonomyBuilder;
    }

    public function getConfig()
    {
        if (!$this->config) {
            $this->config = $this->generateBuilder();
        }

        return $this->config;
    }

    private function generateBuilder()
    {
        $taxonomyName = $this->generateTaxonomyName();

        return $this->configure(new TaxonomyBuilder($taxonomyName));
    }

    public function addToRegistry($key, Registerable $registerable)
    {
        $this->registry[$key] = $registerable;
    }

    public function registerItemsInRegistry()
    {
        foreach ($this->registry as $registerable) {
            $registerable->register();
        }
    }

    public function has($property, $value)
    {
        if ($value instanceof Registerable) {
            $this->addToRegistry($property, $value);
        }

        if ($value instanceof DelegatesMetaDataBinding) {
            $value->setMetaDataBinding($this);
        }

        $this->setProperty($property, $value);
    }

    /**
     * @param string $property
     * @param mixed $value
     */
    private function setProperty($property, $value)
    {
        $reflection = new \ReflectionObject($this);
        if (
            !$reflection->hasProperty($property)
            || !$reflection->getProperty($property)->isPrivate()
        ) {
            $this->$property = $value;
        }
    }

    /**
     * Sets the Taxonomy Meta Box Order.
     * @param $position
     * @param MetaDataBinding $context
     */
    public function setSequentialPosition($position, MetaDataBinding $context)
    {
        add_action('do_meta_boxes', function ($post_type) use ($position, $context) {
            global $wp_meta_boxes;

            if ($context->getBindingName() !== $post_type) {
                return;
            }

            foreach ($wp_meta_boxes[$post_type]['side']['core'] as $key => $metaBox) {
                // Don't replace our recently positioned taxonomy
                if (preg_match('/^understory-taxonomy.+/', $key)) {
                    continue;
                }

                if (
                    is_array($metaBox)
                    && isset($metaBox['args'])
                    && is_array($metaBox['args'])
                    && isset($metaBox['args']['taxonomy'])
                    && $metaBox['args']['taxonomy'] === $this->getBindingName()
                ) {
                    $wp_meta_boxes[$post_type]['side']['core']['understory-taxonomy' . $position] = $metaBox;
                    unset($wp_meta_boxes[$post_type]['side']['core'][$key]);

                }

            }
        }, 10000, 1);
    }

    public function register()
    {
        register_taxonomy(
            $this->getConfig()->getTaxonomyName(),
            $this->getConfig()->getConfig('object_type'),
            $this->getConfig()->build()
        );

        $this->registerItemsInRegistry();
    }

    /**
     * Implantation of HasMetaData->getMetaValue
     *
     * @param  string $key Key for the meta field
     * @return string                Value of the meta field
     */
    public function getMetaValue($key)
    {
        return $this->getMetaDataBinding()->getMetaValue($key);
    }

    /**
     * Implantation of HasMetaData->setMetaValue
     *
     * @param  string $key Key for the meta field
     * @param  string $value Value for the meta field
     */
    public function setMetaValue($key, $value)
    {
        $this->getMetaDataBinding()->setMetaValue($key, $value);
    }

    public function getBindingName()
    {
        return $this->getTaxonomy();
    }

    /**
     * @return Term
     */
    public function getMetaDataBinding()
    {
        if (!isset($this->taxonomy)) {
            $this->setMetaDataBinding($this->generateTaxonomy());
        }

        return $this->taxonomy;
    }

    public function setMetaDataBinding(MetaDataBinding $binding)
    {
        if (!$binding instanceof CustomPostType) {
            $this->taxonomy = $binding;
        }
    }

    public function belongsToPost(CustomPostType $post, $many = false)
    {
        return function () use (&$post, $many) {
            $terms = $post->terms($this->getTaxonomy(), true, get_class($this));

            if ($many) {
                return $terms;
            }

            if (count($terms) > 0) {
                return $terms[0];
            }

            return null;
        };
    }

    public function getChildren()
    {
        return array_map(function ($child) {
            return new static($child);
        }, $this->getMetaDataBinding()->children());
    }

    public function findAll($args = [])
    {
        $args = array_merge([
            'posts_per_page' => '-1',
            'taxonomy' => $this->getTaxonomy(),
        ], $args);

        return Timber\TermGetter::get_terms($args, get_called_class());
    }

    public function findAllTopLevel($args = [])
    {
        $args = array_merge([
            'parent' => '0',
        ], $args);

        return $this->findAll($args);
    }

    public function __isset($property)
    {
        return isset($this->getMetaDataBinding()->$property);
    }

    public function __get($property)
    {
        if (method_exists($this, 'get' . $property)) {
            return call_user_func_array([$this, 'get' . $property], []);
        }

        return $this->getMetaDataBinding()->$property;
    }

    public function __call($method, $args)
    {
        return call_user_func_array(
            [
                $this->getMetaDataBinding(),
                $method,
            ],
            $args
        );
    }

    public static function from($tid, $taxonomy)
    {
        return new static($tid);
    }

    public function __toString()
    {
        return $this->getTaxonomy();
    }

    /**
     * Overwrite to change the way the taxonomy name is determined.
     * This value is fed into the TaxonomyBuilder. If an existing taxonomy of
     * the same name exist, it will load the existing configuration to allow
     * modification.
     *
     * @return string
     */
    protected function generateTaxonomyName()
    {
        $matches = [];
        preg_match('@\\\\([\w]+)$@', get_called_class(), $matches);
        $taxonomyName = strtolower(
            preg_replace('/(?<=\\w)(?=[A-Z])/', "-$1", $matches[1])
        );

        // Use the existing WordPress taxonomy post_tag with an underscore
        if ($taxonomyName === 'post-tag') {
            $taxonomyName = 'post_tag';
        }

        return $taxonomyName;
    }
}


<?php

namespace Tomahawk\Routing\Matcher;

use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Matcher\RedirectableUrlMatcherInterface;
use Symfony\Component\Routing\Route;

/**
 * Class RedirectableUrlMatcher
 *
 * Tries to match routes with and without an ending slash
 *
 * Based (heavily) on the RedirectableUrlMatcher from the Symfony FrameworkBundle
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @package Tomahawk\Routing\Matcher
 */
class RedirectableUrlMatcher extends UrlMatcher implements RedirectableUrlMatcherInterface
{
    /**
     * {@inheritdoc}
     */
    public function match($pathinfo)
    {
        try {
            $parameters = parent::match($pathinfo);
        } catch (ResourceNotFoundException $e) {
            if ('/' !== substr($pathinfo, -1) || !in_array($this->context->getMethod(), array('HEAD', 'GET'))) {
                throw $e;
            }

            // If the path ends with a slash see if a route exists without it
            try {
                parent::match(rtrim($pathinfo, '/'));

                return $this->redirect(rtrim($pathinfo, '/'), null);
            } catch (ResourceNotFoundException $e2) {
                throw $e;
            }
        }

        return $parameters;
    }

    /**
     * Redirects the user to another URL.
     *
     * @param string $path The path info to redirect to
     * @param string $route The route name that matched
     * @param string|null $scheme The URL scheme (null to keep the current one)
     *
     * @return array An array of parameters
     */
    public function redirect($path, $route, $scheme = null)
    {
        return [
            '_controller' => 'Tomahawk\\Routing\\Controller\\RedirectController::urlRedirectAction',
            'path' => $path,
            'permanent' => true,
            'scheme' => $scheme,
            'httpPort' => $this->context->getHttpPort(),
            'httpsPort' => $this->context->getHttpsPort(),
            '_route' => $route,
        ];
    }

    /**
     * {@inheritdoc}
     */
    protected function handleRouteRequirements($pathinfo, $name, Route $route)
    {
        //@codeCoverageIgnoreStart

        // Ignoring as this is already tested by Symfony
        // expression condition
        if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), array('context' => $this->context, 'request' => $this->request))) {
            return array(self::REQUIREMENT_MISMATCH, null);
        }
        //@codeCoverageIgnoreEnd

        // check HTTP scheme requirement
        $scheme = $this->context->getScheme();
        $schemes = $route->getSchemes();
        if ($schemes && !$route->hasScheme($scheme)) {
            return array(self::ROUTE_MATCH, $this->redirect($pathinfo, $name, current($schemes)));
        }

        return array(self::REQUIREMENT_MATCH, null);
    }
}


<?php

/*
 * This file is part of the TomahawkPHP package.
 *
 * (c) Tom Ellis
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Tomahawk\Bundle\CSRFBundle\EventListener;

use Tomahawk\Bundle\CSRFBundle\Exception\InvalidTokenException;
use Tomahawk\Bundle\CSRFBundle\Exception\TokenNotFoundException;
use Tomahawk\Bundle\CSRFBundle\Token\TokenManagerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;

class TokenListener implements EventSubscriberInterface
{
    /**
     * @var TokenManagerInterface
     */
    protected $tokenManager;

    public function __construct(TokenManagerInterface $tokenManager)
    {
        $this->tokenManager = $tokenManager;
    }

    /**
     * KernelEvents::REQUEST event listener
     *
     * @param GetResponseEvent $event
     * @throws InvalidTokenException
     * @throws TokenNotFoundException
     */
    public function onRequest(GetResponseEvent $event)
    {
        $request = $event->getRequest();

        // Does this request require CSRF protection
        if (true !== $request->get('filter_csrf')) {
            return;
        }

        $postedToken = $request->get($this->tokenManager->getTokenName());
        $actualToken = $this->tokenManager->getToken();

        // Check if token is set
        if ('POST' === $request->getMethod() && !$postedToken) {
            throw new TokenNotFoundException();
        }
        // Check if token is valid
        else if ('POST' === $request->getMethod()
            && $postedToken !== $actualToken) {
            throw new InvalidTokenException();
        }
    }

    public static function getSubscribedEvents()
    {
        return array(
            KernelEvents::REQUEST => 'onRequest',
        );
    }
}


<?php

/*
 * This file is part of the TomahawkPHP package.
 *
 * (c) Tom Ellis
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Tomahawk\Console;

use Tomahawk\HttpKernel\Kernel;
use Tomahawk\HttpKernel\KernelInterface;
use Tomahawk\HttpKernel\Bundle\Bundle;
use Tomahawk\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\Console\Application as BaseApplication;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Application.
 *
 * Based on the Symfony FrameworkBundle Console Application
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Application extends BaseApplication
{
    /**
     * @var KernelInterface
     */
    private $kernel;

    /**
     * @var bool
     */
    private $commandsRegistered = false;

    /**
     * Constructor.
     *
     * @param KernelInterface $kernel A KernelInterface instance
     */
    public function __construct(KernelInterface $kernel)
    {
        $this->kernel = $kernel;

        parent::__construct('Tomahawk', Kernel::VERSION.' - '.$kernel->getName().'/'.$kernel->getEnvironment().($kernel->isDebug() ? '/debug' : ''));

        $this->getDefinition()->addOption(new InputOption('--env', '-e', InputOption::VALUE_REQUIRED, 'The Environment name.', $kernel->getEnvironment()));
        $this->getDefinition()->addOption(new InputOption('--no-debug', null, InputOption::VALUE_NONE, 'Switches off debug mode.'));
    }

    /**
     * @return KernelInterface
     */
    public function getKernel()
    {
        return $this->kernel;
    }

    /**
     * Runs the current application.
     *
     * @param InputInterface  $input  An Input instance
     * @param OutputInterface $output An Output instance
     *
     * @return int     0 if everything went fine, or an error code
     */
    public function doRun(InputInterface $input, OutputInterface $output)
    {
        $this->kernel->boot();

        $container = $this->kernel->getContainer();

        foreach ($this->all() as $command) {
            if ($command instanceof ContainerAwareInterface) {
                $command->setContainer($container);
            }
        }

        $this->setDispatcher($container->get('event_dispatcher'));

        return parent::doRun($input, $output);
    }

    /**
     * {@inheritdoc}
     */
    public function get($name)
    {
        $this->registerCommands();

        return parent::get($name);
    }

    /**
     * {@inheritdoc}
     */
    public function all($namespace = null)
    {
        $this->registerCommands();

        return parent::all($namespace);
    }

    /**
     * Register commands from bundles
     */
    protected function registerCommands()
    {
        if ($this->commandsRegistered) {
            return;
        }

        $this->commandsRegistered = true;

        $this->kernel->boot();

        foreach ($this->kernel->getBundles() as $bundle) {
            if ($bundle instanceof Bundle) {
                $bundle->registerCommands($this);
            }
        }
    }
}


<?php

/*
 * This file is part of the TomahawkPHP package.
 *
 * (c) Tom Ellis
 *
 * The is based on code originally distributed inside the Symfony framework.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 * (c) Doctrine Project, Benjamin Eberlei <kontakt@beberlei.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Tomahawk\Bundle\DoctrineBundle\Mapping;

use Doctrine\Common\Persistence\ManagerRegistry;
use Doctrine\ORM\Mapping\MappingException;
use Doctrine\ORM\Tools\DisconnectedClassMetadataFactory;
use Tomahawk\HttpKernel\Bundle\BundleInterface;

/**
 * Based of the DisconnectedMetadataFactory from the DoctrineBundle
 *
 * This class provides methods to access Doctrine entity class metadata for a
 * given bundle, namespace or entity class, for generation purposes
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class DisconnectedMetadataFactory
{
    private $registry;

    /**
     * Constructor.
     *
     * @param ManagerRegistry $registry A ManagerRegistry instance
     */
    public function __construct(ManagerRegistry $registry)
    {
        $this->registry = $registry;
    }

    /**
     * Gets the metadata of all classes of a bundle.
     *
     * @param BundleInterface $bundle A BundleInterface instance
     *
     * @return ClassMetadataCollection A ClassMetadataCollection instance
     *
     * @throws \RuntimeException When bundle does not contain mapped entities
     */
    public function getBundleMetadata(BundleInterface $bundle)
    {
        $namespace = $bundle->getNamespace();
        $metadata = $this->getMetadataForNamespace($namespace);
        if (!$metadata->getMetadata()) {
            throw new \RuntimeException(sprintf('Bundle "%s" does not contain any mapped entities.', $bundle->getName()));
        }

        $path = $this->getBasePathForClass($bundle->getName(), $bundle->getNamespace(), $bundle->getPath());

        $metadata->setPath($path);
        $metadata->setNamespace($bundle->getNamespace());

        return $metadata;
    }

    /**
     * Gets the metadata of a class.
     *
     * @param string $class A class name
     * @param string $path  The path where the class is stored (if known)
     *
     * @return ClassMetadataCollection A ClassMetadataCollection instance
     *
     * @throws MappingException When class is not valid entity or mapped superclass
     */
    public function getClassMetadata($class, $path = null)
    {
        $metadata = $this->getMetadataForClass($class);
        if (!$metadata->getMetadata()) {
            throw MappingException::classIsNotAValidEntityOrMappedSuperClass($class);
        }

        $this->findNamespaceAndPathForMetadata($metadata, $path);

        return $metadata;
    }

    /**
     * Gets the metadata of all classes of a namespace.
     *
     * @param string $namespace A namespace name
     * @param string $path      The path where the class is stored (if known)
     *
     * @return ClassMetadataCollection A ClassMetadataCollection instance
     *
     * @throws \RuntimeException When namespace not contain mapped entities
     */
    public function getNamespaceMetadata($namespace, $path = null)
    {
        $metadata = $this->getMetadataForNamespace($namespace);
        if (!$metadata->getMetadata()) {
            throw new \RuntimeException(sprintf('Namespace "%s" does not contain any mapped entities.', $namespace));
        }

        $this->findNamespaceAndPathForMetadata($metadata, $path);

        return $metadata;
    }

    /**
     * Find and configure path and namespace for the metadata collection.
     *
     * @param ClassMetadataCollection $metadata
     * @param string|null             $path
     *
     * @throws \RuntimeException When unable to determine the path
     */
    public function findNamespaceAndPathForMetadata(ClassMetadataCollection $metadata, $path = null)
    {
        $all = $metadata->getMetadata();
        if (class_exists($all[0]->name)) {
            $r = new \ReflectionClass($all[0]->name);
            $path = $this->getBasePathForClass($r->getName(), $r->getNamespaceName(), dirname($r->getFilename()));
            $ns = $r->getNamespaceName();

        } elseif ($path) {
            // Get namespace by removing the last component of the FQCN
            $nsParts = explode('\\', $all[0]->name);
            array_pop($nsParts);
            $ns = implode('\\', $nsParts);

        } else {
            throw new \RuntimeException(sprintf('Unable to determine where to save the "%s" class (use the --path option).', $all[0]->name));
        }

        $metadata->setPath($path);
        $metadata->setNamespace($ns);
    }

    /**
     * Get a base path for a class
     *
     * @param string $name      class name
     * @param string $namespace class namespace
     * @param string $path      class path
     *
     * @return string
     * @throws \RuntimeException When base path not found
     */
    private function getBasePathForClass($name, $namespace, $path)
    {
        $namespace = str_replace('\\', '/', $namespace);
        $search = str_replace('\\', '/', $path);
        $destination = str_replace('/'.$namespace, '', $search, $c);

        if ($c != 1) {
            throw new \RuntimeException(sprintf('Can\'t find base path for "%s" (path: "%s", destination: "%s").', $name, $path, $destination));
        }

        return $destination;
    }

    /**
     * @param string $namespace
     *
     * @return ClassMetadataCollection
     */
    private function getMetadataForNamespace($namespace)
    {
        $metadata = array();
        foreach ($this->getAllMetadata() as $m) {
            if (strpos($m->name, $namespace) === 0) {
                $metadata[] = $m;
            }
        }

        return new ClassMetadataCollection($metadata);
    }

    /**
     * @param string $entity
     *
     * @return ClassMetadataCollection
     */
    private function getMetadataForClass($entity)
    {
        foreach ($this->registry->getManagers() as $em) {
            $cmf = new DisconnectedClassMetadataFactory();
            $cmf->setEntityManager($em);

            if (!$cmf->isTransient($entity)) {
                return new ClassMetadataCollection(array($cmf->getMetadataFor($entity)));
            }
        }

        return new ClassMetadataCollection(array());
    }

    /**
     * @return array
     */
    private function getAllMetadata()
    {
        $metadata = array();
        foreach ($this->registry->getManagers() as $em) {
            $cmf = new DisconnectedClassMetadataFactory();
            $cmf->setEntityManager($em);
            foreach ($cmf->getAllMetadata() as $m) {
                $metadata[] = $m;
            }
        }

        return $metadata;
    }
}


<?php

/*
 * This file is part of the TomahawkPHP package.
 *
 * (c) Tom Ellis
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Tomahawk\Asset;

/**
 * Asset Container
 *
 * @author Tom Ellis
 */
class AssetContainer implements AssetContainerInterface
{
    /**
     * The asset container name.
     *
     * @var string
     */
    public $name;

    /**
     * All of the added assets.
     *
     * @var array
     */
    public $assets = [];

    /**
     * Create a new asset container instance.
     *
     * @param string $name
     * @return \Tomahawk\Asset\AssetContainer
     */
    public function __construct($name = 'default')
    {
        $this->name = $name;
    }

    /**
     * Set name of container
     *
     * @param string $name
     * @return $this
     */
    public function setName($name)
    {
        $this->name = $name;
        return $this;
    }

    /**
     * Get name of container
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Add a CSS file to the registered assets.
     *
     * @param  string           $name
     * @param  string           $source
     * @param  array            $dependencies
     * @param  array            $attributes
     * @return AssetContainerInterface
     */
    public function addCss($name, $source, $dependencies = array(), $attributes = array())
    {
        if ( ! array_key_exists('media', $attributes)) {
            $attributes['media'] = 'all';
        }

        $this->add('css', $name, $source, $dependencies, $attributes);

        return $this;
    }

    /**
     * Add a JavaScript file to the registered assets.
     *
     * @param  string           $name
     * @param  string           $source
     * @param  array            $dependencies
     * @param  array            $attributes
     * @return AssetContainerInterface
     */
    public function addJs($name, $source, $dependencies = array(), $attributes = array())
    {
        $this->add('js', $name, $source, $dependencies, $attributes);

        return $this;
    }

    /**
     * Add an asset to the array of registered assets.
     *
     * @param  string  $type
     * @param  string  $name
     * @param  string  $source
     * @param  array   $dependencies
     * @param  array   $attributes
     * @return $this
     */
    protected function add($type, $name, $source, $dependencies, $attributes)
    {
        $dependencies = (array) $dependencies;

        $attributes = (array) $attributes;

        $this->assets[$type][$name] = compact('source', 'dependencies', 'attributes');

        return $this;
    }
}


<?php

/*
 * This file is part of the Doctrine Bundle
 *
 * The code was originally distributed inside the Symfony framework.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 * (c) Doctrine Project, Benjamin Eberlei <kontakt@beberlei.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Tomahawk\Bundle\DoctrineBundle\Command;

use Doctrine\DBAL\DriverManager;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Database tool allows you to easily drop and create your configured databases.
 *
 * Based on the DropDatabaseDoctrineCommand from the Symfony DoctrineBundle
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Jonathan H. Wage <jonwage@gmail.com>
 */
class DropDatabaseDoctrineCommand extends DoctrineCommand
{
    const RETURN_CODE_NOT_DROP = 1;

    const RETURN_CODE_NO_FORCE = 2;

    /**
     * {@inheritDoc}
     */
    protected function configure()
    {
        $this
            ->setName('doctrine:database:drop')
            ->setDescription('Drops the configured databases')
            ->addOption('connection', null, InputOption::VALUE_OPTIONAL, 'The connection to use for this command')
            ->addOption('shard', null, InputOption::VALUE_REQUIRED, 'The shard connection to use for this command')
            ->addOption('if-exists', null, InputOption::VALUE_NONE, 'Don\'t trigger an error, when the database doesn\'t exist')
            ->addOption('force', null, InputOption::VALUE_NONE, 'Set this parameter to execute this action')
            ->setHelp(<<<EOT
The <info>doctrine:database:drop</info> command drops the default connections
database:

<info>php app/hatchet doctrine:database:drop</info>

The --force parameter has to be used to actually drop the database.

You can also optionally specify the name of a connection to drop the database
for:

<info>php app/hatchet doctrine:database:drop --connection=default</info>

<error>Be careful: All data in a given database will be lost when executing
this command.</error>
EOT
        );
    }

    /**
     * {@inheritDoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $connection = $this->getDoctrineConnection($input->getOption('connection'));
        $ifExists = $input->getOption('if-exists');
        $params = $connection->getParams();
        if (isset($params['master'])) {
            $params = $params['master'];
        }
        if (isset($params['shards'])) {
            $shards = $params['shards'];
            // Default select global
            $params = array_merge($params, $params['global']);
            if ($input->getOption('shard')) {
                foreach ($shards as $shard) {
                    if ($shard['id'] === (int)$input->getOption('shard')) {
                        // Select sharded database
                        $params = $shard;
                        unset($params['id']);
                        break;
                    }
                }
            }
        }
        $name = isset($params['path']) ? $params['path'] : (isset($params['dbname']) ? $params['dbname'] : false);
        if (!$name) {
            throw new \InvalidArgumentException("Connection does not contain a 'path' or 'dbname' parameter and cannot be dropped.");
        }
        unset($params['dbname']);
        if ($input->getOption('force')) {
            // Reopen connection without database name set
            // as some vendors do not allow dropping the database connected to.
            $connection->close();
            $connection = DriverManager::getConnection($params);
            $shouldDropDatabase = !$ifExists || in_array($name, $connection->getSchemaManager()->listDatabases());
            // Only quote if we don't have a path
            if (!isset($params['path'])) {
                $name = $connection->getDatabasePlatform()->quoteSingleIdentifier($name);
            }
            try {
                if ($shouldDropDatabase) {
                    $connection->getSchemaManager()->dropDatabase($name);
                    $output->writeln(sprintf('<info>Dropped database for connection named <comment>%s</comment></info>', $name));
                } else {
                    $output->writeln(sprintf('<info>Database for connection named <comment>%s</comment> doesn\'t exist. Skipped.</info>', $name));
                }
            } catch (\Exception $e) {
                $output->writeln(sprintf('<error>Could not drop database for connection named <comment>%s</comment></error>', $name));
                $output->writeln(sprintf('<error>%s</error>', $e->getMessage()));
                return self::RETURN_CODE_NOT_DROP;
            }
        } else {
            $output->writeln('<error>ATTENTION:</error> This operation should not be executed in a production environment.');
            $output->writeln('');
            $output->writeln(sprintf('<info>Would drop the database named <comment>%s</comment>.</info>', $name));
            $output->writeln('Please run the operation with --force to execute');
            $output->writeln('<error>All data will be lost!</error>');
            return self::RETURN_CODE_NO_FORCE;
        }
    }
}


<?php

/*
 * This file is part of the TomahawkPHP package.
 *
 * (c) Tom Ellis
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Tomahawk\Routing\Controller;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Tomahawk\DependencyInjection\ContainerAwareInterface;
use Tomahawk\DependencyInjection\ContainerAwareTrait;

class Controller implements ContainerAwareInterface
{
    use ContainerAwareTrait;

    public function forward($controller, array $path = [], array $query = [])
    {
        $path['_controller'] = $controller;
        $subRequest = $this->container->get('http_kernel')->getCurrentRequest()->duplicate($query, null, $path);

        return $this->container->get('http_kernel')->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
    }

    /**
     * Returns a rendered view.
     *
     * @param string $view       The view name
     * @param array  $parameters An array of parameters to pass to the view
     *
     * @return string The rendered view
     */
    public function renderView($view, array $parameters = [])
    {
        return $this->container->get('templating')->render($view, $parameters);
    }

    /**
     * Returns a response
     *
     * @param $view
     * @param array $parameters
     * @param Response|null $response
     * @return Response
     */
    public function render($view, array $parameters = [], Response $response = null)
    {
        $content = $this->renderView($view, $parameters);

        if (null === $response) {
            $response = new Response();
        }

        $response->setContent($content);

        return $response;
    }

    /**
     * Returns true if the service id is defined.
     *
     * @param string $id The service id
     *
     * @return bool    true if the service id is defined, false otherwise
     */
    public function has($id)
    {
        return $this->container->has($id);
    }

    /**
     * Gets a service by id.
     *
     * @param string $id The service id
     *
     * @return object The service
     */
    public function get($id)
    {
        return $this->container->get($id);
    }
}


<?php

namespace Tomahawk\HttpKernel\Config;

use Symfony\Component\Config\FileLocator as BaseFileLocator;
use Tomahawk\HttpKernel\KernelInterface;

class FileLocator extends BaseFileLocator
{
    private $kernel;
    private $path;

    /**
     * Constructor.
     *
     * @param KernelInterface $kernel A KernelInterface instance
     * @param null|string     $path   The path the global resource directory
     * @param array           $paths  An array of paths where to look for resources
     */
    public function __construct(KernelInterface $kernel, $path = null, array $paths = array())
    {
        $this->kernel = $kernel;
        if (null !== $path) {
            $this->path = $path;
            $paths[] = $path;
        }

        parent::__construct($paths);
    }

    /**
     * {@inheritdoc}
     */
    public function locate($file, $currentPath = null, $first = true)
    {
        if ('@' === $file[0]) {
            return $this->kernel->locateResource($file, $this->path, $first);
        }

        return parent::locate($file, $currentPath, $first);
    }
}


<?php

/*
 * This file is part of the TomahawkPHP package.
 *
 * (c) Tom Ellis
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Tomahawk\Bundle\ErrorHandlerBundle;

use Tomahawk\Bundle\ErrorHandlerBundle\Controller\ExceptionController;
use Tomahawk\DependencyInjection\ContainerInterface;
use Tomahawk\HttpKernel\Bundle\Bundle;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpKernel\EventListener\ExceptionListener;
use Symfony\Component\HttpKernel\EventListener\DebugHandlersListener;

class ErrorHandlerBundle extends Bundle
{
    public function boot()
    {
        $this->container->set('exception_listener', function(ContainerInterface $c) {
            return new ExceptionListener(
                'exception_controller:showAction',
                $this->container->get('logger')
            );
        });

        $this->container->set('exception_controller', function(ContainerInterface $c) {
            return new ExceptionController($c['twig'], $c['kernel']->isDebug());
        });

        $this->container->set('debug_handlers_listener', function(ContainerInterface $c) {
            return new DebugHandlersListener(null, $c['logger']);
        });
    }

    /**
     * Register any events for the bundle
     *
     * This is called after all bundles have been boot so you get access
     * to all the services
     *
     * @param EventDispatcherInterface $dispatcher
     */
    public function registerEvents(EventDispatcherInterface $dispatcher)
    {
        $dispatcher->addSubscriber($this->container->get('exception_listener'));
        $dispatcher->addSubscriber($this->container->get('debug_handlers_listener'));
    }
}


<?php

/*
 * This file is part of the TomahawkPHP package.
 *
 * (c) Tom Ellis
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Tomahawk\Cache;

use Tomahawk\Cache\Provider\CacheProviderInterface;

class CacheManager implements CacheInterface
{
    /**
     * @var CacheProviderInterface
     */
    protected $cacheProvider;

    /**
     * @param CacheProviderInterface $cacheProvider
     */
    public function __construct(CacheProviderInterface $cacheProvider)
    {
        $this->cacheProvider = $cacheProvider;
    }

    /**
     * Fetch an item out of the cache
     *
     * @param $id
     * @return mixed
     */
    public function fetch($id)
    {
        return $this->cacheProvider->fetch($id);
    }

    /**
     * Save an item into the cache
     *
     * @param $id
     * @param $value
     * @param bool $lifetime
     * @return bool
     */
    public function save($id, $value, $lifetime = false)
    {
        $this->cacheProvider->save($id, $value, $lifetime);
    }

    /**
     * Check if an item has been cached
     *
     * @param $id
     * @return mixed
     */
    public function contains($id)
    {
        return $this->cacheProvider->contains($id);
    }

    /**
     * Delete an item from the cache
     *
     * @param $id
     * @return bool
     */
    public function delete($id)
    {
        $this->cacheProvider->delete($id);
    }

    /**
     * Delete all items from the cache
     *
     * @return bool
     */
    public function flush()
    {
        $this->cacheProvider->flush();
    }

    /**
     * @return CacheProviderInterface
     */
    public function getProvider()
    {
        return $this->cacheProvider;
    }

}


<?php

/*
 * This file is part of the TomahawkPHP package.
 *
 * (c) Tom Ellis
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Tomahawk\Templating\Twig;

use Tomahawk\Templating\TemplateReference;
use Tomahawk\Templating\Twig\Bridge\TwigEngine as BaseEngine;
use Symfony\Component\Templating\TemplateNameParserInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Config\FileLocatorInterface;
use Symfony\Component\Templating\EngineInterface;

/**
 * This engine knows how to render Twig templates.
 *
 * @author Tom Ellis
 *
 * Heavily based on the original by
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class TwigEngine extends BaseEngine implements EngineInterface
{
    /**
     * @var FileLocatorInterface
     */
    protected $locator;

    /**
     * Constructor.
     *
     * @param \Twig_Environment           $environment A \Twig_Environment instance
     * @param TemplateNameParserInterface $parser      A TemplateNameParserInterface instance
     * @param FileLocatorInterface        $locator     A FileLocatorInterface instance
     */
    public function __construct(\Twig_Environment $environment, TemplateNameParserInterface $parser, FileLocatorInterface $locator)
    {
        parent::__construct($environment, $parser);

        $this->locator = $locator;
    }

    /**
     * {@inheritdoc}
     */
    public function render($name, array $parameters = array())
    {
        try {
            return parent::render($name, $parameters);
        } catch (\Twig_Error $e) {
            if ($name instanceof TemplateReference && !method_exists($e, 'setSourceContext')) {
                try {
                    // try to get the real name of the template where the error occurred
                    $name = $e->getTemplateName();
                    $path = (string) $this->locator->locate($this->parser->parse($name));
                    $e->setTemplateName($path);
                } catch (\Exception $e2) {
                }
            }

            throw $e;
        }
    }

    /**
     * {@inheritdoc}
     *
     * @throws \Twig_Error if something went wrong like a thrown exception while rendering the template
     */
    public function renderResponse($view, array $parameters = array(), Response $response = null)
    {
        if (null === $response) {
            $response = new Response();
        }

        $response->setContent($this->render($view, $parameters));

        return $response;
    }
}


<?php
namespace CFDIReader;

use DOMDocument;
use DOMNode;
use DOMNodeList;
use DOMXPath;

/**
 * Class to clean CFDI and avoid bad common practices.
 *
 * Strictly speaking, CFDI must accomplish all XML rules, including that any other
 * XML element must be isolated in its own namespace and follow their own XSD rules.
 *
 * The common practice (allowed by SAT) is that the CFDI is created, signed and
 * some nodes are attached after sign, some of them does not comply the XML standard.
 *
 * This is why it's better to clear Comprobante/Addenda and remove unused namespaces
 *
 * @package CFDIReader
 */
class CFDICleaner
{
    /** @var DOMDocument */
    protected $dom;

    /**
     * CFDICleaner constructor.
     * @param string $content
     */
    public function __construct(string $content)
    {
        if (! empty($content)) {
            $this->loadContent($content);
        }
    }

    /**
     * Method to clean content and return the result
     * If an error occurs, an exception is thrown
     * @param string $content
     * @return string
     */
    public static function staticClean($content): string
    {
        $cleaner = new self($content);
        $cleaner->clean();
        return $cleaner->retrieveXml();
    }

    /**
     * Check if the CFDI version is complatible to this class
     * @param string $version
     * @return bool
     */
    public static function isVersionAllowed(string $version): bool
    {
        return in_array($version, ['3.2', '3.3']);
    }

    /**
     * Check if a given namespace is allowed (must not be removed from CFDI)
     * @param string $namespace
     * @return bool
     */
    public static function isNameSpaceAllowed(string $namespace): bool
    {
        $fixedNS = [
            'http://www.w3.org/2001/XMLSchema-instance',
            'http://www.w3.org/XML/1998/namespace',
        ];
        foreach ($fixedNS as $ns) {
            if (0 === strcasecmp($ns, $namespace)) {
                return true;
            }
        }
        $willcardNS = [
            'http://www.sat.gob.mx/',
        ];
        foreach ($willcardNS as $ns) {
            if (0 === strpos($namespace, $ns)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Apply all removals (Addenda, Non SAT Nodes and Non SAT namespaces)
     * @return void
     */
    public function clean()
    {
        $this->removeAddenda();
        $this->removeNonSatNSNodes();
        $this->removeNonSatNSschemaLocations();
        $this->removeUnusedNamespaces();
    }

    /**
     * Load the string content as a CFDI
     * This is exposed to reuse the current object instead of create a new instance
     *
     * @param string $content
     *
     * @throws CFDICleanerException when the content is not valid xml
     * @throws CFDICleanerException when the document does not use the namespace http://www.sat.gob.mx/cfd/3
     * @throws CFDICleanerException when cannot find a Comprobante version (or Version) attribute
     * @throws CFDICleanerException when the version is not compatible
     *
     * @return void
     */
    public function loadContent(string $content)
    {
        // run this method with libxml internal errors enabled
        if (true !== libxml_use_internal_errors(true)) {
            try {
                $this->loadContent($content);
            } finally {
                libxml_use_internal_errors(false);
            }
        }

        libxml_clear_errors(); // clear previous libxml errors
        $dom = new DOMDocument();
        @$dom->loadXML($content, LIBXML_NOWARNING | LIBXML_NONET);
        if (false !== $loaderror = libxml_get_last_error()) {
            libxml_clear_errors();  // clear recently libxml errors
            throw new CFDICleanerException('XML Error: ' . $loaderror->message);
        }
        $prefix = $dom->lookupPrefix('http://www.sat.gob.mx/cfd/3');
        if (! $prefix) {
            throw new CFDICleanerException('The XML document is not a CFDI');
        }
        $version = $this->xpathQuery('/' . $prefix . ':Comprobante/@version', $dom->documentElement);
        if ($version->length != 1) {
            $version = $this->xpathQuery('/' . $prefix . ':Comprobante/@Version', $dom->documentElement);
        }
        if ($version->length != 1) {
            throw new CFDICleanerException('The XML document does not contains a version');
        }
        if (! $this->isVersionAllowed($version->item(0)->nodeValue)) {
            throw new CFDICleanerException(
                'The XML document version "' . $version->item(0)->nodeValue . '" is not compatible'
            );
        }
        $this->dom = $dom;
    }

    /**
     * Get the XML content of the CFDI
     * @return string
     */
    public function retrieveXml(): string
    {
        return $this->dom->saveXML();
    }

    /**
     * Helper function to Perform a XPath Query using
     * @param string $query
     * @param DOMNode|null $element
     * @return DOMNodeList
     */
    private function xpathQuery(string $query, DOMNode $element = null): DOMNodeList
    {
        $element = $element ?: $this->dom->documentElement;
        $nodelist = (new DOMXPath($element->ownerDocument))->query($query, $element);
        if (false === $nodelist) {
            $nodelist = new DOMNodeList();
        }
        return $nodelist;
    }

    /**
     * Procedure to remove the Comprobante/Addenda node
     *
     * @return void
     */
    public function removeAddenda()
    {
        $prefix = $this->dom->lookupPrefix('http://www.sat.gob.mx/cfd/3');
        $query = '/' . $prefix . ':Comprobante/' . $prefix . ':Addenda';
        $addendas = $this->xpathQuery($query);
        if ($addendas->length == 0) {
            return;
        }
        for ($i = 0; $i < $addendas->length; $i++) {
            $addenda = $addendas->item($i);
            $addenda->parentNode->removeChild($addenda);
        }
    }

    /**
     * Procedure to drop schemaLocations that are not allowed
     * If the schemaLocation is empty then remove the attribute
     *
     * @return void
     */
    public function removeNonSatNSschemaLocations()
    {
        $xsi = $this->dom->lookupPrefix('http://www.w3.org/2001/XMLSchema-instance');
        if (! $xsi) {
            return;
        }
        $schemaLocations = $this->xpathQuery("//@$xsi:schemaLocation");
        if ($schemaLocations->length === 0) {
            return;
        }
        for ($s = 0; $s < $schemaLocations->length; $s++) {
            $this->removeNonSatNSschemaLocation($schemaLocations->item($s));
        }
    }

    /**
     * @param DOMNode $schemaLocation This is the attribute
     * @return void
     */
    private function removeNonSatNSschemaLocation(DOMNode $schemaLocation)
    {
        $source = $schemaLocation->nodeValue;
        $parts = array_values(array_filter(explode(' ', $source)));
        $partsCount = count($parts);
        if (0 !== $partsCount % 2) {
            throw new CFDICleanerException("The schemaLocation value '" . $source . "' must have even number of URIs");
        }
        $modified = '';
        for ($k = 0; $k < $partsCount; $k = $k + 2) {
            if (! $this->isNameSpaceAllowed($parts[$k])) {
                continue;
            }
            $modified .= $parts[$k] . ' ' . $parts[$k + 1] . ' ';
        }
        $modified = rtrim($modified, ' ');
        if ($source == $modified) {
            return;
        }
        if ('' !== $modified) {
            $schemaLocation->nodeValue = $modified;
        } else {
            $schemaLocation->parentNode->attributes->removeNamedItemNS(
                $schemaLocation->namespaceURI,
                $schemaLocation->nodeName
            );
        }
    }

    /**
     * Procedure to remove all nodes that are not from an allowed namespace
     * @return void
     */
    public function removeNonSatNSNodes()
    {
        $nss = [];
        foreach ($this->xpathQuery('//namespace::*') as $node) {
            $namespace = $node->nodeValue;
            if ($this->isNameSpaceAllowed($namespace)) {
                continue;
            }
            $nss[] = $namespace;
        }
        if (! count($nss)) {
            return;
        }
        foreach ($nss as $namespace) {
            $this->removeNonSatNSNode($namespace);
        }
    }

    /**
     * Procedure to remove all nodes from an specific namespace
     * @param string $namespace
     * @return void
     */
    private function removeNonSatNSNode(string $namespace)
    {
        foreach ($this->dom->getElementsByTagNameNS($namespace, '*') as $children) {
            $children->parentNode->removeChild($children);
        }
    }

    /**
     * Procedure to remove not allowed xmlns definitions
     * @return void
     */
    public function removeUnusedNamespaces()
    {
        $nss = [];
        foreach ($this->xpathQuery('//namespace::*') as $node) {
            $namespace = $node->nodeValue;
            if (! $namespace || $this->isNameSpaceAllowed($namespace)) {
                continue;
            }
            $prefix = $this->dom->lookupPrefix($namespace);
            $nss[$prefix] = $namespace;
        }
        $nss = array_unique($nss);
        foreach ($nss as $prefix => $namespace) {
            $this->dom->documentElement->removeAttributeNS($namespace, $prefix);
        }
    }
}


<?php
namespace Understory;

use Timber;

/**
 * Understory View
 *
 * Extend your View with this class to gain Understory View functionality.
 *
 * On subclasses, define a `configure` function that will register.
 *
 * Inside the `configure` method, use the `has` method to set any Registerables
 * or MetaDataBindings like posts or pages. It will also set the values as
 * context variables to be available in the Twig template. If no MetaDataBinding
 * is specifeid the current Post will be assumed.
 *
 * Use the `setContext` method to bypass any registration and simple set a
 * context variable.
 *
 * The twig template file name name is assumed to be identical to the View's
 * file name. Use the `setTemplate` method to change it.
 *
 */
abstract class View implements DelegatesMetaDataBinding, Registerable, Registry, Composition
{
    /**
     * Path of the template. Leave null to auto generate based on namespace and
     * class name.
     *
     * @var string
     */
    private $template = '';

    /**
     * Object that contains the meta values, usually the Post
     *
     * @var MetaDataBinding
     */
    private $metaDataBinding;

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

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

    /**
     * @var array
     */
    private $contextRegistry = [];
    private $site;

    public function __construct()
    {

    }

    public function setSite(\Understory\Site $site)
    {
        $this->site = $site;
    }

    /**
     * @return \Understory\Site
     */
    public function getSite()
    {
        return $this->site;
    }

    /**
     * Override to configure the view
     */
    protected function configure()
    {
    }

    protected function configureContext($context)
    {
        return $context;
    }

    /**
     * @param string $key
     * @param Registerable $registerable
     */
    public function addToRegistry($key, Registerable $registerable)
    {
        $this->registry[$key] = $registerable;
    }

    /**
     * Implements Registry Interface method to register the items in the registery.
     */
    public function registerItemsInRegistry()
    {
        foreach ($this->registry as $registerable) {
            $registerable->register();
        }
    }

    /**
     * Run when the view is registered. Configures the View and registers
     * any items added to the View's registry.
     */
    public function register()
    {
        $this->configure();
        $this->registerItemsInRegistry();
    }

    /**
     * Implements `Composition::has`
     * Registers any Registerables
     * Sets as MetaDataBinding to delegate to, if $values is a MetaDataBinding
     * Adds to the Timber context
     * Adds as a class property
     *
     * @param string $property
     * @param mixed $value
     */
    public function has($property, $value)
    {
        if ($value instanceof Registerable) {
            $this->addToRegistry($property, $value);
        }

        if ($value instanceof DelegatesMetaDataBinding) {
            $value->setMetaDataBinding($this);
        }

        $this->setContext($property, $value);

        if (!is_callable($value)) {
            $this->$property = $value;
        }
    }

    /**
     * If the delegated MetaDataBinding hasn't been set yet,
     * assume the current post, as a PostType object.
     * Use to bind Registery items.
     */
    protected function initializeBindings()
    {
        if (!$this->getMetaDataBinding()) {
            $siteClass = get_class($this->site);
            if ($binding = $siteClass::getPost()) {
                $this->setMetaDataBinding($binding);
            }

        }
        $this->bindRegistryItems();
    }

    /**
     * Set binding on any Registry items that delegate to a MetaDataBinding.
     */
    protected function bindRegistryItems()
    {
        foreach ($this->registry as $registerable) {
            if ($registerable instanceof DelegatesMetaDataBinding) {
                $registerable->setMetaDataBinding($this->getMetaDataBinding());
            }
        }
    }

    /**
     * Return the file name based on the camel-cased class name.
     *
     * @return string
     */
    public function getFileName()
    {
        $calledClass = preg_replace('/.*Views/i', '', get_called_class());

        return str_replace('\\', DIRECTORY_SEPARATOR, $calledClass);
    }

    /**
     * Return the file name based on the class name that is dashed lower case.
     * This file name format is used by WordPress.
     *
     * @return string
     */
    public function getFileNameDashedCase()
    {
        $calledClass = preg_replace('/.*Views/i', '', get_called_class());

        $calledClass = strtolower(
            preg_replace('/(?<=\\w)(?=[A-Z])/', '-$1', $calledClass)
        );

        return str_replace('\\', DIRECTORY_SEPARATOR, $calledClass);
    }

    /**
     * Generate a Template file path from the namespace (after \Views) and
     * class name of the view. Be sure to keep Views in the \Views namespace.
     *
     * @return string template file path
     */
    public function generateTemplateFileName()
    {
        $template = $this->getFileName() . '.twig';
        $templatePath = preg_replace('/Views/i', 'templates', TEMPLATEPATH);

        if (!file_exists($templatePath . $template)) {
            $templateWithDashes = $this->getFileNameDashedCase() . '.twig';
            if (file_exists($templatePath . $templateWithDashes)) {
                return $templateWithDashes;
            }
        }

        return $template;
    }

    /**
     * Return the template file. It will first check to see if the $template
     * variable is defined, otherwise a template file path will be generated
     * from the namespace (after \Views) and class name of the view. Be sure
     * to keep Views in the \Views namespace.
     *
     * @return string template file path
     */
    public function getTemplate()
    {
        if (!$this->template) {
            $this->setTemplate($this->generateTemplateFileName());
        }

        return $this->template;
    }

    public function getTemplateFilePath()
    {
        return 'app/Views'.$this->getFileName().'.php';
    }

    /**
     * Manually set the path of the template from the base 'templates' directory.
     * Set to empty string or don't set to autogenerate the template path
     * from the namespace and class name.
     *
     * @param string $template template path
     */
    public function setTemplate($template)
    {
        $this->template = $template;
    }

    /**
     * Return the Timber context.
     *
     * @return array
     */
    public function getContext()
    {
        return $this->context;
    }

    /**
     * Set a variable to be available in the Twig template.
     *
     * @param string $key
     * @param mixed $value
     */
    public function setContext($key, $value)
    {
        $this->contextRegistry[$key] = $value;
    }

    /**
     * Return the Timber context with the View's context values added.
     *
     * @return array
     */
    private function initializeContext()
    {
        $this->context = Timber\Timber::get_context();

        $this->context['page'] = $this;
        $this->context['post'] = $this->getMetaDataBinding();

        $this->context = $this->configureContext($this->context);

        foreach ($this->contextRegistry as $key => $value) {

            if (is_callable($value)) {
                $value = call_user_func($value);
            }
            $this->context[$key] = $value;
        }

        return $this->context;
    }

    /**
     * Render the View after initialzing the bindings and context
     */
    public function render()
    {
        $this->initializeBindings();
        return Timber\Timber::compile($this->getTemplate(), $this->initializeContext());
    }

    /**
     * Implentation of MetaDataBinding::getMetaValue
     *
     * @param  string $key Key for the meta field
     * @return string Value of the meta field
     */
    public function getMetaValue($key)
    {
        return $this->getMetaDataBinding()->getMetaValue($key);
    }

    /**
     * Implentation of MetaDataBinding::>setMetaValue
     *
     * @param  string $key Key for the meta field
     * @param  string $value Value for the meta field
     */
    public function setMetaValue($key, $value)
    {
        $this->getMetaDataBinding()->setMetaValue($key, $value);
    }

    public function getBindingName()
    {
        return $this->getTemplate();
    }

    /**
     * @return MetaDataBinding
     */
    public function getMetaDataBinding()
    {
        return $this->metaDataBinding;
    }

    /**
     * @param MetaDataBinding $binding
     */
    public function setMetaDataBinding(MetaDataBinding $binding)
    {
        $this->metaDataBinding = $binding;
    }

    /**
     * If a method doesn't exist on the View, delegate to the MataDataBinding
     *
     * @param string $methodName
     * @param array $args
     * @return mixed
     */
    public function __call($methodName, $args)
    {
        if (method_exists($this->getMetaDataBinding(), $methodName)) {
            return call_user_func_array([$this->getMetaDataBinding(), $methodName], $args);
        }

        // Return property
        return $this->$methodName;
    }

    /**
     * If a property or method doesn't exist on the View, delegate to the
     * MetaDataBinding
     *
     * @param  string $property
     * @return mixed
     */
    public function __get($property)
    {
        return $this->getMetaDataBinding()->$property;
    }
}