<?php

namespace BeyondCode\EmailConfirmation\Traits;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
use Illuminate\Validation\ValidationException;

/**
 * Trait SendsPasswordResetEmails
 * @package BeyondCode\EmailConfirmation\Traits
 */
trait SendsPasswordResetEmails
{
    use \Illuminate\Foundation\Auth\SendsPasswordResetEmails;

    /**
     * Send a reset link to the given user.
     *
     * @param Request $request
     * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse
     * @throws ValidationException
     */
    public function sendResetLinkEmail(Request $request)
    {
        $this->validateEmail($request);

        $user = $this->broker()->getUser($request->only('email'));

        // If the user hasn't confirmed their email address,
        // we will throw a validation exception for this error.
        // A user can not request a password reset link if they are not confirmed.
        if ($user && is_null($user->confirmed_at)) {

            session(['confirmation_user_id' => $user->getKey()]);

            throw ValidationException::withMessages([
                'confirmation' => [
                    __('confirmation::confirmation.not_confirmed_reset_password', [
                        'resend_link' => route('auth.resend_confirmation')
                    ])
                ]
            ]);
        }

        // We will send the password reset link to this user. Once we have attempted
        // to send the link, we will examine the response then see the message we
        // need to show to the user. Finally, we'll send out a proper response.
        $response = $this->broker()->sendResetLink(
            $request->only('email')
        );

        return $response == Password::RESET_LINK_SENT
            ? $this->sendResetLinkResponse($response)
            : $this->sendResetLinkFailedResponse($request, $response);
    }
}


<?php

namespace BeyondCode\EmailConfirmation\Traits;

use BeyondCode\EmailConfirmation\Events\Confirmed;
use Illuminate\Http\Request;
use Illuminate\Auth\Events\Registered;

trait RegistersUsers
{
    use \Illuminate\Foundation\Auth\RegistersUsers {
        register as baseRegister;
    }

    /**
     * Get redirect path after a successful confirmation.
     *
     * @return string
     */
    public function redirectAfterConfirmationPath()
    {
        if (method_exists($this, 'redirectConfirmationTo')) {
            return $this->redirectConfirmationTo();
        }

        return property_exists($this, 'redirectConfirmationTo') ? $this->redirectConfirmationTo : route('login');
    }

    /**
     * Get redirect path after a registration that still needs to be confirmed.
     *
     * @return string
     */
    public function redirectAfterRegistrationPath()
    {
        if (method_exists($this, 'redirectAfterRegistrationTo')) {
            return $this->redirectAfterRegistrationTo();
        }

        return property_exists($this, 'redirectAfterRegistrationTo') ? $this->redirectAfterRegistrationTo : route('login');
    }

    /**
     * Get redirect path after the confirmation was sent.
     *
     * @return string
     */
    public function redirectAfterResendConfirmationPath()
    {
        if (method_exists($this, 'redirectAfterResendConfirmationTo')) {
            return $this->redirectAfterResendConfirmationTo();
        }

        return property_exists($this, 'redirectAfterResendConfirmationTo') ? $this->redirectAfterResendConfirmationTo : route('login');
    }

    /**
     * Confirm a user with a given confirmation code.
     *
     * @param $confirmation_code
     * @return \Illuminate\Http\RedirectResponse
     */
    public function confirm($confirmation_code)
    {
        $model = $this->guard()->getProvider()->createModel();

        $user = $model->where('confirmation_code', $confirmation_code)->firstOrFail();

        $user->confirmation_code = null;
        $user->confirmed_at = now();
        $user->save();

        event(new Confirmed($user));

        return $this->confirmed($user)
            ?: redirect($this->redirectAfterConfirmationPath())->with('confirmation', __('confirmation::confirmation.confirmation_successful'));
    }

    /**
     * Resend a confirmation code to a user.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\RedirectResponse
     */
    public function resendConfirmation(Request $request)
    {
        $model = $this->guard()->getProvider()->createModel();

        $user = $model->findOrFail($request->session()->pull('confirmation_user_id'));
        $this->sendConfirmationToUser($user);

        return redirect($this->redirectAfterResendConfirmationPath())->with('confirmation', __('confirmation::confirmation.confirmation_resent'));
    }

    /**
     * Handle a registration request for the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function register(Request $request)
    {
        $this->validator($request->all())->validate();

        event(new Registered($user = $this->create($request->all())));

        $this->sendConfirmationToUser($user);

        return $this->registered($request, $user)
            ?: redirect($this->redirectAfterRegistrationPath())->with('confirmation', __('confirmation::confirmation.confirmation_info'));
    }

    /**
     * Send the confirmation code to a user.
     *
     * @param $user
     */
    protected function sendConfirmationToUser($user)
    {
        // Create the confirmation code
        $user->confirmation_code = str_random(25);
        $user->save();

        // Notify the user
        $notification = app(config('confirmation.notification'));
        $user->notify($notification);
    }

    /**
     * The users email address has been confirmed.
     *
     * @param  mixed  $user
     * @return mixed
     */
    protected function confirmed($user) {
        //
    }
}


<?php

namespace BeyondCode\EmailConfirmation\Traits;

use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;

trait AuthenticatesUsers
{
    use \Illuminate\Foundation\Auth\AuthenticatesUsers {
        attemptLogin as baseAttemptLogin;
    }

    /**
     * Attempt to log the user into the application.
     *
     * @param  \Illuminate\Http\Request $request
     * @return bool
     * @throws ValidationException
     */
    protected function attemptLogin(Request $request)
    {
        if ($this->guard()->validate($this->credentials($request))) {
            $user = $this->guard()->getLastAttempted();

            if (! is_null($user->confirmed_at)) {
                return $this->baseAttemptLogin($request);
            }

            session([
                'confirmation_user_id' => $user->getKey()
            ]);

            throw ValidationException::withMessages([
                'confirmation' => [
                    __('confirmation::confirmation.not_confirmed', [
                        'resend_link' => route('auth.resend_confirmation')
                    ])
                ]
            ]);
        }
        return false;
    }
}


<?php

namespace BeyondCode\EmailConfirmation\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;

class ConfirmEmail extends Notification implements ShouldQueue
{
    use Queueable;

    /**
     * Get the notification's delivery channels.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        return ['mail'];
    }

    /**
     * Get the mail representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return \Illuminate\Notifications\Messages\MailMessage
     */
    public function toMail($notifiable)
    {
        return (new MailMessage)
            ->subject(__('confirmation::confirmation.confirmation_subject'))
            ->line(__('confirmation::confirmation.confirmation_subject_title'))
            ->line(__('confirmation::confirmation.confirmation_body'))
            ->action(__('confirmation::confirmation.confirmation_button'),
                url("register/confirm/$notifiable->confirmation_code"));
    }
}


<?php

namespace URL;

/**
 * Syntax based normalization of URI's
 *
 * This normalises URI's based on the specification RFC 3986
 * https://tools.ietf.org/html/rfc3986
 *
 * Example usage:
 * <code>
 * require_once 'vendor/autoload.php';
 *
 * $url = 'eXAMPLE://a/./b/../b/%63/%7bfoo%7d';
 * $un = new URL\Normalizer( $url );
 * echo $un->normalize();
 *
 * // result: "example://a/b/c/%7Bfoo%7D"
 * </code>
 *
 * @author Glen Scott <glen@glenscott.co.uk>
 */
class Normalizer
{
    private $url;
    private $scheme;
    private $host;
    private $port;
    private $user;
    private $pass;
    private $path;
    private $query;
    private $fragment;
    private $default_scheme_ports = array( 'http:' => 80, 'https:' => 443, );
    private $components = array( 'scheme', 'host', 'port', 'user', 'pass', 'path', 'query', 'fragment', );
    private $remove_empty_delimiters;
    private $sort_query_params;

    /**
     * Does the original URL have a ? query delimiter
     */
    private $query_delimiter;

    public function __construct($url = null, $remove_empty_delimiters = false, $sort_query_params = false)
    {
        if ($url) {
            $this->setUrl($url);
        }

        $this->remove_empty_delimiters = $remove_empty_delimiters;
        $this->sort_query_params       = $sort_query_params;
    }

    private function getQuery($query)
    {
        $qs = array();
        foreach ($query as $qk => $qv) {
            if (is_array($qv)) {
                $qs[rawurldecode($qk)] = $this->getQuery($qv);
            } else {
                $qs[rawurldecode($qk)] = rawurldecode($qv);
            }
        }
        return $qs;
    }

    public function getUrl()
    {
        return $this->url;
    }

    public function setUrl($url)
    {
        $this->url = $url;

        if (strpos($this->url, '?') !== false) {
            $this->query_delimiter = true;
        } else {
            $this->query_delimiter = false;
        }

        // parse URL into respective parts
        $url_components = $this->mbParseUrl($this->url);

        if (! $url_components) {
            // Reset URL
            $this->url = '';

            // Flush properties
            foreach ($this->components as $key) {
                if (property_exists($this, $key)) {
                    $this->$key = '';
                }
            }

            return false;
        } else {
            // Update properties
            foreach ($url_components as $key => $value) {
                if (property_exists($this, $key)) {
                    $this->$key = $value;
                }
            }

            // Flush missing components
            $missing_components = array_diff(
                array_values($this->components),
                array_keys($url_components)
            );

            foreach ($missing_components as $key) {
                if (property_exists($this, $key)) {
                    $this->$key = '';
                }
            }

            return true;
        }
    }

    public function normalize()
    {

        // URI Syntax Components
        // scheme authority path query fragment
        // @link https://tools.ietf.org/html/rfc3986#section-3

        // Scheme
        // @link https://tools.ietf.org/html/rfc3986#section-3.1

        if ($this->scheme) {
            // Converting the scheme to lower case
            $this->scheme = strtolower($this->scheme) . ':';
        }

        // Authority
        // @link https://tools.ietf.org/html/rfc3986#section-3.2

        $authority = '';
        if ($this->host) {
            $authority .= '//';

            // User Information
            // @link https://tools.ietf.org/html/rfc3986#section-3.2.1

            if ($this->user) {
                if ($this->pass) {
                    $authority .= $this->user . ':' . $this->pass . '@';
                } else {
                    $authority .= $this->user . '@';
                }
            }

            // Host
            // @link https://tools.ietf.org/html/rfc3986#section-3.2.2

            // Converting the host to lower case
            if (mb_detect_encoding($this->host) == 'UTF-8') {
                $authority .= mb_strtolower($this->host, 'UTF-8');
            } else {
                $authority .= strtolower($this->host);
            }

            // Port
            // @link https://tools.ietf.org/html/rfc3986#section-3.2.3

            // Removing the default port
            if (isset($this->default_scheme_ports[$this->scheme] )
                    && $this->port == $this->default_scheme_ports[$this->scheme]) {
                $this->port = '';
            }

            if ($this->port) {
                $authority .= ':' . $this->port;
            }
        }

        // Path
        // @link https://tools.ietf.org/html/rfc3986#section-3.3

        if ($this->path) {
            $this->path = $this->removeAdditionalPathPrefixSlashes($this->path);
            $this->path = $this->removeDotSegments($this->path);
            $this->path = $this->urlDecodeUnreservedChars($this->path);
            $this->path = $this->urlDecodeReservedSubDelimChars($this->path);
        } elseif ($this->url) {
            // Add default path only when valid URL is present
            // Adding trailing /
            $this->path = '/';
        }

        // Query
        // @link https://tools.ietf.org/html/rfc3986#section-3.4

        if ($this->query) {
            $query = $this->parseStr($this->query);

            //encodes every parameter correctly
            $qs = $this->getQuery($query);

            $this->query = '?';

            if ($this->sort_query_params) {
                ksort($qs);
            }

            foreach ($qs as $key => $val) {
                if (strlen($this->query) > 1) {
                    $this->query .= '&';
                }

                if (is_array($val)) {
                    for ($i = 0; $i < count($val); $i++) {
                        if ($i > 0) {
                            $this->query .= '&';
                        }
                        $this->query .= rawurlencode($key) . '=' . rawurlencode($val[$i]);
                    }
                } else {
                    $this->query .= rawurlencode($key) . '=' . rawurlencode($val);
                }
            }

            // Fix http_build_query adding equals sign to empty keys
            $this->query = str_replace('=&', '&', rtrim($this->query, '='));
        } else {
            if ($this->query_delimiter && ! $this->remove_empty_delimiters) {
                $this->query = '?';
            }
        }

        // Fragment
        // @link https://tools.ietf.org/html/rfc3986#section-3.5

        if ($this->fragment) {
            $this->fragment = rawurldecode($this->fragment);
            $this->fragment = rawurlencode($this->fragment);
            $this->fragment = '#' . $this->fragment;
        }

        $this->setUrl($this->scheme . $authority . $this->path . $this->query . $this->fragment);

        return $this->getUrl();
    }

