<?php
/**
 * Import service
 *
 * @category Popov
 * @package Popov_Importer
 * @author Serhii Popov <popow.serhii@gmail.com>
 */
namespace Popov\Importer;

use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
use Popov\Importer\Driver\DriverInterface;
use Popov\Variably\ConfigHandler;
use Popov\Variably\Preprocessor;
use Popov\Db\Db;

class Importer
{
    const MODE_SAVE = 'save';

    const MODE_UPDATE = 'update';

    /**
     * @var Db
     */
    protected $db;

    /**
     * @var Preprocessor
     */
    protected $preprocessor;

    /**
     * @var ConfigHandler
     */
    protected $configHandler;

    /**
     * @var DriverCreator
     */
    protected $driverCreator;

    /**
     * @var ObservableInterface
     */
    protected $observable;

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

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

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

    protected $tableOrders;

    protected $fieldOrders;

    protected $codenamedOrders;

    protected $saved = [];

    /**
     * Current real row from database
     *
     * @var array
     */
    protected $currentRealRows = [];

    /**
     * Unhandled set of row data
     *
     * @var array
     */
    protected $currentRawRow = [];

    /**
     * Messages of import process.
     * Log level can be "info", "error", "success" etc.
     */
    protected $messages = [];

    protected $timeExecution;

    protected $preparedFields = [];

    public function __construct(
        Db $db,
        ConfigHandler $configHandler,
        Preprocessor $preprocessor,
        DriverCreator $driverCreator = null,
        ObservableInterface $observable = null,
        LoggerInterface $logger = null,
        array $config = null
    )
    {
        $this->db = $db;
        $this->configHandler = $configHandler;
        $this->preprocessor = $preprocessor;
        #$this->configHandler = $preprocessor->getConfigHandler();
        $this->driverCreator = $driverCreator ?? new DriverCreator(null, []);
        $this->observable = $observable;
        $this->logger = $logger;

        $this->driverCreator->setConfig($config['importer'] ?? $config);
        $this->configHandler->setConfig($config['importer'] ?? $config)
            ->getVariably()->set('importer', $this);

        $this->config = $config['importer'] ?? $config;
    }

    public function getDb()
    {
        return $this->db;
    }

    public function getPdo()
    {
        return $this->db->getPdo();
    }

    public function setPreprocessor(Preprocessor $preprocessor)
    {
        $this->preprocessor = $preprocessor;

        return $this;
    }

    public function setDriverCreator(DriverCreator $driverCreator)
    {
        $this->driverCreator = $driverCreator;

        return $this;
    }

    public function setObservable(Observable $observable)
    {
        $this->observable = $observable;

        return $this;
    }

    public function setLogger(LoggerInterface $logger)
    {
        $this->logger = $logger;

        return $this;
    }

    public function import($task, $source)
    {
        $this->profiling();
        try {
            $this->runImport($task, $source);
        } catch (\Throwable $e) {
            $this->log('error', $e);
        } catch (\Exception $e) {
            $this->log('error', $e);
        }

        // execution time of the script
        $this->log('info', sprintf('Total Execution Time: %s Mins', $this->profiling(false)));
        #$this->messages['info'][] = sprintf('Total Execution Time: %s Mins', $this->profiling(false));

        ($hasErrors = $this->hasErrors())
            ? $this->log('error', 'During the import the errors occurred!')
            : $this->log('info', 'Data has been imported successfully!');

        return !$hasErrors;
    }

    protected function runImport($task, $source)
    {
        $driver = $this->getDriver($task, $source);

        $this->fieldsMap = $driver->config()['fields'];

        // Reset
        $this->tableOrders = null;
        $this->fieldOrders = null;
        $this->codenamedOrders = null;
        $this->messages = [];

        $this->log('info', sprintf('%s started data processing...', $driverName = $this->getShortDriverName($driver)));
        $this->trigger('run', $driver);
        $this->trigger('run.' . $task, $driver); // here we can rewrite fieldsMap!

        $tables = [];
        for ($colIndex = $driver->firstColumn(); $colIndex <= $driver->lastColumn(); $colIndex++) {
            $tableOrder = null;
            $title = $driver->read($driver->firstRow(), $colIndex);
            foreach ($this->fieldsMap as $table) {
                if (isset($table[$title])) {
                    $tableOrder = $this->getTableOrder($table['__table']);
                    $fieldOrder = $this->getFieldOrder($title, $table['__table']);
                    $tables[$tableOrder][$fieldOrder] = ['index' => $colIndex, 'name' => $title];
                } elseif (isset($table['__dynamic'])) {
                    if (!isset($indexStartAfter)
                        && isset($table['__options']['startAfter'])
                        && ($table['__options']['startAfter'] == $title)
                    ) {
                        $indexStartAfter = $colIndex; // find 'startAfter' column index
                    } elseif (isset($indexStartAfter) && $indexStartAfter < $colIndex && ($title = trim($title))) {
                        $tableOrder = $this->getTableOrder($table['__table']);
                        $tables[$tableOrder][] = ['index' => $colIndex, 'name' => $title];
                        $this->fieldsMap[$tableOrder][$title] = $table['__dynamic'];
                    }
                }
            }
            if (isset($tables[$tableOrder])) {
                ksort($tables[$tableOrder]);
            }
        }
        ksort($tables);

        $successCounter = 0;
        // Skip head row
        for ($rowIndex = ($driver->firstRow() + 1); $rowIndex <= $driver->lastRow(); $rowIndex++) {
            // Reset properties on each iteration
            $this->preparedFields = [];
            $this->currentRealRows = [];

            foreach ($tables as $tableOrder => $table) {
                $fields = $this->fieldsMap[$tableOrder];
                $tableName = $fields['__table'];
                $this->saved[$tableName] = null;

                $this->currentRawRow = $driver->read($rowIndex); // Raw row - not handled yet

                // Collect data from one table in array
                $related = [];
                foreach ($table as $column) {
                    if (!isset($this->currentRawRow[$column['name']])) {
                        // Skip wrong value address
                        continue;
                    }
                    $related[$column['name']] = $this->currentRawRow[$column['name']];
                }

                if (!array_filter($related)) {
                    continue;
                }

                // Handle raw data
                $item = [];
                foreach ($related as $columnName => $value) {
                    $field = $fields[$columnName];
                    $this->handleField($value, $field, $item);
                    $this->preparedFields[$tableName] = $item;
                }

                // @todo Add filter based on @see
                if (!$item/* || $this->handleFilter()*/) {
                    continue;
                }

                // save row
                if (!isset($fields['__exclude']) || !$fields['__exclude']) {
                    if (isset($fields['__foreign'])) {
                        foreach ($fields['__foreign'] as $referenceTable => $foreignField) {
                            // Only if foreign key is set and is real value add it as reference,
                            // otherwise omit this field. It automatically applied to NULL on DB level.
                            if (isset($this->saved[$referenceTable]) && $this->saved[$referenceTable]) {
                                $item[$foreignField] = $this->saved[$referenceTable];
                            }
                        }
                    }
                    if (isset($fields['__ignore'])) {
                        foreach ($fields['__ignore'] as $ignoredField) {
                            unset($item[$ignoredField]);
                        }
                    }
                    $this->saved[$tableName] = $this->save($item, $tableName);
                }
            }
            $successCounter++;
            $this->currentRawRow = [];
            $this->saved = [];
        }

        $this->log('info', sprintf('Number of all rows: %s', $rowIndex - 1));
        $this->log('info', sprintf('Number of success handled rows: %s', $successCounter));
        //$this->log('info', sprintf('%s finish data processing!', $driverName));

        $this->trigger('run.post', $driver);
    }

    public function save(array $row, $table)
    {
        if (!array_filter($row)) {
            return false;
        }
        $id = $this->getIds($row, $table); // important place here
        try {
            // We run preprocessor handle directly before save for have access to $row ID.
            // For example, save default values only if there is no ID in $row.
            $row = $this->handlePreprocessor($row);

            if (!$row) {
                return false;
            }

            $this->trigger('save', $row);
            $this->trigger('save.' . $this->getCurrentFieldsMap('__codename'), $row);

            $modeMethod = $this->getModeMethod($table);
            $this->{$modeMethod}($row, $table);

            $this->trigger('save.post', $row);
            //$this->trigger('save.post.' . $this->getCurrentFieldsMap('__codename'), $row);

            $isDeep = $this->isDeep($row);
            $isDeepCond = $isDeep && (count($id) !== count($row));
            // Double check if we have multiple array. We don't know which data will be inserted or updated
            if ($isDeepCond) {
                $id = $this->getIds($row, $table, false);
            } else {
                $db = $this->getDb();
                $id = $id ?: [$db->lastInsertId()];
            }
            $id = (!isset($id[1]))
                ? ($id ? current($id) : false) // if __identifier set to false then return false
                : $id;

            $this->log('debug', sprintf('Data in table "%s" saved', $table), $row);
        } catch (\Throwable $e) {
            $this->log('error', $e);
        } catch (\Exception $e) {
            $this->log('error', $e);
        }

        return $id;
    }

    protected function getModeMethod($table)
    {
        $options = $this->getTableOptions($table);
        $mode = isset($options['mode']) ? $options['mode'] : self::MODE_SAVE;
        $modeMethod = method_exists($this, $method = $mode . 'Mode') ? $method : self::MODE_SAVE . 'Mode';

        return $modeMethod;
    }

    protected function saveMode(array $row, $table)
    {
        $isDeep = $this->isDeep($row);
        $db = $this->getDb();
        if ($isDeep) {
            $db->multipleSave($table, $row);
        } else {
            // When a table has at least one unique field and this field(s) is the same as field(s) in '__identifier'
            // you can enable `unique` for performance benefit,
            // otherwise if you can not mark field as unique will be used standard SELECT-INSERT/UPDATE approach.
            if (isset($this->config['__options']['unique']) && $this->config['__options']['unique']) {
                $db->save($table, $row);
            } else {
                if (isset($row['id']) && $row['id']) {
                    $db->update($table, $row, 'id = "' . $row['id'] . '"');
                } else {
                    unset($row['id']);
                    $db->add($table, $row);
                }
            }
        }
    }

    protected function updateMode(array $row, $table)
    {
        if (isset($row['id']) && $row['id']) {
            static $sqlMode;
            if (!$sqlMode) {
                // @todo Винести це в конігурацію.
                // INSERT/UPDATE statement in STRICT mode
                // throw error SQLSTATE[HY000]: General error: 1364 Field 'fieldName' doesn't have a default value
                $this->getDb()->exec('SET SESSION sql_mode = "NO_ENGINE_SUBSTITUTION"');
                $sqlMode = true;
            }
            $this->saveMode($row, $table);
        }
    }

