<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Sensio\Bundle\GeneratorBundle\Manipulator;

use Symfony\Component\HttpKernel\KernelInterface;
use Sensio\Bundle\GeneratorBundle\Generator\Generator;

/**
 * Changes the PHP code of a Kernel.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class KernelManipulator extends Manipulator
{
    protected $kernel;
    protected $reflected;

    public function __construct(KernelInterface $kernel)
    {
        $this->kernel = $kernel;
        $this->reflected = new \ReflectionObject($kernel);
    }

    /**
     * Adds a bundle at the end of the existing ones.
     *
     * @param string $bundle The bundle class name
     *
     * @return bool Whether the operation succeeded
     *
     * @throws \RuntimeException If bundle is already defined
     */
    public function addBundle($bundle)
    {
        if (!$this->getFilename()) {
            return false;
        }

        $src = file($this->getFilename());
        $method = $this->reflected->getMethod('registerBundles');
        $lines = array_slice($src, $method->getStartLine() - 1, $method->getEndLine() - $method->getStartLine() + 1);

        // Don't add same bundle twice
        if (false !== strpos(implode('', $lines), $bundle)) {
            throw new \RuntimeException(sprintf('Bundle "%s" is already defined in "AppKernel::registerBundles()".', $bundle));
        }

        $this->setCode(token_get_all('<?php '.implode('', $lines)), $method->getStartLine());

        while ($token = $this->next()) {
            // $bundles
            if (T_VARIABLE !== $token[0] || '$bundles' !== $token[1]) {
                continue;
            }

            // =
            $this->next();

            // array start with traditional or short syntax
            $token = $this->next();
            if (T_ARRAY !== $token[0] && '[' !== $this->value($token)) {
                return false;
            }

            // add the bundle at the end of the array
            while ($token = $this->next()) {
                // look for ); or ];
                if (')' !== $this->value($token) && ']' !== $this->value($token)) {
                    continue;
                }

                if (';' !== $this->value($this->peek())) {
                    continue;
                }

                $this->next();

                $leadingContent = implode('', array_slice($src, 0, $this->line));

                // trim semicolon
                $leadingContent = rtrim(rtrim($leadingContent), ';');

                // We want to match ) & ]
                $closingSymbolRegex = '#(\)|])$#';

                // get closing symbol used
                preg_match($closingSymbolRegex, $leadingContent, $matches);
                $closingSymbol = $matches[0];

                // remove last close parentheses
                $leadingContent = rtrim(preg_replace($closingSymbolRegex, '', rtrim($leadingContent)));

                if ('(' !== substr($leadingContent, -1) && '[' !== substr($leadingContent, -1)) {
                    // end of leading content is not open parentheses or bracket, then assume that array contains at least one element
                    $leadingContent = rtrim($leadingContent, ',').',';
                }

                $lines = array_merge(
                    array($leadingContent, "\n"),
                    array(str_repeat(' ', 12), sprintf('new %s(),', $bundle), "\n"),
                    array(str_repeat(' ', 8), $closingSymbol.';', "\n"),
                    array_slice($src, $this->line)
                );

                Generator::dump($this->getFilename(), implode('', $lines));

                return true;
            }
        }
    }

    public function getFilename()
    {
        return $this->reflected->getFileName();
    }
}


<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Sensio\Bundle\GeneratorBundle\Command;

use Symfony\Component\HttpKernel\Bundle\BundleInterface;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Sensio\Bundle\GeneratorBundle\Generator\Generator;
use Sensio\Bundle\GeneratorBundle\Command\Helper\QuestionHelper;

/**
 * Base class for generator commands.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
abstract class GeneratorCommand extends ContainerAwareCommand
{
    /**
     * @var Generator
     */
    private $generator;

    // only useful for unit tests
    public function setGenerator(Generator $generator)
    {
        $this->generator = $generator;
    }

    abstract protected function createGenerator();

    protected function getGenerator(BundleInterface $bundle = null)
    {
        if (null === $this->generator) {
            $this->generator = $this->createGenerator();
            $this->generator->setSkeletonDirs($this->getSkeletonDirs($bundle));
        }

        return $this->generator;
    }

    protected function getSkeletonDirs(BundleInterface $bundle = null)
    {
        $skeletonDirs = array();

        if (isset($bundle) && is_dir($dir = $bundle->getPath().'/Resources/SensioGeneratorBundle/skeleton')) {
            $skeletonDirs[] = $dir;
        }

        if (is_dir($dir = $this->getContainer()->get('kernel')->getRootdir().'/Resources/SensioGeneratorBundle/skeleton')) {
            $skeletonDirs[] = $dir;
        }

        $skeletonDirs[] = __DIR__.'/../Resources/skeleton';
        $skeletonDirs[] = __DIR__.'/../Resources';

        return $skeletonDirs;
    }

    protected function getQuestionHelper()
    {
        $question = $this->getHelperSet()->get('question');
        if (!$question || get_class($question) !== 'Sensio\Bundle\GeneratorBundle\Command\Helper\QuestionHelper') {
            $this->getHelperSet()->set($question = new QuestionHelper());
        }

        return $question;
    }

    /**
     * Tries to make a path relative to the project, which prints nicer.
     *
     * @param string $absolutePath
     *
     * @return string
     */
    protected function makePathRelative($absolutePath)
    {
        $projectRootDir = dirname($this->getContainer()->getParameter('kernel.root_dir'));

        return str_replace($projectRootDir.'/', '', realpath($absolutePath) ?: $absolutePath);
    }
}


<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Sensio\Bundle\GeneratorBundle\Manipulator;

/**
 * Changes the PHP code of a Kernel.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Manipulator
{
    protected $tokens;
    protected $line;

    /**
     * Sets the code to manipulate.
     *
     * @param array $tokens An array of PHP tokens
     * @param int   $line   The start line of the code
     */
    protected function setCode(array $tokens, $line = 0)
    {
        $this->tokens = $tokens;
        $this->line = $line;
    }

    /**
     * Gets the next token.
     *
     * @return string|null
     */
    protected function next()
    {
        while ($token = array_shift($this->tokens)) {
            $this->line += substr_count($this->value($token), "\n");

            if (is_array($token) && in_array($token[0], array(T_WHITESPACE, T_COMMENT, T_DOC_COMMENT))) {
                continue;
            }

            return $token;
        }
    }

    /**
     * Peeks the next token.
     *
     * @param int $nb
     *
     * @return string|null
     */
    protected function peek($nb = 1)
    {
        $i = 0;
        $tokens = $this->tokens;
        while ($token = array_shift($tokens)) {
            if (is_array($token) && in_array($token[0], array(T_WHITESPACE, T_COMMENT, T_DOC_COMMENT))) {
                continue;
            }

            ++$i;
            if ($i == $nb) {
                return $token;
            }
        }
    }

    /**
     * Gets the value of a token.
     *
     * @param string|string[] $token The token value
     *
     * @return string
     */
    protected function value($token)
    {
        return is_array($token) ? $token[1] : $token;
    }
}


<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Sensio\Bundle\GeneratorBundle\Generator;

use Sensio\Bundle\GeneratorBundle\Model\EntityGeneratorResult;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
use Symfony\Bridge\Doctrine\RegistryInterface;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\ORM\Tools\EntityGenerator;
use Doctrine\ORM\Tools\EntityRepositoryGenerator;
use Doctrine\ORM\Tools\Export\ClassMetadataExporter;
use Doctrine\Common\Util\Inflector;

/**
 * Generates a Doctrine entity class based on its name, fields and format.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Jonathan H. Wage <jonwage@gmail.com>
 */
class DoctrineEntityGenerator extends Generator
{
    private $filesystem;
    private $registry;

    public function __construct(Filesystem $filesystem, RegistryInterface $registry)
    {
        $this->filesystem = $filesystem;
        $this->registry = $registry;
    }

    /**
     * @param BundleInterface $bundle
     * @param string          $entity
     * @param string          $format
     * @param array           $fields
     *
     * @return EntityGeneratorResult
     *
     * @throws \Doctrine\ORM\Tools\Export\ExportException
     */
    public function generate(BundleInterface $bundle, $entity, $format, array $fields)
    {
        // configure the bundle (needed if the bundle does not contain any Entities yet)
        $config = $this->registry->getManager(null)->getConfiguration();
        $config->setEntityNamespaces(array_merge(
            array($bundle->getName() => $bundle->getNamespace().'\\Entity'),
            $config->getEntityNamespaces()
        ));

        $entityClass = $this->registry->getAliasNamespace($bundle->getName()).'\\'.$entity;
        $entityPath = $bundle->getPath().'/Entity/'.str_replace('\\', '/', $entity).'.php';
        if (file_exists($entityPath)) {
            throw new \RuntimeException(sprintf('Entity "%s" already exists.', $entityClass));
        }

        $class = new ClassMetadataInfo($entityClass, $config->getNamingStrategy());
        $class->customRepositoryClassName = str_replace('\\Entity\\', '\\Repository\\', $entityClass).'Repository';
        $class->mapField(array('fieldName' => 'id', 'type' => 'integer', 'id' => true));
        $class->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_AUTO);
        foreach ($fields as $field) {
            $class->mapField($field);
        }

        $entityGenerator = $this->getEntityGenerator();
        if ('annotation' === $format) {
            $entityGenerator->setGenerateAnnotations(true);
            $class->setPrimaryTable(array('name' => Inflector::tableize(str_replace('\\', '', $entity))));
            $entityCode = $entityGenerator->generateEntityClass($class);
            $mappingPath = $mappingCode = false;
        } else {
            $cme = new ClassMetadataExporter();
            $exporter = $cme->getExporter('yml' == $format ? 'yaml' : $format);
            $mappingPath = $bundle->getPath().'/Resources/config/doctrine/'.str_replace('\\', '.', $entity).'.orm.'.$format;

            if (file_exists($mappingPath)) {
                throw new \RuntimeException(sprintf('Cannot generate entity when mapping "%s" already exists.', $mappingPath));
            }

            $mappingCode = $exporter->exportClassMetadata($class);
            $entityGenerator->setGenerateAnnotations(false);
            $entityCode = $entityGenerator->generateEntityClass($class);
        }
        $entityCode = str_replace(
            array("@var integer\n", "@var boolean\n", "@param integer\n", "@param boolean\n", "@return integer\n", "@return boolean\n"),
            array("@var int\n", "@var bool\n", "@param int\n", "@param bool\n", "@return int\n", "@return bool\n"),
            $entityCode
        );

        self::mkdir(dirname($entityPath));
        self::dump($entityPath, $entityCode);

