<?php namespace Maer\Config;

use UnexpectedValueException;
use Maer\Config\Readers;

class Config implements ConfigInterface
{
    /**
     * @var array
     */
    protected $files = [];

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

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

    /**
     * {@inheritdoc}
     */
    public function __construct(array $files = [], array $options = [])
    {
        $this->setReader('php', $options['readers']['php'] ?? new Readers\ArrayReader);
        $this->setReader('ini', $options['readers']['ini'] ?? new Readers\IniReader);
        $this->setReader('json', $options['readers']['json'] ?? new Readers\JsonReader);

        foreach ($options['readers'] ?? [] as $ext => $reader) {
            $this->setReader($ext, $reader);
        }

        if ($files) {
            $this->load($files);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function get($key = null, $default = null)
    {
        if (!$key) {
            return $default;
        }

        $conf =& $this->conf;
        $keys = explode('.', $key);

        foreach ($keys as $test) {
            $direct = implode('.', $keys);

            // Check for a direct match, containing dot
            if (is_array($conf) && array_key_exists($direct, $conf)) {
                return $conf[$direct];
            }

            if (!is_array($conf) || !array_key_exists($test, $conf)) {
                // No hit, return the default
                return $default;
            }

            $conf =& $conf[$test];
            array_shift($keys);
        }

        return $conf;
    }

    /**
     * {@inheritdoc}
     */
    public function set($key, $value = null)
    {
        if (is_array($key)) {
            return $this->merge($key);
        }

        if (func_num_args() < 2) {
            throw new \ArgumentCountError("To few arguments passed to Config::set(). Expected 2 got 1");
        }

        $conf =& $this->conf;

        $segments = explode('.', $key);

        while (count($segments) > 1) {
            $segment = array_shift($segments);
            if (!isset($conf[$segment]) || !is_array($conf[$segment])) {
                $conf[$segment] = [];
            }
            $conf =& $conf[$segment];
        }

        return $conf[array_shift($segments)] = $value;
    }

    /**
     * {@inheritdoc}
     */
    public function push($key, $value)
    {
        $items = $this->get($key);
        if (!is_array($items)) {
            throw new UnexpectedValueException("Expected the target to be array, got " . gettype($items));
        }

        $items[] = $value;
        $this->set($key, $items);
    }

    /**
     * {@inheritdoc}
     */
    public function override(array $array)
    {
        $this->merge($array);
    }

    /**
     * {@inheritdoc}
     */
    public function has($key)
    {
        $conf =& $this->conf;
        $keys = explode('.', $key);

        foreach ($keys as $test) {
            $direct = implode('.', $keys);

            // Check for a direct match, containing dot
            if (is_array($conf) && array_key_exists($direct, $conf)) {
                return true;
            }

            if (!is_array($conf) || !array_key_exists($test, $conf)) {
                return false;
            }

            $conf =& $conf[$test];
            array_shift($keys);
        }

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function exists($key)
    {
        return $this->has($key);
    }

    /**
     * {@inheritdoc}
     */
    public function load($files, $forceReload = false)
    {
        if (!is_array($files)) {
            // Make it an array so we can use the same code
            $files = [$files];
        }

        foreach ($files as $file) {
            if ((array_key_exists($file, $this->files) && !$forceReload)
                || !is_file($file) || !is_readable($file)) {
                // It's already loaded, or doesn't exist, so let's skip it
                continue;
            }

            $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));

            if (!array_key_exists($ext, $this->readers)) {
                throw new \Exception("No reader found for the extension '{$ext}'");
            }

            $conf = $this->readers[$ext]->read($file);

            if ($conf && is_array($conf)) {
                // We're only interested if it is a non-empty array
                $this->override($conf);
                $this->files[$file] = true;
            }

            unset($conf);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function isLoaded($file)
    {
        return array_key_exists($file, $this->files);
    }

    /**
     * {@inheritdoc}
     */
    public function setReader($extension, Readers\ReaderInterface $reader)
    {
        $this->readers[strtolower($extension)] = $reader;
    }

    /**
     * Recursivly merge the array to the existing collection
     *
     * @param  array $array
     */
    protected function merge(array $array)
    {
        $this->conf = array_replace_recursive($this->conf, $array);
    }
}


<?php

/**
 * TechDivision\Import\App\Simple
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Open Software License (OSL 3.0)
 * that is available through the world-wide-web at this URL:
 * http://opensource.org/licenses/osl-3.0.php
 *
 * PHP version 5
 *
 * @author    Tim Wagner <t.wagner@techdivision.com>
 * @copyright 2016 TechDivision GmbH <info@techdivision.com>
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
 * @link      https://github.com/techdivision/import-app-simple
 * @link      http://www.techdivision.com
 */

namespace TechDivision\Import\App;

use Psr\Log\LogLevel;
use League\Event\EmitterInterface;
use Doctrine\Common\Collections\Collection;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\FormatterHelper;
use Symfony\Component\DependencyInjection\TaggedContainerInterface;
use TechDivision\Import\Utils\LoggerKeys;
use TechDivision\Import\Utils\EventNames;
use TechDivision\Import\ApplicationInterface;
use TechDivision\Import\ConfigurationInterface;
use TechDivision\Import\Plugins\PluginFactoryInterface;
use TechDivision\Import\Exceptions\LineNotFoundException;
use TechDivision\Import\Exceptions\FileNotFoundException;
use TechDivision\Import\Exceptions\ImportAlreadyRunningException;
use TechDivision\Import\Services\ImportProcessorInterface;
use TechDivision\Import\Services\RegistryProcessorInterface;

/**
 * The M2IF - Simple Application implementation.
 *
 * This is a example application implementation that should give developers an impression
 * on how the M2IF could be used to implement their own Magento 2 importer.
 *
 * @author    Tim Wagner <t.wagner@techdivision.com>
 * @copyright 2016 TechDivision GmbH <info@techdivision.com>
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
 * @link      https://github.com/techdivision/import-app-simple
 * @link      http://www.techdivision.com
 */
class Simple implements ApplicationInterface
{

    /**
     * The default style to write messages to the symfony console.
     *
     * @var string
     */
    const DEFAULT_STYLE = 'info';

    /**
     * The TechDivision company name as ANSI art.
     *
     * @var string
     */
    protected $ansiArt = ' _______        _     _____  _       _     _
|__   __|      | |   |  __ \(_)     (_)   (_)
   | | ___  ___| |__ | |  | |___   ___ ___ _  ___  _ __
   | |/ _ \/ __| \'_ \| |  | | \ \ / / / __| |/ _ \| \'_ \
   | |  __/ (__| | | | |__| | |\ V /| \__ \ | (_) | | | |
   |_|\___|\___|_| |_|_____/|_| \_/ |_|___/_|\___/|_| |_|
';

    /**
     * The log level => console style mapping.
     *
     * @var array
     */
    protected $logLevelStyleMapping = array(
        LogLevel::INFO      => 'info',
        LogLevel::DEBUG     => 'comment',
        LogLevel::ERROR     => 'error',
        LogLevel::ALERT     => 'error',
        LogLevel::CRITICAL  => 'error',
        LogLevel::EMERGENCY => 'error',
        LogLevel::WARNING   => 'error',
        LogLevel::NOTICE    => 'info'
    );

    /**
     * The PID for the running processes.
     *
     * @var array
     */
    protected $pid;

    /**
     * The actions unique serial.
     *
     * @var string
     */
    protected $serial;

    /**
     * The array with the system logger instances.
     *
     * @var \Doctrine\Common\Collections\Collection
     */
    protected $systemLoggers;

    /**
     * The RegistryProcessor instance to handle running threads.
     *
     * @var \TechDivision\Import\Services\RegistryProcessorInterface
     */
    protected $registryProcessor;

    /**
     * The processor to read/write the necessary import data.
     *
     * @var \TechDivision\Import\Services\ImportProcessorInterface
     */
    protected $importProcessor;

    /**
     * The DI container builder instance.
     *
     * @var \Symfony\Component\DependencyInjection\TaggedContainerInterface
     */
    protected $container;

    /**
     * The system configuration.
     *
     * @var \TechDivision\Import\ConfigurationInterface
     */
    protected $configuration;

    /**
     * The output stream to write console information to.
     *
     * @var \Symfony\Component\Console\Output\OutputInterface
     */
    protected $output;

    /**
     * The plugins to be processed.
     *
     * @var array
     */
    protected $plugins = array();

    /**
     * The flag that stop's processing the operation.
     *
     * @var boolean
     */
    protected $stopped = false;

    /**
     * The filehandle for the PID file.
     *
     * @var resource
     */
    protected $fh;

    /**
     * The plugin factory instance.
     *
     * @var \TechDivision\Import\Plugins\PluginFactoryInterface
     */
    protected $pluginFactory;

    /**
     * The event emitter instance.
     *
     * @var \League\Event\EmitterInterface
     */
    protected $emitter;

    /**
     * The constructor to initialize the instance.
     *
     * @param \Symfony\Component\DependencyInjection\TaggedContainerInterface $container         The DI container instance
     * @param \TechDivision\Import\Services\RegistryProcessorInterface        $registryProcessor The registry processor instance
     * @param \TechDivision\Import\Services\ImportProcessorInterface          $importProcessor   The import processor instance
     * @param \TechDivision\Import\ConfigurationInterface                     $configuration     The system configuration
     * @param \TechDivision\Import\Plugins\PluginFactoryInterface             $pluginFactory     The plugin factory instance
     * @param \Symfony\Component\Console\Output\OutputInterface               $output            The output instance
     * @param \Doctrine\Common\Collections\Collection                         $systemLoggers     The array with the system logger instances
     * @param \League\Event\EmitterInterface                                  $emitter           The event emitter instance
     */
    public function __construct(
        TaggedContainerInterface $container,
        RegistryProcessorInterface $registryProcessor,
        ImportProcessorInterface $importProcessor,
        ConfigurationInterface $configuration,
        PluginFactoryInterface $pluginFactory,
        OutputInterface $output,
        Collection $systemLoggers,
        EmitterInterface $emitter
    ) {

        // register the shutdown function
        register_shutdown_function(array($this, 'shutdown'));

        // initialize the instance with the passed values
        $this->setOutput($output);
        $this->setEmitter($emitter);
        $this->setContainer($container);
        $this->setConfiguration($configuration);
        $this->setSystemLoggers($systemLoggers);
        $this->setPluginFactory($pluginFactory);
        $this->setImportProcessor($importProcessor);
        $this->setRegistryProcessor($registryProcessor);
    }

    /**
     * Set's the event emitter instance.
     *
     * @param \League\Event\EmitterInterface $emitter The event emitter instance
     *
     * @return void
     */
    public function setEmitter(EmitterInterface $emitter)
    {
        $this->emitter = $emitter;
    }

    /**
     * Return's the event emitter instance.
     *
     * @return \League\Event\EmitterInterface The event emitter instance
     */
    public function getEmitter()
    {
        return $this->emitter;
    }

    /**
     * Set's the container instance.
     *
     * @param \Symfony\Component\DependencyInjection\TaggedContainerInterface $container The container instance
     *
     * @return void
     */
    public function setContainer(TaggedContainerInterface $container)
    {
        $this->container = $container;
    }

    /**
     * Return's the container instance.
     *
     * @return \Symfony\Component\DependencyInjection\TaggedContainerInterface The container instance
     */
    public function getContainer()
    {
        return $this->container;
    }

    /**
     * Set's the output stream to write console information to.
     *
     * @param \Symfony\Component\Console\Output\OutputInterface $output The output stream
     *
     * @return void
     */
    public function setOutput(OutputInterface $output)
    {
        $this->output = $output;
    }

    /**
     * Return's the output stream to write console information to.
     *
     * @return \Symfony\Component\Console\Output\OutputInterface The output stream
     */
    public function getOutput()
    {
        return $this->output;
    }

    /**
     * Set's the system configuration.
     *
     * @param \TechDivision\Import\ConfigurationInterface $configuration The system configuration
     *
     * @return void
     */
    public function setConfiguration(ConfigurationInterface $configuration)
    {
        $this->configuration = $configuration;
    }

    /**
     * Return's the system configuration.
     *
     * @return \TechDivision\Import\ConfigurationInterface The system configuration
     */
    public function getConfiguration()
    {
        return $this->configuration;
    }

    /**
     * Set's the RegistryProcessor instance to handle the running threads.
     *
     * @param \TechDivision\Import\Services\RegistryProcessor $registryProcessor The registry processor instance
     *
     * @return void
     */
    public function setRegistryProcessor(RegistryProcessorInterface $registryProcessor)
    {
        $this->registryProcessor = $registryProcessor;
    }

    /**
     * Return's the RegistryProcessor instance to handle the running threads.
     *
     * @return \TechDivision\Import\Services\RegistryProcessor The registry processor instance
     */
    public function getRegistryProcessor()
    {
        return $this->registryProcessor;
    }

    /**
     * Set's the import processor instance.
     *
     * @param \TechDivision\Import\Services\ImportProcessorInterface $importProcessor The import processor instance
     *
     * @return void
     */
    public function setImportProcessor(ImportProcessorInterface $importProcessor)
    {
        $this->importProcessor = $importProcessor;
    }

    /**
     * Return's the import processor instance.
     *
     * @return \TechDivision\Import\Services\ImportProcessorInterface The import processor instance
     */
    public function getImportProcessor()
    {
        return $this->importProcessor;
    }

    /**
     * The array with the system loggers.
     *
     * @param \Doctrine\Common\Collections\Collection $systemLoggers The system logger instances
     *
     * @return void
     */
    public function setSystemLoggers(Collection $systemLoggers)
    {
        $this->systemLoggers = $systemLoggers;
    }

    /**
     * Set's the plugin factory instance.
     *
     * @param \TechDivision\Import\Plugins\PluginFactoryInterface $pluginFactory The plugin factory instance
     *
     * @return void
     */
    public function setPluginFactory(PluginFactoryInterface $pluginFactory)
    {
        $this->pluginFactory = $pluginFactory;
    }

    /**
     * Return's the plugin factory instance.
     *
     * @return \TechDivision\Import\Plugins\PluginFactoryInterface The plugin factory instance
     */
    public function getPluginFactory()
    {
        return $this->pluginFactory;
    }

    /**
     * Return's the logger with the passed name, by default the system logger.
     *
     * @param string $name The name of the requested system logger
     *
     * @return \Psr\Log\LoggerInterface The logger instance
     * @throws \Exception Is thrown, if the requested logger is NOT available
     */
    public function getSystemLogger($name = LoggerKeys::SYSTEM)
    {

        // query whether or not, the requested logger is available
        if (isset($this->systemLoggers[$name])) {
            return $this->systemLoggers[$name];
        }

        // throw an exception if the requested logger is NOT available
        throw new \Exception(
            sprintf(
                'The requested logger \'%s\' is not available',
                $name
            )
        );
    }

    /**
     * Query whether or not the system logger with the passed name is available.
     *
     * @param string $name The name of the requested system logger
     *
     * @return boolean TRUE if the logger with the passed name exists, else FALSE
     */
    public function hasSystemLogger($name = LoggerKeys::SYSTEM)
    {
        return isset($this->systemLoggers[$name]);
    }

    /**
     * Return's the array with the system logger instances.
     *
     * @return \Doctrine\Common\Collections\Collection The logger instance
     */
    public function getSystemLoggers()
    {
        return $this->systemLoggers;
    }

    /**
     * Return's the unique serial for this import process.
     *
     * @return string The unique serial
     */
    public function getSerial()
    {
        return $this->serial;
    }

    /**
     * The shutdown handler to catch fatal errors.
     *
     * This method is need to make sure, that an existing PID file will be removed
     * if a fatal error has been triggered.
     *
     * @return void
     */
    public function shutdown()
    {

        // check if there was a fatal error caused shutdown
        if ($lastError = error_get_last()) {
            // initialize error type and message
            $type = 0;
            $message = '';
            // extract the last error values
            extract($lastError);
            // query whether we've a fatal/user error
            if ($type === E_ERROR || $type === E_USER_ERROR) {
                // clean-up the PID file
                $this->unlock();
                // log the fatal error message
                $this->log($message, LogLevel::ERROR);
            }
        }
    }

    /**
     * Persist the UUID of the actual import process to the PID file.
     *
     * @return void
     * @throws \Exception Is thrown, if the PID can not be locked or the PID can not be added
     * @throws \TechDivision\Import\Exceptions\ImportAlreadyRunningException Is thrown, if a import process is already running
     */
    public function lock()
    {

        // query whether or not, the PID has already been set
        if ($this->pid === $this->getSerial()) {
            return;
        }

        // if not, initialize the PID
        $this->pid = $this->getSerial();

        // open the PID file
        $this->fh = fopen($filename = $this->getPidFilename(), 'a+');

        // try to lock the PID file exclusive
        if (!flock($this->fh, LOCK_EX|LOCK_NB)) {
            throw new ImportAlreadyRunningException(sprintf('PID file %s is already in use', $filename));
        }

        // append the PID to the PID file
        if (fwrite($this->fh, $this->pid . PHP_EOL) === false) {
            throw new \Exception(sprintf('Can\'t write PID %s to PID file %s', $this->pid, $filename));
        }
    }

    /**
     * Remove's the UUID of the actual import process from the PID file.
     *
     * @return void
     * @throws \Exception Is thrown, if the PID can not be removed
     */
    public function unlock()
    {
        try {
            // remove the PID from the PID file if set
            if ($this->pid === $this->getSerial() && is_resource($this->fh)) {
                // remove the PID from the file
                $this->removeLineFromFile($this->pid, $this->fh);

                // finally unlock/close the PID file
                flock($this->fh, LOCK_UN);
                fclose($this->fh);

                // if the PID file is empty, delete the file
                if (filesize($filename = $this->getPidFilename()) === 0) {
                    unlink($filename);
                }
            }
        } catch (FileNotFoundException $fnfe) {
            $this->getSystemLogger()->notice(sprintf('PID file %s doesn\'t exist', $this->getPidFilename()));
        } catch (LineNotFoundException $lnfe) {
            $this->getSystemLogger()->notice(sprintf('PID %s is can not be found in PID file %s', $this->pid, $this->getPidFilename()));
        } catch (\Exception $e) {
            throw new \Exception(sprintf('Can\'t remove PID %s from PID file %s', $this->pid, $this->getPidFilename()), null, $e);
        }
    }

    /**
     * Remove's the passed line from the file with the passed name.
     *
     * @param string   $line The line to be removed
     * @param resource $fh   The file handle of the file the line has to be removed
     *
     * @return void
     * @throws \Exception Is thrown, if the file doesn't exists, the line is not found or can not be removed
     */
    public function removeLineFromFile($line, $fh)
    {

        // initialize the array for the PIDs found in the PID file
        $lines = array();

        // initialize the flag if the line has been found
        $found = false;

        // rewind the file pointer
        rewind($fh);

        // read the lines with the PIDs from the PID file
        while (($buffer = fgets($fh, 4096)) !== false) {
            // remove the new line
            $buffer = trim($buffer);
            // if the line is the one to be removed, ignore the line
            if ($line === $buffer) {
                $found = true;
                continue;
            }

            // add the found PID to the array
            $lines[] = $buffer;
        }

        // query whether or not, we found the line
        if (!$found) {
            throw new LineNotFoundException(sprintf('Line %s can not be found', $line));
        }

        // empty the file and rewind the file pointer
        ftruncate($fh, 0);
        rewind($fh);

        // append the existing lines to the file
        foreach ($lines as $ln) {
            if (fwrite($fh, $ln . PHP_EOL) === false) {
                throw new \Exception(sprintf('Can\'t write %s to file', $ln));
            }
        }
    }

    /**
     * Process the given operation.
     *
     * @param string $serial The unique serial of the actual import process
     *
     * @return void
     * @throws \Exception Is thrown if the operation can't be finished successfully
     */
    public function process($serial)
    {

        try {
            // track the start time
            $startTime = microtime(true);

            // set the serial for this import process
            $this->serial = $serial;

            // invoke the event that has to be fired before the application start's the transaction
            // (if single transaction mode has been activated)
            $this->getEmitter()->emit(EventNames::APP_PROCESS_TRANSACTION_START, $this);

            // start the transaction, if single transaction mode has been configured
            if ($this->getConfiguration()->isSingleTransaction()) {
                $this->getImportProcessor()->getConnection()->beginTransaction();
            }

            // prepare the global data for the import process
            $this->setUp();

            // process the plugins defined in the configuration
            /** @var \TechDivision\Import\Configuration\PluginConfigurationInterface $pluginConfiguration */
            foreach ($this->getConfiguration()->getPlugins() as $pluginConfiguration) {
                // query whether or not the operation has been stopped
                if ($this->isStopped()) {
                    break;
                }
                // process the plugin if not
                $this->pluginFactory->createPlugin($pluginConfiguration)->process();
            }

            // tear down the  instance
            $this->tearDown();

            // commit the transaction, if single transation mode has been configured
            if ($this->getConfiguration()->isSingleTransaction()) {
                $this->getImportProcessor()->getConnection()->commit();
            }

            // track the time needed for the import in seconds
            $endTime = microtime(true) - $startTime;

            // log a message that import has been finished
            $this->log(
                sprintf(
                    'Successfully finished import with serial %s in %f s',
                    $this->getSerial(),
                    $endTime
                ),
                LogLevel::INFO
            );

            // invoke the event that has to be fired before the application has the transaction
            // committed successfully (if single transaction mode has been activated)
            $this->getEmitter()->emit(EventNames::APP_PROCESS_TRANSACTION_SUCCESS, $this);
        } catch (ImportAlreadyRunningException $iare) {
            // tear down
            $this->tearDown();

            // rollback the transaction, if single transaction mode has been configured
            if ($this->getConfiguration()->isSingleTransaction()) {
                $this->getImportProcessor()->getConnection()->rollBack();
            }

            // invoke the event that has to be fired after the application rollbacked the
            // transaction (if single transaction mode has been activated)
            $this->getEmitter()->emit(EventNames::APP_PROCESS_TRANSACTION_FAILURE, $this, $iare);

            // finally, if a PID has been set (because CSV files has been found),
            // remove it from the PID file to unlock the importer
            $this->unlock();

            // track the time needed for the import in seconds
            $endTime = microtime(true) - $startTime;

            // log a warning, because another import process is already running
            $this->getSystemLogger()->warning($iare->__toString());

            // log a message that import has been finished
            $this->getSystemLogger()->info(
                sprintf(
                    'Can\'t finish import with serial because another import process is running %s in %f s',
                    $this->getSerial(),
                    $endTime
                )
            );

            // re-throw the exception
            throw $iare;
        } catch (\Exception $e) {
            // tear down
            $this->tearDown();

            // rollback the transaction, if single transaction mode has been configured
            if ($this->getConfiguration()->isSingleTransaction()) {
                $this->getImportProcessor()->getConnection()->rollBack();
            }

            // invoke the event that has to be fired after the application rollbacked the
            // transaction (if single transaction mode has been activated)
            $this->getEmitter()->emit(EventNames::APP_PROCESS_TRANSACTION_FAILURE, $this, $e);

            // finally, if a PID has been set (because CSV files has been found),
            // remove it from the PID file to unlock the importer
            $this->unlock();

            // track the time needed for the import in seconds
            $endTime = microtime(true) - $startTime;

            // log a message that the file import failed
            foreach ($this->systemLoggers as $systemLogger) {
                $systemLogger->error($e->__toString());
            }

            // log a message that import has been finished
            $this->getSystemLogger()->info(
                sprintf(
                    'Can\'t finish import with serial %s in %f s',
                    $this->getSerial(),
                    $endTime
                )
            );

            // re-throw the exception
            throw $e;
        }
    }

    /**
     * Stop processing the operation.
     *
     * @param string $reason The reason why the operation has been stopped
     *
     * @return void
     */
    public function stop($reason)
    {

        // log a message that the operation has been stopped
        $this->log($reason, LogLevel::INFO);

        // stop processing the plugins by setting the flag to TRUE
        $this->stopped = true;
    }

    /**
     * Return's TRUE if the operation has been stopped, else FALSE.
     *
     * @return boolean TRUE if the process has been stopped, else FALSE
     */
    public function isStopped()
    {
        return $this->stopped;
    }

    /**
     * Gets a service.
     *
     * @param string $id The service identifier
     *
     * @return object The associated service
     *
     * @throws \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException When a circular reference is detected
     * @throws \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException          When the service is not defined
     */
    public function get($id)
    {
        return $this->getContainer()->get($id);
    }

    /**
     * Returns true if the container can return an entry for the given identifier.
     * Returns false otherwise.
     *
     * `has($id)` returning true does not mean that `get($id)` will not throw an exception.
     * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
     *
     * @param string $id Identifier of the entry to look for.
     *
     * @return bool
     */
    public function has($id)
    {
        return $this->getContainer()->has($id);
    }

    /**
     * Lifecycle callback that will be inovked before the
     * import process has been started.
     *
     * @return void
     */
    protected function setUp()
    {
        $this->getEmitter()->emit(EventNames::APP_SET_UP, $this);
    }

    /**
     * Lifecycle callback that will be inovked after the
     * import process has been finished.
     *
     * @return void
     */
    protected function tearDown()
    {
        $this->getEmitter()->emit(EventNames::APP_TEAR_DOWN, $this);
    }

    /**
     * Simple method that writes the passed method the the console and the
     * system logger, if configured and a log level has been passed.
     *
     * @param string $msg      The message to log
     * @param string $logLevel The log level to use
     *
     * @return void
     */
    public function log($msg, $logLevel = null)
    {

        // initialize the formatter helper
        $helper = new FormatterHelper();

        // map the log level to the console style
        $style = $this->mapLogLevelToStyle($logLevel);

        // format the message, according to the passed log level and write it to the console
        $this->getOutput()->writeln($logLevel ? $helper->formatBlock($msg, $style) : $msg);

        // log the message if a log level has been passed
        if ($logLevel && $systemLogger = $this->getSystemLogger()) {
            $systemLogger->log($logLevel, $msg);
        }
    }

    /**
     * Map's the passed log level to a valid symfony console style.
     *
     * @param string $logLevel The log level to map
     *
     * @return string The apropriate symfony console style
     */
    protected function mapLogLevelToStyle($logLevel)
    {

        // query whether or not the log level is mapped
        if (isset($this->logLevelStyleMapping[$logLevel])) {
            return $this->logLevelStyleMapping[$logLevel];
        }

        // return the default style => info
        return Simple::DEFAULT_STYLE;
    }

    /**
     * Return's the PID filename to use.
     *
     * @return string The PID filename
     */
    protected function getPidFilename()
    {
        return $this->getConfiguration()->getPidFilename();
    }
}


<?php
namespace Xety\Cake3Sluggable\Model\Behavior;

use Cake\Event\Event;
use Cake\ORM\Behavior;
use Cake\ORM\Entity;
use Cake\ORM\Query;
use Cake\Utility\Inflector;

class SluggableBehavior extends Behavior
{

    /**
     * Default config.
     *
     * @var array
     */
    protected $_defaultConfig = [
        'field' => 'title',
        'slug' => 'slug',
        'replacement' => '-',
    ];

    /**
     * Slug a field passed in the default config with its replacement.
     *
     * @param \Cake\ORM\Entity $entity The entity that is going to be updated.
     *
     * @return void
     */
    public function slug(Entity $entity)
    {
        $config = $this->config();
        $value = $entity->get($config['field']);
        $entity->set($config['slug'], strtolower(Inflector::slug($value, $config['replacement'])));
    }

    /**
     * BeforeSave handle.
     *
     * @param \Cake\Event\Event  $event  The beforeSave event that was fired.
     * @param \Cake\ORM\Entity $entity The entity that is going to be saved.
     *
     * @return void
     */
    public function beforeSave(Event $event, Entity $entity)
    {
        $this->slug($entity);
    }

    /**
     * Custom finder by slug.
     *
     * @param \Cake\ORM\Query $query The query finder.
     * @param array $options The options passed in the query builder.
     *
     * @return \Cake\ORM\Query
     */
    public function findSlug(Query $query, array $options)
    {
        return $query->where([
            $options['slugField'] => $options['slug']
        ]);
    }
}


<?php

namespace Ekyna\Component\Payum\Sips\Action;

use Payum\Core\Action\GatewayAwareAction;
use Payum\Core\Bridge\Spl\ArrayObject;
use Payum\Core\Exception\RequestNotSupportedException;
use Payum\Core\Exception\RuntimeException;
use Payum\Core\Model\PaymentInterface;
use Payum\Core\Request\Convert;
use Payum\Core\Request\GetCurrency;

/**
 * Class ConvertPaymentAction
 * @package Ekyna\Component\Payum\Sips\Action
 * @author Étienne Dauvergne <contact@ekyna.com>
 */
class ConvertPaymentAction extends GatewayAwareAction
{
    /**
     * {@inheritDoc}
     *
     * @param Convert $request
     */
    public function execute($request)
    {
        RequestNotSupportedException::assertSupports($this, $request);

        /** @var PaymentInterface $payment */
        $payment = $request->getSource();

        $model = ArrayObject::ensureArrayObject($payment->getDetails());

        //$model['DESCRIPTION'] = $payment->getDescription();
        if (false == $model['amount']) {
            $this->gateway->execute($currency = new GetCurrency($payment->getCurrencyCode()));
            if (2 < $currency->exp) {
                throw new RuntimeException('Unexpected currency exp.');
            }
            $divisor = pow(10, 2 - $currency->exp);

            $model['currency_code'] = $currency->numeric;
            $model['amount'] = abs($payment->getTotalAmount() / $divisor);
        }

        if (false == $model['order_id']) {
            $model['order_id'] = $payment->getNumber();
        }

        if (false == $model['customer_id']) {
            $model['customer_id'] = $payment->getClientId();
        }
        if (false == $model['customer_email']) {
            $model['customer_email'] = $payment->getClientEmail();
        }

        $request->setResult((array) $model);
    }

    /**
     * {@inheritDoc}
     */
    public function supports($request)
    {
        return
            $request instanceof Convert &&
            $request->getSource() instanceof PaymentInterface &&
            $request->getTo() == 'array'
        ;
    }
}


<?php
/**
 * Project vn-telco-phonenumber.
 * Created by PhpStorm.
 * User: 713uk13m <dev@nguyenanhung.com>
 * Date: 9/21/18
 * Time: 01:40
 */

namespace nguyenanhung\VnTelcoPhoneNumber;

use \libphonenumber\PhoneNumberUtil;
use \libphonenumber\PhoneNumberToCarrierMapper;
use \libphonenumber\PhoneNumberToTimeZonesMapper;
use \libphonenumber\geocoding\PhoneNumberOfflineGeocoder;
use nguyenanhung\MyDebug\Debug;
use nguyenanhung\MyDebug\Benchmark;
use nguyenanhung\VnTelcoPhoneNumber\Interfaces\PhoneNumberInterface;
use nguyenanhung\VnTelcoPhoneNumber\Interfaces\ProjectInterface;
use nguyenanhung\VnTelcoPhoneNumber\Repository;

/**
 * Class Phone_number
 *
 * @package    nguyenanhung\VnTelcoPhoneNumber
 * @author     713uk13m <dev@nguyenanhung.com>
 * @copyright  713uk13m <dev@nguyenanhung.com>
 */
class Phone_number implements ProjectInterface, PhoneNumberInterface
{
    /** @var object \nguyenanhung\MyDebug\Benchmark */
    private $benchmark;
    /** @var object \nguyenanhung\MyDebug\Debug Class Debug Object */
    private $debug;
    /** @var bool DEBUG Status */
    private $debugStatus = FALSE;
    /** @var null|string Set Debug Level: DEBUG, INFO, ERROR ... etc */
    private $debugLevel = NULL;
    /** @var string Logger Path */
    private $loggerPath = NULL;
    /** @var null Logger Sub Path */
    private $loggerSubPath = NULL;
    /** @var string Filename to write Log */
    private $loggerFilename = NULL;
    /** @var bool Set if Call Name Carrier Mixed */
    protected $normal_name = FALSE;

    /**
     * Phone_number constructor.
     */
    public function __construct()
    {
        if (self::USE_BENCHMARK === TRUE) {
            $this->benchmark = new Benchmark();
            $this->benchmark->mark('code_start');
        }
        $this->debug = new Debug();
        if ($this->debugStatus === TRUE) {
            $this->debug->setDebugStatus($this->debugStatus);
            $this->debug->setGlobalLoggerLevel($this->debugLevel);
            $this->debug->setLoggerPath($this->loggerPath);
            $this->debug->setLoggerSubPath(__CLASS__);
            if (empty($this->loggerFilename)) {
                $this->loggerFilename = 'Log-' . date('Y-m-d') . '.log';
            }
            $this->debug->setLoggerFilename($this->loggerFilename);
        }
    }

    /**
     * Phone_number destructor.
     */
    public function __destruct()
    {
        if (self::USE_BENCHMARK === TRUE) {
            $this->benchmark->mark('code_end');
            $this->debug->debug(__FUNCTION__, 'Elapsed Time: ===> ' . $this->benchmark->elapsed_time('code_start', 'code_end'));
            $this->debug->debug(__FUNCTION__, 'Memory Usage: ===> ' . $this->benchmark->memory_usage());
        }
    }

    /**
     * Get current version of Package
     *
     * @author: 713uk13m <dev@nguyenanhung.com>
     * @time  : 10/22/18 08:42
     *
     * @return mixed|string Current version of Package
     */
    public function getVersion()
    {
        return self::VERSION;
    }

    /**
     * Function setDebugStatus
     * Set Var to DEBUG and save Log
     *
     * @author: 713uk13m <dev@nguyenanhung.com>
     * @time  : 10/9/18 13:47
     *
     * @param bool $debugStatus TRUE if Enable Debug, other if Not
     *
     * @return mixed|void
     */
    public function setDebugStatus($debugStatus = FALSE)
    {
        $this->debugStatus = $debugStatus;
    }

    /**
     * Function setDebugLevel
     * Set String Debug Level
     *
     * @author: 713uk13m <dev@nguyenanhung.com>
     * @time  : 10/9/18 13:47
     *
     * @param bool|string $debugLevel Level to Set Debug: DEBUG, INFO, ERROR, etc...
     *
     * @return mixed|void
     */
    public function setDebugLevel($debugLevel = NULL)
    {
        $this->debugLevel = $debugLevel;
    }

    /**
     * Function setLoggerPath
     * Main Logger Path to Save Log if DEBUG is Enable
     *
     * @author  : 713uk13m <dev@nguyenanhung.com>
     * @time    : 10/9/18 13:51
     *
     * @param bool $loggerPath Set Logger Path to Save
     *
     * @example /your/to/path
     *
     * @return mixed|void
     */
    public function setLoggerPath($loggerPath = FALSE)
    {
        $this->loggerPath = $loggerPath;
    }

    /**
     * Function setLoggerSubPath
     * Sub Logger Path to Save Log if DEBUG is Enable
     *
     * @author  : 713uk13m <dev@nguyenanhung.com>
     * @time    : 10/8/18 14:38
     *
     * @param bool $loggerSubPath Set Logger Sub Path to Save
     *
     * @example /your/to/path
     *
     * @return mixed|void
     */
    public function setLoggerSubPath($loggerSubPath = FALSE)
    {
        $this->loggerSubPath = $loggerSubPath;
    }

    /**
     * Function setLoggerFilename
     * Logger filename to Save Log if DEBUG is Enable
     *
     * @author  : 713uk13m <dev@nguyenanhung.com>
     * @time    : 10/8/18 14:38
     *
     * @param bool $loggerFilename Set Logger Filename to Save
     *
     * @example Log-2018-10-09.log
     *
     * @return mixed|void
     */
    public function setLoggerFilename($loggerFilename = FALSE)
    {
        $this->loggerFilename = $loggerFilename;
    }

    /**
     * Function setNormalName
     * Hàm gọi tới để lấy tên thường của nhà mạng Viêt (Viettel thay cho Viettl Mobile)
     *
     * @author: 713uk13m <dev@nguyenanhung.com>
     * @time  : 9/21/18 01:28
     *
     * @param bool $value Set TRUE if call name from carrier Data Field
     *
     * @return bool
     */
    public function setNormalName($value = FALSE)
    {
        $this->normal_name = $value;
        $this->debug->info(__FUNCTION__, 'setNormalName: ', $this->normal_name);

        return $this->normal_name;
    }

    /**
     * Function Check Phone Number Is Valid?
     *
     * Check số điện thoại nhập vào có phải 1 số điện thoại đúng hay không
     *
     * Returns a boolean whether the supplied PhoneNumber object is valid or not.
     *
     * Important: This doesn't actually validate whether the number is in use.
     * is only able to validate number patterns, and isn't able to check with telecommunication providers.
     *
     * @author: 713uk13m <dev@nguyenanhung.com>
     * @time  : 9/21/18 01:29
     *
     * @param string $phone_number Phone Number Check Valid
     * @param string $region       Region, example VN
     *
     * @see   https://github.com/nguyenanhung/vn-telco-phonenumber/blob/master/test_phone_number.php
     * @see   https://github.com/giggsey/libphonenumber-for-php/blob/master/docs/PhoneNumberUtil.md
     *
     * @return bool|null True if success, False if not, Null if Error
     */
    public function is_valid($phone_number = '', $region = NULL)
    {
        $inputParams = array(
            'phone_number' => $phone_number,
            'region'       => $region
        );
        $this->debug->info(__FUNCTION__, 'Input Params: ', $inputParams);
        if (empty($phone_number)) {
            $this->debug->warning(__FUNCTION__, 'Phone Number input is Empty!');

            return NULL;
        }
        $phone_number = trim($phone_number);
        try {
            $phoneNumberUtil   = PhoneNumberUtil::getInstance();
            $use_region        = NULL !== $region ? strtoupper($region) : self::DEFAULT_REGION;
            $phoneNumberObject = $phoneNumberUtil->parse(trim($phone_number), $use_region);
            $result            = $phoneNumberUtil->isValidNumber($phoneNumberObject);
            $this->debug->debug(__FUNCTION__, 'Use REGION: ' . $use_region);
            $this->debug->info(__FUNCTION__, 'Final Result: ', $result);

            return $result;
        }
        catch (\Exception $e) {
            $message = 'Error File: ' . $e->getFile() . ' - Line: ' . $e->getLine() . ' - Code: ' . $e->getCode() . ' - Message: ' . $e->getMessage();
            $this->debug->error(__FUNCTION__, $message);

            return NULL;
        }

    }

    /**
     * Function Check Phone Number Is Possible?
     *
     * Returns a boolean whether the supplied phone number is possible or not.
     * This function accepts either a PhoneNumber object, or a phone number string and a region code (as with parse()).
     *
     * Hàm check số chuỗi nhập vào có phải 1 số điện thoại hay không
     *
     * @author: 713uk13m <dev@nguyenanhung.com>
     * @time  : 9/21/18 01:29
     *
     * @param string $phone_number Phone Number Check Possible
     * @param string $region       Region, example VN
     *
     * @see   https://github.com/nguyenanhung/vn-telco-phonenumber/blob/master/test_phone_number.php
     * @see   https://github.com/giggsey/libphonenumber-for-php/blob/master/docs/PhoneNumberUtil.md
     *
     * @return bool|null True if success, False if not, Null if Error
     */
    public function is_possible_number($phone_number = '', $region = NULL)
    {
        $inputParams = array(
            'phone_number' => $phone_number,
            'region'       => $region
        );
        $this->debug->info(__FUNCTION__, 'Input Params: ', $inputParams);
        if (empty($phone_number)) {
            $this->debug->warning(__FUNCTION__, 'Phone Number input is Empty!');

            return NULL;
        }
        $phone_number = trim($phone_number);
        try {
            $phoneNumberUtil   = PhoneNumberUtil::getInstance();
            $use_region        = NULL !== $region ? strtoupper($region) : self::DEFAULT_REGION;
            $phoneNumberObject = $phoneNumberUtil->parse(trim($phone_number), $use_region);
            $result            = $phoneNumberUtil->isPossibleNumber($phoneNumberObject);
            $this->debug->debug(__FUNCTION__, 'Use REGION: ' . $use_region);
            $this->debug->info(__FUNCTION__, 'Final Result: ', $result);

            return $result;
        }
        catch (\Exception $e) {
            $message = 'Error File: ' . $e->getFile() . ' - Line: ' . $e->getLine() . ' - Code: ' . $e->getCode() . ' - Message: ' . $e->getMessage();
            $this->debug->error(__FUNCTION__, $message);

            return NULL;
        }
    }

    /**
     * Function get_time_zones_for_number
     *
     * Returns an array of timezones for which the PhoneNumber object supplied belongs in.
     *
     * @author: 713uk13m <dev@nguyenanhung.com>
     * @time  : 9/21/18 01:50
     *
     * @param string $phone_number Phone Number to get Timezone
     * @param string $region       Region, example VN
     *
     * @see   https://github.com/nguyenanhung/vn-telco-phonenumber/blob/master/test_phone_number.php
     * @see   https://github.com/giggsey/libphonenumber-for-php/blob/master/docs/PhoneNumberUtil.md
     *
     * @return array|null Array if Success, Null if Not
     */
    public function get_time_zones_for_number($phone_number = '', $region = NULL)
    {
        $inputParams = array(
            'phone_number' => $phone_number,
            'region'       => $region
        );
        $this->debug->info(__FUNCTION__, 'Input Params: ', $inputParams);
        if (empty($phone_number)) {
            $this->debug->warning(__FUNCTION__, 'Phone Number input is Empty!');

            return NULL;
        }
        $phone_number = trim($phone_number);
        try {
            $phoneNumberUtil   = PhoneNumberUtil::getInstance();
            $use_region        = NULL !== $region ? strtoupper($region) : self::DEFAULT_REGION;
            $phoneNumberObject = $phoneNumberUtil->parse(trim($phone_number), $use_region);
            $timezoneMapper    = PhoneNumberToTimeZonesMapper::getInstance();
            $result            = $timezoneMapper->getTimeZonesForNumber($phoneNumberObject);
            $this->debug->debug(__FUNCTION__, 'Use REGION: ' . $use_region);
            $this->debug->info(__FUNCTION__, 'Final Result: ', $result);

            return $result;
        }
        catch (\Exception $e) {
            $message = 'Error File: ' . $e->getFile() . ' - Line: ' . $e->getLine() . ' - Code: ' . $e->getCode() . ' - Message: ' . $e->getMessage();
            $this->debug->error(__FUNCTION__, $message);

            return NULL;
        }
    }

    /**
     * Function get_carrier_name_for_number
     *
     * Returns the name of the carrier for the supplied PhoneNumber object within the $language supplied.
     *
     * Returns the same as getNameForNumber() without checking whether it is a valid number for carrier mapping.
     *
     * Returns the same as getNameForNumber(), but only if the number is safe for carrier mapping.
     * A number is only validate for carrier mapping if it's a Mobile or Fixed line,
     * and the country does not support Mobile Number Portability.
     *
     * @author: 713uk13m <dev@nguyenanhung.com>
     * @time  : 9/21/18 01:57
     *
     * @param string $phone_number Phone Number to get Carrier Name
     * @param string $region       Region, example VN
     * @param string $mode         Mode String: safe, valid, other...
     *
     * @see   https://github.com/nguyenanhung/vn-telco-phonenumber/blob/master/test_phone_number.php
     * @see   https://github.com/giggsey/libphonenumber-for-php/blob/master/docs/PhoneNumberUtil.md
     *
     * @return mixed|null|string String if Success, Null if Not
     */
    public function get_carrier_name_for_number($phone_number = '', $region = NULL, $mode = NULL)
    {
        $inputParams = array(
            'phone_number' => $phone_number,
            'region'       => $region,
            'mode'         => $mode
        );
        $this->debug->info(__FUNCTION__, 'Input Params: ', $inputParams);
        if (empty($phone_number)) {
            $this->debug->warning(__FUNCTION__, 'Phone Number input is Empty!');

            return NULL;
        }
        $phone_number = trim($phone_number);
        try {
            $phoneNumberUtil   = PhoneNumberUtil::getInstance();
            $use_region        = NULL !== $region ? strtoupper($region) : self::DEFAULT_REGION;
            $phoneNumberObject = $phoneNumberUtil->parse(trim($phone_number), $use_region);
            $carrierMapper     = PhoneNumberToCarrierMapper::getInstance();
            if (strtolower($mode) == 'safe') {
                $result = $carrierMapper->getSafeDisplayName($phoneNumberObject, self::DEFAULT_LANGUAGE);
            } elseif (strtolower($mode) == 'valid') {
                $result = $carrierMapper->getNameForValidNumber($phoneNumberObject, self::DEFAULT_LANGUAGE);
            } else {
                $result = $carrierMapper->getNameForNumber($phoneNumberObject, self::DEFAULT_LANGUAGE);
            }
            $this->debug->debug(__FUNCTION__, 'Use REGION: ' . $use_region);
            $this->debug->info(__FUNCTION__, 'Final Result: ', $result);

            return $result;
        }
        catch (\Exception $e) {
            $message = 'Error File: ' . $e->getFile() . ' - Line: ' . $e->getLine() . ' - Code: ' . $e->getCode() . ' - Message: ' . $e->getMessage();
            $this->debug->error(__FUNCTION__, $message);

            return NULL;
        }
    }

    /**
     * Function get_geocode_description_for_number
     *
     * Returns a text description for the supplied PhoneNumber object, in the $locale language supplied.
     * The description returned might consist of the name of the country, or the name of the geographical area the
     * phone number is from. If $userRegion is supplied, it will also be taken into consideration. If the phone number
     * is from the same region, only a lower-level description will be returned.
     *
     * @author: 713uk13m <dev@nguyenanhung.com>
     * @time  : 10/8/18 16:11
     *
     * @param string $phone_number Phone Number to get Geo Code Description
     * @param string $region       Region, example VN
     * @param string $mode         Valid String (if $mode=valid -> Returns the same as getDescriptionForNumber(),
     *                             but assumes that you have already checked whether the number is suitable for geo
     *                             location.)
     *
     * @see   https://github.com/nguyenanhung/vn-telco-phonenumber/blob/master/test_phone_number.php
     * @see   https://github.com/giggsey/libphonenumber-for-php/blob/master/docs/PhoneNumberUtil.md
     *
     * @return mixed|null|string String if Success, Null if Not
     */
    public function get_geocode_description_for_number($phone_number = '', $region = NULL, $mode = '')
    {
        $inputParams = array(
            'phone_number' => $phone_number,
            'region'       => $region,
            'mode'         => $mode
        );
        $this->debug->info(__FUNCTION__, 'Input Params: ', $inputParams);
        if (empty($phone_number)) {
            $this->debug->warning(__FUNCTION__, 'Phone Number input is Empty!');

            return NULL;
        }
        $phone_number = trim($phone_number);
        try {
            $phoneNumberUtil   = PhoneNumberUtil::getInstance();
            $use_region        = NULL !== $region ? strtoupper($region) : self::DEFAULT_REGION;
            $phoneNumberObject = $phoneNumberUtil->parse(trim($phone_number), $use_region);
            $geoCoder          = PhoneNumberOfflineGeocoder::getInstance();
            if (strtolower($mode) == 'valid') {
                $result = $geoCoder->getDescriptionForValidNumber($phoneNumberObject, self::DEFAULT_LANGUAGE, $use_region);
            } else {
                $result = $geoCoder->getDescriptionForNumber($phoneNumberObject, self::DEFAULT_LANGUAGE, $use_region);
            }
            $this->debug->debug(__FUNCTION__, 'Use REGION: ' . $use_region);
            $this->debug->info(__FUNCTION__, 'Final Result: ', $result);

            return $result;
        }
        catch (\Exception $e) {
            $message = 'Error File: ' . $e->getFile() . ' - Line: ' . $e->getLine() . ' - Code: ' . $e->getCode() . ' - Message: ' . $e->getMessage();
            $this->debug->error(__FUNCTION__, $message);

            return NULL;
        }
    }

    /**
     * Function get_region_code_for_number
     * Returns the region code for the PhoneNumber object you pass.
     *
     * @author: 713uk13m <dev@nguyenanhung.com>
     * @time  : 10/8/18 16:11
     *
     * @param string $phone_number Phone Number to get Region Code
     * @param string $region       Region, example VN
     *
     * @see   https://github.com/nguyenanhung/vn-telco-phonenumber/blob/master/test_phone_number.php
     * @see   https://github.com/giggsey/libphonenumber-for-php/blob/master/docs/PhoneNumberUtil.md
     *
     * @return mixed|null|string String if Success, Null if Not
     */
    public function get_region_code_for_number($phone_number = '', $region = '')
    {
        $inputParams = array(
            'phone_number' => $phone_number,
            'region'       => $region
        );
        $this->debug->info(__FUNCTION__, 'Input Params: ', $inputParams);
        if (empty($phone_number)) {
            $this->debug->warning(__FUNCTION__, 'Phone Number input is Empty!');

            return NULL;
        }
        $phone_number = trim($phone_number);
        try {
            $use_region        = NULL !== $region ? strtoupper($region) : self::DEFAULT_REGION;
            $phoneNumberUtil   = PhoneNumberUtil::getInstance();
            $phoneNumberObject = $phoneNumberUtil->parse(trim($phone_number), $use_region);
            $result            = $phoneNumberUtil->getRegionCodeForNumber($phoneNumberObject);
            $this->debug->debug(__FUNCTION__, 'Use REGION: ' . $use_region);
            $this->debug->info(__FUNCTION__, 'Final Result: ' . $result);

            return $result;
        }
        catch (\Exception $e) {
            $message = 'Error File: ' . $e->getFile() . ' - Line: ' . $e->getLine() . ' - Code: ' . $e->getCode() . ' - Message: ' . $e->getMessage();
            $this->debug->error(__FUNCTION__, $message);

            return NULL;
        }
    }

    /**
     * Function get_country_code_for_region
     * Returns the country calling code for a specific $region.
     *
     * @author: 713uk13m <dev@nguyenanhung.com>
     * @time  : 9/21/18 02:16
     *
     * @param null $region example VN
     *
     * @see   https://github.com/nguyenanhung/vn-telco-phonenumber/blob/master/test_phone_number.php
     * @see   https://github.com/giggsey/libphonenumber-for-php/blob/master/docs/PhoneNumberUtil.md
     *
     * @return int|mixed|null Int if Success, Null if Not
     */
    public function get_country_code_for_region($region = NULL)
    {
        $inputParams = array('region' => $region);
        $this->debug->info(__FUNCTION__, 'Input Params: ', $inputParams);
        try {
            $phoneNumberUtil = PhoneNumberUtil::getInstance();
            $use_region      = NULL !== $region ? strtoupper($region) : self::DEFAULT_REGION;
            $result          = $phoneNumberUtil->getCountryCodeForRegion($use_region);
            $this->debug->debug(__FUNCTION__, 'Use REGION: ' . $use_region);
            $this->debug->info(__FUNCTION__, 'Final Result: ', $result);

            return $result;
        }
        catch (\Exception $e) {
            $message = 'Error File: ' . $e->getFile() . ' - Line: ' . $e->getLine() . ' - Code: ' . $e->getCode() . ' - Message: ' . $e->getMessage();
            $this->debug->error(__FUNCTION__, $message);

            return NULL;
        }
    }

    /**
     * Function get_region_codes_for_country_code
     * Returns a list of region codes that match the $region_codes.
     * For a non-geographical country calling codes, the region code 001 is returned.
     *
     * @author: 713uk13m <dev@nguyenanhung.com>
     * @time  : 9/21/18 02:18
     *
     * @param null $region_codes example 84
     *
     * @see   https://github.com/nguyenanhung/vn-telco-phonenumber/blob/master/test_phone_number.php
     * @see   https://github.com/giggsey/libphonenumber-for-php/blob/master/docs/PhoneNumberUtil.md
     *
     * @return array|mixed|null Array if success, null if not
     */
    public function get_region_codes_for_country_code($region_codes = NULL)
    {
        $inputParams = array('region_codes' => $region_codes);
        $this->debug->info(__FUNCTION__, 'Input Params: ', $inputParams);
        try {
            $phoneNumberUtil = PhoneNumberUtil::getInstance();
            $use_region_code = NULL !== $region_codes ? trim($region_codes) : self::DEFAULT_REGION_CODE;
            $result          = $phoneNumberUtil->getRegionCodesForCountryCode($use_region_code);
            $this->debug->debug(__FUNCTION__, 'Use REGION Code: ' . $use_region_code);
            $this->debug->info(__FUNCTION__, 'Final Result: ', $result);

            return $result;
        }
        catch (\Exception $e) {
            $message = 'Error File: ' . $e->getFile() . ' - Line: ' . $e->getLine() . ' - Code: ' . $e->getCode() . ' - Message: ' . $e->getMessage();
            $this->debug->error(__FUNCTION__, $message);

            return NULL;
        }
    }

    /**
     * Function get_number_type
     * Returns a PhoneNumberType constant for the PhoneNumber object you pass.
     *
     * @author: 713uk13m <dev@nguyenanhung.com>
     * @time  : 9/21/18 01:46
     *
     * @param string $phone_number Phone Number to Get Type
     * @param null   $region       Region, example VN
     *
     * @see   https://github.com/nguyenanhung/vn-telco-phonenumber/blob/master/test_phone_number.php
     * @see   https://github.com/giggsey/libphonenumber-for-php/blob/master/docs/PhoneNumberUtil.md
     *
     * @return int|mixed|null Int if Success, Null if Not
     */
    public function get_number_type($phone_number = '', $region = NULL)
    {
        $inputParams = array(
            'phone_number' => $phone_number,
            'region'       => $region
        );
        $this->debug->info(__FUNCTION__, 'Input Params: ', $inputParams);
        if (empty($phone_number)) {
            $this->debug->warning(__FUNCTION__, 'Phone Number input is Empty!');

            return NULL;
        }
        $phone_number = trim($phone_number);
        try {
            $phoneNumberUtil   = PhoneNumberUtil::getInstance();
            $use_region        = NULL !== $region ? strtoupper($region) : self::DEFAULT_REGION;
            $phoneNumberObject = $phoneNumberUtil->parse(trim($phone_number), $use_region);
            $result            = $phoneNumberUtil->getNumberType($phoneNumberObject);
            $this->debug->debug(__FUNCTION__, 'Use REGION: ' . $use_region);
            $this->debug->info(__FUNCTION__, 'Final Result: ' . $result);

            return $result;
        }
        catch (\Exception $e) {
            $message = 'Error File: ' . $e->getFile() . ' - Line: ' . $e->getLine() . ' - Code: ' . $e->getCode() . ' - Message: ' . $e->getMessage();
            $this->debug->error(__FUNCTION__, $message);

            return NULL;
        }
    }

    /**
     * Function check_phone_number_can_be_internationally_dialled
     * Returns a boolean whether the supplied PhoneNumber object can be dialled internationally.
     *
     * @author: 713uk13m <dev@nguyenanhung.com>
     * @time  : 9/21/18 02:12
     *
     * @param string $phone_number Phone Number to Check
     * @param null   $region       Region, example VN
     *
     * @see   https://github.com/nguyenanhung/vn-telco-phonenumber/blob/master/test_phone_number.php
     * @see   https://github.com/giggsey/libphonenumber-for-php/blob/master/docs/PhoneNumberUtil.md
     *
     * @return bool|null Bool if Success, Null if Error
     */
    public function check_phone_number_can_be_internationally_dialled($phone_number = '', $region = NULL)
    {
        $inputParams = array(
            'phone_number' => $phone_number,
            'region'       => $region
        );
        $this->debug->info(__FUNCTION__, 'Input Params: ', $inputParams);
        if (empty($phone_number)) {
            $this->debug->warning(__FUNCTION__, 'Phone Number input is Empty!');

            return NULL;
        }
        $phone_number = trim($phone_number);
        try {
            $phoneNumberUtil   = PhoneNumberUtil::getInstance();
            $use_region        = NULL !== $region ? strtoupper($region) : NULL;
            $phoneNumberObject = $phoneNumberUtil->parse(trim($phone_number), $use_region);
            $result            = $phoneNumberUtil->canBeInternationallyDialled($phoneNumberObject);
            $this->debug->debug(__FUNCTION__, 'Use REGION: ' . $use_region);
            $this->debug->info(__FUNCTION__, 'Final Result: ' . $result);

            return $result;
        }
        catch (\Exception $e) {
            $message = 'Error File: ' . $e->getFile() . ' - Line: ' . $e->getLine() . ' - Code: ' . $e->getCode() . ' - Message: ' . $e->getMessage();
            $this->debug->error(__FUNCTION__, $message);

            return NULL;
        }
    }

    /**
     * Function find_phone_number_in_string
     * Returns an instance of PhoneNumberMatcher, which can be iterated over (returning PhoneNumberMatch objects).
     * It searches the input $text for phone numbers, using the $defaultRegion.
     * There are also optional parameters to set the phone number $leniency (look in Leniency for possible values),
     * and the $maxTries to search for the phone number.
     *
     * @author: 713uk13m <dev@nguyenanhung.com>
     * @time  : 10/9/18 14:51
     *
     * @param string $text   Text String to Find
     * @param null   $region Region, example VN
     *
     * @see   https://github.com/nguyenanhung/vn-telco-phonenumber/blob/master/test_phone_number.php
     * @see   https://github.com/giggsey/libphonenumber-for-php/blob/master/docs/PhoneNumberUtil.md
     *
     * @return array|mixed|null Array if Success, Null if not or Error (Set DEBUG to Details)
     */
    public function find_phone_number_in_string($text = '', $region = NULL)
    {
        $inputParams = array(
            'text'   => $text,
            'region' => $region
        );
        $this->debug->info(__FUNCTION__, 'Input Params: ', $inputParams);
        if (empty($text)) {
            $this->debug->warning(__FUNCTION__, 'Text input is Empty!');

            return NULL;
        }
        try {
            $phoneNumberUtil    = PhoneNumberUtil::getInstance();
            $use_region         = NULL !== $region ? strtoupper($region) : self::DEFAULT_REGION;
            $phoneNumberMatcher = $phoneNumberUtil->findNumbers($text, $use_region);
            $result             = array();
            foreach ($phoneNumberMatcher as $phoneNumberMatch) {
                $result['number'][] = $phoneNumberMatch->number();
            }
            $this->debug->info(__FUNCTION__, 'Final Result: ', $result);

            return $result;
        }
        catch (\Exception $e) {
            $message = 'Error File: ' . $e->getFile() . ' - Line: ' . $e->getLine() . ' - Code: ' . $e->getCode() . ' - Message: ' . $e->getMessage();
            $this->debug->error(__FUNCTION__, $message);

            return NULL;
        }
    }

    /**
     * Function Format
     * Format Phone Number with Format Style
     * Hàm cho phép Format số điện thoai theo format stype truyền vào
     *
     * @author   : 713uk13m <dev@nguyenanhung.com>
     * @time     : 9/21/18 01:30
     *
     * @param string $phone_number Input Phone Number
     * @param string $format       List command:
     *                             VN, VN_HUMAN,
     *                             E164,INTERNATIONAL, NATIONAL, RFC3966,
     *                             HIDDEN, HIDDEN_HEAD, HIDDEN_MIDDLE, HIDDEN_END
     *
     * @example  phone_number = 0163 295 3760, format = Null or Invalid of List command => Output: 841632953760
     * @example  phone_number = 0163 295 3760, format = VN => Output: 01632953760
     * @example  phone_number = 0163 295 3760, format = VN_HUMAN => Output: 0163 295 3760
     * @example  phone_number = 0163 295 3760, format = NATIONAL => Output: 0163 295 3760
     * @example  phone_number = 0163 295 3760, format = INTERNATIONAL => Output: +84 163 295 3760
     * @example  phone_number = 0163 295 3760, format = E164 => Output: +841632953760
     * @example  phone_number = 0163 295 3760, format = RFC3966 => Output: tel:+84-163-295-3760
     * @example  phone_number = 0163 295 3760, format = HIDDEN => Output: 0163 *** 3760
     * @example  phone_number = 0163 295 3760, format = HIDDEN_HEAD => Output: **** 295 3760
     * @example  phone_number = 0163 295 3760, format = HIDDEN_MIDDLE => Output: 0163 *** 3760
     * @example  phone_number = 0163 295 3760, format = HIDDEN_END => Output: 0163 295 ****
     *
     * @see      https://github.com/nguyenanhung/vn-telco-phonenumber/blob/master/test_phone_number.php
     * @see      https://github.com/giggsey/libphonenumber-for-php/blob/master/docs/PhoneNumberUtil.md
     *
     * @return null|string String if Success, Null if Error, Raw phone input if Exception
     */
    public function format($phone_number = '', $format = '')
    {
        $inputParams = array(
            'phone_number' => $phone_number,
            'format'       => $format
        );
        $this->debug->info(__FUNCTION__, 'Input Params: ', $inputParams);
        if (empty($phone_number)) {
            $this->debug->warning(__FUNCTION__, 'Phone Number input is Empty!');

            return NULL;
        }
        $phone_number = trim($phone_number);
        $format       = strtoupper(trim($format));
        $hidden_list  = array(
            self::HIDDEN_REGION,
            self::HIDDEN_REGION_HEAD,
            self::HIDDEN_REGION_MIDDLE,
            self::HIDDEN_REGION_END
        );
        $this->debug->warning(__FUNCTION__, 'Hidden List Allowed: ', $hidden_list);
        try {
            $phoneNumberUtil   = PhoneNumberUtil::getInstance();
            $phoneNumberObject = $phoneNumberUtil->parse(trim($phone_number), self::DEFAULT_REGION);
            if ($format == self::DEFAULT_REGION) {
                $result = (string) '0' . $phoneNumberObject->getNationalNumber();
            } elseif (in_array($format, $hidden_list)) {
                $result = (string) $this->format_hidden($phone_number, $format);
            } elseif ($format == self::FORMAT_FOR_HUMAN_VIETNAM) {
                $result = (string) $phoneNumberUtil->formatOutOfCountryCallingNumber($phoneNumberObject, self::DEFAULT_COUNTRY);
            } elseif ($format == self::FORMAT_E164) {
                $result = (string) $phoneNumberUtil->format($phoneNumberObject, \libphonenumber\PhoneNumberFormat::E164);
            } elseif ($format == self::FORMAT_INTERNATIONAL) {
                $result = (string) $phoneNumberUtil->format($phoneNumberObject, \libphonenumber\PhoneNumberFormat::INTERNATIONAL);
            } elseif ($format == self::FORMAT_NATIONAL) {
                $result = (string) $phoneNumberUtil->format($phoneNumberObject, \libphonenumber\PhoneNumberFormat::NATIONAL);
            } elseif ($format == self::FORMAT_RFC3966) {
                $result = (string) $phoneNumberUtil->format($phoneNumberObject, \libphonenumber\PhoneNumberFormat::RFC3966);
            } else {
                $result = (string) $phoneNumberObject->getCountryCode() . $phoneNumberObject->getNationalNumber();
            }
            $this->debug->debug(__FUNCTION__, 'Format Result: ' . $result . ' with Format: ' . $format);
            $this->debug->info(__FUNCTION__, 'Final Result: ' . $result);

            return $result;
        }
        catch (\Exception $e) {
            $message = 'Error File: ' . $e->getFile() . ' - Line: ' . $e->getLine() . ' - Code: ' . $e->getCode() . ' - Message: ' . $e->getMessage();
            $this->debug->error(__FUNCTION__, $message);

            return $phone_number;
        }
    }

    /**
     * Function format_hidden
     * Format Hidden Phone number
     * Hàm cho phép format và ẩn đi 1 cụm số theo vị trí,
     * mỗi 1 số điện thoại sẽ chia ra 3 cụm, HEAD, MIDDLE và END
     *
     * @author  : 713uk13m <dev@nguyenanhung.com>
     * @time    : 10/9/18 14:41
     *
     * @param string $phone_number Input Phone Number
     * @param string $place_hidden Place Hidden: HEAD, MIDDLE or END
     *
     * @example place_hidden = HEAD => **** 123 456
     * @example place_hidden = MIDDLE => 0163 *** 456
     * @example place_hidden = END => 0163 123 ***
     *
     * @see     https://github.com/nguyenanhung/vn-telco-phonenumber/blob/master/test_phone_number.php
     *
     * @return mixed|null|string String if Success, Null if Error, Raw phone input if Exception
     */
    public function format_hidden($phone_number = '', $place_hidden = '')
    {
        $inputParams = array(
            'phone_number' => $phone_number,
            'place_hidden' => $place_hidden
        );
        $this->debug->info(__FUNCTION__, 'Input Params: ', $inputParams);
        if (empty($phone_number)) {
            $this->debug->warning(__FUNCTION__, 'Phone Number input is Empty!');

            return NULL;
        }
        $phone_number = trim($phone_number);
        $place_hidden = strtoupper($place_hidden);
        try {
            $phoneNumberUtil     = PhoneNumberUtil::getInstance();
            $phoneNumberObject   = $phoneNumberUtil->parse(trim($phone_number), self::DEFAULT_REGION);
            $phoneNumberVnFormat = $phoneNumberUtil->formatOutOfCountryCallingNumber($phoneNumberObject, "VN");
            /**
             * Phone Number: 0163 123 456
             * $place_hidden = HEAD => **** 123 456
             * $place_hidden = MIDDLE => 0163 *** 456
             * $place_hidden = END => 0163 123 ***
             */
            $exPhone = explode(' ', $phoneNumberVnFormat);
            if (count($exPhone) > 1) {
                if ($place_hidden == self::HIDDEN_PLACE_HEAD) {
                    $result = trim(str_repeat(self::HIDDEN_STRING, strlen($exPhone[0]))) . trim($exPhone[1]) . trim($exPhone[2]);
                    $this->debug->debug(__FUNCTION__, 'Place Hidden is: ' . self::HIDDEN_PLACE_HEAD);
                    $this->debug->debug(__FUNCTION__, 'Result Hidden: ', $result);
                } elseif ($place_hidden == self::HIDDEN_PLACE_MIDDLE) {
                    $result = trim($exPhone[0]) . trim(str_repeat(self::HIDDEN_STRING, strlen($exPhone[1]))) . trim($exPhone[2]);
                    $this->debug->debug(__FUNCTION__, 'Place Hidden is: ' . self::HIDDEN_PLACE_MIDDLE);
                    $this->debug->debug(__FUNCTION__, 'Result Hidden: ', $result);
                } elseif ($place_hidden == self::HIDDEN_PLACE_END) {
                    $result = trim($exPhone[0]) . trim($exPhone[4]) . trim(str_repeat(self::HIDDEN_STRING, strlen($exPhone[2])));
                    $this->debug->debug(__FUNCTION__, 'Place Hidden is: ' . self::HIDDEN_PLACE_END);
                    $this->debug->debug(__FUNCTION__, 'Result Hidden: ', $result);
                } else {
                    $result = trim($exPhone[0]) . trim(str_repeat(self::HIDDEN_STRING, strlen($exPhone[1]))) . trim($exPhone[2]);
                    $this->debug->debug(__FUNCTION__, 'Place Hidden is: ' . $place_hidden);
                    $this->debug->debug(__FUNCTION__, 'Result Hidden: ', $result);
                }
            } else {
                $result = $phoneNumberVnFormat;
                $this->debug->debug(__FUNCTION__, 'Unavailable Hidden for ' . $place_hidden . ' with Phone Number: ' . $phone_number);
                $this->debug->debug(__FUNCTION__, 'Result Hidden: ', $result);
            }
            $this->debug->info(__FUNCTION__, 'Final Result: ', $result);

            return $result;
        }
        catch (\Exception $e) {
            $message = 'Error File: ' . $e->getFile() . ' - Line: ' . $e->getLine() . ' - Code: ' . $e->getCode() . ' - Message: ' . $e->getMessage();
            $this->debug->error(__FUNCTION__, $message);

            return $phone_number;
        }
    }

    /**
     * Function detect_carrier
     * Detect Carrier from Phone Number
     * Nhận diện nhà mạng từ số điện thoại nhập vào
     *
     * @author: 713uk13m <dev@nguyenanhung.com>
     * @time  : 10/9/18 14:37
     *
     * @param string $phone_number   This is Phone Number to be Detect
     * @param null   $get_field_data Get File Data, keyword: name, short_name, id
     *
     * @return mixed|null|string String if Success, Null if Errro
     */
    public function detect_carrier($phone_number = '', $get_field_data = NULL)
    {
        $inputParams = array(
            'phone_number'   => $phone_number,
            'get_field_data' => $get_field_data
        );
        $this->debug->info(__FUNCTION__, 'Input Params: ', $inputParams);
        if (empty($phone_number)) {
            $this->debug->warning(__FUNCTION__, 'Phone Number input is Empty!');

            return NULL;
        }
        try {
            $carrierMapper     = PhoneNumberToCarrierMapper::getInstance();
            $phoneNumberObject = PhoneNumberUtil::getInstance()->parse(trim($phone_number), self::DEFAULT_REGION);
            $carrier           = $carrierMapper->getNameForNumber($phoneNumberObject, self::DEFAULT_LANGUAGE);
            $this->debug->debug(__FUNCTION__, 'Carrier Detect from ' . $phone_number . ' is ', $carrier);
            if ($get_field_data !== NULL) {
                $phone_telco = new Phone_telco();
                $phone_telco->setDebugStatus($this->debugStatus);
                $phone_telco->setLoggerPath($this->loggerPath);
                $phone_telco->__construct();
                $result = $phone_telco->carrier_data($carrier, $get_field_data);
                $this->debug->info(__FUNCTION__, 'Final Result: ', $result);

                return (string) $result;
            }
            if ($this->normal_name === TRUE) {
                $phone_telco = new Phone_telco();
                $phone_telco->setDebugStatus($this->debugStatus);
                $phone_telco->setLoggerPath($this->loggerPath);
                $phone_telco->__construct();
                $result = $phone_telco->carrier_data($carrier, 'name');
                if ($result !== NULL) {
                    return $result;
                }
                $this->debug->info(__FUNCTION__, 'Final Result: ', $result);
            }
            $this->debug->info(__FUNCTION__, 'Final Result: ', $carrier);

            return (string) $carrier;
        }
        catch (\Exception $e) {
            $message = 'Error File: ' . $e->getFile() . ' - Line: ' . $e->getLine() . ' - Code: ' . $e->getCode() . ' - Message: ' . $e->getMessage();
            $this->debug->error(__FUNCTION__, $message);

            return NULL;
        }
    }

    /**
     * Function vn_convert_phone_number
     * Convert Phone Number old to new or new to old
     * Chuyển đổi Số điện thoại từ định dạng mới sang cũ hoặc ngược lại
     *
     * @author: 713uk13m <dev@nguyenanhung.com>
     * @time  : 9/21/18 01:32
     *
     * @param string $phone_number This is Phone number
     * @param string $phone_mode   This mode as old or new
     * @param null   $phone_format This format vn or other, list keyword: VN, VN_HUMAN, E164, INTERNATIONAL, NATIONAL,
     *                             RFC3966, HIDDEN, HIDDEN_HEAD, HIDDEN_MIDDLE, HIDDEN_END
     *
     * @see   https://github.com/nguyenanhung/vn-telco-phonenumber/blob/master/test_phone_number.php
     *
     * @return null|string
     */
    public function vn_convert_phone_number($phone_number = '', $phone_mode = '', $phone_format = NULL)
    {
        $inputParams = array(
            'phone_number' => $phone_number,
            'phone_mode'   => $phone_mode,
            'phone_format' => $phone_format
        );
        $this->debug->info(__FUNCTION__, 'Input Params: ', $inputParams);
        if (empty($phone_number)) {
            $this->debug->warning(__FUNCTION__, 'Phone Number input is Empty!');

            return NULL;
        }
        $mode = strtolower($phone_mode); // old || new
        // Convert Phone Number to CountryCode + NationalNumber
        $phone_number = $this->format(trim($phone_number));
        try {
            // Data Convert Phone Number
            $dataVnConvertPhoneNumber = Repository\DataRepository::getData('vn_convert_phone_number');
            $this->debug->debug(__FUNCTION__, 'Data Convert Phone Number: ', $dataVnConvertPhoneNumber);
            if (is_array($dataVnConvertPhoneNumber) && count($dataVnConvertPhoneNumber) > 0) {
                if ($mode == self::CONVERT_NEW_TO_OLD) {
                    $preg_match_number = self::MATCH_NUMBER_NEW;
                    $this->debug->debug(__FUNCTION__, 'Mode is New to Old with Preg Match: ', $preg_match_number);
                } elseif ($mode == self::CONVERT_OLD_TO_NEW) {
                    $preg_match_number = self::MATCH_NUMBER_OLD;
                    $this->debug->debug(__FUNCTION__, 'Mode is Old to New with Preg Match: ', $preg_match_number);
                } else {
                    $preg_match_number = NULL;
                }
                if ($preg_match_number !== NULL) {
                    if (!preg_match($preg_match_number, $phone_number)) {
                        $result = $this->format($phone_number, $phone_format);
                        $this->debug->warning(__FUNCTION__, 'Phone Number: ' . $phone_number . ' Invalid with Rule: ' . $preg_match_number . ' -> Output Result: ' . $result);

                        return $result;
                    }
                    foreach ($dataVnConvertPhoneNumber as $old_number_prefix => $new_number_prefix) {
                        if ($mode == self::CONVERT_NEW_TO_OLD) {
                            $phone_number_content = self::MAX_LENGTH_NUMBER_NEW - strlen($new_number_prefix); // 84 + number content
                            $phone_number_prefix  = $new_number_prefix;
                            $convert_prefix       = $old_number_prefix;
                            $this->debug->debug(__FUNCTION__, 'Data Convert New to Old: ', array(
                                'phone_number_content' => $phone_number_content,
                                'phone_number_prefix'  => $phone_number_prefix,
                                'convert_prefix'       => $convert_prefix
                            ));
                        } elseif ($mode == self::CONVERT_OLD_TO_NEW) {
                            $phone_number_content = self::MAX_LENGTH_NUMBER_OLD - strlen($old_number_prefix); // 84 + number content
                            $phone_number_prefix  = $old_number_prefix;
                            $convert_prefix       = $new_number_prefix;
                            $this->debug->debug(__FUNCTION__, 'Data Convert Old to New: ', array(
                                'phone_number_content' => $phone_number_content,
                                'phone_number_prefix'  => $phone_number_prefix,
                                'convert_prefix'       => $convert_prefix
                            ));
                        } else {
                            $phone_number_content = NULL;
                            $phone_number_prefix  = NULL;
                            $convert_prefix       = NULL;
                            $this->debug->debug(__FUNCTION__, 'Phone Number Invalid Rule Convert: ', array(
                                'phone_number_content' => $phone_number_content,
                                'phone_number_prefix'  => $phone_number_prefix,
                                'convert_prefix'       => $convert_prefix
                            ));
                        }
                        // Rule to check
                        $ruleCheckConvert = '/^(' . $phone_number_prefix . ')[0-9]{' . $phone_number_content . '}$/';
                        $this->debug->debug(__FUNCTION__, 'Rule preg_match Check to Convert Number: ' . $phone_number . ' is ', $ruleCheckConvert);
                        if (($phone_number_content !== NULL && $phone_number_prefix !== NULL && $convert_prefix !== NULL) && preg_match($ruleCheckConvert, $phone_number)) {
                            // Cắt lấy các số cuối tính từ vị trí đầu tiên trong dãy $phone_number rồi nối đầu số $convert_prefix
                            $phone_number = $convert_prefix . substr($phone_number, strlen($phone_number_prefix), $phone_number_content);
                            $this->debug->debug(__FUNCTION__, 'Rule Check OK -> Phone Number Convert: ', $phone_number);
                            $phone_number = $this->format($phone_number, $phone_format);
                            $this->debug->info(__FUNCTION__, 'Rule Check OK -> Phone Number Final Result Format: ', $phone_number);

                            return $phone_number;
                        }
                    }
                }
            } else {
                $message = 'Invalid or Unavailable Data Convert - Data: ';
                $this->debug->error(__FUNCTION__, $message, $dataVnConvertPhoneNumber);

                return $phone_number;
            }
        }
        catch (\Exception $e) {
            $message = 'Error File: ' . $e->getFile() . ' - Line: ' . $e->getLine() . ' - Code: ' . $e->getCode() . ' - Message: ' . $e->getMessage();
            $this->debug->error(__FUNCTION__, $message);

            return NULL;
        }

        return NULL;
    }

    /**
     * Function vn_phone_number_old_and_new
     * Get Data Phone Number Old and New Convert
     * Lấy về Data Phone Number dạng cũ và mới, dữ liệu trả về dạng Array
     *
     * @author: 713uk13m <dev@nguyenanhung.com>
     * @time  : 9/21/18 01:33
     *
     * @param string $phone_number Phone Number Input
     * @param null   $phone_format Method to Format VN, VN_HUMAN, E164, INTERNATIONAL, NATIONAL, RFC3966, HIDDEN,
     *                             HIDDEN_HEAD, HIDDEN_MIDDLE, HIDDEN_END
     *
     * @see   https://github.com/nguyenanhung/vn-telco-phonenumber/blob/master/test_phone_number.php
     *
     * @return array|null|string Array if Success, Null if Error
     */
    public function vn_phone_number_old_and_new($phone_number = '', $phone_format = NULL)
    {
        $inputParams = array(
            'phone_number' => $phone_number,
            'phone_format' => $phone_format
        );
        $this->debug->info(__FUNCTION__, 'Input Params: ', $inputParams);
        try {
            $old_number = $this->vn_convert_phone_number(trim($phone_number), 'old', $phone_format);
            $new_number = $this->vn_convert_phone_number(trim($phone_number), 'new', $phone_format);
            $this->debug->debug(__FUNCTION__, 'Old Number: ', $old_number);
            $this->debug->debug(__FUNCTION__, 'New Number: ', $new_number);
            if (!empty($old_number) && !empty($new_number)) {
                $result = array($old_number, $new_number);
                $this->debug->info(__FUNCTION__, 'Final Result: ', $result);

                return (array) $result;
            }
            if (!empty($old_number) && empty($new_number)) {
                return array($old_number);
            }
            if (!empty($new_number) && empty($old_number)) {
                return array($new_number);
            }
        }
        catch (\Exception $e) {
            $message = 'Error File: ' . $e->getFile() . ' - Line: ' . $e->getLine() . ' - Code: ' . $e->getCode() . ' - Message: ' . $e->getMessage();
            $this->debug->error(__FUNCTION__, $message);

            return array($phone_number);
        }

        return $phone_number;
    }
}


<?php

namespace Sbp\Plugins\Core;

class PhpOpenerTag
{
    /***************************/
    /* Complete PHP shrot-tags */
    /***************************/
    public static $openerTag = array(
        '#<\?(?!php)#',
        '<?php',
    );

    /***************************/
    /* Remove useless PHP tags */
    /***************************/
    public static $successiveTags = array(
        '#\?><\?php#',
        '',
    );

    /*****************************************/
    /* Mark the compiled file with a comment */
    /*****************************************/
    public static function addMarkerComment($content, $caller)
    {
        $lastParsedFile = call_user_func(array($caller, 'getLastParsedFile'));

        return
            '<?php '.
            constant($caller.'::COMMENT').
            (is_null($lastParsedFile) ? '' : '/*:'.$lastParsedFile.':*/').
            ' ?>'.
            $content;
    }
}


<?php
/**
 * Copyright (c) 2015 Ori Livneh <ori@wikimedia.org>
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * @file
 * @author Ori Livneh <ori@wikimedia.org>
 */

namespace Wikimedia;

class RelPath {
	/**
	 * Split a path into path components.
	 *
	 * @param string $path File path.
	 * @return array Array of path components.
	 */
	public static function splitPath( $path ) {
		$fragments = [];

		while ( true ) {
			$cur = dirname( $path );
			if ( $cur[0] === DIRECTORY_SEPARATOR ) {
				// dirname() on Windows sometimes returns a leading backslash, but other
				// times retains the leading forward slash. Slashes other than the leading one
				// are returned as-is, and therefore do not need to be touched.
				// Furthermore, don't break on *nix where \ is allowed in file/directory names.
				$cur[0] = '/';
			}

			if ( $cur === $path || ( $cur === '.' && basename( $path ) === $path ) ) {
				break;
			}

			$fragment = trim( substr( $path, strlen( $cur ) ), '/' );

			if ( !$fragments ) {
				$fragments[] = $fragment;
			} elseif ( $fragment === '..' && basename( $cur ) !== '..' ) {
				$cur = dirname( $cur );
			} elseif ( $fragment !== '.' ) {
				$fragments[] = $fragment;
			}

			$path = $cur;
		}

		if ( $path !== '' ) {
			$fragments[] = trim( $path, '/' );
		}

		return array_reverse( $fragments );
	}

	/**
	 * Return a relative filepath to path either from the current directory or from
	 * an optional start directory. Both paths must be absolute.
	 *
	 * @param string $path File path.
	 * @param string|null $start Start directory. Optional; if not specified, the current
	 *  working directory will be used.
	 * @return string|bool Relative path, or false if input was invalid.
	 */
	public static function getRelativePath( $path, $start = null ) {
		if ( $start === null ) {
			// @codeCoverageIgnoreStart
			$start = getcwd();
		}
		// @codeCoverageIgnoreEnd

		if ( substr( $path, 0, 1 ) !== '/' || substr( $start, 0, 1 ) !== '/' ) {
			return false;
		}

		$pathParts = self::splitPath( $path );
		$countPathParts = count( $pathParts );

		$startParts = self::splitPath( $start );
		$countStartParts = count( $startParts );

		$commonLength = min( $countPathParts, $countStartParts );
		for ( $i = 0; $i < $commonLength; $i++ ) {
			if ( $startParts[$i] !== $pathParts[$i] ) {
				break;
			}
		}

		$relList = ( $countStartParts > $i )
			? array_fill( 0, $countStartParts - $i, '..' )
			: [];

		$relList = array_merge( $relList, array_slice( $pathParts, $i ) );

		return implode( '/', $relList ) ?: '.';
	}

	/**
	 * Join path components.
	 *
	 * @param string $base Base path.
	 * @param string $path File path to join to base path.
	 * @return string
	 */
	public static function joinPath( $base, $path ) {
		if ( substr( $path, 0, 1 ) === '/' ) {
			// $path is absolute.
			return $path;
		}

		if ( substr( $base, 0, 1 ) !== '/' ) {
			// $base is relative.
			return false;
		}

		$pathParts = self::splitPath( $path );
		$resultParts = self::splitPath( $base );

		while ( ( $part = array_shift( $pathParts ) ) !== null ) {
			switch ( $part ) {
			case '.':
				break;
			case '..':
				if ( count( $resultParts ) > 1 ) {
					array_pop( $resultParts );
				}
				break;
			default:
				$resultParts[] = $part;
				break;
			}
		}

		return implode( '/', $resultParts );
	}
}


<?php
/**
 * Project vn-telco-phonenumber.
 * Created by PhpStorm.
 * User: 713uk13m <dev@nguyenanhung.com>
 * Date: 9/21/18
 * Time: 01:40
 */

namespace nguyenanhung\VnTelcoPhoneNumber;

use nguyenanhung\MyDebug\Debug;
use nguyenanhung\MyDebug\Benchmark;
use nguyenanhung\VnTelcoPhoneNumber\Interfaces\PhoneTelcoInterface;
use nguyenanhung\VnTelcoPhoneNumber\Interfaces\ProjectInterface;
use nguyenanhung\VnTelcoPhoneNumber\Repository\DataRepository;

/**
 * Class Phone_telco
 *
 * @package    nguyenanhung\VnTelcoPhoneNumber
 * @author     713uk13m <dev@nguyenanhung.com>
 * @copyright  713uk13m <dev@nguyenanhung.com>
 */
class Phone_telco implements ProjectInterface, PhoneTelcoInterface
{
    /** @var object \nguyenanhung\MyDebug\Benchmark */
    private $benchmark;
    /** @var object \nguyenanhung\MyDebug\Debug Class Debug Object */
    private $debug;
    /** @var bool DEBUG Status */
    private $debugStatus = FALSE;
    /** @var null|string Set Debug Level: DEBUG, INFO, ERROR ... etc */
    private $debugLevel = NULL;
    /** @var string Logger Path */
    private $loggerPath = NULL;
    /** @var null Logger Sub Path */
    private $loggerSubPath = NULL;
    /** @var string Filename to write Log */
    private $loggerFilename = NULL;

    /**
     * Phone_telco constructor.
     */
    public function __construct()
    {
        if (self::USE_BENCHMARK === TRUE) {
            $this->benchmark = new Benchmark();
            $this->benchmark->mark('code_start');
        }
        $this->debug = new Debug();
        if ($this->debugStatus === TRUE) {
            $this->debug->setDebugStatus($this->debugStatus);
            $this->debug->setGlobalLoggerLevel($this->debugLevel);
            $this->debug->setLoggerPath($this->loggerPath);
            $this->debug->setLoggerSubPath(__CLASS__);
            if (empty($this->loggerFilename)) {
                $this->loggerFilename = 'Log-' . date('Y-m-d') . '.log';
            }
            $this->debug->setLoggerFilename($this->loggerFilename);
        }
    }

    /**
     * Phone_telco destructor.
     */
    public function __destruct()
    {
        if (self::USE_BENCHMARK === TRUE) {
            $this->benchmark->mark('code_end');
            $this->debug->debug(__FUNCTION__, 'Elapsed Time: ===> ' . $this->benchmark->elapsed_time('code_start', 'code_end'));
            $this->debug->debug(__FUNCTION__, 'Memory Usage: ===> ' . $this->benchmark->memory_usage());
        }
    }

    /**
     * Function setDebugStatus
     * Set Var to DEBUG and save Log
     *
     * @author: 713uk13m <dev@nguyenanhung.com>
     * @time  : 10/9/18 13:47
     *
     * @param bool $debugStatus TRUE if Enable Debug, other if Not
     *
     * @return mixed|void
     */
    public function setDebugStatus($debugStatus = FALSE)
    {
        $this->debugStatus = $debugStatus;
    }

    /**
     * Function setDebugLevel
     * Set String Debug Level
     *
     * @author: 713uk13m <dev@nguyenanhung.com>
     * @time  : 10/9/18 13:47
     *
     * @param bool|string $debugLevel Level to Set Debug: DEBUG, INFO, ERROR, etc...
     *
     * @return mixed|void
     */
    public function setDebugLevel($debugLevel = NULL)
    {
        $this->debugLevel = $debugLevel;
    }

    /**
     * Function setLoggerPath
     * Main Logger Path to Save Log if DEBUG is Enable
     *
     * @author  : 713uk13m <dev@nguyenanhung.com>
     * @time    : 10/9/18 13:51
     *
     * @param bool $loggerPath Set Logger Path to Save
     *
     * @example /your/to/path
     *
     * @return mixed|void
     */
    public function setLoggerPath($loggerPath = FALSE)
    {
        $this->loggerPath = $loggerPath;
    }

    /**
     * Function setLoggerSubPath
     * Sub Logger Path to Save Log if DEBUG is Enable
     *
     * @author  : 713uk13m <dev@nguyenanhung.com>
     * @time    : 10/8/18 14:38
     *
     * @param bool $loggerSubPath Set Logger Sub Path to Save
     *
     * @example /your/to/path
     *
     * @return mixed|void
     */
    public function setLoggerSubPath($loggerSubPath = FALSE)
    {
        $this->loggerSubPath = $loggerSubPath;
    }

    /**
     * Function setLoggerFilename
     * Logger filename to Save Log if DEBUG is Enable
     *
     * @author  : 713uk13m <dev@nguyenanhung.com>
     * @time    : 10/8/18 14:38
     *
     * @param bool $loggerFilename Set Logger Filename to Save
     *
     * @example Log-2018-10-09.log
     *
     * @return mixed|void
     */
    public function setLoggerFilename($loggerFilename = FALSE)
    {
        $this->loggerFilename = $loggerFilename;
    }

    /**
     * Get current version of Package
     *
     * @author: 713uk13m <dev@nguyenanhung.com>
     * @time  : 10/22/18 08:42
     *
     * @return mixed|string Current version of Package
     */
    public function getVersion()
    {
        return self::VERSION;
    }

    /**
     * Function Get Data VN Carrier
     *
     * @author: 713uk13m <dev@nguyenanhung.com>
     * @time  : 10/9/18 14:18
     *
     * @param string $carrier      Full Name of Carrier: Viettel, Vinaphone, MobiFone, Vietnamobile
     * @param string $field_output Field Output: name, id, short_name
     *
     * @return mixed|null Field if exists, full data if field_output = full,  null if not or error
     */
    public function carrier_data($carrier = '', $field_output = '')
    {
        $inputParams  = array(
            'carrier'      => $carrier,
            'field_output' => $field_output
        );
        $field_output = strtolower($field_output);
        $this->debug->info(__FUNCTION__, 'Input Params: ', $inputParams);
        try {
            $vnCarrierData = DataRepository::getData('vn_carrier_data');
            $this->debug->debug(__FUNCTION__, 'VN Carrier All Data: ', $vnCarrierData);
            if (array_key_exists($carrier, $vnCarrierData)) {
                $isCarrier = $vnCarrierData[$carrier];
                $this->debug->debug(__FUNCTION__, 'Is Carrier Data: ', $isCarrier);
                if (array_key_exists($field_output, $isCarrier)) {
                    $result = $isCarrier[$field_output];
                    $this->debug->info(__FUNCTION__, 'Final Result get Field : ' . $field_output, $result);

                    return $result;
                }
                if ($field_output = 'full') {
                    $this->debug->info(__FUNCTION__, 'Final Result get Field : ' . $field_output, $isCarrier);

                    return $isCarrier;
                }
            }
        }
        catch (\Exception $e) {
            $message = 'Error File: ' . $e->getFile() . ' - Line: ' . $e->getLine() . ' - Code: ' . $e->getCode() . ' - Message: ' . $e->getMessage();
            $this->debug->error(__FUNCTION__, $message);

            return NULL;
        }

        return NULL;
    }
}


<?php

namespace Sbp\Plugins\Core;

class SemiColon
{
    /******************************/
    /* Complete with a semi-colon */
    /******************************/
    public static function addSemiColons($content, $caller)
    {
        $validComments = call_user_func(array($caller, 'getValidComments'));
        $validSubst = call_user_func(array($caller, 'getValidStringSurrogates'));
        $validHtml = call_user_func(array($caller, 'getHtmlCodes'));
        $beforeSemiColon = '('.$validSubst.'|\+\+|--|[a-zA-Z0-9_\x7f-\xff]!|[a-zA-Z0-9_\x7f-\xff]~|!!|[a-zA-Z0-9_\x7f-\xff\)\]])(?<!<\?php|<\?)';

        return array(
            '#'.$beforeSemiColon.'(\s*(?:'.$validComments.'\s*)*[\n\r]+\s*(?:'.$validComments.'\s*)*)(?=[a-zA-Z0-9_\x7f-\xff\$\}]|$)#U' => '$1;$2',

            '#'.$beforeSemiColon.'(\s*(?:'.$validComments.'\s*)*)$#U' => '$1;$2',

            '#'.$beforeSemiColon.'(\s*(?:'.$validComments.'\s*)*\?>)$#U' => '$1;$2',

            '#'.$beforeSemiColon.'(\s*(?:'.$validComments.'\s*)*'.$validHtml.')$#U' => '$1;$2',

            '#('.$validSubst.'|\+\+|--|[a-zA-Z0-9_\x7f-\xff]!|[a-zA-Z0-9_\x7f-\xff]~|!!|\]|\))(\s*\n\s*\()#U' => '$1;$2',
        );
    }
}