<?php

namespace Ekyna\Component\Commerce\Customer\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Criteria;
use Ekyna\Component\Commerce\Common\Model as Common;
use Ekyna\Component\Commerce\Customer\Model as Model;
use Ekyna\Component\Commerce\Payment\Model\PaymentTermSubjectTrait;
use Ekyna\Component\Commerce\Pricing\Model\VatNumberSubjectTrait;
use Ekyna\Component\Resource\Model as RM;

/**
 * Class Customer
 * @package Ekyna\Component\Commerce\Customer\Entity
 * @author  Etienne Dauvergne <contact@ekyna.com>
 */
class Customer implements Model\CustomerInterface
{
    use Common\IdentityTrait,
        Common\NumberSubjectTrait,
        Common\CurrencySubjectTrait,
        PaymentTermSubjectTrait,
        VatNumberSubjectTrait,
        RM\LocalizedTrait,
        RM\TimestampableTrait;

    /**
     * @var int
     */
    protected $id;

    /**
     * @var string
     */
    protected $company;

    /**
     * @var string
     */
    protected $email;

    /**
     * @var string
     */
    protected $phone;

    /**
     * @var string
     */
    protected $mobile;

    /**
     * @var \DateTime
     */
    protected $birthday;

    /**
     * @var Model\CustomerInterface
     */
    protected $parent;

    /**
     * @var ArrayCollection|Model\CustomerInterface[]
     */
    protected $children;

    /**
     * @var Model\CustomerGroupInterface
     */
    protected $customerGroup;

    /**
     * @var ArrayCollection|Model\CustomerAddressInterface[]
     */
    protected $addresses;

    /**
     * @var float
     */
    protected $creditBalance;

    /**
     * @var float
     */
    protected $outstandingLimit;

    /**
     * @var float
     */
    protected $outstandingBalance;

    /**
     * @var string
     */
    protected $state;

    /**
     * @var string
     */
    protected $description;

    /**
     * Constructor.
     */
    public function __construct()
    {
        $this->creditBalance = 0;
        $this->outstandingLimit = 0;
        $this->outstandingBalance = 0;

        $this->state = Model\CustomerStates::STATE_NEW;

        $this->children = new ArrayCollection();
        $this->addresses = new ArrayCollection();

        $this->createdAt = new \DateTime();
    }

    /**
     * Returns the string representation.
     *
     * @return string
     */
    public function __toString()
    {
        if ($this->company) {
            $sign = '';
            if ($this->hasParent()) {
                $sign = '♦'; //'◊';
            } elseif ($this->hasChildren()) { // TODO Greedy : triggers collection initialization
                $sign = '◊'; //'♦';
            }
            return sprintf('%s [%s] %s %s', $sign, $this->company, $this->lastName, $this->firstName);
        }

        return sprintf('%s %s', $this->lastName, $this->firstName);
    }

    /**
     * Returns the id.
     *
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

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

    /**
     * @inheritdoc
     */
    public function setCompany($company)
    {
        $this->company = $company;

        return $this;
    }

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

    /**
     * @inheritdoc
     */
    public function setEmail($email)
    {
        $this->email = $email;

        return $this;
    }

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

    /**
     * @inheritdoc
     */
    public function setPhone($phone)
    {
        $this->phone = $phone;

        return $this;
    }

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

    /**
     * @inheritdoc
     */
    public function setMobile($mobile)
    {
        $this->mobile = $mobile;

        return $this;
    }

    /**
     * Returns the birthday.
     *
     * @return \DateTime
     */
    public function getBirthday()
    {
        return $this->birthday;
    }

    /**
     * Sets the birthday.
     *
     * @param \DateTime $birthday
     *
     * @return Customer
     */
    public function setBirthday(\DateTime $birthday = null)
    {
        $this->birthday = $birthday;

        return $this;
    }

    /**
     * @inheritdoc
     */
    public function hasParent()
    {
        return null !== $this->parent;
    }

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

    /**
     * @inheritdoc
     */
    public function setParent(Model\CustomerInterface $parent = null)
    {
        if ($parent !== $this->parent) {
            if ($previous = $this->parent) {
                $previous = null;
                $this->parent->removeChild($this);
            }

            if ($this->parent = $parent) {
                $parent->addChild($this);
            }
        }

        return $this;
    }

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

    /**
     * @inheritdoc
     */
    public function hasChild(Model\CustomerInterface $child)
    {
        return $this->children->contains($child);
    }

    /**
     * @inheritdoc
     */
    public function addChild(Model\CustomerInterface $child)
    {
        if (!$this->hasChild($child)) {
            $this->children->add($child);
            $child->setParent($this);
        }

        return $this;
    }

    /**
     * @inheritdoc
     */
    public function removeChild(Model\CustomerInterface $child)
    {
        if ($this->hasChild($child)) {
            $this->children->removeElement($child);
            $child->setParent(null);
        }

        return $this;
    }

    /**
     * @inheritdoc
     */
    public function hasChildren()
    {
        return 0 < $this->children->count();
    }

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

    /**
     * @inheritdoc
     */
    public function setCustomerGroup(Model\CustomerGroupInterface $group = null)
    {
        $this->customerGroup = $group;

        return $this;
    }

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

    /**
     * @inheritdoc
     */
    public function hasAddress(Model\CustomerAddressInterface $address)
    {
        return $this->addresses->contains($address);
    }

    /**
     * @inheritdoc
     */
    public function addAddress(Model\CustomerAddressInterface $address)
    {
        if (!$this->hasAddress($address)) {
            $this->addresses->add($address);
            $address->setCustomer($this);
        }

        return $this;
    }

    /**
     * @inheritdoc
     */
    public function removeAddress(Model\CustomerAddressInterface $address)
    {
        if ($this->hasAddress($address)) {
            $this->addresses->removeElement($address);
            $address->setCustomer(null);
        }

        return $this;
    }

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

    /**
     * @inheritdoc
     */
    public function setCreditBalance($creditBalance)
    {
        $this->creditBalance = (float)$creditBalance;

        return $this;
    }

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

    /**
     * @inheritdoc
     */
    public function setOutstandingLimit($limit)
    {
        $this->outstandingLimit = (float)$limit;

        return $this;
    }

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

    /**
     * @inheritdoc
     */
    public function setOutstandingBalance($amount)
    {
        $this->outstandingBalance = (float)$amount;

        return $this;
    }

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

    /**
     * @inheritdoc
     */
    public function setState($state)
    {
        $this->state = $state;

        return $this;
    }

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

    /**
     * @inheritdoc
     */
    public function setDescription($description)
    {
        $this->description = $description;

        return $this;
    }

    /**
     * @inheritdoc
     */
    public function getDefaultInvoiceAddress($allowParentAddress = false)
    {
        if ($allowParentAddress && $this->hasParent()) {
            if (null !== $address = $this->parent->getDefaultInvoiceAddress($allowParentAddress)) {
                return $address;
            }
        }

        if (null !== $address = $this->findOneAddressBy(Criteria::expr()->eq('invoiceDefault', true))) {
            return $address;
        }

        return null;
    }

    /**
     * @inheritdoc
     */
    public function getDefaultDeliveryAddress($allowParentAddress = false)
    {
        if ($allowParentAddress && $this->hasParent()) {
            if (null !== $address = $this->parent->getDefaultDeliveryAddress($allowParentAddress)) {
                return $address;
            }
        }

        if (null !== $address = $this->findOneAddressBy(Criteria::expr()->eq('deliveryDefault', true))) {
            return $address;
        }

        return null;
    }

    /**
     * Finds one address by expression.
     *
     * @param mixed $expression
     *
     * @return Model\CustomerAddressInterface|null
     */
    protected function findOneAddressBy($expression)
    {
        if (0 < $this->addresses->count()) {
            $criteria = Criteria::create()
                ->where($expression)
                ->setMaxResults(1);

            $matches = $this->addresses->matching($criteria);
            if ($matches->count() == 1) {
                return $matches->first();
            }
        }

        return null;
    }
}


<?php

namespace Skriv\Markup;

/**
 * Main renderer object for processing of SkrivMarkup-enabled text.
 *
 * @author	Amaury Bouchard <amaury@amaury.net>
 * @copyright	© 2013, Amaury Bouchard
 * @package	SkrivMarkup
 * @see		WikiRenderer
 */
class Renderer {
	/** The configuration object. */
	protected $_config = null;
	/** The WikiRenderer object. */
	protected $_wikiRenderer = null;

