<?php
/*
 * Copyright (c) Arnaud Ligny <arnaud@ligny.org>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace PHPoole\Collection;

/**
 * Class Collection.
 */
class Collection implements CollectionInterface
{
    /**
     * Collections's identifier.
     *
     * @var string
     */
    protected $id = '';

    /**
     * Collection's items.
     *
     * @var array
     */
    protected $items = [];

    /**
     * AbstractCollection constructor.
     *
     * @param string|null $id
     * @param array       $items
     */
    public function __construct($id = null, $items = [])
    {
        $this->setId($id);
        $this->items = $items;
    }

    /**
     * If parameter is empty uses the object hash
     * {@inheritdoc}
     */
    public function setId($id = '')
    {
        if (empty($id)) {
            $this->id = spl_object_hash($this);
        } else {
            $this->id = $id;
        }

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * {@inheritdoc}
     */
    public function has($id)
    {
        return array_key_exists($id, $this->items);
    }

    /**
     * {@inheritdoc}
     */
    public function add(ItemInterface $item)
    {
        if ($this->has($item->getId())) {
            throw new \DomainException(sprintf(
                'Failed adding item "%s": an item with that id has already been added.',
                $item->getId()
            ));
        }
        $this->items[$item->getId()] = $item;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function replace($id, ItemInterface $item)
    {
        if ($this->has($id)) {
            $this->items[$id] = $item;
        } else {
            throw new \DomainException(sprintf(
                'Failed replacing item "%s": item does not exist.',
                $item->getId()
            ));
        }
    }

    /**
     * {@inheritdoc}
     */
    public function remove($id)
    {
        if ($this->has($id)) {
            unset($this->items[$id]);
        } else {
            throw new \DomainException(sprintf(
                'Failed removing item with ID "%s": item does not exist.',
                $id
            ));
        }
    }

    /**
     * {@inheritdoc}
     */
    public function get($id)
    {
        if (!$this->has($id)) {
            return false;
        }

        return $this->items[$id];
    }

    /**
     * {@inheritdoc}
     */
    public function keys()
    {
        return array_keys($this->items);
    }

    /**
     * {@inheritdoc}
     */
    public function count()
    {
        return count($this->items);
    }

    /**
     * {@inheritdoc}
     */
    public function toArray()
    {
        return $this->items;
    }

    /**
     * {@inheritdoc}
     */
    public function getIterator()
    {
        return new \ArrayIterator($this->items);
    }

    /**
     * {@inheritdoc}
     */
    public function usort(\Closure $callback = null)
    {
        $items = $this->items;
        $callback ? uasort($items, $callback) : uasort($items, function ($a, $b) {
            if ($a == $b) {
                return 0;
            }

            return ($a < $b) ? -1 : 1;
        });

        return new static(self::getId(), $items);
    }

    /**
     * Sort items by date.
     *
     * @return Collection
     */
    public function sortByDate()
    {
        return $this->usort(function ($a, $b) {
            if (!isset($a['date'])) {
                return -1;
            }
            if (!isset($b['date'])) {
                return 1;
            }
            if ($a['date'] == $b['date']) {
                return 0;
            }

            return ($a['date'] > $b['date']) ? -1 : 1;
        });
    }

    /**
     * {@inheritdoc}
     *
     * @return Collection
     */
    public function filter(\Closure $callback)
    {
        return new static(self::getId(), array_filter($this->items, $callback));
    }

    /**
     * {@inheritdoc}
     */
    public function map(\Closure $callback)
    {
        return new static(self::getId(), array_map($callback, $this->items));
    }

    /**
     * Implement ArrayAccess.
     *
     * @param mixed $offset
     *
     * @return bool
     */
    public function offsetExists($offset)
    {
        return $this->has($offset);
    }

    /**
     * Implement ArrayAccess.
     *
     * @param mixed $offset
     *
     * @return null|CollectionInterface
     */
    public function offsetGet($offset)
    {
        return $this->get($offset);
    }

    /**
     * Implement ArrayAccess.
     *
     * @param mixed $offset
     * @param mixed $value
     */
    public function offsetSet($offset, $value)
    {
        $this->add($value);
    }

    /**
     * Implement ArrayAccess.
     *
     * @param mixed $offset
     */
    public function offsetUnset($offset)
    {
        $this->remove($offset);
    }

    /**
     * Returns a string representation of this object.
     *
     * @return string
     */
    public function __toString()
    {
        return json_encode($this->items)."\n";
    }
}


<?php
/*
 * Copyright (c) Arnaud Ligny <arnaud@ligny.org>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace PHPoole;

use Dflydev\DotAccessData\Data;
use PHPoole\Exception\Exception;

/**
 * Class Config.
 */
class Config
{
    /**
     * Config.
     *
     * @var Data
     */
    protected $data;
    /**
     * Source directory.
     *
     * @var string
     */
    protected $sourceDir;
    /**
     * Destination directory.
     *
     * @var string
     */
    protected $destinationDir;
    /**
     * Default data.
     *
     * @var array
     */
    protected static $defaultData = [
        'site' => [
            'title'        => 'My Webiste',
            'baseline'     => 'An amazing static website!',
            'baseurl'      => 'http://localhost:8000/',
            'canonicalurl' => false,
            'description'  => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
            'taxonomies'   => [
                'tags'       => 'tag',
                'categories' => 'category',
            ],
            'paginate' => [
                'max'  => 5,
                'path' => 'page',
            ],
            'date' => [
                'format'   => 'j F Y',
                'timezone' => 'Europe/Paris',
            ],
            'fmpages' => [
                'robotstxt' => [
                    'title'     => 'Robots.txt',
                    'layout'    => 'robots.txt',
                    'permalink' => 'robots.txt',
                ],
                'sitemap' => [
                    'title'      => 'XML sitemap',
                    'layout'     => 'sitemap.xml',
                    'permalink'  => 'sitemap.xml',
                    'changefreq' => 'monthly',
                    'priority'   => '0.5',
                ],
                '404' => [
                    'title'     => '404 page',
                    'layout'    => '404.html',
                    'permalink' => '404.html',
                ],
                'rss' => [
                    'title'     => 'RSS file',
                    'layout'    => 'rss.xml',
                    'permalink' => 'rss.xml',
                    'section'   => 'blog',
                ],
            ],
        ],
        'content' => [
            'dir' => 'content',
            'ext' => ['md', 'markdown', 'mdown', 'mkdn', 'mkd', 'text', 'txt'],
        ],
        'frontmatter' => [
            'format' => 'yaml',
        ],
        'body' => [
            'format' => 'md',
        ],
        'static' => [
            'dir' => 'static',
        ],
        'layouts' => [
            'dir'      => 'layouts',
            'internal' => [
                'dir' => 'res/layouts',
            ],
        ],
        'output' => [
            'dir'      => '_site',
            'filename' => 'index.html',
        ],
        'themes' => [
            'dir' => 'themes',
        ],
        'generators' => [
            10 => 'PHPoole\Generator\Section',
            20 => 'PHPoole\Generator\Taxonomy',
            30 => 'PHPoole\Generator\Homepage',
            40 => 'PHPoole\Generator\Pagination',
            50 => 'PHPoole\Generator\Alias',
            35 => 'PHPoole\Generator\ExternalBody',
            36 => 'PHPoole\Generator\PagesFromConfig',
            60 => 'PHPoole\Generator\Redirect',
        ],
    ];

    /**
     * Config constructor.
     *
     * @param Config|array|null $config
     */
    public function __construct($config = null)
    {
        $data = new Data(self::$defaultData);

        if ($config) {
            if ($config instanceof self) {
                $data->importData($config->getAll());
            } elseif (is_array($config)) {
                $data->import($config);
            }
        }

        /**
         * Apply environment variables.
         */
        $applyEnv = function ($array) use ($data) {
            $iterator = new \RecursiveIteratorIterator(
                new \RecursiveArrayIterator($array),
                \RecursiveIteratorIterator::SELF_FIRST
            );
            foreach ($iterator as $leafValue) {
                $path = [];
                foreach (range(0, $iterator->getDepth()) as $depth) {
                    $path[] = $iterator->getSubIterator($depth)->key();
                }
                $sPath = implode('_', $path);
                if ($getEnv = getenv('PHPOOLE_'.strtoupper($sPath))) {
                    $data->set(str_replace('_', '.', strtolower($sPath)), $getEnv);
                }
            }
        };
        $applyEnv($data->export());

        $this->setFromData($data);
    }

    /**
     * Import array config to current config.
     *
     * @param array $config
     *
     * @return $this
     */
    public function import($config)
    {
        if (is_array($config)) {
            $data = $this->getAll();
            $origin = $data->export();
            $data->import($config);
            $data->import($origin);
            $this->setFromData($data);
        }
    }

    /**
     * Set config data.
     *
     * @param Data $data
     *
     * @return $this
     */
    protected function setFromData(Data $data)
    {
        if ($this->data !== $data) {
            $this->data = $data;
        }

        return $this;
    }

    /**
     * Get config data.
     *
     * @return Data
     */
    public function getAll()
    {
        return $this->data;
    }

    /**
     * Get data as array.
     *
     * @return array
     */
    public function getAllAsArray()
    {
        return $this->data->export();
    }

    /**
     * Return a config value.
     *
     * @param string $key
     * @param string $default
     *
     * @return array|mixed|null
     */
    public function get($key, $default = '')
    {
        return $this->data->get($key, $default);
    }

    /**
     * Set source directory.
     *
     * @param null $sourceDir
     *
     * @throws Exception
     *
     * @return $this
     */
    public function setSourceDir($sourceDir = null)
    {
        if ($sourceDir === null) {
            $sourceDir = getcwd();
        }
        if (!is_dir($sourceDir)) {
            throw new \InvalidArgumentException(sprintf("'%s' is not a valid source directory.", $sourceDir));
        }
        $this->sourceDir = $sourceDir;

        return $this;
    }

    /**
     * Get source directory.
     *
     * @return string
     */
    public function getSourceDir()
    {
        return $this->sourceDir;
    }

    /**
     * Set destination directory.
     *
     * @param null $destinationDir
     *
     * @throws Exception
     *
     * @return $this
     */
    public function setDestinationDir($destinationDir = null)
    {
        if ($destinationDir === null) {
            $destinationDir = $this->sourceDir;
        }
        if (!is_dir($destinationDir)) {
            throw new \InvalidArgumentException(sprintf("'%s' is not a valid destination directory.", $destinationDir));
        }
        $this->destinationDir = $destinationDir;

        return $this;
    }

    /**
     * Get destination directory.
     *
     * @return string
     */
    public function getDestinationDir()
    {
        return $this->destinationDir;
    }

    /**
     * Path helpers.
     */

    /**
     * Return content directory path.
     *
     * @return string
     */
    public function getContentPath()
    {
        return $this->getSourceDir().'/'.$this->get('content.dir');
    }

    /**
     * Return templates directory path.
     *
     * @return string
     */
    public function getLayoutsPath()
    {
        return $this->getSourceDir().'/'.$this->get('layouts.dir');
    }

    /**
     * Return themes directory path.
     *
     * @return string
     */
    public function getThemesPath()
    {
        return $this->getSourceDir().'/'.$this->get('themes.dir');
    }

    /**
     * Return internal templates directory path.
     *
     * @return string
     */
    public function getInternalLayoutsPath()
    {
        return __DIR__.'/../'.$this->get('layouts.internal.dir');
    }

    /**
     * Return output directory path.
     *
     * @return string
     */
    public function getOutputPath()
    {
        return $this->getSourceDir().'/'.$this->get('output.dir');
    }

    /**
     * Return static files directory path.
     *
     * @return string
     */
    public function getStaticPath()
    {
        return $this->getSourceDir().'/'.$this->get('static.dir');
    }

    /**
     * Themes helpers.
     */

    /**
     * Return theme(s).
     *
     * @return array|null
     */
    public function getTheme()
    {
        if ($themes = $this->get('theme')) {
            if (is_array($themes)) {
                return $themes;
            }

            return [$themes];
        }
    }

    /**
     * Has a (valid) theme(s)?
     *
     * @throws Exception
     *
     * @return bool
     */
    public function hasTheme()
    {
        if ($themes = $this->getTheme()) {
            foreach ($themes as $theme) {
                if (!Util::getFS()->exists($this->getThemeDirPath($theme, 'layouts'))) {
                    throw new Exception(sprintf(
                        "Theme directory '%s/%s/layouts' not found!",
                        $this->getThemesPath(),
                        $theme
                    ));
                }
            }

            return true;
        }

        return false;
    }

    /**
     * Return the path of a specific theme's directory.
     *
     * @param string $theme
     * @param string $dir
     *
     * @return string
     */
    public function getThemeDirPath($theme, $dir = 'layouts')
    {
        return $this->getThemesPath().'/'.$theme.'/'.$dir;
    }
}


<?php

namespace Nekland\Bundle\FeedBundle\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

/**
 * @author Yohan Giarelli <yohan@giarelli.org>
 * @author Nek' <nek.dev+github@gmail.com>
 */
class Configuration implements ConfigurationInterface
{
    /**
     * Generates the configuration tree builder.
     *
     * @return \Symfony\Component\Config\Definition\Builder\TreeBuilder The tree builder
     */
    public function getConfigTreeBuilder()
    {
        $treeBuilder = new TreeBuilder();
        $rootNode = $treeBuilder->root('nekland_feed');

        $rootNode
            ->children()
                ->arrayNode('feeds')
                    ->requiresAtLeastOneElement()
                    ->useAttributeAsKey('name')
                    ->prototype('array')
                        ->children()
                            ->scalarNode('class')->isRequired()->end()
                            ->scalarNode('filename')->isRequired()->end()
                            ->scalarNode('title')->isRequired()->end()
                            ->scalarNode('description')->isRequired()->end()
                            ->scalarNode('language')->isRequired()->end()
                            ->scalarNode('url')->end()
                            ->scalarNode('max_items')->defaultValue(10)->end()
                            ->scalarNode('copyright')->end()
                            ->scalarNode('managingEditor')->end()
                            ->scalarNode('generator')->end()
                            ->scalarNode('webMaster')->end()
                            ->scalarNode('rights')->end()
                            ->scalarNode('subtitle')->end()
                            ->scalarNode('icon')->end()
                            ->arrayNode('image')
                                ->children()
                                    ->scalarNode('url')->isRequired()->end()
                                    ->scalarNode('title')->isRequired()->end()
                                    ->scalarNode('link')->isRequired()->end()
                                    ->scalarNode('width')->end()
                                    ->scalarNode('height')->end()
                                    ->scalarNode('description')->end()
                                ->end()
                            ->end()
                        ->end()
                    ->end()
                ->end()
                ->arrayNode('renderers')
                    ->defaultValue(array(
                        'rss' => array(
                            'id' => 'nekland_feed.renderer.rss'
                        ),
                        'atom' => array(
                            'id' => 'nekland_feed.renderer.atom'
                        )
                    ))
                    ->useAttributeAsKey('name')
                    ->prototype('array')
                        ->children()
                            ->scalarNode('id')->isRequired()->end()
                        ->end()
                    ->end()
                ->end()
                ->arrayNode('loaders')
                    ->defaultValue(array(
                        'rss_file' => array(
                            'id' => 'nekland_feed.loader.rss_file'
                        ),
                        'atom_file' => array(
                            'id' => 'nekland_feed.loader.atom_file'
                        )
                    ))
                    ->useAttributeAsKey('name')
                    ->prototype('array')
                        ->children()
                            ->scalarNode('id')->isRequired()->end()
                        ->end()
                    ->end()
                ->end()
            ->end()
        ;

        return $treeBuilder;
    }

}


<?php

namespace Shieldfy\Sniffer\Types;

use Shieldfy\Sniffer\TypeInterface;

class SerializeType implements TypeInterface
{
    public function sniff($input)
    {
        return $this->isSerialize($input);
    }

    /**
     * @author		Chris Smith <code+php@chris.cs278.org>
     * @copyright	Copyright (c) 2009 Chris Smith (http://www.cs278.org/)
     * @license		http://sam.zoy.org/wtfpl/ WTFPL
     *
     * @param string $value Value to test for serialized form
     *
     * @return bool True if $value is serialized data, otherwise false
     */
    private function isSerialize($value)
    {
        if (!is_string($value)) {
            return false;
        }
        if ($value === 'b:0;') {
            return true;
        }
        $length = strlen($value);
        $end = '';
        switch ($value[0]) {
            case 's':
                if ($value[$length - 2] !== '"') {
                    return false;
                }
            case 'b':
            case 'i':
            case 'd':
                // This looks odd but it is quicker than isset()ing
                $end .= ';';
            case 'a':
            case 'O':
                $end .= '}';
                if ($value[1] !== ':') {
                    return false;
                }
                switch ($value[2]) {
                    case 0:
                    case 1:
                    case 2:
                    case 3:
                    case 4:
                    case 5:
                    case 6:
                    case 7:
                    case 8:
                    case 9:
                        break;
                    default:
                        return false;
                }
            case 'N':
                $end .= ';';
                if ($value[$length - 1] !== $end[0]) {
                    return false;
                }
                break;
            default:
                return false;
        }

        if (version_compare(PHP_VERSION, '7.0.0') >= 0) {
            //options added @ v 7.0 which allow no evaluating for classes
            //object will be instantiated as __PHP_Incomplete_Class
            if (@unserialize($value, []) === false) {
                return false;
            }
        }

        return true;
    }

    public function __toString()
    {
        return 'serialize';
    }
}


<?php

namespace Nekland\Bundle\FeedBundle\Loader;

use Nekland\Bundle\FeedBundle\Feed;
use Nekland\Bundle\FeedBundle\Item\GenericItem;

/**
 * Loads RSS-XML and build a Feed object
 *
 * @throws \InvalidArgumentException
 * @author Yohan Giarelli <yohan@giarelli.org>
 * @author Nek' <nek.dev+github@gmail.com>
 */
class RssLoader implements LoaderInterface
{
    protected static $methodMapping = array(
        'guid'    => 'setFeedId',
        'pubDate' => 'setFeedDate'

    );

    /**
     * @throws \InvalidArgumentException
     * @param $feedContent
     * @return \Nekland\Bundle\FeedBundle\Feed
     */
    public function load($feedContent)
    {
        $feed = new Feed(array('class' => 'Nekland\\Bundle\\FeedBundle\\Item\\GenericItem'));
        $xml = simplexml_load_string($feedContent);

        if (false === $xml) {
            throw new \InvalidArgumentException('The given data is not a valid XML string.');
        }

        foreach ($xml->channel[0] as $xmlItem) {
            if ($xmlItem->getName() != 'item') {
                $this->setParam($xmlItem, $feed);
            } else {
                $this->addItem($xmlItem, $feed);
            }
        }

        return $feed;
    }

    /**
     * Adds an Item to the feed
     *
     * @param \SimpleXMLElement        $element
     * @param \Nekland\Bundle\FeedBundle\Feed $feed
     * @return void
     */
    protected function addItem(\SimpleXMLElement $element, Feed $feed)
    {
        $item = new GenericItem;
        foreach ($element as $subElement) {
            $method = isset(self::$methodMapping[$subElement->getName()]) ?
                self::$methodMapping[$subElement->getName()] :
                'setFeed'.ucfirst($subElement->getName());

            if ($subElement->getName() == 'author') {
                $item->setFeedAuthor(array('name' => (string)$subElement));

            } else if($subElement->getName() == 'pubDate') {
                $date = \DateTime::createFromFormat(\DateTime::RSS, (string) $subElement);
                $item->setFeedDate($date);

            } else if($subElement->getName() == 'link') {
                $routes = array();
                $routes[0] = array('url' => (string) $subElement);
                $item->setFeedRoutes($routes);

            } elseif (count($subElement) === 0) {
                $item->$method((string)$subElement);

            } else {
                $item->$method($this->extractParam($subElement));
            }
        }

        $feed->add($item);
    }

    /**
     * Set a feed param
     *
     * @param \SimpleXMLElement        $element
     * @param \Nekland\Bundle\FeedBundle\Feed $feed
     * @return void
     */
    protected function setParam(\SimpleXMLElement $element, Feed $feed)
    {
        if (count($element) === 0) {
            $feed->set($element->getName(), (string)$element);
        } else {
            $feed->set($element->getName(), $this->extractParam($element));
        }
    }

    /**
     * Extract array params
     *
     * @param \SimpleXMLElement $element
     * @return array
     */
    protected function extractParam(\SimpleXMLElement $element)
    {
        $param = array();
        foreach ($element as $subElement) {
            if (count($subElement) === 0) {
                $param[$subElement->getName()] = (string)$subElement;
            } else {
                $param[$subElement->getName()] = $this->extractParam($subElement);
            }
        }

        return $param;
    }

    /**
     * @return string (ex: "rss")
     */
    public function getFormat()
    {
        return 'rss';
    }
}


<?php
/*
 * Copyright (c) Arnaud Ligny <arnaud@ligny.org>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace PHPoole\Renderer;

use PHPoole\Renderer\Twig\Extension as TwigExtension;

/**
 * Class Twig.
 */
class Twig implements RendererInterface
{
    /**
     * @var \Twig_Environment
     */
    protected $twig;
    /**
     * @var string
     */
    protected $templatesDir;
    /**
     * @var string
     */
    protected $rendered;

    /**
     * {@inheritdoc}
     */
    public function __construct($templatesPath, $config)
    {
        // load layouts
        $loader = new \Twig_Loader_Filesystem($templatesPath);
        // Twig
        $this->twig = new \Twig_Environment($loader, [
            'debug'            => true,
            'strict_variables' => true,
            'autoescape'       => false,
            'cache'            => false,
            'auto_reload'      => true,
        ]);
        // add extensions
        $this->twig->addExtension(new \Twig_Extension_Debug());
        $this->twig->addExtension(new TwigExtension($config->getOutputPath()));
        $this->twig->addExtension(new \Twig_Extension_StringLoader());
        $this->twig->getExtension('Twig_Extension_Core')->setDateFormat($config->get('site.date.format'));
        $this->twig->getExtension('Twig_Extension_Core')->setTimezone($config->get('site.date.timezone'));
    }

    /**
     * {@inheritdoc}
     */
    public function addGlobal($name, $value)
    {
        $this->twig->addGlobal($name, $value);
    }

    /**
     * {@inheritdoc}
     */
    public function render($template, $variables)
    {
        $this->rendered = $this->twig->render($template, $variables);

        // add generator meta
        if (!preg_match('/<meta name="generator".*/i', $this->rendered)) {
            $meta = '<meta name="generator" content="PHPoole" />';
            $this->rendered = preg_replace('/(|)/i', '$1'."\n\t".$meta, $this->rendered);
        }

        // replace excerpt tag by HTML anchor
        $pattern = '/(.*)()(.*)/i';
        $replacement = '$1$3';
        $this->rendered = preg_replace($pattern, $replacement, $this->rendered);

        return $this->rendered;
    }

    /**
     * {@inheritdoc}
     */
    public function isValid($template)
    {
        try {
            $this->twig->parse($this->twig->tokenize($template));

            return true;
        } catch (\Twig_Error_Syntax $e) {
            return false;
        }
    }
}


<?php
/**
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * http://www.gnu.org/copyleft/gpl.html
 */

class AntiSpoofHooks {
	/**
	 * @param DatabaseUpdater $updater
	 * @return bool
	 */
	public static function asUpdateSchema( DatabaseUpdater $updater ) {
		$updater->addExtensionTable( 'spoofuser',
			__DIR__ . '/../sql/patch-antispoof.' . $updater->getDB()->getType() . '.sql' );
		return true;
	}

	/**
	 * On new account creation, record the username's thing-bob.
	 * Replaces AddNewAccountHook for more modern MediaWiki versions-
	 *
	 * @param User $user
	 * @return bool
	 */
	public static function asLocalUserCreated( $user ) {
		$spoof = new SpoofUser( $user->getName() );
		$spoof->record();
		return true;
	}

	/**
	 * On rename, remove the old entry and add the new
	 * (After a successful user rename)
	 *
	 * @param string $uid
	 * @param string $oldName
	 * @param string $newName
	 * @return bool
	 */
	public static function asAddRenameUserHook( $uid, $oldName, $newName ) {
		$spoof = new SpoofUser( $newName );
		$spoof->update( $oldName );
		return true;
	}

	/**
	 * @param User &$oldUser
	 * @return bool
	 */
	public static function asDeleteAccount( User &$oldUser ) {
		$spoof = new SpoofUser( $oldUser->getName() );
		$spoof->remove();
		return true;
	}
}


<?php
/*
 * Copyright (c) Arnaud Ligny <arnaud@ligny.org>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace PHPoole\Renderer;

use PHPoole\Collection\Page\Page;
use PHPoole\Config;
use PHPoole\Exception\Exception;
use PHPoole\Page\NodeType;
use PHPoole\Util;

/**
 * Class Layout.
 */
class Layout
{
    /**
     * Layout file finder.
     *
     * @param Page   $page
     * @param Config $config
     *
     * @throws Exception
     *
     * @return string
     */
    public function finder(Page $page, Config $config)
    {
        $layout = 'unknown';

        // what layouts could be use for the page?
        $layouts = self::fallback($page);

        // take the first available layout
        foreach ($layouts as $layout) {
            // is it in layouts/ dir?
            if (Util::getFS()->exists($config->getLayoutsPath().'/'.$layout)) {
                return $layout;
            }
            // is it in <theme>/layouts/ dir?
            if ($config->hasTheme()) {
                $themes = $config->getTheme();
                foreach ($themes as $theme) {
                    if (Util::getFS()->exists($config->getThemeDirPath($theme, 'layouts').'/'.$layout)) {
                        return $layout;
                    }
                }
            }
            // is it in res/layouts/ dir?
            if (Util::getFS()->exists($config->getInternalLayoutsPath().'/'.$layout)) {
                return $layout;
            }
        }

        throw new Exception(sprintf("Layout '%s' not found for page '%s'!", $layout, $page->getId()));
    }

    /**
     * Layout fall-back.
     *
     * @param $page
     *
     * @return string[]
     *
     * @see finder()
     */
    protected static function fallback(Page $page)
    {
        // remove redundant '.twig' extension
        $layout = str_replace('.twig', '', $page->getLayout());

        switch ($page->getNodeType()) {
            case NodeType::HOMEPAGE:
                $layouts = [
                    'index.html.twig',
                    '_default/list.html.twig',
                    '_default/page.html.twig',
                ];
                break;
            case NodeType::SECTION:
                $layouts = [
                    // 'section/$section.html.twig',
                    '_default/section.html.twig',
                    '_default/list.html.twig',
                ];
                if ($page->getPathname()) {
                    $section = explode('/', $page->getPathname())[0];
                    $layouts = array_merge(
                        [sprintf('section/%s.html.twig', $section)],
                        $layouts
                    );
                }
                break;
            case NodeType::TAXONOMY:
                $layouts = [
                    // 'taxonomy/$singular.html.twig',
                    '_default/taxonomy.html.twig',
                    '_default/list.html.twig',
                ];
                if ($page->getVariable('singular')) {
                    $layouts = array_merge(
                        [sprintf('taxonomy/%s.html.twig', $page->getVariable('singular'))],
                        $layouts
                    );
                }
                break;
            case NodeType::TERMS:
                $layouts = [
                    // 'taxonomy/$singular.terms.html.twig',
                    '_default/terms.html.twig',
                ];
                if ($page->getVariable('singular')) {
                    $layouts = array_merge(
                        [sprintf('taxonomy/%s.terms.html.twig', $page->getVariable('singular'))],
                        $layouts
                    );
                }
                break;
            default:
                $layouts = [
                    // '$section/$layout.twig',
                    // '$layout.twig',
                    // '$section/page.html.twig',
                    // 'page.html.twig',
                    '_default/page.html.twig',
                ];
                $layouts = array_merge(
                    ['page.html.twig'],
                    $layouts
                );

                if ($page->getSection()) {
                    $layouts = array_merge(
                        [sprintf('%s/page.html.twig', $page->getSection())],
                        $layouts
                    );
                }
                if ($page->getLayout()) {
                    $layouts = array_merge(
                        [sprintf('%s.twig', $layout)],
                        $layouts
                    );
                    if ($page->getSection()) {
                        $layouts = array_merge(
                            [sprintf('%s/%s.twig', $page->getSection(), $layout)],
                            $layouts
                        );
                    }
                }
        }

        return $layouts;
    }
}


<?php
/*
 * Copyright (c) Arnaud Ligny <arnaud@ligny.org>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace PHPoole\Step;

use PHPoole\Util;
use Symfony\Component\Finder\Finder;

/**
 * Copy static directory content to site root.
 */
class CopyStatic extends AbstractStep
{
    /**
     * {@inheritdoc}
     *
     * @throws Exception
     */
    public function init($options)
    {
        // clean before
        Util::getFS()->remove($this->phpoole->getConfig()->getOutputPath());

        $this->process = true;
    }

    /**
     * {@inheritdoc}
     *
     * @throws \PHPoole\Exception\Exception
     */
    public function process()
    {
        $count = 0;

        call_user_func_array($this->phpoole->getMessageCb(), ['COPY', 'Copying static files']);
        // copy <theme>/static/ dir if exists
        if ($this->phpoole->getConfig()->hasTheme()) {
            $themes = array_reverse($this->phpoole->getConfig()->getTheme());
            foreach ($themes as $theme) {
                $themeStaticDir = $this->phpoole->getConfig()->getThemeDirPath($theme, 'static');
                if (Util::getFS()->exists($themeStaticDir)) {
                    $finder = new Finder();
                    $finder->files()->in($themeStaticDir);
                    $count += $finder->count();
                    Util::getFS()->mirror(
                        $themeStaticDir,
                        $this->phpoole->getConfig()->getOutputPath(),
                        null,
                        ['override' => true]
                    );
                }
            }
        }
        // copy static/ dir if exists
        $staticDir = $this->phpoole->getConfig()->getStaticPath();
        if (Util::getFS()->exists($staticDir)) {
            $finder = new Finder();
            $finder->files()->filter(function (\SplFileInfo $file) {
                return !(is_array($this->phpoole->getConfig()->get('static.exclude'))
                    && in_array($file->getBasename(), $this->phpoole->getConfig()->get('static.exclude')));
            })->in($staticDir);
            $count += $finder->count();
            Util::getFS()->mirror(
                $staticDir,
                $this->phpoole->getConfig()->getOutputPath(),
                $finder,
                ['override' => true]
            );
        }
        call_user_func_array($this->phpoole->getMessageCb(), ['COPY_PROGRESS', 'Start copy', 0, $count]);
        call_user_func_array($this->phpoole->getMessageCb(), ['COPY_PROGRESS', 'Files copied', $count, $count]);
    }
}


<?php
/*
 * Copyright (c) Arnaud Ligny <arnaud@ligny.org>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace PHPoole\Converter;

use ParsedownExtra;
use PHPoole\Exception\Exception;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Yaml;

/**
 * Class Converter.
 */
class Converter implements ConverterInterface
{
    /**
     * {@inheritdoc}
     */
    public static function convertFrontmatter($string, $type = 'yaml')
    {
        switch ($type) {
            case 'ini':
                return parse_ini_string($string);
            case 'yaml':
            default:
                try {
                    return Yaml::parse((string) $string);
                } catch (ParseException $e) {
                    throw new Exception($e->getMessage());
                }
        }
    }

    /**
     * {@inheritdoc}
     */
    public static function convertBody($string)
    {
        $parsedown = new ParsedownExtra();

        return $parsedown->text($string);
    }
}


<?php
/*
 * Copyright (c) Arnaud Ligny <arnaud@ligny.org>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace PHPoole\Generator;

use PHPoole\Collection\Collection as PageCollection;
use PHPoole\Collection\Page\Page;
use PHPoole\Converter\Converter;

/**
 * Class Homepage.
 */
class ExternalBody extends AbstractGenerator implements GeneratorInterface
{
    /**
     * {@inheritdoc}
     */
    public function generate(PageCollection $pageCollection, \Closure $messageCallback)
    {
        $generatedPages = new PageCollection();

        $filteredPages = $pageCollection->filter(function (Page $page) {
            return null !== $page->getVariable('external');
        });

        /* @var $page Page */
        foreach ($filteredPages as $page) {
            try {
                $pageContent = file_get_contents($page->getVariable('external'), false);
                $html = (new Converter())
                    ->convertBody($pageContent);
                $page->setHtml($html);

                $generatedPages->add($page);
            } catch (\Exception $e) {
                $error = sprintf('Cannot get contents from %s', $page->getVariable('external'));
                $message = sprintf("Unable to generate '%s': %s", $page->getId(), $error);
                call_user_func_array($messageCallback, ['GENERATE_ERROR', $message]);
            }
        }

        return $generatedPages;
    }
}


<?php
/*
 * Copyright (c) Arnaud Ligny <arnaud@ligny.org>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace PHPoole\Collection\Page;

use Cocur\Slugify\Slugify;
use PHPoole\Collection\Item;
use PHPoole\Page\NodeType;
use PHPoole\Page\Parser;
use PHPoole\Page\VariableTrait;
use Symfony\Component\Finder\SplFileInfo;

/**
 * Class Page.
 */
class Page extends Item
{
    use VariableTrait;

    const SLUGIFY_PATTERN = '/(^\/|[^a-z0-9\/]|-)+/';
    // https://regex101.com/r/tJWUrd/1
    const PREFIX_PATTERN = '^(.*?)(([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])|[0-9]+)(-|_|\.)(.*)$';

    /**
     * @var SplFileInfo
     */
    protected $file;
    /**
     * @var string
     */
    protected $fileExtension;
    /**
     * @var string
     */
    protected $filePath;
    /**
     * @var string
     */
    protected $fileName;
    /**
     * @var string
     */
    protected $fileId;

    /**
     * @var bool
     */
    protected $virtual = false;
    /**
     * @var string
     */
    protected $nodeType;

    /**
     * @var string
     */
    protected $id;
    /**
     * @var string
     */
    protected $pathname;
    /**
     * @var string
     */
    protected $path;
    /**
     * @var string
     */
    protected $name;

    /**
     * @var string
     */
    protected $frontmatter;
    /**
     * @var string
     */
    protected $body;
    /**
     * @var string
     */
    protected $html;

    /**
     * Constructor.
     *
     * @param null|SplFileInfo $file
     */
    public function __construct(SplFileInfo $file = null)
    {
        $this->file = $file;

        if ($this->file instanceof SplFileInfo) {
            // file extension: "md"
            $this->fileExtension = pathinfo($this->file, PATHINFO_EXTENSION);
            // file path: "Blog"
            $this->filePath = str_replace(DIRECTORY_SEPARATOR, '/', $this->file->getRelativePath());
            // file name: "Post 1"
            $this->fileName = basename($this->file->getBasename(), '.'.$this->fileExtension);
            // file id: "Blog/Post 1"
            $this->fileId = ($this->filePath ? $this->filePath.'/' : '')
                .($this->filePath && $this->fileName == 'index' ? '' : $this->fileName);
            /*
             * variables default values
             */
            // id - ie: "blog/post-1"
            $this->id = $this->urlize(self::subPrefix($this->fileId));
            // pathname - ie: "blog/post-1"
            $this->pathname = $this->urlize(self::subPrefix($this->fileId));
            // path - ie: "blog"
            $this->path = $this->urlize($this->filePath);
            // name - ie: "post-1"
            $this->name = $this->urlize(self::subPrefix($this->fileName));
            /*
             * front matter default values
             */
            // title - ie: "Post 1"
            $this->setTitle(self::subPrefix($this->fileName));
            // section - ie: "blog"
            $this->setSection(explode('/', $this->path)[0]);
            // date from file meta
            $this->setDate(filemtime($this->file->getPathname()));
            // file as a prefix?
            if (false !== self::getPrefix($this->fileId)) {
                // prefix is a valid date?
                $isValidDate = function ($date, $format = 'Y-m-d') {
                    $d = \DateTime::createFromFormat($format, $date);

                    return $d && $d->format($format) === $date;
                };
                if ($isValidDate(self::getPrefix($this->fileId))) {
                    $this->setDate(self::getPrefix($this->fileId));
                } else {
                    // prefix is an integer
                    $this->setWeight(self::getPrefix($this->fileId));
                }
            }
            // permalink
            $this->setPermalink($this->pathname);

            parent::__construct($this->id);
        } else {
            $this->virtual = true;

            parent::__construct();
        }
        $this->setVariable('virtual', $this->virtual);
        $this->setVariable('published', true);
        $this->setVariable('content_template', 'page.content.twig');
    }

    /**
     * Return matches array if prefix exist or false.
     *
     * @param $string
     *
     * @return array|bool
     */
    public static function asPrefix($string)
    {
        if (preg_match('/'.self::PREFIX_PATTERN.'/', $string, $matches)) {
            return $matches;
        } else {
            return false;
        }
    }

    /**
     * Return prefix if prefix or false.
     *
     * @param $string
     *
     * @return string|bool
     */
    public static function getPrefix($string)
    {
        if (false !== ($matches = self::asPrefix($string))) {
            return $matches[2];
        }

        return false;
    }

    /**
     * Return string without prefix (if exist).
     *
     * @param $string
     *
     * @return string
     */
    public static function subPrefix($string)
    {
        if (false !== ($matches = self::asPrefix($string))) {
            return $matches[1].$matches[7];
        }

        return $string;
    }

    /**
     * Format string into URL.
     *
     * @param $string
     *
     * @return string
     */
    public static function urlize($string)
    {
        return Slugify::create([
            'regexp' => self::SLUGIFY_PATTERN,
        ])->slugify($string);
    }

    /**
     * Is current page is virtual?
     *
     * @return bool
     */
    public function isVirtual()
    {
        return $this->virtual;
    }

    /**
     * Set node type.
     *
     * @param string $nodeType
     *
     * @return self
     */
    public function setNodeType($nodeType)
    {
        $this->nodeType = new NodeType($nodeType);

        return $this;
    }

    /**
     * Get node type.
     *
     * @return string
     */
    public function getNodeType()
    {
        return $this->nodeType;
    }

    /**
     * Parse file content.
     *
     * @return $this
     */
    public function parse()
    {
        $parser = new Parser($this->file);
        $parsed = $parser->parse();
        $this->frontmatter = $parsed->getFrontmatter();
        $this->body = $parsed->getBody();

        return $this;
    }

    /**
     * Set name.
     *
     * @param $name
     *
     * @return $this
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

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

    /**
     * Set path.
     *
     * @param $path
     *
     * @return $this
     */
    public function setPath($path)
    {
        $this->path = $path;

        return $this;
    }

    /**
     * Get path.
     *
     * @return string
     */
    public function getPath()
    {
        return $this->path;
    }

    /**
     * Set path name.
     *
     * @param string $pathname
     *
     * @return $this
     */
    public function setPathname($pathname)
    {
        $this->pathname = $pathname;

        return $this;
    }

    /**
     * Get path name.
     *
     * @return string
     */
    public function getPathname()
    {
        return $this->pathname;
    }

    /**
     * Set section.
     *
     * @param $section
     *
     * @return $this
     */
    public function setSection($section)
    {
        $this->setVariable('section', $section);

        return $this;
    }

    /**
     * Get section.
     *
     * @return mixed|false
     */
    public function getSection()
    {
        if (empty($this->getVariable('section')) && !empty($this->path)) {
            $this->setSection(explode('/', $this->path)[0]);
        }

        return $this->getVariable('section');
    }

    /**
     * Set title.
     *
     * @param $title
     *
     * @return $this
     */
    public function setTitle($title)
    {
        $this->setVariable('title', $title);

        return $this;
    }

    /**
     * Get title.
     *
     * @return mixed|false
     */
    public function getTitle()
    {
        return $this->getVariable('title');
    }

    /**
     * Set date.
     *
     * @param $date
     *
     * @return $this
     */
    public function setDate($date)
    {
        $this->setVariable('date', $date);

        return $this;
    }

    /**
     * Get Date.
     *
     * @return \DateTime|false
     */
    public function getDate()
    {
        return $this->getVariable('date');
    }

    /**
     * Set weight.
     *
     * @param $int
     *
     * @return $this
     */
    public function setWeight($int)
    {
        $this->setVariable('weight', $int);

        return $this;
    }

    /**
     * Get weight.
     *
     * @return int
     */
    public function getWeight()
    {
        return $this->getVariable('weight');
    }

    /**
     * Set permalink.
     *
     * @param $permalink
     *
     * @return $this
     */
    public function setPermalink($permalink)
    {
        $this->setVariable('permalink', $permalink);

        return $this;
    }

    /**
     * Get permalink.
     *
     * @return mixed|false
     */
    public function getPermalink()
    {
        if (empty($this->getVariable('permalink'))) {
            $this->setPermalink($this->getPathname());
        }

        return $this->getVariable('permalink');
    }

    /**
     * Get frontmatter.
     *
     * @return string
     */
    public function getFrontmatter()
    {
        return $this->frontmatter;
    }

    /**
     * Get body.
     *
     * @return string
     */
    public function getBody()
    {
        return $this->body;
    }

    /**
     * Set HTML.
     *
     * @param string $html
     *
     * @return $this
     */
    public function setHtml($html)
    {
        $this->html = $html;

        return $this;
    }

    /**
     * Get HTML alias.
     *
     * @return string
     */
    public function getContent()
    {
        return $this->html;
    }

    /**
     * Set layout.
     *
     * @param $layout
     *
     * @return $this
     */
    public function setLayout($layout)
    {
        $this->setVariable('layout', $layout);

        return $this;
    }

    /**
     * Get layout.
     *
     * @return mixed|false
     */
    public function getLayout()
    {
        return $this->getVariable('layout');
    }
}


<?php
/*
 * Copyright (c) Arnaud Ligny <arnaud@ligny.org>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace PHPoole;

use PHPoole\Collection\Collection as PageCollection;
use PHPoole\Generator\GeneratorManager;
use Symfony\Component\Finder\Finder;

/**
 * Class Builder.
 */
class Builder
{
    const VERSION = '2.x-dev';
    const VERBOSITY_QUIET = -1;
    const VERBOSITY_NORMAL = 0;
    const VERBOSITY_VERBOSE = 1;
    const VERBOSITY_DEBUG = 2;

    /**
     * Library version.
     *
     * @var string
     */
    protected $version;
    /**
     * Steps that are processed by build().
     *
     * @var array
     *
     * @see build()
     */
    protected $steps = [
        'PHPoole\Step\ImportConfig',
        'PHPoole\Step\LocateContent',
        'PHPoole\Step\CreatePages',
        'PHPoole\Step\ConvertPages',
        'PHPoole\Step\GeneratePages',
        'PHPoole\Step\GenerateMenus',
        'PHPoole\Step\CopyStatic',
        'PHPoole\Step\RenderPages',
        'PHPoole\Step\SavePages',
    ];
    /**
     * Config.
     *
     * @var Config
     */
    protected $config;
    /**
     * Content iterator.
     *
     * @var Finder
     */
    protected $content;
    /**
     * Pages collection.
     *
     * @var PageCollection
     */
    protected $pages;
    /**
     * Collection of site menus.
     *
     * @var Collection\Menu\Collection
     */
    protected $menus;
    /**
     * Twig renderer.
     *
     * @var Renderer\Twig
     */
    protected $renderer;
    /**
     * @var \Closure
     */
    protected $messageCallback;
    /**
     * @var GeneratorManager
     */
    protected $generatorManager;
    /**
     * @var array
     */
    protected $log;
    /**
     * @var array
     */
    protected $options;

    /**
     * Builder constructor.
     *
     * @param Config|array|null $config
     * @param \Closure|null     $messageCallback
     */
    public function __construct($config = null, \Closure $messageCallback = null)
    {
        $this->setConfig($config)
            ->setSourceDir(null)
            ->setDestinationDir(null);
        $this->setMessageCallback($messageCallback);
    }

    /**
     * Creates a new Builder instance.
     *
     * @return Builder
     */
    public static function create()
    {
        $class = new \ReflectionClass(get_called_class());

        return $class->newInstanceArgs(func_get_args());
    }

    /**
     * Set config.
     *
     * @param Config|array|null $config
     *
     * @return $this
     */
    public function setConfig($config)
    {
        if (!$config instanceof Config) {
            $config = new Config($config);
        }
        if ($this->config !== $config) {
            $this->config = $config;
        }

        return $this;
    }

    /**
     * @return Config
     */
    public function getConfig()
    {
        return $this->config;
    }

    /**
     * Config::setSourceDir alias.
     *
     * @param $sourceDir
     *
     * @return $this
     */
    public function setSourceDir($sourceDir)
    {
        $this->config->setSourceDir($sourceDir);

        return $this;
    }

    /**
     * Config::setDestinationDir alias.
     *
     * @param $destinationDir
     *
     * @return $this
     */
    public function setDestinationDir($destinationDir)
    {
        $this->config->setDestinationDir($destinationDir);

        return $this;
    }

    /**
     * @param $content
     */
    public function setContent($content)
    {
        $this->content = $content;
    }

    /**
     * @return Finder
     */
    public function getContent()
    {
        return $this->content;
    }

    /**
     * @param $pages
     */
    public function setPages($pages)
    {
        $this->pages = $pages;
    }

    /**
     * @return PageCollection
     */
    public function getPages()
    {
        return $this->pages;
    }

    /**
     * @param $menus
     */
    public function setMenus($menus)
    {
        $this->menus = $menus;
    }

    /**
     * @return Collection\Menu\Collection
     */
    public function getMenus()
    {
        return $this->menus;
    }

    /**
     * @param \Closure|null $messageCallback
     */
    public function setMessageCallback($messageCallback = null)
    {
        if ($messageCallback === null) {
            $messageCallback = function ($code, $message = '', $itemsCount = 0, $itemsMax = 0) {
                switch ($code) {
                    case 'CONFIG':
                    case 'LOCATE':
                    case 'CREATE':
                    case 'CONVERT':
                    case 'GENERATE':
                    case 'MENU':
                    case 'COPY':
                    case 'RENDER':
                    case 'SAVE':
                    case 'TIME':
                        $log = sprintf("%s\n", $message);
                        $this->addLog($log);
                        break;
                    case 'CONFIG_PROGRESS':
                    case 'LOCATE_PROGRESS':
                    case 'CREATE_PROGRESS':
                    case 'CONVERT_PROGRESS':
                    case 'GENERATE_PROGRESS':
                    case 'MENU_PROGRESS':
                    case 'COPY_PROGRESS':
                    case 'RENDER_PROGRESS':
                    case 'SAVE_PROGRESS':
                        if ($itemsCount > 0) {
                            $log = sprintf("(%u/%u) %s\n", $itemsCount, $itemsMax, $message);
                            $this->addLog($log, 1);
                        } else {
                            $log = sprintf("%s\n", $message);
                            $this->addLog($log, 1);
                        }
                        break;
                    case 'LOCATE_ERROR':
                    case 'CREATE_ERROR':
                    case 'CONVERT_ERROR':
                    case 'GENERATE_ERROR':
                    case 'MENU_ERROR':
                    case 'COPY_ERROR':
                    case 'RENDER_ERROR':
                    case 'SAVE_ERROR':
                        $log = sprintf(">> %s\n", $message);
                        $this->addLog($log);
                        break;
                }
            };
        }
        $this->messageCallback = $messageCallback;
    }

    /**
     * @return \Closure
     */
    public function getMessageCb()
    {
        return $this->messageCallback;
    }

    /**
     * @param $renderer
     */
    public function setRenderer($renderer)
    {
        $this->renderer = $renderer;
    }

    /**
     * @return Renderer\Twig
     */
    public function getRenderer()
    {
        return $this->renderer;
    }

    /**
     * @param string $log
     * @param int    $type
     *
     * @return array|null
     */
    public function addLog($log, $type = 0)
    {
        $this->log[] = [
            'type' => $type,
            'log'  => $log,
        ];

        return $this->getLog($type);
    }

    /**
     * @param int $type
     *
     * @return array|null
     */
    public function getLog($type = 0)
    {
        if (is_array($this->log)) {
            return array_filter($this->log, function ($key) use ($type) {
                return $key['type'] <= $type;
            });
        }
    }

    /**
     * @param int $type
     *
     * Display $log string.
     */
    public function showLog($type = 0)
    {
        if ($log = $this->getLog($type)) {
            foreach ($log as $value) {
                printf('%s', $value['log']);
            }
        }
    }

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

    /**
     * Builds a new website.
     *
     * @param array $options
     *
     * @return $this
     */
    public function build($options)
    {
        // backward compatibility
        if ($options === true) {
            $options['verbosity'] = self::VERBOSITY_VERBOSE;
        }
        $this->options = array_merge([
            'verbosity' => self::VERBOSITY_NORMAL, // -1: quiet, 0: normal, 1: verbose, 2: debug
            'drafts'    => false, // build drafts or not
            'dry-run'   => false, // if dry-run is true, generated files are not saved
        ], $options);

        $steps = [];
        // init...
        foreach ($this->steps as $step) {
            /* @var $stepClass Step\StepInterface */
            $stepClass = new $step($this);
            $stepClass->init($this->options);
            $steps[] = $stepClass;
        }
        $this->steps = $steps;
        // ... and process!
        foreach ($this->steps as $step) {
            /* @var $step Step\StepInterface */
            $step->runProcess();
        }
        // show process time
        call_user_func_array($this->messageCallback, [
            'TIME',
            sprintf('Built in %ss', round(microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'], 2)),
        ]);
        // show log
        $this->showLog($this->options['verbosity']);

        return $this;
    }

    /**
     * Return version.
     *
     * @return string
     */
    public function getVersion()
    {
        if (!isset($this->version)) {
            try {
                $this->version = @file_get_contents(__DIR__.'/../VERSION');
                if ($this->version === false) {
                    throw new \Exception('Can\'t get version file!');
                }
            } catch (\Exception $e) {
                $this->version = self::VERSION;
            }
        }

        return $this->version;
    }
}


<?php
/*
 * Copyright (c) Arnaud Ligny <arnaud@ligny.org>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace PHPoole\Renderer\Twig;

use Cocur\Slugify\Bridge\Twig\SlugifyExtension;
use Cocur\Slugify\Slugify;
use Leafo\ScssPhp\Compiler;
use MatthiasMullie\Minify;
use PHPoole\Collection\Collection;
use PHPoole\Collection\CollectionInterface;
use PHPoole\Collection\Page\Page;
use PHPoole\Exception\Exception;
use Symfony\Component\Filesystem\Filesystem;

/**
 * Class Twig\Extension.
 */
class Extension extends SlugifyExtension
{
    /* @var string */
    protected $destPath;
    /**
     * @var Filesystem
     */
    protected $fileSystem;

    /**
     * Constructor.
     *
     * @param string $destPath
     */
    public function __construct($destPath)
    {
        $this->destPath = $destPath;
        parent::__construct(Slugify::create([
            'regexp' => Page::SLUGIFY_PATTERN,
        ]));

        $this->fileSystem = new Filesystem();
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'phpoole';
    }

    /**
     * {@inheritdoc}
     */
    public function getFilters()
    {
        return [
            new \Twig_SimpleFilter('filterBySection', [$this, 'filterBySection']),
            new \Twig_SimpleFilter('filterBy', [$this, 'filterBy']),
            new \Twig_SimpleFilter('sortByTitle', [$this, 'sortByTitle']),
            new \Twig_SimpleFilter('sortByWeight', [$this, 'sortByWeight']),
            new \Twig_SimpleFilter('sortByDate', [$this, 'sortByDate']),
            new \Twig_SimpleFilter('urlize', [$this, 'slugifyFilter']),
            new \Twig_SimpleFilter('minifyCSS', [$this, 'minifyCss']),
            new \Twig_SimpleFilter('minifyJS', [$this, 'minifyJs']),
            new \Twig_SimpleFilter('SCSStoCSS', [$this, 'scssToCss']),
            new \Twig_SimpleFilter('excerpt', [$this, 'excerpt']),
            new \Twig_SimpleFilter('excerptHtml', [$this, 'excerptHtml']),
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function getFunctions()
    {
        return [
            new \Twig_SimpleFunction('url', [$this, 'createUrl'], ['needs_environment' => true]),
            new \Twig_SimpleFunction('minify', [$this, 'minify']),
            new \Twig_SimpleFunction('readtime', [$this, 'readtime']),
            new \Twig_SimpleFunction('toCSS', [$this, 'toCss']),
            new \Twig_SimpleFunction('hash', [$this, 'hashFile']),
        ];
    }

    /**
     * Filter by section.
     *
     * @param \PHPoole\Page\Collection $pages
     * @param string                   $section
     *
     * @return array
     */
    public function filterBySection($pages, $section)
    {
        return $this->filterBy($pages, 'section', $section);
    }

    /**
     * Filter by variable.
     *
     * @param \PHPoole\Page\Collection $pages
     * @param string                   $variable
     * @param string                   $value
     *
     * @throws Exception
     *
     * @return array
     */
    public function filterBy($pages, $variable, $value)
    {
        $filteredPages = $pages->filter(function (Page $page) use ($variable, $value) {
            // filter virtual pages in section
            if ($variable == 'section' && $page->getVariable('virtual')) {
                return false;
            }
            // dedicated getter?
            $method = 'get'.ucfirst($variable);
            if (method_exists($page, $method) && $page->$method() == $value) {
                return true;
            }
            if ($page->getVariable($variable) == $value) {
                return true;
            }
        });

        return $filteredPages;
    }

    /**
     * Sort by title.
     *
     * @param $array|CollectionInterface
     *
     * @return mixed
     */
    public function sortByTitle($array)
    {
        if ($array instanceof Collection) {
            $array = $array->toArray();
        }
        if (is_array($array)) {
            array_multisort(array_keys($array), SORT_NATURAL | SORT_FLAG_CASE, $array);
        }

        return $array;
    }

    /**
     * Sort by weight.
     *
     * @param $array|CollectionInterface
     *
     * @return mixed
     */
    public function sortByWeight($array)
    {
        $callback = function ($a, $b) {
            if (!isset($a['weight'])) {
                return 1;
            }
            if (!isset($b['weight'])) {
                return -1;
            }
            if ($a['weight'] == $b['weight']) {
                return 0;
            }

            return ($a['weight'] < $b['weight']) ? -1 : 1;
        };

        if ($array instanceof Collection) {
            $array = $array->toArray();
        }
        if (is_array($array)) {
            usort($array, $callback);
        }

        return $array;
    }

    /**
     * Sort by date.
     *
     * @param $array|CollectionInterface
     *
     * @return mixed
     */
    public function sortByDate($array)
    {
        $callback = function ($a, $b) {
            if (!isset($a['date'])) {
                return -1;
            }
            if (!isset($b['date'])) {
                return 1;
            }
            if ($a['date'] == $b['date']) {
                return 0;
            }

            return ($a['date'] > $b['date']) ? -1 : 1;
        };

        if ($array instanceof Collection) {
            $array = $array->toArray();
        }
        if (is_array($array)) {
            usort($array, $callback);
        }

        return $array;
    }

    /**
     * Create an URL.
     *
     * $options[
     *     'canonical' => null,
     *     'addhash'   => true,
     * ];
     *
     * @param \Twig_Environment         $env
     * @param string|\PHPoole\Page\Page $value
     * @param array|null                $options
     *
     * @return string
     */
    public function createUrl(\Twig_Environment $env, $value = null, $options = null)
    {
        $base = '';
        $baseurl = $env->getGlobals()['site']['baseurl'];
        $hash = md5($env->getGlobals()['site']['time']);
        $canonical = null;
        $addhash = true;

        if (isset($options['canonical'])) {
            $canonical = $options['canonical'];
        }
        if (is_bool($options)) { // backward compatibility
            $canonical = $options;
        }
        if (isset($options['addhash'])) {
            $addhash = $options['addhash'];
        }

        if ($env->getGlobals()['site']['canonicalurl'] === true || $canonical === true) {
            $base = rtrim($baseurl, '/');
        }
        if ($canonical === false) {
            $base = '';
        }

        if ($value instanceof Page) {
            $value = $value->getPermalink();
            if (false !== strpos($value, '.')) { // file URL (with a dot for extension)
                $url = $base.'/'.ltrim($value, '/');
            } else {
                $url = $base.'/'.ltrim(rtrim($value, '/').'/', '/');
            }
        } else {
            if (preg_match('~^(?:f|ht)tps?://~i', $value)) { // external URL
                $url = $value;
            } elseif (false !== strpos($value, '.')) { // file URL (with a dot for extension)
                $url = $base.'/'.ltrim($value, '/');
                if ($addhash) {
                    $url .= '?'.$hash;
                }
            } else {
                $value = $this->slugifyFilter($value);
                $url = $base.'/'.ltrim(rtrim($value, '/').'/', '/');
            }
        }

        return $url;
    }

    /**
     * Minify a CSS or a JS file.
     *
     * @param string $path
     *
     * @throws Exception
     *
     * @return string
     */
    public function minify($path)
    {
        $filePath = $this->destPath.'/'.$path;
        if (is_file($filePath)) {
            $extension = (new \SplFileInfo($filePath))->getExtension();
            switch ($extension) {
                case 'css':
                    $minifier = new Minify\CSS($filePath);
                    break;
                case 'js':
                    $minifier = new Minify\JS($filePath);
                    break;
                default:
                    throw new Exception(sprintf("File '%s' should be a '.css' or a '.js'!", $path));
            }
            $minifier->minify($filePath);

            return $path;
        }

        throw new Exception(sprintf("File '%s' doesn't exist!", $path));
    }

    /**
     * Minify CSS.
     *
     * @param $value
     *
     * @return string
     */
    public function minifyCss($value)
    {
        $minifier = new Minify\CSS($value);

        return $minifier->minify();
    }

    /**
     * Minify JS.
     *
     * @param $value
     *
     * @return string
     */
    public function minifyJs($value)
    {
        $minifier = new Minify\JS($value);

        return $minifier->minify();
    }

    /**
     * Compile style file to CSS.
     *
     * @param string $path
     *
     * @throws Exception
     *
     * @return string
     */
    public function toCss($path)
    {
        $filePath = $this->destPath.'/'.$path;
        $subPath = substr($path, 0, strrpos($path, '/'));

        if (is_file($filePath)) {
            $extension = (new \SplFileInfo($filePath))->getExtension();
            switch ($extension) {
                case 'scss':
                    $scssPhp = new Compiler();
                    $scssPhp->setImportPaths($this->destPath.'/'.$subPath);
                    $targetPath = preg_replace('/scss/m', 'css', $path);

                    // compile if target file doesn't exists
                    if (!$this->fileSystem->exists($this->destPath.'/'.$targetPath)) {
                        $scss = file_get_contents($filePath);
                        $css = $scssPhp->compile($scss);
                        $this->fileSystem->dumpFile($this->destPath.'/'.$targetPath, $css);
                    }

                    return $targetPath;
                    break;
                default:
                    throw new Exception(sprintf("File '%s' should be a '.scss'!", $path));
            }
        }

        throw new Exception(sprintf("File '%s' doesn't exist!", $path));
    }

    /**
     * Compile SCSS string to CSS.
     *
     * @param $value
     *
     * @return string
     */
    public function scssToCss($value)
    {
        $scss = new Compiler();

        return $scss->compile($value);
    }

    /**
     * Read $lenght first characters of a string and add a suffix.
     *
     * @param $string
     * @param int    $length
     * @param string $suffix
     *
     * @return string
     */
    public function excerpt($string, $length = 450, $suffix = ' …')
    {
        $string = str_replace('
', '

', $string);
        $string = trim(strip_tags($string, '
'), '
');
        if (mb_strlen($string) > $length) {
            $string = mb_substr($string, 0, $length);
            $string .= $suffix;
        }

        return $string;
    }

    /**
     * Read characters before ''.
     *
     * @param $string
     *
     * @return string
     */
    public function excerptHtml($string)
    {
        // https://regex101.com/r/mA2mG0/3
        $pattern = '^(.*)[\n\r\s]*[\n\r\s]*(.*)$';
        preg_match(
            '/'.$pattern.'/s',
            $string,
            $matches
        );
        if (!$matches) {
            return $string;
        }

        return trim($matches[1]);
    }

    /**
     * Calculate estimated time to read a text.
     *
     * @param $text
     *
     * @return float|string
     */
    public function readtime($text)
    {
        $words = str_word_count(strip_tags($text));
        $min = floor($words / 200);
        if ($min === 0) {
            return '1';
        }

        return $min;
    }

    /**
     * Hash file with sha384.
     *
     * @param string $path
     *
     * @return string|null
     */
    public function hashFile($path)
    {
        if (is_file($filePath = $this->destPath.'/'.$path)) {
            return sprintf('sha384-%s', base64_encode(hash_file('sha384', $filePath, true)));
        }
    }
}


<?php
/*
 * Copyright (c) Arnaud Ligny <arnaud@ligny.org>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace PHPoole\Generator;

use PHPoole\Collection\Collection as PageCollection;
use PHPoole\Collection\Page\Page;

/**
 * Class TitleReplace.
 */
class Redirect extends AbstractGenerator implements GeneratorInterface
{
    /**
     * {@inheritdoc}
     */
    public function generate(PageCollection $pageCollection, \Closure $messageCallback)
    {
        $generatedPages = new PageCollection();

        $filteredPages = $pageCollection->filter(function (Page $page) {
            return null !== $page->getVariable('redirect');
        });

        /* @var $page Page */
        foreach ($filteredPages as $page) {
            $alteredPage = clone $page;
            $alteredPage->setLayout('redirect.html');
            $alteredPage->setVariable('destination', $page->getVariable('redirect'));
            $generatedPages->add($alteredPage);
        }

        return $generatedPages;
    }
}


<?php
/*
* This file is part of the job-bundle package.
*
* (c) Hannes Schulz <hannes.schulz@aboutcoders.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Abc\Bundle\SchedulerBundle\Validator\Constraints;

use Abc\Bundle\SchedulerBundle\Model\ScheduleInterface;
use Abc\Bundle\SchedulerBundle\Validator\Constraints as AssertSchedule;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\ConstraintValidator;

/**
 * @author Hannes Schulz <hannes.schulz@aboutcoders.com>
 */
class ScheduleValidator extends ConstraintValidator
{
    /**
     * {@inheritdoc}
     */
    public function validate($value, Constraint $constraint)
    {
        if (null === $value) {
            return;
        }

        if (!$value instanceof ScheduleInterface) {
            throw new \InvalidArgumentException('The value must be an instance of ' . ScheduleInterface::class);
        }

        if(null == $value->getType()) {
            return;
        }

        $this->context->getValidator()
            ->inContext($this->context)
            ->atPath('expression')
            ->validate($value->getExpression(), new AssertSchedule\Expression(['type' => $value->getType()]));
    }
}


<?php
/*
 * Copyright (c) Arnaud Ligny <arnaud@ligny.org>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace PHPoole\Page;

use Symfony\Component\Finder\SplFileInfo;

/**
 * Class Parser.
 */
class Parser
{
    // https://regex101.com/r/xH7cL3/1
    const PATTERN = '^\s*(?:<!--|---|\+++){1}[\n\r\s]*(.*?)[\n\r\s]*(?:-->|---|\+++){1}[\s\n\r]*(.*)$';
    /**
     * @var SplFileInfo
     */
    protected $file;
    /**
     * @var string
     */
    protected $frontmatter;
    /**
     * @var string
     */
    protected $body;

    /**
     * Constructor.
     *
     * @param SplFileInfo $file
     */
    public function __construct(SplFileInfo $file)
    {
        $this->file = $file;
    }

    /**
     * Parse the contents of the file.
     *
     * Example:
     *
     *     ---
     *     title: Title
     *     date: 2016-07-29
     *     ---
     *     Lorem Ipsum.
     *
     * @throws \RuntimeException
     *
     * @return $this
     */
    public function parse()
    {
        if ($this->file->isFile()) {
            if (!$this->file->isReadable()) {
                throw new \RuntimeException('Cannot read file');
            }
            // parse front matter
            preg_match(
                '/'.self::PATTERN.'/s',
                $this->file->getContents(),
                $matches
            );
            // if not front matter, set body only
            if (!$matches) {
                $this->body = $this->file->getContents();

                return $this;
            }
            $this->frontmatter = trim($matches[1]);
            $this->body = trim($matches[2]);
        }

        return $this;
    }

    /**
     * Get frontmatter.
     *
     * @return string
     */
    public function getFrontmatter()
    {
        return $this->frontmatter;
    }

    /**
     * Get body.
     *
     * @return string
     */
    public function getBody()
    {
        return $this->body;
    }
}


<?php
/*
 * Copyright (c) Arnaud Ligny <arnaud@ligny.org>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace PHPoole\Step;

use PHPoole\Collection\Menu\Collection as MenusCollection;
use PHPoole\Collection\Menu\Entry;

/**
 * Generates menus.
 */
class GenerateMenus extends AbstractStep
{
    /**
     * {@inheritdoc}
     */
    public function process()
    {
        call_user_func_array($this->phpoole->getMessageCb(), ['MENU', 'Generating menus']);
        $count = 0;
        $this->phpoole->setMenus(new MenusCollection());
        $this->collectPages();

        /*
         * Removing/adding/replacing menus entries from config array
         * ie:
         * ['site' => [
         *     'menu' => [
         *         'main' => [
         *             'test' => [
         *                 'id'     => 'test',
         *                 'name'   => 'Test website',
         *                 'url'    => 'http://test.org',
         *                 'weight' => 999,
         *             ],
         *         ],
         *     ],
         * ]]
         */
        if (!empty($this->phpoole->getConfig()->get('site.menu'))) {
            foreach ($this->phpoole->getConfig()->get('site.menu') as $name => $entry) {
                /* @var $menu \PHPoole\Collection\Menu\Menu */
                $menu = $this->phpoole->getMenus()->get($name);
                foreach ($entry as $property) {
                    // remove disable entries
                    if (isset($property['disabled']) && $property['disabled']) {
                        if (isset($property['id']) && $menu->has($property['id'])) {
                            $menu->remove($property['id']);
                        }
                        continue;
                    }
                    // add new entries
                    $item = (new Entry($property['id']))
                        ->setName($property['name'])
                        ->setUrl($property['url'])
                        ->setWeight($property['weight']);
                    $menu->add($item);
                    $count++;
                }
            }
        }
        if ($count) {
            call_user_func_array($this->phpoole->getMessageCb(), ['MENU_PROGRESS', 'Start generating', 0, $count]);
            call_user_func_array($this->phpoole->getMessageCb(), ['MENU_PROGRESS', 'Menus generated', $count, $count]);
        } else {
            call_user_func_array($this->phpoole->getMessageCb(), ['MENU_PROGRESS', 'No menu']);
        }
    }

    /**
     * Collects pages with menu entry.
     */
    protected function collectPages()
    {
        foreach ($this->phpoole->getPages() as $page) {
            /* @var $page \PHPoole\Collection\Page\Page */
            if (!empty($page['menu'])) {
                /*
                 * Single case
                 * ie:
                 * menu: main
                 */
                if (is_string($page['menu'])) {
                    $item = (new Entry($page->getId()))
                        ->setName($page->getTitle())
                        ->setUrl($page->getPermalink());
                    /* @var $menu \PHPoole\Collection\Menu\Menu */
                    $menu = $this->phpoole->getMenus()->get($page['menu']);
                    $menu->add($item);
                } else {
                    /*
                     * Multiple case
                     * ie:
                     * menu:
                     *     main:
                     *         weight: 1000
                     *     other
                     */
                    if (is_array($page['menu'])) {
                        foreach ($page['menu'] as $name => $value) {
                            $item = (new Entry($page->getId()))
                                ->setName($page->getTitle())
                                ->setUrl($page->getPermalink())
                                ->setWeight($value['weight']);
                            /* @var $menu \PHPoole\Collection\Menu\Menu */
                            $menu = $this->phpoole->getMenus()->get($name);
                            $menu->add($item);
                        }
                    }
                }
            }
        }
    }
}


<?php
/*
 * Copyright (c) Arnaud Ligny <arnaud@ligny.org>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace PHPoole\Step;

use PHPoole\Exception\Exception;
use Symfony\Component\Finder\Finder;

/**
 * Locates content.
 */
class LocateContent extends AbstractStep
{
    /**
     * {@inheritdoc}
     *
     * @throws Exception
     */
    public function init($options)
    {
        if (!is_dir($this->phpoole->getConfig()->getContentPath())) {
            throw new Exception(sprintf('%s not found!', $this->phpoole->getConfig()->getContentPath()));
        }

        $this->process = true;
    }

    /**
     * {@inheritdoc}
     */
    public function process()
    {
        call_user_func_array($this->phpoole->getMessageCb(), ['LOCATE', 'Loading content']);

        try {
            $content = Finder::create()
                ->files()
                ->in($this->phpoole->getConfig()->getContentPath())
                ->name('/\.('.implode('|', $this->phpoole->getConfig()->get('content.ext')).')$/');
            if (!$content instanceof Finder) {
                throw new Exception(__FUNCTION__.': result must be an instance of Symfony\Component\Finder.');
            }
            $count = $content->count();
            call_user_func_array($this->phpoole->getMessageCb(), ['LOCATE_PROGRESS', 'Start locating', 0, $count]);
            $this->phpoole->setContent($content);
            call_user_func_array($this->phpoole->getMessageCb(), ['LOCATE_PROGRESS', 'Files loaded', $count, $count]);
        } catch (Exception $e) {
            echo $e->getMessage()."\n";
        }
    }
}


<?php

namespace Nekland\Bundle\FeedBundle\Factory;

use Symfony\Component\DependencyInjection\ContainerAware;

use Nekland\Bundle\FeedBundle\Feed;
use Nekland\Bundle\FeedBundle\Renderer\RendererInterface;
use Nekland\Bundle\FeedBundle\Loader\LoaderInterface;

/**
 * This class represent an agnostic feed, Atom, Rss, etc..
 *
 * @throws \InvalidArgumentException
 * @author Yohan Giarelli <yohan@giarelli.org>
 * @author Nek' <nek.dev+github@gmail.com>
 */
class FeedFactory extends ContainerAware
{
    /**
     * Will contains all feeds.
     * @var array|Feed
     */
    protected $feeds;

    /**
     * @var array
     */
    protected $config;

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

    /**
     * @param array $config
     */
    public function setConfig(array $config)
    {
        $this->config = $config;
    }

    /**
     * @param $feed
     * @return bool true if the feed exists
     */
    public function has($feed)
    {
        return isset($this->config['feeds'][$feed]);
    }

    /**
     * Renders a feed
     *
     * @param $feed
     * @param $rendererName
     *
     * @return string
     */
    public function render($feed, $rendererName='rss')
    {
        $renderer = $this->getRenderer($rendererName);

        return $renderer->render($this->get($feed));
    }

    /**
     * Loads a feed
     *
     * @param $feedName
     * @param $loaderName
     * @return Feed
     */
    public function load($feedName, $loaderName = 'rss_file')
    {
        $loader = $this->getLoader($loaderName);
        $feed = $this->get($feedName);
        $loadedFeed = $loader->load($feed->getFilename($loader->getFormat()));

        return $this->feeds[$feedName] = $feed->merge($loadedFeed);
    }

    /**
     * @throws \InvalidArgumentException
     * @param $feed
     * @return Feed
     */
    public function get($feed)
    {
        if(!$this->has($feed)) {
            throw new \InvalidArgumentException('The required feed is not defined. Check your configuration.');
        }

        if(!isset($this->feeds[$feed])) {
            $this->feeds[$feed] = new Feed($this->config['feeds'][$feed]);
        }

        return $this->feeds[$feed];
    }

    /**
     * @param $name
     *
     * @return RendererInterface
     */
    protected function getRenderer($name)
    {
        if (isset($this->config['renderers'][$name])) {
            return $this->container->get($this->config['renderers'][$name]['id']);
        }

        throw new \InvalidArgumentException('Renderer '.$name.' doesn\'t exists');
    }

    /**
     * @param $name
     * @return LoaderInterface
     */
    protected function getLoader($name)
    {
        if (isset($this->config['loaders'][$name])) {
            return $this->container->get($this->config['loaders'][$name]['id']);
        }

        throw new \InvalidArgumentException('Loader '.$name.' doesn\'t exists');
    }

}


<?php
/*
 * Copyright (c) Arnaud Ligny <arnaud@ligny.org>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace PHPoole\Collection\Taxonomy;

use PHPoole\Collection\Collection as PHPooleCollection;
use PHPoole\Collection\ItemInterface;

/**
 * Class Vocabulary.
 */
class Vocabulary extends PHPooleCollection implements ItemInterface
{
    /**
     * Adds term to a Vocabulary collection.
     * {@inheritdoc}
     */
    public function add(ItemInterface $item)
    {
        if ($this->has($item->getId())) {
            // return if already exists
            return $this;
        }
        $this->items[$item->getId()] = $item;

        return $this;
    }
}


<?php

namespace Shieldfy\Sniffer;

use Closure;

class Sniffer
{
    protected $types = [];

    /**
     * Detect constructor.
     */
    public function __construct()
    {
        $this->types = [
            'number'    => \Shieldfy\Sniffer\Types\NumberType::class,
            'string'    => \Shieldfy\Sniffer\Types\StringType::class,
            'json'      => \Shieldfy\Sniffer\Types\JsonType::class,
            'serialize' => \Shieldfy\Sniffer\Types\SerializeType::class,
        ];
    }

    /**
     * Defines which types to use to overwrite the default ones.
     *
     * @param array $types
     */
    public function overwrite(array $types)
    {
        $this->types = $types;

        return $this;
    }

    /**
     * Register new type on the runtime.
     *
     * @param string $name
     * @param string $class
     */
    public function register($name, $class)
    {
        $this->types[$name] = $class;

        return $this;
    }

    /**
     * Start sniffing the content.
     *
     * @param mixed $input
     *
     * @return $type
     */
    public function sniff($input)
    {
        if (is_array($input)) {
            return $this->sniffAll($input);
        }

        return $this->run($input);
    }

    /**
     * test against particular type.
     *
     * @param mixed $input
     *
     * @return bool
     */
    public function is($input, $type)
    {
        if (!isset($this->types[$type])) {
            throw new \Exception('Type Not found use one of supported types ( '.implode(' , ', array_keys($this->types)).' )');
        }
        $typeClass = $this->types[$type];

        return (new $typeClass())->sniff($input);
    }

    /**
     * Start Sniffing array.
     *
     * @param array $inputs
     *
     * @return array $result
     */
    private function sniffAll(array $inputs)
    {
        $result = [];
        foreach ($inputs as $key => $input):
            $result[$key] = $this->run($input);
        endforeach;

        return $result;
    }

    /**
     * Run the tests on the input.
     *
     * @param mixed $input
     *
     * @return mixed result
     */
    private function run($input)
    {
        foreach ($this->types as $name => $typeClass):

            //check if it custom type closure
            if (is_object($typeClass) && $typeClass instanceof Closure) {
                if ($typeClass($input) === true) {
                    return $name;
                }
                continue;
            }

        if ((new $typeClass())->sniff($input) === true) {
            return $name;
        }

        endforeach;
        //nothing captuared
        return 'unknown';
    }
}


<?php
/*
* This file is part of the scheduler-bundle package.
*
* (c) Hannes Schulz <hannes.schulz@aboutcoders.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Abc\Bundle\SchedulerBundle\Doctrine;

use Abc\Bundle\SchedulerBundle\Model\ScheduleInterface;
use Abc\Bundle\SchedulerBundle\Model\ScheduleManager as BaseScheduleManager;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\Common\Persistence\ObjectRepository;

/**
 *  ScheduleManager manages doctrine entities.
 *
 * @author Hannes Schulz <hannes.schulz@aboutcoders.com>
 */
class ScheduleManager extends BaseScheduleManager
{
    /**
     * @var string
     */
    protected $class;

    /**
     * @var ObjectManager
     */
    protected $objectManager;

    /**
     * @var ObjectRepository
     */
    protected $repository;

    /**
     * @param ObjectManager $om
     * @param string        $class
     */
    public function __construct(ObjectManager $om, $class)
    {
        $this->objectManager = $om;
        $this->repository    = $om->getRepository($class);

        $metadata    = $om->getClassMetadata($class);
        $this->class = $metadata->getName();
    }

    /**
     * Updates a schedule
     *
     * @param ScheduleInterface $schedule
     * @param Boolean           $andFlush Whether to flush the changes (default true)
     */
    public function save(ScheduleInterface $schedule, $andFlush = true)
    {
        $this->objectManager->persist($schedule);
        if ($andFlush)
        {
            $this->objectManager->flush();
        }
    }

    /**
     * {@inheritDoc}
     */
    public function getClass()
    {
        return $this->class;
    }

    /**
     * {@inheritDoc}
     */
    public function findSchedules($limit = null, $offset = null)
    {
        return $this->repository->findBy(array(), array(), $limit, $offset);
    }
}


<?php
/*
* This file is part of the scheduler-bundle package.
*
* (c) Hannes Schulz <hannes.schulz@aboutcoders.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Abc\Bundle\SchedulerBundle\Validator\Constraints;

use Abc\Bundle\SchedulerBundle\Validator\ConstraintRegistry;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;

/**
 * @author Hannes Schulz <hannes.schulz@aboutcoders.com>
 */
class ExpressionValidator extends ConstraintValidator
{
    /**
     * @var ConstraintRegistry
     */
    private $registry;

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

    /**
     * {@inheritdoc}
     */
    public function validate($value, Constraint $constraint)
    {
        if (null === $value) {
            return;
        }

        if (!$constraint->type) {
            throw new ConstraintDefinitionException('"type" must be specified on constraint Expression');
        }

        if(!$this->registry->has($constraint->type)) {
            return;
        }

        $this->context->getValidator()
            ->inContext($this->context)
            ->validate($value, $this->registry->get($constraint->type));
    }
}


<?php
/*
* This file is part of the scheduler-bundle package.
*
* (c) Hannes Schulz <hannes.schulz@aboutcoders.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Abc\Bundle\SchedulerBundle\Iterator;

/**
 * @author Hannes Schulz <hannes.schulz@aboutcoders.com>
 */
class IteratorRegistry implements IteratorRegistryInterface
{
    private $registry = [];

    /**
     * {@inheritDoc}
     */
    public function register($name, ScheduleIteratorInterface $processor)
    {
        $this->registry[$name] = $processor;
    }

    /**
     * {@inheritDoc}
     */
    public function get($name)
    {
        if (!array_key_exists($name, $this->registry)) {
            throw new \InvalidArgumentException(sprintf('An iterator for type "%s" is not registered', $name));
        }

        return $this->registry[$name];
    }

    /**
     * {@inheritDoc}
     */
    public function all()
    {
        return $this->registry;
    }
}


<?php
/*
* This file is part of the scheduler-bundle package.
*
* (c) Hannes Schulz <hannes.schulz@aboutcoders.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Abc\Bundle\SchedulerBundle\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\ConfigurationInterface;

/**
 * @author Hannes Schulz <hannes.schulz@aboutcoders.com>
 */
class Configuration implements ConfigurationInterface
{
    /**
     * {@inheritDoc}
     */
    public function getConfigTreeBuilder()
    {
        $treeBuilder = new TreeBuilder();
        $rootNode = $treeBuilder->root('abc_scheduler');
        $supportedDrivers = array('orm', 'custom');

        $rootNode
            ->children()
                ->scalarNode('db_driver')
                    ->cannotBeOverwritten()
                    ->defaultValue('orm')
                    ->validate()
                        ->ifNotInArray($supportedDrivers)
                        ->thenInvalid('The driver %s is not supported. Please choose one of '.json_encode($supportedDrivers))
                    ->end()
                ->end()
                ->scalarNode('model_manager_name')
                    ->defaultNull()
                ->end()
            ->end();

        return $treeBuilder;
    }
}


<?php
/*
 * Copyright (c) Arnaud Ligny <arnaud@ligny.org>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace PHPoole\Step;

use PHPoole\Collection\Page\Page;
use PHPoole\Converter\Converter;
use PHPoole\Exception\Exception;

/**
 * Converts content of all pages.
 */
class ConvertPages extends AbstractStep
{
    /**
     * {@inheritdoc}
     */
    public function process()
    {
        if (count($this->phpoole->getPages()) <= 0) {
            return;
        }
        $message = 'Converting pages';
        if ($this->phpoole->getBuildOptions()['drafts']) {
            $message .= ' (drafts included)';
        }
        call_user_func_array($this->phpoole->getMessageCb(), ['CONVERT', $message]);
        $max = count($this->phpoole->getPages());
        $count = 0;
        $countError = 0;
        /* @var $page Page */
        foreach ($this->phpoole->getPages() as $page) {
            if (!$page->isVirtual()) {
                $count++;
                $convertedPage = $this->convertPage($page, $this->phpoole->getConfig()->get('frontmatter.format'));
                if (false !== $convertedPage) {
                    $message = $page->getPathname();
                    // force convert drafts?
                    if ($this->phpoole->getBuildOptions()['drafts']) {
                        $page->setVariable('published', true);
                    }
                    if ($page->getVariable('published')) {
                        $this->phpoole->getPages()->replace($page->getId(), $convertedPage);
                    } else {
                        $this->phpoole->getPages()->remove($page->getId());
                        $message .= ' (not published)';
                    }
                    call_user_func_array($this->phpoole->getMessageCb(), ['CONVERT_PROGRESS', $message, $count, $max]);
                } else {
                    $this->phpoole->getPages()->remove($page->getId());
                    $countError++;
                }
            }
        }
        if ($countError > 0) {
            $message = sprintf('Number of errors: %s', $countError);
            call_user_func_array($this->phpoole->getMessageCb(), ['CONVERT_ERROR', $message]);
        }
    }

    /**
     * Converts page content:
     * - Yaml frontmatter to PHP array
     * - Markdown body to HTML.
     *
     * @param Page   $page
     * @param string $format
     *
     * @return Page
     */
    public function convertPage(Page $page, $format = 'yaml')
    {
        // converts frontmatter
        if ($page->getFrontmatter()) {
            try {
                $variables = Converter::convertFrontmatter($page->getFrontmatter(), $format);
            } catch (Exception $e) {
                $message = sprintf("Unable to convert frontmatter of '%s': %s", $page->getId(), $e->getMessage());
                call_user_func_array($this->phpoole->getMessageCb(), ['CONVERT_ERROR', $message]);

                return false;
            }
            $page->setVariables($variables);
        }

        // converts body
        $html = Converter::convertBody($page->getBody());
        $page->setHtml($html);

        return $page;
    }
}