<?php

namespace Igniter\Flame\Html;

use BadMethodCallException;
use DateTime;
use Illuminate\Contracts\Routing\UrlGenerator;
use Illuminate\Contracts\Session\Session;
use Illuminate\Contracts\View\Factory;
use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\HtmlString;
use Illuminate\Support\Traits\Macroable;

class FormBuilder
{
    use Macroable, Componentable {
        Macroable::__call as macroCall;
        Componentable::__call as componentCall;
    }

    /**
     * The HTML builder instance.
     *
     * @var HtmlBuilder
     */
    protected $html;

    /**
     * The URL generator instance.
     *
     * @var \Illuminate\Contracts\Routing\UrlGenerator
     */
    protected $url;

    /**
     * The View factory instance.
     *
     * @var \Illuminate\Contracts\View\Factory
     */
    protected $view;

    /**
     * The CSRF token used by the form builder.
     *
     * @var string
     */
    protected $csrfToken;

    /**
     * The session store implementation.
     *
     * @var \Illuminate\Contracts\Session\Session
     */
    protected $session;

    /**
     * The current model instance for the form.
     *
     * @var mixed
     */
    protected $model;

    /**
     * An array of label names we've created.
     *
     * @var array
     */
    protected $labels = [];

    protected $request;

    /**
     * The reserved form open attributes.
     *
     * @var array
     */
    protected $reserved = ['method', 'url', 'route', 'action', 'files'];

    /**
     * The form methods that should be spoofed, in uppercase.
     *
     * @var array
     */
    protected $spoofedMethods = ['DELETE', 'PATCH', 'PUT'];

    /**
     * The types of inputs to not fill values on by default.
     *
     * @var array
     */
    protected $skipValueTypes = ['file', 'password', 'checkbox', 'radio'];

    /**
     * Input Type.
     *
     * @var null
     */
    protected $type = null;

    /**
     * Create a new form builder instance.
     *
     * @param \Igniter\Flame\Html\HtmlBuilder $html
     * @param  \Illuminate\Contracts\Routing\UrlGenerator $url
     * @param  \Illuminate\Contracts\View\Factory $view
     * @param  string $csrfToken
     * @param \Illuminate\Http\Request|null $request
     */
    public function __construct(HtmlBuilder $html, UrlGenerator $url, Factory $view, $csrfToken, Request $request = null)
    {
        $this->url = $url;
        $this->html = $html;
        $this->view = $view;
        $this->csrfToken = $csrfToken;
        $this->request = $request;
    }

    /**
     * Open up a new HTML form.
     *
     * @param  array $options
     *
     * @return \Illuminate\Support\HtmlString
     */
    public function open(array $options = [])
    {
        $method = array_get($options, 'method', 'post');

        // We need to extract the proper method from the attributes. If the method is
        // something other than GET or POST we'll use POST since we will spoof the
        // actual method since forms don't support the reserved methods in HTML.
        $attributes['method'] = $this->getMethod($method);

        $attributes['action'] = $this->getAction($options);

        $attributes['accept-charset'] = 'UTF-8';

        // If the method is PUT, PATCH or DELETE we will need to add a spoofer hidden
        // field that will instruct the Symfony request to pretend the method is a
        // different method than it actually is, for convenience from the forms.
        $append = $this->getAppendage($method);

        if (isset($options['files']) && $options['files']) {
            $options['enctype'] = 'multipart/form-data';
        }

        // Finally we're ready to create the final form HTML field. We will attribute
        // format the array of attributes. We will also add on the appendage which
        // is used to spoof requests for this PUT, PATCH, etc. methods on forms.
        $attributes = array_merge(

            $attributes, array_except($options, $this->reserved)

        );

        // Finally, we will concatenate all of the attributes into a single string so
        // we can build out the final form open statement. We'll also append on an
        // extra value for the hidden _method field if it's needed for the form.
        $attributes = $this->html->attributes($attributes);

        return $this->toHtmlString('<form'.$attributes.'>'.$append);
    }

    /**
     * Create a new model based form builder.
     *
     * @param  mixed $model
     * @param  array $options
     *
     * @return \Illuminate\Support\HtmlString
     */
    public function model($model, array $options = [])
    {
        $this->model = $model;

        return $this->open($options);
    }

    /**
     * Set the model instance on the form builder.
     *
     * @param  mixed $model
     *
     * @return void
     */
    public function setModel($model)
    {
        $this->model = $model;
    }

    /**
     * Close the current form.
     *
     * @return string
     */
    public function close()
    {
        $this->labels = [];

        $this->model = null;

        return $this->toHtmlString('</form>');
    }

    /**
     * Generate a hidden field with the current CSRF token.
     *
     * @return string
     */
    public function token()
    {
        $token = !empty($this->csrfToken) ? $this->csrfToken : $this->session->token();

        return $this->hidden('_token', $token);
    }

    /**
     * Create a form label element.
     *
     * @param  string $name
     * @param  string $value
     * @param  array $options
     * @param  bool $escape_html
     *
     * @return \Illuminate\Support\HtmlString
     */
    public function label($name, $value = null, $options = [], $escape_html = TRUE)
    {
        $this->labels[] = $name;

        $options = $this->html->attributes($options);

        $value = $this->formatLabel($name, $value);

        if ($escape_html) {
            $value = $this->html->entities($value);
        }

        return $this->toHtmlString('<label for="'.$name.'"'.$options.'>'.$value.'</label>');
    }

    /**
     * Format the label value.
     *
     * @param  string $name
     * @param  string|null $value
     *
     * @return string
     */
    protected function formatLabel($name, $value)
    {
        return $value ?: ucwords(str_replace('_', ' ', $name));
    }

    /**
     * Create a form input field.
     *
     * @param  string $type
     * @param  string $name
     * @param  string $value
     * @param  array $options
     *
     * @return \Illuminate\Support\HtmlString
     */
    public function input($type, $name, $value = null, $options = [])
    {
        $this->type = $type;

        if (!isset($options['name'])) {
            $options['name'] = $name;
        }

        // We will get the appropriate value for the given field. We will look for the
        // value in the session for the value in the old input data then we'll look
        // in the model instance if one is set. Otherwise we will just use empty.
        $id = $this->getIdAttribute($name, $options);

        if (!in_array($type, $this->skipValueTypes)) {
            $value = $this->getValueAttribute($name, $value);
        }

        // Once we have the type, value, and ID we can merge them into the rest of the
        // attributes array so we can convert them into their HTML attribute format
        // when creating the HTML element. Then, we will return the entire input.
        $merge = compact('type', 'value', 'id');

        $options = array_merge($options, $merge);

        return $this->toHtmlString('<input'.$this->html->attributes($options).'>');
    }

    /**
     * Create a text input field.
     *
     * @param  string $name
     * @param  string $value
     * @param  array $options
     *
     * @return \Illuminate\Support\HtmlString
     */
    public function text($name, $value = null, $options = [])
    {
        return $this->input('text', $name, $value, $options);
    }

    /**
     * Create a password input field.
     *
     * @param  string $name
     * @param  array $options
     *
     * @return \Illuminate\Support\HtmlString
     */
    public function password($name, $options = [])
    {
        return $this->input('password', $name, '', $options);
    }

    /**
     * Create a hidden input field.
     *
     * @param  string $name
     * @param  string $value
     * @param  array $options
     *
     * @return \Illuminate\Support\HtmlString
     */
    public function hidden($name, $value = null, $options = [])
    {
        return $this->input('hidden', $name, $value, $options);
    }

    /**
     * Create a search input field.
     *
     * @param  string $name
     * @param  string $value
     * @param  array $options
     *
     * @return \Illuminate\Support\HtmlString
     */
    public function search($name, $value = null, $options = [])
    {
        return $this->input('search', $name, $value, $options);
    }

    /**
     * Create an e-mail input field.
     *
     * @param  string $name
     * @param  string $value
     * @param  array $options
     *
     * @return \Illuminate\Support\HtmlString
     */
    public function email($name, $value = null, $options = [])
    {
        return $this->input('email', $name, $value, $options);
    }

    /**
     * Create a tel input field.
     *
     * @param  string $name
     * @param  string $value
     * @param  array $options
     *
     * @return \Illuminate\Support\HtmlString
     */
    public function tel($name, $value = null, $options = [])
    {
        return $this->input('tel', $name, $value, $options);
    }

    /**
     * Create a number input field.
     *
     * @param  string $name
     * @param  string $value
     * @param  array $options
     *
     * @return \Illuminate\Support\HtmlString
     */
    public function number($name, $value = null, $options = [])
    {
        return $this->input('number', $name, $value, $options);
    }

    /**
     * Create a date input field.
     *
     * @param  string $name
     * @param  string $value
     * @param  array $options
     *
     * @return \Illuminate\Support\HtmlString
     */
    public function date($name, $value = null, $options = [])
    {
        if ($value instanceof DateTime) {
            $value = $value->format('Y-m-d');
        }

        return $this->input('date', $name, $value, $options);
    }

    /**
     * Create a datetime input field.
     *
     * @param  string $name
     * @param  string $value
     * @param  array $options
     *
     * @return \Illuminate\Support\HtmlString
     */
    public function datetime($name, $value = null, $options = [])
    {
        if ($value instanceof DateTime) {
            $value = $value->format(DateTime::RFC3339);
        }

        return $this->input('datetime', $name, $value, $options);
    }

    /**
     * Create a datetime-local input field.
     *
     * @param  string $name
     * @param  string $value
     * @param  array $options
     *
     * @return \Illuminate\Support\HtmlString
     */
    public function datetimeLocal($name, $value = null, $options = [])
    {
        if ($value instanceof DateTime) {
            $value = $value->format('Y-m-d\TH:i');
        }

        return $this->input('datetime-local', $name, $value, $options);
    }

    /**
     * Create a time input field.
     *
     * @param  string $name
     * @param  string $value
     * @param  array $options
     *
     * @return \Illuminate\Support\HtmlString
     */
    public function time($name, $value = null, $options = [])
    {
        return $this->input('time', $name, $value, $options);
    }

    /**
     * Create a url input field.
     *
     * @param  string $name
     * @param  string $value
     * @param  array $options
     *
     * @return \Illuminate\Support\HtmlString
     */
    public function url($name, $value = null, $options = [])
    {
        return $this->input('url', $name, $value, $options);
    }