	/**
	 * Factory method. Creates a renderer object of the given type.
	 * @param	string	$type	(optional) Type of rendering object. "html" by default.
	 * @param	array	$params	(optional) Hash of parameters. The accepted parameters depends of the chosen rendering type.
	 * @return	\Skriv\Markup\Renderer	A rendering object.
	 * @throws	Exception	If something goes wrong.
	 */
	static public function factory($type='html', array $params=null) {
		if (!isset($type) || !strcasecmp($type, 'html'))
			return (new Html\Renderer($params));
		throw new \Exception("Unknown Skriv rendering type '$type'.");
	}
	/**
	 * Parses a Skriv text and generates a converted text.
	 * @param	string	$text	The text to parse.
	 * @return	string	The generated string.
	 */
	public function render($text) {
		return ($this->_wikiRenderer->render($text));
	}
	/**
         * Returns the TOC content. By default, the rendered string is returned, but the
         * raw TOC tree is available.
         * @param	bool	$raw	(optional) Set to True to get the raw TOC tree. False by default.
         * @return	string|array	The TOC rendered string or the TOC tree.
         */
        public function getToc($raw=false) {
		return ($this->_config->getToc($raw));
	}
	/**
	 * Returns the footnotes content. By default, the rendered string is returned, but the
	 * raw list of footnotes is available.
	 * @param	bool	$raw	(optional) Set to True to get the raw list of footnotes.
	 *				False by default.
	 * @return	string|array	The footnotes' rendered string or the list of footnotes.
	 */
	public function getFootnotes($raw=false) {
		return ($this->_config->getFootnotes($raw));
	}
	/**
	 * Returns the lines which contain an error.
	 * @return	array	List of lines.
	 */
	public function getErrors() {
		return ($this->_wikiRenderer->errors);
	}
}


<?php

namespace PeskyCMF\Traits;

use Illuminate\Contracts\Validation\Factory;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Validation\ValidationException;
use PeskyCMF\HttpCode;
use Swayok\Utils\Set;

trait DataValidationHelper {

    /**
     * Get a validation factory instance.
     *
     * @return \Illuminate\Contracts\Validation\Factory
     */
    protected function getValidationFactory() {
        return app(Factory::class);
    }

    /**
     * Validate data and throw ValidationException if it is invalid or return validated data
     * @param array|Request $dataOrRequest
     * @param array $rules
     * @param array $messages
     * @param array $customAttributes
     * @return array - data from request filtered by rules keys
     * @throws ValidationException
     */
    public function validate($dataOrRequest, array $rules, array $messages = [], array $customAttributes = []) {
        $errors = $this->validateAndReturnErrors($dataOrRequest, $rules, $messages, $customAttributes);
        if (!empty($errors)) {
            $this->throwValidationErrorsResponse($errors);
        }

        return $this->extractInputFromRules($dataOrRequest, $rules);
    }

    /**
     * Validate data and returm errors array if it is invalid
     * @param array|Request $dataOrRequest
     * @param array $rules
     * @param array $messages
     * @param array $customAttributes
     * @return array - errors
     */
    public function validateAndReturnErrors($dataOrRequest, array $rules, array $messages = [], array $customAttributes = []) {
        $messages = Set::flatten($messages);
        if ($dataOrRequest instanceof Request) {
            $dataOrRequest = $dataOrRequest->all();
        }
        $validator = $this->getValidationFactory()->make($dataOrRequest, $rules, $messages, $customAttributes);
        if ($validator->fails()) {
            return $validator->getMessageBag()->toArray();
        }

        return [];
    }

    /**
     * Get the request input based on the given validation rules.
     *
     * @param array|Request $data
     * @param array $rules
     * @return array
     */
    protected function extractInputFromRules($data, array $rules) {
        $keys = collect($rules)->keys()->map(function ($rule) {
            return explode('.', $rule)[0];
        })->unique()->toArray();
        if (!($data instanceof Request)) {
            $data = collect($data);
        }
        return $data->only($keys);
    }

    /**
     * @return string
     */
    protected function getValidationErrorsResponseMessage() {
        return cmfTransGeneral('.message.invalid_data_received');
    }

    /**
     * @param array $errors
     * @return Response|JsonResponse
     */
    public function makeValidationErrorsJsonResponse(array $errors) {
        return response()->json($this->prepareDataForValidationErrorsResponse($errors), HttpCode::CANNOT_PROCESS);
    }

    /**
     * @param array $errors
     * @throws ValidationException
     */
    public function throwValidationErrorsResponse(array $errors) {
        throw new ValidationException(\Validator::make([], []), $this->makeValidationErrorsJsonResponse($errors));
    }

    /**
     * @param array $errors
     * @return array
     */
    public function prepareDataForValidationErrorsResponse(array $errors) {
        $message = array_get($errors, '_message', $this->getValidationErrorsResponseMessage());
        unset($errors['_message']);

        return [
            '_message' => $message,
            'errors' => static::fixValidationErrorsKeys($errors),
        ];
    }

    /**
     * Replace keys like 'some.column' by 'some[column]' to fit <input> names
     * @param array $errors
     * @return array
     */
    static public function fixValidationErrorsKeys(array $errors) {
        foreach ($errors as $key => $messages) {
            if (strpos($key, '.') !== false) {
                $newKey = preg_replace(
                    ['%^([^\]]+)\]%', '%\[\]\]%'],
                    ['$1', '][]'],
                    str_replace('.', '][', $key) . ']'
                );
                $errors[$newKey] = $messages;
                unset($errors[$key]);
            }
        }

        return $errors;
    }

}


<?php

namespace Ekyna\Component\Commerce\Common\Context;

use Ekyna\Component\Commerce\Cart\Provider\CartProviderInterface;
use Ekyna\Component\Commerce\Common\Country\CountryProviderInterface;
use Ekyna\Component\Commerce\Common\Currency\CurrencyProviderInterface;
use Ekyna\Component\Commerce\Common\Event\ContextChangeEvent;
use Ekyna\Component\Commerce\Common\Event\ContextEvent;
use Ekyna\Component\Commerce\Common\Event\ContextEvents;
use Ekyna\Component\Commerce\Common\Model\CountryInterface;
use Ekyna\Component\Commerce\Common\Model\CurrencyInterface;
use Ekyna\Component\Commerce\Common\Model\SaleInterface;
use Ekyna\Component\Commerce\Customer\Model\CustomerInterface;
use Ekyna\Component\Commerce\Customer\Provider\CustomerProviderInterface;
use Ekyna\Component\Commerce\Customer\Repository\CustomerGroupRepositoryInterface;
use Ekyna\Component\Commerce\Exception\InvalidArgumentException;
use Ekyna\Component\Commerce\Exception\UnexpectedValueException;
use Ekyna\Component\Commerce\Order\Model\OrderInterface;
use Ekyna\Component\Commerce\Pricing\Model\VatDisplayModes;
use Ekyna\Component\Resource\Locale\LocaleProviderInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * Class ContextProvider
 * @package Ekyna\Component\Commerce\Common\Context
 * @author  Etienne Dauvergne <contact@ekyna.com>
 */
class ContextProvider implements ContextProviderInterface
{
    /**
     * @var EventDispatcherInterface
     */
    protected $eventDispatcher;

    /**
     * @var CustomerProviderInterface
     */
    protected $customerProvider;

    /**
     * @var CartProviderInterface
     */
    protected $cartProvider;

    /**
     * @var LocaleProviderInterface
     */
    protected $localProvider;

    /**
     * @var CurrencyProviderInterface
     */
    protected $currencyProvider;

    /**
     * @var CountryProviderInterface
     */
    protected $countryProvider;

    /**
     * @var CustomerGroupRepositoryInterface
     */
    protected $customerGroupRepository;

    /**
     * @var string
     */
    protected $defaultVatDisplayMode;

    /**
     * @var string
     */
    protected $contextClass;

    /**
     * @var ContextInterface
     */
    protected $context;

    /**
     * Constructor.
     *
     * @param EventDispatcherInterface         $eventDispatcher
     * @param CartProviderInterface            $cartProvider
     * @param CustomerProviderInterface        $customerProvider
     * @param LocaleProviderInterface          $localProvider
     * @param CurrencyProviderInterface        $currencyProvider
     * @param CountryProviderInterface         $countryProvider
     * @param CustomerGroupRepositoryInterface $customerGroupRepository
     * @param string                           $defaultVatDisplayMode
     * @param string                           $contextClass
     */
    public function __construct(
        EventDispatcherInterface $eventDispatcher,
        CartProviderInterface $cartProvider,
        CustomerProviderInterface $customerProvider,
        LocaleProviderInterface $localProvider,
        CurrencyProviderInterface $currencyProvider,
        CountryProviderInterface $countryProvider,
        CustomerGroupRepositoryInterface $customerGroupRepository,
        $defaultVatDisplayMode = VatDisplayModes::MODE_ATI,
        $contextClass = Context::class
    ) {
        $this->eventDispatcher = $eventDispatcher;
        $this->customerProvider = $customerProvider;
        $this->cartProvider = $cartProvider;
        $this->localProvider = $localProvider;
        $this->currencyProvider = $currencyProvider;
        $this->countryProvider = $countryProvider;
        $this->customerGroupRepository = $customerGroupRepository;
        $this->defaultVatDisplayMode = $defaultVatDisplayMode;
        $this->contextClass = $contextClass;
    }

