<?php

namespace Neutrino\Foundation\Micro;

use Neutrino\Error;
use Neutrino\Foundation\Kernelize;
use Neutrino\Interfaces\Kernelable;
use Neutrino\Micro\Middleware;
use Phalcon\Di\FactoryDefault as Di;
use Phalcon\Mvc\Micro as MicroKernel;

/**
 * Class Kernel
 *
 * @package Neutrino\Foundation\Micro
 */
abstract class Kernel extends MicroKernel implements Kernelable
{
    use Kernelize;

    /**
     * Return the Provider List to load.
     *
     * @var string[]
     */
    protected $providers = [];

    /**
     * Return the Middlewares to attach onto the application.
     *
     * @var string[]
     */
    protected $middlewares = [];

    /**
     * Return the Events Listeners to attach onto the application.
     *
     * @var string[]
     */
    protected $listeners = [];

    /**
     * The DependencyInjection class to use.
     *
     * @var string
     */
    protected $dependencyInjection = Di::class;

    /**
     * The EventManager class to use.
     *
     * @var string
     */
    protected $eventsManagerClass = null;

    /**
     * Error Handler Outputs
     *
     * @var int
     */
    protected $errorHandlerLvl = [Error\Writer\Phplog::class, Error\Writer\Logger::class, Error\Writer\Json::class];

    /**
     * This methods registers the middlewares to be used by the application
     */
    public function registerMiddlewares()
    {
        foreach ($this->middlewares as $middleware) {
            $this->registerMiddleware(new $middleware);
        }
    }

    /**
     * @param \Neutrino\Micro\Middleware $middleware
     *
     * @throws \RuntimeException
     */
    protected function registerMiddleware(Middleware $middleware)
    {
        $on = $middleware->bindOn();
        if ($on == 'before') {
            $this->before($middleware);
        } elseif ($on == 'after') {
            $this->after($middleware);
        } elseif ($on == 'finish') {
            $this->finish($middleware);
        } else {
            throw new \RuntimeException(__METHOD__ . ': ' . get_class($middleware) . ' can\'t bind on "' . $on . '"');
        }
    }

    /**
     * @override
     *
     * @param array $modules
     * @param bool  $merge
     */
    final public function registerModules(array $modules = [], $merge = false)
    {

    }

    /**
     * Register the routes.
     *
     * @return void
     */
    public function registerRoutes()
    {
        require BASE_PATH . '/routes/micro.php';
    }
}


<?php

namespace Neutrino\Providers;

use Neutrino\Constants\Services;
use Neutrino\Interfaces\Providable;
use Phalcon\Di\Injectable;
use Phalcon\Session\Adapter\Files as FilesAdapter;
use Phalcon\Session\Adapter\Libmemcached as LibmemcachedAdapter;
use Phalcon\Session\Adapter\Memcache as MemcacheAdapter;
use Phalcon\Session\Adapter\Redis as RedisAdapter;
use Phalcon\Session\Bag;

/**
 * Class Session
 *
 *  @package Neutrino\Foundation\Bootstrap
 */
class Session extends Injectable implements Providable
{
    /**
     * Start the session the first time some component request the session service
     *
     * @throws \RuntimeException
     *
     * @return void
     */
    public function registering()
    {
        $di = $this->getDI();

        $di->set(Services::SESSION_BAG, Bag::class);

        $di->setShared(Services::SESSION, function () {
            /** @var \Phalcon\DiInterface $this */

            $sessionConfig = $this->getShared(Services::CONFIG)->session;

            if (!isset($sessionConfig->adapter)) {
                if (!isset($sessionConfig->stores[$sessionConfig->default])) {
                    throw new \RuntimeException("Session store {$sessionConfig->default} not found in stores");
                }

                $sessionConfig = $sessionConfig->stores[$sessionConfig->default];
            }

            $adapter = $sessionConfig->adapter;

            switch ($adapter){
                case 'Aerospike':
                case 'Database':
                case 'HandlerSocket':
                case 'Mongo':
                case 'Files':
                case 'Libmemcached':
                case 'Memcache':
                case 'Redis':
                    $class = 'Phalcon\Session\Adapter\\' . $adapter;
                    break;
                case FilesAdapter::class:
                case LibmemcachedAdapter::class:
                case MemcacheAdapter::class:
                case RedisAdapter::class:
                    $class = $adapter;
                    break;
                default:
                    $class = $adapter;

                    if(!class_exists($adapter)){
                        throw new \RuntimeException("Session Adapter $class not found.");
                    }
            }

            try {
                $options = [];
                if (!empty($sessionConfig->options)) {
                    $options = $sessionConfig->options->toArray();
                }
                /** @var \Phalcon\Session\Adapter|\Phalcon\Session\AdapterInterface $session */
                $session = new $class($options);
            } catch (\Throwable $e) {
                throw new \RuntimeException("Session Adapter $class construction fail.", $e);
            }

            $session->start();

            return $session;
        });
    }
}


<?php

namespace Neutrino\Foundation\Middleware;

use Neutrino\Constants\Events;
use Neutrino\Events\Listener;
use Neutrino\Interfaces\Middleware\AfterInterface;
use Neutrino\Interfaces\Middleware\BeforeInterface;
use Neutrino\Interfaces\Middleware\FinishInterface;

/**
 * ControllerMiddleware
 *
 * Class Controller
 *
 *  @package Neutrino\Foundation\Middleware
 */
abstract class Controller extends Listener
{
    /**
     * Filter methods
     *
     * @var array
     */
    private $filter = [];

    /**
     * The controller who create this middleware
     *
     * @var string
     */
    private $controllerClass;

    /**
     * ControllerMiddleware constructor.
     *
     * @param string $controllerClass The controller who create this middleware
     */
    public function __construct($controllerClass)
    {
        $this->controllerClass = $controllerClass;

        if ($this instanceof BeforeInterface) {
            $this->listen[Events\Dispatch::BEFORE_EXECUTE_ROUTE] = 'checkBefore';
        }
        if ($this instanceof AfterInterface) {
            $this->listen[Events\Dispatch::AFTER_EXECUTE_ROUTE] = 'checkAfter';
        }
        if ($this instanceof FinishInterface) {
            $this->listen[Events\Dispatch::AFTER_DISPATCH] = 'checkFinish';
        }
    }

    /**
     * @return bool
     */
    final public function check()
    {
        $dispatcher = $this->dispatcher;

        if($dispatcher->wasForwarded() && !$dispatcher->isFinished()){
            // Controller has just been forwarded
            return false;
        }

        if ($this->controllerClass !== $dispatcher->getHandlerClass()) {
            return false;
        }

        $action = $dispatcher->getActionName();

        $enable = true;
        if (isset($this->filter['only'])) {
            $enable = isset($this->filter['only'][$action]);
        }

        if ($enable && isset($this->filter['except'])) {
            $enable = !isset($this->filter['except'][$action]);
        }

        return $enable;
    }

    /**
     * Allowed Method.
     *
     * @param array|null $filters
     *
     * @return \Neutrino\Foundation\Middleware\Controller
     */
    final public function only(array $filters = null)
    {
        return $this->filters('only', $filters);
    }

    /**
     * Excepted Method.
     *
     * @param array|null $filters
     *
     * @return \Neutrino\Foundation\Middleware\Controller
     */
    final public function except(array $filters = null)
    {
        return $this->filters('except', $filters);
    }

    /**
     * @param $event
     * @param $source
     * @param $data
     *
     * @return mixed
     */
    final public function checkBefore($event, $source, $data)
    {
        if ($this->check()) {
            /** @var \Neutrino\Interfaces\Middleware\BeforeInterface $this */
            return $this->before($event, $source, $data);
        }

        return true;
    }

    /**
     * @param $event
     * @param $source
     * @param $data
     *
     * @return mixed
     */
    final public function checkAfter($event, $source, $data)
    {
        if ($this->check()) {
            /** @var \Neutrino\Interfaces\Middleware\AfterInterface $this */
            return $this->after($event, $source, $data);
        }

        return true;
    }

    /**
     * @param $event
     * @param $source
     * @param $data
     *
     * @return mixed
     */
    final public function checkFinish($event, $source, $data)
    {
        if ($this->check()) {
            /** @var \Neutrino\Interfaces\Middleware\FinishInterface $this */
            return $this->finish($event, $source, $data);
        }

        return true;
    }

    /**
     * @param            $type
     * @param array|null $filters
     *
     * @return $this
     */
    private function filters($type, array $filters = null)
    {
        if ($filters === null) {
            return $this;
        }
        if (empty($filters)) {
            $this->filter[$type] = [];

            return $this;
        }

        foreach ($filters as $item) {
            $this->filter[$type][$item] = true;
        }

        return $this;
    }
}


<?php

namespace Igniter\Flame\Traits;

/**
 * Extension trait
 *
 * Allows for "Private traits"
 *
 * Adapted from the October ExtendableTrait
 * @link https://github.com/octobercms/library/tree/master/src/Extension/ExtendableTrait.php
 */
trait ExtensionTrait
{
    /**
     * @var array Used to extend the constructor of an extension class. Eg:
     *
     *     BehaviorClass::extend(function($obj) { })
     *
     */
    protected static $extensionCallbacks = [];

    /**
     * @var string The calling class when using a static method.
     */
    public static $extendableStaticCalledClass = null;

