<?php namespace Miladr\Jalali;
/**
 * Jalali DateTime Class, supports years higher than 2038
 * by: Sallar Kaboli
 *
 * Requires PHP >= 5.2
 *
 * PHP's default 'date' function does not support years higher than
 * 2038. Intorduced in PHP5, DateTime class supports higher years
 * and also invalid date entries.
 * Also, Persian users are using classic 'jdate' function for years now
 * and beside the fact that it's amazing and also helped me to write this
 * one, it's really out of date, and can't be used in modern real world
 * web applications as it is completely written in functions.
 *
 * Copyright (C) 2012  Sallar Kaboli (http://sallar.me)
 * Part of Phoenix Framework (p5x.org) by Phoenix Alternatvie
 *
 * Original Jalali to Gregorian (and vice versa) convertor:
 * Copyright (C) 2000  Roozbeh Pournader and Mohammad Toossi
 *
 * List of supported timezones can be found here:
 * http://www.php.net/manual/en/timezones.php
 *
 * PHP version 5
 *
 * LICENSE: This source file is subject to version 3.01 of the PHP license
 * that is available through the world-wide-web at the following URI:
 * http://www.php.net/license/3_01.txt.  If you did not receive a copy of
 * the PHP License and are unable to obtain it through the web, please
 * send a note to license@php.net so we can mail you a copy immediately.
 *
 * @package    jDateTime
 * @author     Sallar Kaboli <sallar.kaboli@gmail.com>
 * @author     Omid Pilevar <omid.pixel@gmail.com>
 * @copyright  2003-2012 Sallar Kaboli
 * @license    http://www.php.net/license/3_01.txt  PHP License 3.01
 * @link       http://sallar.me/projects/jdatetime/
 * @see        DateTime
 * @version    2.0.0
 */
class jDateTime
{

    /**
     * Defaults
     */
    private static $jalali = true; //Use Jalali Date, If set to false, falls back to gregorian
    private static $convert = true; //Convert numbers to Farsi characters in utf-8
    private static $timezone = null; //Timezone String e.g Asia/Tehran, Defaults to Server Timezone Settings
    private static $temp = array();

    /**
     * jDateTime::Constructor
     *
     * Pass these parameteres when creating a new instance
     * of this Class, and they will be used as defaults.
     * e.g $obj = new jDateTime(false, true, 'Asia/Tehran');
     * To use system defaults pass null for each one or just
     * create the object without any parameters.
     *
     * @author Sallar Kaboli
     * @param $convert bool Converts numbers to Farsi
     * @param $jalali bool Converts date to Jalali
     * @param $timezone string Timezone string
     */
    public function __construct($convert = null, $jalali = null, $timezone = null)
    {
        if ( $jalali   !== null ) self::$jalali = ($jalali === false) ? false : true;
        if ( $convert  !== null ) self::$convert = ($convert === false) ? false : true;
        if ( $timezone !== null ) self::$timezone = ($timezone != null) ? $timezone : null;
    }

    /**
     * jDateTime::Date
     *
     * Formats and returns given timestamp just like php's
     * built in date() function.
     * e.g:
     * $obj->date("Y-m-d H:i", time());
     * $obj->date("Y-m-d", time(), false, false, 'America/New_York');
     *
     * @author Sallar Kaboli
     * @param $format string Acceps format string based on: php.net/date
     * @param $stamp int Unix Timestamp (Epoch Time)
     * @param $convert bool (Optional) forces convert action. pass null to use system default
     * @param $jalali bool (Optional) forces jalali conversion. pass null to use system default
     * @param $timezone string (Optional) forces a different timezone. pass null to use system default
     * @return string Formatted input
     */
    public static function date($format, $stamp = false, $convert = null, $jalali = null, $timezone = null)
    {
        //Timestamp + Timezone
        $stamp    = ($stamp != false) ? $stamp : time();
        $timezone = ($timezone != null) ? $timezone : ((self::$timezone != null) ? self::$timezone : date_default_timezone_get());
        $obj      = new \DateTime('@' . $stamp);
        $obj->setTimezone(new \DateTimeZone($timezone));

        if ( (self::$jalali === false && $jalali === null) || $jalali === false ) {
            return $obj->format($format);
        }
        else {

            //Find what to replace
            $chars = (preg_match_all('/([a-zA-Z]{1})/', $format, $chars)) ? $chars[0] : array();

            //Intact Keys
            $intact = array('B','h','H','g','G','i','s','I','U','u','Z','O','P');
            $intact = self::filterArray($chars, $intact);
            $intactValues = array();

            foreach ($intact as $k => $v) {
                $intactValues[$k] = $obj->format($v);
            }
            //End Intact Keys

            //Changed Keys
            list($year, $month, $day) = array($obj->format('Y'), $obj->format('n'), $obj->format('j'));
            list($jyear, $jmonth, $jday) = self::toJalali($year, $month, $day);

            $keys = array('d','D','j','l','N','S','w','z','W','F','m','M','n','t','L','o','Y','y','a','A','c','r','e','T');
            $keys = self::filterArray($chars, $keys, array('z'));
            $values = array();

            foreach ($keys as $k => $key) {

                $v = '';
                switch ($key) {
                    //Day
                    case 'd':
                        $v = sprintf("%02d", $jday);
                        break;
                    case 'D':
                        $v = self::getDayNames($obj->format('D'), true);
                        break;
                    case 'j':
                        $v = $jday;
                        break;
                    case 'l':
                        $v = self::getDayNames($obj->format('l'));
                        break;
                    case 'N':
                        $v = self::getDayNames($obj->format('l'), false, 1, true);
                        break;
                    case 'S':
                        $v = 'ام';
                        break;
                    case 'w':
                        $v = self::getDayNames($obj->format('l'), false, 1, true) - 1;
                        break;
                    case 'z':
                        if ($jmonth > 6) {
                            $v = 186 + (($jmonth - 6 - 1) * 30) + $jday;
                        }
                        else {
                            $v = (($jmonth - 1) * 31) + $jday;
                        }
                        self::$temp['z'] = $v;
                        break;
                    //Week
                    case 'W':
                        $v = is_int(self::$temp['z'] / 7) ? (self::$temp['z'] / 7) : intval(self::$temp['z'] / 7 + 1);
                        break;
                    //Month
                    case 'F':
                        $v = self::getMonthNames($jmonth);
                        break;
                    case 'm':
                        $v = sprintf("%02d", $jmonth);
                        break;
                    case 'M':
                        $v = self::getMonthNames($jmonth, true);
                        break;
                    case 'n':
                        $v = $jmonth;
                        break;
                    case 't':
                        $v = ( $jmonth == 12 ) ? 29 : ( ($jmonth > 6 && $jmonth != 12) ? 30 : 31 );
                        break;
                    //Year
                    case 'L':
                        $tmpObj = new \DateTime('@'.(time()-31536000));
                        $v = $tmpObj->format('L');
                        break;
                    case 'o':
                    case 'Y':
                        $v = $jyear;
                        break;
                    case 'y':
                        $v = $jyear % 100;
                        break;
                    //Time
                    case 'a':
                        $v = ($obj->format('a') == 'am') ? 'ق.ظ' : 'ب.ظ';
                        break;
                    case 'A':
                        $v = ($obj->format('A') == 'AM') ? 'قبل از ظهر' : 'بعد از ظهر';
                        break;
                    //Full Dates
                    case 'c':
                        $v  = $jyear.'-'.sprintf("%02d", $jmonth).'-'.sprintf("%02d", $jday).'T';
                        $v .= $obj->format('H').':'.$obj->format('i').':'.$obj->format('s').$obj->format('P');
                        break;
                    case 'r':
                        $v  = self::getDayNames($obj->format('D'), true).', '.sprintf("%02d", $jday).' '.self::getMonthNames($jmonth, true);
                        $v .= ' '.$jyear.' '.$obj->format('H').':'.$obj->format('i').':'.$obj->format('s').' '.$obj->format('P');
                        break;
                    //Timezone
                    case 'e':
                        $v = $obj->format('e');
                        break;
                    case 'T':
                        $v = $obj->format('T');
                        break;

                }
                $values[$k] = $v;

            }
            //End Changed Keys

            //Merge
            $keys = array_merge($intact, $keys);
            $values = array_merge($intactValues, $values);

            //Return
            $ret = strtr($format, array_combine($keys, $values));
            return
            ($convert === false ||
                ($convert === null && self::$convert === false) ||
                ( $jalali === false || $jalali === null && self::$jalali === false ))
                ? $ret : self::convertNumbers($ret);

        }

    }

    /**
     * jDateTime::gDate
     *
     * Same as jDateTime::Date method
     * but this one works as a helper and returns Gregorian Date
     * in case someone doesn't like to pass all those false arguments
     * to Date method.
     *
     * e.g. $obj->gDate("Y-m-d") //Outputs: 2011-05-05
     *      $obj->date("Y-m-d", false, false, false); //Outputs: 2011-05-05
     *      Both return the exact same result.
     *
     * @author Sallar Kaboli
     * @param $format string Acceps format string based on: php.net/date
     * @param $stamp int Unix Timestamp (Epoch Time)
     * @param $timezone string (Optional) forces a different timezone. pass null to use system default
     * @return string Formatted input
     */
    public static function gDate($format, $stamp = false, $timezone = null)
    {
        return self::date($format, $stamp, false, false, $timezone);
    }

    /**
     * jDateTime::Strftime
     *
     * Format a local time/date according to locale settings
     * built in strftime() function.
     * e.g:
     * $obj->strftime("%x %H", time());
     * $obj->strftime("%H", time(), false, false, 'America/New_York');
     *
     * @author Omid Pilevar
     * @param $format string Acceps format string based on: php.net/date
     * @param $stamp int Unix Timestamp (Epoch Time)
     * @param $jalali bool (Optional) forces jalali conversion. pass null to use system default
     * @param $timezone string (Optional) forces a different timezone. pass null to use system default
     * @return string Formatted input
     */
    public static function strftime($format, $stamp = false, $jalali = null, $timezone = null)
    {
        $str_format_code = array(
            "%a", "%A", "%d", "%e", "%j", "%u", "%w",
            "%U", "%V", "%W",
            "%b", "%B", "%h", "%m",
            "%C", "%g", "%G", "%y", "%Y",
            "%H", "%I", "%l", "%M", "%p", "%P", "%r", "%R", "%S", "%T", "%X", "%z", "%Z",
            "%c", "%D", "%F", "%s", "%x",
            "%n", "%t", "%%"
        );

        $date_format_code = array(
            "D", "l", "d", "j", "z", "N", "w",
            "W", "W", "W",
            "M", "F", "M", "m",
            "y", "y", "y", "y", "Y",
            "H", "h", "g", "i", "A", "a", "h:i:s A", "H:i", "s", "H:i:s", "h:i:s", "H", "H",
            "D j M H:i:s", "d/m/y", "Y-m-d", "U", "d/m/y",
            "\n", "\t", "%"
        );

        //Change Strftime format to Date format
        $format = str_replace($str_format_code, $date_format_code, $format);

        //Convert to date
        return self::date($format, $stamp, $jalali, $timezone);
    }

