<?php

/**
 * Queryflatfile
 *
 * @package Queryflatfile
 * @author  Mathieu NOËL <mathieu@soosyze.com>
 * @license https://github.com/soosyze/queryflatfile/blob/master/LICENSE (MIT License)
 */

namespace Queryflatfile;

use Queryflatfile\Exception\Query\BadFunctionException;
use Queryflatfile\Exception\Query\ColumnsNotFoundException;
use Queryflatfile\Exception\Query\QueryException;
use Queryflatfile\Exception\Query\TableNotFoundException;

/**
 * Réalise des requêtes à partir d'un schéma de données passé en paramètre.
 * Les requêtes se construisent avec le pattern fluent.
 *
 * @author Mathieu NOËL
 *
 * @method Request where( callable|string $column, null|string $operator = null, null|string $value = null, string $bool = 'and', boolean $not = false ) Alias de la fonction de l'objet Queryflatfile\Where
 * @method Request notWhere( callable|string $column, null|string $operator = null, null|string $value = null ) Alias de la fonction de l'objet Queryflatfile\Where
 * @method Request orWhere( callable|string $column, null|string $operator = null, null|string $value = null ) Alias de la fonction de l'objet Queryflatfile\Where
 * @method Request orNotWhere( callable|string $column, null|string $operator = null, null|string $value = null ) Alias de la fonction de l'objet Queryflatfile\Where
 * @method Request between( string $column, mixed $min, mixed $max, string $bool = 'and', boolean $not = false ) Alias de la fonction de l'objet Queryflatfile\Where
 * @method Request orBetween( string $column, mixed $min, mixed $max ) Alias de la fonction de l'objet Queryflatfile\Where
 * @method Request notBetween( string $column, mixed $min, mixed $max ) Alias de la fonction de l'objet Queryflatfile\Where
 * @method Request orNotBetween( string $column, mixed $min, mixed $max ) Alias de la fonction de l'objet Queryflatfile\Where
 * @method Request in( string $column, array $values, string $bool = 'and', boolean $not = false ) Alias de la fonction de l'objet Queryflatfile\Where
 * @method Request orIn( string $column, array $values ) Alias de la fonction de l'objet Queryflatfile\Where
 * @method Request notIn( string $column, array $values ) Alias de la fonction de l'objet Queryflatfile\Where
 * @method Request orNotIn( string $column, array $values ) Alias de la fonction de l'objet Queryflatfile\Where
 * @method Request isNull( string $column, string $condition = '===', string $bool = 'and', boolean $not = false ) Alias de la fonction de l'objet Queryflatfile\Where
 * @method Request orIsNull( string $column ) Alias de la fonction de l'objet Queryflatfile\Where
 * @method Request isNotNull( string $column ) Alias de la fonction de l'objet Queryflatfile\Where
 * @method Request orIsNotNull( string $column ) Alias de la fonction de l'objet Queryflatfile\Where
 * @method Request regex( string $column, string $pattern, string $bool = 'and', boolean $not = false ) Alias de la fonction de l'objet Queryflatfile\Where
 * @method Request orRegex( string $column, string $pattern ) Alias de la fonction de l'objet Queryflatfile\Where
 * @method Request notRegex( string $column, string $pattern ) Alias de la fonction de l'objet Queryflatfile\Where
 * @method Request orNotRegex( string $column, string $pattern ) Alias de la fonction de l'objet Queryflatfile\Where
 */
class Request
{
    /**
     * Le nombre de résultat de la requête.
     *
     * @var int
     */
    private $limit = 0;

    /**
     * Le décalage des résultats de la requête.
     *
     * @var int
     */
    private $offset = 0;

    /**
     * La configuration de la requête.
     *
     * @var array
     */
    private $request = [
        'allColumnsSchema' => [],
        'columns'          => [],
        'leftJoin'         => [],
        'rightJoin'        => [],
        'setUpdate'        => [],
        'union'            => [],
        'values'           => [],
    ];

    /**
     * Le nom de la table courante.
     *
     * @var string
     */
    private $table = '';

    /**
     * Les données de la table.
     *
     * @var array
     */
    private $tableData = [];

    /**
     * Le schéma des tables utilisées par la requête.
     *
     * @var array
     */
    private $tableSchema = [];

    /**
     * Les conditions de la requête.
     *
     * @var Where
     */
    private $where = null;

    /**
     * Les colonnes à trier.
     *
     * @var array
     */
    private $orderBy = [];

    /**
     * Le schéma de base de données.
     *
     * @var Schema
     */
    private $schema = null;

    /**
     * Le type d'exécution (delete|update|insert).
     *
     * @var string
     */
    private $execute = null;

    /**
     * Retourne la requête sous forme de liste.
     *
     * @var bool
     */
    private $lists = null;

    /**
     * Réalise une requête sur un schéma de données
     *
     * @param \Queryflatfile\Schema $sch
     */
    public function __construct(Schema $sch)
    {
        $this->schema = $sch;
    }

    /**
     * Retourne les paramètre de la requête en format pseudo SQL.
     *
     * @return string
     */
    public function __toString()
    {
        $output = '';
        if ($this->request[ 'columns' ]) {
            $output .= 'SELECT ' . implode(', ', $this->request[ 'columns' ]) . ' ';
        } else {
            $output .= 'SELECT * ';
        }
        if ($this->table) {
            $output .= "FROM $this->table ";
        }
        foreach ($this->request[ 'leftJoin' ] as $value) {
            $output .= "LEFT JOIN {$value[ 'table' ]} ON ";
        }
        if ($this->where) {
            $output .= 'WHERE ';
        }
        foreach ($this->request[ 'union' ] as $union) {
            $output .= $union['type'] === 'simple'
                ? 'UNION '
                : 'UNION ALL';
            $output .="({$union[ 'request' ]}) ";
        }
        if ($this->orderBy) {
            foreach ($this->orderBy as $table => $order) {
                $output .= "ORDER BY $table $order ";
            }
        }
        if ($this->limit) {
            $output .= "LIMIT $this->limit ";
        }
        if ($this->offset) {
            $output .= "OFFSET $this->offset ";
        }
        $output = substr($output, 0, -1) . ';';

        return htmlspecialchars($output);
    }

    /**
     * Permet d'utiliser les méthodes de l'objet \Queryflatfile\Where
     * et de personnaliser les closures pour certaines méthodes.
     *
     * @param string $name Nom de la méthode appelée.
     * @param array  $arg  Pararètre de la méthode.
     *
     * @return $this
     */
    public function __call($name, $arg)
    {
        if ($this->where === null) {
            $this->where = new Where();
        }
        if (method_exists($this->where, $name)) {
            if ($name === 'in' && $arg[ 1 ] instanceof \Closure) {
                $request  = new Request($this->schema);
                call_user_func_array($arg[ 1 ], [ &$request ]);
                $arg[ 1 ] = $request->lists();
            }

            call_user_func_array([ $this->where, $name ], $arg);

            return $this;
        }
    }

    /**
     * Ajoute un schéma de données à notre requête.
     *
     * @param \Queryflatfile\Schema $sch
     *
     * @return $this
     */
    public function setSchema(Schema $sch)
    {
        $this->schema = $sch;

        return $this;
    }

    /**
     * Lit les données d'une table.
     *
     * @param string $name Nom de la table.
     *
     * @return array Données de la table.
     */
    public function getTableData($name)
    {
        $sch = $this->schema->getSchemaTable($name);

        return $this->schema->read($this->schema->getPath(), $sch[ 'table' ]);
    }

    /**
     * Retourne le nom des champs sélectionnées.
     *
     * @return array
     */
    public function getSelect()
    {
        return $this->request[ 'columns' ];
    }

    /**
     * Retourne les clauses de la requête exécutées.
     *
     * @return string Chaine de caractère à évaluer.
     */
    public function getWhere()
    {
        return $this->where->execute();
    }

    /**
     * Enregistre les champs sélectionnées par la requête.
     * En cas d'absence de selection, la requêtes retournera toutes les champs.
     *
     * @param array $columns Liste ou tableau des noms des colonnes.
     *
     * @return $this
     */
    public function select()
    {
        $columns = func_get_args();

        foreach ($columns as $column) {
            /* Dans le cas ou les colonnes sont normales. */
            if (!\is_array($column)) {
                $this->request[ 'columns' ][] = $column;

                continue;
            }
            /* Dans le cas ou les colonnes sont dans un tableau. */
            foreach ($column as $fields) {
                $this->request[ 'columns' ][] = $fields;
            }
        }

        return $this;
    }

    /**
     * Enregistre le nom de la source des données principale de la requête.
     *
     * @param string $table Nom de la table.
     *
     * @return $this
     */
    public function from($table)
    {
        $this->table       = $table;
        $this->tableData   = $this->getTableData($table);
        $this->tableSchema = $this->schema->getSchemaTable($table);

        return $this;
    }

    /**
     * Enregistre une jointure gauche.
     *
     * @param string         $table    Nom de la table à joindre.
     * @param string|Closure $column   Nom de la colonne d'une des tables précédentes
     *                                 ou une closure pour affiner les conditions.
     * @param string|null    $operator Opérateur logique ou null pour une closure.
     * @param string|null    $value    Valeur
     *                                 ou une colonne de la table jointe (au format nom_table.colonne)
     *                                 ou null pour une closure.
     *
     * @return $this
     */
    public function leftJoin($table, $column, $operator = null, $value = null)
    {
        if ($column instanceof \Closure) {
            $where = new Where();
            call_user_func_array($column, [ &$where ]);
        } else {
            $where = ( new Where())
                ->where($column, $operator, $value);
        }

        $this->request[ 'leftJoin' ][ $table ][ 'table' ] = $table;
        $this->request[ 'leftJoin' ][ $table ][ 'where' ] = $where;

        return $this;
    }

    /**
     * Enregistre une jointure droite.
     *
     * @param string         $table    Nom de la table à joindre
     * @param string|Closure $column   Nom de la colonne d'une des tables précédentes
     *                                 ou une closure pour affiner les conditions.
     * @param string|null    $operator Opérateur logique ou null pour une closure.
     * @param string|null    $value    Valeur
     *                                 ou une colonne de la table jointe (au format nom_table.colonne)
     *                                 ou null pour une closure.
     *
     * @return $this
     */
    public function rightJoin($table, $column, $operator = null, $value = null)
    {
        if ($column instanceof \Closure) {
            $where = new Where();
            call_user_func_array($column, [ &$where ]);
        } else {
            $where = ( new Where())
                ->where($column, $operator, $value);
        }

        $this->request[ 'rightJoin' ][ $table ][ 'table' ] = $table;
        $this->request[ 'rightJoin' ][ $table ][ 'where' ] = $where;

        return $this;
    }

    /**
     * Enregistre une limitation et un décalage au retour de la requête.
     *
     * @param int $limit  Nombre de résultat maximum à retourner.
     * @param int $offset Décalage sur le jeu de résultat.
     *
     * @return $this
     */
    public function limit($limit, $offset = 0)
    {
        $this->limit  = $limit;
        $this->offset = $offset;

        return $this;
    }

    /**
     * Enregistre un trie des résultats de la requête.
     *
     * @param string $columns Colonnes à trier.
     * @param string $order   Ordre du trie (asc|desc).
     *
     * @return $this
     */
    public function orderBy($columns, $order = 'asc')
    {
        $this->orderBy[ $columns ] = $order;

        return $this;
    }