    /**
     * Create a file input field.
     *
     * @param  string $name
     * @param  array $options
     *
     * @return \Illuminate\Support\HtmlString
     */
    public function file($name, $options = [])
    {
        return $this->input('file', $name, null, $options);
    }

    /**
     * Create a textarea input field.
     *
     * @param  string $name
     * @param  string $value
     * @param  array $options
     *
     * @return \Illuminate\Support\HtmlString
     */
    public function textarea($name, $value = null, $options = [])
    {
        $this->type = 'textarea';

        if (!isset($options['name'])) {
            $options['name'] = $name;
        }

        // Next we will look for the rows and cols attributes, as each of these are put
        // on the textarea element definition. If they are not present, we will just
        // assume some sane default values for these attributes for the developer.
        $options = $this->setTextAreaSize($options);

        $options['id'] = $this->getIdAttribute($name, $options);

        $value = (string)$this->getValueAttribute($name, $value);

        unset($options['size']);

        // Next we will convert the attributes into a string form. Also we have removed
        // the size attribute, as it was merely a short-cut for the rows and cols on
        // the element. Then we'll create the final textarea elements HTML for us.
        $options = $this->html->attributes($options);

        return $this->toHtmlString('<textarea'.$options.'>'.e($value).'</textarea>');
    }

    /**
     * Set the text area size on the attributes.
     *
     * @param  array $options
     *
     * @return array
     */
    protected function setTextAreaSize($options)
    {
        if (isset($options['size'])) {
            return $this->setQuickTextAreaSize($options);
        }

        // If the "size" attribute was not specified, we will just look for the regular
        // columns and rows attributes, using sane defaults if these do not exist on
        // the attributes array. We'll then return this entire options array back.
        $cols = array_get($options, 'cols', 50);

        $rows = array_get($options, 'rows', 10);

        return array_merge($options, compact('cols', 'rows'));
    }

    /**
     * Set the text area size using the quick "size" attribute.
     *
     * @param  array $options
     *
     * @return array
     */
    protected function setQuickTextAreaSize($options)
    {
        $segments = explode('x', $options['size']);

        return array_merge($options, ['cols' => $segments[0], 'rows' => $segments[1]]);
    }

    /**
     * Create a select box field.
     *
     * @param  string $name
     * @param  array $list
     * @param  string $selected
     * @param  array $selectAttributes
     * @param  array $optionsAttributes
     *
     * @return \Illuminate\Support\HtmlString
     */
    public function select(
        $name,
        $list = [],
        $selected = null,
        array $selectAttributes = [],
        array $optionsAttributes = []
    )
    {
        $this->type = 'select';

        // When building a select box the "value" attribute is really the selected one
        // so we will use that when checking the model or session for a value which
        // should provide a convenient method of re-populating the forms on post.
        $selected = $this->getValueAttribute($name, $selected);

        $selectAttributes['id'] = $this->getIdAttribute($name, $selectAttributes);

        if (!isset($selectAttributes['name'])) {
            $selectAttributes['name'] = $name;
        }

        // We will simply loop through the options and build an HTML value for each of
        // them until we have an array of HTML declarations. Then we will join them
        // all together into one single HTML element that can be put on the form.
        $html = [];

        if (isset($selectAttributes['placeholder'])) {
            $html[] = $this->placeholderOption($selectAttributes['placeholder'], $selected);
            unset($selectAttributes['placeholder']);
        }

        foreach ($list as $value => $display) {
            $optionAttributes = isset($optionsAttributes[$value]) ? $optionsAttributes[$value] : [];
            $html[] = $this->getSelectOption($display, $value, $selected, $optionAttributes);
        }

        // Once we have all of this HTML, we can join this into a single element after
        // formatting the attributes into an HTML "attributes" string, then we will
        // build out a final select statement, which will contain all the values.
        $selectAttributes = $this->html->attributes($selectAttributes);

        $list = implode('', $html);

        return $this->toHtmlString("<select{$selectAttributes}>{$list}</select>");
    }

    /**
     * Create a select range field.
     *
     * @param  string $name
     * @param  string $begin
     * @param  string $end
     * @param  string $selected
     * @param  array $options
     *
     * @return \Illuminate\Support\HtmlString
     */
    public function selectRange($name, $begin, $end, $selected = null, $options = [])
    {
        $range = array_combine($range = range($begin, $end), $range);

        return $this->select($name, $range, $selected, $options);
    }

    /**
     * Create a select year field.
     *
     * @return mixed
     */
    public function selectYear()
    {
        return call_user_func_array([$this, 'selectRange'], func_get_args());
    }

    /**
     * Create a select month field.
     *
     * @param  string $name
     * @param  string $selected
     * @param  array $options
     * @param  string $format
     *
     * @return \Illuminate\Support\HtmlString
     */
    public function selectMonth($name, $selected = null, $options = [], $format = '%B')
    {
        $months = [];

        foreach (range(1, 12) as $month) {
            $months[$month] = strftime($format, mktime(0, 0, 0, $month, 1));
        }

        return $this->select($name, $months, $selected, $options);
    }

    /**
     * Get the select option for the given value.
     *
     * @param  string $display
     * @param  string $value
     * @param  string $selected
     * @param  array $attributes
     *
     * @return \Illuminate\Support\HtmlString
     */
    public function getSelectOption($display, $value, $selected, array $attributes = [])
    {
        if (is_array($display)) {
            return $this->optionGroup($display, $value, $selected, $attributes);
        }

        return $this->option($display, $value, $selected, $attributes);
    }

    /**
     * Create an option group form element.
     *
     * @param  array $list
     * @param  string $label
     * @param  string $selected
     * @param  array $attributes
     *
     * @return \Illuminate\Support\HtmlString
     */
    protected function optionGroup($list, $label, $selected, array $attributes = [])
    {
        $html = [];

        foreach ($list as $value => $display) {
            $html[] = $this->option($display, $value, $selected, $attributes);
        }

        return $this->toHtmlString('<optgroup label="'.e($label).'">'.implode('', $html).'</optgroup>');
    }

    /**
     * Create a select element option.
     *
     * @param  string $display
     * @param  string $value
     * @param  string $selected
     * @param  array $attributes
     *
     * @return \Illuminate\Support\HtmlString
     */
    protected function option($display, $value, $selected, array $attributes = [])
    {
        $selected = $this->getSelectedValue($value, $selected);

        $options = ['value' => $value, 'selected' => $selected] + $attributes;

        return $this->toHtmlString('<option'.$this->html->attributes($options).'>'.e($display).'</option>');
    }

    /**
     * Create a placeholder select element option.
     *
     * @param $display
     * @param $selected
     *
     * @return \Illuminate\Support\HtmlString
     */
    protected function placeholderOption($display, $selected)
    {
        $selected = $this->getSelectedValue(null, $selected);

        $options = [
            'selected' => $selected,
            'value'    => '',
        ];

        return $this->toHtmlString('<option'.$this->html->attributes($options).'>'.e($display).'</option>');
    }

    /**
     * Determine if the value is selected.
     *
     * @param  string $value
     * @param  string $selected
     *
     * @return null|string
     */
    protected function getSelectedValue($value, $selected)
    {
        if (is_array($selected)) {
            return in_array($value, $selected, TRUE) OR in_array((string)$value, $selected, TRUE) ? 'selected' : null;
        }
        elseif ($selected instanceof Collection) {
            return $selected->contains($value) ? 'selected' : null;
        }

        return ((string)$value == (string)$selected) ? 'selected' : null;
    }

    /**
     * Create a checkbox input field.
     *
     * @param  string $name
     * @param  mixed $value
     * @param  bool $checked
     * @param  array $options
     *
     * @return \Illuminate\Support\HtmlString
     */
    public function checkbox($name, $value = 1, $checked = null, $options = [])
    {
        return $this->checkable('checkbox', $name, $value, $checked, $options);
    }

    /**
     * Create a radio button input field.
     *
     * @param  string $name
     * @param  mixed $value
     * @param  bool $checked
     * @param  array $options
     *
     * @return \Illuminate\Support\HtmlString
     */
    public function radio($name, $value = null, $checked = null, $options = [])
    {
        if (is_null($value)) {
            $value = $name;
        }

        return $this->checkable('radio', $name, $value, $checked, $options);
    }

    /**
     * Create a checkable input field.
     *
     * @param  string $type
     * @param  string $name
     * @param  mixed $value
     * @param  bool $checked
     * @param  array $options
     *
     * @return \Illuminate\Support\HtmlString
     */
    protected function checkable($type, $name, $value, $checked, $options)
    {
        $this->type = $type;

        $checked = $this->getCheckedState($type, $name, $value, $checked);

        if ($checked) {
            $options['checked'] = 'checked';
        }

        return $this->input($type, $name, $value, $options);
    }

    /**
     * Get the check state for a checkable input.
     *
     * @param  string $type
     * @param  string $name
     * @param  mixed $value
     * @param  bool $checked
     *
     * @return bool
     */
    protected function getCheckedState($type, $name, $value, $checked)
    {
        switch ($type) {
            case 'checkbox':
                return $this->getCheckboxCheckedState($name, $value, $checked);

            case 'radio':
                return $this->getRadioCheckedState($name, $value, $checked);

            default:
                return $this->getValueAttribute($name) == $value;
        }
    }

    /**
     * Get the check state for a checkbox input.
     *
     * @param  string $name
     * @param  mixed $value
     * @param  bool $checked
     *
     * @return bool
     */
    protected function getCheckboxCheckedState($name, $value, $checked)
    {
        $request = $this->request($name);

        if (isset($this->session) && !$this->oldInputIsEmpty() && is_null($this->old($name)) && !$request) {
            return FALSE;
        }

        if ($this->missingOldAndModel($name) && !$request) {
            return $checked;
        }

        $posted = $this->getValueAttribute($name, $checked);

        if (is_array($posted)) {
            return in_array($value, $posted);
        }
        elseif ($posted instanceof Collection) {
            return $posted->contains('id', $value);
        }
        else {
            return (bool)$posted;
        }
    }