   /**
     * jDateTime::Mktime
     *
     * Creates a Unix Timestamp (Epoch Time) based on given parameters
     * works like php's built in mktime() function.
     * e.g:
     * $time = $obj->mktime(0,0,0,2,10,1368);
     * $obj->date("Y-m-d", $time); //Format and Display
     * $obj->date("Y-m-d", $time, false, false); //Display in Gregorian !
     *
     * You can force gregorian mktime if system default is jalali and you
     * need to create a timestamp based on gregorian date
     * $time2 = $obj->mktime(0,0,0,12,23,1989, false);
     *
     * @author Sallar Kaboli
     * @param $hour int Hour based on 24 hour system
     * @param $minute int Minutes
     * @param $second int Seconds
     * @param $month int Month Number
     * @param $day int Day Number
     * @param $year int Four-digit Year number eg. 1390
     * @param $jalali bool (Optional) pass false if you want to input gregorian time
     * @param $timezone string (Optional) acceps an optional timezone if you want one
     * @return int Unix Timestamp (Epoch Time)
     */
    public static function mktime($hour, $minute, $second, $month, $day, $year, $jalali = null, $timezone = null)
    {
        //Defaults
        $month = (intval($month) == 0) ? self::date('m') : $month;
        $day   = (intval($day)   == 0) ? self::date('d') : $day;
        $year  = (intval($year)  == 0) ? self::date('Y') : $year;

        //Convert to Gregorian if necessary
        if ( $jalali === true || ($jalali === null && self::$jalali === true) ) {
            list($year, $month, $day) = self::toGregorian($year, $month, $day);
        }

        //Create a new object and set the timezone if available
        $date = $year.'-'.sprintf("%02d", $month).'-'.sprintf("%02d", $day).' '.$hour.':'.$minute.':'.$second;

        if ( self::$timezone != null || $timezone != null ) {
            $obj = new \DateTime($date, new \DateTimeZone(($timezone != null) ? $timezone : self::$timezone));
        }
        else {
            $obj = new \DateTime($date);
        }

        //Return
        return $obj->format("U");
    }

    /**
     * jDateTime::Checkdate
     *
     * Checks the validity of the date formed by the arguments.
     * A date is considered valid if each parameter is properly defined.
     * works like php's built in checkdate() function.
     * Leap years are taken into consideration.
     * e.g:
     * $obj->checkdate(10, 21, 1390); // Return true
     * $obj->checkdate(9, 31, 1390);  // Return false
     *
     * You can force gregorian checkdate if system default is jalali and you
     * need to check based on gregorian date
     * $check = $obj->checkdate(12, 31, 2011, false);
     *
     * @author Omid Pilevar
     * @param $month int The month is between 1 and 12 inclusive.
     * @param $day int The day is within the allowed number of days for the given month.
     * @param $year int The year is between 1 and 32767 inclusive.
     * @param $jalali bool (Optional) pass false if you want to input gregorian time
     * @return bool
     */
    public static function checkdate($month, $day, $year, $jalali = null)
    {
        //Defaults
        $month = (intval($month) == 0) ? self::date('n') : intval($month);
        $day   = (intval($day)   == 0) ? self::date('j') : intval($day);
        $year  = (intval($year)  == 0) ? self::date('Y') : intval($year);

        //Check if its jalali date
        if ( $jalali === true || ($jalali === null && self::$jalali === true) )
        {
            $epoch = self::mktime(0, 0, 0, $month, $day, $year);

            if( self::date("Y-n-j", $epoch,false) == "$year-$month-$day" ) {
                $ret = true;
            }
            else{
                $ret = false;
            }
        }
        else //Gregorian Date
        {
            $ret = checkdate($month, $day, $year);
        }

        //Return
        return $ret;
    }

    /**
     * System Helpers below
     *
     */
    private static function filterArray($needle, $heystack, $always = array())
    {
        foreach($heystack as $k => $v)
        {
            if( !in_array($v, $needle) && !in_array($v, $always) )
                unset($heystack[$k]);
        }

        return $heystack;
    }

    private static function getDayNames($day, $shorten = false, $len = 1, $numeric = false)
    {
        $ret = '';
        switch ( strtolower($day) ) {
            case 'sat': case 'saturday': $ret = 'شنبه'; $n = 1; break;
            case 'sun': case 'sunday': $ret = 'یکشنبه'; $n = 2; break;
            case 'mon': case 'monday': $ret = 'دوشنبه'; $n = 3; break;
            case 'tue': case 'tuesday': $ret = 'سه شنبه'; $n = 4; break;
            case 'wed': case 'wednesday': $ret = 'چهارشنبه'; $n = 5; break;
            case 'thu': case 'thursday': $ret = 'پنجشنبه'; $n = 6; break;
            case 'fri': case 'friday': $ret = 'جمعه'; $n = 7; break;
        }
        return ($numeric) ? $n : (($shorten) ? mb_substr($ret, 0, $len, 'UTF-8') : $ret);
    }

    private static function getMonthNames($month, $shorten = false, $len = 3)
    {
        $ret = '';
        switch ( $month ) {
            case '1': $ret = 'فروردین'; break;
            case '2': $ret = 'اردیبهشت'; break;
            case '3': $ret = 'خرداد'; break;
            case '4': $ret = 'تیر'; break;
            case '5': $ret = 'مرداد'; break;
            case '6': $ret = 'شهریور'; break;
            case '7': $ret = 'مهر'; break;
            case '8': $ret = 'آبان'; break;
            case '9': $ret = 'آذر'; break;
            case '10': $ret = 'دی'; break;
            case '11': $ret = 'بهمن'; break;
            case '12': $ret = 'اسفند'; break;
        }
        return ($shorten) ? mb_substr($ret, 0, $len, 'UTF-8') : $ret;
    }

    private static function convertNumbers($matches)
    {
        $farsi_array = array("۰", "۱", "۲", "۳", "۴", "۵", "۶", "۷", "۸", "۹");
        $english_array = array("0", "1", "2", "3", "4", "5", "6", "7", "8", "9");
        return str_replace($english_array, $farsi_array, $matches);
    }

    private static function div($a, $b)
    {
        return (int) ($a / $b);
    }
    /**
     * Converts Gregorian DateTime to Jalali and return it as string.
     * By Ahmad Pouramini
     */
	public static function toJalaliStr($g_date, $curSep = '-', $newSep = '/')
	{
		$arr = explode($curSep, $g_date);
		if (count($arr) < 3 || intval($arr[2]) == 0) //invalid dates
			return "";
		else
			$j_date = jDateTime::toJalali($arr[0],$arr[1],$arr[2]);
		$j_date_rev = array($j_date[2],$j_date[1],$j_date[0]);
		return implode($newSep,$j_date_rev);
	}
    /**
     * Converts Jalai DateTime to Gregorian and return it as string.
     * By Ahmad Pouramini
     */
	public static function toGregorianStr($j_date, $sep = '/')
	{
		$arr = explode($sep,$j_date);
		if (count($arr) < 3 || intval($arr[0]) == 0) // invalid date
			return "";
		else
		   $g_date = jDateTime::toGregorian($arr[2],$arr[1],$arr[0]);
		return implode($sep,$g_date);
	}

    /**
     * Gregorian to Jalali Conversion
     * Copyright (C) 2000  Roozbeh Pournader and Mohammad Toossi
     *
     */
    public static function toJalali($g_y, $g_m, $g_d)
    {

        $g_days_in_month = array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
        $j_days_in_month = array(31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29);

        $gy = $g_y-1600;
        $gm = $g_m-1;
        $gd = $g_d-1;

        $g_day_no = 365*$gy+self::div($gy+3, 4)-self::div($gy+99, 100)+self::div($gy+399, 400);

        for ($i=0; $i < $gm; ++$i)
            $g_day_no += $g_days_in_month[$i];
        if ($gm>1 && (($gy%4==0 && $gy%100!=0) || ($gy%400==0)))
            $g_day_no++;
        $g_day_no += $gd;

        $j_day_no = $g_day_no-79;

        $j_np = self::div($j_day_no, 12053);
        $j_day_no = $j_day_no % 12053;

        $jy = 979+33*$j_np+4*self::div($j_day_no, 1461);

        $j_day_no %= 1461;

        if ($j_day_no >= 366) {
            $jy += self::div($j_day_no-1, 365);
            $j_day_no = ($j_day_no-1)%365;
        }

        for ($i = 0; $i < 11 && $j_day_no >= $j_days_in_month[$i]; ++$i)
            $j_day_no -= $j_days_in_month[$i];
        $jm = $i+1;
        $jd = $j_day_no+1;

        return array($jy, $jm, $jd);

    }

    /**
     * Jalali to Gregorian Conversion
     * Copyright (C) 2000  Roozbeh Pournader and Mohammad Toossi
     *
     */
    public static function toGregorian($j_y, $j_m, $j_d)
    {

        $g_days_in_month = array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
        $j_days_in_month = array(31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29);

        $jy = $j_y-979;
        $jm = $j_m-1;
        $jd = $j_d-1;

        $j_day_no = 365*$jy + self::div($jy, 33)*8 + self::div($jy%33+3, 4);
        for ($i=0; $i < $jm; ++$i)
            $j_day_no += $j_days_in_month[$i];

        $j_day_no += $jd;

        $g_day_no = $j_day_no+79;

        $gy = 1600 + 400*self::div($g_day_no, 146097);
        $g_day_no = $g_day_no % 146097;

        $leap = true;
        if ($g_day_no >= 36525) {
            $g_day_no--;
            $gy += 100*self::div($g_day_no,  36524);
            $g_day_no = $g_day_no % 36524;

            if ($g_day_no >= 365)
                $g_day_no++;
            else
                $leap = false;
        }

        $gy += 4*self::div($g_day_no, 1461);
        $g_day_no %= 1461;

        if ($g_day_no >= 366) {
            $leap = false;

            $g_day_no--;
            $gy += self::div($g_day_no, 365);
            $g_day_no = $g_day_no % 365;
        }

        for ($i = 0; $g_day_no >= $g_days_in_month[$i] + ($i == 1 && $leap); $i++)
            $g_day_no -= $g_days_in_month[$i] + ($i == 1 && $leap);
        $gm = $i+1;
        $gd = $g_day_no+1;

        return array($gy, $gm, $gd);

    }

}


<?php namespace Miladr\Jalali;

use Illuminate\Support\ServiceProvider;

class JalaliServiceProvider extends ServiceProvider {

	 /**
   * Indicates if loading of the provider is deferred.
   *
   * @var bool
   */
  protected $defer = false;

  /**
   * Bootstrap the application events.
   *
   * @return void
   */
  public function boot()
  {
    $this->package('miladr/jalali');
  }

  /**
   * Register the service provider.
   *
   * @return void
   */
  public function register()
  {
      $this->app['jalali'] = $this->app->share(function($app)
  		{
  		  return new jDate;
	});
      $this->app->booting(function()
      {
          $loader = \Illuminate\Foundation\AliasLoader::getInstance();
          $loader->alias('jDate', 'Miladr\Jalali\jDate');
	});

      $this->app['jDateTime'] = $this->app->share(function($app)
  		{
  		  return new jDateTime;
	});
      $this->app->booting(function()
      {
          $loader = \Illuminate\Foundation\AliasLoader::getInstance();
          $loader->alias('jDateTime', 'Miladr\Jalali\jDateTime');
	});

  }

  /**
   * Get the services provided by the provider.
   *
   * @return array
   */
  public function provides()
  {
    return array('jalali','jDateTime');
  }

}


<?php
namespace DataTables\Controller\Component;

use Cake\Controller\Component;
use Cake\ORM\TableRegistry;

/**
 * DataTables component
 */
class DataTablesComponent extends Component
{

    protected $_defaultConfig = [
        'start' => 0,
        'length' => 10,
        'order' => [],
        'conditionsOr' => [],  // table-wide search conditions
        'conditionsAnd' => [], // column search conditions
        'matching' => [],      // column search conditions for foreign tables
    ];

    protected $_viewVars = [
        'recordsTotal' => 0,
        'recordsFiltered' => 0,
        'draw' => 0
    ];

    protected $_isAjaxRequest = false;

    protected $_tableName = null;

    protected $_plugin = null;