    /**
     * Enregistre l'action d'insertion de données.
     * Cette fonction doit-être suivie la fonction values().
     *
     * @param string $table   Nom de la table.
     * @param array  $columns Liste des champs par ordre d'insertion dans
     *                        la fonction values().
     *
     * @return $this
     */
    public function insertInto($table, array $columns = null)
    {
        $this->execute = 'insert';
        $this->from($table)->select($columns);

        return $this;
    }

    /**
     * Cette fonction doit suivre la fonction insertInto().
     * Les valeurs doivent suivre le même ordre que les clés précédemment enregistrées.
     *
     * @param array $columns Valeurs des champs.
     *
     * @return $this
     */
    public function values(array $columns)
    {
        $this->request[ 'values' ][] = $columns;

        return $this;
    }

    /**
     * Enregistre l'action de modification de données.
     *
     * @param string $table   Nom de la table.
     * @param array  $columns key=>value des données à modifier.
     *
     * @return $this
     */
    public function update($table, array $columns = null)
    {
        $this->execute                = 'update';
        $this->from($table);
        $this->request[ 'setUpdate' ] = $columns;
        $this->request[ 'columns' ]   = array_keys($columns);

        return $this;
    }

    /**
     * Enregistre l'action de suppression de données.
     *
     * @return $this
     */
    public function delete()
    {
        $this->execute = 'delete';

        return $this;
    }

    /**
     * Enregistre une union 'simple' entre 2 ensembles.
     * Le résultat de l'union ne possède pas de doublon de ligne.
     *
     * @param \Queryflatfile\Request $request Seconde requête.
     * @param string                 $type    (simple|all) Type d'union.
     *
     * @return $this
     */
    public function union(Request $request, $type = 'simple')
    {
        $this->request[ 'union' ][] = [ 'request' => $request, 'type' => $type ];

        return $this;
    }

    /**
     * Enregistre une union all entre 2 ensembles.
     * Les doublons de lignes figure dans le resultat de l'union.
     *
     * @param \Queryflatfile\Request $request
     *
     * @return $this
     */
    public function unionAll(Request $request)
    {
        return $this->union($request, 'all');
    }

    /**
     * Lance l'exécution d'une requête de création, modification ou suppression.
     *
     * @throws BadFunctionException
     */
    public function execute()
    {
        $this->fetchPrepareFrom();
        $this->loadAllColumnsSchema();
        $this->fetchPrepareSelect();
        $this->fetchPrepareWhere();

        if ($this->execute === 'insert') {
            $this->executeInsert();
        } elseif ($this->execute === 'update') {
            $this->executeUpdate();
        } elseif ($this->execute === 'delete') {
            $this->executeDelete();
        } else {
            throw new BadFunctionException('Only the insert, update and delete functions can be executed.');
        }

        $file = $this->tableSchema[ 'table' ];

        $this->schema->save($this->schema->getPath(), $file, $this->tableData);
        $this->init();
    }

    /**
     * Retourne tous les résultats de la requête.
     *
     * @return array les données
     */
    public function fetchAll()
    {
        $this->fetchPrepareFrom();
        $this->loadAllColumnsSchema();
        $this->fetchPrepareSelect();
        $this->fetchPrepareWhere();
        $this->fetchPrepareOrderBy();
        $this->fetchPrepareUnion();
        $this->fetchPrepareLimit();

        $return = [];
        /* Le pointeur en cas de limite de résultat. */
        $i      = 0;

        /*
         * Exécution des jointures.
         * La réunion et l'intersection des ensembles sont soumis à la loi interne * et donc commutative.
         */
        foreach ($this->request[ 'leftJoin' ] as $value) {
            $this->executeLeftJoin($value[ 'table' ], $value[ 'where' ]);
        }

        foreach ($this->request[ 'rightJoin' ] as $value) {
            $this->executeRightJoin($value[ 'table' ], $value[ 'where' ]);
        }

        $limitHandel = $this->orderBy || $this->request['union'];
        foreach ($this->tableData as $row) {
            /* WHERE */
            if ($this->where && !$this->where->execute($row)) {
                continue;
            }

            /* LIMITE */
            if ($this->limit && !$limitHandel) {
                if ($i++ < $this->offset) {
                    continue;
                }
                if (($i++ > $this->offset + $this->limit) && ($this->limit <= count($return))) {
                    break;
                }
            }

            /* SELECT */
            if ($this->lists !== null) {
                $return[] = $row[ $this->lists ];
            } elseif ($this->request[ 'columns' ]) {
                $column   = array_flip($this->request[ 'columns' ]);
                $return[] = array_intersect_key($row, $column);
            } else {
                $return[] = $row;
            }
        }

        /* UNION */
        foreach ($this->request[ 'union' ] as $union) {
            /* Si le retour est demandé en liste. */
            $fetch = $this->lists !== null
                ? $union[ 'request' ]->lists()
                : $union[ 'request' ]->fetchAll();

            /**
             * UNION ALL
             * Pour chaque requêtes unions, on récupère les résultats.
             * On merge puis on supprime les doublons.
             */
            $return = array_merge($return, $fetch);

            /* UNION */
            if ($union[ 'type' ] !== 'all') {
                $return = $this->arrayUniqueMultidimensional($return);
            }
        }

        /* ORDER BY */
        if ($this->orderBy) {
            $return = $this->executeOrderBy($return, $this->orderBy);
        }

        /* LIMIT */
        if ($this->limit && $limitHandel) {
            $return = array_slice($return, $this->offset, $this->limit);
        }

        $this->init();

        return $return;
    }

    /**
     * Retourne le premier résultat de la requête.
     *
     * @return array Résultat de la requête.
     */
    public function fetch()
    {
        $fetch  = $this->limit(1)->fetchAll();

        return !empty($fetch[ 0 ])
            ? $fetch[ 0 ]
            : [];
    }

    /**
     * Retourne les résultats de la requête sous forme de tableau simple,
     * composé uniquement du champ passé en paramètre ou du premier champ sélectionné.
     *
     * @param string|null $name Nom du champ.
     *
     * @throws ColumnsNotFoundException
     * @return array                    Liste du champ passé en paramètre.
     */
    public function lists($name = null)
    {
        if ($name !== null) {
            $this->lists = $name;
        } elseif ($this->request[ 'columns' ]) {
            $this->lists = $this->request[ 'columns' ][ 0 ];
        } else {
            throw new ColumnsNotFoundException('No key selected for the list.');
        }

        return $this->fetchAll();
    }

    /**
     * Initialise les paramètre de la requête.
     */
    public function init()
    {
        $this->where     = null;
        $this->tableData = null;
        $this->table     = '';
        $this->request   = [
            'allColumnsSchema' => [],
            'columns'          => [],
            'leftJoin'         => [],
            'rightJoin'        => [],
            'setUpdate'        => [],
            'union'            => [],
            'values'           => [],
        ];
        $this->limit     = 0;
        $this->offset    = 0;
        $this->delete    = false;
        $this->orderBy   = [];
        $this->execute   = null;
        $this->lists     = null;

        return $this;
    }

    /**
     * Revoie les instances uniques d'un tableau multidimensionnel.
     *
     * @param array $input Table multidimensionnelle.
     *
     * @return array Tableau multidimensionnel avec des entrées uniques.
     */
    public static function arrayUniqueMultidimensional(array $input)
    {
        /* Sérialise les données du tableaux. */
        $serialized = array_map('serialize', $input);

        /* Supprime les doublons sérialisés. */
        $unique = array_unique($serialized);

        /* Redonne les clés au tableau */
        $output = array_intersect_key($input, $unique);

        /* Renvoie le tableau avec ses clés ré-indexé */
        return array_values($output);
    }

    /**
     * Exécute le calcule d'une jointure droite entre 2 ensembles.
     *
     * @param string $table Nom de la table à joindre.
     * @param Where  $where Condition de la jointure.
     *
     * @return $this
     */
    protected function executeRightJoin($table, Where $where)
    {
        $result       = [];
        $tableJoin    = $this->getTableData($table);
        $rowTableNull = $this->getRowTableNull($table);

        foreach ($tableJoin as $rowJoin) {
            /* Si les lignes se sont jointes. */
            $addRow = false;
            /* Join les tables. */
            foreach ($this->tableData as $row) {
                /* Vérifie les conditions. */
                if ($where->executeJoin($row, $rowJoin)) {
                    /* Join les lignes si la condition est bonne. */
                    $result[] = array_merge($row, $rowJoin);
                    $addRow   = true;
                }
            }

            /*
             * Si aucun résultat n'est trouvé alors la ligne est remplie
             * avec les colonnes de la table jointe avec des valeurs null.
             */
            if (!$addRow) {
                $result[] = array_merge($rowTableNull, $rowJoin);
            }
        }
        $this->tableData = $result;

        return $this;
    }

    /**
     * Exécute le calcule d'une jointure gauche entre 2 ensembles.
     *
     * @param string $table Nom de la table à joindre.
     * @param Where  $where Condition de la jointure.
     *
     * @return $this
     */
    protected function executeLeftJoin($table, Where $where)
    {
        $result       = [];
        $tableJoin    = $this->getTableData($table);
        $rowTableNull = $this->getRowTableNull($table);

        foreach ($this->tableData as $row) {
            /* Si les lignes se sont jointes. */
            $addRow = false;
            /* Join les tables. */
            foreach ($tableJoin as $rowJoin) {
                /* Vérifie les conditions. */
                if ($where->executeJoin($row, $rowJoin)) {
                    /* Join les lignes si la condition est bonne. */
                    $result[] = array_merge($rowJoin, $row);
                    $addRow   = true;
                }
            }

            /*
             * Si aucun resultat n'est trouvé alors la ligne est remplie
             * avec les colonnes de la table jointe avec des valeurs null.
             */
            if (!$addRow) {
                $result[] = array_merge($rowTableNull, $row);
            }
        }
        $this->tableData = $result;

        return $this;
    }

    /**
     * Trie le tableau en fonction des clés paramétrés.
     *
     * @param array $data Données à trier.
     * @param array $keys Clés sur lesquelles le trie s'exécute.
     *
     * @return array les données triées
     */
    protected function executeOrderBy(array $data, array $keys)
    {
        $keyLength = count($keys);

        if (!$keys) {
            return $data;
        }

        foreach ($keys as &$value) {
            $value = $value == 'desc' || $value == -1
                ? -1
                : ($value == 'skip' || $value === 0
                ? 0
                : 1);
        }

        usort($data, function ($a, $b) use ($keys, $keyLength) {
            $sorted = 0;
            $ix     = 0;

            while ($sorted === 0 && $ix < $keyLength) {
                $k = $this->obIx($keys, $ix);
                if ($k) {
                    $dir    = $keys[ $k ];
                    $sorted = $this->keySort($a[ $k ], $b[ $k ], $dir);
                    $ix++;
                }
            }

            return $sorted;
        });

        return $data;
    }