    public function getIds(array & $row, $table, $apply = true, $identifierCustom = false)
    {
        $identifierField = $this->getIdentifierField($table, $identifierCustom);

        if (!$identifierField) {
            return false;
        }

        $realRows = $this->getRealRow($row, $table);
        //$realRow = $this->getCurrentRealRow($row, $table);

        if ($apply) {
            $this->applyId($row, $realRows, $identifierField);
        }

        $ids = array_map(function ($id) {
            return $id['id'];
        }, $realRows);

        return $ids;
    }

    public function getRealRow(array $row, $table)
    {
        $identifierField = $this->getIdentifierField($table);

        if (!$identifierField) {
            return false;
        }

        // Identifier can be singular (id) or combined (code, country).
        // Here we reduce all values to array.
        $identifierField = is_array($identifierField) ? $identifierField : [$identifierField];

        $identifiers = $this->prepareIdentifiers($row, $identifierField);

        $sqlPart = [];
        foreach ($identifiers as $field => $identifier) {
            //$sqlPart['column'][] = sprintf('`%s`', $field);
            $sqlPart['where'][] = sprintf('%s IN (%s)', $field, str_repeat('?,', count($identifier) - 1) . '?');
        }

        //$sql = sprintf('SELECT `id`, %s FROM `%s` WHERE %s',
        $sql = sprintf('SELECT * FROM `%s` WHERE %s',
            //implode(', ', $sqlPart['column']),
            $table,
            implode(' AND ', $sqlPart['where'])
        );

        $realRow = $this->db->fetchAll($sql, $this->flatten($identifiers));

        // $currentRealRow is reset on each iteration
        $this->currentRealRows[$table] = $realRow;

        return $realRow;
    }

    public function getIdentifierField($table, $identifierCustom = false)
    {
        $identifierField = ($identifierField = $this->getTableFieldsMap($table, '__identifier'))
            ? $identifierField
            : $identifierCustom;

        return $identifierField;
    }

    protected function prepareIdentifiers($row, $identifierField)
    {
        $isDeep = $this->isDeep($row);
        $identifiers = [];
        foreach ($identifierField as $field) {
            if ($isDeep) {
                foreach ($row as $sub) {
                    $identifiers[$field][] = $sub[$field];
                }
            } else {
                $identifiers[$field] = [$row[$field]];
            }
        }

        return $identifiers;
    }

    protected function flatten($array = null)
    {
        $result = [];
        if (!is_array($array)) {
            $array = func_get_args();
        }
        foreach ($array as $key => $value) {
            if (is_array($value)) {
                $result = array_merge($result, $this->flatten($value));
            } else {
                $result = array_merge($result, [$key => $value]);
            }
        }

        return $result;
    }

    /**
     * Apply inner DB id to row
     *
     * @param $row
     * @param $id
     * @param $identifierField
     */
    protected function applyId(& $row, $id, $identifierField)
    {
        $ids = (array) $id;
        $findId = function ($row) use ($ids, $identifierField) {
            foreach ($ids as $id) {
                // Foton == FOTON
                $bool = true;
                $identifierField = $this->flatten($identifierField);
                foreach ($identifierField as $identifier) {
                    $bool = $bool && (mb_strtolower($id[$identifier]) === mb_strtolower($row[$identifier]));
                }
                if ($bool) {
                    return $id['id'];
                }
            }
            return 0;
        };

        $isDeep = $this->isDeep($row);

        if ($isDeep) {
            foreach ($row as $i => & $r) {
                $r['id'] = $findId($r);
            }
        } else {
            $row['id'] = $findId($row);
        }
    }

    protected function handleField($value, $params, & $row)
    {
        try {
            $codename = $this->getCurrentFieldsMap('__codename');
            $this->configHandler->getVariably()->set('fields', $row);
            #$this->configHandler->getVariably()->set('originFields', $row);
            $this->configHandler->getVariably()->set($codename, $row);

            $value = $this->configHandler->process($value, $params);
        } catch (\Exception $e) {
            $this->log('error', $e);
        }

        $this->preprocessor->correlate($row, $value, $params);

        return $row;
    }

    protected function handlePreprocessor($row)
    {
        $fieldsConfig = $this->getCurrentFieldsMap();
        if (isset($fieldsConfig['__preprocessor'])) {
            $row = $this->preprocessor->setConfig($fieldsConfig['__preprocessor'])
                ->process($row);
        }

        return $row;
    }

    public function getDriverCreator()
    {
        return $this->driverCreator;
    }

    /**
     * Get driver and replace current logger if driver has its own logger
     *
     * @param $configTask
     * @param $source
     * @return DriverInterface
     */
    public function getDriver($configTask, $source)
     {
        $driverFactory = $this->getDriverCreator();
        /** @var DriverInterface $driver */
        $driver = $driverFactory->create($configTask);
        $driver->source($source);

        if (method_exists($driver, 'getLogger') && $driver instanceof LoggerAwareInterface) {
            $this->logger = $driver->getLogger();
        }

        return $driver;
    }

    public function getCurrentTable()
    {
        end($this->preparedFields);
        $table = key($this->preparedFields);
        reset($this->preparedFields);

        return $table;
    }

    public function getCurrentFieldsMap($field = false)
    {
        $currentTable = $this->getCurrentTable();
        $fieldsMap = $this->getTableFieldsMap($currentTable, $field);

        return $fieldsMap;
    }

    /**
     * Get prepared fields grouped by tables
     *
     * @return array
     */
    public function getPreparedFields()
    {
        return $this->preparedFields;
    }

    /**
     * Get current real rows saved in database.
     *
     * This method reduce number of queries to database.
     * You should be sure identifier fields already processed and are available in $preparedFields.
     *
     * Trick: in __preprocessor all fields are available.
     *
     * @param string $table
     * @return array
     */
    public function getCurrentRealRows($table = null)
    {
        $table = $table ?: $this->getCurrentTable();
        if (!isset($this->currentRealRows[$table]) || !$this->currentRealRows[$table]) {
            $fields = $this->getPreparedFields()[$table];
            $this->currentRealRows[$table] = $this->getRealRow($fields, $table);
        }

        return $this->currentRealRows[$table];
    }

    public function getCurrentRealRow($table = null)
    {
        $realRow = $this->getCurrentRealRows($table);
        if (count($realRow) === 1) {
            $realRow = $realRow[0];
        }

        return $realRow;
    }

    /**
     * Get full set of unhandled data from driver not grouped by table
     *
     * Not recommended to use. Use very carefully.
     *
     * @return array
     */
    public function getCurrentRawRow()
    {
        return $this->currentRawRow;
    }

    public function getTableFieldsMap($table, $field = false)
    {
        if (!$table
            || (false === ($tableOrder = $this->getTableOrder($table)))
            || (false === isset($this->fieldsMap[$tableOrder]))
        ) {
            return false;
        }

        $fieldsMap = $this->fieldsMap[$this->getTableOrder($table)];
        if (false === $field) {
            return $fieldsMap;
        }
        if (isset($fieldsMap[$field])) {
            return $fieldsMap[$field];
        }

        return false;
    }

    public function getTableOrderByCodename($codename)
    {
        if (is_null($this->codenamedOrders)) {
            $this->prepareOrders();
        }

        return isset($this->codenamedOrders[$codename]) ? $this->codenamedOrders[$codename] : false;
    }

    public function getTableOrders()
    {
        return $this->tableOrders;
    }

    public function setTableOrder($tableName, $order)
    {
        $this->tableOrders[$tableName] = $order;

        return $this;
    }

    public function unsetTableOrder($tableName)
    {
        unset($this->tableOrders[$tableName]);

        return $this;
    }

    public function getFieldOrder($filedName, $table)
    {
        if (is_null($this->fieldOrders)) {
            $this->prepareOrders();
        }

        #$tableOrder = $this->getTableOrder($table);
        return isset($this->fieldOrders[$table][$filedName]) ? $this->fieldOrders[$table][$filedName] : false;
    }

    public function getTableOrder($tableName)
    {
        if (is_null($this->tableOrders)) {
            $this->prepareOrders();
        }

        return isset($this->tableOrders[$tableName]) ? $this->tableOrders[$tableName] : false;
    }

    protected function prepareOrders()
    {
        $this->fieldOrders = [];
        $this->tableOrders = [];
        $this->codenamedOrders = [];
        foreach ($this->fieldsMap as $i => $fields) {
            if (!isset($fields['__table'])) {
                continue;
            }
            $this->tableOrders[$fields['__table']] = $i;
            $this->codenamedOrders[$fields['__codename']] = $i;

            $f = 0;
            foreach ($fields as $fromName => $toName) {
                // skip reserved config keys
                if ('__' === substr($fromName, 0, 2)) {
                    continue;
                }
                $this->fieldOrders[$fields['__table']][$fromName] = $f++;
            }
        }
    }

    public function getTableOptions($table)
    {
        return isset($this->fieldsMap[$this->getTableOrder($table)]['__options'])
            ? $this->fieldsMap[$this->getTableOrder($table)]['__options']
            : false;
    }

    /**
     * Execution time of the script
     *
     * @var bool $signal true - start, false - stop
     * @return float $timeExecution Total Execution Time in Mins
     */
    protected function profiling($signal = true)
    {
        static $timeStart;
        if ($signal) {
            $timeStart = microtime(true);
        }
        if (!$signal) {
            $this->timeExecution = (microtime(true) - $timeStart) / 60;

            return $this->timeExecution;
        }
    }

    public function getTimeExecution()
    {
        return $this->timeExecution;
    }