    protected $extensionHidden = [
        'fields'  => [],
        'methods' => ['extensionIsHiddenField', 'extensionIsHiddenField'],
    ];

    public function extensionApplyInitCallbacks()
    {
        $classes = array_merge([get_class($this)], class_parents($this));
        foreach ($classes as $class) {
            if (isset(self::$extensionCallbacks[$class]) && is_array(self::$extensionCallbacks[$class])) {
                foreach (self::$extensionCallbacks[$class] as $callback) {
                    call_user_func($callback, $this);
                }
            }
        }
    }

    /**
     * Helper method for `::extend()` static method
     *
     * @param  callable $callback
     *
     * @return void
     */
    public static function extensionExtendCallback($callback)
    {
        $class = get_called_class();
        if (
            !isset(self::$extensionCallbacks[$class]) ||
            !is_array(self::$extensionCallbacks[$class])
        ) {
            self::$extensionCallbacks[$class] = [];
        }

        self::$extensionCallbacks[$class][] = $callback;
    }

    protected function extensionHideField($name)
    {
        $this->extensionHidden['fields'][] = $name;
    }

    protected function extensionHideMethod($name)
    {
        $this->extensionHidden['methods'][] = $name;
    }

    public function extensionIsHiddenField($name)
    {
        return in_array($name, $this->extensionHidden['fields']);
    }

    public function extensionIsHiddenMethod($name)
    {
        return in_array($name, $this->extensionHidden['methods']);
    }

    public static function getCalledExtensionClass()
    {
        return self::$extendableStaticCalledClass;
    }
}


<?php

namespace Neutrino\Support\Model;

/**
 * Trait Eachable
 *
 * @package Neutrino\Support\Model
 */
trait Eachable
{
    /**
     * Use as :
     * foreach(Model::each() as $model){
     *     // ... do some stuff
     * }
     *
     * @param array    $criteria
     * @param null|int $start
     * @param null|int $end
     * @param int      $pad
     *
     * @return \Generator|\Neutrino\Model[]|\Phalcon\Mvc\Model[]|self[]
     */
    public static function each(array $criteria = null, $start = null, $end = null, $pad = 100)
    {
        /** @var \Neutrino\Model $this */

        if (is_null($start)) {
            $start = 0;
        }

        if (is_null($end)) {
            $end = INF;
        }

        if ($start >= $end) {
            return;
        }

        if (empty($criteria['limit'])) {
            $criteria['limit'] = $pad;
        } else {
            $pad = $criteria['limit'];
        }

        $nb = ceil(($end - $start) / $pad);
        $idx = 0;
        $page = 0;
        do {
            $finish = true;

            $criteria['offset'] = $start + ($pad * $page);

            $models = self::find($criteria);

            foreach ($models as $model) {
                $finish = false;

                yield $idx => $model;

                $idx++;
            }

            $page++;

            if ($page >= $nb) {
                $finish = true;
            }
        } while (!$finish);
    }
}


<?php namespace Igniter\Flame\Location;

use DateTime;
use DateTimeImmutable;
use DateTimeInterface;
use Igniter\Flame\Location\Exceptions\WorkingHourException;

class WorkingTime
{
    /** @var int */
    protected $hours;

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

    protected function __construct(int $hours, int $minutes)
    {
        $this->hours = $hours;
        $this->minutes = $minutes;
    }

    public static function create(string $string): self
    {
        if (!preg_match('/^([0-1]\d)|(2[0-4]):[0-5]\d$/', $string))
            throw new WorkingHourException("The string `{$string}` isn't a valid time string. A time string must be a formatted as `18:00`.");

        list($hours, $minutes) = explode(':', $string);

        return new self($hours, $minutes);
    }

    public static function fromDateTime(DateTimeInterface $dateTime): self
    {
        return self::create($dateTime->format('H:i'));
    }

    public function hours(): int
    {
        return $this->hours;
    }

    public function minutes(): int
    {
        return $this->minutes;
    }

    public function isSame(self $time): bool
    {
        return (string)$this === (string)$time;
    }

    public function isAfter(self $time): bool
    {
        if ($this->isSame($time)) {
            return FALSE;
        }

        if ($this->hours > $time->hours) {
            return TRUE;
        }

        return $this->hours === $time->hours && $this->minutes >= $time->minutes;
    }

    public function isBefore(self $time): bool
    {
        if ($this->isSame($time)) {
            return FALSE;
        }

        return !$this->isAfter($time);
    }

    public function isSameOrAfter(self $time): bool
    {
        return $this->isSame($time) || $this->isAfter($time);
    }

    public function diff(self $time): \DateInterval
    {
        return $this->toDateTime()->diff($time->toDateTime());
    }

    /**
     * Convert to DateTime object.
     *
     * @param \DateTime|null $date
     * @return \DateTime
     */
    public function toDateTime(DateTime $date = null): DateTime
    {
        if (!$date) {
            $date = new DateTime('1970-01-01 00:00:00');
        }
        elseif (!($date instanceof DateTimeImmutable)) {
            $date = clone $date;
        }

        return $date->setTime($this->hours, $this->minutes);
    }

    public function format(string $format = 'H:i'): string
    {
        return $this->toDateTime()->format($format);
    }

    public function __toString(): string
    {
        return $this->format();
    }
}


<?php namespace Igniter\Flame\Geolite;

use Igniter\Flame\Geolite\Contracts;
use Igniter\Flame\Geolite\Exception\GeoliteException;
use Igniter\Flame\Geolite\Model\Ellipsoid;

class Distance implements Contracts\DistanceInterface
{
    /**
     * The origin coordinate.
     *
     * @var Contracts\CoordinatesInterface
     */
    protected $from;

    /**
     * The destination coordinate.
     *
     * @var Contracts\CoordinatesInterface
     */
    protected $to;

    /**
     * The user unit.
     *
     * @var string
     */
    protected $unit;

    /**
     * {@inheritDoc}
     */
    public function setFrom(Contracts\CoordinatesInterface $from)
    {
        $this->from = $from;

        return $this;
    }

    /**
     * {@inheritDoc}
     */
    public function getFrom()
    {
        return $this->from;
    }

    /**
     * {@inheritDoc}
     */
    public function setTo(Contracts\CoordinatesInterface $to)
    {
        $this->to = $to;

        return $this;
    }

    /**
     * {@inheritDoc}
     */
    public function getTo()
    {
        return $this->to;
    }

    /**
     * {@inheritDoc}
     */
    public function in($unit)
    {
        $this->unit = $unit;

        return $this;
    }

    /**
     * Returns the approximate flat distance between two coordinates
     * using Pythagoras’ theorem which is not very accurate.
     * @see http://en.wikipedia.org/wiki/Pythagorean_theorem
     * @see http://en.wikipedia.org/wiki/Equirectangular_projection
     *
     * @return double The distance in meters
     */
    public function flat()
    {
        Ellipsoid::checkCoordinatesEllipsoid($this->from, $this->to);

        $latA = deg2rad($this->from->getLatitude());
        $lngA = deg2rad($this->from->getLongitude());
        $latB = deg2rad($this->to->getLatitude());
        $lngB = deg2rad($this->to->getLongitude());

        $x = ($lngB - $lngA) * cos(($latA + $latB) / 2);
        $y = $latB - $latA;

        $sqrt = sqrt(($x * $x) + ($y * $y));

        return $this->convertToUserUnit($sqrt * $this->from->getEllipsoid()->getA());
    }

    /**
     * Returns the approximate distance between two coordinates
     * using the spherical trigonometry called Great Circle Distance.
     * @see http://www.ga.gov.au/earth-monitoring/geodesy/geodetic-techniques/distance-calculation-algorithms.html#circle
     * @see http://en.wikipedia.org/wiki/Cosine_law
     *
     * @return double The distance in meters
     */
    public function greatCircle()
    {
        Ellipsoid::checkCoordinatesEllipsoid($this->from, $this->to);

        $latA = deg2rad($this->from->getLatitude());
        $lngA = deg2rad($this->from->getLongitude());
        $latB = deg2rad($this->to->getLatitude());
        $lngB = deg2rad($this->to->getLongitude());

        $degrees = acos(sin($latA)
            * sin($latB)
            + cos($latA)
            * cos($latB)
            * cos($lngB - $lngA)
        );

        return $this->convertToUserUnit($degrees * $this->from->getEllipsoid()->getA());
    }

    /**
     * Returns the approximate sea level great circle (Earth) distance between
     * two coordinates using the Haversine formula which is accurate to around 0.3%.
     * @see http://www.movable-type.co.uk/scripts/latlong.html
     *
     * @return double The distance in meters
     */
    public function haversine()
    {
        Ellipsoid::checkCoordinatesEllipsoid($this->from, $this->to);

        $latA = deg2rad($this->from->getLatitude());
        $lngA = deg2rad($this->from->getLongitude());
        $latB = deg2rad($this->to->getLatitude());
        $lngB = deg2rad($this->to->getLongitude());

        $dLat = $latB - $latA;
        $dLon = $lngB - $lngA;

        $a = sin($dLat / 2) * sin($dLat / 2) + cos($latA) * cos($latB) * sin($dLon / 2) * sin($dLon / 2);
        $c = 2 * atan2(sqrt($a), sqrt(1 - $a));

        return $this->convertToUserUnit($this->from->getEllipsoid()->getA() * $c);
    }