    /**
     * Exécute l'insertion de données.
     *
     * @throws ColumnsNotFoundException
     */
    protected function executeInsert()
    {
        /* Si l'une des colonnes est de type incrémental. */
        $increments = $this->getIncrement();

        /* Je charge les colonnes de mon schéma. */
        $schemaColumns = $this->tableSchema[ 'fields' ];

        foreach ($this->request[ 'values' ] as $column) {
            /* Pour chaque ligne je vérifie si le nombre de colonne correspond au nombre valeur insérée. */
            if (count($this->request[ 'columns' ]) != count($column)) {
                throw new ColumnsNotFoundException('keys : ' . implode(',', $this->request[ 'columns' ]) . ' != ' . implode(',', $column));
            }

            /* Je prépare l'association clé=>valeur pour chaque ligne à insérer. */
            $row = array_combine($this->request[ 'columns' ], $column);

            foreach ($schemaColumns as $field => $arg) {
                /* Si mon champs existe dans le schema. */
                if (isset($row[ $field ])) {
                    $data[ $field ] = TableBuilder::checkValue($field, $arg[ 'type' ], $row[ $field ], $arg);
                    /* Si le champ est de type incrémental et que sa valeur est supérieure à celui enregistrer dans le schéma. */
                    if ($arg[ 'type' ] === 'increments' && ($data[ $field ] > $increments)) {
                        $increments = $data[ $field ];
                    }

                    continue;
                }

                /* Si mon champ n'existe pas et qu'il de type incrémental. */
                if (!isset($row[ $field ]) && $arg[ 'type' ] === 'increments') {
                    $increments++;
                    $data[ $field ] = $increments;

                    continue;
                }

                /* Sinon on vérifie si une valeur par défaut lui est attribué. */
                $data[ $field ] = $this->schema->getValueDefault($field, $arg);
            }

            $this->tableData[] = $data;
        }
        /* Met à jour les valeurs incrémentales dans le schéma de la table. */
        if ($increments !== null) {
            $this->schema->setIncrements($this->table, $increments);
        }
    }

    /**
     * Exécute le calcul de mise à jour des données.
     */
    protected function executeUpdate()
    {
        /* La variable $row est utilisé dans le test d'évaluation. */
        foreach ($this->tableData as &$row) {
            if ($this->where && !$this->where->execute($row)) {
                continue;
            }
            $row = array_merge($row, $this->request[ 'setUpdate' ]);
        }
    }

    /**
     * Supprime des lignes de la table en fonction des conditions et sauvegarde la table.
     */
    protected function executeDelete()
    {
        foreach ($this->tableData as $key => $row) {
            if ($this->where && !$this->where->execute($row)) {
                continue;
            }
            unset($this->tableData[ $key ]);
        }
        $this->tableData = array_values($this->tableData);
    }

    /**
     * Retourne les champs incrémental de la courrante.
     *
     * @return array Tableau des champ incrémentales et leur valeur.
     */
    protected function getIncrement()
    {
        return $this->tableSchema[ 'increments' ];
    }

    /**
     * Fonction d'appui à orderByExecute().
     *
     * @param array $obj
     * @param int   $ix
     *
     * @return bool|int
     */
    private function obIx(array $obj, $ix)
    {
        $size = 0;
        foreach ($obj as $key => $value) {
            if ($size == $ix) {
                return $key;
            }
            $size++;
        }

        return false;
    }

    /**
     * Fonction de trie de orderByExecute
     *
     * @param type $a
     * @param type $b
     * @param type $c
     *
     * @return int
     */
    private function keySort($a, $b, $c = null)
    {
        $d = $c !== null
            ? $c
            : 1;
        if ($a == $b) {
            return 0;
        }

        return ($a > $b)
            ? 1 * $d
            : -1 * $d;
    }

    /**
     * Charge les colonnes de la table courante et des tables de jointure.
     */
    private function loadAllColumnsSchema()
    {
        $schema = $this->tableSchema[ 'fields' ];

        foreach ($this->request[ 'leftJoin' ] as $value) {
            $schemaTable = $this->schema->getSchemaTable($value[ 'table' ]);
            $schema      = array_merge($schema, $schemaTable[ 'fields' ]);
        }

        foreach ($this->request[ 'rightJoin' ] as $value) {
            $schemaTable = $this->schema->getSchemaTable($value[ 'table' ]);
            $schema      = array_merge($schema, $schemaTable[ 'fields' ]);
        }

        $this->request[ 'allColumnsSchema' ] = $schema;
    }

    /**
     * Vérifie l'existence de la table courante.
     *
     * @throws TableNotFoundException
     */
    private function fetchPrepareFrom()
    {
        if ($this->table === '') {
            throw new TableNotFoundException("Table $this->table is missing.");
        }
    }

    /**
     * Vérifie pour tous les champs sélectionnées, leur l'existence à partir du schéma.
     */
    private function fetchPrepareSelect()
    {
        if ($this->request[ 'columns' ]) {
            $this->diffColumns($this->request[ 'columns' ]);
        } else {
            /* Si aucunes colonnes selectionnées. */
            $this->request[ 'columns' ] = [];
        }
    }

    private function fetchPrepareLimit()
    {
        if (!\is_int($this->limit) || $this->limit < 0) {
            throw new QueryException('The limit must be a non-zero positive integer.');
        }
        if (!\is_int($this->offset) || $this->offset < 0) {
            throw new QueryException('The offset must be a non-zero positive integer.');
        }
    }

    /**
     * Vérifie pour toutes les jointures (LEFT JOIN, RIGHT JOIN) et les clauses conditionnées (WHERE),
     * l'existence des champs à partir du schéma.
     */
    private function fetchPrepareWhere()
    {
        $columns = [];
        /* Merge toutes les colonnes des conditions de chaque jointure. */
        foreach ($this->request[ 'leftJoin' ] as $value) {
            $columns = array_merge($columns, $value[ 'where' ]->getColumns());
        }

        /* Merge toutes les colonnes des conditions de chaque jointure. */
        foreach ($this->request[ 'rightJoin' ] as $value) {
            $columns = array_merge($columns, $value[ 'where' ]->getColumns());
        }

        /* Merge les colonnes des conditions de la requête courante. */
        if ($this->where) {
            $columns = array_merge($columns, $this->where->getColumns());
        }

        if ($columns) {
            $this->diffColumns($columns);
        }
    }

    /**
     * Vérifie pour tous les ORDER BY l'existence des champs à partir du schéma.
     */
    private function fetchPrepareOrderBy()
    {
        if ($this->orderBy) {
            $columns = array_keys($this->orderBy);
            $this->diffColumns($columns);
        }
    }

    /**
     * Vérifie la cohérence des champs dans chaque requêtes entre les UNIONS.
     *
     * @throws ColumnsNotFoundException
     */
    private function fetchPrepareUnion()
    {
        foreach ($this->request[ 'union' ] as $request) {
            if (count($this->getSelect()) != count($request[ 'request' ]->getSelect())) {
                throw new ColumnsNotFoundException('The number of fields in the selections are different : '
                . implode(',', $this->getSelect())
                . ' != '
                . implode(',', $request[ 'request' ]->getSelect()));
            }
        }
    }

    /**
     * Déclenche une exception si l'un des champs passés en paramètre diffère
     * des champs disponibles dans les tables.
     *
     * @param array $columns Liste des champs.
     *
     * @throws ColumnsNotFoundException
     */
    private function diffColumns(array $columns)
    {
        $all  = $this->request[ 'allColumnsSchema' ];
        $diff = array_diff_key(array_flip($columns), $all);

        if ($diff) {
            $columnsDiff = array_flip($diff);

            throw new ColumnsNotFoundException('Column ' . implode(',', $columnsDiff) . ' is absent : ' . $this);
        }
    }

    /**
     * Retourne un tableau associatif avec pour clé les champs de la table et pour valeur null.
     * Si des champs existent dans le schéma ils seront rajouté. Fonction utilisée
     * pour les jointures en cas d'absence de résultat.
     *
     * @param string $table Nom de la table.
     *
     * @return array
     */
    private function getRowTableNull($table)
    {
        /* Le schéma de la table à joindre. */
        $sch         = $this->schema->getSchemaTable($table);
        /* Prend les noms des champs de la table à joindre. */
        $rowTableKey = array_keys($sch[ 'fields' ]);
        /* Prend les noms des champs dans la requête précédente. */
        if (isset($this->tableSchema[ 'fields' ])) {
            $rowTableAllKey = array_keys($this->tableSchema[ 'fields' ]);
            $rowTableKey    = array_merge($rowTableKey, $rowTableAllKey);
        }
        /* Utilise les noms pour créer un tableau avec des valeurs null. */
        return array_fill_keys($rowTableKey, null);
    }
}


<?php

/**
 * Queryflatfile
 *
 * @package Queryflatfile
 * @author  Mathieu NOËL <mathieu@soosyze.com>
 * @license https://github.com/soosyze/queryflatfile/blob/master/LICENSE (MIT License)
 */

namespace Queryflatfile\Driver;

/**
 * Implémentation de Queryflatfile\DriverInterface par l'héritage de Queryflatfile\Driver
 * Manipule des données sérialisées dans des fichiers texte.
 *
 * @author  Mathieu NOËL
 */
class Txt extends \Queryflatfile\Driver
{
    /**
     * {@inheritDoc}
     */
    public function create($path, $fileName, array $data = [])
    {
        $file = $this->getFile($path, $fileName);

        if (!file_exists($path)) {
            mkdir($path, 0775);
        }
        if (!file_exists($file)) {
            $fichier = fopen($file, 'w+');
            fwrite($fichier, serialize($data));

            return fclose($fichier);
        }

        return false;
    }

    /**
     * {@inheritDoc}
     */
    public function read($path, $fileName)
    {
        $file = $this->getFile($path, $fileName);

        $this->isExist($file);
        $this->isRead($file);

        $data = file_get_contents($file);

        return unserialize($data);
    }

    /**
     * {@inheritDoc}
     */
    public function save($path, $fileName, array $data)
    {
        $file = $this->getFile($path, $fileName);

        $this->isExist($file);
        $this->isWrite($file);

        $fp = fopen($file, 'w');
        fwrite($fp, serialize($data));

        return fclose($fp);
    }

    /**
     * {@inheritDoc}
     */
    public function getExtension()
    {
        return 'txt';
    }
}


<?php

namespace MeadSteve\MonoSnag;

use \Monolog\Handler\AbstractProcessingHandler;
use \Monolog\Logger;

class BugsnagHandler extends AbstractProcessingHandler
{
    /**
     * monolog error codes mapped on to bugSnag severities.
     * @var string[]
     */
    protected $severityMapping = array(
        Logger::DEBUG     => 'info',
        Logger::INFO      => 'info',
        Logger::NOTICE    => 'info',
        Logger::WARNING   => 'warning',
        Logger::ERROR     => 'error',
        Logger::CRITICAL  => 'error',
        Logger::ALERT     => 'error',
        Logger::EMERGENCY => 'error'
    );

    /**
     * @var \Bugsnag\Client
     */
    protected $client;

    public function __construct(\Bugsnag\Client $client, $level = Logger::ERROR, $bubble = true)
    {
        parent::__construct($level, $bubble);
        $this->client = $client;
    }