    /**
     * If will be use more ArrayEx functions then inject relative object
     *
     * @param $array
     * @return bool
     */
    protected function isDeep($array)
    {
        if (!is_array($array)) {
            return false;
        }

        foreach ($array as $elm) {
            if (is_array($elm)) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param $message
     * @param string $namespace
     * @deprecated Use log method instead
     */
    public function addMessage($message, $namespace = 'info')
    {
        $this->messages[$namespace][] = $message;
    }

    /**
     * @param array $messages
     * @param $namespace
     * @deprecated
     */
    public function setMessages(array $messages, $namespace)
    {
        $this->messages[$namespace] = $messages;
    }

    /**
     * @return array
     * @deprecated
     */
    public function getMessages()
    {
        return $this->messages;
    }

    public function hasErrors()
    {
        return isset($this->messages['error']);
    }

    public function getErrors()
    {
        if ($this->hasErrors()) {
            return $this->messages['error'];
        }

        return false;
    }

    public function setFieldsMap($orderTable, $options)
    {
        $this->fieldsMap[$orderTable] = $options;

        return $this;
    }

    public function unsetFieldsMap($orderTable)
    {
        unset($this->fieldsMap[$orderTable]);

        return $this;
    }

    public function getFieldsMap($orderTable = null)
    {
        if ($orderTable === null) {
            return $this->fieldsMap;
        }
        if (isset($this->fieldsMap[$orderTable])) {
            return $this->fieldsMap[$orderTable];
        }

        return false;
    }

    public function getSaved($productTable)
    {
        return $this->saved[$productTable];
    }

    protected function getShortDriverName($driver)
    {
        $parts = explode('\\', get_class($driver));

        return array_pop($parts);
    }

    protected function trigger($eventName, $target, $params = [])
    {
        if ($this->observable) {
            $params['context'] = $this;
            $this->observable->trigger($eventName, $target, $params);
        }
    }

    /**
     * Wrapper upon logger.
     *
     * This method write all messages in file if logger is passed and also collect them in memory
     * for following process at the end of execution.
     * All not "info" messages are grouped with number of repeats prefix.
     *
     * Each next "run" resets "messages" that is why you should process this information after each execution.
     *
     * @param string $level
     * @param string $message
     * @param array $context
     */
    public function log($level, $message, array $context = [])
    {
        static $counter = [];

        !$this->logger || $this->logger->log($level, $message, $context);

        // If message is exception convert it to string
        $message = is_object($message) ? $message->__toString() : $message;
        // Group identical messages with add suffix number
        if (isset($this->messages[$level][$hash = md5($message)])) {
            $this->messages[$level][$hash] = '(' . ++$counter[$hash] . ') ' . $message;
        } else {
            $counter[$hash] = 1;
            $this->messages[$level][$hash] = $message;
        }
    }
}


<?php
/**
 * YAWIK
 *
 * @filesource
 * @copyright (c) 2013 - 2016 Cross Solution (http://cross-solution.de)
 * @license   MIT
 */

/** SessionParams.php */
namespace Core\Controller\Plugin;

use Zend\Mvc\Controller\Plugin\AbstractPlugin;
use Zend\Session\Container;
use Core\Repository\RepositoryInterface;
use Zend\Stdlib\Parameters;

/**
 * Manages pagination parameters in a session container.
 *
 * @author Mathias Gelhausen <gelhausen@cross-solution.de>
 */
class PaginationParams extends AbstractPlugin
{

    /**
     * Invoke object as function.
     *
     * if $namespace is given, proxies to {@link getParams}, if $defaults
     * is an array and not callable or proxises to {@link getList} in the other case.
     *
     * if called without arguments, returns itself.
     *
     * @param string|null $namespace
     * @param array|RepositoryInterface $defaults
     * @return \Core\Controller\Plugin\PaginationParams|Parameters|PaginationList
     */
    public function __invoke($namespace = null, $defaults = array('page' => 1), $params = null)
    {
        if (null === $namespace) {
            return $this;
        }

        if (is_array($defaults) && !is_callable($defaults)) {
            return $this->getParams($namespace, $defaults, $params);
        }

        if ($defaults instanceof RepositoryInterface || is_callable($defaults)) {
            return $this->getList($namespace, $defaults);
        }
    }

    /**
     * Sets pagination params and stores them in the session.
     *
     * @param String $namespace
     * @param array|Parameters $params
     * @return \Core\Controller\Plugin\PaginationParams fluent interface
     */
    public function setParams($namespace, $params)
    {
        $session = new Container($namespace);
        $session->params = $params;
        unset($session->list);
        return $this;
    }

    /**
     * Retrieves pagination params.
     *
     * Automatically merges parameters stored in the session according to specs
     * provided.
     *
     * @param string $namespace Session namespace
     * @param array $defaults
     *      1. [paramName] => [defaultValue],
     *         Set default value if paramName is not present in params
     *      2. [paramName]
     *         Store paramName if it is present, do nothing if not.
     * @param Parameters $params
     *
     * @return Parameters
     */
    public function getParams($namespace, $defaults, $params = null)
    {
        $session        = new Container($namespace);
        $sessionParams  = $session->params ?: array();
        $params         = $params ?: clone $this->getController()->getRequest()->getQuery();

        if ($params->get('clear')) {
            $sessionParams = array();
            unset($params['clear']);
        }

        $changed = false;
        foreach ($defaults as $key => $default) {
            if (is_numeric($key)) {
                $key = $default;
                $default = null;
            }
            $value = $params->get($key);
            if (null === $value) {
                if (isset($sessionParams[$key])) {
                    $params->set($key, $sessionParams[$key]);
                } elseif (null !== $default) {
                    $params->set($key, $default);
                    $sessionParams[$key] = $default;
                    $changed = true;
                }
            } else {
                if (!isset($sessionParams[$key]) || $sessionParams[$key] != $value) {
                    $changed = true;
                    $sessionParams[$key] = $value;
                }
            }
        }

        if ($changed) {
            unset($session->list);
            $session->params = $sessionParams;
        }

        return $params;
    }

    /**
     * Gets the list of ids.
     *
     * If no list is stored in the session, it will try to create one
     * using the given callback, which is either a RepositoryInterface or a
     * callable. In the first case, a method called "getPaginationList" will get called.
     * The stored parameters (or an empty array, if nothing is stored) will be passed.
     *
     * @param string $namespace
     * @param RepositoryInterface|callable $callback
     *
     * @return array
     */
    public function getList($namespace, $callback)
    {
        $session = new Container($namespace);
        $params  = $session->params?:array();
        if (!$session->list) {
            $session->list = is_array($callback)
            ? call_user_func($callback, $session->params)
            : $callback->getPaginationList($session->params);
        }
        return $session->list;
    }

    /**
     * Get previous and next id from the list.
     *
     * @param string $namespace
     * @param RepositoryInterface|callable $callback
     * @param string $id Current application id
     * @return array
     *          first entry: previous id or null
     *          second entry: next id or null
     */
    public function getNeighbours($namespace, $callback, $id)
    {
        $list = $this->getList($namespace, $callback);
        $list->setCurrent($id);
        return [
            $list->getPrevious(),
            $list->getNext()
        ];
    }
}


<?php
/**
 * YAWIK
 *
 * @filesource
 * @license MIT
 * @copyright  2013 - 2019 Cross Solution <http://cross-solution.de>
 */

/** */
namespace Core\Queue;

use SlmQueue\Job\JobInterface;
use SlmQueue\Job\JobPluginManager;
use SlmQueue\Queue\AbstractQueue;

/**
 * SlmQueue implementation for a queue backed by MongoDB
 *
 * Heavily inspired by https://github.com/juriansluiman/SlmQueueDoctrine
 *
 * @author Mathias Gelhausen <gelhausen@cross-solution.de>
 */
class MongoQueue extends AbstractQueue
{

    /**#@+
     * Job status
     * @var int
     */
    const STATUS_PENDING = 1;
    const STATUS_RUNNING = 2;
    const STATUS_FAILED  = 3;
    /**#@-*/

    /**
     * Default priority
     * @var int
     */
    const DEFAULT_PRIORITY = 1024;

    /**
     * Used to synchronize time calculations
     *
     * @var \DateTime
     */
    private $now;

    /**
     * @var \MongoDB\Collection
     */
    private $mongoCollection;

    /**
     * Constructor
     *
     * @param \MongoDB\Collection $collection
     * @param string           $name
     * @param JobPluginManager $jobPluginManager
     */
    public function __construct(
        \MongoDB\Collection $collection,
        $name,
        JobPluginManager $jobPluginManager
    ) {
        $this->mongoCollection = $collection;

        parent::__construct($name, $jobPluginManager);
    }

    /**
     * Push a job to the queue.
     *
     * Valid options are:
     *      - priority: the lower the priority is, the sooner the job get popped from the queue (default to 1024)
     *
     * Note : see {@link parseOptionsToDateTime()} for schedule and delay options
     *
     * @param JobInterface $job
     * @param array $options
     */
    public function push(JobInterface $job, array $options = [])
    {
        $envelope = $this->createEnvelope($job, $options);

        $result = $this->mongoCollection->insertOne($envelope);

        $job->setId((string) $result->getInsertedId());
    }

    /**
     * Push a lazy loading job in the queue.
     *
     * Lazy job allows to load the actual job only when executed.
     *
     * You can specify the job to load in two ways:
     * - as string:
     *      The actual job will be pulled from the job manager or instantiated from a valid class name.
     *
     * - as array:
     *      If you need to pass options to the job, you can specify the
     *      job as an array:
     *      [ string:name, array:options ]
     *      The actual job will be pulled from the job manager using the 'build' command,
     *      passing the options along OR if no service for the job is defined, but a valid
     *      class name is given, this class will be instantiated and the options are passed
     *      as constructor arguments,
     *
     * @see push()
     * @see LazyJob
     *
     * @param string|array $service
     * @param mixed|null  $payload
     * @param array $options
     */
    public function pushLazy($service, $payload = null, array $options = [])
    {
        $manager = $this->getJobPluginManager();
        $serviceOptions = [];

        if (is_array($service)) {
            $serviceOptions = $service['options'] ?? $service[1] ?? [];
            $service = $service['name'] ?? $service[0] ?? null;
        }

        if (!$manager->has($service) && !class_exists($service)) {
            throw new \UnexpectedValueException(sprintf(
                'Service name "%s" is not a known job service or existent class',
                $service
            ));
        }

        $lazyOptions = [
            'name' => $service,
            'options' => $serviceOptions,
            'content' => $payload,
        ];

        $job = $this->getJobPluginManager()->build('lazy', $lazyOptions);

        $this->push($job, $options);
    }

    /**
     * Create a mongo document.
     *
     * @param JobInterface $job
     * @param array        $options
     *
     * @return array
     */
    private function createEnvelope(JobInterface $job, array $options = [])
    {
        $scheduled = $this->parseOptionsToDateTime($options);
        $tried     = isset($options['tried']) ? (int) $options['tried'] : null;
        $message   = isset($options['message']) ? $options['message'] : null;
        $trace     = isset($options['trace']) ? $options['trace'] : null;

        $envelope = [
            'queue'     => $this->getName(),
            'status'    => self::STATUS_PENDING,
            'tried'     => $tried,
            'message'   => $message,
            'trace'     => $trace,
            'created'   => $this->dateTimeToUTCDateTime($this->now),
            'data'      => $this->serializeJob($job),
            'scheduled' => $this->dateTimeToUTCDateTime($scheduled),
            'priority'  => isset($options['priority']) ? $options['priority'] : self::DEFAULT_PRIORITY,
        ];

        return $envelope;
    }

    /**
     * Reinsert the job in the queue.
     *
     * @see push()
     * @param JobInterface $job
     * @param array        $options
     */
    public function retry(JobInterface $job, array $options = [])
    {
        $tried = $job->getMetadata('mongoqueue.tries', 0) + 1;
        $job->setMetaData('mongoqueue.tries', $tried);

        $options['tried'] = $tried;
        $envelope = $this->createEnvelope($job, $options);
        unset($envelope['created']);

        $this->mongoCollection->findOneAndUpdate(
            [
                '_id' => new \MongoDB\BSON\ObjectID($job->getId())
            ],
            [
                '$set' => $envelope
            ]
        );
    }

    /**
     * Pop a job from the queue.
     *
     * The status will be set to self::STATUS_RUNNING.
     *
     * @param array $options unused
     * @return null|JobInterface
     */
    public function pop(array $options = [])
    {
        $time      = microtime(true);
        $micro     = sprintf("%06d", ($time - floor($time)) * 1000000);
        $this->now = new \DateTime(
            date('Y-m-d H:i:s.' . $micro, $time),
            new \DateTimeZone(date_default_timezone_get())
        );
        $now = $this->dateTimeToUTCDateTime($this->now);

        $envelope = $this->mongoCollection->findOneAndUpdate(
            [
                'queue' => $this->getName(),
                'status' => self::STATUS_PENDING,
                'scheduled' => ['$lte' => $now],
            ],
            [
                '$set' => [
                    'status' => self::STATUS_RUNNING,
                    'executed' => $now,
                ],
            ],
            [
                'sort' => ['priority' => 1, 'scheduled' => 1],
                'returnDocument' => \MongoDB\Operation\FindOneAndUpdate::RETURN_DOCUMENT_AFTER
            ]
        );

        if (!$envelope) {
            return null;
        }

        return $this->unserializeJob($envelope['data'], ['__id__' => $envelope['_id']]);
    }

    /**
     * Fetch a list of jobs
     *
     * @param array $options
     *
     * @return array
     */
    public function listing(array $options = [])
    {
        $filter = [ 'queue' => $this->getName() ];
        if (isset($options['status'])) {
            $filter['status'] = $options['status'];
        }

        $opt = [ 'sort' => [ 'scheduled' => 1, 'priority' => 1] ];
        if (isset($options['limit'])) {
            $opt['limit'] = $options['limit'];
        }

        $cursor = $this->mongoCollection->find($filter, $opt);
        $jobs   = $cursor->toArray();

        foreach ($jobs as &$envelope) {
            $envelope['job'] = $this->unserializeJob($envelope['data'], ['__id__' => $envelope['_id']]);
        }

        return $jobs;

    }

    /**
     * Delete a job from the queue
     *
     * @param JobInterface $job
     * @param array        $options unused
     *
     * @return bool
     */
    public function delete(JobInterface $job, array $options = [])
    {
        $result = $this->mongoCollection->deleteOne(['_id' => $job->getId()]);

        return (bool) $result->getDeletedCount();
    }

    /**
     * Mark a job as permanent failed.
     *
     * The status will be set to self::STATUS_FAILED
     * @param JobInterface $job
     * @param array        $options unused
     */
    public function fail(JobInterface $job, array $options = [])
    {
        $envelope = $this->createEnvelope($job, $options);
        unset($envelope['created']);
        unset($envelope['scheduled']);
        $envelope['status'] = self::STATUS_FAILED;

        $this->mongoCollection->findOneAndUpdate(
            [
                '_id' => new \MongoDB\BSON\ObjectId($job->getId())
            ],
            [
                '$set' => $envelope
            ]
        );
    }

    /**
     * Parses options to a datetime object
     *
     * valid options keys:
     *
     * scheduled: the time when the job will be scheduled to run next
     * - numeric string or integer - interpreted as a timestamp
     * - string parserable by the DateTime object
     * - DateTime instance
     * delay: the delay before a job become available to be popped (defaults to 0 - no delay -)
     * - numeric string or integer - interpreted as seconds
     * - string parserable (ISO 8601 duration) by DateTimeInterval::__construct
     * - string parserable (relative parts) by DateTimeInterval::createFromDateString
     * - DateTimeInterval instance
     *
     * @see http://en.wikipedia.org/wiki/Iso8601#Durations
     * @see http://www.php.net/manual/en/datetime.formats.relative.php
     *
     * @codeCoverageIgnore
     * @param $options array
     * @return \DateTime
     */
    protected function parseOptionsToDateTime($options)
    {
        $time      = microtime(true);
        $micro     = sprintf("%06d", ($time - floor($time)) * 1000000);
        $this->now = new \DateTime(date('Y-m-d H:i:s.' . $micro, $time), new \DateTimeZone(date_default_timezone_get()));
        $scheduled = isset($options['scheduled']) ? Utils::createDateTime($options['scheduled']) : clone ($this->now);

        if (isset($options['delay'])) {
            $delay = Utils::createDateInterval($options['delay']);
            $scheduled->add($delay);
        }

        return $scheduled;
    }

    /**
     * Converst a \DateTime object to its UTCDateTime representation.
     *
     * @param \DateTime $date
     *
     * @return \MongoDB\BSON\UTCDateTime
     */
    protected function dateTimeToUTCDateTime(\DateTime $date)
    {
        return new \MongoDB\BSON\UTCDateTime($date->getTimestamp() * 1000);
    }

}


<?php
/**
 * YAWIK
 *
 * @filesource
 * @copyright (c) 2013 - 2016 Cross Solution (http://cross-solution.de)
 * @license   MIT
 */

namespace Core\View\Helper\Service;

use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\I18n\View\Helper\DateFormat;
use Locale;

/**
 * Hybridauth authentication adapter factory
 */
class DateFormatHelperFactory implements FactoryInterface
{

    /**
     * Creates an instance of \Core\View\Helper\Params
     *
     * - injects the MvcEvent instance
     *
     * @param ServiceLocatorInterface $serviceLocator
     * @return \Core\View\Helper\Params
     * @see \Zend\ServiceManager\FactoryInterface::createService()
     */
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $helper = new DateFormat();
        $helper->setLocale(Locale::DEFAULT_LOCALE);
        return $helper;
    }
}


<?php
/**
 * YAWIK
 *
 * @filesource
 * @license MIT
 * @copyright  2013 - 2018 Cross Solution <http://cross-solution.de>
 */

/** */
namespace Core\Controller\Console;

use Core\Console\ProgressBar;
use Core\Controller\Plugin\EntityEraser;
use Core\Entity\EntityInterface;
use Core\Service\EntityEraser\LoadEvent;
use Zend\Console\ColorInterface;
use Zend\EventManager\EventManagerInterface;
use Zend\Mvc\Console\Controller\AbstractConsoleController;

/**
 * Purge console action controller
 *
 * @author Mathias Gelhausen <gelhausen@cross-solution.de>
 * @todo write test
 */
class PurgeController extends AbstractConsoleController
{
    /**
     *
     *
     * @var \Core\EventManager\EventManager
     */
    private $loadEvents;