    /**
     * Path segment normalization
     * https://tools.ietf.org/html/rfc3986#section-5.2.4
     */
    public function removeDotSegments($path)
    {
        $new_path = '';

        while (! empty($path)) {
             // A
            $pattern_a   = '!^(\.\./|\./)!x';
            $pattern_b_1 = '!^(/\./)!x';
            $pattern_b_2 = '!^(/\.)$!x';
            $pattern_c   = '!^(/\.\./|/\.\.)!x';
            $pattern_d   = '!^(\.|\.\.)$!x';
            $pattern_e   = '!(/*[^/]*)!x';

            if (preg_match($pattern_a, $path)) {
                // remove prefix from $path
                $path = preg_replace($pattern_a, '', $path);
            } elseif (preg_match($pattern_b_1, $path, $matches) || preg_match($pattern_b_2, $path, $matches)) {
                $path = preg_replace("!^" . $matches[1] . "!", '/', $path);
            } elseif (preg_match($pattern_c, $path, $matches)) {
                $path = preg_replace('!^' . preg_quote($matches[1], '!') . '!x', '/', $path);

                // remove the last segment and its preceding "/" (if any) from output buffer
                $new_path = preg_replace('!/([^/]+)$!x', '', $new_path);
            } elseif (preg_match($pattern_d, $path)) {
                $path = preg_replace($pattern_d, '', $path);
            } else {
                if (preg_match($pattern_e, $path, $matches)) {
                    $first_path_segment = $matches[1];

                    $path = preg_replace('/^' . preg_quote($first_path_segment, '/') . '/', '', $path, 1);

                    $new_path .= $first_path_segment;
                }
            }
        }

        return $new_path;
    }

    public function getScheme()
    {
        return $this->scheme;
    }

    /**
     * Decode unreserved characters
     *
     * @link https://tools.ietf.org/html/rfc3986#section-2.3
     */
    public function urlDecodeUnreservedChars($string)
    {
        $string = rawurldecode($string);
        $string = rawurlencode($string);
        $string = str_replace(array( '%2F', '%3A', '%40' ), array( '/', ':', '@' ), $string);

        return $string;
    }

    /**
     * Decode reserved sub-delims
     *
     * @link https://tools.ietf.org/html/rfc3986#section-2.2
     */
    public function urlDecodeReservedSubDelimChars($string)
    {
        return str_replace(
            array( '%21', '%24', '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C', '%3B', '%3D' ),
            array( '!', '$', '&', "'", '(', ')', '*', '+', ',', ';', '=' ),
            $string
        );
    }

    /**
     * Replacement for PHP's parse_string which does not deal with spaces or dots in key names
     *
     * @param string $string URL query string
     * @return array key value pairs
     */
    private function parseStr($string)
    {
        $params = array();

        $pairs = explode('&', $string);

        foreach ($pairs as $pair) {
            if (! $pair) {
                continue;
            }

            $var = explode('=', $pair, 2);
            $val = ( isset( $var[1] ) ? $var[1] : '' );

            if (isset($params[$var[0]])) {
                if (is_array($params[$var[0]])) {
                    $params[$var[0]][] = $val;
                } else {
                    $params[$var[0]] = array($params[$var[0]], $val);
                }
            } else {
                $params[$var[0]] = $val;
            }
        }

        return $params;
    }

    private function mbParseUrl($url)
    {
        $result = false;

        // Build arrays of values we need to decode before parsing
        $entities = array('%21', '%2A', '%27', '%28', '%29', '%3B', '%3A', '%40', '%26', '%3D', '%24', '%2C', '%2F', '%3F', '%23', '%5B', '%5D');
        $replacements = array('!', '*', "'", "(", ")", ";", ":", "@", "&", "=", "$", ",", "/", "?", "#", "[", "]");

        // Create encoded URL with special URL characters decoded so it can be parsed
        // All other characters will be encoded
        $encodedURL = str_replace($entities, $replacements, urlencode($url));

        // Parse the encoded URL
        $encodedParts = parse_url($encodedURL);

        // Now, decode each value of the resulting array
        if ($encodedParts) {
            foreach ($encodedParts as $key => $value) {
                $result[$key] = urldecode(str_replace($replacements, $entities, $value));
            }
        }
        return $result;
    }

    /*
     * Converts ////foo to /foo within each path segment
     */
    private function removeAdditionalPathPrefixSlashes($path)
    {
        return preg_replace('/(\/)+/', '/', $path);
    }
}


<?php namespace SleepingOwl\Apist\Methods;

use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
use SleepingOwl\Apist\Apist;
use SleepingOwl\Apist\DomCrawler\Crawler;
use SleepingOwl\Apist\Selectors\ApistSelector;

class ApistMethod
{
	/**
	 * @var Apist
	 */
	protected $resource;
	/**
	 * @var string
	 */
	protected $url;
	/**
	 * @var ApistSelector[]|ApistSelector
	 */
	protected $schemaBlueprint;
	/**
	 * @var string
	 */
	protected $method = 'GET';
	/**
	 * @var string
	 */
	protected $content;
	/**
	 * @var Crawler
	 */
	protected $crawler;
	/**
	 * @var \GuzzleHttp\Message\Response
	 */
	protected $response;

	/**
	 * @param $resource
	 * @param $url
	 * @param $schemaBlueprint
	 */
	function __construct($resource, $url, $schemaBlueprint)
	{
		$this->resource = $resource;
		$this->url = $url;
		$this->schemaBlueprint = $schemaBlueprint;
		$this->crawler = new Crawler();
	}

	/**
	 * Perform method action
	 *
	 * @param array $arguments
	 * @return array
	 */
	public function get($arguments = [])
	{
		try
		{
			$this->makeRequest($arguments);
		} catch (ConnectException $e)
		{
			$url = $e->getRequest()->getUrl();
			return $this->errorResponse($e->getCode(), $e->getMessage(), $url);
		} catch (RequestException $e)
		{
			$url = $e->getRequest()->getUrl();
			$status = $e->getCode();
			$response = $e->getResponse();
			$reason = $e->getMessage();
			if ( ! is_null($response))
			{
				$reason = $response->getReasonPhrase();
			}
			return $this->errorResponse($status, $reason, $url);
		}

		return $this->parseBlueprint($this->schemaBlueprint);
	}

	/**
	 * Make http request
	 *
	 * @param array $arguments
	 */
	protected function makeRequest($arguments = [])
	{
		$defaults = $this->getDefaultOptions();
		$arguments = array_merge($defaults, $arguments);
		$client = $this->resource->getGuzzle();
		$request = $client->createRequest($this->getMethod(), $this->url, $arguments);
		$response = $client->send($request);
		$this->setResponse($response);
		$this->setContent((string)$response->getBody());
	}

	/**
	 * @param $blueprint
	 * @param null $node
	 * @return array|string
	 */
	public function parseBlueprint($blueprint, $node = null)
	{
		if (is_null($blueprint))
		{
			return $this->content;
		}
		if ( ! is_array($blueprint))
		{
			$blueprint = $this->parseBlueprintValue($blueprint, $node);
		} else
		{
			array_walk_recursive($blueprint, function (&$value) use ($node)
			{
				$value = $this->parseBlueprintValue($value, $node);
			});
		}
		return $blueprint;
	}

	/**
	 * @param $value
	 * @param $node
	 * @return array|string
	 */
	protected function parseBlueprintValue($value, $node)
	{
		if ($value instanceof ApistSelector)
		{
			return $value->getValue($this, $node);
		}
		return $value;
	}

	/**
	 * Response with error
	 *
	 * @param $status
	 * @param $reason
	 * @param $url
	 * @return array
	 */
	protected function errorResponse($status, $reason, $url)
	{
		return [
			'url'   => $url,
			'error' => [
				'status' => $status,
				'reason' => $reason,
			]
		];
	}

	/**
	 * @return Crawler
	 */
	public function getCrawler()
	{
		return $this->crawler;
	}

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

	/**
	 * @param string $method
	 * @return $this
	 */
	public function setMethod($method)
	{
		$this->method = $method;
		return $this;
	}

	/**
	 * @param string $content
	 * @return $this
	 */
	public function setContent($content)
	{
		$this->content = $content;
		$this->crawler->addContent($content);
		return $this;
	}

	/**
	 * @return array
	 */
	protected function getDefaultOptions()
	{
		return [
			'cookies' => true
		];
	}

	/**
	 * @return Apist
	 */
	public function getResource()
	{
		return $this->resource;
	}

	/**
	 * @return \GuzzleHttp\Message\Response
	 */
	public function getResponse()
	{
		return $this->response;
	}

	/**
	 * @param \GuzzleHttp\Message\Response $response
	 */
	public function setResponse($response)
	{
		$this->response = $response;
	}

}


<?php namespace SleepingOwl\Apist\Selectors;

use SleepingOwl\Apist\Methods\ApistMethod;
use Symfony\Component\DomCrawler\Crawler;

class ResultCallback
{
	/**
	 * @var string
	 */
	protected $methodName;
	/**
	 * @var array
	 */
	protected $arguments;

	/**
	 * @param $methodName
	 * @param $arguments
	 */
	function __construct($methodName, $arguments)
	{
		$this->methodName = $methodName;
		$this->arguments = $arguments;
	}