    /**
     * Get the check state for a radio input.
     *
     * @param  string $name
     * @param  mixed $value
     * @param  bool $checked
     *
     * @return bool
     */
    protected function getRadioCheckedState($name, $value, $checked)
    {
        $request = $this->request($name);

        if ($this->missingOldAndModel($name) && !$request) {
            return $checked;
        }

        return $this->getValueAttribute($name) == $value;
    }

    /**
     * Determine if old input or model input exists for a key.
     *
     * @param  string $name
     *
     * @return bool
     */
    protected function missingOldAndModel($name)
    {
        return (is_null($this->old($name)) && is_null($this->getModelValueAttribute($name)));
    }

    /**
     * Create a HTML reset input element.
     *
     * @param  string $value
     * @param  array $attributes
     *
     * @return \Illuminate\Support\HtmlString
     */
    public function reset($value, $attributes = [])
    {
        return $this->input('reset', null, $value, $attributes);
    }

    /**
     * Create a HTML image input element.
     *
     * @param  string $url
     * @param  string $name
     * @param  array $attributes
     *
     * @return \Illuminate\Support\HtmlString
     */
    public function image($url, $name = null, $attributes = [])
    {
        $attributes['src'] = $this->url->asset($url);

        return $this->input('image', $name, null, $attributes);
    }

    /**
     * Create a color input field.
     *
     * @param  string $name
     * @param  string $value
     * @param  array $options
     *
     * @return \Illuminate\Support\HtmlString
     */
    public function color($name, $value = null, $options = [])
    {
        return $this->input('color', $name, $value, $options);
    }

    /**
     * Create a submit button element.
     *
     * @param  string $value
     * @param  array $options
     *
     * @return \Illuminate\Support\HtmlString
     */
    public function submit($value = null, $options = [])
    {
        return $this->input('submit', null, $value, $options);
    }

    /**
     * Create a button element.
     *
     * @param  string $value
     * @param  array $options
     *
     * @return \Illuminate\Support\HtmlString
     */
    public function button($value = null, $options = [])
    {
        if (!array_key_exists('type', $options)) {
            $options['type'] = 'button';
        }

        return $this->toHtmlString('<button'.$this->html->attributes($options).'>'.$value.'</button>');
    }

    /**
     * Parse the form action method.
     *
     * @param  string $method
     *
     * @return string
     */
    protected function getMethod($method)
    {
        $method = strtoupper($method);

        return $method != 'GET' ? 'POST' : $method;
    }

    /**
     * Get the form action from the options.
     *
     * @param  array $options
     *
     * @return string
     */
    protected function getAction(array $options)
    {
        // We will also check for a "route" or "action" parameter on the array so that
        // developers can easily specify a route or controller action when creating
        // a form providing a convenient interface for creating the form actions.
        if (isset($options['url'])) {
            return $this->getUrlAction($options['url']);
        }

        if (isset($options['route'])) {
            return $this->getRouteAction($options['route']);
        }

        // If an action is available, we are attempting to open a form to a controller
        // action route. So, we will use the URL generator to get the path to these
        // actions and return them from the method. Otherwise, we'll use current.
        elseif (isset($options['action'])) {
            return $this->getControllerAction($options['action']);
        }

        return $this->url->current();
    }

    /**
     * Get the action for a "url" option.
     *
     * @param  array|string $options
     *
     * @return string
     */
    protected function getUrlAction($options)
    {
        if (is_array($options)) {
            return $this->url->to($options[0], array_slice($options, 1));
        }

        return $this->url->to($options);
    }

    /**
     * Get the action for a "route" option.
     *
     * @param  array|string $options
     *
     * @return string
     */
    protected function getRouteAction($options)
    {
        if (is_array($options)) {
            return $this->url->route($options[0], array_slice($options, 1));
        }

        return $this->url->route($options);
    }

    /**
     * Get the action for an "action" option.
     *
     * @param  array|string $options
     *
     * @return string
     */
    protected function getControllerAction($options)
    {
        if (is_array($options)) {
            return $this->url->action($options[0], array_slice($options, 1));
        }

        return $this->url->action($options);
    }

    /**
     * Get the form appendage for the given method.
     *
     * @param  string $method
     *
     * @return string
     */
    protected function getAppendage($method)
    {
        list($method, $appendage) = [strtoupper($method), ''];

        // If the HTTP method is in this list of spoofed methods, we will attach the
        // method spoofer hidden input to the form. This allows us to use regular
        // form to initiate PUT and DELETE requests in addition to the typical.
        if (in_array($method, $this->spoofedMethods)) {
            $appendage .= $this->hidden('_method', $method);
        }

        // If the method is something other than GET we will go ahead and attach the
        // CSRF token to the form, as this can't hurt and is convenient to simply
        // always have available on every form the developers creates for them.
        if ($method != 'GET') {
            $appendage .= $this->token();
        }

        return $appendage;
    }

    /**
     * Get the ID attribute for a field name.
     *
     * @param  string $name
     * @param  array $attributes
     *
     * @return string
     */
    public function getIdAttribute($name, $attributes)
    {
        if (array_key_exists('id', $attributes)) {
            return $attributes['id'];
        }

        if (in_array($name, $this->labels)) {
            return $name;
        }
    }

    /**
     * Get the value that should be assigned to the field.
     *
     * @param  string $name
     * @param  string $value
     *
     * @return mixed
     */
    public function getValueAttribute($name, $value = null)
    {
        if (is_null($name)) {
            return $value;
        }

        $old = $this->old($name);

        if (!is_null($old) && $name != '_method') {
            return $old;
        }

        if (function_exists('app')) {
            $hasNullMiddleware = app("Illuminate\Contracts\Http\Kernel")
                ->hasMiddleware(ConvertEmptyStringsToNull::class);

            if ($hasNullMiddleware
                && is_null($old)
                && is_null($value)
                && !is_null($this->view->shared('errors'))
                && count($this->view->shared('errors')) > 0
            ) {
                return null;
            }
        }

        $request = $this->request($name);
        if (!is_null($request)) {
            return $request;
        }

        if (!is_null($value)) {
            return $value;
        }

        if (isset($this->model)) {
            return $this->getModelValueAttribute($name);
        }
    }

    /**
     * Get value from current Request
     *
     * @param $name
     *
     * @return array|null|string
     */
    protected function request($name)
    {
        if (!isset($this->request)) {
            return null;
        }

        return $this->request->input($this->transformKey($name));
    }

    /**
     * Get the model value that should be assigned to the field.
     *
     * @param  string $name
     *
     * @return mixed
     */
    protected function getModelValueAttribute($name)
    {
        $key = $this->transformKey($name);

        if (method_exists($this->model, 'getFormValue')) {
            return $this->model->getFormValue($key);
        }

        return data_get($this->model, $this->transformKey($name));
    }

    /**
     * Get a value from the session's old input.
     *
     * @param  string $name
     *
     * @return mixed
     */
    public function old($name)
    {
        if (isset($this->session)) {
            $key = $this->transformKey($name);
            $payload = $this->session->getOldInput($key);

            if (!is_array($payload)) {
                return $payload;
            }

            if (!in_array($this->type, ['select', 'checkbox'])) {
                if (!isset($this->payload[$key])) {
                    $this->payload[$key] = collect($payload);
                }

                if (!empty($this->payload[$key])) {
                    $value = $this->payload[$key]->shift();

                    return $value;
                }
            }

            return $payload;
        }
    }

    /**
     * Determine if the old input is empty.
     *
     * @return bool
     */
    public function oldInputIsEmpty()
    {
        return (isset($this->session) && count($this->session->getOldInput()) == 0);
    }

    /**
     * Transform key from array to dot syntax.
     *
     * @param  string $key
     *
     * @return mixed
     */
    protected function transformKey($key)
    {
        return str_replace(['.', '[]', '[', ']'], ['_', '', '.', ''], $key);
    }

    /**
     * Transform the string to an Html serializable object
     *
     * @param $html
     *
     * @return \Illuminate\Support\HtmlString
     */
    protected function toHtmlString($html)
    {
        return new HtmlString($html);
    }

    /**
     * Get the session store implementation.
     *
     * @return  \Illuminate\Contracts\Session\Session  $session
     */
    public function getSessionStore()
    {
        return $this->session;
    }

    /**
     * Set the session store implementation.
     *
     * @param  \Illuminate\Contracts\Session\Session $session
     *
     * @return $this
     */
    public function setSessionStore(Session $session)
    {
        $this->session = $session;

        return $this;
    }

    /**
     * Dynamically handle calls to the class.
     *
     * @param  string $method
     * @param  array $parameters
     *
     * @return \Illuminate\Contracts\View\View|mixed
     *
     * @throws \BadMethodCallException
     */
    public function __call($method, $parameters)
    {
        if (static::hasComponent($method)) {
            return $this->componentCall($method, $parameters);
        }

        if (static::hasMacro($method)) {
            return $this->macroCall($method, $parameters);
        }

        throw new BadMethodCallException("Method {$method} does not exist.");
    }
}


<?php

namespace Neutrino\Config;

use Phalcon\Config;

/**
 * Class Loader
 *
 * @package     Neutrino\Config
 */
class Loader
{
    /**
     * @param string $basePath
     * @param array  $excludes
     *
     * @return \Phalcon\Config
     */
    public static function load($basePath, array $excludes = [])
    {
        if (!is_null($config = self::fromCompile($basePath))) {
            return $config;
        } else {
            return self::fromFiles($basePath, $excludes);
        }
    }

    /**
     * @param string $basePath
     * @param array  $excludes
     *
     * @return array
     */
    public static function raw($basePath, array $excludes = [])
    {
        $config = [];

        $excludes = array_flip($excludes);

        foreach (glob($basePath . '/config/*.php') as $file) {
            if (!isset($excludes[$fileName = basename($file, '.php')])) {
                $config[$fileName] = require $file;
            }
        }

        return $config;
    }

    /**
     * @param string $basePath
     * @param array  $excludes
     *
     * @return \Phalcon\Config
     */
    public static function fromFiles($basePath, array $excludes = [])
    {
        return new Config(self::raw($basePath, $excludes));
    }

    /**
     * @param string $basePath
     *
     * @return null|\Phalcon\Config
     */
    public static function fromCompile($basePath)
    {
        if (file_exists($compilePath = $basePath . '/bootstrap/compile/config.php')) {
            return new Config(require $compilePath);
        }

        return null;
    }
}


<?php