    /**
     * @param \Core\EventManager\EventManager $loadEvents
     *
     * @return self
     */
    public function setLoadEvents($loadEvents)
    {
        $this->loadEvents = $loadEvents;

        return $this;
    }

    /**
     * @return void
     */
    public function indexAction()
    {
        // console purge <entity> <id>

        /* @var EntityEraser $eraser */

        $console  = $this->getConsole();
        $eraser   = $this->plugin(EntityEraser::class);
        $options  = \Zend\Json\Json::decode($this->params('options', '{}'), \Zend\Json\Json::TYPE_ARRAY);
        $eraser->setOptions($options);
        $entities = $eraser->loadEntities($this->params('entity'), $this->params('id'));

        $found = count($entities);
        $console->writeLine(sprintf('Found %s entities to purge.', $found));

        if (0 == $found) {
            return;
        }

        if (!$this->params('no-check')) {
            $console->writeLine('Checking dependencies ... ' . PHP_EOL);

            $eraser = $this->plugin(EntityEraser::class);
            $counts = [];
            $totalCount = 0;
            foreach ($entities as $entity) {
                $console->writeLine('    ' . $this->entityToString($entity));
                $totalCount += 1;
                $dependencies = $eraser->checkDependencies($entity);

                foreach ($dependencies as $dependencyList) {
                    if ($dependencyList->isCount()) {
                        $entitiesCount = $dependencyList->getEntities();
                        $dependendEntities = [];
                    } else {
                        $dependendEntities = $dependencyList->getEntities();
                        $entitiesCount = count($dependendEntities);
                    }

                    $console->writeLine('        ' . $entitiesCount . ' ' . $dependencyList->getName() . ': ' . $dependencyList->getDescription());

                    $totalCount += $entitiesCount;
                    if (!isset($counts[$dependencyList->getName()])) {
                        $counts[$dependencyList->getName()] = 0;
                    }
                    $counts[$dependencyList->getName()] += $entitiesCount;

                    foreach ($dependendEntities as $dependendEntity) {
                        $console->writeLine('        - ' . $this->entityToString($dependendEntity));
                    }
                    $console->writeLine(' ');
                }
                $console->writeLine('');
            }

            $console->writeLine($totalCount . ' entities affected:');
            $console->writeLine('    ' . count($entities) . ' ' . $this->params('entity'));
            foreach ($counts as $name => $count) {
                $console->writeLine('    ' . $count . ' ' . $name);
            }

            $console->writeLine('');
            $confirmed = \Zend\Console\Prompt\Confirm::prompt('Proceed? [y/n] ');

            if (!$confirmed) {
                $console->writeLine('Aborted.');
                exit(1);
            }
        }

        $totalCount = 0;
        $counts = [];

        $progress     = new ProgressBar(count($entities));
        $i = 0;
        foreach ($entities as $entity) {
            $progress->update(++$i, $entity->getId());
            $dependencies = $eraser->erase($entity);

            $totalCount += 1;
            foreach ($dependencies as $list) {
                $entitiesCount = $list->isCount() ? $list->getEntities() : count($list->getEntities());
                $totalCount += $entitiesCount;
                if (!isset($counts[$list->getName()])) {
                    $counts[$list->getName()] = [0, $list->getDescription()];
                }
                $counts[$list->getName()][0] += $entitiesCount;
            }
        }

        $progress->finish();
        $console->writeLine('');
        $console->writeLine('Processed ' . $totalCount . ' entities.');
        $console->writeLine('    ' . count($entities) . ' ' . $this->params('entity') . ' deleted.');
        foreach ($counts as $name => $count) {
            $console->writeLine('    ' . $count[0] . ' ' . $name . ' ' . $count[1]);
        }
    }

    public function listAction()
    {
        $responses = $this->loadEvents->trigger(LoadEvent::FETCH_LIST, $this);
        $console = $this->getConsole();

        $console->writeLine('');
        foreach ($responses as $response) {
            if (!is_array($response) || !isset($response['key'])) {
                continue;
            }

            $console->writeLine(sprintf("%-20s %s", $response['key'], isset($response['description']) ? $response['description'] : ''), ColorInterface::BLUE);

            if (isset($response['options']) && is_array($response['options'])) {
                $console->writeLine('');
                foreach ($response['options'] as $name => $desc) {
                    $console->writeLine(sprintf("%20s- %-15s %s", ' ', $name, $desc));
                }
            }
            $console->writeLine('');
        }
        $console->writeLine();
    }