	/**
	 * Apply result callback to the $node, provided by $method
	 *
	 * @param Crawler $node
	 * @param ApistMethod $method
	 * @return array|string
	 */
	public function apply($node, ApistMethod $method)
	{
		if (is_array($node))
		{
			return $this->applyToArray($node, $method);
		}
		if ($this->methodName === 'else')
		{
			if (is_bool($node)) $node = ! $node;
			$this->methodName = 'then';
		}

		$filter = new ApistFilter($node, $method);
		if (method_exists($filter, $this->methodName))
		{
			return call_user_func_array([
					$filter,
					$this->methodName
				], $this->arguments);
		}

		if ($this->isResourceMethod($method))
		{
			return $this->callResourceMethod($method, $node);
		}
		if ($this->isNodeMethod($node))
		{
			return $this->callNodeMethod($node);
		}
		if ($this->isGlobalFunction())
		{
			return $this->callGlobalFunction($node);
		}
		throw new \InvalidArgumentException("Method '{$this->methodName}' was not found");
	}

	protected function applyToArray($array, ApistMethod $method)
	{
		$result = [];
		foreach ($array as $node)
		{
			$result[] = $this->apply($node, $method);
		}
		return $result;
	}

	/**
	 * @param ApistMethod $method
	 * @return bool
	 */
	protected function isResourceMethod(ApistMethod $method)
	{
		return method_exists($method->getResource(), $this->methodName);
	}

	/**
	 * @param ApistMethod $method
	 * @param $node
	 * @return mixed
	 */
	protected function callResourceMethod(ApistMethod $method, $node)
	{
		$arguments = $this->arguments;
		array_unshift($arguments, $node);
		return call_user_func_array([
			$method->getResource(),
			$this->methodName
		], $arguments);
	}

	/**
	 * @param $node
	 * @return bool
	 */
	protected function isNodeMethod($node)
	{
		return method_exists($node, $this->methodName);
	}

	/**
	 * @param $node
	 * @return mixed
	 */
	protected function callNodeMethod($node)
	{
		return call_user_func_array([
			$node,
			$this->methodName
		], $this->arguments);
	}

	/**
	 * @return bool
	 */
	protected function isGlobalFunction()
	{
		return function_exists($this->methodName);
	}

	/**
	 * @param $node
	 * @return mixed
	 */
	protected function callGlobalFunction($node)
	{
		if (is_object($node))
		{
			$node = $node->text();
		}
		$arguments = $this->arguments;
		array_unshift($arguments, $node);
		return call_user_func_array($this->methodName, $arguments);
	}

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

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

}


<?php namespace SleepingOwl\Apist\Selectors;

use InvalidArgumentException;
use SleepingOwl\Apist\Methods\ApistMethod;
use Symfony\Component\DomCrawler\Crawler;

class ApistSelector
{
	/**
	 * @var string
	 */
	protected $selector;
	/**
	 * @var ResultCallback[]
	 */
	protected $resultMethodChain = [];

	/**
	 * @param $selector
	 */
	function __construct($selector)
	{
		$this->selector = $selector;
	}

	/**
	 * Get value from content by css selector
	 *
	 * @param ApistMethod $method
	 * @param Crawler $rootNode
	 * @return array|null|string|Crawler
	 */
	public function getValue(ApistMethod $method, Crawler $rootNode = null)
	{
		if (is_null($rootNode))
		{
			$rootNode = $method->getCrawler();
		}
		$result = $rootNode->filter($this->selector);
		return $this->applyResultCallbackChain($result, $method);
	}

	/**
	 * Save callable method as result callback to perform it after getValue method
	 *
	 * @param $name
	 * @param $arguments
	 * @return $this
	 */
	function __call($name, $arguments)
	{
		return $this->addCallback($name, $arguments);
	}

	/**
	 * Apply all result callbacks
	 *
	 * @param Crawler $node
	 * @param ApistMethod $method
	 * @return array|string|Crawler
	 */
	protected function applyResultCallbackChain(Crawler $node, ApistMethod $method)
	{
		if (empty($this->resultMethodChain))
		{
			$this->addCallback('text');
		}
		/** @var ResultCallback[] $traceStack */
		$traceStack = [];
		foreach ($this->resultMethodChain as $resultCallback)
		{
			try
			{
				$traceStack[] = $resultCallback;
				$node = $resultCallback->apply($node, $method);
			} catch (InvalidArgumentException $e)
			{
				if ($method->getResource()->isSuppressExceptions())
				{
					return null;
				}
				$message = $this->createExceptionMessage($e, $traceStack);
				throw new InvalidArgumentException($message, 0, $e);
			}
		}
		return $node;
	}

	/**
	 * @param $name
	 * @param $arguments
	 * @return $this
	 */
	public function addCallback($name, $arguments = [])
	{
		$resultCallback = new ResultCallback($name, $arguments);
		$this->resultMethodChain[] = $resultCallback;
		return $this;
	}

	/**
	 * @param $e
	 * @param ResultCallback[] $traceStack
	 * @return string
	 */
	protected function createExceptionMessage(\Exception $e, $traceStack)
	{
		$message = "[ filter({$this->selector})";
		foreach ($traceStack as $callback)
		{
			$message .= '->' . $callback->getMethodName() . '(';
			try
			{
				$message .= implode(', ', $callback->getArguments());
			} catch (\Exception $_e)
			{
			}
			$message .= ')';
		}
		$message .= ' ] ' . $e->getMessage();
		return $message;
	}

}


<?php namespace SleepingOwl\Apist\Yaml;

class YamlApist extends \SleepingOwl\Apist\Apist
{
	/**
	 * @var Parser
	 */
	protected $parser;

	/**
	 * @param array $options
	 */
	function __construct($file = null, $options = [])
	{
		if ( ! is_null($file))
		{
			$this->loadFromYml($file);
		}
		parent::__construct($options);
	}

	/**
	 * Load method data from yaml file
	 * @param $file
	 */
	protected function loadFromYml($file)
	{
		$this->parser = new Parser($file);
		$this->parser->load($this);
	}

	/**
	 * @param $name
	 * @param $arguments
	 * @return array
	 */
	function __call($name, $arguments)
	{
		if (is_null($this->parser))
		{
			throw new \InvalidArgumentException("Method '$name' not found.'");
		}
		$method = $this->parser->getMethod($name);
		$method = $this->parser->insertMethodArguments($method, $arguments);
		$httpMethod = isset($method['method']) ? strtoupper($method['method']) : 'GET';
		$options = isset($method['options']) ? $method['options'] : [];
		return $this->request($httpMethod, $method['url'], $method['blueprint'], $options);
	}

}


<?php namespace Camroncade\Timezone;

use DateTime;
use DateTimeZone;