    /**
     * Writes the record down to the log of the implementing handler
     *
     * @param  array $record
     * @return void
     */
    protected function write(array $record)
    {
        $severity = $this->getSeverity($record['level']);
        if (isset($record['context']['exception'])) {
            $this->client->notifyException(
                $record['context']['exception'],
                function (\Bugsnag\Report $report) use ($record, $severity) {
                    $report->setSeverity($severity);
                    if (isset($record['extra'])) {
                        $report->setMetaData($record['extra']);
                    }
                }
            );
        } else {
            $this->client->notifyError(
                (string) $record['message'],
                (string) $record['formatted'],
                function (\Bugsnag\Report $report) use ($record, $severity) {
                    $report->setSeverity($severity);
                    if (isset($record['extra'])) {
                        $report->setMetaData($record['extra']);
                    }
                }
            );
        }
    }

    /**
     * Returns the Bugsnag severiry from a monolog error code.
     * @param int $errorCode - one of the Logger:: constants.
     * @return string
     */
    protected function getSeverity($errorCode)
    {
        if (isset($this->severityMapping[$errorCode])) {
            return $this->severityMapping[$errorCode];
        } else {
            return $this->severityMapping[Logger::ERROR];
        }
    }
}


<?php

/**
 * Queryflatfile
 *
 * @package Queryflatfile
 * @author  Mathieu NOËL <mathieu@soosyze.com>
 * @license https://github.com/soosyze/queryflatfile/blob/master/LICENSE (MIT License)
 */

namespace Queryflatfile;

use Queryflatfile\Exception\Query\OperatorNotFound;

/**
 * Pattern fluent pour la création des clauses (conditions) de manipulation des données.
 *
 * @author Mathieu NOËL
 */
class Where
{
    /**
     * Les conditions à exécuter.
     *
     * @var array
     */
    protected $where = [];

    /**
     * Les conditions binaires autorisées.
     *
     * @var array
     */
    protected $contidion = [
        '=',
        '==',
        '===',
        '!==',
        '!=',
        '<>',
        '<',
        '<=',
        '>',
        '>=',
        'like',
        'not like',
        'ilike',
        'not ilike'
    ];

    /**
     * Les colonnes appelées pour les clauses.
     *
     * @var array
     */
    protected $columns = [];

    /**
     * Ajoute une condition simple pour la requête.
     * Si la valeur du champ est égal (non égale, supérieur à, ...)  par rapport à une valeur.
     *
     * @param callable|string $column   Sous condition ou une colonne.
     * @param null|string     $operator Type de condition.
     * @param null|string     $value    Valeur de teste.
     * @param string          $bool     Porte logique de la condition (and|or).
     * @param bool            $not      Inverse la condition.
     *
     * @throws OperatorNotFound
     * @return $this
     */
    public function where(
    $column,
        $operator = null,
        $value = null,
        $bool = 'and',
        $not = false
    ) {
        if ($column instanceof \Closure) {
            $where         = new Where();
            call_user_func_array($column, [ &$where ]);
            $this->where[] = [
                'type'   => __FUNCTION__ . 'Callback',
                'column' => $where->getColumns(),
                'value'  => $where,
                'bool'   => $bool,
                'not'    => $not
            ];
            $this->columns = array_merge($this->columns, $where->getColumns());

            return $this;
        }
        if ($value === null) {
            $value    = $operator;
            $operator = '=';
        }

        /* Pour que l'opérateur soit insensible à la case. */
        $condition = strtolower($operator);

        /* Si l'opérateur n'est pas autorisé. */
        if (!in_array($condition, $this->contidion)) {
            throw new OperatorNotFound("The condition $condition is not exist.");
        }
        if ($condition === '=') {
            $condition = '===';
        } elseif (in_array($condition, [ 'like', 'ilike', 'not like', 'not ilike' ])) {
            /* Protection des caractères spéciaux des expressions rationnelles autre que celle imposée. */
            $value   = preg_quote($value);
            /* Le paterne commun au 4 conditions. */
            $pattern = '/^' . strtr($value, ['%' => '.*']);

            /* Pour rendre la regex inssencible à la case. */
            $pattern .= $condition === 'like' || $condition === 'not like'
                ? '$/'
                : '$/i';

            /* Pour inverser le comportement du like. */
            $not = $condition === 'not like' || $condition === 'not ilike';

            return $this->regex($column, $pattern, $bool, $not);
        }

        $this->where[]   = [
            'type'      => __FUNCTION__,
            'column'    => $column,
            'condition' => $condition,
            'value'     => $value,
            'bool'      => $bool,
            'not'       => $not
        ];
        $this->columns[] = $column;

        return $this;
    }

    /**
     * Alias inverse de la fonction where().
     *
     * @param callable|string $column
     * @param null|string     $operator
     * @param null|string     $value
     *
     * @return $this
     */
    public function notWhere($column, $operator = null, $value = null)
    {
        $this->where($column, $operator, $value, 'and', true);

        return $this;
    }

    /**
     * Alias avec la porte logique 'OR' de la fonction where().
     *
     * @param callable|string $column
     * @param null|string     $operator
     * @param null|string
     *
     * @return $this
     */
    public function orWhere($column, $operator = null, $value = null)
    {
        $this->where($column, $operator, $value, 'or');

        return $this;
    }

    /**
     * Alias inverse avec la porte logique 'OR' de la fonction where().
     *
     * @param callable|string
     * @param null|string
     * @param null|string
     *
     * @return $this
     */
    public function orNotWhere($column, $operator = null, $value = null)
    {
        $this->where($column, $operator, $value, 'or', true);

        return $this;
    }

    /**
     * Ajoute une condition between à la requête.
     * Si la valeur du champ est compris entre 2 valeurs.
     *
     * @param string $column Nom de la colonne.
     * @param mixed  $min    Valeur minimum ou égale.
     * @param mixed  $max    Valeur maximum ou égale.
     * @param string $bool   Porte logique de la condition (and|or).
     * @param bool   $not    Inverse la condition.
     *
     * @return $this
     */
    public function between($column, $min, $max, $bool = 'and', $not = false)
    {
        $this->where[]   = [
            'type'   => __FUNCTION__,
            'column' => $column,
            'min'    => $min,
            'max'    => $max,
            'bool'   => $bool,
            'not'    => $not
        ];
        $this->columns[] = $column;

        return $this;
    }

    /**
     * Alias inverse de la fonction between().
     *
     * @param callable|string $column
     * @param mixed           $min
     * @param mixed           $max
     *
     * @return $this
     */
    public function notBetween($column, $min, $max)
    {
        $this->between($column, $min, $max, 'and', true);

        return $this;
    }

    /**
     * Alias avec la porte logique 'OR' de la fonction between().
     *
     * @param callable|string $column
     * @param mixed           $min
     * @param mixed           $max
     *
     * @return $this
     */
    public function orBetween($column, $min, $max)
    {
        $this->between($column, $min, $max, 'or');

        return $this;
    }

    /**
     * Alias inverse avec la porte logique 'OR' de la fonction between().
     *
     * @param callable|string $column
     * @param mixed           $min
     * @param mixed           $max
     *
     * @return $this
     */
    public function orNotBetween($column, $min, $max)
    {
        $this->between($column, $min, $max, 'or', true);

        return $this;
    }

    /**
     * Ajoute une condition in à la requête.
     * Si la valeur du champs est contenu dans une liste.
     *
     * @param string $column Nom de la colonne.
     * @param array  $value  Valeurs à tester.
     * @param string $bool   Porte logique de la condition (and|or).
     * @param bool   $not    Inverse la condition.
     *
     * @return $this
     */
    public function in($column, array $value, $bool = 'and', $not = false)
    {
        $this->where[]   = [
            'type'      => __FUNCTION__,
            'condition' => 'in',
            'column'    => $column,
            'value'     => $value,
            'bool'      => $bool,
            'not'       => $not
        ];
        $this->columns[] = $column;

        return $this;
    }

    /**
     * Alias inverse de la fonction in().
     *
     * @param string $column
     * @param array  $value
     *
     * @return $this
     */
    public function notIn($column, array $value)
    {
        $this->in($column, $value, 'and', true);

        return $this;
    }

    /**
     * Alias avec la porte logique 'OR' de la fonction in().
     *
     * @param string $column
     * @param array  $value
     *
     * @return $this
     */
    public function orIn($column, array $value)
    {
        $this->in($column, $value, 'or');

        return $this;
    }

    /**
     * Alias inverse avec la porte logique 'OR' de la fonction in().
     *
     * @param string $column
     * @param array  $value
     *
     * @return $this
     */
    public function orNotIn($column, array $value)
    {
        $this->in($column, $value, 'or', true);

        return $this;
    }

    /**
     * Ajoute une condition isNull à la requête.
     * Si la valeur du champ est strictement égale à null.
     *
     * @param string $column Nom de la colonne.
     * @param string $bool   Porte logique de la condition (and|or).
     * @param bool   $not    Inverse la condition.
     *
     * @return $this
     */
    public function isNull(
    $column,
        $bool = 'and',
        $not = false
    ) {
        $this->where[]   = [
            'type'      => __FUNCTION__,
            'condition' => '===',
            'column'    => $column,
            'value'     => null,
            'bool'      => $bool,
            'not'       => $not
        ];
        $this->columns[] = $column;

        return $this;
    }

    /**
     * Alias inverse de la fonction isNull().
     *
     * @param string $column
     *
     * @return $this
     */
    public function isNotNull($column)
    {
        $this->isNull($column, 'and', true);

        return $this;
    }

    /**
     * Alias avec la porte logique 'OR' de la fonction isNull().
     *
     * @param string $column
     *
     * @return $this
     */
    public function orIsNull($column)
    {
        $this->isNull($column, 'or');

        return $this;
    }

    /**
     * Alias inverse avec la porte logique 'OR' de la fonction isNull()
     *
     * @param string $column
     *
     * @return $this
     */
    public function orIsNotNull($column)
    {
        $this->isNull($column, 'or', true);

        return $this;
    }

    /**
     * Ajoute une condition avec une expression régulière à la requête.
     *
     * @param string $column  Nom de la colonne.
     * @param string $pattern Expression régulière.
     * @param string $bool    Porte logique de la condition (and|or).
     * @param bool   $not     Inverse la condition.
     *
     * @return $this
     */
    public function regex($column, $pattern, $bool = 'and', $not = false)
    {
        $this->where[]   = [
            'type'   => __FUNCTION__,
            'column' => $column,
            'value'  => $pattern,
            'bool'   => $bool,
            'not'    => $not
        ];
        $this->columns[] = $column;

        return $this;
    }

    /**
     *  Alias inverse de la fonction regex().
     *
     * @param string $column
     * @param string $pattern
     *
     * @return $this
     */
    public function notRegex($column, $pattern)
    {
        $this->regex($column, $pattern, 'and', true);

        return $this;
    }

    /**
     *  Alias avec la porte logique 'OR' de la fonction regex().
     *
     * @param string $column
     * @param string $pattern
     *
     * @return $this
     */
    public function orRegex($column, $pattern)
    {
        $this->regex($column, $pattern, 'or');

        return $this;
    }

    /**
     *  Alias inverse avec la porte logique 'OR' de la fonction regex()
     *
     * @param string $column
     * @param string $pattern
     *
     * @return $this
     */
    public function orNotRegex($column, $pattern)
    {
        $this->regex($column, $pattern, 'or', true);

        return $this;
    }