        if ($mappingPath) {
            self::mkdir(dirname($mappingPath));
            self::dump($mappingPath, $mappingCode);
        }

        $path = $bundle->getPath().str_repeat('/..', substr_count(get_class($bundle), '\\'));
        $this->getRepositoryGenerator()->writeEntityRepositoryClass($class->customRepositoryClassName, $path);
        $repositoryPath = $path.DIRECTORY_SEPARATOR.str_replace('\\', DIRECTORY_SEPARATOR, $class->customRepositoryClassName).'.php';

        return new EntityGeneratorResult($entityPath, $repositoryPath, $mappingPath);
    }

    public function isReservedKeyword($keyword)
    {
        return $this->registry->getConnection()->getDatabasePlatform()->getReservedKeywordsList()->isKeyword($keyword);
    }

    protected function getEntityGenerator()
    {
        $entityGenerator = new EntityGenerator();
        $entityGenerator->setGenerateAnnotations(false);
        $entityGenerator->setGenerateStubMethods(true);
        $entityGenerator->setRegenerateEntityIfExists(false);
        $entityGenerator->setUpdateEntityIfExists(true);
        $entityGenerator->setNumSpaces(4);
        $entityGenerator->setAnnotationPrefix('ORM\\');

        return $entityGenerator;
    }

    protected function getRepositoryGenerator()
    {
        return new EntityRepositoryGenerator();
    }

    /**
     * Checks if the given name is a valid PHP variable name.
     *
     * @see http://php.net/manual/en/language.variables.basics.php
     *
     * @param $name string
     *
     * @return bool
     */
    public function isValidPhpVariableName($name)
    {
        return (bool) preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $name, $matches);
    }
}


<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Sensio\Bundle\GeneratorBundle\Generator;

use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\Common\Inflector\Inflector;

/**
 * Generates a CRUD controller.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class DoctrineCrudGenerator extends Generator
{
    protected $filesystem;
    protected $rootDir;
    protected $routePrefix;
    protected $routeNamePrefix;
    protected $bundle;
    protected $entity;
    protected $entitySingularized;
    protected $entityPluralized;
    protected $metadata;
    protected $format;
    protected $actions;

    /**
     * @param Filesystem $filesystem
     * @param string     $rootDir
     */
    public function __construct(Filesystem $filesystem, $rootDir)
    {
        $this->filesystem = $filesystem;
        $this->rootDir = $rootDir;
    }

    /**
     * Generate the CRUD controller.
     *
     * @param BundleInterface   $bundle           A bundle object
     * @param string            $entity           The entity relative class name
     * @param ClassMetadataInfo $metadata         The entity class metadata
     * @param string            $format           The configuration format (xml, yaml, annotation)
     * @param string            $routePrefix      The route name prefix
     * @param bool              $needWriteActions Whether or not to generate write actions
     * @param bool              $forceOverwrite   Whether or not to overwrite the controller
     *
     * @throws \RuntimeException
     */
    public function generate(BundleInterface $bundle, $entity, ClassMetadataInfo $metadata, $format, $routePrefix, $needWriteActions, $forceOverwrite)
    {
        $this->routePrefix = $routePrefix;
        $this->routeNamePrefix = self::getRouteNamePrefix($routePrefix);
        $this->actions = $needWriteActions ? array('index', 'show', 'new', 'edit', 'delete') : array('index', 'show');

        if (count($metadata->identifier) != 1) {
            throw new \RuntimeException('The CRUD generator does not support entity classes with multiple or no primary keys.');
        }

        $this->entity = $entity;
        $entity = str_replace('\\', '/', $entity);
        $entityParts = explode('/', $entity);
        $entityName = end($entityParts);
        $this->entitySingularized = lcfirst(Inflector::singularize($entityName));
        $this->entityPluralized = lcfirst(Inflector::pluralize($entityName));
        $this->bundle = $bundle;
        $this->metadata = $metadata;
        $this->setFormat($format);

        $this->generateControllerClass($forceOverwrite);

        $dir = sprintf('%s/Resources/views/%s', $this->rootDir, strtolower($entity));

        if (!file_exists($dir)) {
            self::mkdir($dir);
        }

        $this->generateIndexView($dir);

        if (in_array('show', $this->actions)) {
            $this->generateShowView($dir);
        }

        if (in_array('new', $this->actions)) {
            $this->generateNewView($dir);
        }

        if (in_array('edit', $this->actions)) {
            $this->generateEditView($dir);
        }

        $this->generateTestClass();
        $this->generateConfiguration();
    }

    /**
     * Sets the configuration format.
     *
     * @param string $format The configuration format
     */
    protected function setFormat($format)
    {
        switch ($format) {
            case 'yml':
            case 'xml':
            case 'php':
            case 'annotation':
                $this->format = $format;
                break;
            default:
                $this->format = 'yml';
                break;
        }
    }

    /**
     * Generates the routing configuration.
     */
    protected function generateConfiguration()
    {
        if (!in_array($this->format, array('yml', 'xml', 'php'))) {
            return;
        }

        $target = sprintf(
            '%s/Resources/config/routing/%s.%s',
            $this->bundle->getPath(),
            strtolower(str_replace('\\', '_', $this->entity)),
            $this->format
        );

        $this->renderFile('crud/config/routing.'.$this->format.'.twig', $target, array(
            'actions' => $this->actions,
            'route_prefix' => $this->routePrefix,
            'route_name_prefix' => $this->routeNamePrefix,
            'bundle' => $this->bundle->getName(),
            'entity' => $this->entity,
            'identifier' => $this->metadata->identifier[0],
        ));
    }

    /**
     * Generates the controller class only.
     */
    protected function generateControllerClass($forceOverwrite)
    {
        $dir = $this->bundle->getPath();

        $parts = explode('\\', $this->entity);
        $entityClass = array_pop($parts);
        $entityNamespace = implode('\\', $parts);

        $target = sprintf(
            '%s/Controller/%s/%sController.php',
            $dir,
            str_replace('\\', '/', $entityNamespace),
            $entityClass
        );

        if (!$forceOverwrite && file_exists($target)) {
            throw new \RuntimeException('Unable to generate the controller as it already exists.');
        }

        $this->renderFile('crud/controller.php.twig', $target, array(
            'actions' => $this->actions,
            'route_prefix' => $this->routePrefix,
            'route_name_prefix' => $this->routeNamePrefix,
            'bundle' => $this->bundle->getName(),
            'entity' => $this->entity,
            'entity_singularized' => $this->entitySingularized,
            'entity_pluralized' => $this->entityPluralized,
            'identifier' => $this->metadata->identifier[0],
            'entity_class' => $entityClass,
            'namespace' => $this->bundle->getNamespace(),
            'entity_namespace' => $entityNamespace,
            'format' => $this->format,
            // BC with Symfony 2.7
            'use_form_type_instance' => !method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix'),
        ));
    }

    /**
     * Generates the functional test class only.
     */
    protected function generateTestClass()
    {
        $parts = explode('\\', $this->entity);
        $entityClass = array_pop($parts);
        $entityNamespace = implode('\\', $parts);

        $dir = $this->bundle->getPath().'/Tests/Controller';
        $target = $dir.'/'.str_replace('\\', '/', $entityNamespace).'/'.$entityClass.'ControllerTest.php';

        $this->renderFile('crud/tests/test.php.twig', $target, array(
            'route_prefix' => $this->routePrefix,
            'route_name_prefix' => $this->routeNamePrefix,
            'entity' => $this->entity,
            'bundle' => $this->bundle->getName(),
            'entity_class' => $entityClass,
            'namespace' => $this->bundle->getNamespace(),
            'entity_namespace' => $entityNamespace,
            'actions' => $this->actions,
            'form_type_name' => strtolower(str_replace('\\', '_', $this->bundle->getNamespace()).($parts ? '_' : '').implode('_', $parts).'_'.$entityClass),
        ));
    }

    /**
     * Generates the index.html.twig template in the final bundle.
     *
     * @param string $dir The path to the folder that hosts templates in the bundle
     */
    protected function generateIndexView($dir)
    {
        $this->renderFile('crud/views/index.html.twig.twig', $dir.'/index.html.twig', array(
            'bundle' => $this->bundle->getName(),
            'entity' => $this->entity,
            'entity_pluralized' => $this->entityPluralized,
            'entity_singularized' => $this->entitySingularized,
            'identifier' => $this->metadata->identifier[0],
            'fields' => $this->metadata->fieldMappings,
            'actions' => $this->actions,
            'record_actions' => $this->getRecordActions(),
            'route_prefix' => $this->routePrefix,
            'route_name_prefix' => $this->routeNamePrefix,
        ));
    }

    /**
     * Generates the show.html.twig template in the final bundle.
     *
     * @param string $dir The path to the folder that hosts templates in the bundle
     */
    protected function generateShowView($dir)
    {
        $this->renderFile('crud/views/show.html.twig.twig', $dir.'/show.html.twig', array(
            'bundle' => $this->bundle->getName(),
            'entity' => $this->entity,
            'entity_singularized' => $this->entitySingularized,
            'identifier' => $this->metadata->identifier[0],
            'fields' => $this->metadata->fieldMappings,
            'actions' => $this->actions,
            'route_prefix' => $this->routePrefix,
            'route_name_prefix' => $this->routeNamePrefix,
        ));
    }

    /**
     * Generates the new.html.twig template in the final bundle.
     *
     * @param string $dir The path to the folder that hosts templates in the bundle
     */
    protected function generateNewView($dir)
    {
        $this->renderFile('crud/views/new.html.twig.twig', $dir.'/new.html.twig', array(
            'bundle' => $this->bundle->getName(),
            'entity' => $this->entity,
            'entity_singularized' => $this->entitySingularized,
            'route_prefix' => $this->routePrefix,
            'route_name_prefix' => $this->routeNamePrefix,
            'actions' => $this->actions,
            'fields' => $this->metadata->fieldMappings,
        ));
    }

    /**
     * Generates the edit.html.twig template in the final bundle.
     *
     * @param string $dir The path to the folder that hosts templates in the bundle
     */
    protected function generateEditView($dir)
    {
        $this->renderFile('crud/views/edit.html.twig.twig', $dir.'/edit.html.twig', array(
            'route_prefix' => $this->routePrefix,
            'route_name_prefix' => $this->routeNamePrefix,
            'identifier' => $this->metadata->identifier[0],
            'entity' => $this->entity,
            'entity_singularized' => $this->entitySingularized,
            'fields' => $this->metadata->fieldMappings,
            'bundle' => $this->bundle->getName(),
            'actions' => $this->actions,
        ));
    }

    /**
     * Returns an array of record actions to generate (edit, show).
     *
     * @return array
     */
    protected function getRecordActions()
    {
        return array_filter($this->actions, function ($item) {
            return in_array($item, array('show', 'edit'));
        });
    }

    public static function getRouteNamePrefix($prefix)
    {
        $prefix = preg_replace('/{(.*?)}/', '', $prefix); // {foo}_bar -> _bar
        $prefix = str_replace('/', '_', $prefix);
        $prefix = preg_replace('/_+/', '_', $prefix);     // foo__bar -> foo_bar
        $prefix = trim($prefix, '_');

        return $prefix;
    }
}