class Timezone {
	public $timezoneList =  array (
	    '(UTC-11:00) Midway Island' => 'Pacific/Midway',
	    '(UTC-11:00) Samoa' => 'Pacific/Samoa',
	    '(UTC-10:00) Hawaii' => 'Pacific/Honolulu',
	    '(UTC-09:00) Alaska' => 'US/Alaska',
	    '(UTC-08:00) Pacific Time (US & Canada)' => 'America/Los_Angeles',
	    '(UTC-08:00) Tijuana' => 'America/Tijuana',
	    '(UTC-07:00) Arizona' => 'US/Arizona',
	    '(UTC-07:00) Chihuahua' => 'America/Chihuahua',
	    '(UTC-07:00) La Paz' => 'America/Chihuahua',
	    '(UTC-07:00) Mazatlan' => 'America/Mazatlan',
	    '(UTC-07:00) Mountain Time (US & Canada)' => 'US/Mountain',
	    '(UTC-06:00) Central America' => 'America/Managua',
	    '(UTC-06:00) Central Time (US & Canada)' => 'US/Central',
	    '(UTC-06:00) Guadalajara' => 'America/Mexico_City',
	    '(UTC-06:00) Mexico City' => 'America/Mexico_City',
	    '(UTC-06:00) Monterrey' => 'America/Monterrey',
	    '(UTC-06:00) Saskatchewan' => 'Canada/Saskatchewan',
	    '(UTC-05:00) Bogota' => 'America/Bogota',
	    '(UTC-05:00) Eastern Time (US & Canada)' => 'US/Eastern',
	    '(UTC-05:00) Indiana (East)' => 'US/East-Indiana',
	    '(UTC-05:00) Lima' => 'America/Lima',
	    '(UTC-05:00) Quito' => 'America/Bogota',
	    '(UTC-04:00) Atlantic Time (Canada)' => 'Canada/Atlantic',
	    '(UTC-04:30) Caracas' => 'America/Caracas',
	    '(UTC-04:00) La Paz' => 'America/La_Paz',
	    '(UTC-04:00) Santiago' => 'America/Santiago',
	    '(UTC-03:30) Newfoundland' => 'Canada/Newfoundland',
	    '(UTC-03:00) Brasilia' => 'America/Sao_Paulo',
	    '(UTC-03:00) Buenos Aires' => 'America/Argentina/Buenos_Aires',
	    '(UTC-03:00) Georgetown' => 'America/Argentina/Buenos_Aires',
	    '(UTC-03:00) Greenland' => 'America/Godthab',
	    '(UTC-02:00) Mid-Atlantic' => 'America/Noronha',
	    '(UTC-01:00) Azores' => 'Atlantic/Azores',
	    '(UTC-01:00) Cape Verde Is.' => 'Atlantic/Cape_Verde',
	    '(UTC+00:00) Casablanca' => 'Africa/Casablanca',
	    '(UTC+00:00) Edinburgh' => 'Europe/London',
	    '(UTC+00:00) Greenwich Mean Time : Dublin' => 'Etc/Greenwich',
	    '(UTC+00:00) Lisbon' => 'Europe/Lisbon',
	    '(UTC+00:00) London' => 'Europe/London',
	    '(UTC+00:00) Monrovia' => 'Africa/Monrovia',
	    '(UTC+00:00) UTC' => 'UTC',
	    '(UTC+01:00) Amsterdam' => 'Europe/Amsterdam',
	    '(UTC+01:00) Belgrade' => 'Europe/Belgrade',
	    '(UTC+01:00) Berlin' => 'Europe/Berlin',
	    '(UTC+01:00) Bern' => 'Europe/Berlin',
	    '(UTC+01:00) Bratislava' => 'Europe/Bratislava',
	    '(UTC+01:00) Brussels' => 'Europe/Brussels',
	    '(UTC+01:00) Budapest' => 'Europe/Budapest',
	    '(UTC+01:00) Copenhagen' => 'Europe/Copenhagen',
	    '(UTC+01:00) Ljubljana' => 'Europe/Ljubljana',
	    '(UTC+01:00) Madrid' => 'Europe/Madrid',
	    '(UTC+01:00) Paris' => 'Europe/Paris',
	    '(UTC+01:00) Prague' => 'Europe/Prague',
	    '(UTC+01:00) Rome' => 'Europe/Rome',
	    '(UTC+01:00) Sarajevo' => 'Europe/Sarajevo',
	    '(UTC+01:00) Skopje' => 'Europe/Skopje',
	    '(UTC+01:00) Stockholm' => 'Europe/Stockholm',
	    '(UTC+01:00) Vienna' => 'Europe/Vienna',
	    '(UTC+01:00) Warsaw' => 'Europe/Warsaw',
	    '(UTC+01:00) West Central Africa' => 'Africa/Lagos',
	    '(UTC+01:00) Zagreb' => 'Europe/Zagreb',
	    '(UTC+02:00) Athens' => 'Europe/Athens',
	    '(UTC+02:00) Bucharest' => 'Europe/Bucharest',
	    '(UTC+02:00) Cairo' => 'Africa/Cairo',
	    '(UTC+02:00) Harare' => 'Africa/Harare',
	    '(UTC+02:00) Helsinki' => 'Europe/Helsinki',
	    '(UTC+02:00) Istanbul' => 'Europe/Istanbul',
	    '(UTC+02:00) Jerusalem' => 'Asia/Jerusalem',
	    '(UTC+02:00) Kyiv' => 'Europe/Helsinki',
	    '(UTC+02:00) Pretoria' => 'Africa/Johannesburg',
	    '(UTC+02:00) Riga' => 'Europe/Riga',
	    '(UTC+02:00) Sofia' => 'Europe/Sofia',
	    '(UTC+02:00) Tallinn' => 'Europe/Tallinn',
	    '(UTC+02:00) Vilnius' => 'Europe/Vilnius',
	    '(UTC+03:00) Baghdad' => 'Asia/Baghdad',
	    '(UTC+03:00) Kuwait' => 'Asia/Kuwait',
	    '(UTC+03:00) Minsk' => 'Europe/Minsk',
	    '(UTC+03:00) Moscow' => 'Europe/Moscow',
	    '(UTC+03:00) Nairobi' => 'Africa/Nairobi',
	    '(UTC+03:00) Riyadh' => 'Asia/Riyadh',
	    '(UTC+03:00) St. Petersburg' => 'Europe/Moscow',
	    '(UTC+03:00) Volgograd' => 'Europe/Volgograd',
	    '(UTC+03:30) Tehran' => 'Asia/Tehran',
	    '(UTC+04:00) Abu Dhabi' => 'Asia/Muscat',
	    '(UTC+04:00) Baku' => 'Asia/Baku',
	    '(UTC+04:00) Muscat' => 'Asia/Muscat',
	    '(UTC+04:00) Tbilisi' => 'Asia/Tbilisi',
	    '(UTC+04:00) Yerevan' => 'Asia/Yerevan',
	    '(UTC+04:30) Kabul' => 'Asia/Kabul',
	    '(UTC+05:00) Ekaterinburg' => 'Asia/Yekaterinburg',
	    '(UTC+05:00) Islamabad' => 'Asia/Karachi',
	    '(UTC+05:00) Karachi' => 'Asia/Karachi',
	    '(UTC+05:00) Tashkent' => 'Asia/Tashkent',
	    '(UTC+05:30) Chennai' => 'Asia/Calcutta',
	    '(UTC+05:30) Kolkata' => 'Asia/Kolkata',
	    '(UTC+05:30) Mumbai' => 'Asia/Calcutta',
	    '(UTC+05:30) New Delhi' => 'Asia/Calcutta',
	    '(UTC+05:30) Sri Jayawardenepura' => 'Asia/Calcutta',
	    '(UTC+05:45) Kathmandu' => 'Asia/Katmandu',
	    '(UTC+06:00) Almaty' => 'Asia/Almaty',
	    '(UTC+06:00) Astana' => 'Asia/Dhaka',
	    '(UTC+06:00) Dhaka' => 'Asia/Dhaka',
	    '(UTC+06:00) Novosibirsk' => 'Asia/Novosibirsk',
	    '(UTC+06:30) Rangoon' => 'Asia/Rangoon',
	    '(UTC+07:00) Bangkok' => 'Asia/Bangkok',
	    '(UTC+07:00) Hanoi' => 'Asia/Bangkok',
	    '(UTC+07:00) Jakarta' => 'Asia/Jakarta',
	    '(UTC+07:00) Krasnoyarsk' => 'Asia/Krasnoyarsk',
	    '(UTC+08:00) Beijing' => 'Asia/Hong_Kong',
	    '(UTC+08:00) Chongqing' => 'Asia/Chongqing',
	    '(UTC+08:00) Hong Kong' => 'Asia/Hong_Kong',
	    '(UTC+08:00) Irkutsk' => 'Asia/Irkutsk',
	    '(UTC+08:00) Kuala Lumpur' => 'Asia/Kuala_Lumpur',
	    '(UTC+08:00) Manila' => 'Asia/Manila',
	    '(UTC+08:00) Perth' => 'Australia/Perth',
	    '(UTC+08:00) Singapore' => 'Asia/Singapore',
	    '(UTC+08:00) Taipei' => 'Asia/Taipei',
	    '(UTC+08:00) Ulaan Bataar' => 'Asia/Ulan_Bator',
	    '(UTC+08:00) Urumqi' => 'Asia/Urumqi',
	    '(UTC+09:00) Osaka' => 'Asia/Tokyo',
	    '(UTC+09:00) Sapporo' => 'Asia/Tokyo',
	    '(UTC+09:00) Seoul' => 'Asia/Seoul',
	    '(UTC+09:00) Tokyo' => 'Asia/Tokyo',
	    '(UTC+09:00) Yakutsk' => 'Asia/Yakutsk',
	    '(UTC+09:30) Adelaide' => 'Australia/Adelaide',
	    '(UTC+09:30) Darwin' => 'Australia/Darwin',
	    '(UTC+10:00) Brisbane' => 'Australia/Brisbane',
	    '(UTC+10:00) Canberra' => 'Australia/Canberra',
	    '(UTC+10:00) Guam' => 'Pacific/Guam',
	    '(UTC+10:00) Hobart' => 'Australia/Hobart',
	    '(UTC+10:00) Magadan' => 'Asia/Magadan',
	    '(UTC+10:00) Melbourne' => 'Australia/Melbourne',
	    '(UTC+10:00) Port Moresby' => 'Pacific/Port_Moresby',
	    '(UTC+10:00) Sydney' => 'Australia/Sydney',
	    '(UTC+10:00) Vladivostok' => 'Asia/Vladivostok',
	    '(UTC+12:00) Auckland' => 'Pacific/Auckland',
	    '(UTC+12:00) Fiji' => 'Pacific/Fiji',
	    '(UTC+12:00) International Date Line West' => 'Pacific/Kwajalein',
	    '(UTC+12:00) Kamchatka' => 'Asia/Kamchatka',
	    '(UTC+12:00) Marshall Is.' => 'Pacific/Fiji',
	    '(UTC+12:00) New Caledonia' => 'Asia/Magadan',
	    '(UTC+12:00) Solomon Is.' => 'Asia/Magadan',
	    '(UTC+12:00) Wellington' => 'Pacific/Auckland',
	    '(UTC+13:00) Nuku\'alofa' => 'Pacific/Tongatapu'
	);

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

	/**
	 * @param null $selected
	 * @param null $placeholder
	 * @param array $selectAttributes
	 * @param array $optionAttributes
	 *
	 * @return string
     */
	public function selectForm($selected = null, $placeholder = null, array $selectAttributes = [], array $optionAttributes = [] )
	{
		$selectAttributesString = '';
		foreach ($selectAttributes as $key => $value)
		{
			$selectAttributesString = $selectAttributesString . " " . $key . "='" . $value ."'";
		}

		$optionAttributesString = '';
		foreach ($optionAttributes as $key => $value)
		{
			$optionAttributesString = $optionAttributesString . " " . $key . "='" . $value ."'";
		}

		$string = "<select". $selectAttributesString .">\n";

		if (isset($placeholder) && (empty($selected)))
		{
		     $placeholder = "<option value='' disabled selected>{$placeholder}</option>";
		}
		else
		{
		      $placeholder = null;
		}

		$string = $string . $placeholder;
		foreach ($this->timezoneList as $key => $value)
		{
			if ($selected == $value) {
				$selectedString = "selected='" . $value . "'";
			} else {
				$selectedString = '';
			}

			$string = $string . "<option value='" . $value . "'" . $optionAttributesString . " " . $selectedString . ">" . $key . "</option>\n";
		}
		$string = $string . "</select>";

		return $string;
	}

	/**
	 * @param integer $timestamp
	 * @param string $timezone
	 * @param string $format
	 *
	 * @return string
     */
	public function convertFromUTC($timestamp, $timezone, $format = 'Y-m-d H:i:s')
	{
        $date = new DateTime($timestamp, new DateTimeZone('UTC'));

        $date->setTimezone(new DateTimeZone($timezone));

        return $date->format($format);
    }

	/**
	 * @param integer $timestamp
	 * @param string $timezone
	 * @param string $format
	 *
	 * @return string
     */
	public function convertToUTC($timestamp, $timezone, $format = 'Y-m-d H:i:s')
    {
    	$date = new DateTime($timestamp, new DateTimeZone($timezone));

        $date->setTimezone(new DateTimeZone('UTC'));

        return $date->format($format);
    }

}


<?php

namespace duncan3dc\Console;

use duncan3dc\SymfonyCLImate\Output;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Lock\LockInterface;

/**
 * @inheritdoc
 */
class Command extends \Symfony\Component\Console\Command\Command
{
    /**
     * @var bool|LockInterface $lock Whether this command uses locking or not
     */
    protected $lock = true;

    /**
     * @var int $startTime The unix timestamp that the command started running
     */
    protected $startTime = null;

    /**
     * Attempt to lock this command.
     *
     * @param Output $output The output object to use for any error messages
     *
     * @return void
     */
    public function lock(Output $output)
    {
        # If this command doesn't require locking then don't do anything
        if (!$this->lock) {
            return;
        }

        $this->lock = $this->getApplication()->getLockFactory()->createLock($this->getName());

        if (!$this->lock->acquire()) {
            $output->error("Another instance of this command (" . $this->getName() . ") is currently running");
            exit(Application::STATUS_LOCKED);
        }
    }

    /**
     * Release a lock on the command (if one was acquired).
     *
     * @return void
     */
    public function unlock()
    {
        if ($this->lock instanceof LockInterface) {
            $this->lock->release();
        }
    }

    /**
     * Set that this command should not use locking.
     *
     * @return $this
     */
    protected function doNotLock(): Command
    {
        $this->lock = false;
        return $this;
    }

    /**
     * @inheritdoc
     */
    public function run(InputInterface $input, OutputInterface $output)
    {
        $this->startTime = time();

        $timer = new Timer();

        $return = parent::run($input, $output);

        $application = $this->getApplication();
        if ($application instanceof Application && $application->showResourceInfo()) {
            $duration = $timer->getDuration();
            $memory = memory_get_peak_usage(true) / 1048576;
            $output->write("[" . $this->getName() . "] ");
            $output->write("Time: " . $duration->format() . ", ");
            $output->writeln(sprintf("Memory: %4.2fmb", $memory));
        }

        return $return;
    }

    /**
     * Check if the passed time has elapsed since the command start.
     *
     * @param int $timeout The number of seconds the command is allowed to run for
     *
     * @return bool True if the time has been exceeded and the command should end
     */
    public function timeout(int $timeout): bool
    {
        $application = $this->getApplication();
        if (!$application instanceof Application) {
            return false;
        }

        # Check if the application is currently allowing time limiting or not
        if (!$application->timeLimit()) {
            return false;
        }

        $endTime = $this->startTime + $timeout;

        return (time() > $endTime);
    }

    /**
     * @inheritdoc
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        return $this->command($input, $output);
    }

    /**
     * Execute this command.
     *
     * @param InputInterface $input
     * @param Output $output
     *
     * @return int|null Zero or null if everything went fine, otherwise the error code
     */
    protected function command(InputInterface $input, Output $output)
    {
        return parent::execute($input, $output);
    }
}


<?php

namespace duncan3dc\Console;