    /**
     * Returns geodetic distance between between two coordinates using Vincenty inverse
     * formula for ellipsoids which is accurate to within 0.5mm.
     * @see http://www.movable-type.co.uk/scripts/latlong-vincenty.html
     *
     * @return double The distance in meters
     */
    public function vincenty()
    {
        Ellipsoid::checkCoordinatesEllipsoid($this->from, $this->to);

        $a = $this->from->getEllipsoid()->getA();
        $b = $this->from->getEllipsoid()->getB();
        $f = 1 / $this->from->getEllipsoid()->getInvF();

        $lL = deg2rad($this->to->getLongitude() - $this->from->getLongitude());
        $u1 = atan((1 - $f) * tan(deg2rad($this->from->getLatitude())));
        $u2 = atan((1 - $f) * tan(deg2rad($this->to->getLatitude())));

        $sinU1 = sin($u1);
        $cosU1 = cos($u1);
        $sinU2 = sin($u2);
        $cosU2 = cos($u2);

        $lambda = $lL;
        $iterLimit = 100;

        do {
            $sinLambda = sin($lambda);
            $cosLambda = cos($lambda);
            $sinSigma = sqrt(($cosU2 * $sinLambda) * ($cosU2 * $sinLambda) +
                ($cosU1 * $sinU2 - $sinU1 * $cosU2 * $cosLambda) * ($cosU1 * $sinU2 - $sinU1 * $cosU2 * $cosLambda));

            if (0.0 === $sinSigma) {
                return 0.0; // co-incident points
            }

            $cosSigma = $sinU1 * $sinU2 + $cosU1 * $cosU2 * $cosLambda;
            $sigma = atan2($sinSigma, $cosSigma);
            $sinAlpha = $cosU1 * $cosU2 * $sinLambda / $sinSigma;
            $cosSqAlpha = 1 - $sinAlpha * $sinAlpha;
            if ($cosSqAlpha != 0.0) {
                $cos2SigmaM = $cosSigma - 2 * $sinU1 * $sinU2 / $cosSqAlpha;
            }
            else {
                $cos2SigmaM = 0.0;
            }
            $cC = $f / 16 * $cosSqAlpha * (4 + $f * (4 - 3 * $cosSqAlpha));
            $lambdaP = $lambda;
            $lambda = $lL + (1 - $cC) * $f * $sinAlpha * ($sigma + $cC * $sinSigma *
                    ($cos2SigmaM + $cC * $cosSigma * (-1 + 2 * $cos2SigmaM * $cos2SigmaM)));
        } while (abs($lambda - $lambdaP) > 1e-12 && --$iterLimit > 0);

        if (0 === $iterLimit) {
            throw new GeoliteException('Vincenty formula failed to converge !');
        }

        $uSq = $cosSqAlpha * ($a * $a - $b * $b) / ($b * $b);
        $aA = 1 + $uSq / 16384 * (4096 + $uSq * (-768 + $uSq * (320 - 175 * $uSq)));
        $bB = $uSq / 1024 * (256 + $uSq * (-128 + $uSq * (74 - 47 * $uSq)));
        $deltaSigma = $bB * $sinSigma * ($cos2SigmaM + $bB / 4 * ($cosSigma * (-1 + 2 * $cos2SigmaM * $cos2SigmaM) -
                    $bB / 6 * $cos2SigmaM * (-3 + 4 * $sinSigma * $sinSigma) * (-3 + 4 * $cos2SigmaM * $cos2SigmaM)));
        $s = $b * $aA * ($sigma - $deltaSigma);

        return $this->convertToUserUnit($s);
    }

    /**
     * Converts results in meters to user's unit (if any).
     * The default returned value is in meters.
     *
     * @param double $meters
     *
     * @return double
     */
    public function convertToUserUnit($meters)
    {
        switch ($this->unit) {
            case Geolite::KILOMETER_UNIT:
                return $meters / 1000;
            case Geolite::MILE_UNIT:
                return $meters / Geolite::METERS_PER_MILE;
            case Geolite::FOOT_UNIT:
                return $meters / Geolite::FEET_PER_METER;
            default:
                return $meters;
        }
    }
}


<?php

namespace Neutrino\PhpPreloader;

use PhpParser\NodeTraverserInterface;
use PhpParser\Parser;
use PhpParser\PrettyPrinterAbstract as PrettyPrinter;
use RuntimeException;

/**
 * Class AbstractPreloader
 *
 * @package Neutrino\PhpParser
 */
class Preloader
{
    /**
     * The printer.
     *
     * @var \PhpParser\PrettyPrinterAbstract
     */
    protected $printer;

    /**
     * The parser.
     *
     * @var \PhpParser\Parser
     */
    protected $parser;

    /**
     * The traverser.
     *
     * @var \PhpParser\NodeTraverserInterface
     */
    protected $traverser;

    /**
     * AbstractPreloader constructor.
     *
     * @param \PhpParser\PrettyPrinterAbstract  $printer
     * @param \PhpParser\Parser                 $parser
     * @param \PhpParser\NodeTraverserInterface $traverser
     */
    public function __construct(PrettyPrinter $printer, Parser $parser, NodeTraverserInterface $traverser)
    {
        $this->printer = $printer;
        $this->parser = $parser;
        $this->traverser = $traverser;
    }

    /**
     * Prepare the output file and directory.
     *
     * @param string $file
     * @param bool   $strict Use strict type. Only for PHP7
     *
     * @return resource
     */
    public function prepareOutput($file, $strict = false)
    {
        if ($strict && PHP_VERSION_ID < 70000) {
            throw new RuntimeException('Strict mode requires PHP 7 or greater.');
        }

        $dir = dirname($file);
        if (!is_dir($dir) && !mkdir($dir, 0777, true)) {
            throw new RuntimeException("Unable to create directory $dir.");
        }

        $r = fopen($file, 'w');

        if (!$r) {
            throw new RuntimeException("Unable to open $file for writing.");
        }

        if ($strict) {
            fwrite($r, "<?php declare(strict_types=1);\n");
        } else {
            fwrite($r, "<?php\n");
        }

        return $r;
    }

    /**
     * Get a pretty printed string of code from a file while applying visitors.
     *
     * @param string $file
     *
     * @return string
     */
    public function getCode($file)
    {
        $stmts = $this->parse($file);
        $stmts = $this->traverse($stmts);
        $content = $this->prettyPrint($stmts);

        return $content;
    }

    /**
     * Parses PHP code into a node tree.
     *
     * @param string $file
     *
     * @return null|\PhpParser\Node[]
     * @throws \RuntimeException
     */
    public function parse($file)
    {
        if (!is_string($file) || empty($file)) {
            throw new RuntimeException('Invalid filename provided.');
        }

        if (!is_readable($file)) {
            throw new RuntimeException("Cannot open $file for reading.");
        }

        $content = php_strip_whitespace($file);

        return $this->parser->parse($content);
    }

    /**
     * Traverses an array of nodes using the registered visitors.
     *
     * @param \PhpParser\Node[] $stmts
     *
     * @return \PhpParser\Node[]
     */
    public function traverse(array $stmts)
    {
        return $this->traverser->traverse($stmts);
    }

    /**
     * Pretty prints an array of statements.
     *
     * @param \PhpParser\Node[] $stmts
     *
     * @return string
     */
    public function prettyPrint(array $stmts)
    {
        return $this->printer->prettyPrint($stmts);
    }

    /**
     * @return \PhpParser\PrettyPrinterAbstract
     */
    public function getPrinter()
    {
        return $this->printer;
    }

    /**
     * @return \PhpParser\Parser
     */
    public function getParser()
    {
        return $this->parser;
    }

    /**
     * @return \PhpParser\NodeTraverserInterface
     */
    public function getTraverser()
    {
        return $this->traverser;
    }
}


<?php

namespace Neutrino\Providers;

use Neutrino\Constants\Services;
use Neutrino\Database\DatabaseStrategy;
use Neutrino\Interfaces\Providable;
use Phalcon\Di\Injectable;
use Phalcon\Di\Service;

/**
 * Class Database
 *
 * @package Neutrino\Foundation\Bootstrap
 */
class Database extends Injectable implements Providable
{
    /**
     * Database connection is created based in the parameters defined in the configuration file
     */
    public function registering()
    {
        $di = $this->getDI();

        $database = (array)$di->getShared(Services::CONFIG)->database;
        $connections = (array)$database['connections'];

        if (count($connections) > 1) {
            $di->setShared(Services::DB, DatabaseStrategy::class);

            foreach ($connections as $name => $connection) {
                $di->setShared(Services::DB . '.' . $name, function () use ($connection) {
                    return new $connection['adapter']((array)$connection['config']);
                });
            }
        } else {
            $connection = array_shift($connections);
            $serviceName = Services::DB . '.' . $database['default'];

            $service = new Service($serviceName, function () use ($connection) {
                return new $connection['adapter']((array)$connection['config']);
            }, true);

            $di->setRaw($serviceName, $service);
            $di->setRaw(Services::DB, $service);
        }
    }
}


<?php

namespace Igniter\Flame\Database\NestedSet;