namespace Igniter\Flame\Flash;

class Message implements \ArrayAccess
{
    /**
     * The title of the message.
     *
     * @var string
     */
    public $title;

    /**
     * The body of the message.
     *
     * @var string
     */
    public $message;

    /**
     * The message level.
     *
     * @var string
     */
    public $level = 'info';

    /**
     * Whether the message should auto-hide.
     *
     * @var bool
     */
    public $important = FALSE;

    /**
     * Whether the message is an overlay.
     *
     * @var bool
     */
    public $overlay = FALSE;

    /**
     * Create a new message instance.
     *
     * @param array $attributes
     */
    public function __construct($attributes = [])
    {
        $this->update($attributes);
    }

    /**
     * Update the attributes.
     *
     * @param  array $attributes
     *
     * @return $this
     */
    public function update($attributes = [])
    {
        $attributes = array_filter($attributes);

        foreach ($attributes as $key => $attribute) {
            $this->$key = $attribute;
        }

        return $this;
    }

    /**
     * Whether the given offset exists.
     *
     * @param  mixed $offset
     *
     * @return bool
     */
    public function offsetExists($offset)
    {
        return isset($this->$offset);
    }

    /**
     * Fetch the offset.
     *
     * @param  mixed $offset
     *
     * @return mixed
     */
    public function offsetGet($offset)
    {
        return $this->$offset;
    }

    /**
     * Assign the offset.
     *
     * @param  mixed $offset
     *
     * @param $value
     *
     * @return void
     */
    public function offsetSet($offset, $value)
    {
        $this->$offset = $value;
    }

    /**
     * Unset the offset.
     *
     * @param  mixed $offset
     *
     * @return void
     */
    public function offsetUnset($offset)
    {
        //
    }
}


<?php namespace Igniter\Flame\Database\Migrations;

use Illuminate\Database\Migrations\DatabaseMigrationRepository as BaseDatabaseMigrationRepository;
use Illuminate\Database\Schema\Blueprint;

class DatabaseMigrationRepository extends BaseDatabaseMigrationRepository
{
    protected $group;

    /**
     * Get the ran migrations.
     * @return array
     */
    public function getRan()
    {
        return $this->table()
                    ->orderBy('batch', 'asc')
                    ->orderBy('migration', 'asc')
                    ->pluck('migration')->all();
    }

    /**
     * Log that a migration was run.
     * Overrides the parent method and allows insertion of group data
     *
     * @param  string $file
     * @param  int $batch
     *
     * @return void
     */
    public function log($file, $batch)
    {
        $record = ['migration' => $file, 'group' => $this->getGroup(), 'batch' => $batch];

        $this->table()->insert($record);
    }

    /**
     * Create the migration repository data store.
     * @return void
     */
    public function createRepository()
    {
        $schema = $this->getConnection()->getSchemaBuilder();

        $method = (!$schema->hasTable($this->table))
            ? 'create' : 'table';

        $schema->$method($this->table, function (Blueprint $table) use ($method) {
            // Drop old columns from CI_Migration library
            if ($method == 'table') {
                $table->dropColumn('type');
                $table->dropColumn('version');
            }

            // The migrations table is responsible for keeping track of which of the
            // migrations have actually run for the application. We'll create the
            // table to hold the migration file's path as well as the batch ID.
            $table->increments('id');
            $table->string('group');
            $table->string('migration');
            $table->integer('batch');
        });
    }

    /**
     * Delete the migration repository data store.
     * @return void
     */
    public function deleteRepository()
    {
        $schema = $this->getConnection()->getSchemaBuilder();
        $schema->dropIfExists($this->table);
    }

    /**
     * Get a query builder for the migration table.
     * @return \Illuminate\Database\Query\Builder
     */
    protected function table()
    {
        return $this->getConnection()
                    ->table($this->table)
                    ->where('group', $this->getGroup())
                    ->useWritePdo();
    }

    /**
     * Remove a migration from the log.
     *
     * @param  object $migration
     *
     * @return void
     */
    public function delete($migration)
    {
        if (!is_string($migration))
            $migration = $migration->migration;

        $this->table()->where('migration', $migration)->delete();
    }

    /**
     * Get the module or extension the migration belongs to.
     *
     * @return string
     */
    public function getGroup()
    {
        return $this->group;
    }

    /**
     * Set the module or extension the migration belongs to.
     *
     * @param  string $name
     *
     * @return void
     */
    public function setGroup($name)
    {
        $this->group = $name;
    }
}


<?php

namespace Nextras\TracyQueryPanel\Handlers;

use Nette\Database as NDB;
use Nette\Utils\Html;
use Nextras\TracyQueryPanel\IQuery;
use Nextras\TracyQueryPanel\QueryPanel;

class NetteDatabaseHandler implements IQuery
{

	/** @var NDB\ResultSet */
	protected $result;

	public function __construct(NDB\ResultSet $result)
	{
		$this->result = $result;
	}

	public static function register(QueryPanel $queryPanel, NDB\Connection $connection)
	{
		/**
		 * @param NDB\ResultSet|NDB\DriverException $result
		 */
		$connection->onQuery[] = function($_, $result) use ($queryPanel) {
			if ($result instanceof NDB\ResultSet)
			{
				$queryPanel->addQuery(new static($result));
			}
		};
	}

	/**
	 * Suggested behavior: print Tracy\Dumper::toHtml() array
	 * of returned rows so row count is immediately visible.
	 *
	 * @return Html|string
	 */
	public function getResult()
	{
		ob_start();
		NDB\Helpers::dumpResult($this->result);
		return ob_get_clean();
	}

	/**
	 * Arbitrary identifier such as mysql, postgres, elastic, neo4j
	 *
	 * @return string
	 */
	public function getStorageType()
	{
		$dsn = $this->result->connection->getDsn();
		return substr($dsn, 0, strpos($dsn, ':'));
	}

	/**
	 * Database name, fulltext index or similar, NULL if not applicable
	 *
	 * @return NULL|string
	 */
	public function getDatabaseName()
	{
		$dsn = $this->result->connection->getDsn();
		$matches = [];
		if (preg_match('~\b(database|dbname)=(.*?)(;|$)~', $dsn, $matches))
		{
			return $matches[2];
		}
		return NULL;
	}

	/**
	 * Actual formatted query, e.g. 'SELECT * FROM ...'
	 *
	 * @return Html|string
	 */
	public function getQuery()
	{
		return trim(NDB\Helpers::dumpSql($this->result->getQueryString(), $this->result->getParameters(), $this->result->connection));
	}

	/**
	 * @return NULL|float ms
	 */
	public function getElapsedTime()
	{
		return $this->result->getTime();
	}

	/**
	 * e.g. explain
	 *
	 * @return NULL|Html|string
	 */
	public function getInfo()
	{
		// TODO: Implement getInfo() method.
	}

}


<?php
/*
 * Copyright 2008 Sven Sanzenbacher
 *
 * This file is part of the naucon package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace Naucon\HtmlBuilder;

use Naucon\Utility\Map;
use Naucon\Utility\Collection;

/**
 * Html Element Abstract Class
 *
 * @abstract
 * @package    HtmlBuilder
 * @author     Sven Sanzenbacher
 */
abstract class HtmlElementAbstract implements HtmlElementInterface
{
    /**
     * @access      protected
     * @var         string                  html element tag
     */
    protected $tag = null;

    /**
     * @access      protected
     * @var         Map                     html element attributes with key-value-pairs
     */
    protected $attributeMap = null;

    /**
     * @access      protected
     * @var         Collection              child element collection
     */
    protected $childElementCollection = null;

    /**
     * @access      protected
     * @var         array                   black list of attributes witch are not filtered
     */
    protected $_nonFilteredAttributes = array('onclick',
        'ondblclick',
        'onmousedown',
        'onmouseup',
        'onmouseover',
        'onmousemove',
        'onmouseout',
        'onkeypress',
        'onkeydown',
        'onkeyup'
    );

    /**
     * @return      string                  html element tag
     */
    public function getTag()
    {
        return $this->tag;
    }

    /**
     * @access      protected
     * @return      Map
     */
    protected function getAttributeMap()
    {
        if (is_null($this->attributeMap)) {
            $this->attributeMap = new Map();
        }
        return $this->attributeMap;
    }

    /**
     * @return      array                   html element attributes with key-value-pairs
     */
    public function getAttributes()
    {
        return $this->getAttributeMap()->getAll();
    }

    /**
     * @return      bool                    html element has attributes
     */
    public function hasAttributes()
    {
        if ($this->getAttributeMap()->count() > 0) {
            return true;
        }
        return false;
    }

    /**
     * @param       array   $attributes     html element attributes with key-value-pairs
     * @return      HtmlElementAbstract
     */
    public function setAttributes(array $attributes)
    {
        $this->getAttributeMap()->setAll($attributes);
        return $this;
    }

    /**
     * @param       string      $key        html element attribute key
     * @return      string                  html element attribute value
     */
    public function getAttribute($key)
    {
        return $this->getAttributeMap()->get($key);
    }

    /**
     * @param       string      $key        html element attribute key
     * @return      bool                    html element has attribute for given key
     */
    public function hasAttribute($key)
    {
        return $this->getAttributeMap()->hasKey($key);
    }

    /**
     * set attribute to html element
     *
     * @param       string      $key        html element attribute key
     * @param       string      $value      html element attribute value
     * @return      HtmlElementAbstract
     */
    public function setAttribute($key, $value = null)
    {
        if (is_null($value)) {
            $this->getAttributeMap()->remove($key);
        } else {
            $this->getAttributeMap()->set($key, $value);
        }
        return $this;
    }

    /**
     * remove attribute of html element
     *
     * @param       string      $key        html element attribute key
     * @return      HtmlElementAbstract
     */
    public function removeAttribute($key)
    {
        $this->getAttributeMap()->remove($key);
        return $this;
    }

    /**
     * append attribute to html element
     *
     * @param       string      $key        html element attribute key
     * @param       string      $value      html element attribute value
     * @param       string      $seperater  html element attribute seperater
     * @return      HtmlElementAbstract
     */
    public function appendAttribute($key, $value = null, $seperater = null)
    {
        if (!is_null($value)) {
            if ($this->getAttributeMap()->hasKey($key)) {
                if (strlen($seperater) > 0) {
                    $values = array();
                    $values[] = $this->getAttributeMap()->get($key);
                    $values[] = $value;
                    $newValue = implode($seperater, $values);
                } else {
                    $newValue = $this->getAttributeMap()->get($key) . $value;
                }
            } else {
                $newValue = $value;
            }
            $this->getAttributeMap()->set($key, $newValue);
        }
        return $this;
    }