    /**
     * Get a string representation from an entity.
     *
     * Uses the entitys' __toString method, if available.
     * Otherwise just returns the class name and if available, the ID.
     *
     * @param EntityInterface $entity
     *
     * @return string
     */
    private function entityToString(EntityInterface $entity)
    {
        if (method_exists($entity, '__toString')) {
            return $entity->__toString();
        }

        $str = get_class($entity);

        if ($entity instanceof \Core\Entity\IdentifiableEntityInterface) {
            $str .= '( ' . $entity->getId() . ' )';
        }

        return $str;
    }
}


<?php
/**
 * YAWIK
 *
 * @filesource
 * @license MIT
 * @copyright  2013 - 2018 Cross Solution <http://cross-solution.de>
 */

/** */
namespace Core\Service\EntityEraser;

use Core\EventManager\EventManager as CoreEventManager;
use Zend\EventManager\EventInterface;
use Zend\EventManager\SharedEventManagerInterface;

/**
 * EventManager for Dependency events.
 *
 * Handles the repsonses from the listeners and add them to the events' dependency result collection.
 *
 * @author Mathias Gelhausen <gelhausen@cross-solution.de>
 * @todo write test
 */
class EntityEraserEvents extends CoreEventManager
{
    /**
     * Set the event prototype.
     *
     * Only instances of DependencyResultEvent are allowed.
     *
     * @param EventInterface $prototype
     * @throws \InvalidArgumentException
     */
    public function setEventPrototype(EventInterface $prototype)
    {
        if (!$prototype instanceof DependencyResultEvent) {
            throw new \InvalidArgumentException('This event manager only accepts events of the type ' . DependencyResultEvent::class);
        }

        parent::setEventPrototype($prototype);
    }

    /**
     * Triggers listeners.
     *
     * Loops over the responses and tries to add any non empty response as a DepnendecyResult to the collection.
     *
     * @param EventInterface $event
     * @param callable|null  $callback
     *
     * @return \Zend\EventManager\ResponseCollection
     * @throws \InvalidArgumentException if the event is not an instance of DependencyResultEvent
     */
    protected function triggerListeners(EventInterface $event, callable $callback = null)
    {
        if (!$event instanceof DependencyResultEvent) {
            throw new \InvalidArgumentException('This event manager only accepts events of the type ' . DependencyResultEvent::class);
        }

        $results = parent::triggerListeners($event, $callback);

        $dependencies = $event->getDependencyResultCollection();

        foreach ($results as $result) {
            if (null !== $result) {
                try {
                    $dependencies->add($result);
                }
                /* silently ignore all invalid results */
                catch (\UnexpectedValueException $e) {
                } catch (\InvalidArgumentException $e) {
                }
            }
        }

        return $results;
    }
}


<?php

namespace Core\Entity\Hydrator;

use Zend\Hydrator\AbstractHydrator;
use Core\Entity\AnonymEntityInterface;
use Settings\Entity\AwareEntity;

class AnonymEntityHydrator extends AbstractHydrator
{
    public function __construct()
    {
        parent::__construct();
        $this->init();
    }

    protected function init()
    {
    }

    /* (non-PHPdoc)
     * @see \Zend\Hydrator\HydratorInterface::extract()
     */
    public function extract($object)
    {
        $data = array();
        if (is_array($object) || $object instanceof \Traversable) {
            foreach ($object as $key => $value) {
                $data[$key] = $value;
            }
        }
        return $data;
    }

    /* (non-PHPdoc)
     * @see \Zend\Hydrator\HydratorInterface::hydrate()
     */
    public function hydrate(array $data, $object)
    {
        $setterMethods = array();
        if ($object instanceof AwareEntity) {
            $setterMethods = $object->getSetters();
        }
        foreach ($data as $key => $value) {
            $setter = 'set' . ucfirst($key);
            if (in_array($setter, $setterMethods)) {
                $object->$setter($value);
            } else {
                $object->$key = $value;
            }
        }
        return $object;
    }
}


<?php
/**
 * YAWIK
 *
 * @filesource
 * @copyright (c) 2013 - 2016 Cross Solution (http://cross-solution.de)
 * @license   MIT
 */

namespace Core\Form\View\Helper;

use Core\Form\CollectionContainer;
use Zend\Form\View\Helper\AbstractHelper;

/**
 * Helper for rendering form collection containers
 *
 * @author fedys
 */
class FormCollectionContainer extends AbstractHelper
{

    /**
     * @var bool
     */
    protected $displayRemoveButton = true;

    /**
     * Invoke as function.
     *
     * Proxies to {@link render()} or returns self.
     *
     * @param  null|CollectionContainer $container
     * @param string $layout
     * @param array $parameter
     * @return FormCollectionContainer|string
     */
    public function __invoke(CollectionContainer $container = null, $layout = Form::LAYOUT_HORIZONTAL, $parameter = [])
    {
        if (!$container) {
            return $this;
        }

        return $this->render($container, $layout, $parameter);
    }

    /**
     * Renders the forms of a container.
     *
     * @param CollectionContainer $container
     * @param string $layout
     * @param array $parameter
     * @return string
     */
    public function render(CollectionContainer $container, $layout = Form::LAYOUT_HORIZONTAL, $parameter = [])
    {
        $view = $this->getView();
        $view->headscript()
            ->appendFile($view->basepath('modules/Core/js/jquery.formcollection-container.js'));
        $translator = $this->getTranslator();
        $formContainerHelper = $view->formContainer();
        $formsMarkup = '';
        $formTemplateWrapper = '

            '. ($this->displayRemoveButton ? '<button type="button" class="btn btn-sm btn-danger pull-right form-collection-container-remove-button">' . $translator->translate('Remove') . '</button>' : '') . '
            %s

';

        foreach ($container as $form) /* @var $form \Zend\Form\Form */
        {
            $formsMarkup .= sprintf($formTemplateWrapper, $formContainerHelper->renderElement($form, $layout, $parameter));
        }

        $templateForm = $container->getTemplateForm();
        $templateMarkup = sprintf(
            $view->formCollection()->getTemplateWrapper(),
            $view->escapeHtmlAttr(sprintf($formTemplateWrapper, $formContainerHelper->renderElement($templateForm, $layout, $parameter)))
        );

        return sprintf(
            '

%s

                %s%s%s

',
            CollectionContainer::NEW_ENTRY,
            $container->formatAction('remove'),
            $translator->translate('Really remove?'),
            $container->getLabel(),
            $formsMarkup,
            $templateMarkup,
            '
<button type="button" class="btn btn-success form-collection-container-add-button"> ' . sprintf($translator->translate('Add %s'), $container->getLabel()) . '</button>
'
        );
    }
    /**
     * @param boolean $displayRemoveButton
     * @return FormCollectionContainer
     * @since 0.26
     */
    public function setDisplayRemoveButton($displayRemoveButton)
    {
        $this->displayRemoveButton = (bool)$displayRemoveButton;

        return $this;
    }
}


<?php

namespace Core\Form;

/**
 * Class SummaryFormButtonsFieldset
 *
 * @package Core\Form
 */
class SummaryFormButtonsFieldset extends ButtonsFieldset
{
    protected $formId;

    /**
     * Initialize the Buttons of a summary form element.
     */
    public function init()
    {
        $this->setName('buttons');
        if (!isset($this->options['render_summary'])) {
            $this->options['render_summary'] = false;
        }
        $this->setAttribute('class', 'text-right');

        $this->add(
            array(
            //'type' => 'Button',
            'type' => 'Core/Spinner-Submit',
            'name' => 'submit',
            'options' => array(
                'label' => /*@translate*/ 'Save',
            ),
            'attributes' => array(
                'id' => 'submit',
                'type' => 'submit',
                'value' => 'Save',
                'class' => 'sf-submit btn btn-primary btn-xs'
            ),
            )
        );

        $this->add(
            array(
            'type' => 'Button',
            'name' => 'cancel',
            'options' => array(
                'label' => /*@translate*/ 'Cancel',
            ),
            'attributes' => array(
                'id' => 'cancel',
                'type' => 'reset',
                'value' => 'Cancel',
                'class' => 'sf-cancel btn btn-default btn-xs'
            ),
            )
        );
    }

    /**
     * Set Options
     *
     * @param array|\Traversable $options
     * @return $this
     */
    public function setOptions($options)
    {
        if (!isset($options['render_summary'])) {
            $options['render_summary'] = isset($this->options['render_summary'])
                                       ? $this->options['render_summary']
                                       : false;
        }

        parent::setOptions($options);

        if (isset($options['form_id'])) {
            $this->setFormId($options['form_id']);
        }

        return $this;
    }

    /**
     * Set Attribute
     *
     * @param string $key
     * @param mixed  $value
     * @return \Zend\Form\Element|\Zend\Form\ElementInterface
     */
    public function setAttribute($key, $value)
    {
        if ('id' == $key) {
            $this->setFormId($value);
        }
        return parent::setAttribute($key, $value);
    }

    /**
     * Set the Form identifier
     *
     * @param $formId
     * @return $this
     */
    public function setFormId($formId)
    {
        $this->formId = $formId . '-';

        foreach ($this as $button) {
            $button->setAttribute('id', $this->formId . $button->getAttribute('id'));
        }
        return $this;
    }

    /**
     * Gets the form identifier
     *
     * @return string
     */
    public function getFormId()
    {
        return substr($this->formId, 0, -1);
    }
}


<?php
/**
 * YAWIK
 *
 * @filesource
 * @license MIT
 * @copyright  2013 - 2016 Cross Solution <http://cross-solution.de>
 */

/** */
namespace Core\Entity;

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;

/**
 * Implementation of \Core\Entity\MetaDataProviderInterface
 *
 * @author Mathias Gelhausen <gelhausen@cross-solution.de>
 * @since 0.27
 */
trait MetaDataProviderTrait
{
    /**
     * The meta data array.
     *
     * @ODM\Field(type="hash")
     * @var array
     */
    private $metaData = [];

    public function setMetaData($key, $value = null)
    {
        if (is_array($key)) {
            $this->metaData  = $key;
        } else {
            $this->metaData[$key] = $value;
        }

        return $this;
    }
    /**
     * Get meta data.
     *
     * Returns the whole meta data array, if no $key is provided.
     * Returns $default, if there is no meta data for the provided $key.
     *
     * @param null|string $key
     * @param null|mixed $default
     *
     * @return array|mixed|null
     */
    public function getMetaData($key = null, $default = null)
    {
        if (null === $key) {
            return $this->metaData;
        }

        return $this->hasMetaData($key) ? $this->metaData[$key] : $default;
    }