    /**
     * Process query data of ajax request
     *
     */
    private function _processRequest()
    {
        // -- check whether it is an ajax call from data tables server-side plugin or a normal request
        $this->_isAjaxRequest = $this->request->is('ajax');

        // -- add limit
        if( isset($this->request->query['length']) && !empty($this->request->query['length']) )
        {
            $this->config('length', $this->request->query['length']);
        }

        // -- add offset
        if( isset($this->request->query['start']) && !empty($this->request->query['start']) )
        {
            $this->config('start', (int)$this->request->query['start']);
        }

        // -- add order
        if( isset($this->request->query['order']) && !empty($this->request->query['order']) )
        {
            $order = $this->config('order');
            foreach($this->request->query['order'] as $item) {
                $order[$this->request->query['columns'][$item['column']]['name']] = $item['dir'];
            }
            $this->config('order', $order);
        }

        // -- add draw (an additional field of data tables plugin)
        if( isset($this->request->query['draw']) && !empty($this->request->query['draw']) )
        {
            $this->_viewVars['draw'] = (int)$this->request->query['draw'];
        }

        // -- don't support any search if columns data missing
        if( !isset($this->request->query['columns']) ||
            empty($this->request->query['columns']) )
        {
            return;
        }

        // -- check table search field
        $globalSearch = (isset($this->request->query['search']['value']) ?
            $this->request->query['search']['value'] : false);

        // -- add conditions for both table-wide and column search fields
        foreach($this->request->query['columns'] as $column)
        {
            if( $globalSearch && $column['searchable'] == 'true' ) {
                $this->_addCondition( $column['name'], $globalSearch, 'or' );
            }
            $localSearch = $column['search']['value'];
            /* In some circumstances (no "table-search" row present), DataTables
               fills in all column searches with the global search. Compromise:
               Ignore local field if it matches global search. */
            if( !empty($localSearch) && ($localSearch !== $globalSearch) ) {
                $this->_addCondition( $column['name'], $column['search']['value'] );
            }
        }

    }

    /**
     * Find data
     *
     * @param $tableName
     * @param $finder
     * @param array $options
     * @return array|\Cake\ORM\Query
     */
    public function find($tableName, $finder = 'all', array $options = [])
    {

        // -- get table object
        $table = TableRegistry::get($tableName);
        $this->_tableName = $table->alias();

        // -- get query options
        $this->_processRequest();
        $data = $table->find($finder, $options);

        // -- record count
        $this->_viewVars['recordsTotal'] = $data->count();

        // -- filter result
        $data->where($this->config('conditionsAnd'));
        foreach($this->config('matching') as $association => $where) {
            $data->matching( $association, function ($q) use ($where) {
                return $q->where($where);
            });
        };
        $data->andWhere(['or' => $this->config('conditionsOr')]);

        $this->_viewVars['recordsFiltered'] = $data->count();

        // -- add limit
        $data->limit( $this->config('length') );
        $data->offset( $this->config('start') );

        // -- sort
        $data->order( $this->config('order') );

        // -- set all view vars to view and serialize array
        $this->_setViewVars();
        return $data;

    }

    private function _getController()
    {
        return $this->_registry->getController();
    }

    private function _setViewVars()
    {
        $_serialize = [];
        foreach($this->_viewVars as $field => $value) {
            $_serialize[] = $field;
        }
        $this->_getController()->set($this->_viewVars);
        $this->_getController()->set('_serialize', $_serialize);
    }

    private function _addCondition($column, $value, $type = 'and')
    {
        $condition = ["$column LIKE" => "$value%"];

        if( $type === 'or' ) {
            $this->config('conditionsOr', $condition); // merges
            return;
        }

        list($association, $field) = explode('.', $column);
        if( $this->_tableName == $association) {
            $this->config('conditionsAnd', $condition); // merges
        } else {
            $this->config('matching', [$association => $condition]); // merges
        }
    }
}


<?php
/**
 * 等待扩展
 * User: 袁超<yccphp@163.com>
 * Time: 2015.05.18 下午2:11
 */
namespace YuanChao\Editor;

use Illuminate\Support\Facades\Request;

class EndaEditor{

    static $_errors=array();

    protected static function addError($message){
        if(!empty($message)){
            self::$_errors[] = $message;
        }
    }

    protected static function getLastError(){
        return empty(self::$_errors) ? '' : array_pop(self::$_errors);
    }

    /**
     * EndaEditor Upload ImgFile
     * @param string $path
     * @return array
     */
    public static function uploadImgFile($path){
        try{
            // File Upload
            if (Request::hasFile('image')){
                $pic = Request::file('image');
                if($pic->isValid()){
                    $newName = md5(rand(1,1000).$pic->getClientOriginalName()).".".$pic->getClientOriginalExtension();
                    $pic->move($path,$newName);
                    $url = asset($path.'/'.$newName);
                }else{
                    self::addError('The file is invalid');
                }
            }else{
                self::addError('Not File');
            }
        }catch (\Exception $e){
            self::addError($e->getMessage());
        }

        $data = array(
            'status'=>empty($message)?0:1,
            'message'=>self::getLastError(),
            'url'=>!empty($url)?$url:''
        );

        return $data;
    }

    /**
     * 转换 mark 文本
     * @param $markdownText
     * @return string
     */
    public static function MarkDecode($markdownText){
        $parsedown = new \YuanChao\Editor\Parsedown();
        return $parsedown->text($markdownText);
    }

}


<?php
namespace CIDRmatch;

/** CIDR match
 * ================================================================================
 * IDRmatch is a library to match an IP to an IP range in CIDR notation (IPv4 and
 * IPv6).
 *  ================================================================================
 * @package     CIDRmatch
 * @author      Thomas Lutz
 * @copyright   Copyright (c) 2015 - present Thomas Lutz
 * @license     http://tholu.mit-license.org
 *  ================================================================================
 */

class CIDRmatch
{

    public function match($ip, $cidr)
    {
        $c = explode('/', $cidr);
        $subnet = isset($c[0]) ? $c[0] : NULL;
        $mask   = isset($c[1]) ? $c[1] : NULL;
        if ($mask === null) {
            $mask = 32;
        }

        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
            // it's valid
            $ipVersion = 'v4';
        } else {
            // it's not valid
            if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
                // it's valid
                $ipVersion = 'v6';
            } else {
                // it's not valid
                return false;
            }
        }

        switch ($ipVersion) {
            case 'v4':
                return $this->IPv4Match($ip, $subnet, $mask);
                break;
            case 'v6':
                return $this->IPv6Match($ip, $subnet, $mask);
                break;
        }
    }

    // inspired by: http://stackoverflow.com/questions/7951061/matching-ipv6-address-to-a-cidr-subnet
    private function IPv6MaskToByteArray($subnetMask)
    {
        $addr = str_repeat("f", $subnetMask / 4);
        switch ($subnetMask % 4) {
            case 0:
                break;
            case 1:
                $addr .= "8";
                break;
            case 2:
                $addr .= "c";
                break;
            case 3:
                $addr .= "e";
                break;
        }
        $addr = str_pad($addr, 32, '0');
        $addr = pack("H*", $addr);

        return $addr;
    }

    // inspired by: http://stackoverflow.com/questions/7951061/matching-ipv6-address-to-a-cidr-subnet
    private function IPv6Match($address, $subnetAddress, $subnetMask)
    {
        if (!filter_var($subnetAddress, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) || $subnetMask === NULL || $subnetMask === "" || $subnetMask < 0 || $subnetMask > 128) {
            return false;
        }
        $subnet = inet_pton($subnetAddress);
        $addr = inet_pton($address);

        $binMask = $this->IPv6MaskToByteArray($subnetMask);

        return ($addr & $binMask) == $subnet;
    }

    // inspired by: http://stackoverflow.com/questions/594112/matching-an-ip-to-a-cidr-mask-in-php5
    private function IPv4Match($address, $subnetAddress, $subnetMask)
    {
        if (!filter_var($subnetAddress, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) || $subnetMask === NULL || $subnetMask === "" || $subnetMask < 0 || $subnetMask > 32) {
            return false;
        }

        $address = ip2long($address);
        $subnetAddress = ip2long($subnetAddress);
        $mask = -1 << (32 - $subnetMask);
        $subnetAddress &= $mask; # nb: in case the supplied subnet wasn't correctly aligned
        return ($address & $mask) == $subnetAddress;
    }

}


<?php

namespace Yugo\SMSGateway\Vendors\Smsgatewayme;

use Illuminate\Support\Facades\Log;
use Unirest\Request;
use Unirest\Request\Body;

class Callback
{
    /**
     * API URL.
     *
     * @var string
     */
    private $baseUrl = 'https://smsgateway.me/api/v4/';

    /**
     * Callback name.
     *
     * @var string
     */
    private $name;

    /**
     * Callback event name.
     * Available: received, sent, failed.
     *
     * @var string
     */
    private $event;

    /**
     * Hook URL.
     *
     * @var string
     */
    private $url;

    /**
     * Secret key for callback.
     *
     * @var string
     */
    private $secret;

    /**
     * Default device.
     *
     * @var int
     */
    private $device;

    /**
     * Authorization.
     *
     * @var string
     */
    private $token;

    public function __construct(int $device, string $token)
    {
        $this->device = $device;
        $this->token = $token;

        $this->secret = str_random(15);

        Request::defaultHeaders([
            'Accept'        => 'application/json',
            'Authorization' => $this->token,
        ]);
    }

    /**
     * Set default name.
     *
     * @param string $name
     *
     * @return self
     */
    public function name(string $name): self
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Set default event.
     *
     * @param string $event
     *
     * @return self
     */
    public function event(string $event): self
    {
        $events = [
            'MESSAGE_RECEIVED',
            'MESSAGE_SENT',
            'MESSAGE_FAILED',
        ];
        if (!in_array($event, $events)) {
            abort(500, sprintf('Event %s not available.', $event));
        }

        $this->event = $event;

        return $this;
    }

    /**
     * Set hook URL.
     *
     * @param string $url
     *
     * @return self
     */
    public function url(string $url): self
    {
        $this->url = $url;

        return $this;
    }

    /**
     * If not defined, secret key will generated automatically.
     *
     * @param string $secret
     *
     * @return self
     */
    public function secret(string $secret = ''): self
    {
        if (empty($secret)) {
            $secret = str_random(15);
        }

        $this->secret = $secret;

        return $this;
    }

    /**
     * Store new callback to SMSGateway.me server.
     *
     * @return array|null
     */
    public function create(): ?array
    {
        $body = Body::json([
            'name'        => $this->name,
            'event'       => $this->event,
            'device_id'   => $this->device,
            'filter_type' => '',
            'filter'      => '',
            'method'      => 'http',
            'action'      => $this->url,
            'secret'      => $this->secret,
        ]);
        $response = Request::post($this->baseUrl.'callback', [], $body);

        if ($response->code != 200) {
            if (!empty($response->body->message)) {
                Log::error($response->body->message);
            }
        }

        return [
            'code'    => $response->code,
            'message' => ($response->code == 200) ? 'OK' : $response->body->message ?? '',
            'data'    => $response->body,
        ];
    }

    /**
     * Get detailed information about callback.
     *
     * @param int $id
     *
     * @return array|null
     */
    public function info(int $id): ?array
    {
        $response = Request::get($this->baseUrl.'callback/'.$id);

        if ($response->code != 200) {
            if (!empty($response->body->message)) {
                Log::error($response->body->message);
            }
        }

        return [
            'code'    => $response->code,
            'message' => ($response->code == 200) ? 'OK' : $response->body->message ?? '',
            'data'    => $response->body,
        ];
    }

    public function update(int $id): ?array
    {
        $body = Body::json([
            'name'        => $this->name,
            'event'       => $this->event,
            'device_id'   => $this->device,
            'filter_type' => '',
            'filter'      => '',
            'method'      => 'http',
            'action'      => $this->url,
            'secret'      => $this->secret,
        ]);
        $response = Request::put($this->baseUrl.'callback/'.$id, [], $body);

        if ($response->code != 200) {
            if (!empty($response->body->message)) {
                Log::error($response->body->message);
            }
        }

        return [
            'code'    => $response->code,
            'message' => ($response->code == 200) ? 'OK' : $response->body->message ?? '',
            'data'    => $response->body,
        ];
    }
}


<?php namespace Cerbero\Workflow\Console\Commands;