<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Sensio\Bundle\GeneratorBundle\Command;

/**
 * Validator functions.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Validators
{
    /**
     * Validates that the given namespace (e.g. Acme\FooBundle) is a valid format.
     *
     * If $requireVendorNamespace is true, then we require you to have a vendor
     * namespace (e.g. Acme).
     *
     * @param $namespace
     * @param bool $requireVendorNamespace
     *
     * @return string
     */
    public static function validateBundleNamespace($namespace, $requireVendorNamespace = true)
    {
        if (!preg_match('/Bundle$/', $namespace)) {
            throw new \InvalidArgumentException('The namespace must end with Bundle.');
        }

        $namespace = strtr($namespace, '/', '\\');
        if (!preg_match('/^(?:[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*\\\?)+$/', $namespace)) {
            throw new \InvalidArgumentException('The namespace contains invalid characters.');
        }

        // validate reserved keywords
        $reserved = self::getReservedWords();
        foreach (explode('\\', $namespace) as $word) {
            if (in_array(strtolower($word), $reserved)) {
                throw new \InvalidArgumentException(sprintf('The namespace cannot contain PHP reserved words ("%s").', $word));
            }
        }

        // validate that the namespace is at least one level deep
        if ($requireVendorNamespace && false === strpos($namespace, '\\')) {
            $msg = array();
            $msg[] = sprintf('The namespace must contain a vendor namespace (e.g. "VendorName\%s" instead of simply "%s").', $namespace, $namespace);
            $msg[] = 'If you\'ve specified a vendor namespace, did you forget to surround it with quotes (init:bundle "Acme\BlogBundle")?';

            throw new \InvalidArgumentException(implode("\n\n", $msg));
        }

        return $namespace;
    }

    public static function validateBundleName($bundle)
    {
        if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $bundle)) {
            throw new \InvalidArgumentException(sprintf('The bundle name %s contains invalid characters.', $bundle));
        }

        if (!preg_match('/Bundle$/', $bundle)) {
            throw new \InvalidArgumentException('The bundle name must end with Bundle.');
        }

        return $bundle;
    }

    public static function validateControllerName($controller)
    {
        try {
            self::validateEntityName($controller);
        } catch (\InvalidArgumentException $e) {
            throw new \InvalidArgumentException(
                sprintf(
                    'The controller name must contain a : ("%s" given, expecting something like AcmeBlogBundle:Post)',
                    $controller
                )
            );
        }

        return $controller;
    }

    public static function validateFormat($format)
    {
        if (!$format) {
            throw new \RuntimeException('Please enter a configuration format.');
        }

        $format = strtolower($format);

        // in case they typed "yaml", but ok with that
        if ($format == 'yaml') {
            $format = 'yml';
        }

        if (!in_array($format, array('php', 'xml', 'yml', 'annotation'))) {
            throw new \RuntimeException(sprintf('Format "%s" is not supported.', $format));
        }

        return $format;
    }

    /**
     * Performs basic checks in entity name.
     *
     * @param string $entity
     *
     * @return string
     *
     * @throws \InvalidArgumentException
     */
    public static function validateEntityName($entity)
    {
        if (!preg_match('{^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*:[a-zA-Z0-9_\x7f-\xff\\\/]+$}', $entity)) {
            throw new \InvalidArgumentException(sprintf('The entity name isn\'t valid ("%s" given, expecting something like AcmeBlogBundle:Blog/Post)', $entity));
        }

        return $entity;
    }

    public static function getReservedWords()
    {
        return array(
            'abstract',
            'and',
            'array',
            'as',
            'break',
            'callable',
            'case',
            'catch',
            'class',
            'clone',
            'const',
            'continue',
            'declare',
            'default',
            'do',
            'else',
            'elseif',
            'enddeclare',
            'endfor',
            'endforeach',
            'endif',
            'endswitch',
            'endwhile',
            'extends',
            'final',
            'finally',
            'for',
            'foreach',
            'function',
            'global',
            'goto',
            'if',
            'implements',
            'interface',
            'instanceof',
            'insteadof',
            'namespace',
            'new',
            'or',
            'private',
            'protected',
            'public',
            'static',
            'switch',
            'throw',
            'trait',
            'try',
            'use',
            'var',
            'while',
            'xor',
            'yield',
            '__CLASS__',
            '__DIR__',
            '__FILE__',
            '__LINE__',
            '__FUNCTION__',
            '__METHOD__',
            '__NAMESPACE__',
            '__TRAIT__',
            '__halt_compiler',
            'die',
            'echo',
            'empty',
            'exit',
            'eval',
            'include',
            'include_once',
            'isset',
            'list',
            'require',
            'require_once',
            'return',
            'print',
            'unset',
        );
    }
}


<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Sensio\Bundle\GeneratorBundle\Command;

use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Sensio\Bundle\GeneratorBundle\Command\AutoComplete\EntitiesAutoCompleter;
use Sensio\Bundle\GeneratorBundle\Command\Helper\QuestionHelper;
use Sensio\Bundle\GeneratorBundle\Generator\DoctrineCrudGenerator;
use Sensio\Bundle\GeneratorBundle\Generator\DoctrineFormGenerator;
use Sensio\Bundle\GeneratorBundle\Manipulator\RoutingManipulator;