    /**
     * Retourne toutes les colonnes utilisées pour créer la clause.
     *
     * @return array Colonnes.
     */
    public function getColumns()
    {
        $output = [];
        foreach ($this->where as $value) {
            if (!\is_array($value[ 'column' ])) {
                $output[] = $this->getColumn($value[ 'column' ]);

                continue;
            }

            $output = array_merge($output, $value[ 'column' ]);
        }

        return $this->columns;
    }

    /**
     * Retourne TRUE si la suite de condition enregistrée valide les champs du tableau.
     *
     * @param array $row Tableau associatif de champ.
     *
     * @return bool
     */
    public function execute(array $row)
    {
        foreach ($this->where as $key => $value) {
            $columns = $value[ 'column' ];
            switch ($value[ 'type' ]) {
                case 'where':
                case 'isNull':
                case 'in':
                    $predicate = self::predicate($row[ $columns ], $value[ 'condition' ], $value[ 'value' ]);

                    break;
                case 'whereCallback':
                    $predicate = $value[ 'value' ]->execute($row);

                    break;
                case 'between':
                    $predicate = self::predicate($row[ $columns ], '>=', $value[ 'min' ]) && self::predicate($row[ $columns ], '<=', $value[ 'max' ]);

                    break;
                case 'regex':
                    $predicate = !empty($value[ 'not' ])
                        ? !preg_match($value[ 'value' ], $row[ $columns ])
                        : preg_match($value[ 'value' ], $row[ $columns ]);
                    $predicate &= self::predicate($row[ $columns ], '!==', null);

                    break;
            }

            if ($value[ 'type' ] !== 'regex') {
                $predicate = !empty($value[ 'not' ])
                    ? !$predicate
                    : $predicate;
            }

            if ($key == 0) {
                $output = $predicate;
            } elseif ($value[ 'bool' ] === 'and') {
                $output &= $predicate;
            } else {
                $output |= $predicate;
            }
        }

        return $output;
    }

    /**
     * Retourne TRUE si la suite de condition enregistrée valide les champs du tableau
     * par rapport à un autre tableau.
     *
     * @param array $row      Tableau associatif de champ.
     * @param array $rowTable Tableau associatif de champ à tester.
     *
     * @return bool
     */
    public function executeJoin(array $row, array $rowTable)
    {
        foreach ($this->where as $key => $value) {
            $columns = $value[ 'column' ];
            switch ($value[ 'type' ]) {
                case 'where':
                case 'isNull':
                    $val       = $this->isColumn($value[ 'value' ])
                        ? $rowTable[ substr(strrchr($value[ 'value' ], '.'), 1) ]
                        : $value[ 'value' ];
                    $predicate = self::predicate($row[ $columns ], $value[ 'condition' ], $val);

                    break;
                case 'whereCallback':
                    $predicate = $value[ 'value' ]->execute($row);

                    break;
            }

            if ($key == 0) {
                $output = $predicate;
            } elseif ($value[ 'bool' ] === 'and') {
                $output &= $predicate;
            } else {
                $output |= $predicate;
            }
        }

        return $output;
    }

    /**
     * Retourne TRUE si la condition est validée.
     *
     * @param mixed  $columns  Valeur à tester.
     * @param string $operator Condition à réaliser.
     * @param mixed  $value    Valeur de comparaison.
     *
     * @throws \Exception
     * @return bool
     */
    public static function predicate($columns, $operator, $value)
    {
        switch ($operator) {
            case '==':
                return $columns == $value;
            case '=':
            case '===':
                return $columns === $value;
            case '!==':
                return $columns !== $value;
            case '!=':
                return $columns != $value;
            case '<>':
                return $columns <> $value;
            case '<':
                return $columns < $value;
            case '<=':
                return $columns <= $value;
            case '>':
                return $columns > $value;
            case '>=':
                return $columns >= $value;
            case 'in':
                return in_array($columns, $value);
        }

        throw new \Exception("The $operator operator is not supported.");
    }

    /**
     * Si la valeur représente une colonne ou une valeur,
     * where('id', '=', 'test') ici 'test' est une valeur de type chaine de caractère,
     * where('id', '=', 'table.test') ici 'test' est une colonne puisqu'il est précédé du nom de sa table.
     *
     * @param string $value
     *
     * @return bool
     */
    protected function isColumn($value)
    {
        return \is_string($value) && strstr($value, '.');
    }

    /**
     * Retourne le nom de la colonne ou la valeur.
     *
     * @param mixed $value
     *
     * @return string
     */
    protected function getColumn($value)
    {
        return $this->isColumn($value)
            ? substr(strrchr($value, '.'), 1)
            : $value;
    }
}


<?php

/**
 * Queryflatfile
 *
 * @package Queryflatfile
 * @author  Mathieu NOËL <mathieu@soosyze.com>
 * @license https://github.com/soosyze/queryflatfile/blob/master/LICENSE (MIT License)
 */

namespace Queryflatfile;

use Queryflatfile\Exception\TableBuilder\ColumnsNotFoundException;
use Queryflatfile\Exception\TableBuilder\ColumnsValueException;
use Queryflatfile\Exception\TableBuilder\TableBuilderException;

/**
 * Pattern fluent pour la création et configuration des types de données.
 *
 * @author Mathieu NOËL
 */
class TableBuilder
{
    /**
     * Les champs et leurs paramètres.
     *
     * @var array
     */
    private $builder = [];

    /**
     * La valeur des champs incrémentaux.
     *
     * @var array
     */
    private $increment = null;

    /**
     * Enregistre un champ de type `boolean`, true ou false.
     * http://php.net/manual/fr/language.types.boolean.php
     *
     * @param string $name Nom du champ.
     *
     * @return $this
     */
    public function boolean($name)
    {
        $this->builder[ $name ][ 'type' ] = 'boolean';

        return $this;
    }

    /**
     * Enregistre un champ de type `char` avec une limite de taille par défaut de un caractère.
     * http://php.net/language.types.string
     *
     * @param string      $name   Nom du champ
     * @param numeric|int $length longueur maximum de la chaine.
     *
     * @throws Exception
     * @return $this
     */
    public function char($name, $length = 1)
    {
        if (!\is_numeric($length) || $length < 0) {
            throw new TableBuilderException('The length passed in parameter is not of numeric type.');
        }
        $this->builder[ $name ] = [ 'type' => 'char', 'length' => (int) $length ];

        return $this;
    }

    /**
     * Enregistre un champ de type `date` sous le format Y-m-d.
     *
     * @param string $name Nom du champ.
     *
     * @return $this
     */
    public function date($name)
    {
        $this->builder[ $name ][ 'type' ] = 'date';

        return $this;
    }

    /**
     * Enregistre un champ de type `datetime`, sous le format Y-m-d H:i:s.
     *
     * @param string $name Nom du champ.
     *
     * @return $this
     */
    public function datetime($name)
    {
        $this->builder[ $name ][ 'type' ] = 'datetime';

        return $this;
    }

    /**
     * Enregistre un champ de type `float`, nombre à virgule flottant.
     * http://php.net/manual/fr/language.types.float.php
     * Valeur d'insertion autorisé : 1, '1', 1.0, '1.0' (enregistrera 1.0).
     *
     * @param string $name Nom du champ.
     *
     * @return $this
     */
    public function float($name)
    {
        $this->builder[ $name ][ 'type' ] = 'float';

        return $this;
    }

    /**
     * Enregistre un champ de type `increments`, entier positif qui s'incrémente automatiquement.
     * Un seul champ increments est autorisé par table.
     * http://php.net/manual/fr/language.types.integer.php
     *
     * @param string $name nom du champ
     *
     * @throws Exception
     * @return $this
     */
    public function increments($name)
    {
        if ($this->increment !== null) {
            throw new TableBuilderException('Only one incremental column is allowed per table.');
        }

        $this->builder[ $name ][ 'type' ] = 'increments';
        $this->increment                  = 0;

        return $this;
    }

    /**
     * Enregistre un champ de type `integer`, entier signié.
     * http://php.net/manual/fr/language.types.integer.php
     * Valeur d'insertion autorisé : 1, '1', 1.1, '1.1' (enregistrera 1)
     *
     * @param string $name Nom du champ.
     *
     * @return $this
     */
    public function integer($name)
    {
        $this->builder[ $name ][ 'type' ] = 'integer';

        return $this;
    }

    /**
     * Enregistre un champ de type `string` avec une limite de taille  par défaut de 255 caractères.
     * http://php.net/language.types.string
     *
     * @param string      $name   Nom du champ.
     * @param numeric|int $length Longueur maximum de la chaine.
     *
     * @throws Exception
     * @return $this
     */
    public function string($name, $length = 255)
    {
        if (!\is_numeric($length) || $length < 0) {
            throw new TableBuilderException('The length passed in parameter is not of numeric type.');
        }
        $this->builder[ $name ] = [ 'type' => 'string', 'length' => (int) $length ];

        return $this;
    }

    /**
     * Enregistre un champ de type `text` sans limite de taille.
     * http://php.net/language.types.string
     *
     * @param string $name Nom du champ.
     *
     * @return $this
     */
    public function text($name)
    {
        $this->builder[ $name ][ 'type' ] = 'text';

        return $this;
    }

    /**
     * Enregistre un commentaire sur le dernier champ appelé.
     *
     * @param string $comment Commentaire du champ précédent.
     *
     * @return $this
     */
    public function comment($comment)
    {
        $this->checkPreviousBuild('comment');
        $this->builder[ key($this->builder) ][ '_comment' ] = $comment;

        return $this;
    }

    /**
     * Enregistre le champ précédent comme acceptant la valeur NULL.
     *
     * @return $this
     */
    public function nullable()
    {
        $this->checkPreviousBuild('nullable');
        $this->builder[ key($this->builder) ][ 'nullable' ] = true;

        return $this;
    }

    /**
     * Enregistre le champ précédent (uniquement de type integer) comme étant non signié.
     *
     * @throws ColumnsValueException
     * @return $this
     */
    public function unsigned()
    {
        $current = $this->checkPreviousBuild('unsigned');
        if ($current[ 'type' ] !== 'integer') {
            throw new ColumnsValueException("Impossiblie of unsigned type {$current[ 'type' ]} only integer).");
        }

        $this->builder[ key($this->builder) ][ 'unsigned' ] = true;

        return $this;
    }

    /**
     * Enregistre une valeur par défaut au champ précédent.
     * Lève une exception si la valeur par défaut ne correspond pas au type de valeur passée en paramètre.
     *
     * @param mixed $value Valeur à tester.
     *
     * @throws Exception
     * @return $this
     */
    public function valueDefault($value)
    {
        $current = $this->checkPreviousBuild('value default');
        $name    = key($this->builder);
        $type    = $current[ 'type' ];

        if ($type === 'increments') {
            throw new TableBuilderException('An incremental type column can not have a default value.');
        }

        $this->builder[ $name ][ 'default' ] = self::checkValue($name, $type, $value, $current);

        return $this;
    }