    /**
     * @return      Collection
     */
    public function getChildElementCollection()
    {
        if (is_null($this->childElementCollection)) {
            $this->childElementCollection = new Collection();
        }
        return $this->childElementCollection;
    }

    /**
     * @return      array                   html child elements
     */
    public function getChildElements()
    {
        $this->getChildElementCollection()->toArray();
    }

    /**
     * @return      bool                    true = html element has child elements
     */
    public function hasChildElements()
    {
        if ($this->getChildElementCollection()->count() > 0) {
            return true;
        }
        return false;
    }

    /**
     * @param       array       $elements   html child elements
     * @return      HtmlElementAbstract
     */
    public function setChildElements(array $elements)
    {
        $this->getChildElementCollection()->clear();
        $this->getChildElementCollection()->addAll($elements);
        return $this;
    }

    /**
     * add html element as child element
     *
     * @param       HtmlElementInterface        $element
     * @return      HtmlElementAbstract
     */
    public function addChildElement(HtmlElementInterface $element)
    {
        $this->getChildElementCollection()->add($element);
        return $this;
    }

    /**
     * set html element content, replace all child elements
     *
     * @param       string      $content        html element content
     * @return      HtmlElementAbstract
     */
    public function setContent($content)
    {
        if (!is_null($content)) {
            if ($content instanceof HtmlElementInterface) {
                $htmlElementObject = $content;
            } else {
                $htmlElementObject = new HtmlElementContent($content);
            }
            $this->getChildElementCollection()->clear();
            $this->getChildElementCollection()->add($htmlElementObject);
        }
        return $this;
    }

    /**
     * add html element content
     *
     * @param       string      $content        html element content
     * @return      HtmlElementAbstract
     */
    public function addContent($content = null)
    {
        if (!is_null($content)) {
            $htmlElementObject = new HtmlElementContent($content);
            $this->getChildElementCollection()->add($htmlElementObject);
        }
        return $this;
    }

    /**
     * @return      string                  html output
     */
    public function __toString()
    {
        $htmlBuilder = new HtmlBuilder();
        return $htmlBuilder->render($this);
    }
}


<?php

namespace Igniter\Flame\Scaffold;

use Illuminate\Support\ServiceProvider;

class ScaffoldServiceProvider extends ServiceProvider
{
    protected $defer = TRUE;

    /**
     * The commands to be registered.
     *
     * @var array
     */
    protected $commands = [
        'CreateExtension'  => 'command.create.extension',
        'CreateComponent'  => 'command.create.component',
        'CreateController' => 'command.create.controller',
        'CreateModel'      => 'command.create.model',
    ];

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->registerCommands($this->commands);
    }

    /**
     * Register the given commands.
     *
     * @param  array $commands
     *
     * @return void
     */
    protected function registerCommands(array $commands)
    {
        foreach ($commands as $class => $command) {
            $this->{"register{$class}Command"}($command);
        }

        $this->commands(array_values($commands));
    }

    protected function registerCreateExtensionCommand($command)
    {
        $this->app->singleton($command, function ($app) {
            return new Console\CreateExtension($app['files']);
        });
    }

    protected function registerCreateComponentCommand($command)
    {
        $this->app->singleton($command, function ($app) {
            return new Console\CreateComponent($app['files']);
        });
    }

    protected function registerCreateControllerCommand($command)
    {
        $this->app->singleton($command, function ($app) {
            return new Console\CreateController($app['files']);
        });
    }

    protected function registerCreateModelCommand($command)
    {
        $this->app->singleton($command, function ($app) {
            return new Console\CreateModel($app['files']);
        });
    }

    /**
     * Get the services provided by the provider.
     *
     * @return array
     */
    public function provides()
    {
        return array_values($this->commands);
    }
}


<?php

namespace Neutrino\Foundation\Cli\Tasks;

use Neutrino\Cli\Output\Decorate;
use Neutrino\Cli\Task;
use Neutrino\Constants\Services;
use Neutrino\Debug\Reflexion;
use Neutrino\PhpPreloader\Factory;
use Phalcon\Mvc\Router;

/**
 * Class RouteCacheTask
 *
 * @package Neutrino\Foundation\Cli\Tasks
 */
class RouteCacheTask extends Task
{
    /**
     * Generate a cache for http kernel's routes.
     */
    public function mainAction()
    {
        $this->output->write(Decorate::notice(str_pad('Generating http-routes cache', 40, ' ')), false);

        try {
            $router = $this->loadHttpRouter();

            $str = $this->compile($router);

            file_put_contents(BASE_PATH . '/bootstrap/compile/http-routes.php', "<?php\n$str");

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

            @unlink(BASE_PATH . '/bootstrap/compile/http-routes.php');
        }
    }

    /**
     * @return \Phalcon\Mvc\Router
     */
    private function loadHttpRouter()
    {
        $di = $this->getDI();

        $cliRouter = $di->get(Services::ROUTER);

        $di->remove(Services::ROUTER);
        $di->set(Services::ROUTER, new Router(false));

        include BASE_PATH . '/routes/http.php';

        $router = $di->get(Services::ROUTER);

        $di->remove(Services::ROUTER);
        $di->set(Services::ROUTER, $cliRouter);

        return $router;
    }

    private function compile(Router $router)
    {

        $str = "<?php\n";
        $str .= "\$router = \Phalcon\Di::getDefault()->getShared('router');\n";

        $fluents = [];
        foreach ([
                     '_defaultModule'      => 'setDefaultModule',
                     '_defaultNamespace'   => 'setDefaultNamespace',
                     '_defaultController'  => 'setDefaultController',
                     '_defaultAction'      => 'setDefaultAction',
                     '_removeExtraSlashes' => 'removeExtraSlashes',
                     '_notFoundPaths'      => 'notFound',
                 ] as $property => $method) {
            $var = Reflexion::get($router, $property);
            if (!is_null($var)) {
                $fluents[] = "$method(" . var_export($var, true) . ")\n";
            }
        }

        $defaultParams = Reflexion::get($router, '_defaultParams');

        if (!is_null($defaultParams) && [] !== $defaultParams) {
            $fluents[] = "setDefaults(" . var_export(['params' => $defaultParams], true) . ")\n";
        }

        if (!empty($fluents)) {
            $str .= "\$router->" . implode('->', $fluents) . ";";
        }

        foreach ($router->getRoutes() as $route) {
            $str .= "\$router->add("
                . var_export($route->getPattern(), true) . ","
                . var_export($route->getPaths(), true) . ","
                . var_export($route->getHttpMethods(), true) . ")";

            if (!empty($route->getName())) {
                $str .= "->setName('" . $route->getName() . "')";
            }
            if (!empty($route->getHostname())) {
                $str .= "->setHostname('" . $route->getHostname() . "')";
            }

            $str .= "\n;";
        }

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

        $stmts = $preloader->getParser()->parse($str);
        $stmts = $preloader->traverse($stmts);
        $str = $preloader->prettyPrint($stmts);

        return $str;
    }
}


<?php

namespace Neutrino\Support;

use Neutrino\Constants\Events;
use Neutrino\Constants\Services;
use Phalcon\Db\Adapter;
use Phalcon\Di;
use Phalcon\Events\Event;
use Phalcon\Events\Manager;

/**
 * Class Db
 *
 * @package Neutrino\Support
 */
class Db
{
    /**
     * Return queries randed behind callback.
     *
     * @param \Closure $callback
     * @param bool     $pretend
     *
     * @return array
     */
    public static function getQueries(\Closure $callback, $pretend = false)
    {
        $db = Di::getDefault()->get(Services::DB);

        if (is_null($em = $db->getEventsManager())) {
            $db->setEventsManager($em = new Manager());
        }

        $queries = [];

        $listener = function (Event $event, Adapter $db) use (&$queries, $pretend) {
            $queries[] = $db->getRealSQLStatement();

            if ($pretend && $event->isCancelable()) {
                $event->stop();
            }

            return !$pretend;
        };

        $em->attach(Events\Db::BEFORE_QUERY, $listener);

        $callback();

        $em->detach(Events\Db::BEFORE_QUERY, $listener);

        return $queries;
    }

    /**
     * Pretend run SQL behind callback and return queries launched.
     *
     * @param \Closure $callback
     *
     * @return array
     */
    public static function pretend(\Closure $callback)
    {
        return self::getQueries($callback, true);
    }
}


<?php
/*
  +------------------------------------------------------------------------+
  | Phalcon Framework                                                      |
  +------------------------------------------------------------------------+
  | Copyright (c) 2011-2016 Phalcon Team (http://www.phalconphp.com)       |
  +------------------------------------------------------------------------+
  | This source file is subject to the New BSD License that is bundled     |
  | with this package in the file docs/LICENSE.txt.                        |
  |                                                                        |
  | If you did not receive a copy of the license and are unable to         |
  | obtain it through the world-wide-web, please send an email             |
  | to license@phalconphp.com so we can send you a copy immediately.       |
  +------------------------------------------------------------------------+
  | Authors: Andres Gutierrez <andres@phalconphp.com>                      |
  |          Eduar Carvajal <eduar@phalconphp.com>                         |
  |          Nikita Vershinin <endeveit@gmail.com>                         |
  +------------------------------------------------------------------------+
*/

namespace Neutrino\Error;

use Neutrino\Support\Arr;

/**
 * Class Error
 *
 * @package Phalcon\Error
 *
 * @property-read int        type
 * @property-read int        code
 * @property-read string     typeStr
 * @property-read string     message
 * @property-read string     file
 * @property-read string     line
 * @property-read \Exception exception
 * @property-read bool       isException
 * @property-read bool       isError
 */
class Error implements \ArrayAccess, \JsonSerializable
{
    /**
     * @var array
     */
    protected $attributes;