use Cerbero\Workflow\Repositories\PipelineRepositoryInterface;
use Symfony\Component\Console\Input\InputArgument;
use Cerbero\Workflow\Console\Drawing\Drawer;
use Illuminate\Console\Command;

class ReadWorkflowCommand extends Command {

	/**
	 * The console command name.
	 *
	 * @var string
	 */
	protected $name = 'workflow:read';

	/**
	 * The console command description.
	 *
	 * @var string
	 */
	protected $description = 'Show an existing workflow';

	/**
	 * @author	Andrea Marco Sartori
	 * @var		Cerbero\Workflow\Repositories\PipelineRepositoryInterface	$pipelines	Pipeline repository.
	 */
	protected $pipelines;

	/**
	 * @author	Andrea Marco Sartori
	 * @var		Cerbero\Workflow\Console\Drawing\Drawer	$drawer	The workflow drawer.
	 */
	protected $drawer;

	/**
	 * Set the dependencies.
	 *
	 * @param  \Cerbero\Workflow\Repositories\PipelineRepositoryInterface  $pipelines
	 * @param  \Cerbero\Workflow\Console\Drawing\Drawer  $drawer
	 * @return void
	 */
	public function __construct(PipelineRepositoryInterface $pipelines, Drawer $drawer)
	{
		parent::__construct();

		$this->pipelines = $pipelines;

		$this->drawer = $drawer;
	}

	/**
	 * Execute the console command.
	 *
	 * @return mixed
	 */
	public function fire()
	{
		$workflow = ucfirst($this->argument('name'));

		if( ! $this->pipelines->exists($workflow))
		{
			return $this->error("The workflow [$workflow] does not exist.");
		}

		$this->info($this->drawer->draw($workflow));
	}

	/**
	 * Get the console command arguments.
	 *
	 * @return array
	 */
	protected function getArguments()
	{
		return [
			['name', InputArgument::REQUIRED, 'The name of the workflow.'],
		];
	}

}


<?php

namespace spec\Cerbero\Workflow;

use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Cerbero\Workflow\Repositories\PipelineRepositoryInterface;
use Cerbero\Workflow\Inflectors\InflectorInterface;
use Illuminate\Contracts\Container\Container;
use Cerbero\Workflow\Wrappers\DispatcherInterface;
use ArrayAccess;

class WorkflowSpec extends ObjectBehavior {

	public function let(
		PipelineRepositoryInterface $pipelines,
        InflectorInterface $inflector,
        Container $container,
		DispatcherInterface $dispatcher
	) {
		$this->beConstructedWith($pipelines, $inflector, $container, $dispatcher);
	}

    public function it_is_initializable()
    {
        $this->shouldHaveType('Cerbero\Workflow\Workflow');
    }

    /**
     * @testdox	It throws an exception if a workflow does not exist.
     *
     * @author	Andrea Marco Sartori
     * @return	void
     */
    public function it_throws_an_exception_if_a_workflow_does_not_exist($pipelines)
    {
    	$pipelines->exists('unknownWorkflow')->shouldBeCalled()->willReturn(false);

    	$this->shouldThrow('BadFunctionCallException')->duringUnknownWorkflow();
    }

    /**
     * @testdox	It resolves the proper request if existing.
     *
     * @author	Andrea Marco Sartori
     * @return	void
     */
    public function it_resolves_the_proper_request_if_existing($pipelines, $container, Router $router, $inflector, $dispatcher, ExistingRequest $request)
    {
    	$pipelines->exists('registerUser')->willReturn(true);

    	$inflector->of('registerUser')->shouldBeCalled();

    	$inflector->getRequest()->willReturn('spec\Cerbero\Workflow\ExistingRequest');

    	$container->make('spec\Cerbero\Workflow\ExistingRequest')->shouldBeCalled()->willReturn($request);

        $container->make('router')->willReturn($router);

        $router->current()->willReturn($router);

        $router->parameters()->willReturn(['foo' => 'bar']);

    	$inflector->getJob()->willReturn('job');

    	$pipelines->getPipesByPipeline('registerUser')->willReturn(['pipe']);

    	$dispatcher->pipeThrough(['pipe'])->willReturn($dispatcher);

    	$dispatcher->dispatchFrom('job', $request, ['foo' => 'bar'])->shouldBeCalled();

    	$this->registerUser();
    }

    /**
     * @testdox	It dispatches a job through a pipeline from a default request.
     *
     * @author	Andrea Marco Sartori
     * @return	void
     */
    public function it_dispatches_a_job_through_a_pipeline_from_a_default_request($pipelines, $container, Router $router, $inflector, $dispatcher, ArrayAccess $request)
    {
    	$pipelines->exists('registerUser')->willReturn(true);

    	$inflector->of('registerUser')->shouldBeCalled();

    	$inflector->getRequest()->willReturn('NotExistingRequest');

    	$container->make('Illuminate\Http\Request')->shouldBeCalled()->willReturn($request);

        $container->make('router')->willReturn($router);

        $router->current()->willReturn($router);

        $router->parameters()->willReturn(['foo' => 'bar']);

    	$inflector->getJob()->willReturn('job');

    	$pipelines->getPipesByPipeline('registerUser')->willReturn(['pipe']);

    	$dispatcher->pipeThrough(['pipe'])->willReturn($dispatcher);

    	$dispatcher->dispatchFrom('job', $request, ['foo' => 'bar'])->shouldBeCalled();

    	$this->registerUser('foo', 'bar');
    }

}

abstract class ExistingRequest implements ArrayAccess {}

interface Router {
    function current();
    function parameters();
}


<?php namespace Cerbero\Workflow\Repositories;

use Cerbero\Workflow\Wrappers\YamlParserInterface;
use Illuminate\Filesystem\Filesystem;

/**
 * Pipeline repository using YAML.
 *
 * @author	Andrea Marco Sartori
 */
class YamlPipelineRepository implements PipelineRepositoryInterface {

	/**
	 * @author	Andrea Marco Sartori
	 * @var		array	$pipelines	Pipelines list.
	 */
	protected $pipelines;

	/**
	 * @author	Andrea Marco Sartori
	 * @var		Cerbero\Workflow\Wrappers\YamlParserInterface	$parser	YAML parser.
	 */
	protected $parser;

	/**
	 * @author	Andrea Marco Sartori
	 * @var		Illuminate\Filesystem\Filesystem	$files	Filesystem.
	 */
	protected $files;

	/**
	 * @author	Andrea Marco Sartori
	 * @var		string	$path	The workflows path.
	 */
	protected $path;

	/**
	 * Set the dependencies.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	Cerbero\Workflow\Wrappers\YamlParserInterface	$parser
	 * @param	Illuminate\Filesystem\Filesystem	$files
	 * @param	string	$path
	 * @return	void
	 */
	public function __construct(YamlParserInterface $parser, Filesystem $files, $path)
	{
		$this->parser = $parser;

		$this->files = $files;

		$this->path = $path;

		$this->pipelines = $this->parseYaml();
	}

	/**
	 * Parse the YAML file.
	 *
	 * @author	Andrea Marco Sartori
	 * @return	array
	 */
	private function parseYaml()
	{
		$file = $this->getSource();

		return (array) $this->parser->parse($file);
	}

	/**
	 * Retrieve the source of the pipelines.
	 *
	 * @author	Andrea Marco Sartori
	 * @return	string
	 */
	public function getSource()
	{
		$path = rtrim($this->path, '/');

		return "{$path}/workflows.yml";
	}

	/**
	 * Determine whether a given pipeline exists.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	string	$pipeline
	 * @return	boolean
	 */
	public function exists($pipeline)
	{
		$this->normalizePipeline($pipeline);

		return array_key_exists($pipeline, $this->pipelines);
	}

	/**
	 * Normalize the name of the given pipeline.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	string	$pipeline
	 * @return	void
	 */
	protected function normalizePipeline(&$pipeline)
	{
		$pipeline = ucfirst($pipeline);
	}

	/**
	 * Retrieve the pipes of a given pipeline.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	string	$pipeline
	 * @return	array
	 */
	public function getPipesByPipeline($pipeline)
	{
		$this->normalizePipeline($pipeline);

		return $this->pipelines[$pipeline];
	}

	/**
	 * Create the pipelines storage.
	 *
	 * @author	Andrea Marco Sartori
	 * @return	void
	 */
	public function settle()
	{
		$this->files->makeDirectory($this->path, 0755, true, true);

		$this->files->put($this->getSource(), '');
	}

	/**
	 * Store the given pipeline and its pipes.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	string	$pipeline
	 * @param	array	$pipes
	 * @return	void
	 */
	public function store($pipeline, array $pipes)
	{
		$workflow = [$pipeline => $pipes];

		$yaml = $this->parser->dump($workflow);

		$this->files->append($this->getSource(), $yaml);
	}

	/**
	 * Update the given pipeline and its pipes.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	string	$pipeline
	 * @param	array	$attachments
	 * @param	array	$detachments
	 * @return	void
	 */
	public function update($pipeline, array $attachments, array $detachments)
	{
		$this->detach($this->pipelines[$pipeline], $detachments);

		$this->attach($this->pipelines[$pipeline], $attachments);

		$this->refreshPipelines();
	}

	/**
	 * Detach pipes from a given pipeline.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	array	$pipeline
	 * @param	array	$pipes
	 * @return	void
	 */
	protected function detach(array &$pipeline, array $pipes)
	{
		$pipeline = array_diff($pipeline, $pipes);
	}

	/**
	 * Attach pipes to a given pipeline.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	array	$pipeline
	 * @param	array	$pipes
	 * @return	void
	 */
	protected function attach(array &$pipeline, array $pipes)
	{
		$pipeline = array_merge($pipeline, $pipes);
	}

	/**
	 * Refresh the pipelines source.
	 *
	 * @author	Andrea Marco Sartori
	 * @return	void
	 */
	protected function refreshPipelines()
	{
		$yaml = $this->parser->dump($this->pipelines);

		$this->files->put($this->getSource(), $yaml);
	}

	/**
	 * Destroy a given pipeline.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	string	$pipeline
	 * @return	void
	 */
	public function destroy($pipeline)
	{
		unset($this->pipelines[$pipeline]);

		$this->refreshPipelines();
	}

}


<?php namespace Cerbero\Workflow\Console\Commands;

use Symfony\Component\Console\Input\InputOption;

class UpdateWorkflowCommand extends WorkflowGeneratorCommand {

	use AttachesPipesTrait, DeleteIfForcedTrait;

	/**
	 * The console command name.
	 *
	 * @var string
	 */
	protected $name = 'workflow:update';

	/**
	 * The console command description.
	 *
	 * @var string
	 */
	protected $description = 'Update an existing workflow';

	/**
	 * @author	Andrea Marco Sartori
	 * @var		string	$type	Type of class to generate.
	 */
	protected $type = 'Pipe';

	/**
	 * Execute the console command.
	 *
	 * @return mixed
	 */
	public function fire()
	{
		$name = $this->getWorkflowName();

		if( ! $this->pipelines->exists($name))
		{
			return $this->error("The workflow [$name] does not exist.");
		}

		$this->generatePipes();

		$this->deleteDetachedIfForced();

		$this->updateWorkflow($name);

		$this->info('Workflow updated successfully.');
	}

	/**
	 * Delete the detached pipes if force is set.
	 *
	 * @author	Andrea Marco Sartori
	 * @return	void
	 */
	protected function deleteDetachedIfForced()
	{
		$detachments = $this->getNamespacedPipesByOption('detach');

		$this->deleteIfForced($detachments);
	}

	/**
	 * Update the given workflow.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	string	$workflow
	 * @return	void
	 */
	protected function updateWorkflow($workflow)
	{
		$attachments = $this->getNamespacedPipesByOption('attach');

		$detachments = $this->getNamespacedPipesByOption('detach');

		$this->pipelines->update($workflow, $attachments, $detachments);
	}