/**
 * Generates a CRUD for a Doctrine entity.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class GenerateDoctrineCrudCommand extends GenerateDoctrineCommand
{
    private $formGenerator;

    /**
     * @see Command
     */
    protected function configure()
    {
        $this
            ->setName('doctrine:generate:crud')
            ->setAliases(array('generate:doctrine:crud'))
            ->setDescription('Generates a CRUD based on a Doctrine entity')
            ->addArgument('entity', InputArgument::OPTIONAL, 'The entity class name to initialize (shortcut notation)')
            ->addOption('entity', null, InputOption::VALUE_OPTIONAL, 'The entity class name to initialize (shortcut notation)')
            ->addOption('route-prefix', null, InputOption::VALUE_REQUIRED, 'The route prefix')
            ->addOption('with-write', null, InputOption::VALUE_NONE, 'Whether or not to generate create, new and delete actions')
            ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The format used for configuration files (php, xml, yml, or annotation)', 'annotation')
            ->addOption('overwrite', null, InputOption::VALUE_NONE, 'Overwrite any existing controller or form class when generating the CRUD contents')
            ->setHelp(<<<EOT
The <info>%command.name%</info> command generates a CRUD based on a Doctrine entity.

The default command only generates the list and show actions.

<info>php %command.full_name% AcmeBlogBundle:Post --route-prefix=post_admin</info>

Using the --with-write option allows to generate the new, edit and delete actions.

<info>php %command.full_name% AcmeBlogBundle:Post --route-prefix=post_admin --with-write</info>

Every generated file is based on a template. There are default templates but they can be overridden by placing custom templates in one of the following locations, by order of priority:

<info>BUNDLE_PATH/Resources/SensioGeneratorBundle/skeleton/crud
APP_PATH/Resources/SensioGeneratorBundle/skeleton/crud</info>

And

<info>__bundle_path__/Resources/SensioGeneratorBundle/skeleton/form
__project_root__/app/Resources/SensioGeneratorBundle/skeleton/form</info>

You can check https://github.com/sensio/SensioGeneratorBundle/tree/master/Resources/skeleton
in order to know the file structure of the skeleton
EOT
            )
        ;
    }

    /**
     * @see Command
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $questionHelper = $this->getQuestionHelper();

        if ($input->isInteractive()) {
            $question = new ConfirmationQuestion($questionHelper->getQuestion('Do you confirm generation', 'yes', '?'), true);
            if (!$questionHelper->ask($input, $output, $question)) {
                $output->writeln('<error>Command aborted</error>');

                return 1;
            }
        } else {
            // BC to be removed in 4.0
            if ($input->hasOption('entity') && $entityOption = $input->getOption('entity')) {
                @trigger_error('Using the "--entity" option has been deprecated since version 3.0 and will be removed in 4.0. Pass it as argument instead.', E_USER_DEPRECATED);

                $input->setArgument('entity', $entityOption);
            }
        }

        $entity = Validators::validateEntityName($input->getArgument('entity'));
        list($bundle, $entity) = $this->parseShortcutNotation($entity);

        $format = Validators::validateFormat($input->getOption('format'));
        $prefix = $this->getRoutePrefix($input, $entity);
        $withWrite = $input->getOption('with-write');
        $forceOverwrite = $input->getOption('overwrite');

        $questionHelper->writeSection($output, 'CRUD generation');

        try {
            $entityClass = $this->getContainer()->get('doctrine')->getAliasNamespace($bundle).'\\'.$entity;
            $metadata = $this->getEntityMetadata($entityClass);
        } catch (\Exception $e) {
            throw new \RuntimeException(sprintf('Entity "%s" does not exist in the "%s" bundle. Create it with the "doctrine:generate:entity" command and then execute this command again.', $entity, $bundle));
        }

        $bundle = $this->getContainer()->get('kernel')->getBundle($bundle);

        $generator = $this->getGenerator($bundle);
        $generator->generate($bundle, $entity, $metadata[0], $format, $prefix, $withWrite, $forceOverwrite);

        $output->writeln('Generating the CRUD code: <info>OK</info>');

        $errors = array();
        $runner = $questionHelper->getRunner($output, $errors);

        // form
        if ($withWrite) {
            $this->generateForm($bundle, $entity, $metadata, $forceOverwrite);
            $output->writeln('Generating the Form code: <info>OK</info>');
        }

        // routing
        $output->write('Updating the routing: ');
        if ('annotation' != $format) {
            $runner($this->updateRouting($questionHelper, $input, $output, $bundle, $format, $entity, $prefix));
        } else {
            $runner($this->updateAnnotationRouting($bundle, $entity, $prefix));
        }

        $questionHelper->writeGeneratorSummary($output, $errors);
    }

    protected function interact(InputInterface $input, OutputInterface $output)
    {
        $questionHelper = $this->getQuestionHelper();
        $questionHelper->writeSection($output, 'Welcome to the Doctrine2 CRUD generator');

        // namespace
        $output->writeln(array(
            '',
            'This command helps you generate CRUD controllers and templates.',
            '',
            'First, give the name of the existing entity for which you want to generate a CRUD',
            '(use the shortcut notation like <comment>AcmeBlogBundle:Post</comment>)',
            '',
        ));

        if ($input->hasOption('entity') && $entityOption = $input->getOption('entity')) {
            @trigger_error('Using the "--entity" option has been deprecated since version 3.0 and will be removed in 4.0. Pass it as argument instead.', E_USER_DEPRECATED);

            $input->setArgument('entity', $entityOption);
        }

        $question = new Question($questionHelper->getQuestion('The Entity shortcut name', $input->getArgument('entity')), $input->getArgument('entity'));
        $question->setValidator(array('Sensio\Bundle\GeneratorBundle\Command\Validators', 'validateEntityName'));

        $autocompleter = new EntitiesAutoCompleter($this->getContainer()->get('doctrine')->getManager());
        $autocompleteEntities = $autocompleter->getSuggestions();
        $question->setAutocompleterValues($autocompleteEntities);
        $entity = $questionHelper->ask($input, $output, $question);

        $input->setArgument('entity', $entity);
        list($bundle, $entity) = $this->parseShortcutNotation($entity);

        try {
            $entityClass = $this->getContainer()->get('doctrine')->getAliasNamespace($bundle).'\\'.$entity;
            $metadata = $this->getEntityMetadata($entityClass);
        } catch (\Exception $e) {
            throw new \RuntimeException(sprintf('Entity "%s" does not exist in the "%s" bundle. You may have mistyped the bundle name or maybe the entity doesn\'t exist yet (create it first with the "doctrine:generate:entity" command).', $entity, $bundle));
        }

        // write?
        $withWrite = $input->getOption('with-write') ?: false;
        $output->writeln(array(
            '',
            'By default, the generator creates two actions: list and show.',
            'You can also ask it to generate "write" actions: new, update, and delete.',
            '',
        ));
        $question = new ConfirmationQuestion($questionHelper->getQuestion('Do you want to generate the "write" actions', $withWrite ? 'yes' : 'no', '?', $withWrite), $withWrite);

        $withWrite = $questionHelper->ask($input, $output, $question);
        $input->setOption('with-write', $withWrite);

        // format
        $format = $input->getOption('format');
        $output->writeln(array(
            '',
            'Determine the format to use for the generated CRUD.',
            '',
        ));
        $question = new Question($questionHelper->getQuestion('Configuration format (yml, xml, php, or annotation)', $format), $format);
        $question->setValidator(array('Sensio\Bundle\GeneratorBundle\Command\Validators', 'validateFormat'));
        $format = $questionHelper->ask($input, $output, $question);
        $input->setOption('format', $format);

        // route prefix
        $prefix = $this->getRoutePrefix($input, $entity);
        $output->writeln(array(
            '',
            'Determine the routes prefix (all the routes will be "mounted" under this',
            'prefix: /prefix/, /prefix/new, ...).',
            '',
        ));
        $prefix = $questionHelper->ask($input, $output, new Question($questionHelper->getQuestion('Routes prefix', '/'.$prefix), '/'.$prefix));
        $input->setOption('route-prefix', $prefix);

        // summary
        $questionHelper->writeSection($output, 'Summary before generation');
        $output->writeln(array(
            sprintf('You are going to generate a CRUD controller for "<info>%s:%s</info>"', $bundle, $entity),
            sprintf('using the "<info>%s</info>" format.', $format),
            '',
        ));
    }

    /**
     * Tries to generate forms if they don't exist yet and if we need write operations on entities.
     */
    protected function generateForm($bundle, $entity, $metadata, $forceOverwrite = false)
    {
        $this->getFormGenerator($bundle)->generate($bundle, $entity, $metadata[0], $forceOverwrite);
    }

    protected function updateRouting(QuestionHelper $questionHelper, InputInterface $input, OutputInterface $output, BundleInterface $bundle, $format, $entity, $prefix)
    {
        $auto = true;
        if ($input->isInteractive()) {
            $question = new ConfirmationQuestion($questionHelper->getQuestion('Confirm automatic update of the Routing', 'yes', '?'), true);
            $auto = $questionHelper->ask($input, $output, $question);
        }

        $output->write('Importing the CRUD routes: ');
        $this->getContainer()->get('filesystem')->mkdir($bundle->getPath().'/Resources/config/');

        // first, import the routing file from the bundle's main routing.yml file
        $routing = new RoutingManipulator($bundle->getPath().'/Resources/config/routing.yml');
        try {
            $ret = $auto ? $routing->addResource($bundle->getName(), $format, '/'.$prefix, 'routing/'.strtolower(str_replace('\\', '_', $entity))) : false;
        } catch (\RuntimeException $exc) {
            $ret = false;
        }

        if (!$ret) {
            $help = sprintf("        <comment>resource: \"@%s/Resources/config/routing/%s.%s\"</comment>\n", $bundle->getName(), strtolower(str_replace('\\', '_', $entity)), $format);
            $help .= sprintf("        <comment>prefix:   /%s</comment>\n", $prefix);

            return array(
                '- Import the bundle\'s routing resource in the bundle routing file',
                sprintf('  (%s).', $bundle->getPath().'/Resources/config/routing.yml'),
                '',
                sprintf('    <comment>%s:</comment>', $routing->getImportedResourceYamlKey($bundle->getName(), $prefix)),
                $help,
                '',
            );
        }

        // second, import the bundle's routing.yml file from the application's routing.yml file
        $routing = new RoutingManipulator($this->getContainer()->getParameter('kernel.root_dir').'/config/routing.yml');
        try {
            $ret = $auto ? $routing->addResource($bundle->getName(), 'yml') : false;
        } catch (\RuntimeException $e) {
            // the bundle is already imported form app's routing.yml file
            $errorMessage = sprintf(
                "\n\n[ERROR] The bundle's \"Resources/config/routing.yml\" file cannot be imported\n".
                "from \"app/config/routing.yml\" because the \"%s\" bundle is\n".
                "already imported. Make sure you are not using two different\n".
                "configuration/routing formats in the same bundle because it won't work.\n",
                $bundle->getName()
            );
            $output->write($errorMessage);
            $ret = true;
        } catch (\Exception $e) {
            $ret = false;
        }

        if (!$ret) {
            return array(
                '- Import the bundle\'s routing.yml file in the application routing.yml file',
                sprintf('# app/config/routing.yml'),
                sprintf('%s:', $bundle->getName()),
                sprintf('    <comment>resource: "@%s/Resources/config/routing.yml"</comment>', $bundle->getName()),
                '',
                '# ...',
                '',
            );
        }
    }

    protected function updateAnnotationRouting(BundleInterface $bundle, $entity, $prefix)
    {
        $rootDir = $this->getContainer()->getParameter('kernel.root_dir');

        $routing = new RoutingManipulator($rootDir.'/config/routing.yml');

        if (!$routing->hasResourceInAnnotation($bundle->getName())) {
            $parts = explode('\\', $entity);
            $controller = array_pop($parts);

            $ret = $routing->addAnnotationController($bundle->getName(), $controller);
        }
    }

    protected function getRoutePrefix(InputInterface $input, $entity)
    {
        $prefix = $input->getOption('route-prefix') ?: strtolower(str_replace(array('\\', '/'), '_', $entity));

        if ($prefix && '/' === $prefix[0]) {
            $prefix = substr($prefix, 1);
        }

        return $prefix;
    }

    protected function createGenerator($bundle = null)
    {
        return new DoctrineCrudGenerator(
            $this->getContainer()->get('filesystem'),
            $this->getContainer()->getParameter('kernel.root_dir')
        );
    }

    protected function getFormGenerator($bundle = null)
    {
        if (null === $this->formGenerator) {
            $this->formGenerator = new DoctrineFormGenerator($this->getContainer()->get('filesystem'));
            $this->formGenerator->setSkeletonDirs($this->getSkeletonDirs($bundle));
        }

        return $this->formGenerator;
    }

    public function setFormGenerator(DoctrineFormGenerator $formGenerator)
    {
        $this->formGenerator = $formGenerator;
    }
}


<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Sensio\Bundle\GeneratorBundle\Manipulator;

use Symfony\Component\DependencyInjection\Container;
use Sensio\Bundle\GeneratorBundle\Generator\DoctrineCrudGenerator;
use Sensio\Bundle\GeneratorBundle\Generator\Generator;
use Symfony\Component\Yaml\Yaml;

/**
 * Changes the PHP code of a YAML routing file.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class RoutingManipulator extends Manipulator
{
    private $file;

    /**
     * @param string $file The YAML routing file path
     */
    public function __construct($file)
    {
        $this->file = $file;
    }

    /**
     * Adds a routing resource at the top of the existing ones.
     *
     * @param string $bundle
     * @param string $format
     * @param string $prefix
     * @param string $path
     *
     * @return bool Whether the operation succeeded
     *
     * @throws \RuntimeException If bundle is already imported
     */
    public function addResource($bundle, $format, $prefix = '/', $path = 'routing')
    {
        $current = '';
        $code = sprintf("%s:\n", $this->getImportedResourceYamlKey($bundle, $prefix));

        if (file_exists($this->file)) {
            $current = file_get_contents($this->file);

            // Don't add same bundle twice
            if (false !== strpos($current, '@'.$bundle)) {
                throw new \RuntimeException(sprintf('Bundle "%s" is already imported.', $bundle));
            }
        } elseif (!is_dir($dir = dirname($this->file))) {
            Generator::mkdir($dir);
        }

        if ('annotation' == $format) {
            $code .= sprintf("    resource: \"@%s/Controller/\"\n    type:     annotation\n", $bundle);
        } else {
            $code .= sprintf("    resource: \"@%s/Resources/config/%s.%s\"\n", $bundle, $path, $format);
        }
        $code .= sprintf("    prefix:   %s\n", $prefix);
        $code .= "\n";
        $code .= $current;

        if (false === Generator::dump($this->file, $code)) {
            return false;
        }

        return true;
    }

    /**
     * Checks if the routing file contains a line for the bundle.
     *
     * @param string $bundle
     *
     * @return bool
     */
    public function hasResourceInAnnotation($bundle)
    {
        if (!file_exists($this->file)) {
            return false;
        }

        $config = Yaml::parse(file_get_contents($this->file));

        $search = sprintf('@%s/Controller/', $bundle);

        foreach ($config as $resource) {
            if (array_key_exists('resource', $resource)) {
                return $resource['resource'] === $search;
            }
        }

        return false;
    }

    /**
     * Adds an annotation controller resource.
     *
     * @param string $bundle
     * @param string $controller
     *
     * @return bool
     */
    public function addAnnotationController($bundle, $controller)
    {
        $current = '';

        if (file_exists($this->file)) {
            $current = file_get_contents($this->file);
        } elseif (!is_dir($dir = dirname($this->file))) {
            mkdir($dir, 0777, true);
        }

        $code = sprintf("%s:\n", Container::underscore(substr($bundle, 0, -6)).'_'.Container::underscore($controller));

        $code .= sprintf("    resource: \"@%s/Controller/%sController.php\"\n    type:     annotation\n", $bundle, $controller);

        $code .= "\n";
        $code .= $current;

        return false !== file_put_contents($this->file, $code);
    }

    public function getImportedResourceYamlKey($bundle, $prefix)
    {
        $snakeCasedBundleName = Container::underscore(substr($bundle, 0, -6));
        $routePrefix = DoctrineCrudGenerator::getRouteNamePrefix($prefix);

        return sprintf('%s%s%s', $snakeCasedBundleName, '' !== $routePrefix ? '_' : '', $routePrefix);
    }
}