    /**
     * @inheritDoc
     */
    public function getCustomerProvider(): CustomerProviderInterface
    {
        return $this->customerProvider;
    }

    /**
     * @inheritDoc
     */
    public function getCartProvider(): CartProviderInterface
    {
        return $this->cartProvider;
    }

    /**
     * @inheritDoc
     */
    public function getLocalProvider(): LocaleProviderInterface
    {
        return $this->localProvider;
    }

    /**
     * @inheritDoc
     */
    public function getCurrencyProvider(): CurrencyProviderInterface
    {
        return $this->currencyProvider;
    }

    /**
     * @inheritDoc
     */
    public function getCountryProvider(): CountryProviderInterface
    {
        return $this->countryProvider;
    }

    /**
     * @inheritDoc
     */
    public function getContext(SaleInterface $sale = null): ContextInterface
    {
        if ($sale) {
            // TODO Check if up to date
            if (null !== $context = $sale->getContext()) {
                return $context;
            }

            return $this->createSaleContext($sale);
        }

        if (null !== $this->context) {
            return $this->context;
        }

        if ($this->cartProvider->hasCart()) {
            return $this->context = $this->createSaleContext($this->cartProvider->getCart());
        }

        return $this->context = $this->createDefaultContext();
    }

    /**
     * @inheritDoc
     */
    public function setContext($contextOrSale): ContextProviderInterface
    {
        if ($contextOrSale instanceof ContextInterface) {
            $this->context = $this->finalize($contextOrSale);
        } elseif ($contextOrSale instanceof SaleInterface) {
            $this->context = $this->createSaleContext($contextOrSale);
        } else {
            throw new InvalidArgumentException(
                "Expected instance of " . ContextInterface::class . " or " . SaleInterface::class
            );
        }

        return $this;
    }

    /**
     * @inheritDoc
     */
    public function clearContext(): ContextProviderInterface
    {
        $this->context = null;

        return $this;
    }

    /**
     * @inheritDoc
     */
    public function changeCurrencyAndCountry($currency = null, $country = null): ContextProviderInterface
    {
        if (!is_null($currency)) {
            if (is_string($currency)) {
                $currency = $this->currencyProvider->getCurrency($currency);
            }
            if (!$currency instanceof CurrencyInterface) {
                throw new UnexpectedValueException("Expected string or instance of " . CurrencyInterface::class);
            }
            if ($currency === $this->currencyProvider->getCurrency()) {
                $currency = null;
            }
        }

        if (!is_null($country)) {
            if (is_string($country)) {
                $country = $this->countryProvider->getCountry($country);
            }
            if (!$country instanceof CountryInterface) {
                throw new UnexpectedValueException("Expected string or instance of " . CountryInterface::class);
            }
            if ($country === $this->countryProvider->getCountry()) {
                $country = null;
            }
        }

        if ($currency || $country) {
            if ($currency) {
                $this->currencyProvider->setCurrency($currency);
            }
            if ($country) {
                $this->countryProvider->setCountry($country);
            }

            $this->eventDispatcher->dispatch(ContextEvents::CHANGE, new ContextChangeEvent($currency, $country));
        }

        return $this;
    }

    /**
     * Creates and sets the sale context.
     *
     * @param SaleInterface $sale The sale
     *
     * @return ContextInterface
     */
    protected function createSaleContext(SaleInterface $sale): ContextInterface
    {
        $context = $this->createContext();

        if (null !== $group = $sale->getCustomerGroup()) {
            $context
                ->setCustomerGroup($group)
                ->setBusiness($group->isBusiness());
        }
        if (null !== $address = $sale->getInvoiceAddress()) {
            $context->setInvoiceCountry($address->getCountry());
        }
        $address = $sale->isSameAddress() ? $sale->getInvoiceAddress() : $sale->getDeliveryAddress();
        if (null !== $address) {
            $context->setDeliveryCountry($address->getCountry());
        }
        if (null !== $currency = $sale->getCurrency()) {
            $context->setCurrency($currency);
        }
        if (null !== $mode = $sale->getVatDisplayMode()) {
            $context->setVatDisplayMode($mode);
        }
        if ($sale instanceof OrderInterface && null !== $date = $sale->getCreatedAt()) {
            $context->setDate($date);
        }

        $context->setTaxExempt($sale->isTaxExempt());

        if (null !== $customer = $sale->getCustomer()) {
            $this->fillFromCustomer($context, $customer);
        } elseif ($this->customerProvider->hasCustomer()) {
            $this->fillFromCustomer($context, $this->customerProvider->getCustomer());
        }

        $this->finalize($context);

        $sale->setContext($context);

        return $context;
    }

    /**
     * Creates a default context.
     *
     * @return ContextInterface
     */
    protected function createDefaultContext(): ContextInterface
    {
        $context = $this->createContext();

        if ($this->customerProvider->hasCustomer()) {
            $this->fillFromCustomer($context, $this->customerProvider->getCustomer());
        }

        $this->finalize($context);

        return $context;
    }

    /**
     * Fills the context from the given customer.
     *
     * @param ContextInterface  $context
     * @param CustomerInterface $customer
     */
    protected function fillFromCustomer(ContextInterface $context, CustomerInterface $customer): void
    {
        if (null === $context->getCustomerGroup()) {
            $context->setCustomerGroup($customer->getCustomerGroup());
        }
        if (null === $context->getInvoiceCountry()) {
            if (null !== $address = $customer->getDefaultInvoiceAddress(true)) {
                $context->setInvoiceCountry($address->getCountry());
            }
        }
        if (null === $context->getDeliveryCountry()) {
            if (null !== $address = $customer->getDefaultDeliveryAddress(true)) {
                $context->setDeliveryCountry($address->getCountry());
            }
        }
        /*if (null === $context->getCurrency()) {
            $context->setCurrency($customer->getCurrency());
        }
        if (null === $context->getLocale()) {
            $context->setLocale($customer->getLocale());
        }*/
    }

    /**
     * Fills the context's empty properties with default values.
     *
     * @param ContextInterface $context
     *
     * @return ContextInterface
     */
    protected function finalize(ContextInterface $context): ContextInterface
    {
        if (null === $context->getCustomerGroup()) {
            $context->setCustomerGroup($this->customerGroupRepository->findDefault());
        }
        if (null === $context->getInvoiceCountry()) {
            $context->setInvoiceCountry($this->countryProvider->getCountry());
        }
        if (null === $context->getDeliveryCountry()) {
            $context->setDeliveryCountry($this->countryProvider->getCountry());
        }
        if (null === $context->getCurrency()) {
            $context->setCurrency($this->currencyProvider->getCurrency());
        }
        if (null === $context->getLocale()) {
            $context->setLocale($this->localProvider->getCurrentLocale());
        }
        if (null === $context->getVatDisplayMode()) {
            if (null !== $mode = $context->getCustomerGroup()->getVatDisplayMode()) {
                $context->setVatDisplayMode($mode);
            } else {
                $context->setVatDisplayMode($this->defaultVatDisplayMode);
            }
        }

        $this->eventDispatcher->dispatch(ContextEvents::BUILD, new ContextEvent($context));

        return $context;
    }

    /**
     * Creates a new context.
     *
     * @return ContextInterface
     */
    protected function createContext(): ContextInterface
    {
        return new $this->contextClass;
    }
}


<?php
namespace PeskyCMF\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Routing\Route;
use PeskyCMF\Traits\DataValidationHelper;

class ValidateData {

    use DataValidationHelper;

    protected $errorMessage;

    protected function getValidationErrorsResponseMessage() {
        return isset($this->errorMessage)
            ? trans($this->errorMessage)
            : cmfTransGeneral('.message.invalid_data_received');
    }

    /**
     * Handle an incoming request.
     *
     * @param  Request $request
     * @param  \Closure $next
     * @return mixed
     */
    public function handle(Request $request, Closure $next) {
        /** @var Route $route */
        $route = $request->route();
        $actionInfo = $route->getAction();
        if (!empty($actionInfo['validate'])) {
            $messages = [];
            if (!empty($actionInfo['validation_messages'])) {
                $messages = trans($actionInfo['validation_messages']);
                if (!is_array($messages)) {
                    $messages = [];
                }
            }
            $errors = $this->validateAndReturnErrors($request, $actionInfo['validate'], $messages);
            if (!empty($errors)) {
                return $this->makeValidationErrorsJsonResponse($errors);
            }
        }
        return $next($request);
    }

}


<?php

namespace Ekyna\Component\Commerce\Common\Currency;

use Ekyna\Component\Commerce\Exception\InvalidArgumentException;

/**
 * Class ArrayCurrencyConverter
 * @package Ekyna\Component\Commerce\Common\Converter
 * @author  Etienne Dauvergne <contact@ekyna.com>
 */
class ArrayCurrencyConverter extends AbstractCurrencyConverter
{
    /**
     * @var array
     */
    private $rates;