	/**
	 * Get the console command options.
	 *
	 * @return array
	 */
	protected function getOptions()
	{
		return [
			['attach', '-a', InputOption::VALUE_OPTIONAL, 'The pipes to attach to the workflow.', null],
			['detach', '-d', InputOption::VALUE_OPTIONAL, 'The pipes to detach from the workflow.', null],
			['force', '-f', InputOption::VALUE_NONE, 'Delete the files of detached pipes.'],
		];
	}

}


<?php namespace Cerbero\Workflow\Console\Drawing;

/**
 * Geometry applied to the drawing.
 *
 * @author	Andrea Marco Sartori
 */
class Geometry {

	const BORDER_WIDTH = 1;

	const MIN_SPACE_FROM_BORDER_X = 1;

	const MIN_SPACE_FROM_BORDER_Y = 0;

	const ARROW_WIDTH = 1;

	const MIN_PIPE_LENGTH = 8;

	const SPACE_FROM_ARROW = 1;

	/**
	 * @author	Andrea Marco Sartori
	 * @var		string	$core	The name of the core.
	 */
	protected $core;

	/**
	 * @author	Andrea Marco Sartori
	 * @var		array	$pipes	List of pipes.
	 */
	protected $pipes;

	/**
	 * @author	Andrea Marco Sartori
	 * @var		integer	$nesting	Nesting level.
	 */
	protected $nesting = 0;

	/**
	 * Set the name of the core.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	string	$core
	 * @return	void
	 */
	public function setCore($core)
	{
		$this->core = $core;
	}

	/**
	 * Set the pipes.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	array	$pipes
	 * @return	void
	 */
	public function setPipes(array $pipes)
	{
		$this->pipes = $pipes;
	}

	/**
	 * Calculate the half width of the drawing.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	boolean	$up
	 * @return	integer
	 */
	public function getHalfWidth($up = false)
	{
		$number = $this->getTotalWidth();

		return $this->roundHalf($number, $up);
	}

	/**
	 * Round the half of a number, either up or down.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	integer	$number
	 * @param	boolean	$up
	 * @return	integer
	 */
	private function roundHalf($number, $up)
	{
		$round = $up ? 'ceil' : 'floor';

		return $round($number / 2);
	}

	/**
	 * Calculate the total width of the drawing.
	 *
	 * @author	Andrea Marco Sartori
	 * @return	integer
	 */
	protected function getTotalWidth()
	{
		$borders = (static::BORDER_WIDTH + static::MIN_SPACE_FROM_BORDER_X) * 2;

		if(empty($this->pipes))
		{
			return $borders + $this->getCoreLength();
		}

		$borders *= count($this->pipes);

		$name = ($this->getLongestPipeLength() + static::SPACE_FROM_ARROW) * 2;

		return $borders + $name + static::ARROW_WIDTH;
	}

	/**
	 * Calculate the length of the core name.
	 *
	 * @author	Andrea Marco Sartori
	 * @return	integer
	 */
	protected function getCoreLength()
	{
		return strlen($this->core);
	}

	/**
	 * Calculate the length of the longest pipe name.
	 *
	 * @author	Andrea Marco Sartori
	 * @return	integer
	 */
	protected function getLongestPipeLength()
	{
		if(empty($this->pipes)) return 0;

		return array_reduce($this->pipes, function($carry, $pipe)
		{
			return strlen($pipe) > $carry ? strlen($pipe) : $carry;

		}, static::MIN_PIPE_LENGTH);
	}

	/**
	 * Retrieve the spaced pipe and method pair.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	string	$pipe
	 * @param	string	$arrow
	 * @param	string	$method
	 * @return	string
	 */
	public function getSpacedPipe($pipe, $arrow, $method)
	{
		$left = $this->getSpacesByWord($pipe);

		$arrow = $this->addSpacesToArrow($arrow);

		$right = $this->getSpacesByWord($method);

		return $left.$pipe.$arrow.$method.$right;
	}

	/**
	 * Retrieve the blank spaces close to a word.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	string	$word
	 * @return	string
	 */
	protected function getSpacesByWord($word)
	{
		$length = $this->getSideBordersLength() + static::SPACE_FROM_ARROW + static::ARROW_WIDTH;

		$extra = $this->getHalfWidth(true) - $length - strlen($word);

		return $extra > 0 ? str_repeat(' ', $extra) : '';
	}

	/**
	 * Retrieve the length of the borders of a side.
	 *
	 * @author	Andrea Marco Sartori
	 * @return	integer
	 */
	protected function getSideBordersLength()
	{
		$border = (static::BORDER_WIDTH + static::MIN_SPACE_FROM_BORDER_X);

		return $border * $this->nesting;
	}

	/**
	 * Add spaces around the given arrow.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	string	$arrow
	 * @return	string
	 */
	protected function addSpacesToArrow($arrow)
	{
		$spaces = str_repeat(' ', static::SPACE_FROM_ARROW);

		return "{$spaces}{$arrow}{$spaces}";
	}

	/**
	 * Retrieve the left borders formatted with the given border.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	string	$border
	 * @return	string
	 */
	public function getLeftBordersWith($border)
	{
		$border = str_repeat($border, static::BORDER_WIDTH);

		$space = str_repeat(' ', static::MIN_SPACE_FROM_BORDER_X);

		return str_repeat("{$border}{$space}", $this->nesting);
	}

	/**
	 * Retrieve the right borders formatted with the given border.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	string	$border
	 * @return	string
	 */
	public function getRightBordersWith($border)
	{
		$space = str_repeat(' ', static::MIN_SPACE_FROM_BORDER_X);

		$border = str_repeat($border, static::BORDER_WIDTH);

		return str_repeat("{$space}{$border}", $this->nesting);
	}

	/**
	 * Increase the nesting level.
	 *
	 * @author	Andrea Marco Sartori
	 * @return	void
	 */
	public function increaseNesting()
	{
		$this->nesting++;
	}

	/**
	 * Calculate the width of the drawing without the borders.
	 *
	 * @author	Andrea Marco Sartori
	 * @return	integer
	 */
	public function getWidthButBorders()
	{
		return $this->getTotalWidth() - $this->getBordersLength();
	}

	/**
	 * Calculate the length of the borders.
	 *
	 * @author	Andrea Marco Sartori
	 * @return	integer
	 */
	protected function getBordersLength()
	{
		return $this->getSideBordersLength() * 2;
	}

	/**
	 * Retrieve the spaced core name.
	 *
	 * @author	Andrea Marco Sartori
	 * @return	string
	 */
	public function getSpacedCore()
	{
		$left = $this->getSpacesByCore();

		$right = $this->getSpacesByCore(true);

		return $left.$this->core.$right;
	}

	/**
	 * Retrieve the blank spaces close to the core.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	boolean	$up
	 * @return	string
	 */
	protected function getSpacesByCore($up = false)
	{
		$free = $this->getTotalWidth() - $this->getBordersLength() - $this->getCoreLength();

		return $free < 1 ? '' : str_repeat(' ', $this->roundHalf($free, $up));
	}

	/**
	 * Decrease the nesting level.
	 *
	 * @author	Andrea Marco Sartori
	 * @return	void
	 */
	public function decreaseNesting()
	{
		$this->nesting--;
	}

}


<?php namespace Cerbero\Workflow\Console\Commands;

/**
 * Trait to attach pipes to pipelines.
 *
 * @author	Andrea Marco Sartori
 */
trait AttachesPipesTrait {

	/**
	 * @author	Andrea Marco Sartori
	 * @var		string	$currentPipe	Pipe to generate.
	 */
	protected $currentPipe;

	/**
	 * Generate the specified pipes.
	 *
	 * @author	Andrea Marco Sartori
	 * @return	void
	 */
	protected function generatePipes()
	{
		foreach ($this->getPipesByOption('attach') as $pipe)
		{
			$this->currentPipe = $pipe;

			parent::fire();
		}
	}

	/**
	 * Retrieve a list of pipes.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	string  $option
	 * @return	array
	 */
	protected function getPipesByOption($option)
	{
		$pipes = $this->option($option);

		preg_match_all('/\w+/', $pipes, $matches);

		return array_map('ucfirst', $matches[0]);
	}

	/**
	 * Get the default namespace for the class.
	 *
	 * @param  string  $rootNamespace
	 * @return string
	 */
	protected function getDefaultNamespace($rootNamespace)
	{
		$workflows = $this->getWorkflowsNamespace();

		$pipeline = $this->getWorkflowName();

		return "{$rootNamespace}\\{$workflows}\\{$pipeline}";
	}

	/**
	 * Retrieve the namespace of the workflows.
	 *
	 * @author	Andrea Marco Sartori
	 * @return	string
	 */
	protected function getWorkflowsNamespace()
	{
		$relative = ltrim(config('workflow.path'), app_path());

		$chunks = array_map('ucfirst', explode('/', $relative));

		return implode('\\', $chunks);
	}

	/**
	 * Retrieve a list of pipes with their namespaces.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	string  $option
	 * @return	array
	 */
	protected function getNamespacedPipesByOption($option)
	{
		return array_map([$this, 'parseName'], $this->getPipesByOption($option));
	}

	/**
	 * Get the desired class name from the input.
	 *
	 * @return string
	 */
	protected function getNameInput()
	{
		return $this->currentPipe;
	}

}


<?php

namespace spec\Cerbero\Workflow\Repositories;

use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Cerbero\Workflow\Wrappers\YamlParserInterface;
use Illuminate\Filesystem\Filesystem;

class YamlPipelineRepositorySpec extends ObjectBehavior
{

    /**
     * @author    Andrea Marco Sartori
     * @var        array    $pipeline    Example of pipeline.
     */
    private $pipeline = array('RegisterUser' => ['Notifier', 'Logger']);

	function let(YamlParserInterface $parser, Filesystem $files)
	{
		$this->beConstructedWith($parser, $files, 'path/to/workflows');

		$parser->parse('path/to/workflows/workflows.yml')->shouldBeCalled()->willReturn($this->pipeline);
	}

    function it_is_initializable()
    {
        $this->shouldHaveType('Cerbero\Workflow\Repositories\YamlPipelineRepository');
        $this->shouldHaveType('Cerbero\Workflow\Repositories\PipelineRepositoryInterface');
    }

    /**
     * @testdox	It returns false when a pipeline does not exist.
     *
     * @author	Andrea Marco Sartori
     * @return	void
     */
    public function it_returns_false_when_a_pipeline_does_not_exist()
    {
    	$this->exists('unknownPipeline')->shouldReturn(false);
    }

    /**
     * @testdox	It returns true when a pipeline exists.
     *
     * @author	Andrea Marco Sartori
     * @return	void
     */
    public function it_returns_true_when_a_pipeline_exists()
    {
    	$this->exists('RegisterUser')->shouldReturn(true);
    }

    /**
     * @testdox	It returns the pipeline of a fiven workflow.
     *
     * @author	Andrea Marco Sartori
     * @return	void
     */
    public function it_returns_the_pipeline_of_a_fiven_workflow()
    {
    	$expected = ['Notifier', 'Logger'];

    	$this->getPipesByPipeline('registerUser')->shouldReturn($expected);
    }

    /**
     * @testdox    It retrieves the source of the pipelines.
     *
     * @author    Andrea Marco Sartori
     * @return    void
     */
    public function it_retrieves_the_source_of_the_pipelines()
    {
        $this->getSource()->shouldReturn('path/to/workflows/workflows.yml');
    }

    /**
     * @testdox    It creates the YAML file.
     *
     * @author    Andrea Marco Sartori
     * @return    void
     */
    public function it_creates_the_YAML_file($files)
    {
        $files->makeDirectory('path/to/workflows', 0755, true, true)->shouldBeCalled();

        $files->put('path/to/workflows/workflows.yml', '')->shouldBeCalled();

        $this->settle();
    }