<?php

/*
 * Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *  * Neither the name of Yii Software LLC nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * Originally forked from
 * https://github.com/yiisoft/yii2/blob/2.0.15/framework/i18n/MessageFormatter.php
 */

namespace Symfony\Polyfill\Intl\MessageFormatter;

/**
 * A polyfill implementation of the MessageFormatter class provided by the intl extension.
 *
 * It only supports the following message formats:
 *  * plural formatting for english ('one' and 'other' selectors)
 *  * select format
 *  * simple parameters
 *  * integer number parameters
 *
 * It does NOT support the ['apostrophe-friendly' syntax](http://www.php.net/manual/en/messageformatter.formatmessage.php).
 * Also messages that are working with the fallback implementation are not necessarily compatible with the
 * PHP intl MessageFormatter so do not rely on the fallback if you are able to install intl extension somehow.
 *
 * @author Alexander Makarov <sam@rmcreative.ru>
 * @author Carsten Brandt <mail@cebe.cc>
 * @author Nicolas Grekas <p@tchwork.com>
 *
 * @internal
 */
class MessageFormatter
{
    private $locale;
    private $pattern;
    private $tokens;
    private $errorCode = 0;
    private $errorMessage = '';

    public function __construct($locale, $pattern)
    {
        $this->locale = (string) $locale;

        if (!$this->setPattern($pattern)) {
            throw new \IntlException('Message pattern is invalid.');
        }
    }

    public static function create($locale, $pattern)
    {
        $formatter = new static($locale, '-');

        return $formatter->setPattern($pattern) ? $formatter : null;
    }

    public static function formatMessage($locale, $pattern, array $args)
    {
        if (null === $formatter = self::create($locale, $pattern)) {
            return false;
        }

        return $formatter->format($args);
    }

    public function getLocale()
    {
        return $this->locale;
    }

    public function getPattern()
    {
        return $this->pattern;
    }

    public function getErrorCode()
    {
        return $this->errorCode;
    }

    public function getErrorMessage()
    {
        return $this->errorMessage;
    }

    public function setPattern($pattern)
    {
        $pattern = (string) $pattern;
        try {
            $this->tokens = self::tokenizePattern($pattern);
            $this->pattern = $pattern;
        } catch (\DomainException $e) {
            return false;
        }

        return true;
    }

    public function format(array $args)
    {
        $this->errorCode = 0;
        $this->errorMessage = '';

        if (!$args) {
            return $this->pattern;
        }

        try {
            return self::parseTokens($this->tokens, $args, $this->locale);
        } catch (\DomainException $e) {
            $this->errorCode = -1;
            $this->errorMessage = $e->getMessage();

            return false;
        }
    }

    public function parse($value)
    {
        $this->errorCode = -1;
        $this->errorMessage = sprintf('The PHP intl extension is required to use "MessageFormatter::%s()".', __FUNCTION__);

        return false;
    }

    private static function parseTokens(array $tokens, array $args, $locale)
    {
        foreach ($tokens as $i => $token) {
            if (\is_array($token)) {
                $tokens[$i] = self::parseToken($token, $args, $locale);
            }
        }

        return implode('', $tokens);
    }

    private static function tokenizePattern($pattern)
    {
        if (false === $start = $pos = strpos($pattern, '{')) {
            return array($pattern);
        }

        $depth = 1;
        $tokens = array(substr($pattern, 0, $pos));

        while (true) {
            $open = strpos($pattern, '{', 1 + $pos);
            $close = strpos($pattern, '}', 1 + $pos);

            if (false === $open) {
                if (false === $close) {
                    break;
                }
                $open = \strlen($pattern);
            }

            if ($close > $open) {
                ++$depth;
                $pos = $open;
            } else {
                --$depth;
                $pos = $close;
            }

            if (0 === $depth) {
                $tokens[] = explode(',', substr($pattern, 1 + $start, $pos - $start - 1), 3);
                $start = 1 + $pos;
                $tokens[] = substr($pattern, $start, $open - $start);
                $start = $open;
            }

            if (0 !== $depth && (false === $open || false === $close)) {
                break;
            }
        }

        if ($depth) {
            throw new \DomainException('Message pattern is invalid.');
        }

        return $tokens;
    }

    /**
     * Parses pattern based on ICU grammar.
     *
     * @see http://icu-project.org/apiref/icu4c/classMessageFormat.html#details
     */
    private static function parseToken(array $token, array $args, $locale)
    {
        if (!isset($args[$param = trim($token[0])])) {
            return '{'.$param.'}';
        }

        $arg = $args[$param];
        $type = isset($token[1]) ? trim($token[1]) : 'none';
        switch ($type) {
            case 'date': //XXX use DateFormatter?
            case 'time':
            case 'spellout':
            case 'ordinal':
            case 'duration':
            case 'choice':
            case 'selectordinal':
                throw new \DomainException(sprintf('The PHP intl extension is required to use the "%s" message format.', $type));

            case 'number':
                $format = isset($token[2]) ? trim($token[2]) : null;
                if (!is_numeric($arg) || (null !== $format && 'integer' !== $format)) {
                    throw new \DomainException('The PHP intl extension is required to use the "number" message format with non-integer values.');
                }

                $number = number_format($arg); //XXX use NumberFormatter?
                if (null === $format && false !== $pos = strpos($arg, '.')) {
                    // add decimals with unknown length
                    $number .= '.'.substr($arg, $pos + 1);
                }

                return $number;

            case 'none':
                return $arg;

            case 'select':
                /* http://icu-project.org/apiref/icu4c/classicu_1_1SelectFormat.html
                selectStyle = (selector '{' message '}')+
                */
                if (!isset($token[2])) {
                    throw new \DomainException('Message pattern is invalid.');
                }
                $select = self::tokenizePattern($token[2]);
                $c = \count($select);
                $message = false;
                for ($i = 0; 1 + $i < $c; ++$i) {
                    if (\is_array($select[$i]) || !\is_array($select[1 + $i])) {
                        throw new \DomainException('Message pattern is invalid.');
                    }
                    $selector = trim($select[$i++]);
                    if (false === $message && 'other' === $selector || $selector == $arg) {
                        $message = implode(',', $select[$i]);
                    }
                }
                if (false !== $message) {
                    return self::parseTokens(self::tokenizePattern($message), $args, $locale);
                }
                break;

            case 'plural': //TODO make it locale-dependent based on symfony/translation rules
                /* http://icu-project.org/apiref/icu4c/classicu_1_1PluralFormat.html
                pluralStyle = [offsetValue] (selector '{' message '}')+
                offsetValue = "offset:" number
                selector = explicitValue | keyword
                explicitValue = '=' number  // adjacent, no white space in between
                keyword = [^[[:Pattern_Syntax:][:Pattern_White_Space:]]]+
                message: see MessageFormat
                */
                if (!isset($token[2])) {
                    throw new \DomainException('Message pattern is invalid.');
                }
                $plural = self::tokenizePattern($token[2]);
                $c = \count($plural);
                $message = false;
                $offset = 0;
                for ($i = 0; 1 + $i < $c; ++$i) {
                    if (\is_array($plural[$i]) || !\is_array($plural[1 + $i])) {
                        throw new \DomainException('Message pattern is invalid.');
                    }
                    $selector = trim($plural[$i++]);

                    if (1 === $i && 0 === strncmp($selector, 'offset:', 7)) {
                        $pos = strpos(str_replace(array("\n", "\r", "\t"), ' ', $selector), ' ', 7);
                        $offset = (int) trim(substr($selector, 7, $pos - 7));
                        $selector = trim(substr($selector, 1 + $pos, \strlen($selector)));
                    }
                    if (false === $message && 'other' === $selector ||
                        '=' === $selector[0] && (int) substr($selector, 1, \strlen($selector)) === $arg ||
                        'one' === $selector && 1 == $arg - $offset
                    ) {
                        $message = implode(',', str_replace('#', $arg - $offset, $plural[$i]));
                    }
                }
                if (false !== $message) {
                    return self::parseTokens(self::tokenizePattern($message), $args, $locale);
                }
                break;
        }

        throw new \DomainException('Message pattern is invalid.');
    }
}


<?php

namespace Symfony\Polyfill\Php73;

/**
 * @author Gabriel Caruso <carusogabriel34@gmail.com>
 * @author Ion Bazan <ion.bazan@gmail.com>
 *
 * @internal
 */
final class Php73
{
    public static $startAt = 1533462603;

    /**
     * @param bool $asNum
     *
     * @return array|float|int
     */
    public static function hrtime($asNum = false)
    {
        $ns = \microtime(false);
        $s = \substr($ns, 11) - self::$startAt;
        $ns = 1E9 * (float) $ns;

        if ($asNum) {
            $ns += $s * 1E9;

            return \PHP_INT_SIZE === 4 ? $ns : (int) $ns;
        }

        return array($s, (int) $ns);
    }
}


<?php
namespace SKAgarwal\GoogleApi;

use Illuminate\Support\ServiceProvider as BaseServiceProvider;