    /**
     * Constructor.
     *
     * @param array  $rates
     * @param string $defaultCurrency
     */
    public function __construct(array $rates, $defaultCurrency = 'USD')
    {
        parent::__construct($defaultCurrency);

        $this->rates = [];
        foreach ($rates as $pair => $rate) {
            $this->addRate($pair, $rate);
        }
    }

    /**
     * Adds the conversion rate.
     *
     * @param string $pair
     * @param float  $rate
     *
     * @return ArrayCurrencyConverter
     */
    private function addRate($pair, $rate)
    {
        if (!preg_match('~^[A-Z]{3}/[A-Z]{3}$~', $pair)) {
            throw new InvalidArgumentException("Unexpected currency pair '$pair'.");
        }

        if (!(is_float($rate) && 0 < $rate)) {
            throw new InvalidArgumentException("Unexpected rate '$rate'.");
        }

        $this->rates[$pair] = $rate;

        return $this;
    }

    /**
     * @inheritdoc
     */
    public function getRate($base, $quote = null, \DateTime $date = null)
    {
        $base = strtoupper($base);
        $quote = strtoupper($quote ? $quote : $this->defaultCurrency);

        if ($base === $quote) {
            return 1.0;
        }

        $pair = "$base/$quote";
        if (!isset($this->rates[$pair])) {
            throw new InvalidArgumentException("Undefined conversion pair '$pair'.");
        }

        return $this->rates[$pair];
    }
}


<?php

namespace PeskyCMF\Config;

use App\AppSettings;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Foundation\Application;
use Illuminate\View\View;
use PeskyCMF\ApiDocs\CmfApiDocumentationModule;
use PeskyCMF\Auth\CmfAccessPolicy;
use PeskyCMF\Auth\CmfAuthModule;
use PeskyCMF\Auth\Middleware\CmfAuth;
use PeskyCMF\Http\Middleware\UseCmfSection;
use PeskyCMF\PeskyCmfAppSettings;
use PeskyCMF\Providers\PeskyCmfLanguageDetectorServiceProvider;
use PeskyCMF\Scaffold\ScaffoldConfig;
use PeskyCMF\Scaffold\ScaffoldConfigInterface;
use PeskyCMF\Scaffold\ScaffoldLoggerInterface;
use PeskyCMF\UI\CmfUIModule;
use PeskyORM\ORM\Column;
use PeskyORM\ORM\RecordInterface;
use PeskyORM\ORM\TableInterface;
use Swayok\Utils\Folder;
use Vluzrmos\LanguageDetector\Providers\LanguageDetectorServiceProvider;

abstract class CmfConfig extends ConfigsContainer {

    static private $instances = [];

    protected final function __construct() {
        self::$instances[get_class($this)] = $this;
        if (!array_key_exists(__CLASS__, self::$instances)) {
            $this->useAsPrimary();
        }
    }

    private static function _protect() {
        if (static::class === CmfConfig::class) {
            throw new \BadMethodCallException('Attempt to call method of CmfConfig class instead of its child class');
        }
    }

    /**
     * Use this object as default config
     * Note: this is used in *Record, *Table, and *TableStructure DB classes of CMS
     */
    public final function useAsDefault() {
        self::$instances['default'] = $this;
    }

    /**
     * Get CmfConfig marked as default one (or primary config if default one not provided)
     * @return CmfConfig
     */
    static final public function getDefault(): CmfConfig {
        return isset(self::$instances['default']) ? self::$instances['default'] : self::getPrimary();
    }

    /**
     * Use this object as primary config
     * Note: this object will be returned when you call CmfConfig::getInstance() instead of CustomConfig::getInstance()
     */
    public final function useAsPrimary() {
        self::$instances[__CLASS__] = $this;
    }

    /**
     * @return CmfConfig
     */
    static final public function getPrimary(): CmfConfig {
        if (!isset(self::$instances[__CLASS__])) {
            throw new \BadMethodCallException('Primary CMF Config is not specified');
        }
        return self::$instances[__CLASS__];
    }

    /**
     * Returns instance of config class it was called from
     * Note: method excluded from toArray() results but key "config_instance" added instead of it
     * @return $this
     */
    static final public function getInstance() {
        $class = get_called_class();
        if (!array_key_exists($class, self::$instances)) {
            self::$instances[$class] = new $class;
        }
        return self::$instances[$class];
    }

    /**
     * File name for this site section in 'configs' folder of project's root directory (without '.php' extension)
     * Example: 'admin' for config/admin.php;
     */
    static protected function configsFileName(): string {
        return 'peskycmf';
    }

    /**
     * @param string $key
     * @param mixed $default
     * @return mixed
     */
    static public function config($key, $default = null) {
        return config(static::getInstance()->configsFileName() . '.' . $key, $default);
    }

    static public function cmf_routes_config_files(): array {
        return [
            __DIR__ . '/peskycmf.routes.php',
        ];
    }

    /**
     * @return array
     */
    static public function language_detector_configs(): array {
        return [
            'autodetect' => true,
            'driver' => 'browser',
            'cookie' => true,
            'cookie_name' => static::makeUtilityKey('locale'),
            'cookie_encrypt' => true,
            'languages' => static::locales_for_language_detector(),
        ];
    }

    /**
     * @return PeskyCmfAppSettings|\App\AppSettings
     */
    static public function getAppSettings(): AppSettings {
        return app(PeskyCmfAppSettings::class);
    }

    /**
     * @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard|\Illuminate\Auth\SessionGuard
     */
    static public function getAuthGuard(): Guard {
        return static::getAuthModule()->getAuthGuard();
    }

    /**
     * @return \PeskyCMF\Db\Admins\CmfAdmin|\Illuminate\Contracts\Auth\Authenticatable|\PeskyCMF\Db\Traits\ResetsPasswordsViaAccessKey|\App\Db\Admins\Admin|null
     */
    static public function getUser(): ?RecordInterface {
        return static::getAuthModule()->getUser();
    }

    /**
     * @return CmfAuthModule
     */
    static public function getAuthModule(): CmfAuthModule {
        return app(CmfAuthModule::class);
    }

    /**
     * @return CmfUIModule
     */
    static public function getUiModule(): CmfUIModule {
        return app(CmfUIModule::class);
    }

    /**
     * Email address used in "From" header for emails sent to users
     * @return string
     * @throws \UnexpectedValueException
     */
    static public function system_email_address(): string {
        return static::config('system_email_address', function () {
            return 'noreply@' . request()->getHost();
        });
    }

    /**
     * Url prefix for routes
     * @return string
     */
    static public function url_prefix(): string {
        return static::config('url_prefix', 'admin');
    }

    /**
     * Url prefix for routes
     * @return string
     */
    static public function app_subfolder(): string {
        return static::config('app_subfolder', 'Admin');
    }

    /**
     * @return string
     */
    static public function getPathToCmfClasses(): string {
        return app_path(static::app_subfolder());
    }

    /**
     * @return string|null
     */
    static public function recaptcha_private_key(): ?string {
        return config('services.recaptcha.private_key');
    }

    /**
     * @return string|null
     */
    static public function recaptcha_public_key(): ?string {
        return config('services.recaptcha.public_key');
    }

    static public function recaptcha_script(): string {
        return 'https://www.google.com/recaptcha/api.js?hl=' . static::getShortLocale();
    }

    /**
     * @return string
     */
    static public function default_page_title(): string {
        return setting()->default_browser_title(function () {
            return static::transCustom('.default_page_title');
        }, true);
    }

    /**
     * @return string
     */
    static public function page_title_addition(): string {
        return setting()->browser_title_addition(function () {
            return static::default_page_title();
        }, true);
    }

    /**
     * Controller class name for CMF scaffolds API
     * @return string
     */
    static public function cmf_scaffold_api_controller_class(): string {
        return \PeskyCMF\Http\Controllers\CmfScaffoldApiController::class;
    }

    /**
     * General controller class name for CMF (basic ui views, custom pages views, login/logout, etc.)
     * @return string
     */
    static public function cmf_general_controller_class(): string {
        return \PeskyCMF\Http\Controllers\CmfGeneralController::class;
    }

    /**
     * Prefix for route names in peskycmf.routes.php
     * Use with caution and only when you really know what you're doing
     * @return string
     */
    static public function routes_names_prefix(): string {
        return static::url_prefix() . '/';
    }

    /**
     * @param string $routeAlias
     * @return string
     */
    static public function getRouteName($routeAlias): string {
        return static::routes_names_prefix() . $routeAlias;
    }

    /**
     * @param string $routeName
     * @param array $parameters
     * @param bool $absolute
     * @return string
     */
    static public function route(string $routeName, array $parameters = [], bool $absolute = false): string {
        return route(static::getRouteName($routeName), $parameters, $absolute);
    }

    /**
     * Note: placed here to avoid problems with auth module constructor when registering routes for all cmf sections
     * @return array
     */
    static public function auth_middleware(): array {
        return (array)static::config('auth.middleware', [CmfAuth::class]);
    }

    /**
     * Basic set of middlewares for scaffold api controller
     * @return array
     */
    static public function middleware_for_cmf_scaffold_api_controller(): array {
        return [];
    }