use duncan3dc\SymfonyCLImate\Output;
use Stecman\Component\Symfony\Console\BashCompletion\CompletionCommand;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Lock\Factory;
use Symfony\Component\Lock\Store\FlockStore;
use Symfony\Component\Lock\StoreInterface;

/**
 * {@inheritDoc}
 */
class Application extends \Symfony\Component\Console\Application
{
    /**
     * The return code used when a command cannot run as it is locked
     */
    const STATUS_LOCKED = 201;

    /**
     * The return code used when a permissions issue prevented a command from running
     */
    const STATUS_PERMISSIONS = 202;

    /** @var Factory|null */
    private $lockFactory;

    /**
     * @var string $lockPath The directory to store command locks in
     */
    protected $lockPath = "/tmp/console-locks";

    /**
     * @var boolean $timeLimit Whether the application currently allows time limits for commands
     */
    protected $timeLimit = true;

    /**
     * @var bool $showResourceInfo Whether the application should show resource info for commands
     */
    private $showResourceInfo = true;

    /**
     * {@inheritDoc}
     */
    public function __construct(string $name = "UNKNOWN", string $version = "UNKNOWN")
    {
        parent::__construct($name, $version);

        $dispatcher = new EventDispatcher();
        $this->setDispatcher($dispatcher);

        # Attempt to acquire a unique lock on the command
        $dispatcher->addListener(ConsoleEvents::COMMAND, function (ConsoleCommandEvent $event) {
            $command = $event->getCommand();
            if ($command instanceof Command) {
                $command->lock($event->getOutput());
            }
        });

        # If we achieved a lock above then unlock it after the job finishes
        $dispatcher->addListener(ConsoleEvents::TERMINATE, function (ConsoleTerminateEvent $event) {
            $command = $event->getCommand();
            if ($command instanceof Command) {
                $command->unlock();
            }
        });

        $definition = $this->getDefinition();
        $definition->addOption(new InputOption("no-time-limit", null, InputOption::VALUE_NONE, "Prevent the command from ending at the regular time limit"));
        $definition->addOption(new InputOption("show-resource-info", null, InputOption::VALUE_NONE, "Show the resource info it took to run a command"));
        $definition->addOption(new InputOption("hide-resource-info", null, InputOption::VALUE_NONE, "Hide the resource info it took to run a command"));
    }

    /**
     * Search the specified path for command classes and add them to the application.
     *
     * Files/classes must be named using CamelCase and must end in Command.
     * Each uppercase character will be converted to lowercase and preceded by a hyphen.
     * Directories will represent namespaces and each separater will be replaced with a colon.
     * eg Category/Topic/RunCommand.php will create a command called category:topic:run
     *
     * @param string $path The path to search for the command classes
     * @param string $namespace The parent namespace that these commands are in
     * @param string $suffix The class suffix to search for
     *
     * @return $this
     */
    public function loadCommands(string $path, string $namespace = "", string $suffix = "Command"): Application
    {
        $commands = [];

        # Get the realpath so we can strip it from the start of the filename
        $realpath = (string) realpath($path);

        $finder = (new Finder())->files()->in($path)->name("/[A-Z].*{$suffix}.php/");
        foreach ($finder as $file) {
            # Get the realpath of the file and ensure the class is loaded
            $filename = (string) $file->getRealPath();
            require_once $filename;

            # Convert the filename to a class
            $class = $filename;
            $class = str_replace($realpath, "", $class);
            $class = str_replace(".php", "", $class);
            $class = str_replace("/", "\\", $class);

            # Convert the class name to a command name
            $command = $class;
            if (substr($command, 0, 1) == "\\") {
                $command = substr($command, 1);
            }
            $command = (string) preg_replace_callback("/^([A-Z])(.*){$suffix}$/", function ($match) {
                return strtolower($match[1]) . $match[2];
            }, $command);
            $command = preg_replace_callback("/(\\\\)?([A-Z])/", function ($match) {
                $result = ($match[1]) ? ":" : "-";
                $result .= strtolower($match[2]);
                return $result;
            }, $command);

            $class = $namespace . $class;

            # Don't attempt create things we can't instantiate
            $reflected = new \ReflectionClass($class);
            if (!$reflected->isInstantiable()) {
                continue;
            }

            $commands[] = new $class($command);
        }

        if (count($commands) < 1) {
            throw new \InvalidArgumentException("No commands were found in the path (" . $path . ")");
        }

        $this->addCommands($commands);

        return $this;
    }

    /**
     * {@inheritDoc}
     */
    public function run(InputInterface $input = null, OutputInterface $output = null)
    {
        if ($input === null) {
            # Allow namespace contents to be listed when they are entered with a trailing colon
            if (isset($_SERVER["argv"][1]) && substr($_SERVER["argv"][1], -1) === ":") {
                # Re-create the argv contents to simulate the user running the list command
                $argv = [$_SERVER["argv"][0]];
                $argv[] = "list";
                $argv[] = substr($_SERVER["argv"][1], 0, -1);
                $argv += array_slice($_SERVER["argv"], 2, count($_SERVER["argv"]) - 2);
                $_SERVER["argv"] = $argv;
            }
            $input = new ArgvInput();
        }

        if ($output === null) {
            $output = new Output();
        }

        parent::run($input, $output);
    }

    /**
     * Override configureIO() so that we can check if the global options were set.
     *
     * {@inheritDoc}
     */
    protected function configureIO(InputInterface $input, OutputInterface $output)
    {
        parent::configureIO($input, $output);

        if ($input->hasParameterOption("--no-time-limit")) {
            $this->timeLimit = false;
        } else {
            $this->timeLimit = true;
        }

        # In quiet mode, don't display resource info by default
        if ($output->isQuiet()) {
            $this->showResourceInfo = false;
        }

        if ($input->hasParameterOption("--show-resource-info")) {
            $this->showResourceInfo = true;
        } elseif ($input->hasParameterOption("--hide-resource-info")) {
            $this->showResourceInfo = false;
        }

        if ($output->isVeryVerbose()) {
            $handler = new \NunoMaduro\Collision\Handler();
            $handler->setOutput($output);
            $provider = new \NunoMaduro\Collision\Provider(null, $handler);
            $provider->register();
            $this->setCatchExceptions(false);
        }
    }

    /**
     * Allow commands to check if the application currently allows time limiting or prevents it.
     *
     * @return bool
     */
    public function timeLimit(): bool
    {
        return $this->timeLimit;
    }

    /**
     * Allow commands to check if the application should show resource info or not.
     *
     * @return bool
     */
    public function showResourceInfo(): bool
    {
        return $this->showResourceInfo;
    }

    /**
     * Set the lock store to use.
     *
     * @param StoreInterface $store
     *
     * @return Factory
     */
    public function setLockStore(StoreInterface $store): Factory
    {
        $this->lockFactory = new Factory($store);
        return $this->lockFactory;
    }

    /**
     * Get the lock factory in use.
     *
     * @return Factory
     */
    public function getLockFactory(): Factory
    {
        if ($this->lockFactory !== null) {
            return $this->lockFactory;
        }

        if (!is_dir($this->lockPath)) {
            (new Filesystem())->mkdir($this->lockPath);
        }
        $store = new FlockStore($this->lockPath);

        return $this->setLockStore($store);
    }

    /**
     * Set the path for the directory where lock files are stored.
     *
     * @param string $path The path to use
     *
     * @return $this
     */
    public function setLockPath(string $path): Application
    {
        if (!is_dir($path)) {
            (new Filesystem())->mkdir($path);
        }
        if (!$realpath = realpath($path)) {
            throw new \InvalidArgumentException("The directory (" . $path . ") is unavailable");
        }

        $this->lockPath = $realpath;

        return $this;
    }

    /**
     * {@inheritDoc}
     */
    protected function getDefaultCommands()
    {
        $commands = parent::getDefaultCommands();
        $commands[] = new CompletionCommand();
        return $commands;
    }

    /**
     * Run another command, usually from within the currently running command.
     *
     * @param string $command The fully namespaced name of the command to run
     * @param array $options An array of options to pass to the command
     * @param InputInterface $current The input used in the parent command
     * @param OutputInterface $output The output used in the parent command
     *
     * @return int 0 if everything went fine, or an error code
     */
    public function runCommand(string $command, array $options, InputInterface $current, OutputInterface $output): int
    {
        # The first input argument must be the command name
        array_unshift($options, $command);
        $input = new ArrayInput($options);

        if (!$current->isInteractive()) {
            $input->setInteractive(false);
        }

        $command = $this->get($command);
        return $this->doRunCommand($command, $input, $output);
    }
}


<?php

namespace duncan3dc\Console;

class Duration
{
    /**
     * @var array The segments to break the duration up into
     */
    private $times = [
        "hours"     =>  3600,
        "minutes"   =>  60,
        "seconds"   =>  1,
    ];

    /**
     * @var float The duration this instance represents.
     */
    private $time;

    /**
     * Set the start time of the timer.
     *
     * @param float $time The time to start timing from
     */
    public function __construct(float $time)
    {
        $this->time = $time;
    }

    /**
     * Formats the elapsed time as a string.
     *
     * @return string
     */
    public function format(): string
    {
        foreach ($this->times as $unit => $value) {
            if ($this->time >= $value) {
                $time = floor($this->time / $value * 100) / 100;
                return "{$time} {$unit}";
            }
        }

        return round($this->time * 1000) . " ms";
    }
}


<?php

/**
 * Part of the Pagination.
 *
 * For the full copyright and license information,
 * view the LICENSE file that was distributed with this source code.
 *
 * @author  Kilte Leichnam <nwotnbm@gmail.com>
 */
namespace Kilte\Pagination;

/**
 * Pagination Class.
 */
class Pagination
{
    /**
     * Number of the first page.
     */
    const BASE_PAGE = 1;

    /**
     * First page.
     */
    const TAG_FIRST = 'first';

    /**
     * The page before the previous neighbour pages.
     */
    const TAG_LESS = 'less';

    /**
     * Previous pages.
     */
    const TAG_PREVIOUS = 'previous';

    /**
     * Current page.
     */
    const TAG_CURRENT = 'current';

    /**
     * Next pages.
     */
    const TAG_NEXT = 'next';

    /**
     * The page after the next neighbour pages.
     */
    const TAG_MORE = 'more';

    /**
     * Last page.
     */
    const TAG_LAST = 'last';

    /**
     * @var int Total Items
     */
    private $totalItems;

    /**
     * @var int Number of the current page
     */
    private $currentPage;

    /**
     * @var int Items per page
     */
    private $perPage;

    /**
     * @var int Total pages
     */
    private $totalPages;

    /**
     * @var int Offset
     */
    private $offset;

    /**
     * @var int Number of neighboring pages at the left and the right sides
     */
    private $neighbours;