    /**
     * Retourne la valeur s'il correspond au type déclaré.
     * Sinon déclenche une exception.
     *
     * @param string $name  Nom du champ.
     * @param string $type  Type de donnée (string|text|int|float|bool|char|date|datetime).
     * @param mixed  $value Valeur à tester.
     * @param array  $arg   Arguments de tests optionnels (length).
     *
     * @throws ColumnsValueException
     * @return mixed
     */
    public static function checkValue($name, $type, $value, array $arg = [])
    {
        $error = 'The default value (' . $value . ') for column ' . $name . ' does not correspond to type ' . $type . '.';

        switch (strtolower($type)) {
            case 'string':
            case 'char':
                if (!\is_string($value)) {
                    throw new ColumnsValueException($error);
                }
                if (!isset($arg[ 'length' ]) || strlen($value) > $arg[ 'length' ]) {
                    throw new ColumnsValueException('The default value is larger than the specified size.');
                }

                break;
            case 'text':
                if (!\is_string($value)) {
                    throw new ColumnsValueException($error);
                }

                break;
            case 'integer':
            case 'increments':
                if (!\is_numeric($value)) {
                    throw new ColumnsValueException($error);
                }

                return (int) $value;
            case 'float':
                if (!\is_numeric($value)) {
                    throw new ColumnsValueException($error);
                }

                return (float) $value;
            case 'boolean':
                if (!\is_bool($value)) {
                    throw new ColumnsValueException($error);
                }

                break;
            case 'date':
                if (strtolower($value) === 'current_date') {
                    return 'current_date';
                }
                if (($timestamp = strtotime($value))) {
                    return date('Y-m-d', $timestamp);
                }

                throw new ColumnsValueException($error);
            case 'datetime':
                if (strtolower($value) === 'current_datetime') {
                    return 'current_datetime';
                }
                if (($timestamp = strtotime($value))) {
                    return date('Y-m-d H:i:s', $timestamp);
                }

                throw new ColumnsValueException($error);
            default:
                throw new ColumnsValueException("Type $type not supported");
        }

        return $value;
    }

    /**
     * Retourne le tableau contenant les configurations d'ajout.
     *
     * @return array Les configurations.
     */
    public function build()
    {
        return array_filter($this->builder, function ($var) {
            return !isset($var[ 'opt' ]);
        });
    }

    /**
     * Retourne le tableau contenant toutes les configurations.
     *
     * @return array Les configurations.
     */
    public function buildFull()
    {
        return $this->builder;
    }

    /**
     * Retourne la liste des champs incrémentaux.
     *
     * @return array
     */
    public function getIncrement()
    {
        return $this->increment;
    }

    /**
     * Enregistre la suppression d'une colonne.
     *
     * @param string $name Nom de la colonne.
     *
     * @return $this
     */
    public function dropColumn($name)
    {
        $this->builder[] = [ 'opt' => 'drop', 'name' => $name ];

        return $this;
    }

    /**
     * Enregistre le renommage d'une colonne.
     *
     * @param string $from Nom de la colonne.
     * @param string $to   Nouveau nom de la colonne.
     *
     * @return $this
     */
    public function renameColumn($from, $to)
    {
        $this->builder[] = [ 'opt' => 'rename', 'name' => $from, 'to' => $to ];

        return $this;
    }

    /**
     * Enregistre la modification du champ précédent.
     *
     * @return $this
     */
    public function modify()
    {
        $this->checkPreviousBuild('modify');
        $key                             = key($this->builder);
        $this->builder[ $key ][ 'opt' ]  = 'modify';
        $this->builder[ $key ][ 'name' ] = $key;

        return $this;
    }

    /**
     * Retourne le champs courant.
     * Déclenche une exception si le champ courant n'existe pas ou
     * si le champ courant est une opération.
     *
     * @param string $opt Nom de l'opération réalisé.
     *
     * @throws ColumnsNotFoundException
     * @return array                    Paramètres du champ.
     */
    protected function checkPreviousBuild($opt)
    {
        $str     = 'No column selected for ' . $opt . '.';
        if (!($current = end($this->builder))) {
            throw new ColumnsNotFoundException($str);
        }
        if (isset($current[ 'opt' ])) {
            throw new ColumnsNotFoundException($str);
        }

        return $current;
    }
}


<?php

/**
 * Queryflatfile
 *
 * @package Queryflatfile
 * @author  Mathieu NOËL <mathieu@soosyze.com>
 * @license https://github.com/soosyze/queryflatfile/blob/master/LICENSE (MIT License)
 */

namespace Queryflatfile;

/**
 * Implementation partiel Queryflatfile\DriverInterface.
 *
 * @author Mathieu NOËL
 */
abstract class Driver implements DriverInterface
{
    /**
     * {@inheritDoc}
     */
    abstract public function create($path, $fileName, array $data = []);

    /**
     * {@inheritDoc}
     */
    abstract public function read($path, $fileName);

    /**
     * {@inheritDoc}
     */
    abstract public function save($path, $fileName, array $data);

    /**
     * {@inheritDoc}
     */
    public function delete($path, $fileName)
    {
        return unlink($this->getFile($path, $fileName));
    }

    /**
     * {@inheritDoc}
     */
    public function has($path, $fileName)
    {
        return file_exists($this->getFile($path, $fileName));
    }

    /**
     * {@inheritDoc}
     */
    abstract public function getExtension();

    /**
     * Concatène le chemin, le nom du fichier et l'extension.
     *
     * @param string $path     Chemin de la table.
     * @param string $fileName Nom du fichier.
     *
     * @return string Chemin complet du fichier.
     */
    public function getFile($path, $fileName)
    {
        $DS   = DIRECTORY_SEPARATOR;
        $file = $path . $DS . $fileName . '.' . $this->getExtension();

        return str_replace('\\', $DS, $file);
    }

    /**
     * Déclenche une exception si le fichier passé en paramètre d'existe pas.
     *
     * @param string $file Chemin complet du fichier.
     *
     * @throws Exception\Driver\FileNotFoundException
     */
    protected function isExist($file)
    {
        if (!file_exists($file)) {
            throw new Exception\Driver\FileNotFoundException("The $file file is missing.");
        }
    }

    /**
     * Déclenche une exception si le fichier passé en paramètre n'a pas le droit d'écriture.
     *
     * @codeCoverageIgnore has
     *
     * @param string $file Chemin complet du fichier.
     *
     * @throws Exception\Driver\FileNotWritableException
     */
    protected function isWrite($file)
    {
        if (!\is_writable($file)) {
            throw new Exception\Driver\FileNotWritableException("The $file file is not writable.");
        }
    }

    /**
     * Déclenche une exception si le fichier passé en paramètre n'a pas le droit d'être lu.
     *
     * @codeCoverageIgnore has
     *
     * @param string $file Chemin complet du fichier.
     *
     * @throws Exception\Driver\FileNotReadableException
     */
    protected function isRead($file)
    {
        if (!\is_readable($file)) {
            throw new Exception\Driver\FileNotReadableException("The $file file is not readable.");
        }
    }
}


<?php

/**
 * Queryflatfile
 *
 * @package Queryflatfile
 * @author  Mathieu NOËL <mathieu@soosyze.com>
 * @license https://github.com/soosyze/queryflatfile/blob/master/LICENSE (MIT License)
 */

namespace Queryflatfile\Driver;

/**
 * Implémentation de Queryflatfile\DriverInterface par l'héritage de Queryflatfile\Driver
 * Manipule des données dans des fichiers de type JSON
 *
 * @author Mathieu NOËL
 */
class Json extends \Queryflatfile\Driver
{
    /**
     * {@inheritDoc}
     */
    public function create($path, $fileName, array $data = [])
    {
        $this->checkExtension();

        $file = $this->getFile($path, $fileName);

        if (!file_exists($path)) {
            mkdir($path, 0775, true);
        }
        if (!file_exists($file)) {
            $fichier = fopen($file, 'w+');
            fwrite($fichier, json_encode($data));

            return fclose($fichier);
        }

        return false;
    }

    /**
     * {@inheritDoc}
     */
    public function read($path, $fileName)
    {
        $this->checkExtension();
        $file = $this->getFile($path, $fileName);

        $this->isExist($file);
        $this->isRead($file);

        $json = file_get_contents($file);

        return json_decode($json, true);
    }

    /**
     * {@inheritDoc}
     */
    public function save($path, $fileName, array $data)
    {
        $this->checkExtension();

        $file = $this->getFile($path, $fileName);

        $this->isExist($file);
        $this->isWrite($file);

        $fp = fopen($file, 'w');
        fwrite($fp, json_encode($data));

        return fclose($fp);
    }

    /**
     * {@inheritDoc}
     */
    public function getExtension()
    {
        return 'json';
    }

    /**
     * Déclanche une exception si le l'extension du fichier n'est pas chargée.
     *
     * @codeCoverageIgnore has
     *
     * @throws Exception\Driver\ExtensionNotLoadedException
     */
    private function checkExtension()
    {
        if (!extension_loaded('json')) {
            throw new Exception\Driver\ExtensionNotLoadedException('The json extension is not loaded.');
        }
    }
}


<?php

/**
 * DframeFramework
 * Copyright (c) Sławomir Kaleta.
 *
 * @license https://github.com/dframe/dframe/blob/master/LICENCE (MIT)
 */

namespace Dframe\Router;

use Dframe\Router;

/**
 * Short Description.
 *
 * @author Sławomir Kaleta <slaszka@gmail.com>
 */
class Response extends Router
{
    /**
     * @var int
     */
    public $status = 200;

    /**
     * @var null|string
     */
    protected $body = '';

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

    /**
     * @var array
     */
    public static $code = [
        100 => 'Continue',
        101 => 'Switching Protocols',
        102 => 'Processing',
        200 => 'OK',
        201 => 'Created',
        202 => 'Accepted',
        203 => 'Non-Authoritative Information',
        204 => 'No Content',
        205 => 'Reset Content',
        206 => 'Partial Content',
        207 => 'Multi-Status',
        300 => 'Multiple Choices',
        301 => 'Moved Permanently',
        302 => 'Found',
        303 => 'See Other',
        304 => 'Not Modified',
        305 => 'Use Proxy',
        306 => 'Switch Proxy',
        307 => 'Temporary Redirect',
        400 => 'Bad Request',
        401 => 'Unauthorized',
        402 => 'Payment Required',
        403 => 'Forbidden',
        404 => 'Not Found',
        405 => 'Method Not Allowed',
        406 => 'Not Acceptable',
        407 => 'Proxy Authentication Required',
        408 => 'Request Timeout',
        409 => 'Conflict',
        410 => 'Gone',
        411 => 'Length Required',
        412 => 'Precondition Failed',
        413 => 'Request Entity Too Large',
        414 => 'Request-URI Too Long',
        415 => 'Unsupported Media Type',
        416 => 'Requested Range Not Satisfiable',
        417 => 'Expectation Failed',
        418 => 'I\'m a teapot',
        422 => 'Unprocessable Entity',
        423 => 'Locked',
        424 => 'Failed Dependency',
        425 => 'Unordered Collection',
        426 => 'Upgrade Required',
        449 => 'Retry With',
        450 => 'Blocked by Windows Parental Controls',
        500 => 'Internal Server Error',
        501 => 'Not Implemented',
        502 => 'Bad Gateway',
        503 => 'Service Unavailable',
        504 => 'Gateway Timeout',
        505 => 'HTTP Version Not Supported',
        506 => 'Variant Also Negotiates',
        507 => 'Insufficient Storage',
        509 => 'Bandwidth Limit Exceeded',
        510 => 'Not Extended',
    ];