    /**
     * The menu structure of the site.
     * @return array
     * You may use static::getMenuItem($resourceName) or static::getMenuItems() to get
     * menu items from registered scaffold configs.
     * Todo: add CmfSidebarMenuItem class to help designing menu items
     * Menu item format:
     *    [
     *         [
     *              'label' => 'label',
     *              'url' => '/url',
     *              'icon' => 'icon',
     *         ],
     *         [
     *              'label' => 'label',
     *              'icon' => 'icon',
     *              'open' => false,
     *              'submenu' => [...]
     *         ],
     *    ]
     * Available options:
     *  - label - text of menu item (required)
     *  - icon - icon for menu item (optinal)
     *  - url - where to send user (required if no 'submenu' present; can contain empty value to hide menu item)
     *  - submenu - array of arrays (optional); arrays may contain all keys described here except 'submenu' (3rd level of menu items not supported)
     *  - additional - any html to add to menu item (optional)
     *  - counter - name of a "counter" variable (optional); adds  to menu item.
     *          You can provide contents for this tag via CmfConfig::getCountersForMenu() method.
     *          Example: public function getCountersForMenu() {
     *              return ['prending_orders' => '2'];
     *          }
     *          'prending_orders' here is the name of a "counter".
     *          The idea is to add a possibility to show some autoupdatable numbers (counters) on menu items like
     *          pending orders count, new messages count, etc...
     *          Counters updated on every POST/PUT/DELETE request and by timeout. Also you can be manually
     *          request counters update by JS function AdminUI.updateMenuCounters();
     *          Update interval can be changed via JS app config: CmfSettings.menuCountersUpdateIntervalMs = 30000;
     */
    static public function menu(): array {
        return array_merge(
            [
                [
                    'label' => static::transCustom('.page.dashboard.menu_title'),
                    'url' => routeToCmfPage('dashboard'),
                    'icon' => 'glyphicon glyphicon-dashboard',
                ],
                /*[
                    'label' => static::transCustom('.users.menu_title'),
                    'url' => '/resource/users',
                    'icon' => 'fa fa-group'
                ],*/
                /*[
                    'label' => static::transCustom('.menu.section_utils'),
                    'icon' => 'glyphicon glyphicon-align-justify',
                    'submenu' => [
                        [
                            'label' => static::transCustom('.admins.menu_title'),
                            'url' => '/resource/admins',
                            'icon' => 'glyphicon glyphicon-user'
                        ],
                    ],
                ]*/
                /*[
                    'label' => 'required',
                    'icon' => 'optional',
                    'submenu' => [], //< 'optional'
                    'url' => 'required
                ]*/
            ],
            static::getMenuItems()
        );
    }

    /**
     * Get values for menu items counters (details in CmfConfig::menu())
     * @return array like ['pending_orders' => '2']
     */
    static public function getValuesForMenuItemsCounters(): array {
        return static::getUiModule()->getValuesForMenuItemsCounters();
    }

    /**
     * @param string $itemKey
     * @param array|\Closure $menuItem - format: see menu()
     */
    static public function addMenuItem(string $itemKey, $menuItem) {
        static::getUiModule()->addCustomMenuItem($itemKey, $menuItem);
    }

    /**
     * @param array $itemsKeys - list of keys to return values for, if empty - will return all menu items
     * @return array
     */
    static protected function getMenuItems(...$itemsKeys): array {
        $menuItems = static::getUiModule()->getMenuItems();
        if (empty($itemsKeys)) {
            return $menuItems;
        } else {
            return array_intersect_key($menuItems, array_flip($itemsKeys));
        }
    }

    /**
     * @param array $excludeItemsWithKeys - list of keys to exclude from resulting menu items array
     * @return array
     */
    static protected function getMenuItemsExcept(...$excludeItemsWithKeys): array {
        if (empty($excludeItemsWithKeys)) {
            return static::getInstance()->getMenuItems();
        } else {
            return array_diff_key(static::getInstance()->getMenuItems(), array_flip($excludeItemsWithKeys));
        }
    }

    /**
     * Get menu item config or null if there is no such menu item
     * @param string $resourceName
     * @return array|null
     */
    static protected function getMenuItem($resourceName): ?array {
        return array_get(static::getMenuItems(), $resourceName);
    }

    /**
     * Name for custom CMF dictionary that contains translation for CMF resource sections and pages
     * @return string
     */
    static public function custom_dictionary_name(): string {
        return static::config('dictionary', 'cmf::custom');
    }

    /**
     * Translate from custom dictionary. You can use it via CmfConfig::transCustom() insetad of
     * CmfConfig::getPrimary()->transCustom() if you need to get translation for primary config.
     * Note: if there is no translation in your dictionary - it will be imported from 'cmf::custom' dictionary
     * @param string $path - without dictionary name. Example: 'admins.test' will be converted to '{dictionary}.admins.test'
     * @param array $parameters
     * @param null|string $locale
     * @return string|array
     */
    static public function transCustom($path, array $parameters = [], $locale = null) {
        $dict = static::getInstance()->custom_dictionary_name();
        $path = '.' . ltrim($path, '.');
        $primaryPath = $dict . $path;
        $trans = trans($primaryPath, $parameters, $locale);
        if ($trans === $primaryPath && $dict !== 'cmf::custom') {
            $fallbackPath = 'cmf::custom' . $path;
            $trans = trans($fallbackPath, $parameters, $locale);
            if ($trans === $fallbackPath) {
                return $primaryPath;
            }
        }
        return $trans;
    }

    /**
     * Dictionary that contains general ui translations for CMF
     * @return string
     */
    static public function cmf_general_dictionary_name(): string {
        return 'cmf::cmf';
    }

    /**
     * Translate from custom dictionary. You can use it via CmfConfig::transGeneral() insetad of
     * CmfConfig::getPrimary()->transGeneral() if you need to get translation for primary config
     * Note: if there is no translation in your dictionary - it will be imported from 'cmf::cmf' dictionary
     * @param string $path - without dictionary name. Example: 'admins.test' will be converted to '{dictionary}.admins.test'
     * @param array $parameters
     * @param null|string $locale
     * @return string|array
     */
    static public function transGeneral($path, array $parameters = [], $locale = null) {
        $dict = static::getInstance()->cmf_general_dictionary_name();
        $path = '.' . ltrim($path, '.');
        $primaryPath = $dict . $path;
        $trans = trans($primaryPath, $parameters, $locale);
        if ($trans === $primaryPath && $dict !== 'cmf::cmf') {
            $fallbackPath = 'cmf::cmf' . $path;
            $trans = trans($fallbackPath, $parameters, $locale);
            if ($trans === $fallbackPath) {
                return $primaryPath;
            }
        }
        return $trans;
    }

    /**
     * Translations for Api Docs
     * @param string $translationPath
     * @param array $parameters
     * @param null $locale
     * @return string|array
     */
    static public function transApiDoc(string $translationPath, array $parameters = [], $locale = null) {
        if (static::class === self::class) {
            // redirect CmfConfig::transApiDoc() calls to primary config class
            return self::getPrimary()->transApiDoc($translationPath, $parameters, $locale);
        } else {
            $translationPath = 'api_docs.' . ltrim($translationPath, '.');
            return static::transCustom($translationPath, $parameters, $locale);
        }
    }

    /**
     * Default CMF language
     * @return string
     */
    static public function default_locale(): string {
        return static::config('locale', 'en');
    }

    /**
     * Supported locales for CMF
     * @return array - each value is locale code (usually 2 chars: en, ru; rarely - 5 chars: ru_RU, en_US)
     */
    static public function locales(): array {
        return array_unique(array_values(static::config('locales', ['en'])));
    }

    /**
     * Supported locales for CMF
     * Note: you can redirect locales using key as locale to redirect from and value as locale to redirect to
     * For details see: https://github.com/vluzrmos/laravel-language-detector
     * @return array
     */
    static public function locales_for_language_detector(): array {
        return (array)static::config('locales', ['en']);
    }

    /**
     * Change locale inside CMF/CMS area
     * @param string $locale
     */
    static public function setLocale($locale) {
        static::_protect();
        \LanguageDetector::apply($locale);
        \LanguageDetector::addCookieToQueue(\App::getLocale());
        Column::setValidationErrorsMessages((array)static::transGeneral('form.message.column_validation_errors') ?: []);
    }

    /**
     * @return string - 2 chars lowercases
     */
    static public function getShortLocale(): string {
        return strtolower(substr(app()->getLocale(), 0, 2));
    }