    /**
     * Create instance.
     *
     * @param int $totalItems  Total items
     * @param int $currentPage Number of the current page
     * @param int $perPage     Items per page
     * @param int $neighbours  Number of neighboring pages at the left and the right sides
     *
     * @throws \LogicException
     *
     * @return self
     */
    public function __construct($totalItems, $currentPage, $perPage, $neighbours = 4)
    {
        $this->totalItems = (int) $totalItems;
        $this->currentPage = (int) $currentPage;
        $this->perPage = (int) $perPage;
        $this->neighbours = (int) $neighbours;
        if ($this->perPage <= 0) {
            throw new \LogicException('Items per page must be at least 1');
        }
        if ($this->neighbours <= 0) {
            throw new \LogicException('Number of neighboring pages must be at least 1');
        }
        if ($this->currentPage < self::BASE_PAGE) {
            $this->currentPage = self::BASE_PAGE;
        }
        $this->totalPages = (int) ceil($this->totalItems / $this->perPage);
        if ($this->currentPage > $this->totalPages) {
            $this->currentPage = $this->totalPages;
        }
        $this->offset = abs(intval($this->currentPage * $this->perPage - $this->perPage));
    }

    /**
     * Returns the offset of the list's slice for the current page.
     *
     * @return int
     */
    public function offset()
    {
        return $this->offset;
    }

    /**
     * Returns the limit of the list's slice for the current page.
     *
     * @return int
     */
    public function limit()
    {
        return $this->perPage;
    }

    /**
     * Returns number of the current page.
     *
     * @return int
     */
    public function currentPage()
    {
        return $this->currentPage;
    }

    /**
     * Returns number of the last page.
     *
     * @return int
     */
    public function totalPages()
    {
        return $this->totalPages;
    }

    /**
     * Display.
     *
     * @return array
     */
    public function build()
    {
        if ($this->totalItems == 0 || $this->totalPages == 1) {
            return array();
        }

        $output = array();

        // Previous
        $offset = $this->currentPage - 1;
        $limit = $this->currentPage - $this->neighbours;
        $limit = $limit < self::BASE_PAGE ? self::BASE_PAGE : $limit;
        for ($i = $offset; $i >= $limit; $i--) {
            $output[$i] = self::TAG_PREVIOUS;
        }

        if ($limit - self::BASE_PAGE >= 2) {
            $output[$limit - 1] = self::TAG_LESS;
        }

        // First
        if ($this->currentPage - $this->neighbours > self::BASE_PAGE) {
            $output[self::BASE_PAGE] = self::TAG_FIRST;
        }

        // Next
        $offset = $this->currentPage + 1;
        $limit = $this->currentPage + $this->neighbours;
        $limit = $limit > $this->totalPages ? $this->totalPages : $limit;
        for ($i = $offset; $i <= $limit; $i++) {
            $output[$i] = self::TAG_NEXT;
        }

        if ($this->totalPages - $limit > 0) {
            $output[$limit + 1] = self::TAG_MORE;
        }

        // Last
        if ($this->currentPage + $this->neighbours < $this->totalPages) {
            $output[$this->totalPages] = self::TAG_LAST;
        }

        // Current
        $output[$this->currentPage] = self::TAG_CURRENT;

        ksort($output);

        return $output;
    }
}


<?php

declare(strict_types=1);

namespace ChangelogGenerator;

use Symfony\Component\Console\Output\OutputInterface;
use const PHP_EOL;
use function array_filter;
use function array_map;
use function array_unique;
use function array_values;
use function count;
use function mb_strlen;
use function sprintf;
use function str_repeat;

class ChangelogGenerator
{
    /** @var IssueRepository */
    private $issueRepository;

    /** @var IssueGrouper */
    private $issueGrouper;

    public function __construct(IssueRepository $issueRepository, IssueGrouper $issueGrouper)
    {
        $this->issueRepository = $issueRepository;
        $this->issueGrouper    = $issueGrouper;
    }

    public function generate(
        ChangelogConfig $changelogConfig,
        OutputInterface $output
    ) : void {
        $issues      = $this->issueRepository->getMilestoneIssues($changelogConfig);
        $issueGroups = $this->issueGrouper->groupIssues($issues, $changelogConfig);

        $output->writeln([
            $this->buildMarkdownHeaderText($changelogConfig->getMilestone(), '='),
            '',
            sprintf('- Total issues resolved: **%s**', $this->getNumberOfIssues($issues)),
            sprintf('- Total pull requests resolved: **%s**', $this->getNumberOfPullRequests($issues)),
            sprintf('- Total contributors: **%s**', $this->getNumberOfContributors($issues)),
        ]);

        foreach ($issueGroups as $issueGroup) {
            $output->writeln([
                '',
                $this->buildMarkdownHeaderText($issueGroup->getName(), '-'),
                '',
            ]);

            foreach ($issueGroup->getIssues() as $issue) {
                $output->writeln($issue->render());
            }
        }

        if ($changelogConfig->showContributors()) {
            $this->outputContributors($output, $issues);
        }

        $output->writeln('');
    }

    /**
     * @param Issue[] $issues
     */
    private function outputContributors(OutputInterface $output, array $issues) : void
    {
        $contributors = $this->buildContributorsList($issues);

        $output->writeln([
            '',
            $this->buildMarkdownHeaderText('Contributors', '-'),
            '',
        ]);

        foreach ($contributors as $contributor) {
            $output->writeln(sprintf(' - [@%s](https://github.com/%s)', $contributor, $contributor));
        }
    }

    /**
     * @param Issue[] $issues
     *
     * @return string[]
     */
    private function buildContributorsList(array $issues) : array
    {
        $contributors = [];

        foreach ($issues as $issue) {
            foreach ($issue->getContributors() as $contributor) {
                $contributors[$contributor] = $contributor;
            }
        }

        return array_values($contributors);
    }

    private function buildMarkdownHeaderText(string $header, string $headerCharacter) : string
    {
        return sprintf(
            '%s%s%s',
            $header,
            PHP_EOL,
            str_repeat($headerCharacter, mb_strlen($header))
        );
    }

    /**
     * @param Issue[] $issues
     */
    private function getNumberOfIssues(array $issues) : int
    {
        return count(array_filter($issues, static function (Issue $issue) : bool {
            return ! $issue->isPullRequest();
        }));
    }

    /**
     * @param Issue[] $issues
     */
    private function getNumberOfPullRequests(array $issues) : int
    {
        return count(array_filter($issues, static function (Issue $issue) : bool {
            return $issue->isPullRequest();
        }));
    }

    /**
     * @param Issue[] $issues
     */
    private function getNumberOfContributors(array $issues) : int
    {
        return count(array_unique(array_map(static function (Issue $issue) : string {
            return $issue->getUser();
        }, $issues)));
    }
}


<?php

declare(strict_types=1);

namespace ChangelogGenerator;

use function array_filter;
use function array_intersect;
use function count;
use function implode;
use function strpos;

class IssueGrouper
{
    /**
     * @param Issue[] $issues
     *
     * @return IssueGroup[]
     */
    public function groupIssues(array $issues, ChangelogConfig $changelogConfig) : array
    {
        $this->linkIssues($issues);

        return $this->groupIssuesByLabels($issues, $changelogConfig);
    }

    /**
     * @param Issue[] $issues
     *
     * @return IssueGroup[]
     */
    private function groupIssuesByLabels(array $issues, ChangelogConfig $changelogConfig) : array
    {
        $issueGroups = [];

        foreach ($this->getIssuesToGroup($issues) as $issue) {
            $groupName = $this->generateIssueGroupName($issue, $changelogConfig);

            if (! isset($issueGroups[$groupName])) {
                $issueGroups[$groupName] = new IssueGroup($groupName);
            }

            $issueGroups[$groupName]->addIssue($issue);
        }

        return $issueGroups;
    }

    private function generateIssueGroupName(Issue $issue, ChangelogConfig $changelogConfig) : string
    {
        $labelFilters = $changelogConfig->getLabels();

        if (count($labelFilters) === 0) {
            $labels = $issue->getLabels();
        } else {
            $labels = array_intersect($issue->getLabels(), $labelFilters);
        }

        return implode(',', $labels);
    }

    /**
     * @param Issue[] $issues
     *
     * @return Issue[]
     */
    private function getIssuesToGroup(array $issues) : array
    {
        return array_filter($issues, static function (Issue $issue) : bool {
            return (! $issue->isPullRequest() && $issue->getLinkedPullRequest() !== null) === false;
        });
    }

    /**
     * @param Issue[] $issues
     */
    private function linkIssues(array $issues) : void
    {
        foreach ($issues as $issue) {
            if (! $issue->isPullRequest()) {
                continue;
            }

            foreach ($issues as $i) {
                if ($i->isPullRequest() || strpos($issue->getBody(), '#' . $i->getNumber()) === false) {
                    continue;
                }

                $i->setLinkedPullRequest($issue);
                $issue->setLinkedIssue($i);
            }
        }
    }
}


<?php

namespace Spatie\FailedJobMonitor;

use Illuminate\Support\ServiceProvider as IlluminateServiceProvider;

class FailedJobMonitorServiceProvider extends IlluminateServiceProvider
{
    /**
     * Bootstrap the application services.
     */
    public function boot()
    {
        $this->publishes([
            __DIR__.'/../config/failed-job-monitor.php' => config_path('failed-job-monitor.php'),
        ], 'config');

        $this->app->make(FailedJobNotifier::class)->register();
    }

    /**
     * Register the application services.
     */
    public function register()
    {
        $this->mergeConfigFrom(
            __DIR__.'/../config/failed-job-monitor.php',
            'failed-job-monitor'
        );

        $this->app->singleton(FailedJobNotifier::class);
    }
}


<?php

namespace Spatie\HttpStatusCheck;

use Spatie\Crawler\CrawlObserver;
use Psr\Http\Message\UriInterface;
use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Exception\RequestException;
use Symfony\Component\Console\Output\OutputInterface;

class CrawlLogger extends CrawlObserver
{
    const UNRESPONSIVE_HOST = 'Host did not respond';

    /**
     * @var \Symfony\Component\Console\Output\OutputInterface
     */
    protected $consoleOutput;

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

    /**
     * @var string|null
     */
    protected $outputFile = null;

    /**
     * @param \Symfony\Component\Console\Output\OutputInterface $consoleOutput
     */
    public function __construct(OutputInterface $consoleOutput)
    {
        $this->consoleOutput = $consoleOutput;
    }

    /**
     * Called when the crawl will crawl the url.
     *
     * @param \Psr\Http\Message\UriInterface $url
     */
    public function willCrawl(UriInterface $url)
    {
    }

    /**
     * Called when the crawl has ended.
     */
    public function finishedCrawling()
    {
        $this->consoleOutput->writeln('');
        $this->consoleOutput->writeln('Crawling summary');
        $this->consoleOutput->writeln('----------------');

        ksort($this->crawledUrls);

        foreach ($this->crawledUrls as $statusCode => $urls) {
            $colorTag = $this->getColorTagForStatusCode($statusCode);

            $count = count($urls);

            if (is_numeric($statusCode)) {
                $this->consoleOutput->writeln("<{$colorTag}>Crawled {$count} url(s) with statuscode {$statusCode}</{$colorTag}>");
            }

            if ($statusCode == static::UNRESPONSIVE_HOST) {
                $this->consoleOutput->writeln("<{$colorTag}>{$count} url(s) did have unresponsive host(s)</{$colorTag}>");
            }
        }

        $this->consoleOutput->writeln('');
    }

    protected function getColorTagForStatusCode(string $code): string
    {
        if ($this->startsWith($code, '2')) {
            return 'info';
        }

        if ($this->startsWith($code, '3')) {
            return 'comment';
        }

        return 'error';
    }