    /**
     * @testdox    It stores the given pipeline and its pipes.
     *
     * @author    Andrea Marco Sartori
     * @return    void
     */
    public function it_stores_the_given_pipeline_and_its_pipes($parser, $files)
    {
        $parser->dump($this->pipeline)->willReturn('foo');

        $files->append('path/to/workflows/workflows.yml', 'foo')->shouldBeCalled();

        $this->store('RegisterUser', ['Notifier', 'Logger']);
    }

    /**
     * @testdox    It updates an existing pipeline by attaching and detaching pipes.
     *
     * @author    Andrea Marco Sartori
     * @return    void
     */
    public function it_updates_an_existing_pipeline_by_attaching_and_detaching_pipes($parser, $files)
    {
        $updated = ['RegisterUser' => ['Logger', 'Buzzer']];

        $parser->dump($updated)->willReturn('foo');

        $files->put('path/to/workflows/workflows.yml', 'foo')->shouldBeCalled();

        $this->update('RegisterUser', ['Buzzer'], ['Notifier']);
    }

    /**
     * @testdox    It does not attach if no attachments are specified.
     *
     * @author    Andrea Marco Sartori
     * @return    void
     */
    public function it_does_not_attach_if_no_attachments_are_specified($parser, $files)
    {
        $updated = ['RegisterUser' => ['Notifier']];

        $parser->dump($updated)->willReturn('foo');

        $files->put('path/to/workflows/workflows.yml', 'foo')->shouldBeCalled();

        $this->update('RegisterUser', [], ['Logger']);
    }

    /**
     * @testdox    It does not detach if no detachments are specified.
     *
     * @author    Andrea Marco Sartori
     * @return    void
     */
    public function it_does_not_detach_if_no_detachments_are_specified($parser, $files)
    {
        $updated = ['RegisterUser' => ['Notifier', 'Logger', 'Buzzer']];

        $parser->dump($updated)->willReturn('foo');

        $files->put('path/to/workflows/workflows.yml', 'foo')->shouldBeCalled();

        $this->update('RegisterUser', ['Buzzer'], []);
    }

    /**
     * @testdox    It destroys a pipeline.
     *
     * @author    Andrea Marco Sartori
     * @return    void
     */
    public function it_destroys_a_pipeline($parser, $files)
    {
        $parser->dump([])->willReturn('foo');

        $files->put('path/to/workflows/workflows.yml', 'foo')->shouldBeCalled();

        $this->destroy('RegisterUser');
    }
}


<?php namespace Cerbero\Workflow\Console\Commands;

use Symfony\Component\Console\Input\InputOption;

class DeleteWorkflowCommand extends WorkflowGeneratorCommand {

	use DeleteIfForcedTrait;

	/**
	 * The console command name.
	 *
	 * @var string
	 */
	protected $name = 'workflow:delete';

	/**
	 * The console command description.
	 *
	 * @var string
	 */
	protected $description = 'Delete an existing workflow';

	/**
	 * Execute the console command.
	 *
	 * @return mixed
	 */
	public function fire()
	{
		$this->inflector->of($name = $this->getWorkflowName());

		if( ! $this->pipelines->exists($name))
		{
			return $this->error("The workflow [$name] does not exist.");
		}

		$this->deleteAllFilesOfWorkflowIfForced($name);

		$this->pipelines->destroy($name);

		$this->info('Workflow deleted successfully.');
	}

	/**
	 * Delete all the generated files of the given workflow if forced.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	string	$workflow
	 * @return	void
	 */
	protected function deleteAllFilesOfWorkflowIfForced($workflow)
	{
		$files = $this->pipelines->getPipesByPipeline($workflow);

		$files[] = $this->inflector->getRequest();

		$files[] = $this->inflector->getJob();

		$this->deleteIfForced($files);
	}

	/**
	 * Get the console command options.
	 *
	 * @return array
	 */
	protected function getOptions()
	{
		return [
			['force', '-f', InputOption::VALUE_NONE, 'Delete all the generated files of a workflow.'],
		];
	}

}


<?php

namespace Yugo\SMSGateway\Providers;

use Illuminate\Support\ServiceProvider;
use Yugo\SMSGateway\Interfaces\SMS;

class SmsServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap services.
     *
     * @return void
     */
    public function boot()
    {
        $this->publishes([
            realpath(__DIR__.'/../config/message.php') => config_path('message.php'),
        ]);

        $this->loadMigrationsFrom(realpath(__DIR__.'/../database/migrations/'));
    }

    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        $className = studly_case(strtolower(config('message.vendor', 'smsgatewayme')));
        $classPath = '\Yugo\SMSGateway\Vendors\\'.$className;

        if (!class_exists($classPath)) {
            abort(500, sprintf(
                'SMS vendor %s is not available.',
                $className
            ));
        }

        app()->bind(SMS::class, $classPath);
    }
}


<?php

namespace Yugo\SMSGateway\Vendors\Smsgatewayme;

use Illuminate\Support\Facades\Log;
use Unirest\Request;
use Unirest\Request\Body;

class Contact
{
    /**
     * API URL.
     *
     * @var string
     */
    private $baseUrl = 'https://smsgateway.me/api/v4/';

    /**
     * Default device.
     *
     * @var int
     */
    private $device;

    /**
     * Authorization.
     *
     * @var string
     */
    private $token;

    public function __construct(int $device, string $token)
    {
        $this->device = $device;
        $this->token = $token;

        Request::defaultHeaders([
            'Accept'        => 'application/json',
            'Authorization' => $this->token,
        ]);
    }

    /**
     * Store new contact to SMSGateway.me.
     *
     * @param string $name
     * @param array  $numbers
     *
     * @return array|null
     */
    public function create(string $name, array $numbers): ?array
    {
        $body = Body::json([
            [
                'name'          => $name,
                'phone_numbers' => $numbers,
            ],
        ]);
        $response = Request::post($this->baseUrl.'contact', [], $body);

        if ($response->code != 200) {
            if (!empty($response->body->message)) {
                Log::error($response->body->message);
            }
        }

        return [
            'code'    => $response->code,
            'message' => ($response->code == 200) ? 'OK' : $response->body->message ?? '',
            'data'    => $response->body,
        ];
    }

    /**
     * Get detailed stored contact.
     *
     * @param int $id
     *
     * @return array|null
     */
    public function info(int $id): ?array
    {
        $response = Request::get($this->baseUrl.'contact/'.$id);

        if ($response->code != 200) {
            if (!empty($response->body->message)) {
                Log::error($response->body->message);
            }
        }

        return [
            'code'    => $response->code,
            'message' => ($response->code == 200) ? 'OK' : $response->body->message ?? '',
            'data'    => $response->body,
        ];
    }

    /**
     * Add number to existing contact.
     *
     * @param int    $id
     * @param string $number
     *
     * @return array|null
     */
    public function addNumber(int $id, string $number): ?array
    {
        $response = Request::put($this->baseUrl.sprintf('contact/%d/phone-number/%s', $id, $number));

        if ($response->code != 200) {
            if (!empty($response->body->message)) {
                Log::error($response->body->message);
            }
        }

        return [
            'code'    => $response->code,
            'message' => ($response->code == 200) ? 'OK' : $response->body->message ?? '',
            'data'    => $response->body,
        ];
    }

    /**
     * Remove number from existing contact.
     *
     * @param int    $id
     * @param string $number
     *
     * @return array|null
     */
    public function removeNumber(int $id, string $number): ?array
    {
        $response = Request::delete($this->baseUrl.sprintf('contact/%d/phone-number/%s', $id, $number));

        if ($response->code != 200) {
            if (!empty($response->body->message)) {
                Log::error($response->body->message);
            }
        }

        return [
            'code'    => $response->code,
            'message' => ($response->code == 200) ? 'OK' : $response->body->message ?? '',
            'data'    => $response->body,
        ];
    }

    public function update(int $id, string $name, array $numbers = []): ?array
    {
        $body = Body::json([
            'name'          => $name,
            'phone_numbers' => $numbers,
        ]);
        $response = Request::put($this->baseUrl.'contact/'.$id, [], $body);

        if ($response->code != 200) {
            if (!empty($response->body->message)) {
                Log::error($response->body->message);
            }
        }

        return [
            'code'    => $response->code,
            'message' => ($response->code == 200) ? 'OK' : $response->body->message ?? '',
            'data'    => $response->body,
        ];
    }
}


<?php namespace Cerbero\Workflow\Pipes;

use \Closure;
use Illuminate\Contracts\Container\Container;

/**
 * Abstract implementation of a pipe.
 *
 * @author	Andrea Marco Sartori
 */
abstract class AbstractPipe implements PipeInterface {

	/**
	 * @author	Andrea Marco Sartori
	 * @var		Illuminate\Contracts\Container\Container	$container	Service container.
	 */
	protected $container;

	/**
	 * Set the dependencies.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	Illuminate\Contracts\Container\Container	$container
	 * @return	void
	 */
	public function __construct(Container $container)
	{
		$this->container = $container;
	}

	/**
	 * Handle the given job.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	mixed	$job
	 * @param	Closure	$next
	 * @return	mixed
	 */
	public function handle($job, Closure $next)
	{
		$this->callBefore($job);

		$handled = $next($job);

		$this->callAfter($handled, $job);

		return $handled;
	}

	/**
	 * Call the before method.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	Cerbero\Jobs\Job	$job
	 * @return	void
	 */
	protected function callBefore($job)
	{
		$this->callIfExists('before', [$job]);
	}

	/**
	 * Call a method if it exists and resolve its dependencies.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	string	$method
	 * @param	array	$parameters
	 * @return	void
	 */
	private function callIfExists($method, array $parameters = [])
	{
		if(method_exists($this, $method))
		{
			$this->container->call([$this, $method], $parameters);
		}
	}

	/**
	 * Call the after method.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	mixed	$handled
	 * @param	Cerbero\Jobs\Job	$job
	 * @return	void
	 */
	protected function callAfter($handled, $job)
	{
		$this->callIfExists('after', [$handled, $job]);
	}

}


<?php

namespace Yugo\SMSGateway\Vendors;

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Unirest\Request;
use Unirest\Request\Body;
use Yugo\SMSGateway\Interfaces\SMS;
use Yugo\SMSGateway\Vendors\Smsgatewayme\Callback;
use Yugo\SMSGateway\Vendors\Smsgatewayme\Contact;

class Smsgatewayme implements SMS
{
    /**
     * API base URL.
     *
     * @var string
     */
    private $baseUrl = 'https://smsgateway.me/api/v4/';

    /**
     * Device ID from SMSgateway.me.
     *
     * @var string
     */
    private $device = null;

    /**
     * Generated token from SMSgateway.me.
     *
     * @link http://smsgateway.me/dashboard/settings
     *
     * @var string
     */
    private $token = null;

    /**
     * Store requested data into cache.
     *
     * @var bool
     */
    private $cache = false;

    public function __construct()
    {
        $this->device = (int) config('message.smsgatewayme.device');
        $this->token = config('message.smsgatewayme.token');

        Request::defaultHeaders([
            'Accept'        => 'application/json',
            'Authorization' => $this->token,
        ]);
    }

    /**
     * Set device ID manually.
     *
     * @param int $id
     *
     * @return self
     */
    public function setDevice(int $id): self
    {
        $this->device = $id;

        return $this;
    }

    /**
     * Set token manually.
     *
     * @param string $token
     *
     * @return self
     */
    public function setToken(string $token): self
    {
        $this->token = $token;

        return $this;
    }

    /**
     * Set cache as true and trigger cache data for every request.
     *
     * @param bool $cache
     *
     * @return self
     */
    public function setCache(bool $cache = true): self
    {
        $this->cache = $cache;

        return $this;
    }

    /**
     * Get device information.
     *
     * @return object|null
     */
    public function device(?int $id = null): ?array
    {
        if (is_null($id)) {
            $id = $this->device;
        }

        $key = sprintf('smsgatewayme.device.%s', $id);
        $device = Cache::remember($key, 3600 * 24 * 7, function () use (&$response, $id) {
            $response = Request::get($this->baseUrl.'device/'.$id);

            if ($response->code != 200) {
                if (!empty($response->body->message)) {
                    Log::error($response->body->message);
                }
            }

            return $response->body;
        });

        return [
            'code'    => $response->code ?? 200,
            'message' => 'OK',
            'data'    => $device,
        ];
    }