    static protected $localeSuffixMap = [
        'en' => 'US',
        'ko' => 'KR',
        'ja' => 'JP',
        'cs' => 'CZ',
        'da' => 'DK',
        'et' => 'EE',
        'fa' => 'IR',
        'kh' => 'KM',
        'nb' => 'NO',
        'pt' => 'BR',
        'sv' => 'SE',
        'vi' => 'VN',
        'zh' => 'CN',
    ];
    /**
     * Get locale in format "en_US" or "ru-RU" or "it-it"
     * @param string $separator
     * @param bool $lowercased - true: will return "it-it" instead of "it-IT"
     * @return string
     */
    static public function getLocaleWithSuffix($separator = '_', $lowercased = false): ?string {
        $locale = preg_split('%[-_]%', strtolower(app()->getLocale()));
        if (count($locale) === 2) {
            return $locale[0] . $separator . ($lowercased ? $locale[1] : strtoupper($locale[1]));
        } else {
            $localeSuffix = isset(static::$localeSuffixMap[$locale[0]]) ? static::$localeSuffixMap[$locale[0]] : $locale[0];
            return $locale[0] . $separator . ($lowercased ? $localeSuffix : strtoupper($localeSuffix));
        }
    }

    /**
     * Reset locale to default
     */
    static public function resetLocale() {
        static::setLocale(\LanguageDetector::getDriver()->detect());
    }

    /**
     * @return string
     */
    static public function session_message_key(): string {
        return static::makeUtilityKey('message');
    }

    /**
     * Start page URL of CMS section
     *
     * @param bool $absolute
     * @return string
     */
    static public function home_page_url($absolute = false): string {
        return static::route('cmf_start_page', [], $absolute);
    }

    /**
     * How much rows to display in data tables
     * @return int
     */
    static public function rows_per_page(): int {
        return 25;
    }

    /**
     * Additional configs for jQuery Data Tables lib
     * @return array
     */
    static public function data_tables_config(): array {
        return [
            'scrollX' => true,
            'scrollY' => 'calc(100vh - 248px)',
            'scrollCollapse' => true,
            'width' => '100%',
            'filter' => true,
            'stateSave' => true,
            'dom' => "
                <'row toolbar-container'
                    <'col-xs-12 col-md-5'<'filter-toolbar btn-toolbar text-left'>>
                    <'col-xs-12 col-md-7'<'toolbar btn-toolbar text-right'>>
                >
                <'#query-builder-container'<'#query-builder'>>
                <'row data-grid-container'<'col-sm-12'tr>>
                <'row pagination-container'
                    <'col-md-3 hidden-xs hidden-sm'i>
                    <'col-xs-12 col-md-6'p>
                    <'col-md-3 hidden-xs hidden-sm'l>
                >",
        ];
    }

    /**
     * Additional configs for CKEditor lib
     * @return array
     */
    static public function ckeditor_config(): array {
        return [
            'language' => static::getShortLocale(),
            'toolbarGroups' => [
                [ 'name' => 'clipboard', 'groups' => [ 'clipboard', 'undo' ] ],
                [ 'name' => 'editing', 'groups' => [ 'find', 'selection', 'spellchecker', 'editing' ] ],
                [ 'name' => 'links', 'groups' => [ 'links' ] ],
                [ 'name' => 'insert', 'groups' => [ 'insert' ] ],
                [ 'name' => 'forms', 'groups' => [ 'forms' ] ],
                [ 'name' => 'tools', 'groups' => [ 'tools' ] ],
                [ 'name' => 'document', 'groups' => [ 'mode', 'document', 'doctools' ] ],
                [ 'name' => 'others', 'groups' => [ 'others' ] ],
                [ 'name' => 'about', 'groups' => [ 'about' ] ],
                '/',
                [ 'name' => 'basicstyles', 'groups' => [ 'basicstyles', 'cleanup' ] ],
                [ 'name' => 'paragraph', 'groups' => [ 'list', 'indent', 'blocks', 'align', 'bidi', 'paragraph' ] ],
                [ 'name' => 'styles', 'groups' => [ 'styles' ] ],
                [ 'name' => 'colors', 'groups' => [ 'colors' ] ],
            ],
            'removeButtons' => 'Superscript,Find,Replace,SelectAll,Scayt,Flash,Smiley,PageBreak,Iframe,Form,Checkbox,'
                . 'Radio,TextField,Textarea,Select,Button,ImageButton,HiddenField,Maximize,Save,NewPage,Preview,Print,'
                . 'Templates,Strike,Subscript,BidiLtr,BidiRtl,Language,Styles',
            'format_tags' => 'p;h1;h2;h3;pre',
            'enterMode' => 1, //< insert
 on pressing ENTER
            'forceEnterMode' => true,
            'removeDialogTabs' => 'image:advanced',
            'extraPlugins' => 'uploadimage',
            'filebrowserImageUploadUrl' => static::route('cmf_ckeditor_upload_image', ['_token' => csrf_token()]),
            'uploadUrl' => static::route('cmf_ckeditor_upload_image', ['_token' => csrf_token()]),
            'contentsCss' => static::css_files_for_wysiwyg_editor(),
        ];
    }

    /**
     * Add some css files inside wysuwyg editor to allow custom styling while editing wysiwyg contents
     * @return array
     */
    static public function css_files_for_wysiwyg_editor(): array {
        return [];
    }

    /**
     * Data inserts for CmsPage-related scaffold configs to be added to ckeditor's plugin
     * Use WysiwygFormInput::createDataInsertConfig() and WysiwygFormInput::createDataInsertConfigWithArguments()
     * to create valid config
     * @param ScaffoldConfig $scaffold
     * @return array
     */
    static public function getAdditionalWysywygDataInsertsForCmsPages(ScaffoldConfig $scaffold): array {
        return [];
    }

    /**
     * Html inserts for CmsPage-related scaffold configs to be added to ckeditor's plugin
     * Use WysiwygFormInput::createHtmlInsertConfig('', 'menu title') to create valid config
     * @param ScaffoldConfig $scaffold
     * @return array
     */
    static public function getWysywygHtmlInsertsForCmsPages(ScaffoldConfig $scaffold): array {
        return [];
    }

    /**
     * Get ScaffoldConfig instance
     * @param string $resourceName
     * @return ScaffoldConfig|ScaffoldConfigInterface
     * @throws \InvalidArgumentException
     */
    static public function getScaffoldConfig(string $resourceName): ScaffoldConfigInterface {
        return static::getUiModule()->getScaffoldConfig($resourceName);
    }

    /**
     * Get ScaffoldConfig class name
     * @param string $resourceName
     * @return string
     * @throws \InvalidArgumentException
     */
    static public function getScaffoldConfigClass(string $resourceName): string {
        return static::getUiModule()->getScaffoldConfigClass($resourceName);
    }

    /**
     * Get TableInterface instance for $tableName
     * Note: can be ovewritted to allow usage of fake tables in resources routes
     * It is possible to use this with static::getScaffoldConfig() to alter default scaffold configs
     * @param string $resourceName
     * @return TableInterface
     */
    static public function getTableByResourceName(string $resourceName): TableInterface {
        return static::getUiModule()->getTableByResourceName($resourceName);
    }

    static public function getApiDocumentationModule(): CmfApiDocumentationModule {
        return app(CmfApiDocumentationModule::class);
    }

    protected $httpRequestsLogger;

    /**
     * @return null|ScaffoldLoggerInterface;
     */
    static public function getHttpRequestsLogger(): ?ScaffoldLoggerInterface {
        if (!static::getInstance()->httpRequestsLogger && app()->bound(ScaffoldLoggerInterface::class)) {
            static::setHttpRequestsLogger(app(ScaffoldLoggerInterface::class));
        }
        return static::getInstance()->httpRequestsLogger;
    }

    /**
     * Logger will be used to logs requested records pk and changes
     * @param ScaffoldLoggerInterface $httpRequestsLogger
     */
    static public function setHttpRequestsLogger(ScaffoldLoggerInterface $httpRequestsLogger) {
        static::getInstance()->httpRequestsLogger = $httpRequestsLogger;
    }

    /**
     * @return array
     */
    static public function getCachedPagesTemplates(): array {
        return \Cache::remember(
            static::getCacheKeyForOptimizedUiTemplates('pages'),
            (int)static::config('ui.optimize_ui_templates.timeout', 0),
            function () {
                $generalControllerClass = static::cmf_general_controller_class();
                /** @var \PeskyCMF\Http\Controllers\CmfGeneralController $controller */
                $controller = new $generalControllerClass();
                if (static::getUser()) {
                    return [
                        static::route('cmf_main_ui', [], false) => $controller->getBasicUiView(),
                    ];
                } else {
                    return [
                        static::route('cmf_login', [], false) . '.html' => $controller->getLoginTpl(),
                        static::route('cmf_forgot_password', [], false) . '.html' => $controller->getForgotPasswordTpl(),
                    ];
                }
            }
        );
    }

    /**
     * @return array
     */
    static public function getCachedResourcesTemplates(): array {
        if (!static::getUser()) {
            return [];
        }
        return \Cache::remember(
            static::getCacheKeyForOptimizedUiTemplates('resources'),
            (int)static::config('ui.optimize_ui_templates.timeout', 0),
            function () {
                return static::collectResourcesTemplatesToBeCached();
            }
        );
    }