    public function hasMetaData($key)
    {
        return array_key_exists($key, $this->metaData);
    }
}


<?php
/**
 * @author Rik van der Kemp <rik@zicht.nl>
 * @copyright Zicht Online <http://www.zicht.nl>
 */

namespace Zicht\Bundle\PageBundle\Security\Voter;

use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;

/**
 * Checks on 'vote' whether or not the current user is and admin
 *
 * @package Zicht\Bundle\PageBundle\Security\Voter
 */
abstract class AbstractAdminAwareVoter extends AbstractVoter
{
    /**
     * Returns the vote for the given parameters.
     *
     * This method must return one of the following constants:
     * ACCESS_GRANTED, ACCESS_DENIED, or ACCESS_ABSTAIN.
     *
     * @param TokenInterface $token A TokenInterface instance
     * @param object $object The object to secure
     * @param array $attributes An array of attributes associated with the method being invoked
     *
     * @return integer either ACCESS_GRANTED, ACCESS_ABSTAIN, or ACCESS_DENIED
     */
    public function vote(TokenInterface $token, $object, array $attributes)
    {
        // ignore checks for switch user
        if (in_array('ROLE_PREVIOUS_ADMIN', $attributes)) {
            return VoterInterface::ACCESS_ABSTAIN;
        }

        /**
         * Admin users should see content no matter the scheduled dates
         * Since you can set the decision strategy to unanimous, you want to grant this explicitly
         */
        if (!is_null($object) && $this->supportsClass(get_class($object)) && sizeof($token->getRoles())) {
            /** @var \Symfony\Component\Security\Core\Role\Role $role */
            foreach ($token->getRoles() as $role) {
                if (in_array($role->getRole(), array('ROLE_ADMIN', 'ROLE_SUPER_ADMIN'))) {
                    return VoterInterface::ACCESS_GRANTED;
                }
            }
        }

        return VoterInterface::ACCESS_ABSTAIN;
    }
}


<?php

/*
 * This file is part of the Yawik project.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Core\Service;

use Core\Application;
use Interop\Container\ContainerInterface;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Finder\Finder;
use Zend\ModuleManager\Listener\ListenerOptions;
use Zend\Stdlib\Glob;

/**
 * Class CacheWarmupService
 *
 * @package Core\Service
 * @author Anthonius Munthi <me@itstoni.com>
 * @since 0.32
 */
class ClearCacheService
{
    /**
     * @var ListenerOptions
     */
    private $options;

    /**
     * @var Filesystem
     */
    private $filesystem;

    /**
     * @var SymfonyStyle
     */
    private $io;

    /**
     * Clear cache constructor.
     * @param ListenerOptions $options
     * @param Filesystem|null $filesystem
     */
    public function __construct(ListenerOptions $options, Filesystem $filesystem = null)
    {
        if (is_null($filesystem)) {
            $filesystem = new Filesystem();
        }
        $this->options = $options;
        $this->filesystem = $filesystem;
        if (php_sapi_name() === 'cli') {
            $this->io = new SymfonyStyle(new StringInput(''), new ConsoleOutput());
        }
    }

    /**
     * Creates new ClearCacheService object
     * @param ContainerInterface $container
     * @return ClearCacheService
     */
    public static function factory(ContainerInterface $container)
    {
        /* @var \Zend\ModuleManager\ModuleManager $manager */
        $config = $container->get('ApplicationConfig');
        $options = new ListenerOptions($config['module_listener_options']);
        return new static($options);
    }

    /**
     * Clear all cache files in cache directory.
     * Only cleans cache file in path/to/yawik/var/cache/*.php.
     * Files in sub cache directory will not be removed
     *
     * @throws \Exception when cache directory is null
     * @throws \Exception when cache directory is not exists or not writable
     */
    public function clearCache()
    {
        // do not clear cache when cache directory not exists
        $cacheDir = $this->options->getCacheDir();
        if (is_null($cacheDir)) {
            throw new \Exception(sprintf(
                'Cache directory is not configured properly.'
            ));
        }
        if (!is_dir($cacheDir) || !is_writable($cacheDir)) {
            throw new \Exception(
                sprintf(
                    'Can not clear cache in "%s". Please be sure that directory exists and writable.',
                    $cacheDir
                )
            );
        }
        $finder = Finder::create()
            ->in($cacheDir)
            ->ignoreDotFiles(false)
            ->name('*.php')
            ->name('.checksum')
            ->depth(0)
        ;
        try {
            $this->filesystem->remove($finder);
            return true;
        } catch (\Exception $e) {
            // just log the error
            $this->log('<error>'.$e->getMessage().'</error>');
            return false;
        }
    }

    /**
     * This function will check cache by creating md5 sum
     * from all file modification time in config/autoload/*.php.
     * If checksum is invalid it will automatically call clear cache.
     */
    public function checkCache()
    {
        $options = $this->options;

        $configDir = Application::getConfigDir();

        $cacheDir = $options->getCacheDir();

        if (!is_dir($cacheDir)) {
            mkdir($cacheDir, 0777, true);
        }

        $mtimes = [];
        $mtimes[] = filemtime($configDir.'/config.php');

        foreach ($options->getConfigGlobPaths() as $path) {
            foreach (Glob::glob($path, Glob::GLOB_BRACE) as $file) {
                $mtimes[] = filemtime($file);
            }
        }

        $checksum = md5(serialize($mtimes));

        $checksumFile = $options->getCacheDir().'/.checksum';
        if (!file_exists($checksumFile)) {
            touch($checksumFile);
        }
        if (is_readable($checksumFile)) {
            $cacheSum = file_get_contents($checksumFile);
            if ($cacheSum != $checksum) {
                $this->clearCache();
                file_put_contents($checksumFile, $checksum, LOCK_EX);
            }
        } else {
            $this->log("Can\'t process cache .checksum file is not readable.");
        }
    }

    private function log($message)
    {
        $io = $this->io;
        if ($io instanceof SymfonyStyle) {
            $io->writeln($message);
        }
    }
}


<?php
/**
 * YAWIK
 *
 * @filesource
 * @copyright (c) 2013 - 2016 Cross Solution (http://cross-solution.de)
 * @license   MIT
 * @author Carsten Bleek <bleek@cross-solution.de>
 * @author Mathias Gelhausen <gelhausen@cross-solution.de>
 * @author Mathias Weitz <weitz@xenon>
 * @author Rafal Ksiazek <harpcio@gmail.com>
 * @author Miroslav Fedeleš <miroslav.fedeles@gmail.com>
 */

/** FileEntity.php */
namespace Core\Entity;

use Auth\Entity\User;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Auth\Entity\UserInterface;

/**
 * stores files in MongoGridFS into the collection "files". You can override this.
 *
 * @ODM\Document(collection="files", repositoryClass="Core\Repository\File")
 * @ODM\InheritanceType("COLLECTION_PER_CLASS")
 */
class FileEntity extends AbstractIdentifiableEntity implements FileInterface
{
    /**
     * owner of an attachment. Typically this is the candidate who applies for a joboffer.
     *
     * @ODM\ReferenceOne(targetDocument="\Auth\Entity\User", storeAs="id") */
    protected $user;

    /**
     * Name of the attachment
     *
     * @ODM\Field */
    protected $name;

    /**
     * mimetype of the attachment.
     *
     * @ODM\Field(type="string") */
    protected $mimetype;

    /**
     * Binary data of the Attachment.
     *
    /** @ODM\File */
    protected $file;

    /**
     * Used by MongoGridFS. We don't use this. We use $dateUploaded instead.
     * @ODM\Field */
    protected $uploadDate;

    /**
     * date of uploaded file
     *
     * @ODM\Field(type="tz_date")
     */
    protected $dateUploaded;

    /** @ODM\Field */
    protected $length;

    /** @ODM\Field */
    protected $chunkSize;

    /** @ODM\Field */
    protected $md5;

    /**
     * @var PermissionsInterface
     * @ODM\EmbedOne(targetDocument="\Core\Entity\Permissions")
     */
    protected $permissions;

    /**
     * @return string
     */
    public function getResourceId()
    {
        return 'Entity/File';
    }

    /**
     * Sets the owner of a file
     *
     * @param UserInterface $user
     * @return $this
     */
    public function setUser(UserInterface $user)
    {
        if ($this->user) {
            $this->getPermissions()->revoke($this->user, Permissions::PERMISSION_ALL, false);
        }
        $this->user = $user;
        $this->getPermissions()->grant($user, Permissions::PERMISSION_ALL);

        return $this;
    }

    /**
     * Gets the owner of a file
     *
     * @return User
     */
    public function getUser()
    {
        return $this->user;
    }

    /**
     * Sets the name of a file
     *
     * @param $name
     * @return $this
     */
    public function setName($name)
    {
        $this->name = $name;
        return $this;
    }

    /**
     * Gets the name of a file
     *
     * @return mixed
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Gets the length of file in GB, MB ot kB format
     *
     * @return string
     */
    public function getPrettySize()
    {
        $size = $this->getLength();

        if ($size >= 1073741824) {
            return round($size / 1073741824, 2) . ' GB';
        }

        if ($size >= 1048576) {
            return round($size / 1048576, 2) . ' MB';
        }

        if ($size >= 1024) {
            return round($size / 1024, 2) . ' kB';
        }

        return (string)$size;
    }

    /**
     * Sets the mime-type of a file
     *
     * @param $mime
     * @return $this
     */
    public function setType($mime)
    {
        $this->mimetype = $mime;
        return $this;
    }

    /**
     * Gets the mime-type of a file
     *
     * @return mixed
     */
    public function getType()
    {
        return $this->mimetype;
    }

    /**
     * Sets the uploaded date of a file
     *
     * @param \DateTime|null $date
     * @return $this
     */
    public function setDateUploaded(\DateTime $date = null)
    {
        $this->dateUploaded = $date;
        return $this;
    }

    /**
     * Gets the uploaded date of a file
     *
     * @return mixed
     */
    public function getDateUploaded()
    {
        if (!$this->dateUploaded) {
            $this->setDateUploaded(new \DateTime());
        }
        return $this->dateUploaded;
    }

    /**
     * Gets the file
     *
     * @return mixed
     */
    public function getFile()
    {
        return $this->file;
    }

    /**
     * Sets the file
     *
     * @param $file
     * @return $this
     */
    public function setFile($file)
    {
        $this->setDateUploaded(new \DateTime());
        $this->file = $file;
        return $this;
    }

    /**
     * Gets the length of the file
     *
     * @return mixed
     */
    public function getLength()
    {
        return $this->length;
    }

    /**
     * Gets the resource of a file
     *
     * @return null|\stream
     */
    public function getResource()
    {
        if ($this->file instanceof \Doctrine\MongoDB\GridFSFile) {
            return $this->file->getMongoGridFSFile()->getResource();
        }
        return null;
    }

    /**
     * Returns the binary data of a file
     *
     * @see \Core\Entity\FileInterface::getContent()
     * @return null|string
     */
    public function getContent()
    {
        if ($this->file instanceof \Doctrine\MongoDB\GridFSFile) {
            return $this->file->getMongoGridFSFile()->getBytes();
        }
        return null;
    }