class ServiceProvider extends BaseServiceProvider
{
    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton('GooglePlaces', function ($app) {
            $key = isset($app['config']['google.places.key'])
                ? $app['config']['google.places.key'] : null;

            $verifySSL = isset($app['config']['google.places.verify_ssl'])
                ? $app['config']['google.places.verify_ssl'] : true;

            $headers = isset($app['config']['google.places.headers'])
                ? $app['config']['google.places.headers'] : [];

            return new PlacesApi($key, $verifySSL, $headers);
        });
    }

    /**
     * Boot
     */
    public function boot()
    {
        $configFile = __DIR__ . '/../config/google.php';

        $this->publishes([
            $configFile => config_path('google.php'),
        ]);
    }
}


<?php
namespace CristianPontes\ZohoCRMClient\Transport;

use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;

/**
 * Base abstract logger-aware Transport Decorator
 */
abstract class AbstractTransportDecorator implements Transport, LoggerAwareInterface
{
    protected $transport;

    function __construct(Transport $transport)
    {
        $this->transport = $transport;
    }

    /**
     * @param string $module
     * @param string $method
     * @param array $paramList
     * @return array
     */
    abstract public function call($module, $method, array $paramList);

    /**
     * Sets a logger instance on the transport
     *
     * @param LoggerInterface $logger
     */
    public function setLogger(LoggerInterface $logger)
    {
        if ($this->transport instanceof LoggerAwareInterface) {
            $this->transport->setLogger($logger);
        }
    }
}


<?php
namespace CristianPontes\ZohoCRMClient\Request;
use CristianPontes\ZohoCRMClient\Response\Record;

/**
 * GetRelatedRecords API Call
 *
 * You can use the getRelatedRecords method to fetch related records
 *
 * @see https://www.zoho.com/crm/help/api/getrelatedrecords.html
 */
class GetSearchRecordsByPDC extends AbstractRequest
{

    protected function configureRequest()
    {
        $this->request
            ->setMethod('getSearchRecordsByPDC')
            ->setParam('newFormat', '1')
            ->setParam('selectColumns', 'All');
    }

    /**
     * @param int $index
     * @return GetSearchRecordsByPDC
     */
    public function fromIndex($index)
    {
        $this->request->setParam('fromIndex', (int) $index);
        return $this;
    }

    /**
     * @param int $index
     * @return GetSearchRecordsByPDC
     */
    public function toIndex($index)
    {
        $this->request->setParam('toIndex', (int) $index);
        return $this;
    }

    /**
     * Column names to select i.e, ['Last Name', 'Website', 'Email']
     * When not set defaults to all columns
     *
     * @param array|string $columns
     * @return GetSearchRecordsByPDC
     */
    public function selectColumns($columns)
    {
        if (!is_array($columns)) {
            $columns = func_get_args();
        }
        $this->request->setParam(
            'selectColumns',
            $this->request->getModule() . '(' . implode(',', $columns) . ')'
        );
        return $this;
    }

    /**
     * Include the empty fields in the response.
     *
     * @return GetSearchRecordsByPDC
     */
    public function withEmptyFields()
    {
        $this->request->setParam('newFormat', "2");
        return $this;
    }

    /**
     * Specify the predefined search column
     * @see https://www.zoho.com/crm/help/api/getsearchrecordsbypdc.html#Default_Predefined_Columns
     *
     * @param $column string
     * @return GetSearchRecordsByPDC
     */
    public function searchColumn($column)
    {
        $this->request->setParam('searchColumn', $column);
        return $this;
    }

    /**
     * Specify the value to be searched
     *
     * @param $value string
     * @return GetSearchRecordsByPDC
     */
    public function searchValue($value)
    {
        $this->request->setParam('searchValue', $value);
        return $this;
    }

    /**
     * @return Record[]
     */
    public function request()
    {
        return $this->request
            ->request();
    }
}


<?php
namespace CristianPontes\ZohoCRMClient\Request;

use Buzz\Message\Form\FormUpload;
use CristianPontes\ZohoCRMClient\Response\MutationResult;

/**
 * UploadFile API Call
 *
 * You can use this method to attach files to records
 *
 * @see https://www.zoho.com/crm/help/api/uploadfile.html
 */

class UploadFile extends AbstractRequest
{
    protected function configureRequest()
    {
        $this->request
            ->setMethod('uploadFile');
    }

    /**
     * Set rhe record Id to delete
     *
     * @param $id
     * @return UploadFile
     */
    public function id($id)
    {
        $this->request->setParam('id', $id);
        return $this;
    }

    /**
     * Attach a external link to a record
     *
     * @param $link
     * @return UploadFile
     */
    public function attachLink($link)
    {
        $this->request->setParam('attachmentUrl', $link);
        return $this;
    }

    /**
     * Pass the file input stream to a record
     *
     * @param $path - this must be the full path of the file. i.e: /home/path/to/file.extension
     * @return UploadFile
     */
    public function uploadFromPath($path)
    {
        $file = new FormUpload($path);
        $this->request->setParam('content', $file);
        return $this;
    }

    /**
     * @return MutationResult
     */
    public function request()
    {
        return $this->request
            ->request();
    }
}


<?php
namespace CristianPontes\ZohoCRMClient\Transport;

use CristianPontes\ZohoCRMClient\Exception;
use CristianPontes\ZohoCRMClient\Request\ConvertLead;
use CristianPontes\ZohoCRMClient\Response;
use CristianPontes\ZohoCRMClient\ZohoError;

use SimpleXMLElement;

/**
 * XmlDataTransportDecorator handles the XML communication with Zoho
 */
class XmlDataTransportDecorator extends AbstractTransportDecorator
{
    /** @var string */
    private $module;
    /** @var string */
    private $method;
    /** @var array */
    private $call_params;

    /**
     * @param Transport $transport to be decorated with XML support
     */
    public function __construct(Transport $transport)
    {
        parent::__construct($transport);
    }

    /**
     * @param string $module
     * @param string $method
     * @param array $paramList
     * @return array
     */
    public function call($module, $method, array $paramList)
    {
        $this->module = $module;
        $this->method = $method;
        $this->call_params = $paramList;

        if ($this->method == 'convertLead') {
            $paramList['xmlData'] = $this->encodeRequestConvertLead($paramList);
        } else {
            if (array_key_exists('xmlData', $paramList)) {
                $paramList['xmlData'] = $this->encodeRecords($paramList['xmlData']);
            }
        }

        $response = $this->transport->call($module, $method, $paramList);

        return $this->parse($response);
    }

    /**
     *
     * @param array $paramList
     * @return string XML representation of the records
     */
    private function encodeRequestConvertLead(array $paramList)
    {
        $root = new SimpleXMLElement('<Potentials></Potentials>');
        $options = array();

        // row 1 (options)
        foreach (ConvertLead::getOptionFields() as $optionName) {
            if (isset($paramList[$optionName])) {
                $options[$optionName] = $paramList[$optionName];
            }
        }
        $row = $root->addChild('row');
        $row->addAttribute('no', 1);
        $this->encodeRecord($options, 'option', $row);

        // row 2 (data)
        if (array_key_exists('xmlData', $paramList)) {
            $record = $paramList['xmlData'];
            if ($record) {
                $row = $root->addChild('row');
                $row->addAttribute('no', 2);
                $this->encodeRecord($record, 'FL', $row);
            }
        }
        return $root->asXML();
    }

    /**
     * @param array $records
     * @throws \CristianPontes\ZohoCRMClient\Exception\RuntimeException
     * @return string XML representation of the records
     */
    private function encodeRecords(array $records)
    {
        $module = $this->method == 'updateRelatedRecords' ?
                $this->call_params['relatedModule'] : $this->module;

        $root = new SimpleXMLElement('<'.$module.'></'.$module.'>');

        foreach ($records as $no => $record) {
            $row = $root->addChild('row');
            $row->addAttribute('no', $no + 1);

            $this->encodeRecord($record, 'FL', $row);
        }

        return $root->asXML();
    }

    /**
     * Encodes a request record
     * @param array $record
     * @param string $childName XML node name
     * @param SimpleXMLElement $xml
     */
    private function encodeRecord($record, $childName, &$xml)
    {
        foreach ($record as $key => $value)
        {
            if ($value instanceof \DateTime)
            {
                if ($value->format('His') === '000000') {
                    $value = $value->format('m/d/Y');
                } else {
                    $value = $value->format('Y-m-d H:i:s');
                }
            }

            $keyValue = $xml->addChild($childName);
            $keyValue->addAttribute('val', $key);

            if(is_array($value)) {
               $this->parseNestedValues($value, $keyValue);
            }
            else {
                $keyValue[0] = $value;
            }
        }
    }

    /**
     * @param $array
     * @param SimpleXMLElement $xml
     */
    private function parseNestedValues($array, &$xml)
    {
        foreach($array as $key => $value)
        {
            if(is_array($value))
            {
                $type = isset($value['@type']) ? $value['@type'] : "null";
                unset($value['@type']);

                $subNode = $xml->addChild("$type");
                $subNode->addAttribute('no', $key + 1);
                $this->parseNestedValues($value, $subNode);
            }
            else
            {
                $keyValue = $xml->addChild('FL');
                $keyValue[0] = $value;
                $keyValue->addAttribute('val', $key);
            }
        }
    }

    /**
     * Parses the XML returned by Zoho to the appropriate objects
     *
     * @param string $content Response body as returned by Zoho
     * @throws Exception\UnexpectedValueException When invalid XML is given to parse
     * @throws Exception\NoDataException when Zoho tells us there is no data
     * @throws Exception\ZohoErrorException when content is a Error response
     * @return bool|Response\Record|Response\Record[]|Response\Field[]|Response\MutationResult
     */
    private function parse($content)
    {

        if ($this->method == 'downloadFile') {
            return $this->parseResponseDownloadFile($content);
        }

        $xml = new SimpleXMLElement($content);
        if (isset($xml->error)) {
            throw new Exception\ZohoErrorException(
                new ZohoError(
                    (string) $xml->error->code,
                    (string) $xml->error->message
                )
            );
        }

        if (isset($xml->nodata)) {
            throw new Exception\NoDataException(
                new ZohoError(
                    (string)$xml->nodata->code, (string) $xml->nodata->message
                )
            );
        }

        if ($this->method == 'getFields') {
            return $this->parseResponseGetFields($xml);
        }

        if ($this->method == 'deleteRecords') {
            return $this->parseResponseDeleteRecords($xml);
        }

        if ($this->method == 'uploadFile') {
            return $this->parseResponseUploadFile($xml);
        }

        if ($this->method == 'deleteFile') {
            return $this->parseResponseDeleteFile($xml);
        }

        if ($this->method == 'getDeletedRecordIds') {
            return $this->parseResponseGetDeletedRecordIds($xml);
        }

        if ($this->method == 'convertLead') {
            return $this->parseResponseConvertLead($xml);
        }

        if ($this->method == 'updateRelatedRecords') {
            return $this->parseUpdateRelatedRecords($xml);
        }

        if (isset($xml->result->{$this->module})) {
            return $this->parseResponseGetRecords($xml);
        }

        if (isset($xml->result->row->success) || isset($xml->result->row->error)) {
            return $this->parseResponsePostRecordsMultiple($xml);
        }

        throw new Exception\UnexpectedValueException('Xml doesn\'t contain expected fields');
    }