    /**
     * @return array
     */
    static protected function collectResourcesTemplatesToBeCached(): array {
        $resourceTemplates = [];
        /** @var ScaffoldConfig $scaffoldConfigClass */
        foreach (static::getUiModule()->getResources() as $resourceName => $scaffoldConfigClass) {
            /** @var ScaffoldConfig $scaffoldConfig */
            $scaffoldConfig = new $scaffoldConfigClass();
            $splitted = $scaffoldConfig->renderTemplatesAndSplit();
            if (!empty($splitted)) {
                $resourceTemplates[$resourceName] = $splitted;
            }
        }
        return $resourceTemplates;
    }

    /**
     * User-id-based cache key
     * @param string $group
     * @return string
     */
    static protected function getCacheKeyForOptimizedUiTemplates($group): string {
        return static::getCacheKeyForOptimizedUiTemplatesBasedOnUserId($group);
    }

    /**
     * User-id-based cache key
     * @param string $group
     * @return string
     */
    static protected function getCacheKeyForOptimizedUiTemplatesBasedOnUserId($group): string {
        if (static::getAuthModule()->getAccessPolicyClassName() === CmfAccessPolicy::class) {
            $userId = 'any';
        } else {
            $user = static::getUser();
            $userId = $user ? $user->getAuthIdentifier() : 'not_authenticated';
        }
        return static::url_prefix() . '_templates_' . static::getShortLocale() . '_' . $group . '_user_' . $userId;
    }

    /**
     * Role-based cache key
     * @param string $group
     * @return string
     */
    static protected function getCacheKeyForOptimizedUiTemplatesBasedOnUserRole($group): string {
        if (static::getAuthModule()->getAccessPolicyClassName() === CmfAccessPolicy::class) {
            $userId = 'any';
        } else {
            $userId = 'not_authenticated';
            $user = static::getUser();
            if ($user && $user->existsInDb()) {
                if ($user::hasColumn('is_superadmin')) {
                    $userId = '__superadmin__';
                } else if ($user::hasColumn('role')) {
                    $userId = $user->role;
                } else {
                    $userId = 'user';
                }
            }
        }
        return static::url_prefix() . '_templates_' . static::getShortLocale() . '_' . $group . '_user_' . $userId;
    }

    /**
     * @param $keySuffix
     * @return string
     */
    static public function makeUtilityKey($keySuffix): string {
        return preg_replace('%[^a-zA-Z0-9]+%i', '_', static::url_prefix()) . '_' . $keySuffix;
    }

    /**
     * @param string $sectionName - cmf section name
     */
    public function declareRoutes($sectionName) {
        $cmfConfig = $this;
        $groupConfig = [
            'prefix' => static::url_prefix(),
            'middleware' => (array)static::config('routes_middleware', ['web']),
        ];
        $cmfSectionSelectorMiddleware = $this->getUseCmfSectionMiddleware($sectionName);
        array_unshift($groupConfig['middleware'], $cmfSectionSelectorMiddleware);
        $namespace = static::config('controllers_namespace');
        if (!empty($namespace)) {
            $groupConfig['namespace'] = ltrim($namespace, '\\');
        }
        // custom routes
        $files = (array)static::config('routes_files', []);
        if (count($files) > 0) {
            foreach ($files as $filePath) {
                \Route::group($groupConfig, function () use ($filePath, $cmfConfig) {
                    // warning! $cmfConfig may be used inside included file
                    include base_path($filePath);
                });
            }
        }

        unset($groupConfig['namespace']); //< cmf routes should be able to use controllers from vendors dir
        if (!\Route::has(static::getRouteName('cmf_start_page'))) {
            \Route::group($groupConfig, function () {
                \Route::get('/', [
                    'uses' => static::cmf_general_controller_class() . '@redirectToUserProfile',
                    'as' => static::getRouteName('cmf_start_page'),
                ]);
            });
        }

        \Route::group($groupConfig, function () use ($cmfConfig) {
            // warning! $cmfConfig may be used inside included file
            include __DIR__ . '/peskycmf.routes.php';
        });

        // special route for ckeditor config.js file
        $groupConfig['middleware'] = [$cmfSectionSelectorMiddleware]; //< only 1 needed
        \Route::group($groupConfig, function () {
            \Route::get('ckeditor/config.js', [
                'uses' => static::cmf_general_controller_class() . '@getCkeditorConfigJs',
                'as' => static::getRouteName('cmf_ckeditor_config_js'),
            ]);
        });
    }

    /**
     * Get middleware that will tell system whick CMF section to use
     * @param string $sectionName
     * @return string
     */
    protected function getUseCmfSectionMiddleware($sectionName): string {
        return UseCmfSection::class . ':' . $sectionName;
    }

    /**
     * @param Application $app
     */
    public function updateAppConfigs($app) {
        /** @var \Illuminate\Config\Repository $appConfigs */
        $appConfigs = $app['config'];
        // add auth guard but do not select it as primary
        $this->addAuthGuardConfigToAppConfigs($appConfigs);
    }

    /**
     * @param \Illuminate\Config\Repository $appConfigs
     */
    protected function addAuthGuardConfigToAppConfigs($appConfigs) {
        // merge cmf guard and provider with configs in config/auth.php
        $cmfAuthConfig = static::config('auth.guard');
        if (!is_array($cmfAuthConfig)) {
            // custom auth guard name provided
            return;
        }

        $config = $appConfigs->get('auth', [
            'guards' => [],
            'providers' => [],
        ]);

        $guardName = array_get($cmfAuthConfig, 'name') ?: static::url_prefix();
        if (array_key_exists($guardName, $config['guards'])) {
            throw new \UnexpectedValueException('There is already an auth guard with name "' . $guardName . '"');
        }
        $provider = array_get($cmfAuthConfig, 'provider');
        if (is_array($provider)) {
            $providerName = array_get($provider, 'name', $guardName);
            if (empty($provider['model'])) {
                $provider['model'] = static::config('auth.user_record_class', function () {
                    throw new \UnexpectedValueException('You need to provide a DB Record class for users');
                });
            }
        } else {
            $providerName = $provider;
            $provider = null;
        }
        if (array_key_exists($providerName, $config['providers'])) {
            throw new \UnexpectedValueException('There is already an auth provider with name "' . $guardName . '"');
        }
        $config['guards'][$guardName] = [
            'driver' => array_get($cmfAuthConfig, 'driver', 'session'),
            'provider' => $providerName,
        ];
        if (!empty($provider)) {
            $config['providers'][$providerName] = $provider;
        }

        $appConfigs->set('auth', $config);
    }

    /**
     * @param Application $app
     */
    public function initSection($app) {
        $this->useAsPrimary();

        // init auth module
        /** @var CmfAuthModule $cmfAuthModuleClass */
        $cmfAuthModuleClass = static::config('auth.module') ?: CmfAuthModule::class;
        /** @var CmfAuthModule $authModule */
        $authModule = new $cmfAuthModuleClass($this);
        $app->singleton(CmfAuthModule::class, function () use ($authModule) {
            return $authModule;
        });
        $authModule->init();

        // init UI module
        /** @var CmfAuthModule $cmfAuthModuleClass */
        $cmfUIModuleClass = static::config('ui.module') ?: CmfUIModule::class;
        /** @var CmfAuthModule $authModule */
        $uiModule = new $cmfUIModuleClass($this);
        $app->singleton(CmfUIModule::class, function () use ($uiModule) {
            return $uiModule;
        });

        // init API Documentation module
        $app->singleton(CmfApiDocumentationModule::class, function () {
            $cmfApiDocsModuleClass = static::config('api_documentation.module') ?: CmfApiDocumentationModule::class;
            return new $cmfApiDocsModuleClass($this);
        });

        // send $cmfConfig and $uiModule var to all views
        \View::composer('*', function (View $view) use ($uiModule) {
            $view
                ->with('cmfConfig', $this)
                ->with('uiModule', $uiModule);
        });

        /** @var PeskyCmfLanguageDetectorServiceProvider $langDetectorProvider */
        $langDetectorProvider = $app->register(PeskyCmfLanguageDetectorServiceProvider::class);
        $app->alias(LanguageDetectorServiceProvider::class, PeskyCmfLanguageDetectorServiceProvider::class);
        $langDetectorProvider->importConfigsFromPeskyCmf($this);

        // configure session
        $this->configureSession($app);

        if (static::config('file_access_mask') !== null) {
            umask(static::config('file_access_mask'));
        }

        // Register resource name to ScaffoldConfig class mappings
        //$resources = static::getUiModule()->getScaffolds();
        /** @var ScaffoldConfig $scaffoldConfigClass */
        /*foreach ($resources as $scaffoldConfigClass) {
            static::registerScaffoldConfigForResource($scaffoldConfigClass::getResourceName(), $scaffoldConfigClass);
        }*/
    }