    /**
     * Send message to single number.
     *
     * @param string $destination
     * @param string $text
     *
     * @return object|null
     */
    public function send(array $destinations, string $text): ?array
    {
        $this->checkConfig();

        $messages = [];
        foreach ($destinations as $destination) {
            $messages[] = [
                'phone_number' => $destination,
                'message'      => $text,
                'device_id'    => $this->device,
            ];
        }

        $body = Body::json($messages);
        $response = Request::post($this->baseUrl.'message/send', [], $body);

        if ($response->code == 200) {
            return [
                'code'    => $response->code,
                'message' => 'OK',
                'data'    => $response->body,
            ];
        } else {
            if (!empty($response->body->message)) {
                Log::error($response->body->message);
            }

            return [
                'code'    => $response->code,
                'message' => $response->body->message ?? '',
                'data'    => $response->body,
            ];
        }
    }

    /**
     * Cancel queued message.
     *
     * @return array|null
     */
    public function cancel(array $identifiers = []): ?array
    {
        $this->checkConfig();

        if (empty($identifiers)) {
            return null;
        }

        $messages = [];
        foreach ($identifiers as $id) {
            $messages[] = ['id' => (int) $id];
        }

        $body = Body::json($messages);
        $response = Request::post($this->baseUrl.'message/cancel', [], $body);

        if ($response->code == 200) {
            return [
                'code'    => $response->code,
                'message' => 'OK',
                'data'    => $response->body,
            ];
        } else {
            if (!empty($response->body->message)) {
                Log::error($response->body->message);
            }

            return [
                'code'    => $response->code,
                'message' => $response->body->message ?? '',
                'data'    => $response->body,
            ];
        }
    }

    /**
     * Get detailed information about message.
     *
     * @param int $id
     *
     * @return array|null
     */
    public function info(int $id): ?array
    {
        $this->checkConfig();

        $key = sprintf('smsgatewayme.info.%s', $id);

        if ($this->cache === true and Cache::has($key)) {
            $message = [
                'code'    => 200,
                'message' => 'OK',
                'data'    => Cache::get($key),
            ];
        } else {
            $response = Request::get($this->baseUrl.'message/'.$id);

            if ($response->code == 200) {
                Cache::put($key, $response->body, 3600 * 24);
            } else {
                if (!empty($response->body->message)) {
                    Log::error($response->body->message);
                }
            }

            $message = [
                'code'    => $response->code,
                'message' => ($response->code == 200) ? 'OK' : $response->body->message ?? '',
                'data'    => $response->body,
            ];
        }

        return $message;
    }

    /**
     * Callback/hook operations for SMSgateway.me.
     *
     * @return callable
     */
    public function callback(): Callback
    {
        return new Callback($this->device, $this->token);
    }

    /**
     * Contact utilities.
     *
     * @return Contact
     */
    public function contact(): Contact
    {
        return new Contact($this->device, $this->token);
    }

    /**
     * Check default config for easy debuggin.
     *
     * @return void
     */
    private function checkConfig(): void
    {
        if (empty($this->device)) {
            Log::warning('Config "message.smsgatewayme.device" is not defined.');
        }

        if (empty($this->token)) {
            Log::warning('Config "message.smsgatewayme.token" is not defined.');
        }
    }
}


<?php

namespace Cerbero\Workflow\Wrappers;

use Exception;
use ArrayAccess;
use ReflectionClass;
use ReflectionParameter;
use Illuminate\Contracts\Bus\Dispatcher;

/**
 * Wrapper to marshal commands before dispatching them.
 *
 * @author	Andrea Marco Sartori
 */
class MarshalDispatcher implements DispatcherInterface
{

	/**
	 * @author	Andrea Marco Sartori
	 * @var		Illuminate\Contracts\Bus\Dispatcher	$dispatcher	Bus dispatcher.
	 */
	protected $dispatcher;

	/**
	 * @author	Andrea Marco Sartori
	 * @var		string  $command	Command to dispatch
	 */
	protected $command;

	/**
	 * @author	Andrea Marco Sartori
	 * @var		\ArrayAccess  $values	Parameters values
	 */
	protected $values;

	/**
	 * Set the dependencies.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	Illuminate\Contracts\Bus\Dispatcher	$dispatcher
	 * @return	void
	 */
	public function __construct(Dispatcher $dispatcher)
	{
		$this->dispatcher = $dispatcher;
	}

	/**
     * Set the pipes commands should be piped through before dispatching.
     *
	 * @author	Andrea Marco Sartori
     * @param  array  $pipes
     * @return $this
     */
    public function pipeThrough(array $pipes)
    {
    	$this->dispatcher->pipeThrough($pipes);

    	return $this;
    }

    /**
     * Marshal a command and dispatch it.
     *
	 * @author	Andrea Marco Sartori
     * @param  mixed  $command
     * @param  \ArrayAccess  $source
     * @param  array  $extras
     * @return mixed
     */
    public function dispatchFrom($command, ArrayAccess $source, array $extras = [])
    {
    	$this->command = $command;

    	$this->values = array_merge((array) $source, $extras);

    	return $this->dispatcher->dispatch($this->marshal());
    }

    /**
     * Marshal the command to dispatch.
     *
	 * @author	Andrea Marco Sartori
     * @return mixed
     */
    protected function marshal()
    {
        $reflection = new ReflectionClass($this->command);

        $constructor = $reflection->getConstructor();

        $params = $this->getParamsToInject($constructor->getParameters());

        return $reflection->newInstanceArgs($params);
    }

    /**
     * Retrieve the arguments to inject into the command constructor.
     *
     * @author	Andrea Marco Sartori
     * @param	array	$parameters
     * @return	array
     */
    protected function getParamsToInject(array $parameters)
    {
        return array_map(function ($parameter)
        {
            return $this->grabParameter($parameter);

        }, $parameters);
    }

    /**
     * Get a parameter value for a marshaled command.
     *
	 * @author	Andrea Marco Sartori
     * @param  \ReflectionParameter  $parameter
     * @return mixed
     */
    protected function grabParameter(ReflectionParameter $parameter)
    {
        if (isset($this->values[$parameter->name]))
        {
            return $this->values[$parameter->name];
        }

        if ($parameter->isDefaultValueAvailable())
        {
            return $parameter->getDefaultValue();
        }

        throw new Exception("Unable to map parameter [{$parameter->name}] to command [{$this->command}]");
    }

}


<?php

namespace spec\Cerbero\Workflow\Console\Drawing;

use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Cerbero\Workflow\Repositories\PipelineRepositoryInterface;
use Cerbero\Workflow\Console\Drawing\Geometry;

class DrawerSpec extends ObjectBehavior
{
	function let(PipelineRepositoryInterface $pipelines)
	{
		$geometry = new Geometry;

		$this->beConstructedWith($pipelines, $geometry);
	}

    function it_is_initializable()
    {
        $this->shouldHaveType('Cerbero\Workflow\Console\Drawing\Drawer');
    }

    /**
     * @testdox	It draws a workflow with zero pipes.
     *
     * @author	Andrea Marco Sartori
     * @return	void
     */
    public function it_draws_a_workflow_with_zero_pipes($pipelines)
    {
    	$pipelines->getPipesByPipeline('RegisterUser')->willReturn([]);

    	$drawing = file_get_contents(__DIR__ . '/stubs/zero.stub');

    	$this->draw('RegisterUser')->shouldReturn($drawing);
    }

    /**
     * @testdox	It draws a workflow with one pipe.
     *
     * @author	Andrea Marco Sartori
     * @return	void
     */
    public function it_draws_a_workflow_with_one_pipe($pipelines)
    {
    	$pipelines->getPipesByPipeline('RegisterUser')->willReturn(['App\Workflows\RegisterUser\Notifier']);

    	$drawing = file_get_contents(__DIR__ . '/stubs/one.stub');

    	$this->draw('RegisterUser')->shouldReturn($drawing);
    }

    /**
     * @testdox	It draws a workflow with two pipe.
     *
     * @author	Andrea Marco Sartori
     * @return	void
     */
    public function it_draws_a_workflow_with_two_pipe($pipelines)
    {
    	$pipelines->getPipesByPipeline('RegisterUser')->willReturn(['App\Workflows\RegisterUser\Logger', 'App\Workflows\RegisterUser\Notifier']);

    	$drawing = file_get_contents(__DIR__ . '/stubs/two.stub');

    	$this->draw('RegisterUser')->shouldReturn($drawing);
    }

    /**
     * @testdox	It draws a workflow with many pipes.
     *
     * @author	Andrea Marco Sartori
     * @return	void
     */
    public function it_draws_a_workflow_with_many_pipes($pipelines)
    {
    	$pipelines->getPipesByPipeline('LoginUser')->willReturn(['LongName', 'TheLongestNameEver', 'LongerName', 'Name', 'VeryLongName']);

    	$drawing = file_get_contents(__DIR__ . '/stubs/many.stub');

    	$this->draw('LoginUser')->shouldReturn($drawing);
    }
}


<?php namespace Cerbero\Workflow;

use Cerbero\Workflow\Repositories\PipelineRepositoryInterface;
use Cerbero\Workflow\Inflectors\InflectorInterface;
use Cerbero\Workflow\Wrappers\DispatcherInterface;
use Illuminate\Contracts\Container\Container;

/**
 * Hub of the pipelines.
 *
 * @author	Andrea Marco Sartori
 */
class Workflow {

	/**
	 * @author	Andrea Marco Sartori
	 * @var		Cerbero\Workflow\Repositories\PipelineRepositoryInterface	$pipelines	Pipelines repository.
	 */
	protected $pipelines;

	/**
	 * @author	Andrea Marco Sartori
	 * @var		Cerbero\Workflow\Inflectors\InflectorInterface	$inflector	Inflector.
	 */
	protected $inflector;

	/**
	 * @author	Andrea Marco Sartori
	 * @var		Illuminate\Contracts\Container\Container	$container	Service container.
	 */
	protected $container;

	/**
	 * @author	Andrea Marco Sartori
	 * @var		Cerbero\Workflow\Wrappers\DispatcherInterface	$dispatcher	Bus dispatcher.
	 */
	protected $dispatcher;

	/**
	 * Set the dependencies.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	Cerbero\Workflow\Repositories\PipelineRepositoryInterface	$pipelines
	 * @param	Cerbero\Workflow\Wrappers\DispatcherInterface	$dispatcher
	 * @param	Cerbero\Workflow\Inflectors\InflectorInterface	$inflector
	 * @param	Illuminate\Contracts\Container\Container	$container
	 * @return	void
	 */
	public function __construct(
		PipelineRepositoryInterface $pipelines,
		InflectorInterface $inflector,
		Container $container,
		DispatcherInterface $dispatcher
	) {
		$this->pipelines  = $pipelines;
		$this->inflector  = $inflector;
		$this->container  = $container;
		$this->dispatcher = $dispatcher;
	}

	/**
	 * Dynamically call pipelines.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	string	$name
	 * @param	array	$arguments
	 * @return	mixed
	 */
	public function __call($name, $arguments)
	{
		$this->failIfNotExists($name);

		$this->inflector->of($name);

		return $this->dispatchWorkflow($name);
	}

	/**
	 * Throw an exception if the given workflow does not exist.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	string	$workflow
	 * @return	void
	 */
	protected function failIfNotExists($workflow)
	{
		if( ! $this->pipelines->exists($workflow))
		{
			$error = "The workflow [$workflow] does not exist.";

			throw new \BadFunctionCallException($error);
		}
	}

