<?php

/*
 * This file is part of the Active Collab DatabaseObject project.
 *
 * (c) A51 doo <info@activecollab.com>. All rights reserved.
 */

namespace ActiveCollab\DatabaseObject;

use ActiveCollab\ContainerAccess\ContainerAccessInterface;
use ActiveCollab\ContainerAccess\ContainerAccessInterface\Implementation as ContainerAccessInterfaceImplementation;
use ActiveCollab\DatabaseConnection\ConnectionInterface;
use ActiveCollab\DatabaseObject\Entity\EntityInterface;
use ActiveCollab\DatabaseObject\Exception\ObjectNotFoundException;
use InvalidArgumentException;
use LogicException;
use Psr\Log\LoggerInterface;
use ReflectionClass;
use RuntimeException;

/**
 * @package ActiveCollab\DatabaseObject
 */
class Pool implements PoolInterface, ProducerInterface, ContainerAccessInterface
{
    use ContainerAccessInterfaceImplementation;

    /**
     * @var ConnectionInterface
     */
    protected $connection;

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

    /**
     * @var EntityInterface[]
     */
    private $objects_pool = [];

    /**
     * @param ConnectionInterface  $connection
     * @param LoggerInterface|null $log
     */
    public function __construct(ConnectionInterface $connection, LoggerInterface &$log = null)
    {
        $this->connection = $connection;
        $this->log = $log;
    }

    /**
     * Produce new instance of $type.
     *
     * @param  string          $type
     * @param  array|null      $attributes
     * @param  bool            $save
     * @return EntityInterface
     */
    public function &produce($type, array $attributes = null, $save = true)
    {
        $registered_type = $this->requireRegisteredType($type);

        $object = $this->getProducerForRegisteredType($registered_type)->produce($type, $attributes, $save);

        if ($object instanceof EntityInterface) {
            if ($object->isLoaded()) {
                $this->objects_pool[$registered_type][$object->getId()] = $object;
            }

            return $object;
        } else {
            throw new RuntimeException("Failed to produce an instance of '$type'");
        }
    }

    /**
     * Update an instance.
     *
     * @param  EntityInterface $instance
     * @param  array|null      $attributes
     * @param  bool            $save
     * @return EntityInterface
     */
    public function &modify(EntityInterface &$instance, array $attributes = null, $save = true)
    {
        if ($instance->isNew()) {
            throw new RuntimeException('Only objects that are saved to database can be modified');
        }

        $registered_type = $this->requireRegisteredType(get_class($instance));

        $instance = $this->getProducerForRegisteredType($registered_type)->modify($instance, $attributes, $save);

        if ($instance instanceof EntityInterface) {
            $this->objects_pool[$registered_type][$instance->getId()] = $instance;
        }

        return $instance;
    }

    /**
     * Scrap an instance (move it to trash, if object supports, or delete it).
     *
     * @param  EntityInterface $instance
     * @param  bool            $force_delete
     * @return EntityInterface
     */
    public function &scrap(EntityInterface &$instance, $force_delete = false)
    {
        if ($instance->isNew()) {
            throw new RuntimeException('Only objects that are saved to database can be modified');
        }

        $registered_type = $this->requireRegisteredType(get_class($instance));

        $instance_id = $instance->getId();
        $instance = $this->getProducerForRegisteredType($registered_type)->scrap($instance, $force_delete);

        if ($instance->isNew() && !empty($this->objects_pool[$registered_type][$instance_id])) {
            unset($this->objects_pool[$registered_type][$instance_id]);
        }

        return $instance;
    }

    /**
     * @var ProducerInterface
     */
    private $default_producer;

    /**
     * @var ProducerInterface[]
     */
    private $producers = [];

    /**
     * Return producer for registered type.
     *
     * @param  string            $registered_type
     * @return ProducerInterface
     */
    protected function &getProducerForRegisteredType($registered_type)
    {
        if (empty($this->producers[$registered_type])) {
            if (empty($this->default_producer)) {
                $default_producer_class = $this->getDefaultProducerClass();

                $this->default_producer = new $default_producer_class($this->connection, $this);

                if ($this->default_producer instanceof ContainerAccessInterface && $this->hasContainer()) {
                    $this->default_producer->setContainer($this->getContainer());
                }
            }

            return $this->default_producer;
        } else {
            return $this->producers[$registered_type];
        }
    }

    /**
     * @return string
     */
    public function getDefaultProducerClass()
    {
        return Producer::class;
    }

    /**
     * Register producer instance for the given type.
     *
     * @param string            $type
     * @param ProducerInterface $producer
     */
    public function registerProducer($type, ProducerInterface $producer)
    {
        $registered_type = $this->requireRegisteredType($type);

        if (empty($this->producers[$registered_type])) {
            if ($producer instanceof ContainerAccessInterface && $this->hasContainer()) {
                $producer->setContainer($this->getContainer());
            }

            $this->producers[$registered_type] = $producer;
        } else {
            throw new LogicException("Producer for '$type' is already registered");
        }
    }

    /**
     * Register producerby providing a producer class name.
     *
     * @param string $type
     * @param string $producer_class
     */
    public function registerProducerByClass($type, $producer_class)
    {
        if (class_exists($producer_class)) {
            $producer_class_reflection = new ReflectionClass($producer_class);

            if ($producer_class_reflection->implementsInterface(ProducerInterface::class)) {
                $this->registerProducer($type, new $producer_class($this->connection, $this));
            } else {
                throw new InvalidArgumentException("Class '$producer_class' does not implement '" . ProducerInterface::class . "' interface");
            }
        }
    }

    /**
     * Return object from object pool by the given type and ID; if object is not found, return NULL.
     *
     * @param  string                   $type
     * @param  int                      $id
     * @param  bool                     $use_cache
     * @return object
     * @throws InvalidArgumentException
     */
    public function &getById($type, $id, $use_cache = true)
    {
        $registered_type = $this->requireRegisteredType($type);

        $id = (int) $id;

        if ($id < 1) {
            throw new InvalidArgumentException('ID is expected to be a number larger than 0');
        }

        if (isset($this->objects_pool[$registered_type][$id]) && $use_cache) {
            return $this->objects_pool[$registered_type][$id];
        } else {
            $type_fields = $this->getTypeFields($registered_type);

            if ($row = $this->connection->executeFirstRow($this->getSelectOneByType($registered_type), [$id])) {
                $object_class = in_array('type', $type_fields) ? $row['type'] : $type;

                /** @var object|EntityInterface $object */
                $object = new $object_class($this->connection, $this, $this->log);

                if ($object instanceof ContainerAccessInterface && $this->hasContainer()) {
                    $object->setContainer($this->getContainer());
                }

                $object->loadFromRow($row);

                return $this->addToObjectPool($registered_type, $id, $object);
            } else {
                $object = null;

                return $this->addToObjectPool($registered_type, $id, $object);
            }
        }
    }

    /**
     * Return object from object pool by the given type and ID; if object is not found, raise an exception.
     *
     * @param  string                   $type
     * @param  int                      $id
     * @param  bool                     $use_cache
     * @return object
     * @throws ObjectNotFoundException
     * @throws InvalidArgumentException
     */
    public function &mustGetById($type, $id, $use_cache = true)
    {
        $result = $this->getById($type, $id, $use_cache);

        if (empty($result)) {
            throw new ObjectNotFoundException($type, $id);
        }

        return $result;
    }

    /**
     * Reload an object of the give type with the given ID.
     *
     * @param  string $type
     * @param  int    $id
     * @return object
     */
    public function &reload($type, $id)
    {
        return $this->getById($type, $id, false);
    }

    /**
     * Check if object #ID of $type is in the pool.
     *
     * @param  string $type
     * @param  int    $id
     * @return bool
     */
    public function isInPool($type, $id)
    {
        $this->requireRegisteredType($type);

        $id = (int) $id;

        if ($id < 1) {
            throw new InvalidArgumentException('ID is expected to be a number larger than 0');
        }

        return isset($this->objects_pool[$type][$id]);
    }

    /**
     * Add object to the object pool.
     *
     * @param  string               $registered_type
     * @param  int                  $id
     * @param  EntityInterface|null $value_to_store
     * @return EntityInterface
     */
    private function &addToObjectPool($registered_type, $id, &$value_to_store)
    {
        if (empty($this->objects_pool[$registered_type])) {
            $this->objects_pool[$registered_type] = [];
        }

        $this->objects_pool[$registered_type][$id] = $value_to_store;

        return $this->objects_pool[$registered_type][$id];
    }

    /**
     * Add object to the pool.
     *
     * @param EntityInterface $object
     */
    public function remember(EntityInterface &$object)
    {
        if ($object->isLoaded()) {
            $this->addToObjectPool($this->requireRegisteredType(get_class($object)), $object->getId(), $object);
        } else {
            throw new InvalidArgumentException('Object needs to be saved in the database to be remembered');
        }
    }

    /**
     * {@inheritdoc}
     */
    public function forget($type, $id)
    {
        $this->requireRegisteredType($type);

        $ids_to_forget = (array) $id;

        foreach ($ids_to_forget as $id_to_forget) {
            $id_to_forget = (int) $id_to_forget;

            if ($id_to_forget < 1) {
                throw new InvalidArgumentException('ID is expected to be a number larger than 0');
            }

            if (isset($this->objects_pool[$type][$id_to_forget])) {
                unset($this->objects_pool[$type][$id_to_forget]);
            }
        }
    }

    /**
     * Return number of records of the given type that match the given conditions.
     *
     * @param  string            $type
     * @param  array|string|null $conditions
     * @return int
     */
    public function count($type, $conditions = null)
    {
        $this->requireRegisteredType($type);

        if ($conditions = $this->connection->prepareConditions($conditions)) {
            return $this->connection->executeFirstCell('SELECT COUNT(`id`) AS "row_count" FROM ' . $this->getTypeTable($type, true) . " WHERE $conditions");
        } else {
            return $this->connection->executeFirstCell('SELECT COUNT(`id`) AS "row_count" FROM ' . $this->getTypeTable($type, true));
        }
    }