    /**
     * @param Application $app
     */
    protected function configureSession($app) {
        /** @var \Illuminate\Config\Repository $appConfigs */
        $appConfigs = $app['config'];
        $config = array_merge(
            $appConfigs->get('session'),
            ['path' => '/' . trim(static::url_prefix(), '/')],
            (array)static::config('session', [])
        );
        $appConfigs->set('session', $config);
    }

}


<?php

namespace Ekyna\Component\Commerce\Common\Listener;

use Ekyna\Bundle\CoreBundle\Model\UploadableInterface;
use Ekyna\Bundle\CoreBundle\Uploader\UploaderInterface;

/**
 * Class UploadableListener
 * @package Ekyna\Component\Commerce\Common\Listener
 * @author  Etienne Dauvergne <contact@ekyna.com>
 */
class UploadableListener
{
    /**
     * @var UploaderInterface
     */
    private $uploader;

    /**
     * @var bool
     */
    private $enabled = true;

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

    /**
     * Sets whether the listener is enabled.
     *
     * @param boolean $enabled
     */
    public function setEnabled($enabled)
    {
        $this->enabled = (bool)$enabled;
    }

    /**
     * Pre persist event handler.
     *
     * @param UploadableInterface $uploadable
     */
    public function prePersist(UploadableInterface $uploadable)
    {
        if (!$this->enabled) {
            return;
        }

        // TODO Remove (when handled by resource behavior).
        $uploadable
            ->setCreatedAt(new \DateTime())
            ->setUpdatedAt(new \DateTime());

        $this->uploader->prepare($uploadable);
    }

    /**
     * Post persist event handler.
     *
     * @param UploadableInterface $uploadable
     */
    public function postPersist(UploadableInterface $uploadable)
    {
        if (!$this->enabled) {
            return;
        }

        $this->uploader->upload($uploadable);
    }

    /**
     * Pre update event handler.
     *
     * @param UploadableInterface $uploadable
     */
    public function preUpdate(UploadableInterface $uploadable)
    {
        if (!$this->enabled) {
            return;
        }

        // TODO Remove (when handled by resource behavior).
        $uploadable->setUpdatedAt(new \DateTime());

        $this->uploader->prepare($uploadable);
    }

    /**
     * Post update event handler.
     *
     * @param UploadableInterface $uploadable
     */
    public function postUpdate(UploadableInterface $uploadable)
    {
        if (!$this->enabled) {
            return;
        }

        $this->uploader->upload($uploadable);
    }

    /**
     * Pre remove event handler.
     *
     * @param UploadableInterface $uploadable
     */
    public function preRemove(UploadableInterface $uploadable)
    {
        if (!$this->enabled) {
            return;
        }

        $uploadable->setOldPath($uploadable->getPath());
    }

    /**
     * Post remove event handler.
     *
     * @param UploadableInterface $uploadable
     */
    public function postRemove(UploadableInterface $uploadable)
    {
        if (!$this->enabled) {
            return;
        }

        $this->uploader->remove($uploadable);
    }
}


<?php

namespace Ekyna\Component\Commerce\Bridge\Symfony\Serializer\Normalizer;

use Ekyna\Component\Commerce\Customer\Model\CustomerGroupInterface;
use Ekyna\Component\Resource\Serializer\AbstractResourceNormalizer;

/**
 * Class CustomerGroupNormalizer
 * @package Ekyna\Component\Commerce\Bridge\Symfony\Serializer\Normalizer
 * @author  Etienne Dauvergne <contact@ekyna.com>
 */
class CustomerGroupNormalizer extends AbstractResourceNormalizer
{
    /**
     * @inheritdoc
     *
     * @param CustomerGroupInterface $group
     */
    public function normalize($group, $format = null, array $context = [])
    {
        if ($format === 'csv' && $this->contextHasGroup('TableExport', $context)) {
            return (string)$group;
        }

        return parent::normalize($group, $format, $context);
    }

    /**
     * @inheritdoc
     */
    public function denormalize($data, $class, $format = null, array $context = [])
    {
        //$object = parent::denormalize($data, $class, $format, $context);

        throw new \Exception('Not yet implemented');
    }

    /**
     * @inheritdoc
     */
    public function supportsNormalization($data, $format = null)
    {
        return $data instanceof CustomerGroupInterface;
    }

    /**
     * @inheritdoc
     */
    public function supportsDenormalization($data, $type, $format = null)
    {
        return class_exists($type) && is_subclass_of($type, CustomerGroupInterface::class);
    }
}


<?php

namespace Ekyna\Component\Commerce\Bridge\Symfony\DependencyInjection;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;

/**
 * Class ConfigureValidatorPass
 * @package Ekyna\Component\Commerce\Bridge\Symfony\DependencyInjection
 * @author  Etienne Dauvergne <contact@ekyna.com>
 */
class ConfigureValidatorPass implements CompilerPassInterface
{
    /**
     * {@inheritdoc}
     */
    public function process(ContainerBuilder $container)
    {
        if (!$container->has('validator.builder')) {
            throw new ServiceNotFoundException('Validation is not enabled.');
        }

        $validatorBuilder = $container->getDefinition('validator.builder');

        $names = [
            'cart',
            'common',
            'invoice',
            'customer',
            'order',
            'payment',
            'pricing',
            'quote',
            'shipment',
            'stock',
            'subject',
            'supplier',
            'support',
        ];

        $paths = [];
        foreach ($names as $name) {
            $paths[] = realpath(__DIR__ . sprintf('/../Resources/validation/%s.xml', $name));
        }

        $validatorBuilder->addMethodCall('addXmlMappings', [$paths]);
    }
}


<?php

namespace PeskyCMF\Db\Traits;

use PeskyCMF\Db\CmfDbRecord;
use PeskyCMF\Db\CmfDbTable;

trait CacheHelpersTrait {

    /**
     * Generate cache key from $baseKey for current class and DB Object id (if current class instance of \App\Db\BaseDbObject)
     * @param string|array $baseKey - use method name; array - will be jsoned and hashed
     * @param string|array|null $suffix
     * @return string
     * @throws \UnexpectedValueException
     * @throws \PeskyORM\Exception\OrmException
     * @throws \InvalidArgumentException
     * @throws \BadMethodCallException
     * @throws \RuntimeException
     */
    public function generateCacheKey($baseKey, $suffix = null) {
        if (is_array($baseKey)) {
            asort($baseKey);
            $baseKey = \Hash::make(json_encode($baseKey, JSON_UNESCAPED_UNICODE));
        }
        $key = get_class($this) . '->' . $baseKey;
        if ($this instanceof CmfDbRecord) {
            $key .= '-' . $this::getTableStructure()->getSchema();
            $key .= '-id-' . $this->getPrimaryKeyValue();
        } else if ($this instanceof CmfDbTable) {
            $key .= '-' . $this::getTableStructure()->getSchema();
        }
        if (!empty($suffix)) {
            if (is_array($suffix)) {
                asort($suffix);
                $suffix = \Hash::make(json_encode($suffix, JSON_UNESCAPED_UNICODE));
            }
            $key .= '-' . $suffix;
        }
        return $key;
    }

    /**
     * Return data cached via $callback call
     * @param string|array $baseKey - use method name; array - will be jsoned and hashed
     * @param int|\DateTime $minutes
     * @param \Closure $dataCallback
     * @param string|array|null $cacheKeySuffix
     * @param bool $recache - true: update cache forcefully
     * @return mixed
     * @throws \UnexpectedValueException
     * @throws \RuntimeException
     * @throws \PeskyORM\Exception\OrmException
     * @throws \InvalidArgumentException
     * @throws \BadMethodCallException
     */
    public function cachedData($baseKey, $minutes, \Closure $dataCallback, $cacheKeySuffix = '', $recache = false) {
        $cacheKay = $this->generateCacheKey($baseKey, $cacheKeySuffix);
        if ($recache) {
            \Cache::forget($cacheKay);
        }
        return \Cache::remember($cacheKay, $minutes, $dataCallback);
    }

    /**
     * @param string|array $baseKey
     * @param string|array|null $cacheKeySuffix
     * @return bool
     * @throws \UnexpectedValueException
     * @throws \RuntimeException
     * @throws \PeskyORM\Exception\OrmException
     * @throws \InvalidArgumentException
     * @throws \BadMethodCallException
     */
    public function removeCachedData($baseKey, $cacheKeySuffix = '') {
        return \Cache::forget($this->generateCacheKey($baseKey, $cacheKeySuffix));
    }

    /**
     * @param string|array $baseKey - use method name; array - will be jsoned and hashed
     * @param string|array|null $cacheKeySuffix
     * @param mixed $default
     * @return mixed
     * @throws \UnexpectedValueException
     * @throws \RuntimeException
     * @throws \PeskyORM\Exception\OrmException
     * @throws \InvalidArgumentException
     * @throws \BadMethodCallException
     */
    public function getDataFromCache($baseKey, $cacheKeySuffix = '', $default = null) {
        $cacheKay = $this->generateCacheKey($baseKey, $cacheKeySuffix);
        return \Cache::get($cacheKay, $default);
    }

}