use Igniter\Flame\Database\Builder as BuilderBase;
use Igniter\Flame\Database\Traits\NestedTree;
use Illuminate\Database\Eloquent\Model as ModelBase;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\Query\Builder as BaseQueryBuilder;
use Illuminate\Database\Query\Builder as Query;
use Illuminate\Database\Query\Expression;
use Illuminate\Support\Arr;
use Kalnoy\Nestedset\Collection;
use Kalnoy\Nestedset\NestedSet;
use LogicException;

class Builder extends BuilderBase
{
    /**
     * @var NestedTree|ModelBase
     */
    protected $model;

    /**
     * Get node's `lft` and `rgt` values.
     *
     * @since 2.0
     *
     * @param mixed $id
     * @param bool $required
     *
     * @return array
     */
    public function getNodeData($id, $required = FALSE)
    {
        $query = $this->toBase();

        $query->where($this->model->getKeyName(), '=', $id);

        $data = $query->first([$this->model->getLftName(),
            $this->model->getRgtName()]);

        if (!$data && $required) {
            throw new ModelNotFoundException;
        }

        return (array)$data;
    }

    /**
     * Get plain node data.
     *
     * @since 2.0
     *
     * @param mixed $id
     * @param bool $required
     *
     * @return array
     */
    public function getPlainNodeData($id, $required = FALSE)
    {
        return array_values($this->getNodeData($id, $required));
    }

    /**
     * Scope limits query to select just root node.
     *
     * @return $this
     */
    public function whereIsRoot()
    {
        $this->query->whereNull($this->model->getParentIdName());

        return $this;
    }

    /**
     * Limit results to ancestors of specified node.
     *
     * @since 2.0
     *
     * @param mixed $id
     * @param bool $andSelf
     *
     * @param string $boolean
     *
     * @return $this
     */
    public function whereAncestorOf($id, $andSelf = FALSE, $boolean = 'and')
    {
        $keyName = $this->model->getKeyName();

        if (NestedSet::isNode($id)) {
            $value = '?';

            $this->query->addBinding($id->getRgt());

            $id = $id->getKey();
        }
        else {
            $valueQuery = $this->model
                ->newQuery()
                ->toBase()
                ->select("_.".$this->model->getRgtName())
                ->from($this->model->getTable().' as _')
                ->where($keyName, '=', $id)
                ->limit(1);

            $this->query->mergeBindings($valueQuery);

            $value = '('.$valueQuery->toSql().')';
        }

        $this->query->whereNested(function ($inner) use ($value, $andSelf, $id) {
            list($lft, $rgt) = $this->wrappedColumns();

            $inner->whereRaw("{$value} between {$lft} and {$rgt}");

            if (!$andSelf) {
                $inner->where($this->model->getKeyName(), '<>', $id);
            }
        }, $boolean);

        return $this;
    }

    /**
     * @param $id
     * @param bool $andSelf
     *
     * @return $this
     */
    public function orWhereAncestorOf($id, $andSelf = FALSE)
    {
        return $this->whereAncestorOf($id, $andSelf, 'or');
    }

    /**
     * @param $id
     *
     * @return \Igniter\Flame\Database\NestedSet\Builder
     */
    public function whereAncestorOrSelf($id)
    {
        return $this->whereAncestorOf($id, TRUE);
    }

    /**
     * Get ancestors of specified node.
     *
     * @since 2.0
     *
     * @param mixed $id
     * @param array $columns
     *
     * @return \Kalnoy\Nestedset\Collection
     */
    public function ancestorsOf($id, array $columns = ['*'])
    {
        return $this->whereAncestorOf($id)->get($columns);
    }

    /**
     * @param $id
     * @param array $columns
     *
     * @return \Kalnoy\Nestedset\Collection
     */
    public function ancestorsAndSelf($id, array $columns = ['*'])
    {
        return $this->whereAncestorOf($id, TRUE)->get($columns);
    }

    /**
     * Add node selection statement between specified range.
     *
     * @since 2.0
     *
     * @param array $values
     * @param string $boolean
     * @param bool $not
     *
     * @return $this
     */
    public function whereNodeBetween($values, $boolean = 'and', $not = FALSE)
    {
        $this->query->whereBetween($this->model->getLftName(), $values, $boolean, $not);

        return $this;
    }

    /**
     * Add node selection statement between specified range joined with `or` operator.
     *
     * @since 2.0
     *
     * @param array $values
     *
     * @return $this
     */
    public function orWhereNodeBetween($values)
    {
        return $this->whereNodeBetween($values, 'or');
    }

    /**
     * Add constraint statement to descendants of specified node.
     *
     * @since 2.0
     *
     * @param mixed $id
     * @param string $boolean
     * @param bool $not
     * @param bool $andSelf
     *
     * @return $this
     */
    public function whereDescendantOf($id, $boolean = 'and', $not = FALSE,
        $andSelf = FALSE
    )
    {
        if (NestedSet::isNode($id)) {
            $data = $id->getBounds();
        }
        else {
            $data = $this->model->newNestedSetQuery()
                                ->getPlainNodeData($id, TRUE);
        }

        // Don't include the node
        if (!$andSelf) {
            ++$data[0];
        }

        return $this->whereNodeBetween($data, $boolean, $not);
    }

    /**
     * @param mixed $id
     *
     * @return \Igniter\Flame\Database\NestedSet\Builder
     */
    public function whereNotDescendantOf($id)
    {
        return $this->whereDescendantOf($id, 'and', TRUE);
    }

    /**
     * @param mixed $id
     *
     * @return \Igniter\Flame\Database\NestedSet\Builder
     */
    public function orWhereDescendantOf($id)
    {
        return $this->whereDescendantOf($id, 'or');
    }

    /**
     * @param mixed $id
     *
     * @return \Igniter\Flame\Database\NestedSet\Builder
     */
    public function orWhereNotDescendantOf($id)
    {
        return $this->whereDescendantOf($id, 'or', TRUE);
    }

    /**
     * @param $id
     * @param string $boolean
     * @param bool $not
     *
     * @return $this
     */
    public function whereDescendantOrSelf($id, $boolean = 'and', $not = FALSE)
    {
        return $this->whereDescendantOf($id, $boolean, $not, TRUE);
    }

    /**
     * Get descendants of specified node.
     *
     * @since 2.0
     *
     * @param mixed $id
     * @param array $columns
     * @param bool $andSelf
     *
     * @return Collection
     */
    public function descendantsOf($id, array $columns = ['*'], $andSelf = FALSE)
    {
        try {
            return $this->whereDescendantOf($id, 'and', FALSE, $andSelf)->get($columns);
        } catch (ModelNotFoundException $e) {
            return $this->model->newCollection();
        }
    }

    /**
     * @param $id
     * @param array $columns
     *
     * @return Collection
     */
    public function descendantsAndSelf($id, array $columns = ['*'])
    {
        return $this->descendantsOf($id, $columns, TRUE);
    }

    /**
     * @param $id
     * @param $operator
     * @param $boolean
     *
     * @return $this
     */
    protected function whereIsBeforeOrAfter($id, $operator, $boolean)
    {
        if (NestedSet::isNode($id)) {
            $value = '?';

            $this->query->addBinding($id->getLft());
        }
        else {
            $valueQuery = $this->model
                ->newQuery()
                ->toBase()
                ->select('_n.'.$this->model->getLftName())
                ->from($this->model->getTable().' as _n')
                ->where('_n.'.$this->model->getKeyName(), '=', $id);

            $this->query->mergeBindings($valueQuery);

            $value = '('.$valueQuery->toSql().')';
        }

        list($lft,) = $this->wrappedColumns();

        $this->query->whereRaw("{$lft} {$operator} {$value}", [], $boolean);

        return $this;
    }

    /**
     * Constraint nodes to those that are after specified node.
     *
     * @since 2.0
     *
     * @param mixed $id
     * @param string $boolean
     *
     * @return $this
     */
    public function whereIsAfter($id, $boolean = 'and')
    {
        return $this->whereIsBeforeOrAfter($id, '>', $boolean);
    }

    /**
     * Constraint nodes to those that are before specified node.
     *
     * @since 2.0
     *
     * @param mixed $id
     * @param string $boolean
     *
     * @return $this
     */
    public function whereIsBefore($id, $boolean = 'and')
    {
        return $this->whereIsBeforeOrAfter($id, '<', $boolean);
    }

    /**
     * @return $this
     */
    public function whereIsLeaf()
    {
        list($lft, $rgt) = $this->wrappedColumns();

        return $this->whereRaw("$lft = $rgt - 1");
    }

    /**
     * @param array $columns
     *
     * @return \Igniter\Flame\Database\NestedSet\Builder[]|\Illuminate\Database\Eloquent\Collection
     */
    public function leaves(array $columns = ['*'])
    {
        return $this->whereIsLeaf()->get($columns);
    }

    /**
     * Include depth level into the result.
     *
     * @param string $as
     *
     * @return $this
     */
    public function withDepth($as = 'depth')
    {
        if ($this->query->columns === null) $this->query->columns = ['*'];

        $table = $this->wrappedTable();

        list($lft, $rgt) = $this->wrappedColumns();

        $alias = '_d';
        $wrappedAlias = $this->query->getGrammar()->wrapTable($alias);

        $query = $this->model
            ->newScopedQuery('_d')
            ->toBase()
            ->selectRaw('count(1) - 1')
            ->from($this->model->getTable().' as '.$alias)
            ->whereRaw("{$table}.{$lft} between {$wrappedAlias}.{$lft} and {$wrappedAlias}.{$rgt}");

        $this->query->selectSub($query, $as);

        return $this;
    }