    /**
     * Return true if object of the given type with the given ID exists.
     *
     * @param  string $type
     * @param  int    $id
     * @return bool
     */
    public function exists($type, $id)
    {
        return (bool) $this->count($type, ['`id` = ?', $id]);
    }

    /**
     * Find records by type.
     *
     * @param  string $type
     * @return Finder
     */
    public function find($type)
    {
        $registered_type = $this->requireRegisteredType($type);

        $default_finder_class = $this->getDefaultFinderClass();

        /** @var Finder $finder */
        $finder = new $default_finder_class($this->connection, $this, $this->log, $registered_type);

        if ($finder instanceof ContainerAccessInterface && $this->hasContainer()) {
            $finder->setContainer($this->getContainer());
        }

        return $finder;
    }

    /**
     * @return string
     */
    public function getDefaultFinderClass()
    {
        return Finder::class;
    }

    /**
     * {@inheritdoc}
     */
    public function findBySql($type, $sql, ...$arguments)
    {
        if (empty($type)) {
            throw new InvalidArgumentException('Type is required');
        }

        if (empty($sql)) {
            throw new InvalidArgumentException('SQL statement is required');
        }

        $this->requireRegisteredType($type);

        if (in_array('type', $this->getTypeFields($type))) {
            $return_by = ConnectionInterface::RETURN_OBJECT_BY_FIELD;
            $return_by_value = 'type';
        } else {
            $return_by = ConnectionInterface::RETURN_OBJECT_BY_CLASS;
            $return_by_value = $type;
        }

        if ($this->hasContainer()) {
            return $this->connection->advancedExecute($sql, $arguments, ConnectionInterface::LOAD_ALL_ROWS, $return_by, $return_by_value, [&$this->connection, &$this, &$this->log], $this->getContainer());
        } else {
            return $this->connection->advancedExecute($sql, $arguments, ConnectionInterface::LOAD_ALL_ROWS, $return_by, $return_by_value, [&$this->connection, &$this, &$this->log]);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getTypeTable($type, $escaped = false)
    {
        $registered_type = $this->requireRegisteredType($type);

        if ($escaped) {
            if (empty($this->types[$registered_type]['escaped_table_name'])) {
                $this->types[$registered_type]['escaped_table_name'] = $this->connection->escapeTableName($this->types[$registered_type]['table_name']);
            }

            return $this->types[$registered_type]['escaped_table_name'];
        } else {
            return $this->types[$registered_type]['table_name'];
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getTypeFields($type)
    {
        return $this->types[$this->requireRegisteredType($type)]['fields'];
    }

    /**
     * {@inheritdoc}
     */
    public function getGeneratedTypeFields($type)
    {
        return $this->types[$this->requireRegisteredType($type)]['generated_fields'];
    }

    /**
     * Get a particular type property, and make it (using $callback) if it is not set already.
     *
     * @param  string   $type
     * @param  string   $property
     * @param  callable $callback
     * @return mixed
     */
    public function getTypeProperty($type, $property, callable $callback)
    {
        $registered_type = $this->requireRegisteredType($type);

        if (!array_key_exists($property, $this->types[$registered_type])) {
            $this->types[$registered_type][$property] = call_user_func($callback, $registered_type, $property);
        }

        return $this->types[$registered_type][$property];
    }

    /**
     * Return select by ID-s query for the given type.
     *
     * @param  string $type
     * @return string
     */
    private function getSelectOneByType($type)
    {
        if (empty($this->types[$type]['sql_select_by_ids'])) {
            $this->types[$type]['sql_select_by_ids'] = 'SELECT ' . $this->getEscapedTypeFields($type) . ' FROM ' . $this->getTypeTable($type, true) . ' WHERE `id` IN ? ORDER BY `id`';
        }

        return $this->types[$type]['sql_select_by_ids'];
    }

    /**
     * Return a list of escaped field names for the given type.
     *
     * @param  string $type
     * @return string
     */
    public function getEscapedTypeFields($type)
    {
        return $this->getTypeProperty($type, 'escaped_fields', function () use ($type) {
            $table_name = $this->getTypeTable($type, true);

            $escaped_field_names = [];

            foreach ($this->getTypeFields($type) as $field_name) {
                $escaped_field_names[] = $table_name . '.' . $this->connection->escapeFieldName($field_name);
            }

            foreach ($this->getGeneratedTypeFields($type) as $field_name) {
                $escaped_field_names[] = $table_name . '.' . $this->connection->escapeFieldName($field_name);
            }

            return implode(',', $escaped_field_names);
        });
    }

    /**
     * Return default order by for the given type.
     *
     * @param  string   $type
     * @return string[]
     */
    public function getTypeOrderBy($type)
    {
        return $this->types[$this->requireRegisteredType($type)]['order_by'];
    }

    /**
     * Return escaped list of fields that we can order by.
     *
     * @param  string $type
     * @return string
     */
    public function getEscapedTypeOrderBy($type)
    {
        return $this->getTypeProperty($type, 'escaped_order_by', function () use ($type) {
            $table_name = $this->getTypeTable($type, true);

            return implode(',', array_map(function ($field_name) use ($table_name) {
                if (substr($field_name, 0, 1) == '!') {
                    return $table_name . '.' . $this->connection->escapeFieldName(substr($field_name, 1)) . ' DESC';
                } else {
                    return $table_name . '.' . $this->connection->escapeFieldName($field_name);
                }
            }, $this->getTypeOrderBy($type)));
        });
    }

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

    /**
     * @return array
     */
    public function getRegisteredTypes()
    {
        return array_keys($this->types);
    }

    /**
     * Cached type to registered type map.
     *
     * @var array
     */
    private $known_types = [];

    /**
     * Return registered type for the given $type. This function is subclassing aware.
     *
     * @param  string      $type
     * @return string|null
     */
    public function getRegisteredType($type)
    {
        $type = ltrim($type, '\\');

        if (empty($this->known_types[$type])) {
            if (isset($this->types[$type])) {
                $this->known_types[$type] = $type;
            } else {
                if (class_exists($type, true)) {
                    $reflection_class = new ReflectionClass($type);

                    if ($reflection_class->implementsInterface(EntityInterface::class)) {
                        foreach ($this->types as $registered_type => $registered_type_properties) {
                            if ($reflection_class->isSubclassOf($registered_type)) {
                                $this->known_types[ $type ] = $registered_type;
                                break;
                            }
                        }
                    }
                }

                if (empty($this->known_types[$type])) {
                    $this->known_types[$type] = null;
                }
            }
        }

        return $this->known_types[$type];
    }

    /**
     * {@inheritdoc}
     */
    public function requireRegisteredType($type)
    {
        if ($registered_type = $this->getRegisteredType($type)) {
            return $registered_type;
        } else {
            throw new InvalidArgumentException("Type '$type' is not registered");
        }
    }

    /**
     * Return true if $type is registered.
     *
     * @param  string $type
     * @return bool
     */
    public function isTypeRegistered($type)
    {
        return (bool) $this->getRegisteredType($type);
    }

    /**
     * Return true if $type is polymorph (has type column that is used to figure out a class of individual record).
     *
     * @param  string $type
     * @return bool
     */
    public function isTypePolymorph($type)
    {
        return in_array('type', $this->getTypeFields($type));
    }

    /**
     * @param string[] $types
     */
    public function registerType(...$types)
    {
        foreach ($types as $type) {
            $type = ltrim($type, '\\');

            if (class_exists($type, true)) {
                $reflection = new ReflectionClass($type);

                if ($reflection->implementsInterface(EntityInterface::class)) {
                    $default_properties = $reflection->getDefaultProperties();

                    if (empty($default_properties['order_by'])) {
                        $default_properties['order_by'] = '';
                    }

                    $this->types[$type] = [
                        'table_name' => $default_properties['table_name'],
                        'fields' => $default_properties['fields'],
                        'generated_fields' => $default_properties['generated_fields'],
                        'order_by' => $default_properties['order_by'],
                    ];
                } else {
                    throw new InvalidArgumentException("Type '$type' does not implement '" . EntityInterface::class . "' interface");
                }
            } else {
                throw new InvalidArgumentException("Type '$type' is not defined");
            }
        }
    }

    /**
     * Return trait names by object.
     *
     * Note: $type does not need to be directly registered, because we need to support subclasses, which call can have
     * different traits impelemnted!
     *
     * @param  string $type
     * @return array
     */
    public function getTraitNamesByType($type)
    {
        if (empty($this->types[$type]['traits'])) {
            $this->types[$type]['traits'] = [];

            $this->recursiveGetTraitNames(new ReflectionClass($type), $this->types[ $type ]['traits']);
        }

        return $this->types[$type]['traits'];
    }

    /**
     * Recursively get trait names for the given class.
     *
     * @param ReflectionClass $class
     * @param array           $trait_names
     */
    private function recursiveGetTraitNames(ReflectionClass $class, array &$trait_names)
    {
        $trait_names = array_merge($trait_names, $class->getTraitNames());

        if ($class->getParentClass()) {
            $this->recursiveGetTraitNames($class->getParentClass(), $trait_names);
        }
    }
}


<?php
/*
 * Copyright (c) Atsuhiro Kubo <kubo@iteman.jp>,
 * All rights reserved.
 *
 * This file is part of PHPMentorsPageflowerBundle.
 *
 * This program and the accompanying materials are made available under
 * the terms of the BSD 2-Clause License which accompanies this
 * distribution, and is available at http://opensource.org/licenses/BSD-2-Clause
 */

namespace PHPMentors\PageflowerBundle\Conversation;

use PHPMentors\DomainKata\Entity\EntityInterface;
use PHPMentors\DomainKata\Repository\RepositoryInterface;

class ConversationRepository implements RepositoryInterface
{
    /**
     * @var ConversationCollectionInterface
     */
    private $conversationCollection;

    /**
     * {@inheritdoc}
     */
    public function add(EntityInterface $entity)
    {
        assert($entity instanceof Conversation);

        $this->conversationCollection[$entity->getConversationId()] = $entity;
    }

    /**
     * @param ConversationCollectionInterface $conversationCollection
     */
    public function setConversationCollection(ConversationCollectionInterface $conversationCollection)
    {
        $this->conversationCollection = $conversationCollection;
    }

    /**
     * @param string $conversationId
     *
     * @return Conversation
     */
    public function findByConversationId($conversationId)
    {
        return $this->conversationCollection[$conversationId];
    }

    /**
     * {@inheritdoc}
     */
    public function remove(EntityInterface $entity)
    {
        assert($entity instanceof Conversation);

        unset($this->conversationCollection[$entity->getConversationId()]);
    }
}


<?php

/**
 * Part of the Antares package.
 *
 * NOTICE OF LICENSE
 *
 * Licensed under the 3-clause BSD License.
 *
 * This source file is subject to the 3-clause BSD License that is
 * bundled with this package in the LICENSE file.
 *
 * @package    Access Control
 * @version    0.9.0
 * @author     Antares Team
 * @license    BSD License (3-clause)
 * @copyright  (c) 2017, Antares
 * @link       http://antaresproject.io
 */

namespace Antares\Acl\Adapter;

use Antares\Html\Form\Grid;
use Antares\Acl\Contracts\ControlsAdapter as AdapterContract;
use Antares\Contracts\Html\Form\Fieldset;
use Illuminate\Support\Fluent;

class ControlsAdapter implements AdapterContract
{

    /**
     * adaptee controls on form instance
     *
     * @param FormFactory $form
     * @param array $controls
     * @param Fluent $model
     * @return boolean
     */
    public function adaptee(Grid &$grid, array $controls = array(), Fluent $model)
    {
        if (empty($controls)) {
            return false;
        }
        $grid->fieldset(function (Fieldset $fieldset) use($controls, $model) {
            $editable    = $model->get('editable', []);
            $displayable = $model->get('displayable', []);

            foreach ($controls as $control) {
                $name = $control['name'];

                $fieldset->control($control['type'], $name)
                        ->label($control['label'])
                        ->value($control['value'])
                        ->attributes(['disabled' => 'disabled', 'readonly' => 'readonly']);

                $fieldset->control('input:checkbox', 'editable[' . $name . ']')
                        ->value($control['value'])
                        ->label(trans('editable'))
                        ->attributes($this->checked($control, $editable));

                $fieldset->control('input:checkbox', 'displayable[' . $name . ']')
                        ->value($control['value'])
                        ->label(trans('displayable'))
                        ->attributes($this->checked($control, $displayable));
            }
        });
    }

    /**
     * verify whether checkbox should be checked
     *
     * @param array $control
     * @param array $displayable
     * @return string
     */
    protected function checked(array $control, array $displayable)
    {
        if (empty($displayable)) {
            return ['checked' => 'checked'];
        }
        foreach ($displayable as $item) {
            if ($control['name'] == $item['name']) {
                return ['checked' => 'checked'];
            }
        }
        return [];
    }

}


<?php
/**
 * Copyright © OXID eSales AG. All rights reserved.
 * See LICENSE file for license details.
 */

namespace OxidEsales\EcondaTrackingComponent\Adapter;

use OxidEsales\EcondaTrackingComponent\Adapter\Helper\ActiveUserDataProvider;
use OxidEsales\EcondaTrackingComponent\Adapter\Modifiers\EntityModifierByCurrentAction;
use OxidEsales\EcondaTrackingComponent\Adapter\Modifiers\OrderStepsMapModifier;
use OxidEsales\EcondaTrackingComponent\Adapter\Modifiers\PageMapModifier;
use OxidEsales\EcondaTrackingComponent\Adapter\Modifiers\EntityModifierByCurrentBasketAction;
use OxidEsales\EcondaTrackingComponent\Adapter\Page\PageIdentifiers;
use OxidEsales\EcondaTrackingComponent\TrackingCodeGenerator\ActivePageEntityInterface;
use OxidEsales\Eshop\Application\Model\User;
use OxidEsales\Eshop\Core\Registry;
use OxidEsales\Eshop\Core\Str;

/**
 * This class is responsible for preparing entity for later use in Econda tracking component.
 */
class ActivePageEntityPreparator extends \OxidEsales\Eshop\Core\Base
{
    /**
     * @var ActivePageEntityInterface
     */
    private $activePageEntity;

    /**
     * @var PageIdentifiers
     */
    private $pageIdentifiers;

    /**
     * @var User
     */
    private $user;

    /**
     * @var PageMapModifier
     */
    private $pageMapModifier;

    /**
     * @var EntityModifierByCurrentAction
     */
    private $entityModifierByCurrentAction;

    /**
     * @var OrderStepsMapModifier
     */
    private $orderStepsMapModifier;

    /**
     * @var EntityModifierByCurrentBasketAction
     */
    private $entityModifierByCurrentBasketAction;

    /**
     * @var ActiveUserDataProvider
     */
    private $activeUserDataProvider;

    /**
     * @param ActivePageEntityInterface           $activePageEntity
     * @param PageIdentifiers                     $pageIdentifiers
     * @param User                                $activeUser
     * @param PageMapModifier                     $pageMapModifier
     * @param EntityModifierByCurrentAction       $entityModifierByCurrentAction
     * @param OrderStepsMapModifier               $orderStepsMapModifier
     * @param EntityModifierByCurrentBasketAction $trackingCodeGeneratorModifierForBasketEvents
     * @param ActiveUserDataProvider              $activeUserDataProvider
     */
    public function __construct(
        ActivePageEntityInterface $activePageEntity,
        $pageIdentifiers,
        $activeUser,
        $pageMapModifier,
        $entityModifierByCurrentAction,
        $orderStepsMapModifier,
        $trackingCodeGeneratorModifierForBasketEvents,
        $activeUserDataProvider
    ) {
        $this->activePageEntity = $activePageEntity;
        $this->pageIdentifiers = $pageIdentifiers;
        $this->user = $activeUser;
        $this->pageMapModifier = $pageMapModifier;
        $this->entityModifierByCurrentAction = $entityModifierByCurrentAction;
        $this->orderStepsMapModifier = $orderStepsMapModifier;
        $this->entityModifierByCurrentBasketAction = $trackingCodeGeneratorModifierForBasketEvents;
        $this->activeUserDataProvider = $activeUserDataProvider;

        parent::__construct();
    }

    /**
     * Makes OXID eShop page entity.
     *
     * @param array $parameters Plugin parameters.
     *
     * @return ActivePageEntityInterface
     */
    public function prepareEntity($parameters)
    {
        $this->activePageEntity->setPageid($this->getPageIdentifiers()->getPageId());
        $this->activePageEntity->setLangid(Registry::getLang()->getBaseLanguage());
        $domain = str_ireplace('www.', '', parse_url(Registry::getConfig()->getShopUrl(), PHP_URL_HOST));
        $this->activePageEntity->setSiteid($domain);

        $this->setControllerInfo($parameters);

        $this->setLoginsTracking();
        $this->setEmailTracking();

        $this->activePageEntity = $this->getEntityModifierByCurrentBasketAction()->modifyEntity($this->activePageEntity);

        return $this->activePageEntity;
    }

    /**
     * @param array $parameters
     */
    private function setControllerInfo($parameters)
    {
        $pageMap = $this->getPageMapModifer()->modifyPagesMap($parameters);
        $this->activePageEntity = $this->getEntityModifierByCurrentAction()->modifyEntity(
            $parameters,
            $this->getActiveUser(),
            $this->activePageEntity
        );

        $controllerName = $this->getPageIdentifiers()->getCurrentControllerName();
        if (is_string($controllerName) && array_key_exists($controllerName, $pageMap)) {
            $this->activePageEntity->setContent($pageMap[$controllerName]);
        } else {
            $this->activePageEntity->setContent('Content/' . Str::getStr()->preg_replace('/\.tpl$/', '', $this->getPageIdentifiers()->getCurrentTemplateName()));
        }

        $orderStepsMap = $this->getOrderStepsMapModifier()->modifyOrderStepsMap();
        if (is_string($controllerName) && array_key_exists($controllerName, $orderStepsMap)) {
            $this->activePageEntity->setOrderProcess($orderStepsMap[$controllerName]);
        }
    }

    /**
     * Sets login event for tracking.
     */
    private function setLoginsTracking()
    {
        $currentView = Registry::getConfig()->getActiveView();
        $functionName = $currentView->getFncName();
        if ('login_noredirect' == $functionName) {
            $this->activePageEntity->setLoginUserId($this->activeUserDataProvider->getActiveUserHashedId());
            $this->activePageEntity->setLoginResult($this->activeUserDataProvider->isLoaded() ? '0' : '1');
        }
    }

    /**
     * Sets email for tracking.
     */
    private function setEmailTracking()
    {
        $hashedEmail = $this->activeUserDataProvider->getActiveUserHashedEmail();
        if (!is_null($hashedEmail)) {
            $this->activePageEntity->setEmail($hashedEmail);
        }
    }

    /**
     * @return bool|User
     */
    private function getActiveUser()
    {
        return $this->user;
    }

    /**
     * @return PageIdentifiers
     */
    private function getPageIdentifiers()
    {
        return $this->pageIdentifiers;
    }

    /**
     * @return PageMapModifier
     */
    private function getPageMapModifer()
    {
        return $this->pageMapModifier;
    }

    /**
     * @return EntityModifierByCurrentAction
     */
    private function getEntityModifierByCurrentAction()
    {
        return $this->entityModifierByCurrentAction;
    }

    /**
     * @return OrderStepsMapModifier
     */
    private function getOrderStepsMapModifier()
    {
        return $this->orderStepsMapModifier;
    }

    /**
     * @return EntityModifierByCurrentBasketAction
     */
    private function getEntityModifierByCurrentBasketAction()
    {
        return $this->entityModifierByCurrentBasketAction;
    }
}


<?php
/*
 * Copyright (c) Atsuhiro Kubo <kubo@iteman.jp>,
 * All rights reserved.
 *
 * This file is part of PHPMentorsPageflowerBundle.
 *
 * This program and the accompanying materials are made available under
 * the terms of the BSD 2-Clause License which accompanies this
 * distribution, and is available at http://opensource.org/licenses/BSD-2-Clause
 */

namespace PHPMentors\PageflowerBundle\Pageflow;

use PHPMentors\DomainKata\Entity\EntityInterface;
use PHPMentors\DomainKata\Repository\RepositoryInterface;

class PageflowRepository implements RepositoryInterface
{
    /**
     * @var array
     */
    private $pageflows = array();

    /**
     * {@inheritdoc}
     */
    public function add(EntityInterface $entity)
    {
        assert($entity instanceof Pageflow);

        $this->pageflows[$entity->getPageflowId()] = $entity;
    }

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

    /**
     * @param string $pageflowId
     *
     * @return Pageflow
     */
    public function findByPageflowId($pageflowId)
    {
        if (array_key_exists($pageflowId, $this->pageflows)) {
            return $this->pageflows[$pageflowId];
        } else {
            return null;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function remove(EntityInterface $entity)
    {
        assert($entity instanceof Pageflow);

        if (array_key_exists($entity->getPageflowId(), $this->pageflows)) {
            unset($this->pageflows[$entity->getPageflowId()]);
        }
    }
}


<?php

/**
 * Part of the Antares package.
 *
 * NOTICE OF LICENSE
 *
 * Licensed under the 3-clause BSD License.
 *
 * This source file is subject to the 3-clause BSD License that is
 * bundled with this package in the LICENSE file.
 *
 * @package    Access Control
 * @version    0.9.0
 * @author     Antares Team
 * @license    BSD License (3-clause)
 * @copyright  (c) 2017, Antares
 * @link       http://antaresproject.io
 */

namespace Antares\Acl\Http\Handlers;

use Antares\Foundation\Http\Composers\LeftPane;

class ControlPane extends LeftPane
{

    /**
     * Handle pane for dashboard page.
     *
     * @return void
     */
    public function compose($name = null, $options = array())
    {

        $menu = app('antares.widget')->make('menu.control.pane');
        $auth = app('auth');

        $acl                  = app('antares.acl')->make('antares/acl');
        $canAdministratorList = $auth->is('super-administrator') && $acl->can('admin-list');
        $canRoleList          = $acl->can('roles-list');
        if (!$canAdministratorList and ! $canRoleList) {
            return;
        }

        $menu->add('general-settings')
                ->link(handles('antares::settings/index'))
                ->title(trans('System'))
                ->icon('zmdi-settings');

        $pane = app()->make('antares.widget')->make('pane.left');
        $pane->add('control')->content(view('antares/acl::partial._control_pane'));
    }

}


<?php
/*
 * This file is part of the prooph/ProophEventStoreModule.
 * (c) Alexander Miertsch <contact@prooph.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * Date: 05.09.14 - 21:23
 */

namespace ProophEventStoreModule\Factory;

use Prooph\EventStore\Aggregate\AggregateRepository;
use Prooph\EventStore\Aggregate\AggregateType;
use Prooph\EventStore\Configuration\Exception\ConfigurationException;
use Zend\ServiceManager\AbstractFactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

/**
 * Class AbstractRepositoryFactory
 *
 * @package ProophEventStoreModuleTest\Factory
 * @author Alexander Miertsch <kontakt@codeliner.ws>
 */
class AbstractRepositoryFactory implements AbstractFactoryInterface
{
    /**
     * @var array
     */
    protected $repositoryMap;

    /**
     * Determine if we can create a service with name
     *
     * @param ServiceLocatorInterface $serviceLocator
     * @param $name
     * @param $requestedName
     * @return bool
     */
    public function canCreateServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName)
    {
        if (is_null($this->repositoryMap)) {
            if (! $serviceLocator->has('config')) {
                $this->repositoryMap = false;
                return false;
            }

            $config = $serviceLocator->get('config');

            if (! isset($config['prooph.event_store']) || ! isset($config['prooph.event_store']['repository_map'])) {
                $this->repositoryMap = false;
                return;
            }

            $this->repositoryMap = (array)$config['prooph.event_store']['repository_map'];
        }

        return isset($this->repositoryMap[$requestedName]);
    }

    /**
     * Create service with name
     *
     * @param ServiceLocatorInterface $serviceLocator
     * @param $name
     * @param $requestedName
     * @throws \Prooph\EventStore\Configuration\Exception\ConfigurationException
     * @return AggregateRepository
     */
    public function createServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName)
    {
        $repoConfig = $this->repositoryMap[$requestedName];

        $repoClass = "";
        $repoAggregateTranslator = "";
        $repoStreamStrategy = "";
        $repoAggregateType = "";

        if (is_string($repoConfig)) {
            $repoClass = $repoConfig;
            $repoAggregateTranslator = "prooph.event_store.default_aggregate_translator";
            $repoStreamStrategy = "prooph.event_store.default_stream_strategy";
            $repoAggregateType  = "prooph.event_store." . $requestedName . "aggregate_type";
        } else if (is_array($repoConfig)) {
            if (! isset($repoConfig['repository_class'])) {
                throw ConfigurationException::configurationError("Missing repository_class for alias " . $requestedName);
            }

            if (! isset($repoConfig['aggregate_type'])) {
                throw ConfigurationException::configurationError("Missing aggregate_type for alias " . $requestedName);
            }

            $repoClass = $repoConfig['repository_class'];
            $repoAggregateTranslator = isset($repoConfig['aggregate_translator'])? $repoConfig['aggregate_translator'] : "prooph.event_store.default_aggregate_translator";
            $repoStreamStrategy = isset($repoConfig['stream_strategy'])? $repoConfig['stream_strategy'] : "prooph.event_store.default_stream_strategy";
            $repoAggregateType = $repoConfig['aggregate_type'];
        } else {
            throw ConfigurationException::configurationError("Wrong type provided for repository map of alias " . $requestedName);
        }

        if (! class_exists($repoClass)) {
            throw ConfigurationException::configurationError(sprintf(
                "Repository alias %s points to a non existing class %s",
                $requestedName,
                $repoClass
            ));
        }

        return new $repoClass(
            $serviceLocator->get('prooph.event_store'),
            $serviceLocator->get($repoAggregateTranslator),
            $serviceLocator->get($repoStreamStrategy),
            AggregateType::fromString($repoAggregateType)
        );
    }
}


<?php

namespace UFCOE\Elgg;

class SiteUrl {

	protected $host;
	protected $scheme;
	protected $url_segments = array();

	/**
	 * Constructor
	 *
	 * @param string $site_url if not given, will retrieve from elgg_get_site_url()
	 * @throws \InvalidArgumentException
	 */
	public function __construct($site_url = null) {
		if (!$site_url && is_callable('elgg_get_site_url')) {
			$site_url = elgg_get_site_url();
		}
		if (!preg_match('~^(https?)\\://([^/]+)(/.*|$)~', $site_url, $m)) {
			throw new \InvalidArgumentException('$siteUrl must be full URL');
		}

		list (, $this->scheme, $this->host, $path) = $m;
		$path = trim($path, '/');
		$this->url_segments = ($path === '') ? array() : explode('/', $path);
	}

	/**
	 * Get a site path object if the URL is within the Elgg site, or false if not.
	 *
	 * @param string $url          URL, possibly within the site
	 * @param bool   $match_scheme Require the URL scheme to match, e.g. http: won't match https:
	 * @return SitePath|bool
	 */
	public function getSitePath($url, $match_scheme = true) {
		$ret = $this->analyzeUrl($url);

		if (!$ret || !$ret['path_within_site'] || ($ret['site_host'] !== $ret['host'])) {
			return false;
		}

		if ($match_scheme && ($ret['scheme'] !== $ret['site_scheme'])) {
			return false;
		}

		return new SitePath(implode('/', $ret['url_segments_within_site']));
	}

	/**
	 * Analyze a URL.
	 *
	 * @param string $url URL
	 * @return array|false
	 */
	public function analyzeUrl($url) {
		$url = trim($url);

		if (!preg_match('~^(https?)\\://([^/]+)(/.*|$)~', $url, $m)) {
			return false;
		}

		list (, $scheme, $host, $path) = $m;

		$path = self::normalizePath($path);

		$segments = ($path === '') ? array() : explode('/', $path);

		$ret = [
			'site_scheme' => $this->scheme,
			'site_host' => $this->host,
			'site_url_segments' => $this->url_segments,
			'host' => $host,
			'scheme' => $scheme,
			'path_within_site' => false,
			'url_segments' => $segments,
			'url_segments_within_site' => [],
		];

		$site_segments = $this->url_segments;
		while (count($site_segments)) {
			$site_segment = array_shift($site_segments);
			$segment = array_shift($segments);

			if ($segment !== $site_segment) {
				// not in site
				return $ret;
			}
		}

		$ret['path_within_site'] = true;
		$ret['url_segments_within_site'] = $segments;

		return $ret;
	}

	/**
	 * Trim and remove fragment/querystring from a path
	 *
	 * @param string $path URL path
	 * @return string
	 */
	public static function normalizePath($path) {
		// remove fragment/query
		list($path, ) = explode('#', $path, 2);
		list($path, ) = explode('?', $path, 2);

		return trim($path, '/ ');
	}
}


<?php
/**
 * Copyright © OXID eSales AG. All rights reserved.
 * See LICENSE file for license details.
 */

namespace OxidEsales\EcondaTrackingComponent\Adapter\Helper;

/**
 * Class responsible for building category path.
 */
class CategoryPathBuilder
{
    /**
     * Builds basket product category path.
     *
     * @param \OxidEsales\Eshop\Application\Model\Article $product Article to build category id.
     *
     * @return string
     */
    public function getBasketProductCategoryPath($product)
    {
        $categoryPath = '';
        if ($category = $product->getCategory()) {
            $table = $category->getViewName();
            $database = \OxidEsales\Eshop\Core\DatabaseProvider::getDb(\OxidEsales\Eshop\Core\DatabaseProvider::FETCH_MODE_ASSOC);
            $query = "select {$table}.oxtitle as oxtitle from {$table}
                       where {$table}.oxleft <= " . $database->quote($category->oxcategories__oxleft->value) . " and
                             {$table}.oxright >= " . $database->quote($category->oxcategories__oxright->value) . " and
                             {$table}.oxrootid = " . $database->quote($category->oxcategories__oxrootid->value) . "
                       order by {$table}.oxleft";

            $result = $database->select($query);
            if ($result != false && $result->count() > 0) {
                while (!$result->EOF) {
                    if ($categoryPath) {
                        $categoryPath .= '/';
                    }
                    $categoryPath .= strip_tags($result->fields['oxtitle']);
                    $result->fetchRow();
                }
            }
        }

        return $categoryPath;
    }
}


<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateRolesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create(config('roleManager.rolesTable'), function (Blueprint $table) {
            $table->increments('id');
            $table->char('name', 255)->unique();
            $table->text('description')->nullable();
            $table->timestamps();
        });
        Schema::create(
            'permissions_roles',
            function (Blueprint $table) {
                $table->increments('id');
                $table->integer('roles_id')->unsigned();
                $table->integer('permissions_id')->unsigned();
                $table->foreign('roles_id')
                    ->references('id')
                    ->on(config('roleManager.rolesTable'))
                    ->onDelete('cascade');
                $table->foreign('permissions_id')
                    ->references('id')
                    ->on(config('roleManager.permissionsTable'))
                    ->onDelete('cascade');
            }
        );

        Schema::create(
            'roles_user',
            function (Blueprint $table) {
                $table->increments('id');
                $table->integer('user_id')->unsigned();
                $table->integer('roles_id')->unsigned();
                $table->foreign('roles_id')
                    ->references('id')
                    ->on(config('roleManager.rolesTable'))
                    ->onDelete('cascade');
                $table->foreign('user_id')
                    ->references('id')->on('users')
                    ->onDelete('cascade');
            }
        );
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('roles_user');
        Schema::dropIfExists('permissions_roles');
        Schema::dropIfExists(config('roleManager.rolesTable'));
    }
}


<?php

/*
 * This file is part of the Active Collab DatabaseObject project.
 *
 * (c) A51 doo <info@activecollab.com>. All rights reserved.
 */

namespace ActiveCollab\DatabaseObject\Entity;

use ActiveCollab\ContainerAccess\ContainerAccessInterface;
use ActiveCollab\ContainerAccess\ContainerAccessInterface\Implementation as ContainerAccessInterfaceImplementation;
use ActiveCollab\DatabaseConnection\ConnectionInterface;
use ActiveCollab\DatabaseConnection\Record\ValueCasterInterface;
use ActiveCollab\DatabaseObject\Exception\ValidationException;
use ActiveCollab\DatabaseObject\Pool;
use ActiveCollab\DatabaseObject\PoolInterface;
use ActiveCollab\DatabaseObject\Validator;
use ActiveCollab\DatabaseObject\ValidatorInterface;
use ActiveCollab\DateValue\DateTimeValue;
use ActiveCollab\DateValue\DateTimeValueInterface;
use ActiveCollab\DateValue\DateValue;
use ActiveCollab\DateValue\DateValueInterface;
use DateTime;
use Doctrine\Common\Inflector\Inflector;
use InvalidArgumentException;
use LogicException;
use Psr\Log\LoggerInterface;

/**
 * @package ActiveCollab\DatabaseObject
 */
abstract class Entity implements EntityInterface, ContainerAccessInterface
{
    use ContainerAccessInterfaceImplementation;

    /**
     * @var ConnectionInterface
     */
    protected $connection;

    /**
     * @var Pool
     */
    protected $pool;

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

    /**
     * Name of the table.
     *
     * @var string
     */
    protected $table_name;

    /**
     * Primary key fields.
     *
     * @var array
     */
    protected $primary_key = 'id';

    /**
     * Name of autoincrement field (if exists).
     *
     * @var string
     */
    protected $auto_increment = 'id';

    /**
     * @var string[]
     */
    protected $order_by = ['id'];

    /**
     * Table fields that are managed by this entity.
     *
     * @var array
     */
    protected $fields = [];

    /**
     * Generated fields that are loaded, but not managed by the entity.
     *
     * @var array
     */
    protected $generated_fields = [];

    /**
     * List of default field values.
     *
     * @var array
     */
    protected $default_field_values = [];

    /**
     * @param ConnectionInterface  $connection
     * @param PoolInterface        $pool
     * @param LoggerInterface|null $log
     */
    public function __construct(ConnectionInterface $connection, PoolInterface $pool, LoggerInterface &$log = null)
    {
        $this->connection = $connection;
        $this->pool = $pool;
        $this->log = $log;

        if ($traits = $pool->getTraitNamesByType(get_class($this))) {
            foreach ($traits as $trait) {
                $trait_constructor = str_replace('\\', '', $trait);

                if (method_exists($this, $trait_constructor)) {
                    $this->$trait_constructor();
                }
            }
        }

        $this->configure();
    }

    /**
     * Execute post-construction configuration.
     */
    protected function configure()
    {
    }

    // ---------------------------------------------------
    //  Internals, not overridable
    // ---------------------------------------------------

    /**
     * Indicates if this is new object (not saved).
     *
     * @var bool
     */
    private $is_new = true;

    /**
     * This flag is set to true when data from row are inserted into fields.
     *
     * @var bool
     */
    private $is_loading = false;

    /**
     * Field values.
     *
     * @var array
     */
    private $values = [];

    /**
     * Array of modified field values.
     *
     * Elements of this array are populated on setter call. Real name is
     * resolved, old value is saved here (if exists) and new one is set. Keys
     * used in this array are real field names only!
     *
     * @var array
     */
    private $old_values = [];

    /**
     * Array of modified fiels.
     *
     * @var array
     */
    private $modified_fields = [];

    /**
     * Primary key is updated.
     *
     * @var bool
     */
    private $primary_key_modified = false;

    /**
     * Validate object properties before object is saved.
     *
     * This method is called before the item is saved and can be used to fetch
     * errors in data before we really save it database. $errors is instance of
     * ValidationErrors class that is used for error collection. If collection
     * is empty object is considered valid and save process will continue
     *
     * @param ValidatorInterface $validator
     */
    public function validate(ValidatorInterface &$validator)
    {
        $this->triggerEvent('on_validate', [&$validator]);
    }

    /**
     * {@inheritdoc}
     */
    public function is($object)
    {
        if ($object instanceof EntityInterface) {
            if ($this->isLoaded()) {
                return $object->isLoaded() && get_class($this) == get_class($object) && $this->getId() == $object->getId();
            } else {
                foreach ($this->getFields() as $field_name) {
                    if (!$object->fieldExists($field_name) || !$this->areFieldValuesSame($this->getFieldValue($field_name), $object->getFieldValue($field_name))) {
                        return false;
                    }
                }

                return true;
            }
        }

        return false;
    }

    /**
     * Return true if field values match.
     *
     * @param  mixed $value_1
     * @param  mixed $value_2
     * @return bool
     */
    private function areFieldValuesSame($value_1, $value_2)
    {
        if (($value_1 instanceof DateValueInterface && $value_2 instanceof DateValueInterface) || ($value_1 instanceof DateTimeValueInterface && $value_2 instanceof DateTimeValueInterface)) {
            return $value_1->getTimestamp() == $value_2->getTimestamp();
        } else {
            return $value_1 === $value_2;
        }
    }

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

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

    // ---------------------------------------------------
    //  CRUD methods
    // ---------------------------------------------------

    /**
     * Load data from database row.
     *
     * If $cache_row is set to true row data will be added to cache
     *
     * @param  array                    $row
     * @throws InvalidArgumentException
     */
    public function loadFromRow(array $row)
    {
        if (empty($row)) {
            throw new InvalidArgumentException('Database row expected');
        }

        $this->startLoading();

        $found_generated_fields = [];

        foreach ($row as $k => $v) {
            if ($this->isGeneratedField($k)) {
                $found_generated_fields[] = $k;
            } elseif ($this->fieldExists($k)) {
                $this->setFieldValue($k, $v);
            }
        }

        if (!empty($found_generated_fields)) {
            $generated_field_values = [];

            $value_caster = $this->getGeneratedFieldsValueCaster();

            if ($value_caster instanceof ValueCasterInterface) {
                $generated_field_values = array_intersect_key($row, array_flip($found_generated_fields));

                $value_caster->castRowValues($generated_field_values);
            }

            foreach ($generated_field_values as $k => $v) {
                $this->setFieldValue($k, $v);
            }
        }

        $this->doneLoading();
    }

    /**
     * Save object into database (insert or update).
     *
     * @return $this
     * @throws ValidationException
     */
    public function &save()
    {
        // ---------------------------------------------------
        //  Populate defaults
        // ---------------------------------------------------

        if ($this->isNew()) {
            foreach ($this->default_field_values as $field_name => $field_value) {
                if (empty($this->values[$field_name]) && !array_key_exists($field_name, $this->values)) {
                    $this->setFieldValue($field_name, $field_value);
                }
            }
        }

        // ---------------------------------------------------
        //  Trigger on before save
        // ---------------------------------------------------

        $is_new = $this->isNew();
        $modifications = $this->getModifications();

        $this->triggerEvent('on_before_save', [$is_new, $modifications]);

        // ---------------------------------------------------
        //  Validate
        // ---------------------------------------------------

        $values_to_validate = $this->values;

        foreach ($this->fields as $field_name) {
            if (empty($values_to_validate[$field_name]) && !array_key_exists($field_name, $values_to_validate)) {
                $values_to_validate[$field_name] = null;
            }
        }

        $validator = new Validator($this->connection, $this->table_name, $this->getId(), $this->getOldFieldValue('id'), $values_to_validate);

        $this->validate($validator);

        if ($validator->hasErrors()) {
            throw $validator->createException();
        }

        // ---------------------------------------------------
        //  Do save
        // ---------------------------------------------------

        if ($this->isNew()) {
            $this->insert();
        } else {
            $this->update();
        }

        $this->triggerEvent('on_after_save', [$is_new, $modifications]);

        $this->pool->remember($this);

        return $this;
    }

    /**
     * Delete specific object (and related objects if neccecery).
     *
     * @param  bool  $bulk
     * @return $this
     */
    public function &delete($bulk = false)
    {
        if ($this->isLoaded()) {
            $this->connection->transact(function () use ($bulk) {
                $this->triggerEvent('on_before_delete', [$bulk]);

                $this->connection->delete($this->table_name, $this->getWherePartById($this->getId()));
                $this->is_new = true;

                $this->triggerEvent('on_after_delete', [$bulk]);
            });
        }

        return $this;
    }

    /**
     * Create a copy of this object and optionally save it.
     *
     * @param  bool   $save
     * @return object
     */
    public function copy($save = false)
    {
        $object_class = get_class($this);

        /** @var EntityInterface $copy */
        $copy = new $object_class($this->connection, $this->pool, $this->log);

        foreach ($this->getFields() as $field) {
            if ($this->isPrimaryKey($field)) {
                continue;
            }

            $copy->setFieldValue($field, $this->getFieldValue($field));
        }

        if ($save) {
            $copy->save();
        }

        return $copy;
    }

    // ---------------------------------------------------
    //  Flags
    // ---------------------------------------------------

    /**
     * Return value of $is_new variable.
     *
     * @return bool
     */
    public function isNew()
    {
        return (bool) $this->is_new;
    }

    /**
     * Returns true if this object have row in database.
     *
     * @return bool
     */
    public function isLoaded()
    {
        return !$this->is_new;
    }

    /**
     * Mark start of loading from row.
     */
    private function startLoading()
    {
        $this->is_loading = true;
    }

    /**
     * Done loading from row.
     */
    private function doneLoading()
    {
        if ($this->is_loading) {
            $this->is_loading = false;
        }

        $this->setAsLoaded();
    }

    /**
     * Set loaded stamp value.
     */
    private function setAsLoaded()
    {
        $this->is_new = false;
        $this->resetModifiedFlags();
    }

    /**
     * Returns true if this object is in the middle of hydration process
     * (loading values from database row).
     *
     * @return bool
     */
    protected function isLoading()
    {
        return $this->is_loading;
    }

    // ---------------------------------------------------
    //  Fields
    // ---------------------------------------------------

    /**
     * Return object ID.
     *
     * @return int
     */
    public function getId()
    {
        return $this->getFieldValue('id');
    }

    /**
     * Set value of id field.
     *
     * @param  int   $value
     * @return $this
     */
    public function &setId($value)
    {
        $this->setFieldValue('id', $value);

        return $this;
    }

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

    /**
     * {@inheritdoc}
     */
    public function getModifications()
    {
        $result = [];

        if (count($this->getModifiedFields())) {
            foreach ($this->getModifiedFields() as $field) {
                $old_value = $this->getOldFieldValue($field);
                $new_value = $this->getFieldValue($field);

                if ($old_value != $new_value) {
                    $result[$field] = [$this->getOldFieldValue($field), $this->getFieldValue($field)];
                }
            }
        }

        return $result;
    }

    /**
     * Check if this object has modified columns.
     *
     * @return bool
     */
    public function isModified()
    {
        return (bool) count($this->modified_fields);
    }

    /**
     * Returns true if specific field is modified.
     *
     * @param  string $field
     * @return bool
     */
    public function isModifiedField($field)
    {
        return in_array($field, $this->modified_fields);
    }

    /**
     * Return true if primary key is modified.
     *
     * @return bool
     */
    public function isPrimaryKeyModified()
    {
        return $this->primary_key_modified;
    }

    /**
     * Revert field to old value.
     *
     * @param $field
     */
    public function revertField($field)
    {
        if ($this->isModifiedField($field)) {
            $this->setFieldValue($field, $this->getOldFieldValue($field)); // revert field value

            if (($key = array_search($field, $this->modified_fields)) !== false) {
                unset($this->modified_fields[$field]); // remove modified flag
            }
        }
    }

    /**
     * Check if selected field is primary key.
     *
     * @param  string $field Field that need to be checked
     * @return bool
     */
    public function isPrimaryKey($field)
    {
        return $field === 'id';
    }

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

    /**
     * {@inheritdoc}
     */
    public function fieldExists($field)
    {
        return in_array($field, $this->fields) || in_array($field, $this->generated_fields);
    }

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

    /**
     * {@inheritdoc}
     */
    public function generatedFieldExists($field)
    {
        return in_array($field, $this->generated_fields);
    }

    /**
     * {@inheritdoc}
     */
    public function isGeneratedField($field)
    {
        return $this->generatedFieldExists($field);
    }

    /**
     * @var ValueCasterInterface
     */
    private $generated_fields_value_caster;

    /**
     * @return ValueCasterInterface
     */
    private function getGeneratedFieldsValueCaster()
    {
        return $this->generated_fields_value_caster;
    }

    /**
     * Set generated fields value caster.
     *
     * @param  ValueCasterInterface|null $value_caster
     * @return $this
     */
    protected function &setGeneratedFieldsValueCaster(ValueCasterInterface $value_caster = null)
    {
        $this->generated_fields_value_caster = $value_caster;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getFieldValue($field, $default = null)
    {
        if (empty($this->values[$field]) && !array_key_exists($field, $this->values)) {
            return empty($this->default_field_values[$field]) && !array_key_exists($field, $this->default_field_values) ? $default : $this->default_field_values[$field];
        } else {
            return $this->values[$field];
        }
    }

    /**
     * Return old field values, before fields were updated.
     *
     * @return array
     */
    public function getOldValues()
    {
        return $this->old_values;
    }

    /**
     * Return all field value.
     *
     * @param  string $field
     * @return mixed
     */
    public function getOldFieldValue($field)
    {
        return isset($this->old_values[$field]) ? $this->old_values[$field] : null;
    }

    /**
     * Set specific field value.
     *
     * Set value of the $field. This function will make sure that everything
     * runs fine - modifications are saved, in case of primary key old value
     * will be remembered in case we need to update the row and so on
     *
     * @param  string                   $field
     * @param  mixed                    $value
     * @return $this
     * @throws InvalidArgumentException
     */
    public function &setFieldValue($field, $value)
    {
        if ($this->fieldExists($field)) {
            if ($field === 'id') {
                $value = $value === null ? null : (int) $value;
            }

            if ($value === null && array_key_exists($field, $this->default_field_values)) {
                throw new InvalidArgumentException("Value of '$field' can't be null");
            }

            if (!$this->isLoading()) {
                $this->triggerEvent('on_prepare_field_value_before_set', [$field, &$value]);
            }

            if (!array_key_exists($field, $this->values) || ($this->values[$field] !== $value)) {

                // If we are loading object there is no need to remember if this field
                // was modified, if PK has been updated and old value. We just skip that
                if (!$this->isLoading()) {
                    if (isset($this->values[$field])) {
                        $old_value = $this->values[$field]; // Remember old value
                    }

                    // Save primary key value. Also make sure that only the first PK value is
                    // saved as old. Not to save second value on third modification ;)
                    if ($this->isPrimaryKey($field) && !$this->primary_key_modified) {
                        $this->primary_key_modified = true;
                    }

                    // Save old value if we haven't done that already
                    if (isset($old_value) && !isset($this->old_values[$field])) {
                        $this->old_values[$field] = $old_value;
                    }

                    // Remember that this field is modified
                    if (!in_array($field, $this->modified_fields)) {
                        $this->modified_fields[] = $field;
                    }
                }

                $this->values[$field] = $value;
            }
        } else {
            throw new InvalidArgumentException("Field '$field' does not exist");
        }

        return $this;
    }

    /**
     * Return a list of attributes that this object supports.
     *
     * @return array
     */
    protected function getAttributes()
    {
        return [];
    }

    /**
     * Return setter method name for the given attribute.
     *
     * @param  string $attribute
     * @return string
     */
    private function getAttributeSetter($attribute)
    {
        return 'set' . Inflector::classify($attribute);
    }

    /**
     * Set non-field value during DataManager::create() and DataManager::update() calls.
     *
     * @param  string $attribute
     * @param  mixed  $value
     * @return $this
     */
    public function &setAttribute($attribute, $value)
    {
        if (in_array($attribute, $this->getAttributes())) {
            $setter = $this->getAttributeSetter($attribute);

            if (method_exists($this, $setter)) {
                $this->$setter($value);
            }
        } else {
            $this->triggerEvent('on_set_attribute', [$attribute, $value]);
        }

        return $this;
    }

    /**
     * Use input $value and return a valid DateValue instance.
     *
     * @param  mixed          $value
     * @return DateValue|null
     */
    protected function getDateValueInstanceFrom($value)
    {
        if ($value === null) {
            return null;
        } elseif ($value instanceof DateTime) {
            return new DateValue($value->format('Y-m-d'));
        } else {
            return new DateValue($value);
        }
    }

    /**
     * Use input $value and return a valid DateTimeValue instance.
     *
     * @param  mixed              $value
     * @return DateTimeValue|null
     */
    protected function getDateTimeValueInstanceFrom($value)
    {
        if ($value === null) {
            return null;
        } elseif ($value instanceof DateTime) {
            return new DateTimeValue($value->format('Y-m-d H:i:s'), 'UTC');
        } else {
            return new DateTimeValue($value, 'UTC');
        }
    }

    // ---------------------------------------------------
    //  Database interaction
    // ---------------------------------------------------

    /**
     * Insert record in the database.
     */
    private function insert()
    {
        $last_insert_id = $this->connection->insert($this->table_name, $this->values);

        if (empty($this->values[$this->auto_increment])) {
            $this->values[$this->auto_increment] = $last_insert_id;
        }

        $this->values = array_merge($this->values, $this->refreshGeneratedFieldValues($last_insert_id));
        $this->setAsLoaded();
    }

    /**
     * Update database record.
     */
    private function update()
    {
        if (count($this->modified_fields)) {
            $updates = [];

            foreach ($this->modified_fields as $modified_field) {
                $updates[$modified_field] = $this->values[$modified_field];
            }

            if ($this->primary_key_modified) {
                $old_id = isset($this->old_values['id']) ? $this->old_values['id'] : $this->getId();

                if ($this->pool->exists(get_class($this), $this->getId())) {
                    throw new LogicException('Object #' . $this->getId() . " can't be overwritten");
                } else {
                    $this->connection->update($this->table_name, $updates, $this->getWherePartById($old_id));
                }
            } else {
                $this->connection->update($this->table_name, $updates, $this->getWherePartById($this->getId()));
            }

            $this->values = array_merge($this->values, $this->refreshGeneratedFieldValues($this->getId()));
            $this->setAsLoaded();
        }
    }

    /**
     * Return an array with potentially refreshed values of generated fields.
     *
     * @param  int   $id
     * @return array
     */
    private function refreshGeneratedFieldValues($id)
    {
        $result = [];

        if (!empty($this->generated_fields)) {
            $result = $this->connection->selectFirstRow($this->getTableName(), $this->generated_fields, $this->getWherePartById($id));

            if (empty($result)) {
                $result = [];
            }
        }

        $value_caster = $this->getGeneratedFieldsValueCaster();

        if ($value_caster instanceof ValueCasterInterface) {
            $value_caster->castRowValues($result);
        }

        return $result;
    }

    /**
     * Return where part of query.
     *
     * @param  int    $id
     * @return string
     */
    private function getWherePartById($id)
    {
        if (empty($id)) {
            throw new InvalidArgumentException("Value '$id' is not a valid ID");
        }

        return $this->connection->prepare('(`id` = ?)', $id);
    }

    /**
     * Reset modification idicators.
     *
     * Useful when you use setXXX functions but you dont want to modify
     * anything (just loading data from database in fresh object using
     * setFieldValue function)
     */
    private function resetModifiedFlags()
    {
        $this->modified_fields = $this->old_values = [];
        $this->primary_key_modified = false;
    }

    // ---------------------------------------------------
    //  Events
    // ---------------------------------------------------

    /**
     * Registered event handlers.
     *
     * @var array
     */
    private $event_handlers = [];

    /**
     * Register an internal event handler.
     *
     * @param string   $event
     * @param callable $handler
     */
    protected function registerEventHandler($event, callable $handler)
    {
        if (empty($event)) {
            throw new InvalidArgumentException('Event name is required');
        }

        if (is_callable($handler)) {
            if (empty($this->event_handlers[$event])) {
                $this->event_handlers[$event] = [];
            }

            $this->event_handlers[$event][] = $handler;
        } else {
            throw new InvalidArgumentException('Handler not callable');
        }
    }

    /**
     * Trigger an internal event.
     *
     * @param string $event
     * @param array  $event_parameters
     */
    protected function triggerEvent($event, array $event_parameters = [])
    {
        if (isset($this->event_handlers[$event])) {
            foreach ($this->event_handlers[$event] as $handler) {
                call_user_func_array($handler, $event_parameters);
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    public function jsonSerialize()
    {
        $result = [
            'id' => $this->getId(),
            'type' => get_class($this),
        ];

        $this->triggerEvent('on_json_serialize', [&$result]);

        return $result;
    }

    /**
     * {@inheritdoc}
     */
    public function jsonSerializeDetails()
    {
        return [];
    }
}


<?php

/*
 * This file is part of the Active Collab DatabaseObject project.
 *
 * (c) A51 doo <info@activecollab.com>. All rights reserved.
 */

namespace ActiveCollab\DatabaseObject;

use ActiveCollab\DatabaseConnection\ConnectionInterface;
use ActiveCollab\DatabaseObject\Exception\ValidationException;
use InvalidArgumentException;

/**
 * @package ActiveCollab\DatabaseObject
 */
class Validator implements ValidatorInterface
{
    /**
     * @var array
     */
    private $errors = [];

    /**
     * @var ConnectionInterface
     */
    private $connection;

    /**
     * @var string
     */
    private $table_name;

    /**
     * @var int|null
     */
    private $object_id;

    /**
     * @var int|null
     */
    private $old_object_id;

    /**
     * @var array
     */
    private $field_values;

    /**
     * @param ConnectionInterface $connection
     * @param string              $table_name
     * @param int|null            $object_id
     * @param int|null            $old_object_id
     * @param array               $field_values
     */
    public function __construct(ConnectionInterface $connection, $table_name, $object_id, $old_object_id, array $field_values)
    {
        $this->connection = $connection;
        $this->table_name = $table_name;
        $this->object_id = $object_id;
        $this->old_object_id = $old_object_id;
        $this->field_values = $field_values;
    }

    /**
     * Check if value of $field_name is present.
     *
     * Note: strings are trimmed prior to check, and values that empty() would return true for (like '0') are consdiered
     * to be present (because we check strlen(trim($value)).
     *
     * @param  string $field_name
     * @return bool
     */
    public function present($field_name)
    {
        if (empty($field_name)) {
            throw new InvalidArgumentException("Value '$field_name' is not a valid field name");
        }

        if (array_key_exists($field_name, $this->field_values)) {
            if (is_string($this->field_values[$field_name])) {
                if (mb_strlen(trim($this->field_values[$field_name])) > 0) {
                    return true;
                } else {
                    return $this->failPresenceValidation($field_name);
                }
            } else {
                if (empty($this->field_values[$field_name])) {
                    return $this->failPresenceValidation($field_name);
                } else {
                    return true;
                }
            }
        } else {
            return $this->failPresenceValidation($field_name);
        }
    }

    /**
     * Fail presence validation.
     *
     * @param  string $field_name
     * @return bool
     */
    private function failPresenceValidation($field_name)
    {
        $this->addFieldError($field_name, "Value of '$field_name' is required");

        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function lowerThan($field_name, $reference_value, $allow_null = false)
    {
        return $this->compareValues($field_name, $reference_value, $allow_null, function ($a, $b) {
            return $a < $b;
        }, "Value of '$field_name' is not lower than $reference_value");
    }

    /**
     * {@inheritdoc}
     */
    public function lowerThanOrEquals($field_name, $reference_value, $allow_null = false)
    {
        return $this->compareValues($field_name, $reference_value, $allow_null, function ($a, $b) {
            return $a <= $b;
        }, "Value of '$field_name' is not lower than or equal to $reference_value");
    }

    /**
     * {@inheritdoc}
     */
    public function greaterThan($field_name, $reference_value, $allow_null = false)
    {
        return $this->compareValues($field_name, $reference_value, $allow_null, function ($a, $b) {
            return $a > $b;
        }, "Value of '$field_name' is not greater than $reference_value");
    }

    /**
     * {@inheritdoc}
     */
    public function greaterThanOrEquals($field_name, $reference_value, $allow_null = false)
    {
        return $this->compareValues($field_name, $reference_value, $allow_null, function ($a, $b) {
            return $a >= $b;
        }, "Value of '$field_name' is not greater than or equal to $reference_value");
    }

    /**
     * Validate field value by comparing it to a reference value using a closure.
     *
     * @param  string   $field_name
     * @param  mixed    $reference_value
     * @param  bool     $allow_null
     * @param  callable $compare_with
     * @param  string   $validation_failed_message
     * @return bool
     */
    protected function compareValues($field_name, $reference_value, $allow_null, callable $compare_with, $validation_failed_message)
    {
        if (empty($field_name)) {
            throw new InvalidArgumentException("Value '$field_name' is not a valid field name");
        }

        if (array_key_exists($field_name, $this->field_values)) {
            if ($this->field_values[$field_name] === null) {
                if ($allow_null) {
                    return true;
                } else {
                    return $this->failPresenceValidation($field_name);
                }
            }

            if (call_user_func($compare_with, $this->field_values[$field_name], $reference_value)) {
                return true;
            } else {
                $this->addFieldError($field_name, $validation_failed_message);

                return false;
            }
        } else {
            return $this->failPresenceValidation($field_name);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function inArray($field_name, array $array_of_values, $allow_null = false)
    {
        if (empty($field_name)) {
            throw new InvalidArgumentException("Value '$field_name' is not a valid field name");
        }

        if (array_key_exists($field_name, $this->field_values)) {
            if ($this->field_values[$field_name] === null) {
                if ($allow_null) {
                    return true;
                } else {
                    return $this->failPresenceValidation($field_name);
                }
            }

            if (in_array($this->field_values[$field_name], $array_of_values)) {
                return true;
            } else {
                $this->addFieldError($field_name, "Value of '$field_name' is not present in the list of supported values");

                return false;
            }
        } else {
            return $this->failPresenceValidation($field_name);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function unique($field_name, ...$context)
    {
        return $this->uniqueWhere($field_name, '', ...$context);
    }

    /**
     * {@inheritdoc}
     */
    public function uniqueWhere($field_name, $where, ...$context)
    {
        if (empty($field_name)) {
            throw new InvalidArgumentException("Value '$field_name' is not a valid field name");
        }

        if (empty($context) && (!array_key_exists($field_name, $this->field_values) || $this->field_values[$field_name] === null)) {
            return true; // NULL is always good for single column keys because MySQL does not check NULL for uniqueness
        }

        $field_names = [$field_name];

        if (count($context)) {
            $field_names = array_merge($field_names, $context);
        }

        // Check if we have existsing columns
        foreach ($field_names as $v) {
            if (!array_key_exists($v, $this->field_values)) {
                throw new InvalidArgumentException("Field '$v' is not known");
            }
        }

        $table_name = $this->connection->escapeTableName($this->table_name);

        $conditions = [];

        if ($where) {
            $conditions[] = $this->connection->prepareConditions($where);
        }

        foreach ($field_names as $v) {
            $escaped_field_name = $this->connection->escapeFieldName($v);

            if ($this->field_values[$v] === null) {
                $conditions[] = "$escaped_field_name IS NULL";
            } else {
                $conditions[] = $this->connection->prepare("$escaped_field_name = ?", $this->field_values[$v]);
            }
        }

        $conditions = implode(' AND ', $conditions);

        if (empty($this->object_id)) {
            $sql = sprintf("SELECT COUNT(`id`) AS 'row_count' FROM $table_name WHERE $conditions");
        } else {
            $sql = $this->connection->prepare("SELECT COUNT(`id`) AS 'row_count' FROM $table_name WHERE ($conditions) AND (`id` != ?)", ($this->old_object_id ? $this->old_object_id : $this->object_id));
        }

        if ($this->connection->executeFirstCell($sql) > 0) {
            if (empty($context)) {
                $this->addFieldError($field_name, "Value of '$field_name' needs to be unique");
            } else {
                $this->addFieldError($field_name, "Value of '$field_name' needs to be unique in context of " . implode(', ', array_map(function ($field_name) {
                    return "'$field_name'";
                }, $context)));
            }

            return false;
        }

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function presentAndUnique($field_name, ...$context)
    {
        if ($this->present($field_name)) {
            return $this->unique($field_name, ...$context);
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function presentAndUniqueWhere($field_name, $where, ...$context)
    {
        if ($this->present($field_name)) {
            return $this->uniqueWhere($field_name, $where, ...$context);
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function email($field_name, $allow_null = false)
    {
        if (array_key_exists($field_name, $this->field_values)) {
            if ($this->field_values[$field_name] === null) {
                if ($allow_null) {
                    return true;
                } else {
                    return $this->failPresenceValidation($field_name);
                }
            }

            if (filter_var($this->field_values[$field_name], FILTER_VALIDATE_EMAIL)) {
                return true;
            } else {
                $this->addFieldError($field_name, "Value of '$field_name' is not a valid email address");

                return false;
            }
        } else {
            return $this->failPresenceValidation($field_name);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function url($field_name, $allow_null = false)
    {
        if (array_key_exists($field_name, $this->field_values)) {
            if ($this->field_values[$field_name] === null) {
                if ($allow_null) {
                    return true;
                } else {
                    return $this->failPresenceValidation($field_name);
                }
            }

            if (filter_var($this->field_values[$field_name], FILTER_VALIDATE_URL)) {
                return true;
            } else {
                $this->addFieldError($field_name, "Value of '$field_name' is not a valid URL");

                return false;
            }
        } else {
            return $this->failPresenceValidation($field_name);
        }
    }

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

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

    /**
     * {@inheritdoc}
     */
    public function getFieldErrors($field_name)
    {
        return isset($this->errors[$field_name]) ? $this->errors[$field_name] : [];
    }

    /**
     * {@inheritdoc}
     */
    public function addFieldError($field_name, $error_message)
    {
        if (empty($this->errors[$field_name])) {
            $this->errors[$field_name] = [];
        }

        $this->errors[$field_name][] = $error_message;
    }

    /**
     * @return ValidationException
     */
    public function createException()
    {
        $message = 'Validation failed';

        $first_messages = [];
        $counter = 0;

        foreach ($this->errors as $field => $error_messages) {
            foreach ($error_messages as $error_message) {
                $first_messages[] = $error_message;
                ++$counter;

                if ($counter > 3) {
                    break 2;
                }
            }
        }

        if (!empty($first_messages)) {
            $message .= ': ' . implode(', ', $first_messages);
        }

        return (new ValidationException($message))->setErrors($this->errors);
    }
}


<?php
namespace Acelaya\Expressive\Router;

use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Router;
use Zend\Expressive\Router\Exception;
use Zend\Expressive\Router\Route;
use Zend\Expressive\Router\RouteResult;
use Zend\Expressive\Router\RouterInterface;

class SlimRouter implements RouterInterface
{
    /**
     * @var Router
     */
    private $router;
    /**
     * @var Route[]
     */
    private $routes;

    public function __construct(Router $router = null)
    {
        if (null === $router) {
            $router = $this->createRouter();
        }

        $this->router = $router;
        $this->routes = [];
    }

    /**
     * Create a default Aura router instance
     *
     * @return Router
     */
    private function createRouter()
    {
        return new Router();
    }

    /**
     * @param Route $route
     */
    public function addRoute(Route $route): void
    {
        $this->routes[] = $route;
    }

    public function dummyCallable()
    {
    }

    /**
     * @param  Request $request
     * @return RouteResult
     */
    public function match(Request $request): RouteResult
    {
        $this->injectRoutes();

        $matchedRoutes = $this->router->getMatchedRoutes($request->getMethod(), $request->getUri()->getPath());
        if (count($matchedRoutes) === 0) {
            return RouteResult::fromRouteFailure(null);
        }

        /** @var \Slim\Route $matchedRoute */
        $matchedRoute = array_shift($matchedRoutes);
        $params = $matchedRoute->getParams();

        // Get the middleware from the route params and remove it
        $middleware = $params['middleware'];
        unset($params['middleware']);

        $route = new Route(
            $matchedRoute->getPattern(),
            $middleware,
            $matchedRoute->getHttpMethods(),
            $matchedRoute->getName()
        );
        return RouteResult::fromRoute($route, $params);
    }

    /**
     * Generate a URI from the named route.
     *
     * Takes the named route and any substitutions, and attempts to generate a
     * URI from it. Additional router-dependent options may be passed.
     *
     * The URI generated MUST NOT be escaped. If you wish to escape any part of
     * the URI, this should be performed afterwards; consider passing the URI
     * to league/uri to encode it.
     *
     * @see https://github.com/auraphp/Aura.Router#generating-a-route-path
     * @see http://framework.zend.com/manual/current/en/modules/zend.mvc.routing.html
     * @param string $name
     * @param array $substitutions
     * @param array $options
     * @return string
     * @throws Exception\RuntimeException if unable to generate the given URI.
     */
    public function generateUri(string $name, array $substitutions = [], array $options = []): string
    {
        $this->injectRoutes();

        if (! $this->router->hasNamedRoute($name)) {
            throw new Exception\RuntimeException(sprintf(
                'Cannot generate URI based on route "%s"; route not found',
                $name
            ));
        }

        return $this->router->urlFor($name, $substitutions);
    }

    private function injectRoutes(): void
    {
        foreach ($this->routes as $key => $route) {
            $this->injectRoute($route);
            unset($this->routes[$key]);
        }
    }

    private function injectRoute(Route $route): void
    {
        $slimRoute = new \Slim\Route($route->getPath(), [$this, 'dummyCallable']);
        $slimRoute->setName($route->getName());

        $allowedMethods = $route->getAllowedMethods();
        $slimRoute->via($allowedMethods === Route::HTTP_METHOD_ANY ? 'ANY' : $allowedMethods);

        // Process options
        $options = $route->getOptions();
        if (isset($options['conditions']) && is_array($options['conditions'])) {
            $slimRoute->setConditions($options['conditions']);
        }
        // The middleware is merged with the rest of the route params
        $params = [
            'middleware' => $route->getMiddleware()
        ];
        if (isset($options['defaults']) && is_array($options['defaults'])) {
            $params = array_merge($options['defaults'], $params);
        }
        $slimRoute->setParams($params);

        $this->router->map($slimRoute);
    }
}


<?php
/**
 * Copyright © OXID eSales AG. All rights reserved.
 * See LICENSE file for license details.
 */

namespace OxidEsales\EcondaTrackingComponent\Adapter\ProductPreparation;

use OxidEsales\Eshop\Application\Model\Article;
use OxidEsales\Eshop\Core\Registry;

/**
 * Prepares product data for later use in another component.
 */
class ProductDataPreparator
{
    /**
     * @var ProductTitlePreparator
     */
    private $productTitlePreparator;

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

    /**
     * @param Article $product
     * @param string  $categoryPath
     *
     * @return array
     */
    public function prepareForDetailsPage($product, $categoryPath = 'NULL')
    {
        return $this->prepareData($product, $categoryPath);
    }

    /**
     * @param Article $product
     * @param string  $categoryPath
     * @param int     $amount
     *
     * @return array
     */
    public function prepareForTransaction($product, $categoryPath = 'NULL', $amount = 1)
    {
        $data = $this->prepareData($product, $categoryPath);
        $dataWithCount = array_merge(
            $data,
            ['count' => $amount]
        );

        return $dataWithCount;
    }

    /**
     * @param Article|ProductInterface $product
     * @param string                   $categoryPath
     *
     * @return array
     */
    protected function prepareData($product, $categoryPath)
    {
        $currentProductId = $product->oeEcondaTrackingGetProductId();
        $productData = [
            'pid' => $currentProductId,
            'sku' => $product->oeEcondaTrackingGetSku(),
        ];

        $currency = Registry::getConfig()->getActShopCurrencyObject();
        $productMergedData = array_merge(
            $productData,
            [
                'name'  => $this->getProductTitlePreparator()->prepareProductTitle($product),
                'group' => "{$categoryPath}/{$product->oxarticles__oxtitle->value}",
                'price' => $product->getPrice()->getBruttoPrice() * (1 / $currency->rate),
                'var1' => $product->getVendor() ? $product->getVendor()->getTitle() : "NULL",
                'var2' => $product->getManufacturer() ? $product->getManufacturer()->getTitle() : "NULL",
                'var3' => $product->getId(),
            ]
        );

        return $productMergedData;
    }

    /**
     * @return ProductTitlePreparator
     */
    protected function getProductTitlePreparator()
    {
        return $this->productTitlePreparator;
    }
}