    /**
     * Class constructor sets the attributes.
     *
     * @param array $options
     */
    public function __construct(array $options = [])
    {
        $defaults = [
            'type'        => -1,
            'code'        => 0,
            'message'     => 'No error message',
            'file'        => '',
            'line'        => '',
            'exception'   => null,
            'isException' => false,
            'isError'     => false,
        ];

        $options = array_merge($defaults, $options);

        foreach ($options as $option => $value) {
            $this->attributes[$option] = $value;
        }

        $this->attributes['typeStr'] = Helper::verboseErrorType($this->attributes['type']);
        $this->attributes['logLvl'] = Helper::getLogType($this->attributes['type']);
    }

    /**
     * @param \Exception|\Error|\Throwable $e
     *
     * @return \Neutrino\Error\Error
     */
    public static function fromException($e)
    {
        return new static([
            'type'        => -1,
            'code'        => $e->getCode(),
            'message'     => $e->getMessage(),
            'file'        => $e->getFile(),
            'line'        => $e->getLine(),
            'isException' => true,
            'exception'   => $e,
        ]);
    }

    public static function fromError($errno, $errstr, $errfile, $errline)
    {
        return new static([
            'type'    => $errno,
            'code'    => $errno,
            'message' => $errstr,
            'file'    => $errfile,
            'line'    => $errline,
            'isError' => true,
        ]);
    }

    public function isFateful()
    {
        $type = $this->type;

        return $type == -1 ||
            $type == E_ERROR ||
            $type == E_PARSE ||
            $type == E_CORE_ERROR ||
            $type == E_COMPILE_ERROR ||
            $type == E_RECOVERABLE_ERROR;
    }

    /**
     * Magic method to retrieve the attributes.
     *
     * @param  string $name
     *
     * @return mixed
     */
    public function __get($name)
    {
        return isset($this->attributes[$name]) ? $this->attributes[$name] : null;
    }

    public function __isset($name)
    {
        return isset($this->attributes[$name]);
    }

    /**
     * Whether a offset exists
     *
     * @link  http://php.net/manual/en/arrayaccess.offsetexists.php
     *
     * @param mixed $offset

     *                      An offset to check for.
     *

     *
     * @return boolean true on success or false on failure.
     *

     *

     * The return value will be casted to boolean if non-boolean was returned.
     * @since 5.0.0
     */
    public function offsetExists($offset)
    {
        return Arr::has($this->attributes, $offset);
    }

    /**
     * Offset to retrieve
     *
     * @link  http://php.net/manual/en/arrayaccess.offsetget.php
     *
     * @param mixed $offset

     *                      The offset to retrieve.
     *

     *
     * @return mixed Can return all value types.
     * @since 5.0.0
     */
    public function offsetGet($offset)
    {
        return Arr::get($this->attributes, $offset);
    }

    /**
     * Offset to set
     *
     * @link  http://php.net/manual/en/arrayaccess.offsetset.php
     *
     * @param mixed $offset

     *                      The offset to assign the value to.
     *

     * @param mixed $value

     *                      The value to set.
     *

     *
     * @return void
     * @since 5.0.0
     */
    public function offsetSet($offset, $value)
    {
        $this->attributes[$offset] = $value;
    }

    /**
     * Offset to unset
     *
     * @link  http://php.net/manual/en/arrayaccess.offsetunset.php
     *
     * @param mixed $offset

     *                      The offset to unset.
     *

     *
     * @return void
     * @since 5.0.0
     */
    public function offsetUnset($offset)
    {
        if (isset($this->attributes[$offset])) {
            unset($this->attributes[$offset]);
        }
    }

    /**
     * Specify data which should be serialized to JSON
     *
     * @link  http://php.net/manual/en/jsonserializable.jsonserialize.php
     * @return mixed data which can be serialized by json_encode,
     *        which is a value of any type other than a resource.
     * @since 5.4.0
     */
    function jsonSerialize()
    {
        $json = $this->attributes;

        if ($this->attributes['isException']) {
            /** @var \Exception $exception */
            $exception         = $this->attributes['exception'];
            $json['exception'] = [
                'class'   => get_class($exception),
                'code'    => $exception->getCode(),
                'message' => $exception->getMessage(),
                'traces'  => Helper::formatExceptionTrace($exception)
            ];
        }

        return $json;
    }
}


<?php

namespace Neutrino\Security;

use Neutrino\Constants\Services;
use Phalcon\Di\Injectable;

/**
 * Class RateLimiter
 *
 * @see     https://github.com/laravel/framework/blob/5.2/src/Illuminate/Cache/RateLimiter.php
 *
 *  @package Neutrino\Security
 */
class RateLimiter extends Injectable
{
    /**
     * Cache key prefix. The name of the rate limiter.
     *
     * @var string
     */
    private $name;

    /**
     * Cache key suffix for the flag "too many attempts"
     *
     * @var string
     */
    private $klock = '.lockout';

    /**
     * RateLimiter constructor.
     *
     * @param string $name
     */
    public function __construct($name = '')
    {
        $this->name = $name;
    }

    /**
     * Determine if the given key has been "accessed" too many times.
     *
     * @param  string $key
     * @param  int    $maxAttempts
     * @param  int    $decaySeconds
     *
     * @return bool
     */
    public function tooManyAttempts($key, $maxAttempts, $decaySeconds = 1)
    {
        /** @var \Neutrino\Cache\CacheStrategy $cache */
        $cache = $this->{Services::CACHE};

        if ($cache->exists($this->name . $key . $this->klock, $decaySeconds)) {
            return true;
        }
        if ($this->attempts($key, $decaySeconds) >= $maxAttempts) {
            $cache->save(
                $this->name . $key . $this->klock,
                time() + ($decaySeconds),
                $decaySeconds
            );

            $this->resetAttempts($key);

            return true;
        }

        return false;
    }

    /**
     * Increment the counter for a given key for a given decay time.
     *
     * @param  string $key
     * @param  int    $decaySeconds
     *
     * @return int
     */
    public function hit($key, $decaySeconds = 1)
    {
        $key = $this->name . $key;

        /** @var \Neutrino\Cache\CacheStrategy $cache */
        $cache = $this->{Services::CACHE};

        if (!$cache->exists($key, $decaySeconds)) {
            $cache->save($key, 0, $decaySeconds);
        }

        $value = (int)$cache->get($key, $decaySeconds);

        $value++;
        $cache->save($key, $value, $decaySeconds);

        return $value;
    }

    /**
     * Get the number of attempts for the given key.
     *
     * @param  string $key
     * @param  int    $decaySeconds
     *
     * @return mixed
     */
    public function attempts($key, $decaySeconds = 1)
    {
        $value = $this->{Services::CACHE}->get($this->name . $key, $decaySeconds);

        return is_null($value) ? 0 : (int)$value;
    }

    /**
     * Reset the number of attempts for the given key.
     *
     * @param  string $key
     *
     * @return mixed
     */
    public function resetAttempts($key)
    {
        return $this->{Services::CACHE}->delete($this->name . $key);
    }

    /**
     * Get the number of retries left for the given key.
     *
     * @param  string $key
     * @param  int    $maxAttempts
     * @param         $decaySeconds
     *
     * @return int
     */
    public function retriesLeft($key, $maxAttempts, $decaySeconds)
    {
        $attempts = $this->attempts($key, $decaySeconds);

        return $attempts === 0 ? $maxAttempts : $maxAttempts - $attempts;
    }

    /**
     * Clear the hits and lockout for the given key.
     *
     * @param  string $key
     *
     * @return void
     */
    public function clear($key)
    {
        $this->resetAttempts($key);

        $this->{Services::CACHE}->delete($this->name . $key . $this->klock);
    }

    /**
     * Get the number of seconds until the "key" is accessible again.
     *
     * @param  string $key
     * @param  int    $decaySeconds
     *
     * @return int
     */
    public function availableIn($key, $decaySeconds)
    {
        $time = $this->{Services::CACHE}->get($this->name . $key . $this->klock, $decaySeconds);

        return $time - time();
    }
}


<?php

namespace Neutrino\Assets;

use Neutrino\Assets\Exception\CompilatorException;
use Neutrino\Process\Exception;
use Neutrino\Process\Process;
use Phalcon\Di\Injectable;

/**
 * Class SassCompiler
 *
 * Neutrino\Assets
 */
class SassCompiler extends Injectable implements AssetsCompilator
{
    /**
     * @param array $options
     *
     * @return bool
     *
     * @throws CompilatorException
     */
    public function compile(array $options)
    {
        if (empty($options['sass_file'])) {
            throw new CompilatorException('sass_file option can\'t be empty.');
        }
        if (empty($options['output_file'])) {
            throw new CompilatorException('output_file option can\'t be empty.');
        }
        if (empty($options['sass_cmd'])) {
            $cmd = 'sass';
            $procOptions = ['bypass_shell' => false];
        } else {
            $cmd = $options['sass_cmd'];
            $procOptions = [];
        }

        $cmd = [$cmd, '"' . $options['sass_file'] . '"', '"' . $options['output_file'] . '"'];

        $cmd = array_merge($cmd, isset($options['cmd_options']) ? $options['cmd_options'] : []);

        $cwd = isset($options['base_path']) ? $options['base_path'] : BASE_PATH;

        /** @var Process $proc */
        $proc = $this->getDI()->get(Process::class, [implode(' ', $cmd), $cwd, $procOptions]);

        try {
            $proc->start();
        } catch (Exception $e) {
            throw new CompilatorException('Can\'t open process.');
        }

        $proc->wait();

        if (!empty($str = $proc->getOutput())) {
            throw new CompilatorException($str);
        }

        if (!empty($str = $proc->getError())) {
            throw new CompilatorException($str);
        }

        return true;
    }
}


<?php

namespace Neutrino\Foundation;

use Neutrino\Constants\Env;
use Neutrino\Debug\Debugger;
use Neutrino\Interfaces\Kernelable;
use Phalcon\Config;
use Phalcon\Http\Response;

/**
 * Class Application
 *
 * Phalcon Application Bootstrap
 *
 * @package Neutrino\Foundation
 */
class Bootstrap
{
    /**
     * @var \Phalcon\Config
     */
    private $config;

    /**
     * Application constructor.
     *
     * @param \Phalcon\Config $config
     */
    public function __construct(Config $config)
    {
        $this->config = $config;
    }