    /**
     * @param string|null $haystack
     * @param string|array $needles
     *
     * @return bool
     */
    public function startsWith($haystack, $needles): bool
    {
        foreach ((array) $needles as $needle) {
            if ($needle != '' && substr($haystack, 0, strlen($needle)) === (string) $needle) {
                return true;
            }
        }

        return false;
    }

    /**
     * Set the filename to write the output log.
     *
     * @param string $filename
     */
    public function setOutputFile($filename)
    {
        $this->outputFile = $filename;
    }

    public function crawled(
        UriInterface $url,
        ResponseInterface $response,
        ?UriInterface $foundOnUrl = null
    ) {
        $statusCode = $response->getStatusCode();

        $reason = $response->getReasonPhrase();

        $colorTag = $this->getColorTagForStatusCode($statusCode);

        $timestamp = date('Y-m-d H:i:s');

        $message = "{$statusCode} {$reason} - ".(string) $url;

        if ($this->outputFile && $colorTag === 'error') {
            file_put_contents($this->outputFile, $message.PHP_EOL, FILE_APPEND);
        }

        $this->consoleOutput->writeln("<{$colorTag}>[{$timestamp}] {$message}</{$colorTag}>");

        $this->crawledUrls[$statusCode][] = $url;
    }

    public function crawlFailed(
        UriInterface $url,
        RequestException $requestException,
        ?UriInterface $foundOnUrl = null
    ) {
        $statusCode = self::UNRESPONSIVE_HOST;

        $reason = $requestException->getResponse()
            ? $requestException->getResponse()->getReasonPhrase()
            : $requestException->getMessage();

        $colorTag = $this->getColorTagForStatusCode($statusCode);

        $timestamp = date('Y-m-d H:i:s');

        $message = "{$statusCode}: {$reason} - ".(string) $url;

        if ($foundOnUrl) {
            $message .= " (found on {$foundOnUrl})";
        }

        if ($this->outputFile) {
            file_put_contents($this->outputFile, $message.PHP_EOL, FILE_APPEND);
        }

        $this->consoleOutput->writeln("<{$colorTag}>[{$timestamp}] {$message}</{$colorTag}>");

        $this->crawledUrls[$statusCode][] = $url;
    }
}


<?php

namespace Spatie\HttpStatusCheck;

use Spatie\Crawler\Crawler;
use GuzzleHttp\RequestOptions;
use Spatie\Crawler\CrawlAllUrls;
use Spatie\Crawler\CrawlInternalUrls;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;

class ScanCommand extends Command
{
    protected function configure()
    {
        $this->setName('scan')
            ->setDescription('Check the http status code of all links on a website.')
            ->addArgument(
                'url',
                InputArgument::REQUIRED,
                'The url to check'
            )
            ->addOption(
                'concurrency',
                'c',
                InputOption::VALUE_REQUIRED,
                'The amount of concurrent connections to use',
                10
            )
            ->addOption(
                'output',
                'o',
                InputOption::VALUE_REQUIRED,
                'Log all non-2xx and non-3xx responses in this file'
            )
            ->addOption(
                'dont-crawl-external-links',
                'x',
                InputOption::VALUE_NONE,
                'Dont crawl external links'
            )
            ->addOption(
                'timeout',
                't',
                InputOption::VALUE_OPTIONAL,
                'The maximum number of seconds the request can take',
                10
            )
            ->addOption(
                'user-agent',
                'u',
                InputOption::VALUE_OPTIONAL,
                'The User Agent to pass for the request',
                ''
            )
            ->addOption(
                'skip-verification',
                's',
                InputOption::VALUE_NONE,
                'Skips checking the SSL certificate'
            )
            ->addOption(
                'options',
                'opt',
                InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL,
                'Additional options to the request',
                []
            )
            ->addOption(
                'ignore-robots',
                null,
                InputOption::VALUE_NONE,
                'Ignore robots checks'
            );
    }

    /**
     * @param \Symfony\Component\Console\Input\InputInterface $input
     * @param \Symfony\Component\Console\Output\OutputInterface $output
     *
     * @return int
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $baseUrl = $input->getArgument('url');
        $crawlProfile = $input->getOption('dont-crawl-external-links') ? new CrawlInternalUrls($baseUrl) : new CrawlAllUrls();

        $output->writeln("Start scanning {$baseUrl}");
        $output->writeln('');

        $crawlLogger = new CrawlLogger($output);

        if ($input->getOption('output')) {
            $outputFile = $input->getOption('output');

            if (file_exists($outputFile)) {
                $helper = $this->getHelper('question');
                $question = new ConfirmationQuestion(
                    "The output file `{$outputFile}` already exists. Overwrite it? (y/n)",
                    false
                );

                if (! $helper->ask($input, $output, $question)) {
                    $output->writeln('Aborting...');

                    return 0;
                }
            }

            $crawlLogger->setOutputFile($input->getOption('output'));
        }

        $clientOptions = [
            RequestOptions::TIMEOUT => $input->getOption('timeout'),
            RequestOptions::VERIFY => ! $input->getOption('skip-verification'),
            RequestOptions::ALLOW_REDIRECTS => false,
        ];

        $clientOptions = array_merge($clientOptions, $input->getOption('options'));

        if ($input->getOption('user-agent')) {
            $clientOptions[RequestOptions::HEADERS]['user-agent'] = $input->getOption('user-agent');
        }

        $crawler = Crawler::create($clientOptions)
            ->setConcurrency($input->getOption('concurrency'))
            ->setCrawlObserver($crawlLogger)
            ->setCrawlProfile($crawlProfile);

        if ($input->getOption('ignore-robots')) {
            $crawler->ignoreRobots();
        }

        $crawler->startCrawling($baseUrl);

        return 0;
    }
}


<?php

namespace Arrilot\BitrixModels\Models;

use Arrilot\BitrixModels\Models\Traits\ModelEventsTrait;
use Arrilot\BitrixModels\Queries\BaseQuery;
use Illuminate\Support\Collection;
use LogicException;

abstract class BaseBitrixModel extends ArrayableModel
{
    use ModelEventsTrait;

    /**
     * @var string|null
     */
    protected static $currentLanguage = null;

    /**
     * Array of model fields keys that needs to be saved with next save().
     *
     * @var array
     */
    protected $fieldsSelectedForSave = [];

    /**
     * Array of errors that are passed to model events.
     *
     * @var array
     */
    protected $eventErrors = [];

    /**
     * Have fields been already fetched from DB?
     *
     * @var bool
     */
    protected $fieldsAreFetched = false;

    /**
     * Internal part of create to avoid problems with static and inheritance
     *
     * @param $fields
     *
     * @throws LogicException
     *
     * @return static|bool
     */
    protected static function internalCreate($fields)
    {
        throw new LogicException('internalCreate is not implemented');
    }

    /**
     * Save model to database.
     *
     * @param array $selectedFields save only these fields instead of all.
     *
     * @return bool
     */
    abstract public function save($selectedFields = []);

    /**
     * Determine whether the field should be stopped from passing to "update".
     *
     * @param string $field
     * @param mixed  $value
     * @param array  $selectedFields
     *
     * @return bool
     */
    abstract protected function fieldShouldNotBeSaved($field, $value, $selectedFields);

    /**
     * Get all model attributes from cache or database.
     *
     * @return array
     */
    public function get()
    {
        $this->load();

        return $this->fields;
    }

    /**
     * Load model fields from database if they are not loaded yet.
     *
     * @return $this
     */
    public function load()
    {
        if (!$this->fieldsAreFetched) {
            $this->refresh();
        }

        return $this;
    }

    /**
     * Get model fields from cache or database.
     *
     * @return array
     */
    public function getFields()
    {
        if ($this->fieldsAreFetched) {
            return $this->fields;
        }

        return $this->refreshFields();
    }

    /**
     * Refresh model from database and place data to $this->fields.
     *
     * @return array
     */
    public function refresh()
    {
        return $this->refreshFields();
    }

    /**
     * Refresh model fields and save them to a class field.
     *
     * @return array
     */
    public function refreshFields()
    {
        if ($this->id === null) {
            $this->original = [];
            return $this->fields = [];
        }

        $this->fields = static::query()->getById($this->id)->fields;
        $this->original = $this->fields;

        $this->fieldsAreFetched = true;

        return $this->fields;
    }

    /**
     * Fill model fields if they are already known.
     * Saves DB queries.
     *
     * @param array $fields
     *
     * @return void
     */
    public function fill($fields)
    {
        if (!is_array($fields)) {
            return;
        }

        if (isset($fields['ID'])) {
            $this->id = $fields['ID'];
        }

        $this->fields = $fields;

        $this->fieldsAreFetched = true;

        if (method_exists($this, 'afterFill')) {
            $this->afterFill();
        }

        $this->original = $this->fields;
    }

    /**
     * Set current model id.
     *
     * @param $id
     */
    protected function setId($id)
    {
        $this->id = $id;
        $this->fields['ID'] = $id;
    }

    /**
     * Create new item in database.
     *
     * @param $fields
     *
     * @throws LogicException
     *
     * @return static|bool
     */
    public static function create($fields)
    {
        return static::internalCreate($fields);
    }

    /**
     * Get count of items that match $filter.
     *
     * @param array $filter
     *
     * @return int
     */
    public static function count(array $filter = [])
    {
        return static::query()->filter($filter)->count();
    }

    /**
     * Get item by its id.
     *
     * @param int $id
     *
     * @return static|bool
     */
    public static function find($id)
    {
        return static::query()->getById($id);
    }

    /**
     * Update model.
     *
     * @param array $fields
     *
     * @return bool
     */
    public function update(array $fields = [])
    {
        $keys = [];
        foreach ($fields as $key => $value) {
            array_set($this->fields, $key, $value);
            $keys[] = $key;
        }

        return $this->save($keys);
    }

    /**
     * Create an array of fields that will be saved to database.
     *
     * @param $selectedFields
     *
     * @return array|null
     */
    protected function normalizeFieldsForSave($selectedFields)
    {
        $fields = [];
        if ($this->fields === null) {
            return [];
        }

        foreach ($this->fields as $field => $value) {
            if (!$this->fieldShouldNotBeSaved($field, $value, $selectedFields)) {
                $fields[$field] = $value;
            }
        }

        return $fields ?: null;
    }

    /**
     * Instantiate a query object for the model.
     *
     * @throws LogicException
     *
     * @return BaseQuery
     */
    public static function query()
    {
        throw new LogicException('public static function query() is not implemented');
    }

    /**
     * Handle dynamic static method calls into a new query.
     *
     * @param  string  $method
     * @param  array  $parameters
     * @return mixed
     */
    public static function __callStatic($method, $parameters)
    {
        return static::query()->$method(...$parameters);
    }

    /**
     * Returns the value of a model property.
     *
     * This method will check in the following order and act accordingly:
     *
     *  - a property defined by a getter: return the getter result
     *
     * Do not call this method directly as it is a PHP magic method that
     * will be implicitly called when executing `$value = $component->property;`.
     * @param string $name the property name
     * @return mixed the property value
     * @throws \Exception if the property is not defined
     * @see __set()
     */
    public function __get($name)
    {
        // Если уже сохранен такой релейшн, то возьмем его
        if (isset($this->related[$name]) || array_key_exists($name, $this->related)) {
            return $this->related[$name];
        }

        // Если нет сохраненных данных, ищем подходящий геттер
        $getter = $name;
        if (method_exists($this, $getter)) {
            // read property, e.g. getName()
            $value = $this->$getter();

            // Если геттер вернул запрос, значит $name - релейшен. Нужно выполнить запрос и сохранить во внутренний массив
            if ($value instanceof BaseQuery) {
                $this->related[$name] = $value->findFor();
                return $this->related[$name];
            }
        }

        throw new \Exception('Getting unknown property: ' . get_class($this) . '::' . $name);
    }