	/**
	 * Dispatch the given workflow.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	string	$workflow
	 * @return	mixed
	 */
	protected function dispatchWorkflow($workflow)
	{
		$job = $this->inflector->getJob();

		$request = $this->resolveRequest();

		$pipes = $this->pipelines->getPipesByPipeline($workflow);

		$parameters = $this->container->make('router')->current()->parameters();

		return $this->dispatcher->pipeThrough($pipes)->dispatchFrom($job, $request, $parameters);
	}

	/**
	 * Resolve the apter request.
	 *
	 * @author	Andrea Marco Sartori
	 * @return	Illuminate\Http\Request
	 */
	protected function resolveRequest()
	{
		if(class_exists($request = $this->inflector->getRequest()))
		{
			return $this->container->make($request);
		}

		return $this->container->make('Illuminate\Http\Request');
	}

}


<?php namespace Cerbero\Workflow\Console\Commands;

/**
 * Trait to delete files if forced.
 *
 * @author	Andrea Marco Sartori
 */
trait DeleteIfForcedTrait {

	/**
	 * Delete the given files if force is set.
	 *
	 * @author	Andrea Marco Sartori
	 * @return	void
	 */
	protected function deleteIfForced(array $files)
	{
		if( ! $this->option('force')) return;

		foreach ($files as $file)
		{
			if($this->files->exists($path = $this->getPath($file)))
			{
				$this->files->delete($path);
			}
		}
	}

}


<?php namespace Cerbero\Workflow;

use Cerbero\Workflow\WorkflowRunner;
use Illuminate\Foundation\AliasLoader;
use Illuminate\Support\ServiceProvider;
use Cerbero\Workflow\Inflectors\Inflector;
use Cerbero\Workflow\Wrappers\SymfonyYamlParser;
use Cerbero\Workflow\Repositories\YamlPipelineRepository;
use Cerbero\Workflow\Wrappers\LaravelTraitNamespaceDetector;

/**
 * Workflow service provider.
 *
 * @author	Andrea Marco Sartori
 */
class WorkflowServiceProvider extends ServiceProvider {

	/**
	 * @author	Andrea Marco Sartori
	 * @var		array	$commands	List of registered commands.
	 */
	protected $commands = [
		'cerbero.workflow.create',
		'cerbero.workflow.read',
		'cerbero.workflow.update',
		'cerbero.workflow.delete',
	];

	/**
	 * Boot the package up.
	 *
	 * @author	Andrea Marco Sartori
	 * @return	void
	 */
	public function boot()
	{
		$this->publishConfig();

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

		$facade = 'Cerbero\Workflow\Facades\Workflow';

		AliasLoader::getInstance()->alias('Workflow', $facade);
	}

	/**
	 * Publish the configuration file.
	 *
	 * @author	Andrea Marco Sartori
	 * @return	void
	 */
	private function publishConfig()
	{
		$config = __DIR__ . '/config/workflow.php';

		$this->publishes([$config => config_path('workflow.php')]);

		$this->mergeConfigFrom($config, 'workflow');
	}

	/**
	 * Register the services.
	 *
	 * @author	Andrea Marco Sartori
	 * @return	void
	 */
	public function register()
	{
		$this->registerPipelineRepository();

		$this->registerInflector();

		$this->registerDispatcher();

		$this->registerWorkflow();

		$this->registerWorkflowRunnersHook();

		$this->registerCommands();
	}

	/**
	 * Register the pipeline repository.
	 *
	 * @author	Andrea Marco Sartori
	 * @return	void
	 */
	private function registerPipelineRepository()
	{
		$abstract = 'Cerbero\Workflow\Repositories\PipelineRepositoryInterface';

		$this->app->bind($abstract, function($app)
		{
			return new YamlPipelineRepository
			(
				new SymfonyYamlParser,

				new \Illuminate\Filesystem\Filesystem,

				config('workflow.path')
			);
		});
	}

	/**
	 * Register the inflector.
	 *
	 * @author	Andrea Marco Sartori
	 * @return	void
	 */
	private function registerInflector()
	{
		$abstract = 'Cerbero\Workflow\Inflectors\InflectorInterface';

		$this->app->bind($abstract, function()
		{
			return new Inflector(new LaravelTraitNamespaceDetector);
		});
	}

	/**
	 * Register the bus dispatcher.
	 *
	 * @author	Andrea Marco Sartori
	 * @return	void
	 */
	private function registerDispatcher()
	{
		$abstract = 'Cerbero\Workflow\Wrappers\DispatcherInterface';

		$this->app->bind($abstract, function($app)
		{
			return $app['Cerbero\Workflow\Wrappers\MarshalDispatcher'];
		});
	}

	/**
	 * Register the package main class.
	 *
	 * @author	Andrea Marco Sartori
	 * @return	void
	 */
	private function registerWorkflow()
	{
		$this->app->singleton('cerbero.workflow', function($app)
		{
			return $app['Cerbero\Workflow\Workflow'];
		});
	}

	/**
	 * Register the hook for the workflow runners.
	 *
	 * @author	Andrea Marco Sartori
	 * @return	void
	 */
	private function registerWorkflowRunnersHook()
	{
		$this->app->afterResolving(function(WorkflowRunner $runner, $app)
		{
			$runner->setWorkflow($app['cerbero.workflow']);
		});
	}

	/**
	 * Register the console commands.
	 *
	 * @author	Andrea Marco Sartori
	 * @return	void
	 */
	private function registerCommands()
	{
		foreach ($this->commands as $command)
		{
			$name = ucfirst(last(explode('.', $command)));

			$this->app->singleton($command, function($app) use($name)
			{
				return $app["Cerbero\Workflow\Console\Commands\\{$name}WorkflowCommand"];
			});
		}
	}

}


<?php namespace Cerbero\Workflow\Console\Drawing;

use Cerbero\Workflow\Repositories\PipelineRepositoryInterface;

/**
 * Class to generate the drawing.
 *
 * @author	Andrea Marco Sartori
 */
class Drawer {

	const BORDER_X = '║';

	const BORDER_Y = '═';

	const BORDER_NW = '╔';

	const BORDER_NE = '╗';

	const BORDER_SE = '╝';

	const BORDER_SW = '╚';

	const NOCK = '║';

	const PILE = '∇';

	const CROSSROADS = '╬';

	const CROSSROADS_UP = '╩';

	const CROSSROADS_DOWN = '╦';

	/**
	 * @author	Andrea Marco Sartori
	 * @var		Cerbero\Workflow\Repositories\PipelineRepositoryInterface	$pipelines	Pipeline repository.
	 */
	protected $pipelines;

	/**
	 * @author	Andrea Marco Sartori
	 * @var		Cerbero\Workflow\Console\Drawing\Geometry	$geometry	The applied geometry.
	 */
	protected $geometry;

	/**
	 * @author	Andrea Marco Sartori
	 * @var		array	$pipes	List of pipes.
	 */
	protected $pipes;

	/**
	 * @author	Andrea Marco Sartori
	 * @var		string	$drawing	The resulting drawing.
	 */
	protected $drawing = '';

	/**
	 * Set the dependencies.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	Cerbero\Workflow\Repositories\PipelineRepositoryInterface	$pipelines
	 * @param	Cerbero\Workflow\Console\Drawing\Geometry	$geometry
	 * @return	void
	 */
	public function __construct(PipelineRepositoryInterface $pipelines, Geometry $geometry)
	{
		$this->pipelines = $pipelines;

		$this->geometry = $geometry;
	}

	/**
	 * Draw the given workflow.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	string	$workflow
	 * @return	string
	 */
	public function draw($workflow)
	{
		$this->geometry->setCore($workflow);

		$this->setPipesOfWorkflow($workflow);

		$this->drawCenteredChar(static::NOCK);

		$this->drawPipesBeginning();

		$this->drawCore();

		$this->drawPipesEnd();

		$this->drawCenteredChar(static::PILE);

		return $this->drawing;
	}

	/**
	 * Set the pipes of the given workflow.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	string	$workflow
	 * @return	void
	 */
	protected function setPipesOfWorkflow($workflow)
	{
		$pipes = $this->pipelines->getPipesByPipeline($workflow);

		$this->pipes = array_map(function($pipe)
		{
			$chunks = explode('\\', $pipe);

			return end($chunks);

		}, $pipes);

		$this->geometry->setPipes($this->pipes);
	}

	/**
	 * Draw a character in the middle of the drawing.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	string	$character
	 * @return	void
	 */
	protected function drawCenteredChar($character)
	{
		$spaces = str_repeat(' ', $this->geometry->getHalfWidth());

		$this->drawRow($spaces . $character);
	}

	/**
	 * Draw a row of the drawing.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	string	$row
	 * @return	void
	 */
	protected function drawRow($row)
	{
		$this->drawing .= $row . PHP_EOL;
	}

	/**
	 * Draw the beginning of all pipes.
	 *
	 * @author	Andrea Marco Sartori
	 * @return	void
	 */
	protected function drawPipesBeginning()
	{
		foreach ($this->pipes as $pipe)
		{
			$this->drawBorderTop();

			$this->drawBordered(
				$this->geometry->getSpacedPipe($pipe, static::NOCK, 'before()')
			);
		}
	}

	/**
	 * Draw content wrapped by borders.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	string	$content
	 * @return	void
	 */
	protected function drawBordered($content)
	{
		$left = $this->geometry->getLeftBordersWith(static::BORDER_X);

		$right = $this->geometry->getRightBordersWith(static::BORDER_X);

		$this->drawRow($left.$content.$right);
	}

	/**
	 * Draw the top border.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	boolean	$isCore
	 * @return	void
	 */
	protected function drawBorderTop($isCore = false)
	{
		$crossroads = $isCore ? static::CROSSROADS_UP : static::CROSSROADS;

		$this->drawBorder(static::BORDER_NW, $crossroads, static::BORDER_NE);

		$this->geometry->increaseNesting();
	}

	/**
	 * Draw a border with the given bendings.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	string	$left
	 * @param	string	$middle
	 * @param	string	$right
	 * @return	void
	 */
	protected function drawBorder($left, $middle, $right)
	{
		$width = $this->geometry->getWidthButBorders();

		$border = str_repeat(static::BORDER_Y, $width);

		$this->replaceUtf8($border, $left, 0);
		$this->replaceUtf8($border, $middle, floor($width / 2));
		$this->replaceUtf8($border, $right, $width - 1);

		$this->drawBordered($border);
	}

	/**
	 * Replace a character in a given position of a string.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	string	$original
	 * @param	string	$replacement
	 * @param	integer	$position
	 * @return	void
	 */
	private function replaceUtf8(&$original, $replacement, $position)
	{
		$start = mb_substr($original, 0, $position, "UTF-8");

		$end = mb_substr($original, $position + 1, mb_strlen($original, 'UTF-8'), "UTF-8");

		$original = $start . $replacement . $end;
	}

	/**
	 * Draw the core of the workflow.
	 *
	 * @author	Andrea Marco Sartori
	 * @return	void
	 */
	protected function drawCore()
	{
		$this->drawBorderTop(true);

		$this->drawBordered($this->geometry->getSpacedCore());

		$this->drawBorderBottom(true);
	}

	/**
	 * Draw the bottom border.
	 *
	 * @author	Andrea Marco Sartori
	 * @param	boolean	$isCore
	 * @return	void
	 */
	protected function drawBorderBottom($isCore = false)
	{
		$this->geometry->decreaseNesting();

		$crossroads = $isCore ? static::CROSSROADS_DOWN : static::CROSSROADS;

		$this->drawBorder(static::BORDER_SW, $crossroads, static::BORDER_SE);
	}

	/**
	 * Draw the end of all pipes.
	 *
	 * @author	Andrea Marco Sartori
	 * @return	void
	 */
	protected function drawPipesEnd()
	{
		$pipes = array_reverse($this->pipes);

		foreach ($pipes as $pipe)
		{
			$this->drawBordered(
				$this->geometry->getSpacedPipe($pipe, static::NOCK, 'after()')
			);

			$this->drawBorderBottom();
		}
	}

}