    /**
     * @param $kernelClass
     *
     * @return \Phalcon\Application
     */
    public function make($kernelClass)
    {
        /** @var \Phalcon\Application|\Neutrino\Interfaces\Kernelable $kernel */
        $kernel = new $kernelClass;

        $kernel->bootstrap($this->config);

        if (APP_DEBUG && APP_ENV !== Env::TEST && php_sapi_name() !== 'cli') {
            Debugger::register();
        }

        $kernel->registerServices();
        $kernel->registerMiddlewares();
        $kernel->registerListeners();
        $kernel->registerRoutes();
        $kernel->registerModules([]);

        return $kernel;
    }

    /**
     * @param \Neutrino\Interfaces\Kernelable|\Phalcon\Application $kernel
     */
    public function run(Kernelable $kernel)
    {
        $kernel->boot();

        if (($response = $kernel->handle()) instanceof Response) {
            if (!$response->isSent()) {
                $response->send();
            }
        };

        $kernel->terminate();
    }
}


<?php namespace Igniter\Flame\Foundation\Providers;

use Illuminate\Log\LogServiceProvider as BaseLogServiceProvider;
use Illuminate\Log\Writer;

class LogServiceProvider extends BaseLogServiceProvider
{
    /**
     * Configure the Monolog handlers for the application.
     *
     * @param  \Illuminate\Log\Writer $log
     *
     * @return void
     */
    protected function configureSingleHandler(Writer $log)
    {
        $log->useFiles(
            $this->app->storagePath().'/logs/system.log',
            $this->logLevel()
        );
    }

    /**
     * Configure the Monolog handlers for the application.
     *
     * @param  \Illuminate\Log\Writer $log
     *
     * @return void
     */
    protected function configureDailyHandler(Writer $log)
    {
        $log->useDailyFiles(
            $this->app->storagePath().'/logs/system.log', $this->maxFiles(),
            $this->logLevel()
        );
    }
}


<?php

namespace Igniter\Flame\Database;

use Illuminate\Database\DatabaseServiceProvider as BaseDatabaseServiceProvider;
use Illuminate\Support\Facades\Schema;

class DatabaseServiceProvider extends BaseDatabaseServiceProvider
{
    /**
     * Bootstrap the application events.
     *
     * @return void
     */
    public function boot()
    {
        Model::setConnectionResolver($this->app['db']);

        Model::setEventDispatcher($this->app['events']);

        Schema::defaultStringLength(128);
    }

    /**
     * Register the service provider.
     * @return void
     */
    public function register()
    {
        Model::clearBootedModels();

        Model::clearExtendedClasses();

        $this->registerEloquentFactory();

        $this->registerQueueableEntityResolver();

        $this->registerConnectionServices();
    }

    /**
     * Returns the default database driver, not just the connection name.
     * @return string
     */
    protected function getDefaultDatabaseDriver()
    {
        $defaultConnection = $this->app['db']->getDefaultConnection();

        return $this->app['config']['database.connections.'.$defaultConnection.'.driver'];
    }
}


<?php

namespace Igniter\Flame\Support;

use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\ServiceProvider;

class HelperServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }

    /**
     * Register the application services.
     *
     * @return void
     */
    public function register()
    {
        $file = $this->app->make(Filesystem::class);

        $path = realpath(__DIR__.'/Helpers');
        $helpers = $file->glob($path.'/*.php');

        foreach ($helpers as $helper) {
            require_once $helper;
        }
    }
}


<?php

namespace Neutrino\Foundation\Cli\Tasks;

use Neutrino\Cli\Output\Decorate;
use Neutrino\Cli\Task;
use Neutrino\Dotconst;

/**
 * Class ConfigCacheTask
 *
 * @package     Neutrino\Foundation\Cli\Tasks
 */
class DotconstCacheTask extends Task
{