    /**
     * Получить запрос для релейшена по имени
     * @param string $name - название релейшена, например `orders` для релейшена, определенного через метод getOrders()
     * @param bool $throwException - кидать ли исключение в случае ошибки
     * @return BaseQuery - запрос для подгрузки релейшена
     * @throws \InvalidArgumentException
     */
    public function getRelation($name, $throwException = true)
    {
        $getter = $name;
        try {
            $relation = $this->$getter();
        } catch (\BadMethodCallException $e) {
            if ($throwException) {
                throw new \InvalidArgumentException(get_class($this) . ' has no relation named "' . $name . '".', 0, $e);
            }

            return null;
        }

        if (!$relation instanceof BaseQuery) {
            if ($throwException) {
                throw new \InvalidArgumentException(get_class($this) . ' has no relation named "' . $name . '".');
            }

            return null;
        }

        return $relation;
    }

    /**
     * Reset event errors back to default.
     */
    protected function resetEventErrors()
    {
        $this->eventErrors = [];
    }

    /**
     * Declares a `has-one` relation.
     * The declaration is returned in terms of a relational [[BaseQuery]] instance
     * through which the related record can be queried and retrieved back.
     *
     * A `has-one` relation means that there is at most one related record matching
     * the criteria set by this relation, e.g., a customer has one country.
     *
     * For example, to declare the `country` relation for `Customer` class, we can write
     * the following code in the `Customer` class:
     *
     * ```php
     * public function country()
     * {
     *     return $this->hasOne(Country::className(), 'ID', 'PROPERTY_COUNTRY');
     * }
     * ```
     *
     * Note that in the above, the 'ID' key in the `$link` parameter refers to an attribute name
     * in the related class `Country`, while the 'PROPERTY_COUNTRY' value refers to an attribute name
     * in the current BaseBitrixModel class.
     *
     * Call methods declared in [[BaseQuery]] to further customize the relation.
     *
     * @param string $class the class name of the related record
     * @param string $foreignKey
     * @param string $localKey
     * @return BaseQuery the relational query object.
     */
    public function hasOne($class, $foreignKey, $localKey = 'ID')
    {
        return $this->createRelationQuery($class, $foreignKey, $localKey, false);
    }

    /**
     * Declares a `has-many` relation.
     * The declaration is returned in terms of a relational [[BaseQuery]] instance
     * through which the related record can be queried and retrieved back.
     *
     * A `has-many` relation means that there are multiple related records matching
     * the criteria set by this relation, e.g., a customer has many orders.
     *
     * For example, to declare the `orders` relation for `Customer` class, we can write
     * the following code in the `Customer` class:
     *
     * ```php
     * public function orders()
     * {
     *     return $this->hasMany(Order::className(), 'PROPERTY_COUNTRY_VALUE', 'ID');
     * }
     * ```
     *
     * Note that in the above, the 'customer_id' key in the `$link` parameter refers to
     * an attribute name in the related class `Order`, while the 'id' value refers to
     * an attribute name in the current BaseBitrixModel class.
     *
     * Call methods declared in [[BaseQuery]] to further customize the relation.
     *
     * @param string $class the class name of the related record
     * @param string $foreignKey
     * @param string $localKey
     * @return BaseQuery the relational query object.
     */
    public function hasMany($class, $foreignKey, $localKey = 'ID')
    {
        return $this->createRelationQuery($class, $foreignKey, $localKey, true);
    }

    /**
     * Creates a query instance for `has-one` or `has-many` relation.
     * @param string $class the class name of the related record.
     * @param string $foreignKey
     * @param string $localKey
     * @param bool $multiple whether this query represents a relation to more than one record.
     * @return BaseQuery the relational query object.
     * @see hasOne()
     * @see hasMany()
     */
    protected function createRelationQuery($class, $foreignKey, $localKey, $multiple)
    {
        /* @var $class BaseBitrixModel */
        /* @var $query BaseQuery */
        $query = $class::query();
        $query->foreignKey = $localKey;
        $query->localKey = $foreignKey;
        $query->primaryModel = $this;
        $query->multiple = $multiple;
        return $query;
    }

    /**
     * Записать модели как связанные
     * @param string $name - название релейшена
     * @param Collection|BaseBitrixModel $records - связанные модели
     * @see getRelation()
     */
    public function populateRelation($name, $records)
    {
        $this->related[$name] = $records;
    }

    /**
     * Setter for currentLanguage.
     *
     * @param $language
     * @return mixed
     */
    public static function setCurrentLanguage($language)
    {
        self::$currentLanguage = $language;
    }

    /**
     * Getter for currentLanguage.
     *
     * @return string
     */
    public static function getCurrentLanguage()
    {
        return self::$currentLanguage;
    }

    /**
     * Get value from language field according to current language.
     *
     * @param $field
     * @return mixed
     */
    protected function getValueFromLanguageField($field)
    {
        $key = $field . '_' . $this->getCurrentLanguage();

        return isset($this->fields[$key]) ? $this->fields[$key] : null;
    }
}


<?php

namespace Arrilot\BitrixModels;

use Arrilot\BitrixBlade\BladeProvider;
use Arrilot\BitrixModels\Debug\IlluminateQueryDebugger;
use Arrilot\BitrixModels\Models\BaseBitrixModel;
use Arrilot\BitrixModels\Models\EloquentModel;
use Bitrix\Main\Config\Configuration;
use DB;
use Illuminate\Container\Container;
use Illuminate\Events\Dispatcher;
use Illuminate\Pagination\Paginator;
use Illuminate\Database\Capsule\Manager as Capsule;

class ServiceProvider
{
    public static $illuminateDatabaseIsUsed = false;

    /**
     * Register the service provider.
     *
     * @return void
     */
    public static function register()
    {
        BaseBitrixModel::setCurrentLanguage(strtoupper(LANGUAGE_ID));
        self::bootstrapIlluminatePagination();
    }

    /**
     * Register eloquent.
     *
     * @return void
     */
    public static function registerEloquent()
    {
        $capsule = self::bootstrapIlluminateDatabase();
        class_alias(Capsule::class, 'DB');

        if ($_COOKIE["show_sql_stat"] == "Y") {
            Capsule::enableQueryLog();

            $em = \Bitrix\Main\EventManager::getInstance();
            $em->addEventHandler('main', 'OnAfterEpilog', [IlluminateQueryDebugger::class, 'onAfterEpilogHandler']);
        }

        static::addEventListenersForHelpersHighloadblockTables($capsule);
    }

    /**
     * Bootstrap illuminate/pagination
     */
    protected static function bootstrapIlluminatePagination()
    {
        if (class_exists(BladeProvider::class)) {
            Paginator::viewFactoryResolver(function () {
                return BladeProvider::getViewFactory();
            });
        }

        Paginator::$defaultView = 'pagination.default';
        Paginator::$defaultSimpleView = 'pagination.simple-default';

        Paginator::currentPathResolver(function () {
            return $GLOBALS['APPLICATION']->getCurPage();
        });

        Paginator::currentPageResolver(function ($pageName = 'page') {
            $page = $_GET[$pageName];

            if (filter_var($page, FILTER_VALIDATE_INT) !== false && (int)$page >= 1) {
                return $page;
            }

            return 1;
        });
    }

    /**
     * Bootstrap illuminate/database
     * @return Capsule
     */
    protected static function bootstrapIlluminateDatabase()
    {
        $config = self::getBitrixDbConfig();

        $capsule = new Capsule(self::instantiateServiceContainer());
        $capsule->addConnection([
            'driver'    => 'mysql',
            'host'      => $config['host'],
            'database'  => $config['database'],
            'username'  => $config['login'],
            'password'  => $config['password'],
            'charset'   => 'utf8',
            'collation' => 'utf8_unicode_ci',
            'prefix'    => '',
            'strict'    => false,
        ]);

        if (class_exists(Dispatcher::class)) {
            $capsule->setEventDispatcher(new Dispatcher());
        }

        $capsule->setAsGlobal();
        $capsule->bootEloquent();

        static::$illuminateDatabaseIsUsed = true;

        return $capsule;
    }

    /**
     * Instantiate service container if it's not instantiated yet.
     */
    protected static function instantiateServiceContainer()
    {
        $container = Container::getInstance();

        if (!$container) {
            $container = new Container();
            Container::setInstance($container);
        }

        return $container;
    }

    /**
     * Get bitrix database configuration array.
     *
     * @return array
     */
    protected static function getBitrixDbConfig()
    {
        $config = Configuration::getInstance();
        $connections = $config->get('connections');

        return $connections['default'];
    }

    /**
     * Для множественных полей Highload блоков битрикс использует вспомогательные таблицы.
     * Данный метод вешает обработчики на eloquent события добавления и обновления записей которые будут актуализировать и эти таблицы.
     *
     * @param Capsule $capsule
     */
    private static function addEventListenersForHelpersHighloadblockTables(Capsule $capsule)
    {
        $dispatcher = $capsule->getEventDispatcher();
        if (!$dispatcher) {
            return;
        }

        $dispatcher->listen(['eloquent.deleted: *'], function($event, $payload) {
            /** @var EloquentModel $model */
            $model = $payload[0];
            if (empty($model->multipleHighloadBlockFields)) {
                return;
            }

            $modelTable = $model->getTable();
            foreach ($model->multipleHighloadBlockFields as $multipleHighloadBlockField) {
                if (!empty($model['ID'])) {
                    $tableName = $modelTable.'_'.strtolower($multipleHighloadBlockField);
                    DB::table($tableName)->where('ID', $model['ID'])->delete();
                }
            }
        });

        $dispatcher->listen(['eloquent.updated: *', 'eloquent.created: *'], function($event, $payload) {
            /** @var EloquentModel $model */
            $model = $payload[0];
            if (empty($model->multipleHighloadBlockFields)) {
                return;
            }

            $dirty = $model->getDirty();
            $modelTable = $model->getTable();
            foreach ($model->multipleHighloadBlockFields as $multipleHighloadBlockField) {
                if (isset($dirty[$multipleHighloadBlockField]) && !empty($model['ID'])) {
                    $tableName = $modelTable.'_'.strtolower($multipleHighloadBlockField);

                    if (substr($event, 0, 16) === 'eloquent.updated') {
                        DB::table($tableName)->where('ID', $model['ID'])->delete();
                    }

                    $unserializedValues = unserialize($dirty[$multipleHighloadBlockField]);
                    if (!$unserializedValues) {
                        continue;
                    }

                    $newRows = [];
                    foreach ($unserializedValues as $unserializedValue) {
                        $newRows[] = [
                            'ID' => $model['ID'],
                            'VALUE' => $unserializedValue,
                        ];
                    }

                    if ($newRows) {
                        DB::table($tableName)->insert($newRows);
                    }
                }
            }
        });
    }
}