    /**
     * Sets Permissions of a file
     *
     * @param PermissionsInterface $permissions
     * @return $this
     */
    public function setPermissions(PermissionsInterface $permissions)
    {
        $this->permissions = $permissions;
        return $this;
    }

    /**
     * Gets Permissions of a file
     *
     * @return PermissionsInterface
     */
    public function getPermissions()
    {
        if (!$this->permissions) {
            $perms = new Permissions();
            if ($this->user instanceof UserInterface) {
                $perms->grant($this->user, PermissionsInterface::PERMISSION_ALL);
            }
            $this->setPermissions($perms);
        }
        return $this->permissions;
    }

    /**
     * @see \Core\Entity\FileInterface::getUri()
     * @since 0.27
     */
    public function getUri()
    {
    }
}


<?php
/**
 * @author Rik van der Kemp <rik@zicht.nl>
 * @copyright Zicht Online <http://www.zicht.nl>
 */

namespace Zicht\Bundle\PageBundle\Aliasing\Strategy;

use Zicht\Bundle\UrlBundle\Aliasing\AliasingStrategy;

/**
 * Class LanguageAwareAliasingStrategy
 *
 * @package Zicht\Bundle\UrlBundle\Url\Aliasing\Strategy
 */
class LanguageAwareAliasingStrategy implements AliasingStrategy
{
    /**
     * @var string
     */
    public $basePath;

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

    /**
     * @var AliasingStrategy
     */
    protected $strategyWrapper;

    /**
     * LanguageAwareAliasingStrategy constructor.
     *
     * @param AliasingStrategy $strategyWrapper
     * @param array $localesToPrefix
     */
    public function __construct(AliasingStrategy $strategyWrapper, $localesToPrefix = array())
    {
        $this->basePath = '/';
        $this->strategyWrapper = $strategyWrapper;
        $this->localesToPrefix = $localesToPrefix;
    }

    /**
     * Generate public alias
     *
     * @param mixed $subject
     * @param string $currentAlias
     * @return string
     */
    public function generatePublicAlias($subject, $currentAlias = '')
    {
        $alias = $this->strategyWrapper->generatePublicAlias($subject, $currentAlias);

        if ($alias !== null && method_exists($subject, 'getLanguage')) {
            if (in_array($subject->getLanguage(), $this->localesToPrefix)) {
                $alias = sprintf('%s%s%s', $this->basePath, $subject->getLanguage(), $alias);
            }
        }

        return $alias;
    }

    /**
     * @param array $localesToPrefix
     * @return void
     */
    public function setLocalesToPrefix($localesToPrefix)
    {
        $this->localesToPrefix = $localesToPrefix;
    }
}


<?php
/**
 * YAWIK
 *
 * @filesource
 * @license MIT
 * @copyright  2013 - 2018 Cross Solution <http://cross-solution.de>
 */

/** */
namespace Core\Service\EntityEraser;

use Core\Entity\EntityInterface;

/**
 * Event for collecting dependencies.
 *
 * @author Mathias Gelhausen <gelhausen@cross-solution.de>
 * @todo write test
 */
class DependencyResultEvent extends BaseEvent
{
    const CHECK_DEPENDENCIES = 'check-dependencies';
    const DELETE = 'delete';

    /**
     * @var EntityInterface
     */
    private $entity;

    /**
     * @var DependencyResultCollection
     */
    private $dependencyResultCollection;

    /**
     * DependencyResultEvent constructor.
     *
     * @param null|string $name
     * @param null|object $target
     * @param null|\Traversable|array $params
     */
    public function __construct($name = null, $target = null, $params = null)
    {
        parent::__construct($name, $target, $params);
        $this->resetDependencyResultCollection();
    }

    /**
     * @internal
     * this is needed, because Core\EventManager clones the event prototype
     * and internal object references are not cloned recursively.
     */
    public function __clone()
    {
        $this->resetDependencyResultCollection();
    }

    /**
     * Resets the dependencyResultCollection
     */
    private function resetDependencyResultCollection()
    {
        $this->dependencyResultCollection = new DependencyResultCollection();
    }

    /**
     * @return EntityInterface
     */
    public function getEntity()
    {
        return $this->entity;
    }

    /**
     * @param EntityInterface $entity
     *
     * @return self
     */
    public function setEntity($entity)
    {
        $this->entity = $entity;

        return $this;
    }

    /**
     * Gets the class name of the entity.
     *
     * @return string
     */
    public function getEntityClass()
    {
        return get_class($this->getEntity());
    }

    /**
     * Checks the entitys' type.
     *
     * @param string $class Type name to check against.
     *
     * @return bool
     */
    public function isEntityInstanceOf($class)
    {
        return $this->getEntity() instanceof $class;
    }

    /**
     * @return DependencyResultCollection
     */
    public function getDependencyResultCollection()
    {
        return $this->dependencyResultCollection;
    }

    /**
     * Shortcut to add dependencies to the collection.
     *
     * @param string|\Traversable|array $name
     * @param null|array|\Traversable       $entities
     * @param array|null $options
     *
     * @return DependencyResultCollection
     */
    public function addDependencies($name, $entities = null, array $options = null)
    {
        return $this->dependencyResultCollection->add($name, $entities, $options);
    }

    /**
     * Returns true, if this is a delete event.
     *
     * @return bool
     */
    public function isDelete()
    {
        return self::DELETE == $this->getName();
    }

    public function setParam($name, $value)
    {
        if ('entity' == $name) {
            $this->setEntity($value);
            return;
        }

        parent::setParam($name, $value);
    }

    public function setParams($params)
    {
        if (is_array($params) || $params instanceof \ArrayAccess) {
            if (isset($params['entity'])) {
                $this->setEntity($params['entity']);
                unset($params['entity']);
            }
        } elseif (is_object($params)) {
            if (isset($params->entity)) {
                $this->setEntity($params->entity);
                unset($params->entity);
            }
        }

        parent::setParams($params);
    }
}


<?php

namespace Core\Entity\Hydrator;

use Zend\Hydrator\AbstractHydrator;
use Zend\Hydrator\Filter\FilterComposite;
use Zend\Hydrator\Filter\MethodMatchFilter;
use Core\Entity\EntityInterface;

class EntityHydrator extends AbstractHydrator
{
    public function __construct()
    {
        parent::__construct();
        $this->init();
    }

    protected function init()
    {
    }

    /* (non-PHPdoc)
     * @see \Zend\Hydrator\HydratorInterface::extract()
     */
    public function extract($object)
    {
        if (!$object instanceof EntityInterface) {
            return array();
            //@todo Error-Handling
        }

        $getters = array_filter(
            get_class_methods($object),
            function ($methodName) use ($object) {
                return "get" === substr($methodName, 0, 3)
                && method_exists($object, 's' . substr($methodName, 1));
            }
        );

        $data = array();
        foreach ($getters as $getter) {
            $propertyValue = $object->$getter();
            $propertyName = lcfirst(substr($getter, 3));
            if ($this->getFilter()->filter($propertyName)) {
                $data[$propertyName] = $this->extractValue($propertyName, $propertyValue, $object);
            }
        }
        return $data;
    }

    /* (non-PHPdoc)
     * @see \Zend\Hydrator\HydratorInterface::hydrate()
     */
    public function hydrate(array $data, $object)
    {
        if (!$object instanceof EntityInterface) {
            return array();
            //@todo Error-Handling
        }
        $setters = array_filter(
            get_class_methods($object),
            function ($methodName) {
                return "set" === substr($methodName, 0, 3);
            }
        );

        foreach ($setters as $setter) {
            $propertyName = lcfirst(substr($setter, 3));
            if (isset($data[$propertyName])) {
                $value = $this->hydrateValue($propertyName, $data[$propertyName]);
                if (null !== $value) {
                    $object->$setter($value);
                }
                unset($data[$propertyName]);
            }
        }

        foreach ($data as $key => $value) {
            if ($value instanceof \MongoId) {
                $object->setId($value->__toString());
            }
        }

        return $object;
    }

    /**
     * exclude methods from the automatism
     *
     * @param $methods
     */
    public function setExcludeMethods($methods)
    {
        if (is_string($methods)) {
            $methods = array($methods);
        }
        foreach ($methods as $method) {
            $this->addFilter($method, new MethodMatchFilter($method), FilterComposite::CONDITION_AND);
        }
    }
}


<?php
/**
 * @author Rik van der Kemp <rik@zicht.nl>
 * @copyright Zicht Online <http://www.zicht.nl>
 */
namespace Zicht\Bundle\PageBundle\Command;

use Doctrine\DBAL\Connection;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;

/**
 * Class CleanAliasCommand
 *
 * Remove aliases referencing pages that do not exist anymore.
 */
class CleanAliasCommand extends ContainerAwareCommand
{
    /**
     * {@inheritDoc}
     */
    protected function configure()
    {
        $this
            ->setName('zicht:page:clean:alias')
            ->addArgument('locale', InputArgument::REQUIRED, 'Page locale to clean')
            ->setDescription('Clean aliases that are not attached to pages any more.');
    }