    /**
     * Response constructor.
     *
     * @param null $body
     */
    public function __construct($body = null)
    {
        if (isset($body)) {
            $this->body = $body;
        }

        return $this;
    }

    /**
     * @param null $body
     *
     * @return Response
     */
    public static function create($body = null)
    {
        return new self($body);
    }

    /**
     * @param null $body
     *
     * @return Response
     */
    public static function render($body = null)
    {
        return new self($body);
    }

    /**
     * @param null $body
     * @param null $status
     *
     * @return Response
     */
    public static function renderJSON($body = null, $status = null)
    {
        $body = json_encode($body, JSON_NUMERIC_CHECK);
        $Response = new self($body);

        if (isset($status)) {
            $Response->status($status);
        }

        $Response->headers(['Content-Type' => 'application/json']);

        return $Response;
    }

    /**
     * @param null $body
     * @param null $status
     *
     * @return Response
     */
    public static function renderJSONP($body = null, $status = null)
    {
        $callback = null;
        if (isset($_GET['callback'])) {
            $callback = $_GET['callback'];
        }

        $Response = new self($callback . '(' . json_encode($body) . ')');

        if (isset($status)) {
            $Response->status($status);
        }

        $Response->headers(['Content-Type' => 'application/jsonp']);

        return $Response;
    }

    /**
     * Address redirection.
     *
     * @param string $url
     * @param int    $status
     * @param array  $headers
     *
     * @return Response|object
     */
    public static function redirect($url = '', $status = 301, $headers = [])
    {
        $Response = new Response();
        $Response->status($status);

        if (!empty($headers)) {
            $Response->headers($headers);
        }

        $Response->headers([
            'Location' => (new Router())->makeUrl($url),
        ]);

        return $Response;
    }

    /**
     * @param $json
     *
     * @return $this
     */
    public function json($json)
    {
        $this->headers(['Content-Type' => 'application/json']);
        $this->body = json_encode($json);

        return $this;
    }

    /**
     * @param $code
     *
     * @return $this
     */
    public function status($code)
    {
        $this->status = $code;

        return $this;
    }

    /**
     * @return int
     */
    public function getStatus()
    {
        return $this->status;
    }

    /**
     * @param bool $headers
     *
     * @return $this
     */
    public function headers($headers = false)
    {
        $this->headers = array_unique(array_merge($this->headers, $headers));

        return $this;
    }

    /**
     * @return array
     */
    public function getHeaders()
    {
        return $this->headers;
    }

    /**
     * @param null $body
     *
     * @return $this
     */
    public function body($body = null)
    {
        $this->body = $body;

        return $this;
    }

    /**
     * @return null|string
     */
    public function getBody()
    {
        return $this->body;
    }

    public function display()
    {
        if (!headers_sent()) {
            if (PHP_SAPI !== 'cli') {
                $protocol = (isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.1');
                $status = (!empty($this->status) ? $this->status : 200);
                $string = sprintf('%s %d %s', $protocol, $status, self::$code[$status]);

                header($string, true, $status); // Default header
                if (!empty($this->headers)) {
                    foreach ($this->headers as $field => $value) {
                        if (is_array($value)) {
                            foreach ($value as $v) {
                                header("$field" . ': ' . $v, false);
                            }
                        } else {
                            header("$field" . ': ' . $value);
                        }
                    }
                }
            }
        }

        if (!empty($this->getBody())) {
            echo $this->getBody();
        }
    }

    /**
     * @return null|string
     */
    public function __toString()
    {
        return $this->body;
    }
}


<?php

/**
 * DframeFramework
 * Copyright (c) Sławomir Kaleta.
 *
 * @license https://github.com/dframe/dframe/blob/master/LICENCE (MIT)
 */

namespace Dframe;

use Dframe\Router\Response;

/**
 * Message Class.
 *
 * @author Sławomir Kaleta <slaszka@gmail.com>
 * @author Mike Everhart <mike@plasticbrain.net>
 */
class Messages
{
    /**
     * @var string
     */
    public $msgId;

    /**
     * @var array
     */
    public $msgTypes = ['help', 'info', 'warning', 'success', 'error'];

    /**
     * @var \Psr\SimpleCache\CacheInterface
     */
    protected $driver;

    /**
     * @var \Dframe\Router
     */
    protected $router;

    /**
     * Add a message to the queue.
     *
     * @param $driver
     */
    public function __construct($driver)
    {
        $this->driver = $driver;
        if (!($this->driver instanceof \Psr\SimpleCache\CacheInterface) === true) {
            throw new \Exception("This class Require instance Of Psr\SimpleCache\CacheInterface", 1);
        }

        // Generate a unique ID for this user and session
        $this->msgId = md5(uniqid());

        $has = $this->driver->has('flash_messages');
        if ($has === false) {
            $this->driver->set('flash_messages', []);
        }
    }

    /**
     * Add a message to the queue.
     *
     * @param string $type     The type of message to add
     * @param string $message  The message
     * @param string $redirect (optional) If set, the user will be redirected to this URL
     *
     * @return mixed
     */
    public function add($type, $message, $redirect = null)
    {
        if (!isset($type) or !isset($message[0])) {
            return false;
        }
        // Replace any shorthand codes with their full version
        if (strlen(trim($type)) === 1) {
            $type = str_replace(['h', 'i', 'w', 'e', 's'], ['help', 'info', 'warning', 'error', 'success'], $type);
        }

        $router = new Router();

        try {
            if (!in_array($type, $this->msgTypes)) {  // Make sure it's a valid message type
                throw new BaseException('"' . strip_tags($type) . '" is not a valid message type!', 501);
            }
        } catch (BaseException $e) {
            $msg = null;
            if (ini_get('display_errors') === "on") {
                $msg .= '
';
                $msg .= 'Message: ' . $e->getMessage() . '

';

                $msg .= 'Accept: ' . $_SERVER['HTTP_ACCEPT'] . '
';
                if (isset($_SERVER['HTTP_REFERER'])) {
                    $msg .= 'Referer: ' . $_SERVER['HTTP_REFERER'] . '

';
                }

                $msg .= 'Request Method: ' . $_SERVER['REQUEST_METHOD'] . '

';

                $msg .= 'Current file Path: ' . $this->router->currentPath() . '
';

                $msg .= 'File Exception: ' . $e->getFile() . ':' . $e->getLine() . '

';
                $msg .= 'Trace:
' . $e->getTraceAsString() . '
';
                $msg .= '
';

                return Response::create($msg)->display();
            }

            return Response::create($e->getMessage())->status(501)->display();
        }

        $get = $this->driver->get('flash_messages');
        $get[$type][] = $message;
        $this->driver->set('flash_messages', $get);

        if (!is_null($redirect)) {
            return $router->redirect($redirect, 301);
        }

        return true;
    }

    /**
     * Display the queued messages.
     *
     * @param string $type  Which messages to display
     * @param bool   $print True print the messages on the screen
     *
     * @return mixed
     */
    public function display($type = 'all', $print = false)
    {
        $messages = '';
        $data = '';

        if (in_array($type, $this->msgTypes)) {
            // Print a certain type of message?

            $flashMessages = $this->driver->get('flash_messages');
            foreach ($flashMessages[$type] as $msg) {
                $messages .= $msg;
            }

            $data .= $messages;

            // Clear the viewed messages
            $this->clear($type);
        } elseif ($type === 'all') {

            // Print ALL queued messages
            $flashMessages = $this->driver->get('flash_messages');
            foreach ($flashMessages as $type => $msgArray) {
                $messages = '';
                foreach ($msgArray as $msg) {
                    $messages .= $msg;
                }
                $data .= $messages;
            }

            // Clear ALL of the messages
            $this->clear();
        } else {

            // Invalid Message Type?
            return false;
        }

        // Print everything to the screen or return the data
        if ($print) {
            echo $data;
            return null;
        }

        return $data;
    }

    /**
     * Clear messages from the session data.
     *
     * @param string $type The type of messages to clear
     *
     * @return bool
     */
    public function clear($type = 'all')
    {
        if ($type === 'all') {
            $this->driver->remove('flash_messages');
        } else {
            $flashMessages = $this->driver->get('flash_messages');
            unset($flashMessages[$type]);
            $this->driver->set('flash_messages', $flashMessages);
        }

        return true;
    }

    /**
     * Check to  see if there are any queued error messages.
     *
     * @return bool true There ARE error messages false There are NOT any error messages
     */
    public function hasErrors()
    {
        $flashMessages = $this->driver->get('flash_messages');
        return empty($flashMessages['error']) ? false : true;
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return $this->hasMessages();
    }

    /**
     * Check to see if there are any ($type) messages queued.
     *
     * @param string $type The type of messages to check for
     *
     * @return bool
     */
    public function hasMessages($type = null)
    {
        if (!is_null($type)) {
            $flashMessages = $this->driver->get('flash_messages');
            if (!empty($flashMessages[$type])) {
                return $flashMessages[$type];
            }
        } else {
            $flashMessages = $this->driver->get('flash_messages');
            foreach ($this->msgTypes as $type) {
                if (!empty($flashMessages[$type])) {
                    return true;
                }
            }
        }

        return false;
    }
}


<?php

/**
 * DframeFramework
 * Copyright (c) Sławomir Kaleta.
 *
 * @license https://github.com/dframe/dframe/blob/master/LICENCE (MIT)
 */

namespace Dframe;

use Psr\SimpleCache\CacheInterface;

/**
 * Session Class.
 *
 * @author Sławomir Kaleta <slaszka@gmail.com>
 */
class Session implements CacheInterface
{
    /**
     * @var string
     */
    protected $name;

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

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

    /**
     * Session constructor.
     *
     * @param array $app
     */
    public function __construct($app = [])
    {
        $options = $this->app->config['session'] ?? [];
        $this->name = APP_NAME ?? '_sessionName';

        if (!isset($_SESSION)) {
            $cookie = [
                'lifetime' => $options['cookie']['lifetime'] ?? 0,
                'path' => $options['cookie']['path'] ?? '/',
                'domain' => $options['cookie']['domain'] ?? null,
                'secure' => $options['cookie']['secure'] ?? ($_SERVER['HTTPS'] ?? null),
                'httpOnly' => $options['cookie']['httpOnly'] ?? false,
            ];

            session_set_cookie_params(
                $cookie['lifetime'],
                $cookie['path'],
                $cookie['domain'],
                $cookie['secure'],
                $cookie['httpOnly']
            );
            session_name($this->name);
            session_start();
        }

        if (php_sapi_name() != 'cli') {
            $this->ipAddress = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'];
            $this->userAgent = $_SERVER["HTTP_USER_AGENT"] ?? 'unknown';

            if ($this->isValidFingerprint() != true) {
                // Refresh Session
                $_SESSION = [];
                $_SESSION['_fingerprint'] = $this->getFingerprint();
            }
        }
    }

    /**
     * @return bool
     */
    public function isValidFingerprint()
    {
        $_fingerprint = $this->getFingerprint();
        if (isset($_SESSION['_fingerprint']) and $_SESSION['_fingerprint'] === $_fingerprint) {
            return true;
        }

        return false;
    }

    /**
     * @return string
     */
    private function getFingerprint()
    {
        return md5($this->ipAddress . $this->userAgent . $this->name);
    }