    /**
     * @param SimpleXMLElement $xml
     * @return array
     */
    private function parseResponseGetRecords(SimpleXMLElement $xml)
    {
        $records = array();
        foreach ($xml->result->{$this->module}->row as $row) {
            $records[(string) $row['no']] = $this->rowToRecord($row);
        }

        return $records;
    }

    /**
     * @param SimpleXMLElement $row
     * @return Response\Record
     */
    private function rowToRecord($row)
    {
        $data = array();
        foreach($row as $field) {
            if ($field->count() > 0) {
                foreach ($field->children() as $item) {
                    foreach ($item->children() as $subitem) {
                        $data[(string) $field['val']][(string) $item['no']][(string) $subitem['val']] = (string) $subitem;
                    }
                }
            }
            else {
                $data[(string) $field['val']] = (string) $field;
            }
        }

        return new Response\Record($data, (int) $row['no']);
    }

    /**
     * @param SimpleXMLElement $xml
     * @return array
     */
    private function parseResponseGetFields($xml)
    {
        $records = array();
        foreach ($xml->section as $section) {
            foreach ($section as $field) {
                $options = array();
                if ($field->children()->count() > 0) {
                    $options = array();
                    foreach ($field->children() as $value) {
                        $options[] = (string) $value;
                    }
                }

                $records[] = new Response\Field(
                    (string) $section['name'],
                    (string) $field['label'],
                    (string) $field['type'],
                    (string) $field['req'] === 'true',
                    (string) $field['isreadonly'] === 'true',
                    (int) $field['maxlength'],
                    $options,
                    (string) $field['customfield'] === 'true',
                    (string) isset($field['lm']) ? $field['lm'] : false
                );
            }
        }
        return $records;
    }

    /**
     * @param SimpleXMLElement $xml
     * @return Response\MutationResult
     */
    private function parseResponseDeleteRecords($xml)
    {
        return new Response\MutationResult(1, (string) $xml->result->code);
    }

    /**
     * @param SimpleXMLElement $xml
     * @return Response\MutationResult
     */
    private function parseResponseUploadFile($xml)
    {
        $code = isset($xml->result->recorddetail) ? "4800" : "0";
        $response = new Response\MutationResult(1, $code);
        if($code === "4800")
        {
            $response->setId((string) $xml->result->recorddetail->FL[0]);
            $response->setCreatedTime((string) $xml->result->recorddetail->FL[1]);
            $response->setModifiedTime((string) $xml->result->recorddetail->FL[2]);
        }
        return $response;
    }

    /**
     * @param SimpleXMLElement $xml
     * @return Response\MutationResult
     */
    private function parseResponseDeleteFile($xml)
    {
        return new Response\MutationResult(1, (string) $xml->success->code);
    }

    /**
     * @param $file_content
     * @return bool
     * @throws Exception\Exception
     */
    private function parseResponseDownloadFile($file_content)
    {
        if(!isset($this->call_params['file_path'])) {
            throw new Exception\Exception('Missed file path, set it');
        }

        $fp = fopen($this->call_params['file_path'], 'w');
        $success = fwrite($fp, $file_content);
        fclose($fp);

        return $success ? true : false;
    }

    /**
     * @param SimpleXMLElement $xml
     * @return Response\Record
     */
    private function parseResponseGetDeletedRecordIds($xml)
    {
        $ids = explode(',', (string) $xml->result->DeletedIDs);
        return new Response\Record($ids, 1);
    }

    /**
     * @param SimpleXMLElement $xml
     * @return array
     */
    private function parseResponseConvertLead($xml)
    {
        $records = array();
        foreach ($xml as $row) {
            $records[$row->getName()] = (string) $row;
        }

        return $records;
    }

    /**
     * @param SimpleXMLElement $xml
     * @return Response\Record
     */
    private function parseUpdateRelatedRecords($xml)
    {
        $result  = (array) $xml->result;
        $added   = isset($result['added-ids'])   ? eval('return ' . $result['added-ids'] . ';')   : array();
        $updated = isset($result['updated-ids']) ? eval('return ' . $result['updated-ids'] . ';') : array();
        $data = array(
            'added-ids'   => $added,
            'updated-ids' => $updated
        );
        return new Response\Record($data, 1);
    }

    /**
     * @param $xml
     * @return array
     */
    private function parseResponsePostRecordsMultiple($xml)
    {
        $records = array();
        foreach ($xml->result->row as $row) {
            $no = (string) $row['no'];
            if (isset($row->success)) {
                $success = new Response\MutationResult((int) $no, (string) $row->success->code);
                foreach ($row->success->details->children() as $field) {
                    $method = 'set' . str_replace(' ', '', $field['val']);
                    if (method_exists($success, $method)) {
                        $success->{$method}((string) $field);
                    }
                }
                $records[$no] = $success;
            } else {
                $error = new Response\MutationResult((int) $no, (string) $row->error->code);
                $error->setError(
                    new ZohoError((string) $row->error->code, (string) $row->error->details)
                );
                $records[$no] = $error;
            }
        }

        return $records;
    }
}


<?php
namespace CristianPontes\ZohoCRMClient\Request;
use CristianPontes\ZohoCRMClient\Response\Record;

/**
 * SearchRecords API Call
 *
 * You can use the searchRecords method to get the list of records that meet your search criteria
 *
 * @see https://www.zoho.com/crm/help/api/searchrecords.html
 */
class SearchRecords extends AbstractRequest
{
    private $wheres = array();
    private $orWheres = array();

    protected function configureRequest()
    {
        $this->request
            ->setMethod('searchRecords')
            ->setParam('selectColumns', 'All')
            ->setParam('newFormat', '1');
    }

    /**
     * Column names to select i.e, ['Last Name', 'Website', 'Email']
     * When not set defaults to all columns
     *
     * @param array $columns
     * @return SearchRecords
     */
    public function selectColumns($columns)
    {
        if (!is_array($columns)) {
            $columns = func_get_args();
        }
        $this->request->setParam(
            'selectColumns',
            $this->request->getModule() . '(' . implode(',', $columns) . ')'
        );
        return $this;
    }

    /**
     * @param int $index
     * @return SearchRecords
     */
    public function fromIndex($index)
    {
        $this->request->setParam('fromIndex', (int) $index);
        return $this;
    }

    /**
     * @param int $index
     * @return SearchRecords
     */
    public function toIndex($index)
    {
        $this->request->setParam('toIndex', (int) $index);
        return $this;
    }

    /**
     * Include the empty fields in the response.
     *
     * @return SearchRecords
     */
    public function withEmptyFields()
    {
        $this->request->setParam('newFormat', "2");
        return $this;
    }

    /**
     * Search record by an equal criteria.
     * Could be multiple 'wheres' into a single request
     * i.e: ('First Name', 'Cristian')
     *
     * @param $field string
     * @param $value string
     * @return SearchRecords
     */
    public function where($field, $value)
    {
        $this->wheres[$field] = $value;
        $this->parseQueries();
        return $this;
    }

    /**
     * Search record by an equal criteria adding.
     * This function adds a secondary alternative after established 'where(s)'
     * Could be multiple 'orWheres' into a single request
     * i.e: ('Email', 'hsilencee@gmail.com')
     *
     * @param $field string
     * @param $value string
     * @return SearchRecords
     */
    public function orWhere($field, $value)
    {
        $this->orWheres[] = array($field => $value);
        $this->parseQueries();
        return $this;
    }

    private function parseQueries()
    {
        $wheres = '';
        $orWheres = '';

        $wheres_keys = array_keys($this->wheres);
        for ($i = 0; $i < count($wheres_keys); $i++) {
            $current = $wheres_keys[$i];
            $wheres .= '('.$current.':'.$this->wheres[$current].')';
            if(isset($wheres_keys[$i+1])){
                $wheres .= 'AND';
            }
        }
        foreach ($this->orWheres as $item) {
            foreach ($item as $key => $value) {
                $orWheres .= 'OR('.$key.':'.$value.')';
            }
        }
        $query = '(('.$wheres.')'.$orWheres.')';
        $this->request->setParam('criteria', $query);
    }

    /**
     * @return Record[]
     */
    public function request()
    {
        return $this->request
            ->request();
    }

}


<?php

namespace GeniusTS\LaravelHijri;

use GeniusTS\HijriDate\Date;
use GeniusTS\HijriDate\Hijri;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\ServiceProvider;

/**
 * Class HijriDatesServiceProvider
 *
 * @package GeniusTS\LaravelHijri
 */
class HijriDatesServiceProvider extends ServiceProvider
{

    protected $configuration = __DIR__ . '/../resources/config/hijri-dates.php';

    protected $translations = __DIR__ . '/../resources/lang';

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        $this->publishes([$this->configuration => config_path('hijri-dates.php')], 'config');
        $this->publishes([$this->translations => resource_path('lang/vendor/hijri-dates/')], 'translation');

        $this->mergeConfigFrom($this->configuration, 'hijri-dates');
        $this->loadTranslationsFrom($this->translations, 'hijri-dates');

        $translation = Config::get('hijri-dates.translation');
        Date::setTranslation(new $translation);
        Date::setToStringFormat(Config::get('hijri-dates.toStringFormat'));
        Date::setDefaultNumbers(Config::get('hijri-dates.numeric_system'));

        Hijri::setDefaultAdjustment(Config::get('hijri-dates.adjustment'));
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

}


<?php

/**
 * Sugar Mailer
 *
 * The contents of this file are subject to the terms of the GNU General
 * Public License Version 3.0. You may not use this file except in
 * compliance with the license. Any of the license terms and conditions
 * can be waived if you get permission from the copyright holder.
 *
 * Copyright (c) 2019 ~ ikkez
 * Christian Knuth <ikkez0n3@gmail.com>
 * https://github.com/ikkez/F3-Sugar/
 *
 * @version 1.2.1
 * @date: 09.04.2019
 */