    /**
     * {@inheritDoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $locale = $input->getArgument('locale');

        /** @var Connection $connection */
        $connection = $this->getContainer()->get('doctrine')->getConnection();
        $stmt = $connection->prepare(
            'SELECT
                page.id as original_page_id,
                url_alias.id,
                url_alias.internal_url,
                url_alias.public_url,
                REPLACE(url_alias.internal_url, :url_pre_id, "") AS page_id
            FROM url_alias
            LEFT JOIN page ON page.id = REPLACE(url_alias.internal_url, :url_pre_id, \'\')
            WHERE internal_url LIKE :url_like
            AND page.id IS NULL'
        );
        $stmt->execute(
            [
                ':url_pre_id' => sprintf('/%s/page/', $locale),
                ':url_like' => sprintf('/%s/page/', sprintf('/%s/page/', $locale) . '%'),
            ]
        );

        $table = new Table($output);
        $table->setHeaders(['Alias', 'Internal URL', 'Stripped Id', 'Original Page Id']);

        $records = $stmt->fetchAll();

        if (!count($records)) {
            $output->writeln('No obsolete aliases found!');
            return 0;
        }

        foreach ($records as $record) {
            $table->addRow(
                [
                   $record['public_url'],
                   $record['internal_url'],
                   $record['page_id'],
                   $record['original_page_id'],
                ]
            );
        }

        $table->render();

        $helper = $this->getHelper('question');
        $confirm = new ConfirmationQuestion('Would you like to remove the above obsolete url aliases? [Y/n] : ', false);

        if (!$helper->ask($input, $output, $confirm)) {
            $output->writeln('Okay, bye');
            return 0;
        }

        $stmt->execute();

        $aliasIds = [];
        foreach ($stmt->fetchAll() as $record) {
            $aliasIds[] = $record['id'];
        }

        $connection->query(sprintf('DELETE FROM url_alias WHERE id IN (%s)', implode(', ', $aliasIds)));
        $output->writeln(sprintf('Removed %d aliases', count($records)));

        return 0;
    }
}


<?php
/**
 * YAWIK
 *
 * @filesource
 * @license MIT
 * @copyright  2013 - 2017 Cross Solution <http://cross-solution.de>
 */

/** */
namespace Core\View\Helper;

use Core\View\Helper\Proxy\HelperProxy;
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
use Zend\ServiceManager\Exception\ServiceNotFoundException;
use Zend\View\Helper\AbstractHelper;

/**
 * View helper to safely use module specific view helpers in other modules.
 *
 * In most scenarios, to make something failsafe, it takes a lot of (redundant) code.
 * With this proxy helper, this can be simplified by just adding a few characters.
 *
 *

 *
 *      //
 *      // ORIGINAL
 *      //
 *      <?=$this->helper()->someOutput()?>
 *
 *      //
 *      // Failsafe code
 *      //
 *      <? if ($this->getHelperPluginManager()->has('plugin')):
 *              $plugin = $this->getHelperPluginManager()->get('helper');
 *
 *              // do something with the plugin, i.E. call some method
 *              echo $plugin->someOutput();
 *         else:
 *              // helper does not exist.
 *              // do something to hanlde this case.
 *              echo '';
 *         endif;
 *      ?>
 *
 *      //
 *      // Failsafe with Proxy:
 *      //
 *      <?=$this->proxy('helper')->someOutput()?>
 *
 *      // Proxy view helper will return a {@link \Core\View\Helper\Proxy\HelperProxy} instance
 *      // which will return null on every method call, if the helper does not exist.
 *

 *
 * Advanced usage:
 *
 * - Load a helper without risking an exception:
 *   This will return a {@link \Core\View\Helper\Proxy\HelperProxy} instance.
 *
<? $helper = $this->proxy()->plugin('HelperServiceName', [options])?>

 *
 * - Call an invokable helper and expect an array returned.
 *   This will return an empty array, if the helper does not exist.
 *
<? $array = $this->proxy('helper', Proxy::EXPECT_ARRAY)?>

 *
 * - Expect other return types:
 *

 *      // Object (which is default)
 *      $helper = $this->proxy('helper', Proxy::EXPECT_OBJECT);
 *
 *      // Iterator (returns an IteratorAggregate which returns an empty ArrayIterator.
 *      $helper = $this->proxy('helper', Proxy::EXPECT_ITERATOR);
 *
 *      // Skalar values: Simply specify the expected result, which will be returned,
 *      // if the helper does not exist.
 *      $value = $this->proxy('helper', ''); // return empty string.
 *      $value = $this->proxy('helper', false) // return FALSE
 *

 *
 * - Directly calls an invokable helper with Arguments
 *

 *      <?=$this->proxy('invokableHelper', ['arg1', 'arg2'], false)?>
 *

 *
 * - Call a method on a plugin
 *   (see {@link HelperProxy::call()})
 *

 *      $this->proxy('helper')->call('method', ['arg1',...], 'expectedValue');
 *

 *
 * - Call consecutive methods on plugin
 *   (see {@link HelperProxy::consecutive()})
 *

 *      $this->proxy('helper')->consecutive(['method1', 'method2' => ['arg1', ... ], ...]);
 *

 *
 * - Call chained methods on plugin
 *   (see {@link HelperProxy::chain()})
 *

 *      $this->proxy('helper')->chain(['method' => ['arg'], 'method2'], 'expectedValue');
 *

 *
 * @author Mathias Gelhausen <gelhausen@cross-solution.de>
 * @since 0.29
 */
class Proxy extends AbstractHelper
{
    /**
     * Loads and possiblity executes a view helper.
     *
     * @param null|string   $plugin
     * @param array|string  $args
     * @param mixed         $expect
     *
     * @return mixed|self|ProxyNoopHelper|ProxyNoopIterator
     */
    public function __invoke($plugin = null, $args = null, $expect = HelperProxy::EXPECT_SELF)
    {
        if (null === $plugin) {
            return $this;
        }

        $plugin = $this->plugin($plugin);

        if (null === $args) {
            return $plugin;
        }

        return $plugin->call('__invoke', $args, $expect);
    }

    /**
     * Loads a plugin from the plugin helper manager.
     *
     * Returns false, if either the helper plugin manager cannot be
     * retrieved from the renderer or the requested plugin does not exist.
     *
     * @param string $plugin
     * @param true|array  $options if true, only return if plugin exists or not.
     *
     * @return bool|HelperProxy
     */
    public function plugin($plugin, $options = null)
    {
        $renderer = $this->getView();

        if (!method_exists($renderer, 'getHelperPluginManager')) {
            return true === $options ? false : new HelperProxy(false);
        }

        /* @var \Zend\View\HelperPluginManager $manager */
        $manager   = $renderer->getHelperPluginManager();
        $hasPlugin = $manager->has($plugin);

        if (true === $options) {
            return $hasPlugin;
        }

        if ($hasPlugin) {
            $pluginInstance = $manager->get($plugin, $options);
        } else {
            $pluginInstance = false;
        }

        return new HelperProxy($pluginInstance);
    }

    /**
     * Does a plugin exists?
     *
     * @param string $plugin
     *
     * @return bool
     */
    public function exists($plugin)
    {
        return $this->plugin($plugin, true);
    }
}


<?php
/**
 * YAWIK
 *
 * @filesource
 * @copyright (c) 2013 - 2016 Cross Solution (http://cross-solution.de)
 * @license   MIT
 */
namespace Core\Entity;

use Core\Repository\RepositoryService;

/**
 *  Manager for attached entities.
 *
 * @author Miroslav Fedeleš <miroslav.fedeles@gmail.com>
 * @author Mathias Gelhausen <gelhausen@cross-solution.de>
 * @since 0.28
 * @since 0.29 Add ability to create attached entity (which are added automatically)
 */
class AttachableEntityManager
{

    /**
     * @var RepositoryService
     */
    protected $repositories;

    /**
     * @var array
     */
    protected $references = [];

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

    /**
     * @param array $references
     * @return AttachableEntityManager
     */
    public function setReferences(array & $references)
    {
        $this->references = & $references;

        return $this;
    }

    /**
     * Adds an $entity using an optional $key.
     * If $key is not provided then $entity's FQCN will be used as a key
     * Any existing $entity with the same $key will be replaced.
     *
     * @param IdentifiableEntityInterface $entity
     * @param string $key
     * @return AttachableEntityManager
     */
    public function addAttachedEntity(IdentifiableEntityInterface $entity, $key = null)
    {
        $className = get_class($entity);

        if (! isset($key)) {
            $key = $className;
        }

        $reference = [
            'repository' => $className
        ];
        $entityId = $entity->getId();

        // check if entity is not persisted
        if (! $entityId) {
            // persist entity & retrieve its ID
            $this->repositories->getRepository($className)->store($entity);
            $entityId = $entity->getId();
        }

        $reference['id'] = $entityId;
        $this->references[$key] = $reference;

        return $this;
    }

    /**
     * Creates an entity and adds it.
     *
     * @param string        $entityClass
     * @param array|string  $values
     * @param null|string   $key
     *
     * @return \Core\Entity\EntityInterface
     * @since 0.29
     */
    public function createAttachedEntity($entityClass, $values = [], $key = null)
    {
        if (is_string($values)) {
            $key = $values;
            $values = [];
        }

        $entity = $this->repositories->getRepository($entityClass)->create($values);

        $this->addAttachedEntity($entity, $key);

        return $entity;
    }

    /**
     * @param string $key
     * @return IdentifiableEntityInterface|null
     */
    public function getAttachedEntity($key)
    {
        if (! isset($this->references[$key])) {
            return;
        }

        $reference = $this->references[$key];
        $entity = $this->repositories->getRepository($reference['repository'])
            ->find($reference['id']);

        if (! $entity) {
            // remove reference if entity does not exists
            unset($this->references[$key]);
        }

        return $entity;
    }

    /**
     * @param string $key
     * @return bool Whether entity with given $key existed
     */
    public function removeAttachedEntity($key)
    {
        if (isset($this->references[$key])) {
            unset($this->references[$key]);
            return true;
        }

        return false;
    }
}


<?php
/**
 * @author Gerard van Helden <gerard@zicht.nl>
 * @copyright Zicht Online <http://zicht.nl>
 */

namespace Zicht\Bundle\PageBundle\Controller;

use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Zicht\Bundle\PageBundle\Model\PageInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Zicht\Bundle\PageBundle\Entity\ControllerPageInterface;
use Symfony\Component\HttpFoundation\Request;
use Zicht\Bundle\PageBundle\Model\ViewValidationInterface;

/**
 * Controller for public page actions
 */
class PageController extends AbstractController
{
    /**
     * Redirects to the page identified by the passed id.
     *
     * @param string $id
     * @return Response
     *
     * @Route("page/{id}/redirect", name="page_redirect")
     */
    public function redirectAction($id)
    {
        return new RedirectResponse($this->generateUrl('zicht_page_page_view', array('id' => $id)));
    }

    /**
     * Redirects to the specified page. This is useful for posting an autocomplete ID, which in turn redirects to
     * the specified page.
     *
     * @param Request $r
     * @return Response
     *
     * @Route("/goto")
     */
    public function gotoAction(Request $r)
    {
        return $this->redirect(
            $this->get('zicht_url.provider')->url($this->getPageManager()->findForView($r->get('id')))
        );
    }

    /**
     * View a page.
     *
     * @param Request $request
     * @param string $id
     * @return Response
     *
     * @Route("page/{id}")
     */
    public function viewAction(Request $request, $id)
    {
        /** @var $pageManager \Zicht\Bundle\PageBundle\Manager\PageManager */
        $pageManager = $this->getPageManager();
        $page = $pageManager->findForView($id);

        if (null !== ($validator = $this->getViewActionValidator())) {
            $validator->validate($page);
        }

        if ($page instanceof ControllerPageInterface) {
            return $this->forward(
                $page->getController(),
                (array)$page->getControllerParameters()
                + array(
                    'parameters' => $request->query->all(),
                    '_locale'       => $request->attributes->get('_locale'),
                    '_internal_url' => $request->attributes->get('_internal_url'),
                ),
                $request->query->all()
            );
        }

        return $this->renderPage($page);
    }

    /**
     * @return null|ViewValidationInterface
     */
    protected function getViewActionValidator()
    {
        if (($validator = $this->get('zicht_page.controller.view_validator')) instanceof ViewValidationInterface) {
            return $validator;
        }
        return null;
    }

    /**
     * Render a page with the specified additional template variables.
     *
     * @param PageInterface $page
     * @param array $vars
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function renderPage(PageInterface $page, $vars = array())
    {
        return $this->render(
            $this->getPageManager()->getTemplate($page),
            $vars + array(
                'page' => $page,
                'id' => $page->getId(),
            )
        );
    }
}