    /**
     * Dotconst compile & cache.
     *
     * @description Dotconst compile & cache.
     *
     * @throws \Exception
     */
    public function mainAction()
    {
        $this->output->write(Decorate::notice(str_pad('Generating dotconst cache', 40, ' ')), false);

        try {
            self::generateCache();

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

    public static function generateCache()
    {
        Dotconst\Compile::compile(BASE_PATH, BASE_PATH . '/bootstrap/compile');
    }
}


<?php

namespace Igniter\Flame\Filesystem;

use Illuminate\Filesystem\FilesystemServiceProvider as BaseFilesystemServiceProvider;

/**
 * Class FilesystemServiceProvider
 */
class FilesystemServiceProvider extends BaseFilesystemServiceProvider
{
    /**
     * Register the service provider.
     * @return void
     */
    public function register()
    {
        $this->registerNativeFilesystem();

        $this->registerFlysystem();
    }

    /**
     * Register the native filesystem implementation.
     * @return void
     */
    protected function registerNativeFilesystem()
    {
        $this->app->singleton('files', function () {
            $config = $this->app['config'];
            $files = new Filesystem;
            $files->filePermissions = $config->get('system.filePermissions', null);
            $files->folderPermissions = $config->get('system.folderPermissions', null);
            $files->pathSymbols = [
                '$' => $config->get('system.extensionsPath', base_path('extensions')),
                '~' => base_path(),
            ];

            return $files;
        });
    }

    /**
     * Get the services provided by the provider.
     * @return array
     */
    public function provides()
    {
        return ['files', 'filesystem'];
    }
}


<?php

namespace Neutrino\Database\Cli\Tasks;

/**
 * Class ResfreshTask
 *
 * @package Neutrino\Database\Cli\Tasks
 */
class RefreshTask extends BaseTask
{
    /**
     * @description Reset and re-run all migrations.
     *
     * @option      -f, --force : Force the operation to run when in production.
     * @option      --path : The path of migrations files to be executed.
     * @option      --step : The number of migrations to be reverted & re-run.
     * @option      --pretend : Dump the SQL queries that would be run.
     */
    public function mainAction()
    {
        if (!$this->confirmToProceed()) {
            return;
        }

        $step = $this->getOption('step') ?: 0;

        if ($step > 0) {
            $this->runRollback();
        } else {
            $this->runReset();
        }

        $this->runMigrate();
    }

    /**
     * Run the rollback command.
     *
     * @return void
     */
    protected function runRollback()
    {
        $this->callTask(RollbackTask::class, 'main', $this->arguments, $this->options);
    }

    /**
     * Run the reset command.
     *
     * @return void
     */
    protected function runReset()
    {
        $this->callTask(ResetTask::class, 'main', $this->arguments, $this->options);
    }

    /**
     * Run the migrate command.
     *
     * @return void
     */
    protected function runMigrate()
    {
        $this->callTask(MigrateTask::class, 'main', $this->arguments, $this->options);
    }
}


<?php

namespace Neutrino\Cli\Output;

use Neutrino\Support\Str;
use Neutrino\Version;

/**
 * Class Helper
 *
 * @package Neutrino\Cli\Output
 */
final class Helper
{
    private static $reflections = [];

    /**
     * Remove the decoration of a string
     *
     * @param $string
     *
     * @return mixed
     */
    public static function removeDecoration($string)
    {
        return preg_replace("/\033\\[[^m]*m/", '', $string);
    }

    /**
     * Return the real len of a string (without decoration)
     *
     * @param $string
     *
     * @return int
     */
    public static function strlenWithoutDecoration($string)
    {
        return self::strlen(self::removeDecoration($string));
    }

    /**
     * Return the len of a string
     *
     * @param $string
     *
     * @return int
     */
    public static function strlen($string)
    {
        if (false === $encoding = mb_detect_encoding($string, null, true)) {
            return strlen($string);
        }

        return mb_strwidth($string, $encoding);
    }

    /**
     * Correct Str::pad wrong output when str is decorate
     *
     * @param string $str
     * @param int    $size
     * @param string $pad
     * @param int    $type
     *
     * @return string
     */
    public static function strPad($str, $size, $pad, $type = STR_PAD_RIGHT)
    {
        $washLen = self::strlenWithoutDecoration($str);

        switch ($type) {
            case STR_PAD_BOTH:
                $m = $size - $washLen;

                return str_repeat($pad, floor($m / 2)) . $str . str_repeat($pad, ceil($m / 2));
            case STR_PAD_LEFT:
                return str_repeat($pad, $size - $washLen) . $str;
            case STR_PAD_RIGHT:
            default:
                return $str . str_repeat($pad, $size - $washLen);
        }
    }

    /**
     * @param \Phalcon\Cli\Router\Route|\Phalcon\Mvc\Router\Route $route
     * @param bool                                                $decorate
     *
     * @return string
     */
    public static function describeRoutePattern($route, $decorate = false)
    {
        $paths = $route->getPaths();

        $compiled = $route->getCompiledPattern();
        $pattern = $route->getPattern();
        if ($compiled !== $pattern) {
            preg_match_all('/(:[\w_]+|\([^?][^\/\)]+\))/', $pattern, $matches);

            foreach ($matches[1] as $idx => $match) {
                if (Str::startsWith($match, ':') && in_array($match, [':controller', ':module', ':action', ':namespace'])) {
                    $match = '{' . str_replace(':', '', $match) . '}';
                    if($decorate){
                        $match = Decorate::notice($match);
                    }
                    $compiled = preg_replace('/\([^?][^\/\)]+\)/', $match, $compiled, 1);
                }
            }

            foreach ($paths as $key => $value) {
                if (in_array($key, ['controller', 'task', 'action', 'middleware'])) {
                    continue;
                }
                if (is_int($value)) {
                    $key = '{' . $key . '}';
                    if($decorate){
                        $key = Decorate::notice($key);
                    }
                    $compiled = preg_replace('/\([^?][^\/\)]+\)/', $key, $compiled, 1);
                }
            }
            preg_match('/\^(.+)\$/', $compiled, $matchs);
            $compiled = $matchs[1];
        }

        return $compiled;
    }

    /**
     * @param $class
     * @param $methodName
     *
     * @return array
     */
    public static function getTaskInfos($class, $methodName)
    {
        $infos = [];
        $reflection = self::getReflection($class);

        try {
            $method = $reflection->getMethod($methodName);
        } catch (\Exception $e) {
            return ['__exception' => "Methods $class::$methodName not found."];
        }

        if (!empty($method)) {
            $docBlock = $method->getDocComment();

            preg_match_all('/\*\s*@(\w+)(.*)/', $docBlock, $annotations);
            $docBlock = preg_replace('/\*\s*@(\w+)(.*)/', '', $docBlock);

            foreach ($annotations[1] as $k => $annotation) {
                switch ($annotation) {
                    case 'description':
                        $infos['description'] = trim($annotations[2][$k]);
                        break;
                    case 'argument':
                    case 'option':
                        $infos[$annotation . 's'][] = trim($annotations[2][$k]);
                        break;
                }
            }

            if (empty($infos['description'])) {
                preg_match_all('/\*([^\n\r]+)/', $docBlock, $lines);

                $rows = [];
                foreach ($lines[1] as $line) {
                    if ($line == '*' || $line == '/') {
                        continue;
                    }
                    $rows[] = preg_replace('/^ /', '', rtrim($line));
                }

                $infos['description'] = implode(PHP_EOL, $rows);
            }
        }

        return $infos;
    }

    /**
     * @return string
     */
    public static function neutrinoVersion()
    {
        return Decorate::info('Neutrino framework') . ' version ' . Decorate::notice('v' . Version::get() . ' ['. Version::getId().']');
    }

    /**
     * @param string $class
     *
     * @return \ReflectionClass
     */
    private static function getReflection($class)
    {
        if (!isset(self::$reflections[$class])) {
            self::$reflections[$class] = new \ReflectionClass($class);
        }

        return self::$reflections[$class];
    }
}


<?php
/**
 * @author Taylor Otwell
 *
 * Adapted for Phalcon
 */

namespace Neutrino\Support\Facades;

use Mockery;
use Mockery\MockInterface;
use Phalcon\DiInterface;
use RuntimeException;

/**
 * Class Facade
 *
 *  @package Neutrino\Support\Facades
 */
abstract class Facade
{
    /**
     * @var \Phalcon\DiInterface
     */
    protected static $di;

    /**
     * The resolved object instances.
     *
     * @var array
     */
    protected static $resolvedInstance;

    public static function clearResolvedInstances()
    {
        self::$resolvedInstance = [];
    }

    /**
     * @param \Phalcon\DiInterface $di
     */
    public static function setDependencyInjection(DiInterface $di)
    {
        static::$di = $di;
    }

    /**
     * Hotswap the underlying instance behind the facade.
     *
     * @param  mixed $instance
     *
     * @return void
     */
    public static function swap($instance)
    {
        self::$resolvedInstance[static::getFacadeAccessor()] = $instance;

        static::$di->setShared(static::getFacadeAccessor(), $instance);
    }

    /**
     * Initiate a mock expectation on the facade.
     *
     * @param mixed ...$params
     *
     * @return \Mockery\Expectation
     */
    public static function shouldReceive(...$params)
    {
        $name = static::getFacadeAccessor();
        if (static::isMock()) {
            $mock = self::$resolvedInstance[$name];
        } else {
            $mock = static::createFreshMockInstance();
        }

        return $mock->shouldReceive(...$params);
    }

    /**
     * Get the root object behind the facade.
     *
     * @return mixed
     */
    public static function getFacadeRoot()
    {
        return static::resolveFacadeInstance(static::getFacadeAccessor());
    }

    /**
     * Handle dynamic, static calls to the object.
     *
     * @param  string $method
     * @param  array  $args
     *
     * @return mixed
     * @throws RuntimeException
     */
    public static function __callStatic($method, $args)
    {
        $instance = static::getFacadeRoot();

        if (!$instance) {
            throw new RuntimeException('A facade root has not been set.');
        }

        if (empty($args)) {
            return $instance->$method();
        } else {
            return $instance->$method(...$args);
        }
    }

    /**
     * Get the registered name of the component.
     *
     * @return string
     * @throws RuntimeException
     */
    protected static function getFacadeAccessor()
    {
        throw new RuntimeException('Facade does not implement getFacadeAccessor method.');
    }

    /**
     * Create a fresh mock instance.
     *
     * @return MockInterface
     */
    protected static function createFreshMockInstance()
    {
        $name = static::getFacadeAccessor();

        self::$resolvedInstance[$name] = $mock = static::createMockInstance();

        $mock->shouldAllowMockingProtectedMethods();

        if (isset(static::$di)) {
            static::$di->setShared($name, $mock);
        }

        return $mock;
    }

    /**
     * Create a fresh mock instance.
     *
     * @return MockInterface
     */
    protected static function createMockInstance()
    {
        $class = static::getMockableClass();

        return $class ? Mockery::mock($class) : Mockery::mock();
    }

    /**
     * Determines whether a mock is set as the instance of the facade.
     *
     * @return bool
     */
    protected static function isMock()
    {
        $name = static::getFacadeAccessor();

        return isset(self::$resolvedInstance[$name]) && self::$resolvedInstance[$name] instanceof MockInterface;
    }

    /**
     * Get the mockable class for the bound instance.
     *
     * @return string|null
     */
    protected static function getMockableClass()
    {
        if ($root = static::getFacadeRoot()) {
            return get_class($root);
        }

        return null;
    }

    /**
     * Resolve the facade root instance from the container.
     *
     * @param  string|object $name
     *
     * @return mixed
     * @throws RuntimeException
     */
    protected static function resolveFacadeInstance($name)
    {
        if (is_object($name)) {
            return $name;
        }

        if (isset(self::$resolvedInstance[$name])) {
            return self::$resolvedInstance[$name];
        }

        return self::$resolvedInstance[$name] = static::$di->getShared($name);
    }
}


<?php namespace Igniter\Flame\Pagic\Processors;

use Igniter\Flame\Pagic\Finder;
use Igniter\Flame\Pagic\Parsers\FileParser;

class Processor
{
    /**
     * Process the results of a singular "select" query.
     *
     * @param  \Igniter\Flame\Pagic\Finder $finder
     * @param  array $result
     *
     * @return array
     */
    public function processSelect(Finder $finder, $result)
    {
        if ($result === null) {
            return null;
        }

        $fileName = array_get($result, 'fileName');

        return [$fileName => $this->parseTemplateContent($result, $fileName)];
    }

    /**
     * Process the results of a "select" query.
     *
     * @param  \Igniter\Flame\Pagic\Finder $finder
     * @param  array $results
     *
     * @return array
     */
    public function processSelectAll(Finder $finder, $results)
    {
        if (!count($results)) {
            return [];
        }

        $items = [];

        foreach ($results as $result) {
            $fileName = array_get($result, 'fileName');
            $items[$fileName] = $this->parseTemplateContent($result, $fileName);
        }

        return $items;
    }

    /**
     * Helper to break down template content in to a useful array.
     *
     * @param $result
     * @param $fileName
     *
     * @return array
     */
    protected function parseTemplateContent($result, $fileName)
    {
        $content = array_get($result, 'content');

        $processed = FileParser::parse($content);

        $content = [
            'fileName' => $fileName,
            'mTime'    => array_get($result, 'mTime'),
            'content'  => $content,
            'markup'   => $processed['markup'],
            'code'     => $processed['code'],
        ];

        if (!empty($processed['data']))
            $content = $content + $processed['data'];

        return $content;
    }

    /**
     * Process the data in to an insert action.
     *
     * @param  \Igniter\Flame\Pagic\Finder $finder
     * @param  array $data
     *
     * @return string
     */
    public function processInsert(Finder $finder, $data)
    {
        return FileParser::render($data);
    }

    /**
     * Process the data in to an update action.
     *
     * @param  \Igniter\Flame\Pagic\Finder $finder
     * @param  array $data
     *
     * @return string
     */
    public function processUpdate(Finder $finder, $data)
    {
        $existingData = $finder->getModel()->attributesToArray();

        return FileParser::render($data + $existingData);
    }
}


<?php

namespace Neutrino\Http\Middleware;

use Neutrino\Foundation\Middleware\Controller;
use Neutrino\Interfaces\Middleware\BeforeInterface;
use Phalcon\Events\Event;

/**
 * Class Ajax
 *
 * @package Neutrino\Http\Middleware
 */
class Ajax extends Controller 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 (isset($_SERVER["HTTP_X_REQUESTED_WITH"]) && "xmlhttprequest" === strtolower($_SERVER["HTTP_X_REQUESTED_WITH"])) {
            return true;
        }

        $this->response->setStatusCode(400, 'Bad Request');

        return false;
    }
}


<?php

namespace Neutrino\Events;

use Phalcon\Di\Injectable;
use Phalcon\Events\Event;

/**
 * Class Listener
 *
 *  @package Neutrino\Events
 */
abstract class Listener extends Injectable
{
    /**
     * List to event to listen.
     *
     * ex : [
     *      {eventName} => {methodCall},
     *      \Neutrino\Constants\Events\Application::BOOT => 'onBoot',
     *      \Neutrino\Constants\Events\Dispatch::BEFORE_DISPATCH => 'onBeforeDispatch'
     * ]
     *
     * @var string[]
     */
    protected $listen;

    /**
     * List to space to listen.
     *
     * ex : [
     *      {eventSpace},
     *      \Neutrino\Constants\Events::APPLICATION,
     *      \Neutrino\Constants\Events::DISPATCH,
     * ]
     *
     * @var string[]
     */
    protected $space;

    /**
     * Closure attached to the EventsManager
     *
     * @var array
     */
    private $closures = [];

    /**
     * Attach all require event to make the listener
     */
    public function attach()
    {
        $em = $this->getEventsManager();

        if (!empty($this->space)) {
            foreach ($this->space as $space) {
                $em->attach($space, $this);
            }
        }

        if (!empty($this->listen)) {
            foreach ($this->listen as $event => $callback) {
                if (!method_exists($this, $callback)) {
                    throw new \RuntimeException(
                        "Method '$callback' not exist in " . get_class($this)
                    );
                }

                $this->closures[$event] = $closure = function (Event $event, $handler, $data = null) use ($callback) {
                    return $this->$callback($event, $handler, $data);
                };

                $em->attach($event, $closure);
            }
        }
    }

    /**
     * Detach all event attached to the EventsManager
     */
    public function detach()
    {
        $em = $this->getEventsManager();

        if (!empty($this->space)) {
            foreach ($this->space as $space) {
                $em->detach($space, $this);
            }
        }

        foreach ($this->closures as $event => $closure) {
            $em->detach($event, $closure);
        }

        $this->closures = [];
    }
}