    /**
     * Register the session.
     *
     * @param int $time .
     */
    public function register($time = 60)
    {
        $_SESSION['sessionId'] = session_id();
        $_SESSION['sessionTime'] = (int) $time;
    }

    /**
     * Checks if the session is registered.
     *
     * @return bool
     */
    public function authLogin()
    {
        if (!empty($_SESSION['sessionId'])) {
            return true;
        }

        return false;
    }

    /**
     * @param       $key
     * @param array $in
     *
     * @return bool
     */
    public function keyExists($key, $in = [])
    {
        if (empty($in)) {
            $in = $_SESSION;
        }

        if (array_key_exists($key, $in) === true) {
            return true;
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function set($key, $value, $tll = null)
    {
        $_SESSION[$key] = $value;
    }

    /**
     * {@inheritdoc}
     */
    public function get($key, $or = null)
    {
        return isset($_SESSION[$key]) ? $_SESSION[$key] : $or;
    }

    /**
     * @param $key
     */
    public function remove($key)
    {
        $this->delete($key);
    }

    /**
     * {@inheritdoc}
     */
    public function delete($key)
    {
        if (isset($_SESSION[$key])) {
            unset($_SESSION[$key]);
        }
    }

    /**
     *
     */
    public function end()
    {
        $this->clear();
    }

    /**
     * {@inheritdoc}
     */
    public function clear()
    {
        session_destroy();
        $_SESSION = [];
    }

    /**
     * {@inheritdoc}
     */
    public function setMultiple($values, $ttl = null)
    {
        foreach ($values as $value) {
            $this->set($value['key'], $value['value'], $ttl);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getMultiple($keys, $default = null)
    {
        $cache = [];
        foreach ($keys as $key) {
            $cache[$key] = $this->get($key, $default);
        }

        return $cache;
    }

    /**
     * {@inheritdoc}
     */
    public function deleteMultiple($keys)
    {
        foreach ($keys as $key) {
            $this->delete($key);
        }

        return true;
    }

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


<?php

/**
 * DframeFramework
 * Copyright (c) Sławomir Kaleta.
 *
 * @license https://github.com/dframe/dframe/blob/master/LICENCE (MIT)
 */

namespace Dframe\Asset;

use Assetic\Asset\AssetCollection;
use Assetic\Asset\FileAsset;
use Assetic\Filter\CssImportFilter;
use Assetic\Filter\CssMinFilter;
use Assetic\Filter\CssRewriteFilter;
use Assetic\Filter\PhpCssEmbedFilter;
use Dframe\Asset\Exceptions\AsseticException;
use Dframe\Router;
use Patchwork\JSqueeze;

set_time_limit(120);

/**
 * Short Description.
 *
 * @author Sławomir Kaleta <slaszka@gmail.com>
 */
class Assetic extends Router
{
    /**
     * @param $path
     *
     * @throws AsseticException
     */
    private function checkDir($path)
    {
        if (!is_dir($path)) {
            if (!mkdir($path, 0777, true)) {
                throw new AsseticException('Unable to create' . $path, 403);
            }
        }
    }

    /**
     * @param null $url
     * @param null $path
     * @param bool $compress
     *
     * @return null|string
     * @throws AsseticException
     */
    public function assetJs($url = null, $path = null, $compress = true)
    {

        // Basic paths
        $srcPath = $this->routeMap['assets']['assetsPath'] . $this->routeMap['assets']['assetsDir'] . '/' . $url;
        if (is_null($path)) {
            $path = 'assets';
            if (isset($this->routeMap['assets']['assetsDir']) and !empty($this->routeMap['assets']['assetsDir'])) {
                $path = $this->routeMap['assets']['assetsDir'];
                $this->checkDir($path);
            }
            $dstPath = $this->routeMap['assets']['cachePath'] . $path . '/' . $url;
        } else {
            $dstPath = $this->routeMap['assets']['cachePath'] . $path;
        }

        // Copying a file if it does not exist
        if (!file_exists($dstPath)) {
            if (!file_exists($srcPath)) {
                return $srcPath;
            }

            $pathInfo = pathinfo($dstPath);
            if (!file_exists($pathInfo['dirname'])) {
                if (!mkdir($pathInfo['dirname'], 0777, true)) {
                    throw new AsseticException('Unable to create' . $path, 403);
                }
            }

            $js = file_get_contents($srcPath);
            if ($compress === true and $this->routeMap['assets']['minifyJsEnabled'] == true) {
                $jSqueeze = new JSqueeze();
                $js = $jSqueeze->squeeze($js, true, true, false);
            }

            if (!file_put_contents($dstPath, $js)) {
                $msg = date('Y-m-d h:m:s') . ' :: Unable to copy an asset From: ' . $srcPath . ' TO ' . $dstPath . "\n";
                $out = fopen(APP_DIR . '/View/logs/router.txt', 'w');
                fwrite($out, $msg);
                fclose($out);
            }
        }

        // Return the link to the copy
        $expressionUrl = $url;
        $url = $this->requestPrefix . $this->routeMap['assets']['cacheUrl'] . $path . '/';
        $url .= $expressionUrl;

        return $url;
    }

    /**
     * @param null $url
     * @param null $path
     * @param bool $compress
     *
     * @return null|string
     * @throws AsseticException
     */
    public function assetCss($url = null, $path = null, $compress = true)
    {

        // Basic paths
        $srcPath = $this->routeMap['assets']['assetsPath'] . $this->routeMap['assets']['assetsDir'] . '/' . $url;
        if (is_null($path)) {
            $path = 'assets';
            if (isset($this->routeMap['assets']['assetsDir']) and !empty($this->routeMap['assets']['assetsDir'])) {
                $path = $this->routeMap['assets']['assetsDir'];
                $this->checkDir($path);
            }
            $dstPath = $this->routeMap['assets']['cachePath'] . $path . '/' . $url;
        } else {
            $dstPath = $this->routeMap['assets']['cachePath'] . $path;
        }

        // Copying a file if it does not exist
        if (!file_exists($dstPath)) {
            if (!file_exists($srcPath)) {
                return '';
            }

            $pathInfo = pathinfo($dstPath);
            if (!file_exists($pathInfo['dirname'])) {
                if (!mkdir($pathInfo['dirname'], 0777, true)) {
                    throw new AsseticException('Unable to create' . $path, 403);
                }
            }

            $args = [];
            //$args[] = new Yui\CssCompressorFilter('C:\yuicompressor-2.4.7\build\yuicompressor-2.4.7.jar', 'java'),

            if ($compress === true) {
                if ($this->routeMap['assets']['minifyCssEnabled'] == true) {
                    $args[] = new CssMinFilter();
                }

                $args[] = new PhpCssEmbedFilter();
                $args[] = new CssRewriteFilter();
                $args[] = new CssImportFilter();
            }

            $css = new AssetCollection(
                [
                    new FileAsset($srcPath),
                ],
                $args
            );

            preg_match_all('/url\("([^\)]+?\.(woff2|woff|eot|ttf|svg|png|jpg|jpeg|gif))/', $css->dump(), $m);

            $srcPathInfo = pathinfo($srcPath);

            foreach ($m['1'] as $key => $url) {
                $subPathInfo = pathinfo($pathInfo['dirname'] . '/' . $url);
                if (!file_exists($subPathInfo['dirname'])) {
                    if (!mkdir($subPathInfo['dirname'], 0777, true)) {
                        throw new AsseticException('Unable to create' . $path, 403);
                    }
                }

                if (!copy($srcPathInfo['dirname'] . '/' . $url, $pathInfo['dirname'] . '/' . $url)) {
                    $msg = date('Y-m-d h:m:s') . ' :: Unable to copy an asset From: ' . $srcPathInfo['dirname'] . '/' . $url . ' TO ' . $pathInfo['dirname'] . '/' . $url . "\n";
                    $out = fopen(APP_DIR . 'View/logs/router.txt', 'w');
                    fwrite($out, $msg);
                    fclose($out);
                }
            }

            if (!file_put_contents($dstPath, $css->dump())) {
                $msg = date('Y-m-d h:m:s') . ' :: Unable to copy an asset From: ' . $srcPath . ' TO ' . $dstPath . "\n";
                $out = fopen(APP_DIR . '/View/logs/router.txt', 'w');
                fwrite($out, $msg);
                fclose($out);
            }
        }

        // Return the link to the copy
        $expressionUrl = $url;
        $url = $this->requestPrefix . $this->routeMap['assets']['cacheUrl'] . $path . '/';
        $url .= $expressionUrl;

        return $url;
    }
}


<?php

/**
 * DframeFramework
 * Copyright (c) Sławomir Kaleta.
 *
 * @license https://github.com/dframe/dframe/blob/master/LICENCE (MIT)
 */

namespace Dframe\View;

use Dframe\Config;
use Dframe\View\Exceptions\ViewException;

/**
 * Twig View.
 *
 * @author Sławomir Kaleta <slaszka@gmail.com>
 */
class TwigView implements ViewInterface
{
    /**
     * @var \Twig_Environment
     */
    public $twig;

    /**
     * @var array
     */
    public $assign;

    /**
     * TwigView constructor.
     */
    public function __construct()
    {
        $twigConfig = Config::load('view/twig');
        $loader = new \Twig_Loader_Filesystem($twigConfig->get('setTemplateDir'));
        $twig = new \Twig_Environment(
            $loader,
            [
                'cache' => $twigConfig->get('setCompileDir'),
            ]
        );
        $this->twig = $twig;
    }

    /**
     * Set the var to the template.
     *
     * @param string $name
     * @param string $value
     *
     * @return mixed
     */
    public function assign($name, $value)
    {
        try {
            if (isset($this->assign[$name])) {
                throw new ViewException('You can\'t assign "' . $name . '" in Twig');
            }

            $assign = $this->assign[$name] = $value;
        } catch (ViewException $e) {
            echo $e->getMessage() . '

                File: ' . $e->getFile() . '

                Code line: ' . $e->getLine() . '

                Trace: ' . $e->getTraceAsString();
            exit();
        }

        return $assign;
    }

    /**
     * Return code.
     *
     * @param string $name Filename
     * @param string $path Alternative Path
     *
     * @return void
     */
    public function fetch($name, $path = null)
    {
        //return throw new \Exception('This module don't have fetch');
    }

    /**
     * Transfers the code to the Smarty template.
     *
     * @param string $name
     * @param string $path
     *
     * @return mixed
     */
    public function renderInclude($name, $path = null)
    {
        $twigConfig = Config::load('twig');
        $pathFile = pathFile($name);
        $folder = $pathFile[0];
        $name = $pathFile[1];

        $path = $twigConfig->get('setTemplateDir') . DIRECTORY_SEPARATOR . $folder . $name . $twigConfig->get('fileExtension', '.twig');

        try {
            if (!is_file($path)) {
                throw new ViewException('Can not open template ' . $name . ' in: ' . $path);
            }

            $renderInclude = $this->twig->render($name, $this->assign);
        } catch (ViewException $e) {
            echo $e->getMessage() . '

                        File: ' . $e->getFile() . '

                        Code line: ' . $e->getLine() . '

                        Trace: ' . $e->getTraceAsString();
            exit();
        }

        return $renderInclude;
    }
}