    /**
     * Get wrapped `lft` and `rgt` column names.
     *
     * @since 2.0
     *
     * @return array
     */
    protected function wrappedColumns()
    {
        $grammar = $this->query->getGrammar();

        return [
            $grammar->wrap($this->model->getLftName()),
            $grammar->wrap($this->model->getRgtName()),
        ];
    }

    /**
     * Get a wrapped table name.
     *
     * @since 2.0
     *
     * @return string
     */
    protected function wrappedTable()
    {
        return $this->query->getGrammar()->wrapTable($this->getQuery()->from);
    }

    /**
     * Wrap model's key name.
     *
     * @since 2.0
     *
     * @return string
     */
    protected function wrappedKey()
    {
        return $this->query->getGrammar()->wrap($this->model->getKeyName());
    }

    /**
     * Exclude root node from the result.
     *
     * @return $this
     */
    public function withoutRoot()
    {
        $this->query->whereNotNull($this->model->getParentIdName());

        return $this;
    }

    /**
     * Equivalent of `withoutRoot`.
     *
     * @since 2.0
     * @deprecated since v4.1
     *
     * @return $this
     */
    public function hasParent()
    {
        $this->query->whereNotNull($this->model->getParentIdName());

        return $this;
    }

    /**
     * Get only nodes that have children.
     *
     * @since 2.0
     * @deprecated since v4.1
     *
     * @return $this
     */
    public function hasChildren()
    {
        list($lft, $rgt) = $this->wrappedColumns();

        $this->query->whereRaw("{$rgt} > {$lft} + 1");

        return $this;
    }

    /**
     * Order by node position.
     *
     * @param string $dir
     *
     * @return $this
     */
    public function defaultOrder($dir = 'asc')
    {
        $this->query->orders = null;

        $this->query->orderBy($this->model->getLftName(), $dir);

        return $this;
    }

    /**
     * Order by reversed node position.
     *
     * @return $this
     */
    public function reversed()
    {
        return $this->defaultOrder('desc');
    }

    /**
     * Move a node to the new position.
     *
     * @param mixed $key
     * @param int $position
     *
     * @return int
     */
    public function moveNode($key, $position)
    {
        list($lft, $rgt) = $this->model->newNestedSetQuery()
                                       ->getPlainNodeData($key, TRUE);

        if ($lft < $position && $position <= $rgt) {
            throw new LogicException('Cannot move node into itself.');
        }

        // Get boundaries of nodes that should be moved to new position
        $from = min($lft, $position);
        $to = max($rgt, $position - 1);

        // The height of node that is being moved
        $height = $rgt - $lft + 1;

        // The distance that our node will travel to reach it's destination
        $distance = $to - $from + 1 - $height;

        // If no distance to travel, just return
        if ($distance === 0) {
            return 0;
        }

        if ($position > $lft) {
            $height *= -1;
        }
        else {
            $distance *= -1;
        }

        $params = compact('lft', 'rgt', 'from', 'to', 'height', 'distance');

        $boundary = [$from, $to];

        $query = $this->toBase()->where(function (Query $inner) use ($boundary) {
            $inner->whereBetween($this->model->getLftName(), $boundary);
            $inner->orWhereBetween($this->model->getRgtName(), $boundary);
        });

        return $query->update($this->patch($params));
    }

    /**
     * Make or remove gap in the tree. Negative height will remove gap.
     *
     * @since 2.0
     *
     * @param int $cut
     * @param int $height
     *
     * @return int
     */
    public function makeGap($cut, $height)
    {
        $params = compact('cut', 'height');

        $query = $this->toBase()->whereNested(function (Query $inner) use ($cut) {
            $inner->where($this->model->getLftName(), '>=', $cut);
            $inner->orWhere($this->model->getRgtName(), '>=', $cut);
        });

        return $query->update($this->patch($params));
    }

    /**
     * Get patch for columns.
     *
     * @since 2.0
     *
     * @param array $params
     *
     * @return array
     */
    protected function patch(array $params)
    {
        $grammar = $this->query->getGrammar();

        $columns = [];

        foreach ([$this->model->getLftName(), $this->model->getRgtName()] as $col) {
            $columns[$col] = $this->columnPatch($grammar->wrap($col), $params);
        }

        return $columns;
    }

    /**
     * Get patch for single column.
     *
     * @since 2.0
     *
     * @param string $col
     * @param array $params
     *
     * @return string
     */
    protected function columnPatch($col, array $params)
    {
        extract($params);

        /** @var int $height */
        if ($height > 0) $height = '+'.$height;

        if (isset($cut)) {
            return new Expression("case when {$col} >= {$cut} then {$col}{$height} else {$col} end");
        }

        /** @var int $distance */
        /** @var int $lft */
        /** @var int $rgt */
        /** @var int $from */
        /** @var int $to */
        if ($distance > 0) $distance = '+'.$distance;

        return new Expression("case ".
            "when {$col} between {$lft} and {$rgt} then {$col}{$distance} ". // Move the node
            "when {$col} between {$from} and {$to} then {$col}{$height} ". // Move other nodes
            "else {$col} end"
        );
    }

    /**
     * Get statistics of errors of the tree.
     *
     * @since 2.0
     *
     * @return array
     */
    public function countErrors()
    {
        $checks = [];

        // Check if lft and rgt values are ok
        $checks['oddness'] = $this->getOdnessQuery();

        // Check if lft and rgt values are unique
        $checks['duplicates'] = $this->getDuplicatesQuery();

        // Check if parent_id is set correctly
        $checks['wrong_parent'] = $this->getWrongParentQuery();

        // Check for nodes that have missing parent
        $checks['missing_parent'] = $this->getMissingParentQuery();

        $query = $this->query->newQuery();

        foreach ($checks as $key => $inner) {
            $inner->selectRaw('count(1)');

            $query->selectSub($inner, $key);
        }

        return (array)$query->first();
    }

    /**
     * @return BaseQueryBuilder
     */
    protected function getOdnessQuery()
    {
        return $this->model
            ->newNestedSetQuery()
            ->toBase()
            ->whereNested(function (BaseQueryBuilder $inner) {
                list($lft, $rgt) = $this->wrappedColumns();

                $inner->whereRaw("{$lft} >= {$rgt}")
                      ->orWhereRaw("({$rgt} - {$lft}) % 2 = 0");
            });
    }

    /**
     * @return BaseQueryBuilder
     */
    protected function getDuplicatesQuery()
    {
        $table = $this->wrappedTable();

        $firstAlias = 'c1';
        $secondAlias = 'c2';

        $waFirst = $this->query->getGrammar()->wrapTable($firstAlias);
        $waSecond = $this->query->getGrammar()->wrapTable($secondAlias);

        $query = $this->model
            ->newNestedSetQuery($firstAlias)
            ->toBase()
            ->from($this->query->raw("{$table} as {$waFirst}, {$table} {$waSecond}"))
            ->whereRaw("{$waFirst}.id < {$waSecond}.id")
            ->whereNested(function (BaseQueryBuilder $inner) use ($waFirst, $waSecond) {
                list($lft, $rgt) = $this->wrappedColumns();

                $inner->orWhereRaw("{$waFirst}.{$lft}={$waSecond}.{$lft}")
                      ->orWhereRaw("{$waFirst}.{$rgt}={$waSecond}.{$rgt}")
                      ->orWhereRaw("{$waFirst}.{$lft}={$waSecond}.{$rgt}")
                      ->orWhereRaw("{$waFirst}.{$rgt}={$waSecond}.{$lft}");
            });

        return $this->model->applyNestedSetScope($query, $secondAlias);
    }

    /**
     * @return BaseQueryBuilder
     */
    protected function getWrongParentQuery()
    {
        $table = $this->wrappedTable();
        $keyName = $this->wrappedKey();

        $grammar = $this->query->getGrammar();

        $parentIdName = $grammar->wrap($this->model->getParentIdName());

        $parentAlias = 'p';
        $childAlias = 'c';
        $intermAlias = 'i';

        $waParent = $grammar->wrapTable($parentAlias);
        $waChild = $grammar->wrapTable($childAlias);
        $waInterm = $grammar->wrapTable($intermAlias);

        $query = $this->model
            ->newNestedSetQuery('c')
            ->toBase()
            ->from($this->query->raw("{$table} as {$waChild}, {$table} as {$waParent}, $table as {$waInterm}"))
            ->whereRaw("{$waChild}.{$parentIdName}={$waParent}.{$keyName}")
            ->whereRaw("{$waInterm}.{$keyName} <> {$waParent}.{$keyName}")
            ->whereRaw("{$waInterm}.{$keyName} <> {$waChild}.{$keyName}")
            ->whereNested(function (BaseQueryBuilder $inner) use ($waInterm, $waChild, $waParent) {
                list($lft, $rgt) = $this->wrappedColumns();

                $inner->whereRaw("{$waChild}.{$lft} not between {$waParent}.{$lft} and {$waParent}.{$rgt}")
                      ->orWhereRaw("{$waChild}.{$lft} between {$waInterm}.{$lft} and {$waInterm}.{$rgt}")
                      ->whereRaw("{$waInterm}.{$lft} between {$waParent}.{$lft} and {$waParent}.{$rgt}");
            });

        $this->model->applyNestedSetScope($query, $parentAlias);
        $this->model->applyNestedSetScope($query, $intermAlias);

        return $query;
    }