class Mailer {

	protected
		$smtp,
		$recipients,
		$message,
		$charset;

	static $EOL="\r\n";

	/**
	 * create mailer instance working with a specific charset
	 * usually one of these:
	 *      ISO-8859-1
	 *      ISO-8859-15
	 *      UTF-8
	 * @param string $enforceCharset
	 */
	public function __construct($enforceCharset='UTF-8') {
		$this->charset = $enforceCharset;
		$this->reset();
	}

	/**
	 * initialize SMTP plugin
	 */
	public function initSMTP() {
		/** @var \Base $f3 */
		$f3 = \Base::instance();
		$this->smtp = new \SMTP(
			$f3->get('mailer.smtp.host'),
			$f3->get('mailer.smtp.port'),
			$f3->get('mailer.smtp.scheme'),
			$f3->get('mailer.smtp.user'),
			$f3->get('mailer.smtp.pw'));
		if (!$f3->devoid('mailer.errors_to',$errors_to))
			$this->setErrors($errors_to);
		if (!$f3->devoid('mailer.reply_to',$reply_to))
			$this->setReply($reply_to);
		if (!$f3->devoid('mailer.from_mail',$from_mail)) {
			if ($f3->devoid('mailer.from_name',$from_name))
				$from_name = NULL;
			$this->setFrom($from_mail, $from_name);
		}
	}

	/**
	 * encode special chars if possible
	 * @param $str
	 * @return mixed
	 */
	protected function encode($str) {
		if (empty($str) || $this->charset == 'UTF-8')
			return $str;
		if (extension_loaded('iconv'))
			$out = @iconv("UTF-8", $this->charset."//IGNORE", $str);
		if (!isset($out) || !$out)
			$out = extension_loaded('mbstring')
				? mb_convert_encoding($str,$this->charset,"UTF-8")
				: utf8_decode($str);
		return $out ?: $str;
	}

	/**
	 * encode and split header strings
	 * @param $str
	 * @return string
	 */
	protected function encodeHeader($str) {
		if (extension_loaded('iconv')) {
			$out = iconv_mime_encode('Subject', $str,
				['input-charset' => 'UTF-8', 'output-charset' => $this->charset]);
			$out = substr($out, strlen('Subject: '));
		} elseif(extension_loaded('mbstring')) {
			mb_internal_encoding('UTF-8');
			$out = mb_encode_mimeheader($str, $this->charset, 'B', static::$EOL, strlen('Subject: '));
		} else
			$out = wordwrap($str,65,static::$EOL);
		return $out;
	}

	/**
	 * build email with title string
	 * @param $email
	 * @param null $title
	 * @return string
	 */
	protected function buildMail($email, $title=null) {
		return ($title?'"'.$title.'" ':'').'<'.$email.'>';
	}

	/**
	 * set encoded header value
	 * @param $key
	 * @param $val
	 */
	public function set($key, $val) {
		$this->smtp->set($key, $this->encode($val));
	}

	/**
	 * set message sender
	 * @param $email
	 * @param null $title
	 */
	public function setFrom($email, $title=null) {
		$this->set('From', $this->buildMail($email,$title));
	}

	/**
	 * add a direct recipient
	 * @param $email
	 * @param null $title
	 */
	public function addTo($email, $title=null) {
		$this->recipients['To'][$email] = $title;
	}

	/**
	 * add a carbon copy recipient
	 * @param $email
	 * @param null $title
	 */
	public function addCc($email, $title=null) {
		$this->recipients['Cc'][$email] = $title;
	}

	/**
	 * add a blind carbon copy recipient
	 * @param $email
	 * @param null $title
	 */
	public function addBcc($email, $title=null) {
		$this->recipients['Bcc'][$email] = $title;
	}

	/**
	 * set reply-to field respected by most email clients
	 * @param $email
	 * @param null $title
	 */
	public function setReply($email, $title=null) {
		$this->set('Reply-To', $this->buildMail($email,$title));
	}

	/**
	 * set receipient for bounce error mails
	 * @param $email
	 * @param null $title
	 */
	public function setErrors($email, $title=null) {
		$this->set('Sender', $this->buildMail($email,$title));
	}

	/**
	 * reset recipients if key was given, or restart whole smtp plugin
	 * @param null $key
	 */
	public function reset($key=null) {
		if ($key) {
			$key = ucfirst($key);
			$this->smtp->clear($key);
			if (isset($this->recipients[$key]))
				unset($this->recipients[$key]);
		} else {
			$this->recipients = array();
			$this->initSMTP();
		}
	}

	/**
	 * set message in plain text format
	 * @param $message
	 */
	public function setText($message) {
		$this->setContent($message,'text/plain');
	}

	/**
	 * set message in HTML text format
	 * @param $message
	 */
	public function setHTML($message) {
		$f3 = \Base::instance();
		// we need a clean template instance for extending it one-time
		$tmpl = new \Template();
		// create traceable jump links
		if ($f3->exists('mailer.jumplinks',$jumplink) && $jumplink)
			$tmpl->extend('a', function($node) use($f3, $tmpl) {
				if (isset($node['@attrib'])) {
					$attr = $node['@attrib'];
					unset($node['@attrib']);
				} else
					$attr = array();
				if (isset($attr['href'])) {
					if (!$f3->exists('mailer.jump_route',$ping_route))
						$ping_route = '/mailer-jump';
					$attr['href'] = $f3->get('SCHEME').'://'.$f3->get('HOST').$f3->get('BASE').
						$ping_route.'?target='.urlencode($attr['href']);
				}
				$params = '';
				foreach ($attr as $key => $value)
					$params.=' '.$key.'="'.$value.'"';
				return '<a'.$params.'>'.$tmpl->build($node).'</a>';
			});
		$message = $tmpl->build($tmpl->parse($message));
		$this->setContent($message,'text/html');
	}

	/**
	 * set message contents by mime type
	 * @param string $data message data
	 * @param string $mime the mime type
	 * @param null $charset
	 */
	public function setContent($data, $mime, $charset=NULL) {
		if (!$charset)
			$charset=$this->charset;
		$this->message[$mime] = [
			'content'=>$data,
			'type'=>$mime.'; '.$charset
		];
	}

	/**
	 * add a file attachment
	 * @param $path
	 * @param null $alias
	 * @param null $cid
	 */
	public function attachFile($path, $alias=null, $cid=null) {
		$this->smtp->attach($path,$alias,$cid);
	}

	/**
	 * send message
	 * @param $subject
	 * @param bool $mock
	 * @param bool|string $log log level [false,true,'verbose']
	 * @return bool
	 */
	public function send($subject,$mock=false,$log='verbose') {
		foreach ($this->recipients as $key => $rcpts) {
			$mails = array();
			foreach ($rcpts as $mail=>$title)
				$mails[] = $this->buildMail($mail,$title);
			$this->set($key,implode(', ',$mails));
		}
		$this->smtp->set('Subject', $this->encodeHeader($this->encode($subject)));
		$body = '';
		$hash=uniqid(NULL,TRUE);
		$multipart = count($this->message) > 1;
		if ($multipart)
			$this->smtp->set('Content-Type', 'multipart/alternative; boundary="'.$hash.'"');
		$i=0;
		foreach ($this->message as $msg) {
			if ($multipart) {
				$body .= '--'.$hash.static::$EOL;
				$body .= 'Content-Type: '.$msg['type'].static::$EOL.static::$EOL;
			} else
				$this->smtp->set('Content-Type', $msg['type']);
			$body .= $msg['content'].static::$EOL.static::$EOL;
		}
		if ($multipart)
			$body .= '--'.$hash.'--'.static::$EOL;
		$success = $this->smtp->send($this->encode($body),$log,$mock);
		$f3 = \Base::instance();
		if (!$success && $f3->exists('mailer.on.failure',$fail_handler))
			$f3->call($fail_handler,array($this,$this->smtp->log()));
		return $success;
	}

	/**
	 * save the send mail to disk
	 * @param $filename
	 */
	public function save($filename) {
		$f3 = \Base::instance();
		$lines = explode("\n",$this->smtp->log());
		$start = false;
		$out = '';
		for($i=0,$max=count($lines);$i<$max;$i++) {
			if (!$start && preg_match('/^354.*?$/',$lines[$i],$matches)) {
				$start=true;
				continue;
			} elseif (preg_match('/^250.*?$\s^QUIT/m',
				$lines[$i].($i+1 < $max ? "\n".$lines[$i+1] : ''),$matches))
				break;
			if ($start)
				$out.=$lines[$i]."\n";
		}
		if ($out) {
			$path = $f3->get('mailer.storage_path');
			if (!is_dir($path))
				mkdir($path,0777,true);
			$f3->write($path.$filename,$out);
		}
	}

	/**
	 * expose smtp log
	 * @return mixed
	 */
	public function log() {
		return $this->smtp->log();
	}

	/**
	 * receive and proceed message ping
	 * @param Base $f3
	 * @param $params
	 */
	static public function ping(\Base $f3, $params) {
		$hash = $params['hash'];
		// trigger ping event
		if ($f3->exists('mailer.on.ping',$ping_handler))
			$f3->call($ping_handler,array($hash));
		$img = new \Image();
		// 1x1 transparent 8bit PNG
		$img->load(base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMA'.
			'AAAl21bKAAAABGdBTUEAALGPC/xhBQAAAANQTFRFAAAAp3o92gAAAAF0U'.
			'k5TAEDm2GYAAAAKSURBVAjXY2AAAAACAAHiIbwzAAAAAElFTkSuQmCC'));
		$img->render();
	}

	/**
	 * track clicked link and reroute
	 * @param Base $f3
	 */
	static public function jump(\Base $f3, $params) {
		$target = $f3->get('GET.target');
		// trigger jump event
		if ($f3->exists('mailer.on.jump',$jump_handler))
			$f3->call($jump_handler,array($target,$params));
		$f3->reroute(urldecode($target));
	}

	/**
	 * init routing
	 */
	static public function initTracking() {
		/** @var \Base $f3 */
		$f3 = \Base::instance();
		if (!$f3->exists('mailer.ping_route',$ping_route))
			$ping_route = '/mailer-ping/@hash.png';
		$f3->route('GET '.$ping_route,'\Mailer::ping');

		if (!$f3->exists('mailer.jump_route',$jump_route))
			$jump_route = '/mailer-jump';
		$f3->route('GET '.$jump_route,'\Mailer::jump');
	}

}