    /**
     * @return $this
     */
    protected function getMissingParentQuery()
    {
        return $this->model
            ->newNestedSetQuery()
            ->toBase()
            ->whereNested(function (BaseQueryBuilder $inner) {
                $grammar = $this->query->getGrammar();

                $table = $this->wrappedTable();
                $keyName = $this->wrappedKey();
                $parentIdName = $grammar->wrap($this->model->getParentIdName());
                $alias = 'p';
                $wrappedAlias = $grammar->wrapTable($alias);

                $existsCheck = $this->model
                    ->newNestedSetQuery()
                    ->toBase()
                    ->selectRaw('1')
                    ->from($this->query->raw("{$table} as {$wrappedAlias}"))
                    ->whereRaw("{$table}.{$parentIdName} = {$wrappedAlias}.{$keyName}")
                    ->limit(1);

                $this->model->applyNestedSetScope($existsCheck, $alias);

                $inner->whereRaw("{$parentIdName} is not null")
                      ->addWhereExistsQuery($existsCheck, 'and', TRUE);
            });
    }

    /**
     * Get the number of total errors of the tree.
     *
     * @since 2.0
     *
     * @return int
     */
    public function getTotalErrors()
    {
        return array_sum($this->countErrors());
    }

    /**
     * Get whether the tree is broken.
     *
     * @since 2.0
     *
     * @return bool
     */
    public function isBroken()
    {
        return $this->getTotalErrors() > 0;
    }

    /**
     * Fixes the tree based on parentage info.
     *
     * Nodes with invalid parent are saved as roots.
     *
     * @return int The number of fixed nodes
     */
    public function fixTree()
    {
        $columns = [
            $this->model->getKeyName(),
            $this->model->getParentIdName(),
            $this->model->getLftName(),
            $this->model->getRgtName(),
        ];

        $dictionary = $this->model->newNestedSetQuery()
                                  ->defaultOrder()
                                  ->get($columns)
                                  ->groupBy($this->model->getParentIdName())
                                  ->all();

        return self::fixNodes($dictionary);
    }

    /**
     * @param array $dictionary
     *
     * @return int
     */
    protected static function fixNodes(array &$dictionary)
    {
        $fixed = 0;

        $cut = self::reorderNodes($dictionary, $fixed);

        // Save nodes that have invalid parent as roots
        while (!empty($dictionary)) {
            $dictionary[null] = reset($dictionary);

            unset($dictionary[key($dictionary)]);

            $cut = self::reorderNodes($dictionary, $fixed, null, $cut);
        }

        return $fixed;
    }

    /**
     * @param array $dictionary
     * @param int $fixed
     * @param $parentId
     * @param int $cut
     *
     * @return int
     */
    protected static function reorderNodes(array &$dictionary, &$fixed,
        $parentId = null, $cut = 1
    )
    {
        if (!isset($dictionary[$parentId])) {
            return $cut;
        }

        /** @var ModelBase|NestedTree $model */
        foreach ($dictionary[$parentId] as $model) {
            $lft = $cut;

            $cut = self::reorderNodes($dictionary,
                $fixed,
                $model->getKey(),
                $cut + 1);

            $rgt = $cut;

            if ($model->rawNode($lft, $rgt, $parentId)->isDirty()) {
                $model->save();

                $fixed++;
            }

            ++$cut;
        }

        unset($dictionary[$parentId]);

        return $cut;
    }

    /**
     * Rebuild the tree based on raw data.
     *
     * If item data does not contain primary key, new node will be created.
     *
     * @param array $data
     * @param bool $delete Whether to delete nodes that exists but not in the data
     *                     array
     *
     * @return int
     */
    public function rebuildTree(array $data, $delete = FALSE)
    {
        if ($this->model->usesSoftDelete()) {
            $this->withTrashed();
        }

        $existing = $this->get()->getDictionary();
        $dictionary = [];

        $this->buildRebuildDictionary($dictionary, $data, $existing);

        if (!empty($existing)) {
            if ($delete && !$this->model->usesSoftDelete()) {
                $this->model
                    ->newScopedQuery()
                    ->whereIn($this->model->getKeyName(), array_keys($existing))
                    ->delete();
            }
            else {
                foreach ($existing as $model) {
                    $dictionary[$model->getParentId()][] = $model;

                    if ($delete && $this->model->usesSoftDelete() &&
                        !$model->{$model->getDeletedAtColumn()}
                    ) {
                        $time = $this->model->fromDateTime($this->model->freshTimestamp());

                        $model->{$model->getDeletedAtColumn()} = $time;
                    }
                }
            }
        }

        return $this->fixNodes($dictionary);
    }

    /**
     * @param array $dictionary
     * @param array $data
     * @param array $existing
     * @param mixed $parentId
     */
    protected function buildRebuildDictionary(array &$dictionary,
        array $data,
        array &$existing,
        $parentId = null
    )
    {
        $keyName = $this->model->getKeyName();

        foreach ($data as $itemData) {
            if (!isset($itemData[$keyName])) {
                $model = $this->model->newInstance($this->model->getAttributes());

                // We will save it as raw node since tree will be fixed
                $model->rawNode(0, 0, $parentId);
            }
            else {
                if (!isset($existing[$key = $itemData[$keyName]])) {
                    throw new ModelNotFoundException;
                }

                $model = $existing[$key];

                unset($existing[$key]);
            }

            $model->fill(Arr::except($itemData, 'children'))->save();

            $dictionary[$parentId][] = $model;

            if (!isset($itemData['children'])) continue;

            $this->buildRebuildDictionary($dictionary,
                $itemData['children'],
                $existing,
                $model->getKey());
        }
    }

    /**
     * @param string|null $table
     *
     * @return $this
     */
    public function applyNestedSetScope($table = null)
    {
        return $this->model->applyNestedSetScope($this, $table);
    }

    /**
     * Get the root node.
     *
     * @param array $columns
     *
     * @return self
     */
    public function root(array $columns = ['*'])
    {
        return $this->whereIsRoot()->first($columns);
    }
}


<?php

namespace Neutrino\Database\Migrations\Storage\Database;

use Neutrino\Model;
use Phalcon\Db\Column;

/**
 * Class MigrationModel
 *
 * @package Neutrino\Database\Migrations\Storage\Database
 */
class MigrationModel extends Model
{
    public $id;

    public $migration;

    public $batch;

    /**
     * Initializes metaDatas & columnsMap if they are not.
     */
    public function initialize()
    {
        parent::initialize();

        $this->setSource('migrations');

        $this->primary('id', Column::TYPE_INTEGER, [
            'unsigned'      => true,
            'autoIncrement' => true
        ]);

        $this->column('migration', Column::TYPE_VARCHAR);
        $this->column('batch', Column::TYPE_INTEGER);
    }
}


<?php

namespace Igniter\Flame\Database\Traits;

use Exception;

/**
 * Purgeable model trait
 *
 * Usage:
 **
 * In the model class definition:
 *
 *   use \Igniter\Flame\Database\Traits\Purgeable;
 *
 * You must set attribute names which should not be saved to the database:
 *
 *   protected $purgeable = ['password'];
 */
trait Purgeable
{
    /**
     * @var array List of original attribute values before they were purged.
     */
    protected $originalPurgeableValues = [];

    /**
     * Boot the purgeable trait for a model.
     * @return void
     * @throws \Exception
     */
    public static function bootPurgeable()
    {
        if (!property_exists(get_called_class(), 'purgeable'))
            throw new Exception(sprintf(
                'You must define a $purgeable property in %s to use the Purgeable trait.', get_called_class()
            ));

        /*
         * Remove any purge attributes from the data set
         */
        static::extend(function ($model) {
            $model->bindEvent('model.saveInternal', function () use ($model) {
                $model->purgeAttributes();
            });
        });
    }

    /**
     * Adds an attribute to the purgeable attributes list
     *
     * @param  array|string|null $attributes
     *
     * @return $this
     */
    public function addPurgeable($attributes = null)
    {
        $attributes = is_array($attributes) ? $attributes : func_get_args();

        $this->purgeable = array_merge($this->purgeable, $attributes);

        return $this;
    }

    /**
     * Removes purged attributes, used before saving.
     *
     * @param null $attributesToPurge
     *
     * @return array Clean attributes
     */
    public function purgeAttributes($attributesToPurge = null)
    {
        if ($attributesToPurge !== null) {
            $purgeable = is_array($attributesToPurge) ? $attributesToPurge : [$attributesToPurge];
        }
        else {
            $purgeable = $this->getPurgeableAttributes();
        }

        $attributes = $this->getAttributes();
        $cleanAttributes = array_diff_key($attributes, array_flip($purgeable));
        $originalAttributes = array_diff_key($attributes, $cleanAttributes);

        if (is_array($this->originalPurgeableValues)) {
            $this->originalPurgeableValues = array_merge($this->originalPurgeableValues, $originalAttributes);
        }
        else {
            $this->originalPurgeableValues = $originalAttributes;
        }

        return $this->attributes = $cleanAttributes;
    }

    /**
     * Returns a collection of fields to be hashed.
     */
    public function getPurgeableAttributes()
    {
        return $this->purgeable;
    }

    /**
     * Returns the original values.
     */
    public function getOriginalPurgeValues()
    {
        return $this->originalPurgeableValues;
    }

    /**
     * Returns the original value of a single attribute.
     *
     * @param $attribute
     *
     * @return null
     */
    public function getOriginalPurgeValue($attribute)
    {
        return isset($this->originalPurgeableValues[$attribute])
            ? $this->originalPurgeableValues[$attribute]
            : null;
    }

    /**
     * Restores the original values.
     */
    public function restorePurgedValues()
    {
        $this->attributes = array_merge($this->getAttributes(), $this->originalPurgeableValues);

        return $this;
    }
}


<?php

namespace Neutrino\HttpClient\Contract\Streaming;

use Phalcon\Events\Manager;
use Phalcon\Events\ManagerInterface;

/**
 * Trait Streamize
 *
 * @package Neutrino\HttpClient\Contract\Streaming
 */
trait Streamize
{
    /** @var Manager */
    private $eventManager;

    /** @var int|null */
    protected $bufferSize;

    /**
     * Sets the events manager
     *
     * @param ManagerInterface $eventsManager
     */
    public function setEventsManager(ManagerInterface $eventsManager)
    {
        $this->eventManager = $eventsManager;
    }

    /**
     * @return \Phalcon\Events\Manager
     */
    public function getEventsManager()
    {
        if (!isset($this->eventManager)) {
            $this->setEventsManager(new Manager());
        }

        return $this->eventManager;
    }

    /**
     * @param string $event
     * @param \Closure $callback
     *
     * @return $this
     */
    public function on($event, $callback)
    {
        $this->checkEvent($event);

        $this->getEventsManager()->attach($event, $callback);

        return $this;
    }

    /**
     * @param string $event
     * @param \Closure $callback
     *
     * @return $this
     */
    public function off($event, $callback)
    {
        $this->checkEvent($event);

        $this->getEventsManager()->detach($event, $callback);

        return $this;
    }

    /**
     * @param int $size
     *
     * @return $this
     */
    public function setBufferSize($size)
    {
        $this->bufferSize = $size;

        return $this;
    }

    /**
     * @param string $event
     */
    private function checkEvent($event)
    {
        if ($event == Streamable::EVENT_START
            || $event == Streamable::EVENT_PROGRESS
            || $event == Streamable::EVENT_FINISH
        ) {
            return;
        }

        throw new \RuntimeException(static::class . ' only support ' . implode(', ',
                [
                    Streamable::EVENT_START,
                    Streamable::EVENT_PROGRESS,
                    Streamable::EVENT_FINISH,
                ]));
    }
}


<?php

namespace Igniter\Flame\Setting;

use Illuminate\Support\Arr;

abstract class SettingStore
{
    /**
     * The settings items.
     * @var array
     */
    protected $items = [];

    /**
     * Whether the store has changed since it was last loaded.
     * @var boolean
     */
    protected $unsaved = FALSE;

    /**
     * Whether the settings data are loaded.
     * @var boolean
     */
    protected $loaded = FALSE;

    /**
     * Get a specific key from the settings data.
     *
     * @param  string|array $key
     * @param  mixed $default Optional default value.
     *
     * @return mixed
     */
    public function get($key, $default = null)
    {
        $this->load();

        return Arr::get($this->items, $key, $default);
    }

    /**
     * Determine if a key exists in the settings data.
     *
     * @param  string $key
     *
     * @return boolean
     */
    public function has($key)
    {
        $this->load();

        return Arr::has($this->items, $key);
    }

    /**
     * Set a specific key to a value in the settings data.
     *
     * @param string|array $key Key string or associative array of key => value
     * @param mixed $value Optional only if the first argument is an array
     */
    public function set($key, $value = null)
    {
        $this->load();
        $this->unsaved = TRUE;

        if (is_array($key)) {
            foreach ($key as $k => $v) {
                Arr::set($this->items, $k, $v);
            }
        }
        else {
            Arr::set($this->items, $key, $value);
        }

        return $this;
    }

    /**
     * Unset a key in the settings data.
     *
     * @param  string $key
     */
    public function forget($key)
    {
        $this->unsaved = TRUE;

        if ($this->has($key)) {
            Arr::forget($this->items, $key);
        }
    }

    /**
     * Unset all keys in the settings data.
     * @return void
     */
    public function forgetAll()
    {
        $this->unsaved = TRUE;
        $this->items = [];
    }

    /**
     * Get all settings data.
     * @return array
     */
    public function all()
    {
        $this->load();

        return $this->items;
    }

    /**
     * Save any changes done to the settings data.
     * @return void
     */
    public function save()
    {
        if (!$this->unsaved) {
            // either nothing has been changed, or data has not been loaded, so
            // do nothing by returning early
            return;
        }

        $this->write($this->items);
        $this->unsaved = FALSE;
    }

    /**
     * Make sure data is loaded.
     *
     * @param bool $force Force a reload of data. Default false.
     */
    public function load($force = FALSE)
    {
        if (!$this->loaded || $force) {
            $this->items = $this->read();
            $this->loaded = TRUE;
        }
    }

    /**
     * Read the data from the store.
     * @return array
     */
    abstract protected function read();

    /**
     * Write the data into the store.
     *
     * @param  array $data
     *
     * @return void
     */
    abstract protected function write(array $data);
}


<?php

namespace Neutrino\View\Engines\Volt\Compiler\Extensions;

use Neutrino\Debug\Reflexion;
use Neutrino\Support\Str;
use Neutrino\View\Engines\Volt\Compiler\ExtensionExtend;

/**
 * Class StrExtension
 *
 * Neutrino\View\Extensions
 */
class StrExtension extends ExtensionExtend
{
    /**
     * This method is called on any attempt to compile a function call
     *
     * @param $name
     * @param $arguments
     *
     * @return null|string
     */
    public function compileFunction($name, $arguments, $funcArguments)
    {
        if (!Str::startsWith($name, 'str_') || function_exists($name)) {
            return null;
        }

        $name = substr($name, 4);

        if (method_exists(Str::class, $name) && Reflexion::getReflectionMethod(Str::class, $name)->isPublic()) {
            return Str::class . '::' . $name . '(' . $arguments . ')';
        }

        return null;
    }

    public function compileFilter($name, $arguments, $funcArguments)
    {
        switch ($name) {
            case 'slug':
                return Str::class . '::slug(' . $arguments . ')';
            case 'limit':
                return Str::class . '::limit(' . $arguments . ')';
            case 'words':
                return Str::class . '::words(' . $arguments . ')';
        }

        return null;
    }

    public function resolveExpression($expr)
    {
    }

    public function compileStatement($statement)
    {
    }
}


<?php

namespace Neutrino\Auth\Middleware;

use Neutrino\Constants\Services;
use Neutrino\Foundation\Middleware\Controller as ControllerMiddleware;
use Neutrino\Interfaces\Middleware\BeforeInterface;
use Phalcon\Events\Event;

/**
 * Class Authenticate
 *
 * @package Neutrino\Auth\Middleware
 */
class Authenticate extends ControllerMiddleware implements BeforeInterface
{

    /**
     * Called before the execution of handler
     *
     * @param \Phalcon\Events\Event     $event
     * @param \Phalcon\Dispatcher|mixed $source
     * @param mixed|null                $data
     *
     * @throws \Exception
     * @return bool
     */
    public function before(Event $event, $source, $data = null)
    {
        if ($this->{Services::AUTH}->check()) {
            return true;
        }

        $this->response->setStatusCode(401, 'Unauthorized');

        return false;
    }
}


<?php

namespace Neutrino\Foundation\Cli\Tasks;

use Neutrino\Cli\Output\Decorate;
use Neutrino\Cli\Task;
use Neutrino\Optimizer\Composer;
use Neutrino\PhpPreloader\Exceptions\DirConstantException;
use Neutrino\PhpPreloader\Exceptions\FileConstantException;
use Neutrino\PhpPreloader\Factory;
use Neutrino\Support\Path;

/**
 * Class OptimizeTask
 *
 * @package Neutrino\Foundation\Cli
 */
class OptimizeTask extends Task
{
    /**
     * @var Composer
     */
    private $optimizer;

    private $compileTasks = [
        ConfigCacheTask::class,
        DotconstCacheTask::class,
        RouteCacheTask::class
    ];

    /**
     * Runs all optimization
     *
     * @description Runs all optimization.
     *
     * @option      -m, --memory: Generate a memory optimized autoloader.
     * @option      -f, --force: Force optimization.
     */
    public function mainAction()
    {
        if (APP_DEBUG && !$this->hasOption('f', 'force')) {
            $this->info('Application is in debug mode.');
            $this->info('For optimize in debug please use the --force, -f option.');

            return;
        }

        $this->optimizer = $this->getDI()->get(Composer::class, [
            BASE_PATH . '/bootstrap/compile/loader.php',
            BASE_PATH . '/vendor/composer',
            BASE_PATH
        ]);

        if ($this->hasOption('m', 'memory')) {
            $this->optimizeMemory();
        } else {
            $this->optimizeProcess();
        }

        $this->optimizeClass();

        foreach ($this->compileTasks as $compileTask) {
            $this->application->handle([
                'task' => $compileTask
            ]);
        }
    }

    /**
     * Build an memory optimized autoloader
     *
     * @return bool|int
     */
    protected function optimizeMemory()
    {
        $this->output->write(Decorate::notice(str_pad('Generating memory optimized auto-loader', 40, ' ')), false);

        $return = $this->optimizer->optimizeMemory();

        if ($return) {
            $this->info('Success');
        } else {
            $this->error('Error');
        }

        return $return;
    }

    /**
     * Build an process optimized autoloader
     *
     * @return bool|int
     */
    protected function optimizeProcess()
    {
        $this->output->write(Decorate::notice(str_pad('Generating optimized auto-loader', 40, ' ')), false);

        $return = $this->optimizer->optimizeProcess();

        if ($return) {
            $this->info('Success');
        } else {
            $this->error('Error');
        }

        return $return;
    }

    protected function optimizeClass()
    {
        $this->output->write(Decorate::notice(str_pad('Compiling common classes', 40, ' ')), false);

        $outputFile = BASE_PATH . '/bootstrap/compile/compile.php';
        $compileConfigFile = BASE_PATH . '/config/compile.php';

        $preloader = (new Factory())->create();

        $handle = $preloader->prepareOutput($outputFile);

        $files = require __DIR__ . '/Optimize/compile.php';

        if (file_exists($compileConfigFile)) {
            $files = array_unique(array_map(function ($path) {
                return Path::normalize($path);
            }, array_merge($files, require $compileConfigFile)));
        }

        try {
            $parts = [];

            foreach ($files as $file) {
                $file = Path::normalize($file);

                try {
                    $stmts = $preloader->parse($file);
                    $stmts = $preloader->traverse($stmts);

                    $parts = array_merge($parts, $stmts);
                } catch (DirConstantException $e) {
                    $this->block([
                        "Usage of __DIR__ constant is prohibited. Use BASE_PATH . '/path' instead.",
                        "in : $file"
                    ], 'error');
                } catch (FileConstantException $e) {
                    $this->block([
                        "Usage of __FILE__ constant is prohibited. Use BASE_PATH . '/path' instead.",
                        "in : $file"
                    ], 'error');
                } catch (\Exception $e) {
                    $this->block([
                        $e->getMessage(),
                        "in : $file"
                    ], 'error');
                }
            }

            fwrite($handle, $preloader->prettyPrint($parts) . PHP_EOL);

            $this->info("Success");
        } catch (\Exception $e) {
            $this->error("Error");
            $this->block([$e->getMessage()], 'error');

        } finally {
            if (isset($r) && is_resource($r)) {
                fclose($r);
            }
            if (isset($e)) {
                @unlink($outputFile);
            }
        }
    }
}


<?php

namespace Neutrino\Middleware;

use Neutrino\Constants\Services;
use Neutrino\Foundation\Middleware\Controller as ControllerMiddleware;
use Neutrino\Http\Standards\StatusCode;
use Neutrino\Interfaces\Middleware\AfterInterface;
use Neutrino\Interfaces\Middleware\BeforeInterface;
use Neutrino\Security\RateLimiter;
use Phalcon\Events\Event;

/**
 * Class Throttle
 *
 * @package Neutrino\Middleware
 */
abstract class Throttle extends ControllerMiddleware implements BeforeInterface, AfterInterface
{
    /**
     * Name of the throttle limiter.
     *
     * @var string
     */
    protected $name;

    /**
     * Number of max request by $decay
     *
     * @var int
     */
    private $max;

    /**
     * Decay time (seconds)
     *
     * @var int
     */
    private $decay;

    /**
     * The Rate Limiter
     *
     * @var \Neutrino\Security\RateLimiter
     */
    private $limiter;

    /**
     * Throttle constructor.
     *
     * @param string $controllerClass
     * @param int    $max   Number of max request by $decay
     * @param int    $decay Decay time (seconds)
     */
    public function __construct($controllerClass, $max, $decay = 60)
    {
        parent::__construct($controllerClass);

        if (!isset($this->name)) {
            throw new \RuntimeException(static::class . '->name is empty.');
        }

        $this->max = $max;
        $this->decay = $decay;
    }

    /**
     * Called before the execution of handler
     *
     * @param \Phalcon\Events\Event|mixed $event
     * @param \Phalcon\Dispatcher|mixed   $source
     * @param mixed|null                  $data
     *
     * @throws \Exception
     * @return bool
     */
    public function before(Event $event, $source, $data = null)
    {
        $signature = $this->resolveRequestSignature();

        $limiter = $this->getLimiter();

        if ($limiter->tooManyAttempts($signature, $this->max, $this->decay)) {
            $this->addHeader($signature, true);

            return false;
        }

        $limiter->hit($signature, $this->decay);

        $this->addHeader($this->resolveRequestSignature(), false);

        return true;
    }

    /**
     * Called after the execution of handler
     *
     * @param \Phalcon\Events\Event|mixed $event
     * @param \Phalcon\Dispatcher|mixed   $source
     * @param mixed|null                  $data
     *
     * @throws \Exception
     * @return bool
     */
    public function after(Event $event, $source, $data = null)
    {
        $this->addHeader($this->resolveRequestSignature(), false);

        return true;
    }

    /**
     * Resolve the request signature based on :
     *  Module : Namespace : Controller : Action | HOST | URI | ClientIP
     *
     * @return string
     */
    protected function resolveRequestSignature()
    {
        /** @var \Phalcon\Http\Request $request */
        $request = $this->getDI()->getShared(Services::REQUEST);
        /** @var \Phalcon\Mvc\Router $router */
        $router = $this->getDI()->getShared(Services::ROUTER);

        return crc32(
            $router->getModuleName() .
            ':' . $router->getNamespaceName() .
            ':' . $router->getControllerName() .
            ':' . $router->getActionName() .
            '|' . $request->getHttpHost() .
            '|' . $request->getURI() .
            '|' . $request->getClientAddress()
        );
    }

    /**
     * Add the limit header information to the response.
     *
     * @param string $signature
     * @param bool   $tooManyAttempts Bind specific values when there are too many attempts
     */
    protected function addHeader($signature, $tooManyAttempts = false)
    {
        /** @var \Phalcon\Http\Response $response */
        $response = $this->getDI()->getShared(Services::RESPONSE);

        $limiter = $this->getLimiter();

        $response->setHeader('X-RateLimit-Limit', $this->max);
        if ($tooManyAttempts) {
            $msg = StatusCode::message(StatusCode::TOO_MANY_REQUESTS);

            $response
                ->setContent($msg)
                ->setStatusCode(StatusCode::TOO_MANY_REQUESTS, $msg)
                ->setHeader('X-RateLimit-Remaining', 0)
                ->setHeader(
                'Retry-After',
                $limiter->availableIn($signature, $this->decay)
            );
        } else {
            $response->setHeader(
                'X-RateLimit-Remaining',
                $limiter->retriesLeft($signature, $this->max, $this->decay)
            );
        }
    }

    /**
     * Bind and return the limiter instance.
     *
     * @return \Neutrino\Security\RateLimiter
     */
    protected function getLimiter()
    {
        if (!isset($this->limiter)) {
            $this->limiter = $this->getDI()->get(RateLimiter::class, [$this->name]);
        }

        return $this->limiter;
    }
}


<?php

namespace Neutrino\View\Engines\Volt;

use Neutrino\Constants\Env;
use Neutrino\Constants\Services;
use Neutrino\View\Engines\EngineRegister;
use Phalcon\Mvc\View\Engine\Volt;

/**
 * Class VoltEngineRegister
 *
 * @package Neutrino\View\Engines\Volt
 */
class VoltEngineRegister extends EngineRegister
{

    /**
     * @param $view
     * @param $di
     *
     * @return \Phalcon\Mvc\View\Engine
     */
    public function register($view, $di)
    {
        /* @var \Phalcon\Di $di */
        $volt = new Volt($view, $di);

        $config = $di->getShared(Services::CONFIG)->view;

        $options = array_merge(
            [
                'compiledPath' => $config->compiled_path,
                'compiledSeparator' => '_',
                'compileAlways' => APP_ENV === Env::DEVELOPMENT || APP_DEBUG,
            ],
            isset($config->options) ? (array)$config->options : []
        );

        $volt->setOptions($options);

        $compiler = $volt->getCompiler();

        $extensions = isset($config->extensions) ? $config->extensions : [];
        foreach ($extensions as $extension) {
            $compiler->addExtension(new $extension($compiler));
        }

        $filters = isset($config->filters) ? $config->filters : [];
        foreach ($filters as $name => $filter) {
            $filter = new $filter($compiler);
            $compiler->addFilter($name, function ($resolvedArgs, $exprArgs) use ($filter) {
                /* @var \Neutrino\View\Engines\Volt\Compiler\FilterExtend $filter */
                return $filter->compileFilter($resolvedArgs, $exprArgs);
            });
        }

        $compiler->addFunction('dump', 'Neutrino\Debug\VarDump::dump');
        $functions = isset($config->functions) ? $config->functions : [];
        foreach ($functions as $name => $function) {
            $function = new $function($compiler);
            $compiler->addFunction($name, function ($resolvedArgs, $exprArgs) use ($function) {
                /* @var \Neutrino\View\Engines\Volt\Compiler\FunctionExtend $function */
                return $function->compileFunction($resolvedArgs, $exprArgs);
            });
        }

        return $volt;
    }
}