<?php namespace Andreyco\Instagram;

use Andreyco\Instagram\Exception\AuthException;
use Andreyco\Instagram\Exception\CurlException;
use Andreyco\Instagram\Exception\InvalidParameterException;
use Andreyco\Instagram\Exception\PaginationException;

/**
 * Instagram API class
 * API Documentation: http://instagram.com/developer/
 * Class Documentation: https://github.com/Andreyco/Instagram
 *
 * @author Andrej Badin
 * @since 8.7.2014
 * @copyright Andrej Badin - 2014
 * @version 3.0.0
 * @license BSD http://www.opensource.org/licenses/bsd-license.php
 */
class Client {

    /**
     * The API base URL
     */
    const API_URL = 'https://api.instagram.com/v1/';

    /**
     * The API OAuth URL
     */
    const API_OAUTH_URL = 'https://api.instagram.com/oauth/authorize';

    /**
     * The OAuth token URL
     */
    const API_OAUTH_TOKEN_URL = 'https://api.instagram.com/oauth/access_token';

    /**
     * The Instagram API Key
     *
     * @var string
     */
    private $_apikey;

    /**
     * The Instagram OAuth API secret
     *
     * @var string
     */
    private $_apisecret;

    /**
     * The callback URL
     *
     * @var string
     */
    private $_callbackurl;

    /**
     * The user access token
     *
     * @var string
     */
    private $_accesstoken;

    /**
     * Available scopes
     *
     * @var array
     */
    private $_scope = array();
    private $_defaulScope = array('basic');
    private $_availableScope = array(
      'basic',
      'public_content',
      'follower_list',
      'comments',
      'relationships',
      'likes',
    );

    /**
     * Available actions
     *
     * @var array
     */
    private $_actions = array('follow', 'unfollow', 'block', 'unblock', 'approve', 'deny');

    /**
     * Whether a signed header should be used.
     *
     * @var bool
     */
    private $_enforceSignedRequests = false;

    /**
     * Default constructor
     *
     * @param array|string $config          Instagram configuration data
     * @return void
     */
    public function __construct($config) {
        // if you want to access user data
        if (true === is_array($config)) {
            $this->setApiKey($config['apiKey']);
            $this->setApiSecret($config['apiSecret']);
            $this->setApiCallback($config['apiCallback']);
            $scope = empty($config['scope']) ? $this->_defaulScope : $config['scope'];
            $this->setScope($scope);
            return;
        }

        // if you only want to access public data
        if (true === is_string($config)) {
            $this->setApiKey($config);
            return;
        }

        throw new InvalidParameterException('Error: __construct() -  Invalid configuration data for client.');
    }

    /**
     * Generates the OAuth login URL
     *
     * @param array [optional] $scope       Requesting additional permissions
     * @return string                       Instagram OAuth login URL
     */
    public function getLoginUrl($scope = array(), $state = null) {
        $scope = $this->mergeScope($scope);

        $state = is_string($state) ? "&state={$state}" : '';

        return self::API_OAUTH_URL .
            '?client_id=' . $this->getApiKey() .
            '&redirect_uri=' . urlencode($this->getApiCallback()) .
            '&scope=' . implode('+', $scope) .
            '&response_type=code' .
            $state;
    }

    /**
     * Search for a user
     *
     * @param string $name                  Instagram username
     * @param integer [optional] $limit     Limit of returned results
     * @return mixed
     */
    public function searchUser($name, $limit = 20) {
        return $this->_makeCall('users/search', array('q' => $name, 'count' => $limit));
    }

    /**
     * Get user info
     *
     * @param integer [optional] $id        Instagram user ID
     * @return mixed
     */
    public function getUser($id = 'self') {
        return $this->_makeCall('users/' . $id);
    }

    /**
     * Get user recent media
     *
     * @param integer [optional] $id        Instagram user ID
     * @param integer [optional] $limit     Limit of returned results
     * @return mixed
     */
    public function getUserMedia($id = 'self', $limit = 20) {
        return $this->_makeCall('users/' . $id . '/media/recent', array('count' => $limit));
    }

    /**
     * Get the liked photos of a user
     *
     * @param integer [optional] $limit     Limit of returned results
     * @return mixed
     */
    public function getUserLikes($limit = 20) {
        return $this->_makeCall('users/self/media/liked', array('count' => $limit));
    }

    /**
     * Get the list of users authenticated user follows
     *
     * @param integer [optional] $limit     Limit of returned results
     * @return mixed
     */
    public function getSelfFollows() {
        return $this->_makeCall('users/self/follows');
    }

    /**
     * Get the list of users authenticated user is followed by
     *
     * @param integer [optional] $id        Instagram user ID
     * @param integer [optional] $limit     Limit of returned results
     * @return mixed
     */
    public function getSelfFollowedBy() {
        return $this->_makeCall('users/self/followed-by');
    }

    /**
     * Get information about a relationship to another user
     *
     * @param integer $id                   Instagram user ID
     * @return mixed
     */
    public function getUserRelationship($id) {
        return $this->_makeCall('users/' . $id . '/relationship');
    }

    /**
     * Modify the relationship between the current user and the target user
     *
     * @param string $action                Action command (follow/unfollow/block/unblock/approve/deny)
     * @param integer $user                 Target user ID
     * @return mixed
     */
    public function modifyRelationship($action, $user) {
        if (true === in_array($action, $this->_actions) && isset($user)) {
            return $this->_makeCall('users/' . $user . '/relationship', array('action' => $action), 'POST');
        }
        throw new InvalidParameterException('Error: modifyRelationship() - This method requires an action command and the target user id.');
    }

    /**
     * Search media by its location
     *
     * @param float $lat                    Latitude of the center search coordinate
     * @param float $lng                    Longitude of the center search coordinate
     * @param integer [optional] $distance  Distance in metres (default is 1km (distance=1000), max. is 5km)
     * @param long [optional] $minTimestamp Media taken later than this timestamp (default: 5 days ago)
     * @param long [optional] $maxTimestamp Media taken earlier than this timestamp (default: now)
     * @return mixed
     */
    public function searchMedia($lat, $lng, $distance = 1000, $minTimestamp = NULL, $maxTimestamp = NULL) {
        return $this->_makeCall('media/search', array('lat' => $lat, 'lng' => $lng, 'distance' => $distance, 'min_timestamp' => $minTimestamp, 'max_timestamp' => $maxTimestamp));
    }

    /**
     * Get media by its id
     *
     * @param integer $id                   Instagram media ID
     * @return mixed
     */
    public function getMedia($id) {
        return $this->_makeCall('media/' . $id);
    }

    /**
     * Search for tags by name
     *
     * @param string $name                  Valid tag name
     * @return mixed
     */
    public function searchTags($name) {
        return $this->_makeCall('tags/search', array('q' => $name));
    }

    /**
     * Get info about a tag
     *
     * @param string $name                  Valid tag name
     * @return mixed
     */
    public function getTag($name) {
        return $this->_makeCall('tags/' . $name);
    }

    /**
     * Get a recently tagged media
     *
     * @param string $name                  Valid tag name
     * @param integer [optional] $limit     Limit of returned results
     * @return mixed
     */
    public function getTagMedia($name, $limit = 20) {
        return $this->_makeCall('tags/' . $name . '/media/recent', array('count' => $limit));
    }

    /**
     * Get a list of users who have liked this media
     *
     * @param integer $id                   Instagram media ID
     * @return mixed
     */
    public function getMediaLikes($id) {
        return $this->_makeCall('media/' . $id . '/likes');
    }

    /**
     * Get a list of comments for this media
     *
     * @param integer $id                   Instagram media ID
     * @return mixed
     */
    public function getMediaComments($id) {
        return $this->_makeCall('media/' . $id . '/comments');
    }

    /**
     * Add a comment on a media
     *
     * @param integer $id                   Instagram media ID
     * @param string $text                  Comment content
     * @return mixed
     */
    public function addMediaComment($id, $text) {
        return $this->_makeCall('media/' . $id . '/comments', array('text' => $text), 'POST');
    }

    /**
     * Remove user comment on a media
     *
     * @param integer $id                   Instagram media ID
     * @param string $commentID             User comment ID
     * @return mixed
     */
    public function deleteMediaComment($id, $commentID) {
        return $this->_makeCall('media/' . $id . '/comments/' . $commentID, null, 'DELETE');
    }

    /**
     * Set user like on a media
     *
     * @param integer $id                   Instagram media ID
     * @return mixed
     */
    public function likeMedia($id) {
        return $this->_makeCall('media/' . $id . '/likes', null, 'POST');
    }

    /**
     * Remove user like on a media
     *
     * @param integer $id                   Instagram media ID
     * @return mixed
     */
    public function deleteLikedMedia($id) {
        return $this->_makeCall('media/' . $id . '/likes', null, 'DELETE');
    }

    /**
    * Get information about a location
    *
    * @param integer $id                   Instagram location ID
    * @return mixed
    */
    public function getLocation($id) {
        return $this->_makeCall('locations/' . $id);
    }

    /**
     * Get recent media from a given location
     *
     * @param integer $id                   Instagram location ID
     * @return mixed
     */
    public function getLocationMedia($id) {
        return $this->_makeCall('locations/' . $id . '/media/recent');
    }

    /**
     * Get recent media from a given location
     *
     * @param float $lat                    Latitude of the center search coordinate
     * @param float $lng                    Longitude of the center search coordinate
     * @param integer [optional] $distance  Distance in meter (max. distance: 5km = 5000)
     * @return mixed
     */
    public function searchLocation($lat, $lng, $distance = 1000) {
        return $this->_makeCall('locations/search', array('lat' => $lat, 'lng' => $lng, 'distance' => $distance));
    }

    /**
     * Pagination feature
     *
     * @param object  $obj                  Instagram object returned by a method
     * @param integer $limit                Limit of returned results
     * @return mixed
     */
    public function pagination($obj, $limit = 20) {
        if (true === is_object($obj) && !is_null($obj->pagination)) {
            if (!isset($obj->pagination->next_url)) {
                return;
            }
            $apiCall = explode('?', $obj->pagination->next_url);
            if (count($apiCall) < 2) {
                return;
            }
            $function = str_replace(self::API_URL, '', $apiCall[0]);
            if (isset($obj->pagination->next_max_id)) {
                return $this->_makeCall($function, array('max_id' => $obj->pagination->next_max_id, 'count' => $limit));
            } else {
                return $this->_makeCall($function, array('cursor' => $obj->pagination->next_cursor, 'count' => $limit));
            }
        } else {
            throw new PaginationException("Error: pagination() | This method doesn't support pagination.");
        }
    }

    /**
     * Get the OAuth data of a user by the returned callback code
     *
     * @param string $code                  OAuth2 code variable (after a successful login)
     * @param boolean [optional] $token     If it's true, only the access token will be returned
     * @return mixed
     */
    public function getOAuthToken($code, $token = false) {
        $apiData = array(
            'grant_type'      => 'authorization_code',
            'client_id'       => $this->getApiKey(),
            'client_secret'   => $this->getApiSecret(),
            'redirect_uri'    => $this->getApiCallback(),
            'code'            => $code
        );

        $result = $this->_makeOAuthCall($apiData);
        return (false === $token) ? $result : $result->access_token;
    }

    /**
     * The call operator
     *
     * @param string $function              API resource path
     * @param array [optional] $params      Additional request parameters
     * @param string [optional] $method     Request type GET|POST
     * @return mixed
     */
    protected function _makeCall($function, $params = null, $method = 'GET') {
        if (isset($params['count']) && $params['count'] < 1) {
            throw new InvalidParameterException('InstagramClient: you are trying to query 0 records!');
        }

        // if the call needs an authenticated user
        if (true === isset($this->_accesstoken)) {
            $authMethod = '?access_token=' . $this->getAccessToken();
        } else {
            throw new AuthException("Error: _makeCall() | This method requires an valid users access token.");
        }

        if (isset($params) && is_array($params)) {
            $paramString = '&' . http_build_query($params);
        } else {
            $paramString = null;
        }

        $apiCall = self::API_URL . $function . $authMethod . (('GET' === $method) ? $paramString : null);

        // we want JSON
        $headerData = array('Accept: application/json');

        if ($this->_enforceSignedRequests) {
            $apiCall .= (strstr($apiCall, '?') ? '&' : '?') . 'sig=' . $this->_signHeader($function, $authMethod, $params);
        }

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $apiCall);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headerData);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

        if ('POST' === $method) {
            curl_setopt($ch, CURLOPT_POST, count($params));
            curl_setopt($ch, CURLOPT_POSTFIELDS, ltrim($paramString, '&'));
        } else if ('DELETE' === $method) {
            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
        }

        $jsonData = curl_exec($ch);

        $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

        if(403 === $httpcode) {
            $error = json_decode($jsonData, true);
            throw new CurlException('_makeCall() - ' . $error['error_type'] . ' error: ' . $error['error_message']);
        }

        if (false === $jsonData) {
            throw new CurlException('_makeCall() - cURL error: ' . curl_error($ch));
        }
        curl_close($ch);

        return json_decode($jsonData);
    }

    /**
     * The OAuth call operator
     *
     * @param array $apiData The post API data
     * @return mixed
     */
    private function _makeOAuthCall($apiData) {
        $apiHost = self::API_OAUTH_TOKEN_URL;

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $apiHost);
        curl_setopt($ch, CURLOPT_POST, count($apiData));
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($apiData));
        curl_setopt($ch, CURLOPT_HTTPHEADER, array('Accept: application/json'));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

        $jsonData = curl_exec($ch);
        if (false === $jsonData) {
            throw new CurlException('_makeOAuthCall() - cURL error: ' . curl_error($ch));
        }
        curl_close($ch);

        return json_decode($jsonData);
    }

    /**
     * Sign header by using endpoint, parameters and the API secret.
     *
     * @param string
     * @param string
     * @param array
     *
     * @return string The signature
     */
    private function _signHeader($endpoint, $authMethod, $params)
    {
        if (!is_array($params)) {
            $params = array();
        }
        if ($authMethod) {
            list($key, $value) = explode('=', substr($authMethod, 1), 2);
            $params[$key] = $value;
        }
        $baseString = '/' . $endpoint;
        ksort($params);
        foreach ($params as $key => $value) {
            $baseString .= '|' . $key . '=' . $value;
        }
        $signature = hash_hmac('sha256', $baseString, $this->_apisecret, false);
        return $signature;
    }

    /**
     * Access Token Setter
     *
     * @param object|string $data
     * @return void
     */
    public function setAccessToken($data) {
        (true === is_object($data)) ? $token = $data->access_token : $token = $data;
        $this->_accesstoken = $token;
    }

    /**
     * Access Token Getter
     *
     * @return string
     */
    public function getAccessToken() {
        return $this->_accesstoken;
    }

    /**
     * API-key Setter
     *
     * @param string $apiKey
     * @return void
     */
    public function setApiKey($apiKey) {
        $this->_apikey = $apiKey;
    }

    /**
     * API Key Getter
     *
     * @return string
     */
    public function getApiKey() {
        return $this->_apikey;
    }

    /**
     * API Secret Setter
     *
     * @param string $apiSecret
     * @return void
     */
    public function setApiSecret($apiSecret) {
        $this->_apisecret = $apiSecret;
    }

    /**
     * API Secret Getter
     *
     * @return string
     */
    public function getApiSecret() {
        return $this->_apisecret;
    }

    /**
     * API Callback URL Setter
     *
     * @param string $apiCallback
     * @return void
     */
    public function setApiCallback($apiCallback) {
        $this->_callbackurl = $apiCallback;
    }

    /**
     * API Callback URL Getter
     *
     * @return string
     */
    public function getApiCallback() {
        return $this->_callbackurl;
    }

    /**
     * Enforce Signed Header.
     *
     * @param bool $enforceSignedRequests
     *
     * @return void
     */
    public function setEnforceSignedRequests($enforceSignedRequests)
    {
        $this->_enforceSignedRequests = $enforceSignedRequests;
    }

    /**
     * Permission Scope setter.
     *
     * @param array $scope
     * @return void
     */
    public function setScope(array $scope) {
        $this->_scope = $this->mergeScope($scope);
    }

    /**
     * Merge permission scope with default
     * scope. Allow only valid values.
     *
     * @param array @scope
     */
    private function mergeScope(array $scope) {
        if (empty($scope)) return $this->_scope;
        $scope = array_merge($scope, $this->_defaulScope);
        $scope = array_unique($scope);

        $intersectingScope = array_intersect($scope, $this->_availableScope);

        if (count($intersectingScope) !== count($scope)) {
            throw new InvalidParameterException('Error: mergeScope() - Invalid permission scope parameter used.');
        }

        return $intersectingScope;
    }

    /**
     * Permission Scope getter.
     *
     * @return array
     */
    public function getScope() {
        return empty($this->_scope) ? $this->_defaulScope : $this->_scope;
    }

}


<?php namespace Andreyco\Instagram\Support\Laravel\ServiceProvider;

use Illuminate\Support\ServiceProvider;

class Instagram extends ServiceProvider {

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

    /**
     * Bootstrap the application events.
     *
     * @return void
     */
    public function boot()
    {
        $this->publishes([
            __DIR__.'/../config/config.php' => config_path('instagram.php'),
        ]);
    }

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind('andreyco.instagram', function($app) {
            $client = new \Andreyco\Instagram\Client([
                'apiKey'      => $app['config']->get('instagram.clientId'),
                'apiSecret'   => $app['config']->get('instagram.clientSecret'),
                'apiCallback' => $app['config']->get('instagram.redirectUri'),
                'scope'       => $app['config']->get('instagram.scope'),
            ]);

            if ($app['config']->get('instagram.enforceSignedRequests') === true) {
                $client->setEnforceSignedRequests(true);
            }

            return $client;
        });
    }

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

}


<?php

namespace Qafeen\Manager;

use Qafeen\Manager\Traits\Helper;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;

/**
 * Custom Process builder.
 *
 * @author  Mohammed Mudasir <hello@mudasir.me>
 */
class ProcessBuilder
{
    use Helper;

    /**
     * @var \Illuminate\Console\Command
     */
    protected $console;

    /**
     * ProcessBuilder constructor.
     *
     * @param $console
     */
    public function __construct($console)
    {
        $this->console = $console;
    }

    /**
     * Run the created process builder.
     *
     * @param $command
     *
     * @return bool
     */
    public function run($command)
    {
        $process = new Process(
            $command,
            null,
            null,
            null,
            ini_get('max_execution_time')
        );

        $process->run(function ($type, $buffer) {
            if (Process::ERR === $type) {
                $this->console->warn(trim($buffer));
            } else {
                $this->console->info(trim($buffer));
            }
        });

        if (!$process->isSuccessful()) {
            throw new ProcessFailedException($process);
        }

        return true;
    }
}


<?php

namespace Qafeen\Manager\Manage;

/**
 * Manage Service Provider.
 *
 * @author Mohammed Mudasir <hello@mudasir.me>
 */
class Facade extends File
{
    /**
     * @var \Illuminate\Support\Collection
     */
    protected $facades;

    /**
     * Is file registered in config/manger.php.
     *
     * @return bool
     */
    public function isRegistered()
    {
        return $this->registered;
    }

    /**
     * Search package by given name.
     *
     * @return array
     */
    public function search()
    {
        $this->console->info('Searching directory for facades(aliases).');

        $facades = $this->getFacades();

        if (!$facades->count()) {
            $this->console->warn('No facades file found. Nothing to install.');

            return [];
        }

        $this->console->line(
            " Found {$facades->count()} facade".($facades->count() > 1 ? 's' : '').'.'
        );

        $facades->each(function ($facade, $index) {
            $currentCount = $index + 1;

            $this->console->line(" $currentCount. $facade");
        });

        if (!$this->console->confirm('Register facades?', true)) {
            return [];
        }

        $this->registered = true;

        return $this->getFacades()->toArray();
    }

    /**
     * Get Facades list from the package.
     *
     * @return \Illuminate\Support\Collection
     */
    public function getFacades()
    {
        return $this->facades ?:
            $this->facades = $this->fileHas('/class [A-Z]\w+ extends Facade/i')->getClasses();
    }

    /**
     * Get facades count.
     *
     * @return int
     */
    public function count()
    {
        return $this->facades->count();
    }
}


<?php

namespace Qafeen\Manager\Manage;

use Illuminate\Filesystem\Filesystem;
use Qafeen\Manager\Traits\Helper;

/**
 * Handle configuration file.
 *
 * @author Mohammed Mudasir <hello@mudasir.me>
 */
class ConfigFile
{
    use Helper;

    /**
     * @var array
     */
    protected $providers;

    /**
     * Facades (aliases).
     *
     * @var array
     */
    protected $aliases;

    /**
     * @var \Illuminate\Filesystem\Filesystem
     */
    protected $filesystem;

    /**
     * ConfigFile constructor.
     *
     * @param array $providers
     * @param array $aliases
     */
    public function __construct(array $providers = [], array $aliases = [])
    {
        $this->providers = $providers;

        $this->aliases = $aliases;

        $this->filesystem = new Filesystem();
    }

    /**
     * Make config/manager.php file.
     *
     * @return bool
     */
    public function make()
    {
        $content = $this->getManagerStubContent();

        $content = str_replace('@providers', $this->stringify('providers'), $content);

        $content = str_replace('@aliases', $this->stringify('aliases'), $content);

        $this->filesystem->put($this->getManagerFilePath(), $content);

        return true;
    }

    /**
     * Get the configuration details form config/manager.php file.
     *
     * @param  $name
     *
     * @return array|mixed
     */
    public function getFromConfig($name)
    {
        if (!$config = config("manager.$name")) {
            return [];
        }

        return $config;
    }

    /**
     * Get the content from manager stub file.
     *
     * @return string
     */
    protected function getManagerStubContent()
    {
        return file_get_contents($this->getManagerStubFilePath());
    }

    /**
     * Get manager file path.
     *
     * @return string
     */
    protected function getManagerFilePath()
    {
        return config_path('manager.php');
    }

    /**
     * Get manager stub file path.
     *
     * @return string
     */
    protected function getManagerStubFilePath()
    {
        return realpath(__DIR__.'/../stubs/manager.stub');
    }

    /**
     * Convert providers or facade details to template.
     *
     * @param  $name
     *
     * @return string
     */
    protected function stringify($name)
    {
        $classes = array_unique(array_merge(
            $this->suffixClass($this->getFromConfig($name)),
            $this->suffixClass($this->$name)
        ));

        $newLine2Tabs = PHP_EOL.'        ';

        if ($name == 'providers') {
            return "'$name' => [$newLine2Tabs".
                        implode(','.$newLine2Tabs, $classes).
                    PHP_EOL.'    ],'.PHP_EOL;
        }

        $template = "'$name' => [$newLine2Tabs";

        foreach ($classes as $fullClassName) {
            $template .= "'{$this->getClassName($fullClassName)}' => $fullClassName,$newLine2Tabs";
        }

        $template .= PHP_EOL.'    ],'.PHP_EOL;

        return $template;
    }

    /**
     * Suffix '::class' to class name.
     *
     * @param array $class
     *
     * @return array
     */
    protected function suffixClass(array $class)
    {
        return array_map(function ($class) {
            return "$class::class";
        }, $class);
    }

    /**
     * Get the class name by given full qualified class name.
     *
     * @param string $class
     *
     * @return mixed
     */
    public function getClassName($class)
    {
        return preg_replace('/::class|Facade/i', '', last(explode('\\', $class)));
    }
}


<?php

namespace Qafeen\Manager\Console;

use Illuminate\Console\Command;
use Illuminate\Support\Collection;
use Qafeen\Manager\Manager;
use Qafeen\Manager\Packages;
use Qafeen\Manager\ProcessBuilder;

/**
 * Install Package Command.
 *
 * @author Mohammed Mudasir <hello@mudasir.me>
 */
class Add extends Command
{
    /**
     * Manager install console command.
     *
     * @var string
     */
    protected $signature = 'add {package : Specify Package name. eg: vendor/package:version}';

    /**
     * Install provided package.
     *
     * @var string
     */
    protected $description = 'Install provided package.';

    /**
     * Entry point for installation.
     *
     * @return mixed
     */
    public function handle()
    {
        $packageInfo = $this->tokenizePackageInfo();
        $packages = $this->getPackages();
        $total = $packages->count();

        if (!$total) {
            $this->warn(' No package found. Make sure you spell it correct as specified on github or packagist.');
        }

        if ($packages->first()['name'] !== $packageInfo['name']) {
            $this->warn($total.' package'.($total > 1 ? 's' : '').' found by given name.');

            return $this->call('add', ['package' => $this->prettify($packages)]);
        }

        $this->downloadPackage()
             ->runConfiguration();
    }

    /**
     * Start downloading package.
     *
     * @return \Qafeen\Manager\Console\Add
     */
    public function downloadPackage()
    {
        ProcessBuilder::instance($this)->run($this->composerRequire());

        return $this;
    }

    /**
     * Start installation process.
     *
     * @return void
     */
    public function runConfiguration()
    {
        Manager::instance($this->tokenizePackageInfo()['name'], $this)->install();
    }

    /**
     * Search packages from packagist.org.
     *
     * @return mixed
     */
    public function getPackages()
    {
        return Packages::instance($this->tokenizePackageInfo()['name'])->search();
    }

    /**
     * Get the package name and version provided by user.
     *
     * Note: We will not store package information in Add command instance
     * due to possibility of it might be call again in a loop if user
     * provided an invalid package ID.
     *
     * @return array
     */
    public function tokenizePackageInfo()
    {
        $info = explode(':', $this->argument('package'));

        return [
            'name'    => $info[0],
            'version' => (count($info) > 1) ? last($info) : null,
        ];
    }

    /**
     * Create composer require command.
     *
     * @return string
     */
    public function composerRequire()
    {
        return "composer require {$this->argument('package')}";
    }

    /**
     * Prettify Package result.
     *
     * @param Collection $packages
     *
     * @return mixed
     */
    public function prettify(Collection $packages)
    {
        $summary = [];

        foreach ($packages as $key => $package) {
            $summary[] = [
                'id'   => $key + 1,
                'name' => $this->prettifyPackageInfo($package),
            ];
        }

        return $packages[$this->askPackageKey($summary)]['name'];
    }

    /**
     * Ask user for package key.
     *
     * @param        $summary
     * @param string $message
     *
     * @return mixed
     */
    public function askPackageKey($summary, $message = 'Please provide an id')
    {
        $this->table(['id', 'name'], $summary);

        $selected = $this->ask($message);

        $key = collect($summary)->pluck('id')->search($selected);

        if ($key === false) {
            $this->warn('Invalid package name or id given.');

            return $this->askPackageKey($summary, 'Please provide a valid id');
        }

        return $key;
    }

    /**
     * Prettify package details.
     *
     * @param array $package
     *
     * @return string
     */
    private function prettifyPackageInfo($package)
    {
        $newLine2Tab = PHP_EOL.'       ';
        $downloads = number_format($package['downloads']);
        $stars = number_format($package['favers']);

        $reputation = " [<fg=green;options=bold>⇩ $downloads</>".
                      " <fg=magenta;options=bold>★ $stars</>]";

        $holdSpace = strlen($package['name'].$downloads.$stars);

        $insertSpace = str_repeat(' ', 65 - $holdSpace);

        return "<fg=yellow>{$package['name']}</>".$insertSpace.$reputation.PHP_EOL.
                wordwrap($package['description']).PHP_EOL.
                $package['repository'].
            $newLine2Tab;
    }
}


<?php

namespace Qafeen\Manager\Manage;

/**
 * Manage Migration.
 *
 * @author Mohammed Mudasir <hello@mudasir.me>
 */
class Migration extends File
{
    /**
     * @var bool
     */
    protected $hasMigrationFile;

    /**
     * Run the migration command.
     *
     * @return bool|int
     */
    public function run()
    {
        if ($this->search()) {
            $this->registered = true;

            return $this->console->call('migrate');
        }

        return false;
    }

    /**
     * Search package by given name.
     *
     * @return bool
     */
    public function search()
    {
        $this->console->info('Searching directory for Migrations.');

        return $this->hasMigrationFile() ? $this->console->confirm('Run migrations?', true) : false;
    }

    /**
     * Get Migration file list from the package.
     *
     * @return bool
     */
    public function hasMigrationFile()
    {
        $this->count = $this->fileHas('/class [A-Z]\w+ extends Migration/i')
                            ->getClasses()
                            ->count();

        return $this->hasMigrationFile = $this->count > 0;
    }

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


<?php

namespace Qafeen\Manager;

use ErrorException;
use Exception;
use Illuminate\Console\Command;
use Qafeen\Manager\Manage\ConfigFile;
use Qafeen\Manager\Manage\Facade;
use Qafeen\Manager\Manage\Migration;
use Qafeen\Manager\Manage\Resource;
use Qafeen\Manager\Manage\ServiceProvider;
use Qafeen\Manager\Traits\Helper;
use Symfony\Component\Finder\Finder;

/**
 * Package manager will handling installing, uninstalling or deleting packages.
 *
 * @author Mohammed Mudasir <hello@mudasir.me>
 */
class Manager
{
    use Helper;

    /**
     * Configuration detail of a given package.
     *
     * @var array
     */
    protected $config;

    /**
     * Name of a package.
     *
     * @var string
     */
    protected $name;

    /**
     * package directory.
     *
     * @var string
     */
    protected $directory;

    /**
     * List of service providers.
     *
     * @var \Qafeen\Manager\Manage\ServiceProvider
     */
    protected $providers;

    /**
     * @var \Qafeen\Manager\Manage\Facade
     */
    protected $facades;

    /**
     * @var \Qafeen\Manager\Manage\Migration
     */
    protected $migration;

    /**
     * @var \Qafeen\Manager\Manage\Resource
     */
    protected $resources;

    /**
     * Console class to notify user.
     *
     * @var \Qafeen\Manager\Console\Add
     */
    protected $console;

    /**
     * List of package files.
     *
     * @var \Symfony\Component\Finder\Finder
     */
    protected $files;

    /**
     * @param string $name
     * @param mixed  $console
     */
    public function __construct($name, $console)
    {
        $this->setName($name)
             ->setDirectory("vendor/$name/")
             ->setConsole($console);
    }

    /**
     * Run the build.
     *
     * @throws \ErrorException
     *
     * @return \Qafeen\Manager\Manager
     */
    public function build()
    {
        $providers = $this->getProviders()->search();
        $facades = $this->getFacades()->search();

        if (!ConfigFile::instance($providers, $facades)->make()) {
            throw new ErrorException('Unable to register providers and facades. Please report this incident at Qafeen/Manager');
        }

        $this->getResources()->publish($providers[0]);

        return $this;
    }

    /**
     * Get Service Providers.
     *
     * @return ServiceProvider
     */
    public function getProviders()
    {
        return $this->providers ?:
            $this->providers = new ServiceProvider(clone $this->getFiles(), $this->console);
    }

    /**
     * Get Facades.
     *
     * @return Facade
     */
    public function getFacades()
    {
        return $this->facades ?:
            $this->facades = new Facade(clone $this->getFiles(), $this->console);
    }

    /**
     * Get migration.
     *
     * @return Migration
     */
    public function getMigration()
    {
        return $this->migration ?:
            $this->migration = new Migration(clone $this->getFiles(), $this->console);
    }

    /**
     * Start installation process.
     *
     * @throws ErrorException
     *
     * @return bool
     */
    public function install()
    {
        $this->build()->getMigration()->run();

        return $this->notifyUser();
    }

    /**
     * Get the package files.
     *
     * @return \Symfony\Component\Finder\Finder
     */
    public function getFiles()
    {
        return $this->files ?: $this->files = Finder::create()->in(realpath($this->directory));
    }

    /**
     * Check manager file exists in package or not.
     *
     * @return bool
     */
    public function hasManagerFile()
    {
        if (app('filesystem')->exists($this->directory.'manager.yml')) {
            return true;
        }

        $this->console->warn("No manager.yml file found in {$this->name} package.");

        return false;
    }

    /**
     * Load the package configuration form file.
     *
     * @return bool
     */
    public function loadManagerFile()
    {
        // @todo If manager.yml file is given then we don't need to search whole project

        return false;
    }

    /**
     * Check to see if we have a valid command class to work.
     *
     * @param  $class
     *
     * @throws Exception
     *
     * @return bool
     */
    public function isValidConsole($class)
    {
        if ($class instanceof Command) {
            return true;
        }

        throw new Exception(get_class($class).' not found.');
    }

    /**
     * Set the name of the package.
     *
     * @param  $name
     *
     * @return $this
     */
    private function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Set the directory of the package.
     *
     * @param $path
     *
     * @return $this
     */
    private function setDirectory($path)
    {
        $this->directory = $path;

        return $this;
    }

    /**
     * Set the given console class.
     *
     * @param $class
     *
     * @return $this
     */
    private function setConsole($class)
    {
        if ($this->isValidConsole($class)) {
            $this->console = $class;
        }

        return $this;
    }

    /**
     * Notify user.
     *
     * @return bool
     */
    protected function notifyUser()
    {
        $this->console->line('');

        $this->console->line("{$this->isDone($this->getProviders()->isRegistered())} ".
            "{$this->getProviders()->count()} service provider registered.");

        $this->console->line("{$this->isDone($this->getFacades()->isRegistered())} ".
            "{$this->getFacades()->count()} facade registered.");

        $this->console->line("{$this->isDone($this->getMigration()->isRegistered())} ".
            "{$this->getMigration()->count()} migration file ran.");

        $this->console->line("{$this->isDone($this->getResources()->isRegistered())} ".
            '- '.$this->console->tokenizePackageInfo()['name'].
            ' file publish.');

        return true;
    }

    /**
     * Get resource instance.
     *
     * @return \Qafeen\Manager\Manage\Resource
     */
    public function getResources()
    {
        return $this->resources ?:
            $this->resources = Resource::instance(clone $this->getFiles(), $this->console);
    }

    /**
     * Get the correct symbol on bases of done and not done.
     *
     * @param $ans
     *
     * @return string
     */
    protected function isDone($ans)
    {
        return $ans ? ' <fg=green;bold>✓</>' : ' <fg=red;bold>✗</>';
    }
}


<?php

namespace Qafeen\Manager\Traits;

use Exception;

/**
 * Helper trait.
 *
 * @author Mohammed Mudasir <hello@mudasir.me>
 */
trait Helper
{
    /**
     * Instantiate a class and use it like chain responsibility.
     *
     * @throws Exception
     *
     * @return static
     */
    public static function instance()
    {
        switch (func_num_args()) {
            case func_num_args() == 0:
                return new static();
            case func_num_args() == 1:
                return new static(func_get_arg(0));
            case func_num_args() == 2:
                return new static(func_get_arg(0), func_get_arg(1));
            case func_num_args() == 3:
                return new static(func_get_arg(0), func_get_arg(1), func_get_arg(2));
            case func_num_args() == 4:
                return new static(func_get_arg(0), func_get_arg(1), func_get_arg(2), func_get_arg(3));
            case func_num_args() == 5:
                return new static(
                    func_get_arg(0), func_get_arg(1), func_get_arg(2), func_get_arg(3), func_get_arg(4)
                );
            default:
                throw new Exception('Unable to instantiate class with given arguments');
        }
    }
}


<?php

namespace Qafeen\Manager\Manage;

use hanneskod\classtools\Iterator\ClassIterator;
use Qafeen\Manager\Traits\Helper;
use Symfony\Component\Finder\Finder;
use Symfony\Component\HttpFoundation\File\Exception\FileException;

/**
 * Manage.php.
 *
 * @author Mohammed Mudasir <hello@mudasir.me>
 */
abstract class File
{
    use Helper;

    /**
     * @var \Symfony\Component\Finder\Finder
     */
    protected $finder;

    /**
     * @var \Illuminate\Console\Command
     */
    protected $console;

    /**
     * Is file registered.
     *
     * @var bool
     */
    protected $registered = false;

    /**
     * Count of migration files.
     *
     * @var int|null
     */
    protected $count = null;

    /**
     * @var \hanneskod\classtools\Iterator\SplFileInfo[]
     */
    protected $files;

    /**
     * File registered and migration ran successfully.
     *
     * @return bool
     */
    public function isRegistered()
    {
        return $this->registered;
    }

    /**
     * Facade constructor.
     *
     * @param \Symfony\Component\Finder\Finder $finder
     * @param \Qafeen\Manager\Console\Add      $console
     */
    public function __construct(Finder $finder, $console)
    {
        $this->finder = $finder;

        $this->console = $console;
    }

    /**
     * Get classes from given files.
     *
     * @param  $contains
     *
     * @return \Qafeen\Manager\Manage\File
     */
    public function fileHas($contains)
    {
        $this->files = (new ClassIterator($this->finder->contains($contains)))->getClassMap();

        $this->count = count($this->files);

        return $this;
    }

    /**
     * Get classes from files.
     *
     * @return \Illuminate\Support\Collection
     */
    public function getClasses()
    {
        return collect(array_keys($this->files));
    }

    /**
     * Get files.
     *
     * @return \hanneskod\classtools\Iterator\SplFileInfo[]
     */
    public function getFiles()
    {
        return $this->files;
    }

    /**
     * Get the file by class name.
     *
     * @param $class
     *
     * @throws \Symfony\Component\HttpFoundation\File\Exception\FileException;
     *
     * @return \hanneskod\classtools\Iterator\SplFileInfo
     */
    public function getFile($class)
    {
        if (is_null($this->count)) {
            return $this->fileHas($class)->getFile($class);
        }

        if (isset($this->files[$class])) {
            return $this->files[$class];
        }

        throw new FileException("File does not exists with given `{$class}` class.");
    }
}


<?php

namespace Qafeen\Manager\Manage;

/**
 * Manage Service Provider.
 *
 * @author Mohammed Mudasir <hello@mudasir.me>
 */
class ServiceProvider extends File
{
    /**
     * @var \Illuminate\Support\Collection
     */
    protected $providers;

    /**
     * Start searching service provider in package.
     *
     * @return array
     */
    public function search()
    {
        $this->console->info('Searching directory for service providers.');

        $sps = $this->getProviders();

        if (!$sps->count()) {
            $this->console->warn('No service provider file found. Nothing to install.');

            return [];
        }

        $this->console->line(
            " Found {$sps->count()} Service provider".($sps->count() > 1 ? 's' : '').'.'
        );

        $sps->each(function ($sp, $index) {
            $currentCount = $index + 1;

            $this->console->line(" $currentCount. $sp");
        });

        if (!$this->console->confirm('Register service providers?', true)) {
            return [];
        }

        $this->registered = true;

        return $this->getProviders()->toArray();
    }

    /**
     * Get service providers.
     *
     * @return \Illuminate\Support\Collection
     */
    public function getProviders()
    {
        return $this->providers ?:
            $this->providers = $this->fileHas('/class [A-Z]\w+ extends ServiceProvider/i')->getClasses();
    }

    /**
     * Get service providers count.
     *
     * @return int
     */
    public function count()
    {
        return $this->providers->count();
    }
}


<?php

namespace Qafeen\Manager;

use GuzzleHttp\Client;
use Qafeen\Manager\Traits\Helper;

/**
 * Collection of packages.
 *
 * @author Mohammed Mudasir <hello@mudasir.me>
 */
class Packages
{
    use Helper;

    /**
     * Packagist URL.
     */
    const PACKAGIST_URL = 'https://packagist.org/';

    /**
     * Guzzle HTTP client.
     *
     * @var \GuzzleHttp\Client
     */
    protected $client;

    /**
     * Raw collection of packages which is downloaded from packagist website.
     *
     * @var \Illuminate\Support\Collection;
     */
    protected $rawPackages;

    /**
     * Name of a package which will be search when get method is called.
     *
     * @var null|string
     */
    protected $packageName;

    /**
     * Packages constructor.
     *
     * @param string $packageName
     */
    public function __construct($packageName = null)
    {
        $this->client = new Client();

        $this->packageName = $packageName;
    }

    /**
     * Search the given package.
     *
     * @return mixed
     */
    public function search()
    {
        $url = self::PACKAGIST_URL.'search.json?q='.$this->getPackageName();

        $response = $this->client
                         ->get($url)
                         ->getBody()
                         ->getContents();

        $this->rawPackages = collect(json_decode($response, true));

        return collect($this->rawPackages->get('results'));
    }

    /**
     * Get the count of the package.
     *
     * @return int
     */
    public function count()
    {
        return (int) $this->rawPackages->get('total');
    }

    /**
     * Get the package name.
     *
     * @return string
     */
    public function getPackageName()
    {
        return $this->packageName ?: '';
    }
}


<?php

namespace Qafeen\Manager\Manage;

/**
 * Manage Resource.
 *
 * @author Mohammed Mudasir <hello@mudasir.me>
 */
class Resource extends File
{
    /**
     * Publish resource files.
     *
     * @param string $provider package service provider
     *
     * @return bool|int
     */
    public function publish($provider)
    {
        $class = last(explode('\\', $provider));

        $this->console->info("Searching {$provider} to publish vendor file.");

        if (!$this->finder->contains($class)->contains('/\$this->publishes/i')->count()) {
            $this->console->warn('Nothing to publish.');

            return true;
        }

        $tag = $this->console->ask("If the \"{$this->console->tokenizePackageInfo()['name']}\" has specify vendor publish tag in installation guide then please add it here or press enter to skip adding tag.", false);

        $this->console->call('vendor:publish', [
            '--provider' => $provider,
            '--tag'      => $tag,
        ]);

        return $this->registered = true;
    }
}


<?php

namespace Schuppo\PasswordStrength;

use Illuminate\Contracts\Validation\Factory;
use Illuminate\Support\ServiceProvider;
use Illuminate\Translation\Translator;

class PasswordStrengthServiceProvider extends ServiceProvider
{
    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        app()->singleton('passwordStrength', function () {
            return new PasswordStrength();
        });

        app()->singleton('passwordStrength.translationProvider', function () {
            return new PasswordStrengthTranslationProvider();
        });
    }

    /**
     * Bootstrap application services.
     *
     * @return void
     */
    public function boot(Factory $validator)
    {
        $passwordStrength = app('passwordStrength');
        $translator = app('passwordStrength.translationProvider')->get($validator);

        foreach(['letters', 'numbers', 'caseDiff', 'symbols'] as $rule) {
            $snakeCasedRule = snake_case($rule);

            $validator->extend($rule, function ($_, $value, $__) use ($passwordStrength, $rule) {
                $capitalizedRule = ucfirst($rule);
                return call_user_func([$passwordStrength, "validate{$capitalizedRule}"], $value);
            }, $translator->get("password-strength::validation.{$snakeCasedRule}"));
        }
    }
}


<?php
/*
 * This file is part of the FreshVichUploaderSerializationBundle
 *
 * (c) Artem Henvald <genvaldartem@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fresh\VichUploaderSerializationBundle\EventListener;

use Doctrine\Common\Annotations\Reader;
use Doctrine\Common\Persistence\Proxy;
use Doctrine\Common\Util\ClassUtils;
use Fresh\VichUploaderSerializationBundle\Annotation\VichSerializableClass;
use Fresh\VichUploaderSerializationBundle\Annotation\VichSerializableField;
use Fresh\VichUploaderSerializationBundle\Exception\IncompatibleUploadableAndSerializableFieldAnnotationException;
use JMS\Serializer\EventDispatcher\Events;
use JMS\Serializer\EventDispatcher\EventSubscriberInterface;
use JMS\Serializer\EventDispatcher\ObjectEvent;
use JMS\Serializer\EventDispatcher\PreSerializeEvent;
use Psr\Log\LoggerInterface;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\Routing\RequestContext;
use Vich\UploaderBundle\Mapping\Annotation\UploadableField;
use Vich\UploaderBundle\Storage\StorageInterface;

/**
 * JmsPreSerializeListener Class.
 *
 * @author Artem Henvald <genvaldartem@gmail.com>
 */
class JmsSerializerSubscriber implements EventSubscriberInterface
{
    /** @var StorageInterface */
    private $storage;

    /** @var RequestContext */
    private $requestContext;

    /** @var Reader */
    private $annotationReader;

    /** @var PropertyAccessorInterface */
    private $propertyAccessor;

    /** @var LoggerInterface */
    private $logger;

    /** @var array */
    private $serializedObjects = [];

    /**
     * @param StorageInterface          $storage
     * @param RequestContext            $requestContext
     * @param Reader                    $annotationReader
     * @param PropertyAccessorInterface $propertyAccessor
     * @param LoggerInterface           $logger
     */
    public function __construct(StorageInterface $storage, RequestContext $requestContext, Reader $annotationReader, PropertyAccessorInterface $propertyAccessor, LoggerInterface $logger)
    {
        $this->storage = $storage;
        $this->requestContext = $requestContext;
        $this->annotationReader = $annotationReader;
        $this->propertyAccessor = $propertyAccessor;
        $this->logger = $logger;
    }

    /**
     * @return array
     */
    public static function getSubscribedEvents(): array
    {
        return [
            ['event' => Events::PRE_SERIALIZE, 'method' => 'onPreSerialize'],
            ['event' => Events::POST_SERIALIZE, 'method' => 'onPostSerialize'],
        ];
    }

    /**
     * @param PreSerializeEvent $event
     *
     * @throws IncompatibleUploadableAndSerializableFieldAnnotationException
     */
    public function onPreSerialize(PreSerializeEvent $event): void
    {
        $object = $event->getObject();

        if ($object instanceof Proxy && !$object->__isInitialized()) {
            $object->__load();
        }

        $objectUid = \spl_object_hash($object);
        if (\array_key_exists($objectUid, $this->serializedObjects)) {
            return;
        }

        $classAnnotation = $this->annotationReader->getClassAnnotation(
            new \ReflectionClass(ClassUtils::getClass($object)),
            VichSerializableClass::class
        );

        if ($classAnnotation instanceof VichSerializableClass) {
            $reflectionClass = ClassUtils::newReflectionClass(\get_class($object));
            $this->logger->debug(\sprintf(
                'Found @VichSerializableClass annotation for the class "%s"',
                $reflectionClass->getName()
            ));

            foreach ($reflectionClass->getProperties() as $property) {
                $vichSerializableAnnotation = $this->annotationReader->getPropertyAnnotation($property, VichSerializableField::class);

                if ($vichSerializableAnnotation instanceof VichSerializableField) {
                    $vichUploadableFileAnnotation = $this->annotationReader->getPropertyAnnotation($property, UploadableField::class);

                    if ($vichUploadableFileAnnotation instanceof UploadableField) {
                        throw new IncompatibleUploadableAndSerializableFieldAnnotationException(\sprintf(
                            'The field "%s" in the class "%s" cannot have @UploadableField and @VichSerializableField annotations at the same moment.',
                            $property->getName(),
                            $reflectionClass->getName()
                        ));
                    }
                    $this->logger->debug(\sprintf(
                        'Found @VichSerializableField annotation for the field "%s" in the class "%s"',
                        $property->getName(),
                        $reflectionClass->getName()
                    ));

                    $uri = null;
                    $property->setAccessible(true);

                    if ($property->getValue($event->getObject())) {
                        $uri = $this->storage->resolveUri($object, $vichSerializableAnnotation->getField());
                        if ($vichSerializableAnnotation->isIncludeHost() && false === \filter_var($uri, FILTER_VALIDATE_URL)) {
                            $uri = $this->getHostUrl().$uri;
                        }
                    }
                    $this->serializedObjects[$objectUid][$property->getName()] = $property->getValue($event->getObject());
                    $property->setValue($object, $uri);
                }
            }
        }
    }

    /**
     * @param ObjectEvent $event
     */
    public function onPostSerialize(ObjectEvent $event): void
    {
        $object = $event->getObject();

        if ($object instanceof Proxy && !$object->__isInitialized()) {
            $object->__load();
        }

        $objectUid = \spl_object_hash($object);
        if (!\array_key_exists($objectUid, $this->serializedObjects)) {
            return;
        }

        foreach ($this->serializedObjects[$objectUid] as $propertyName => $propertyValue) {
            $this->propertyAccessor->setValue($object, $propertyName, $propertyValue);
        }
        unset($this->serializedObjects[$objectUid]);
    }

    /**
     * Get host url (scheme://host:port).
     *
     * @return string
     */
    private function getHostUrl(): string
    {
        $scheme = $this->requestContext->getScheme();
        $url = $scheme.'://'.$this->requestContext->getHost();

        $httpPort = $this->requestContext->getHttpPort();
        if ('http' === $scheme && $httpPort && 80 !== $httpPort) {
            return $url.':'.$httpPort;
        }

        $httpsPort = $this->requestContext->getHttpsPort();
        if ('https' === $scheme && $httpsPort && 443 !== $httpsPort) {
            return $url.':'.$httpsPort;
        }

        return $url;
    }
}


<?php
/**
 * This file is part of PHPDebugConsole
 *
 * @package   PHPDebugConsole
 * @author    Brad Kent <bkfake-github@yahoo.com>
 * @license   http://opensource.org/licenses/MIT MIT
 * @copyright 2014-2019 Brad Kent
 * @version   v2.3
 */

namespace bdk\Debug\Output;

use bdk\Debug;

/**
 * Output object as HTML
 */
class HtmlObject
{

	/**
     * Constructor
     *
     * @param \bdk\Debug $debug Debug instance
     */
	public function __construct(Debug $debug)
	{
		$this->debug = $debug;
	}

    /**
     * Dump object as html
     *
     * @param array $abs object abstraction
     *
     * @return string
     */
	public function dump($abs)
	{
        $title = \trim($abs['phpDoc']['summary']."\n\n".$abs['phpDoc']['description']);
        $strClassName = $this->debug->output->html->markupClassname($abs['className'], 'span', array(
            'title' => $title ?: null,
        ));
        if ($abs['isRecursion']) {
            return $strClassName
                .' *RECURSION*';
        }
        if ($abs['isExcluded']) {
            return $this->dumpToString($abs)
                .$strClassName."\n"
                .'(not inspected)';
        }
        $html = $this->dumpToString($abs)
            .$strClassName."\n"
            .'
'."\n"
                .'
extends
'."\n"
                    .'
'.\implode('
'."\n".'
', $abs['extends']).'
'."\n"
                .'
implements
'."\n"
                    .'
'.\implode('
'."\n".'
', $abs['implements']).'
'."\n"
                .$this->dumpConstants($abs['constants'])
                .$this->dumpProperties($abs)
                .($abs['collectMethods'] && $this->debug->output->getCfg('outputMethods')
                    ? $this->dumpMethods($abs['methods'])
                    : '')
                .$this->dumpPhpDoc($abs['phpDoc'])
            .'
'."\n";
        // remove
's with empty
'
        $html = \preg_replace('#
]*>\w+
\s*
]*>
\s*#', '', $html);
        $html = \str_replace(' title=""', '', $html);
        return $html;
    }

    /**
     * Dump object's __toString or stringified value
     *
     * @param array $abs object abstraction
     *
     * @return string html
     */
    protected function dumpToString($abs)
    {
        $val = '';
        if ($abs['stringified']) {
            $val = $abs['stringified'];
        } elseif (isset($abs['methods']['__toString']['returnValue'])) {
            $val = $abs['methods']['__toString']['returnValue'];
        }
        if (!$val) {
            return '';
        }
        $valAppend = '';
        $len = \strlen($val);
        if ($len > 100) {
            $val = \substr($val, 0, 100);
            $valAppend = '… ('.($len - 100).' more chars)';
        }
        $toStringDump = $this->debug->output->html->dump($val);
        $parsed = $this->debug->utilities->parseTag($toStringDump);
        $classArray = \explode(' ', $parsed['attribs']['class']);
        $classArray[] = 't_stringified';
        if ($len > 100) {
            $classArray[] = 't_string_trunc';   // truncated
        }
        $attribs = array(
            'class' => $classArray,
            'title' => isset($parsed['attribs']['title'])
                // ie a timestamp will have a human readable date in title
                ? (!$abs['stringified'] ? '__toString() : ' : '').$parsed['attribs']['title']
                : (!$abs['stringified'] ? '__toString()' : null),
        );
        return $this->debug->utilities->buildTag(
            'span',
            $attribs,
            $parsed['innerhtml'].$valAppend
        )."\n";
    }

    /**
     * dump object constants as html
     *
     * @param array $constants array of name=>value
     *
     * @return string html
     */
    protected function dumpConstants($constants)
    {
        $str = '';
        if ($constants && $this->debug->output->getCfg('outputConstants')) {
            $str = '
constants
'."\n";
            foreach ($constants as $k => $value) {
                $str .= '
'
                    .''.$k.''
                    .' = '
                    .$this->debug->output->html->dump($value)
                    .'
'."\n";
            }
        }
        return $str;
    }

    /**
     * Dump object methods as html
     *
     * @param array $methods methods as returned from getMethods
     *
     * @return string html
     */
    protected function dumpMethods($methods)
    {
        $label = \count($methods)
            ? 'methods'
            : 'no methods';
        $str = '
'.$label.'
'."\n";
        $magicMethods = \array_intersect(array('__call','__callStatic'), \array_keys($methods));
        $str .= $this->magicMethodInfo($magicMethods);
        foreach ($methods as $methodName => $info) {
            if (!isset($info['phpDoc']['return'])) {
                $info['phpDoc']['return'] = array(
                    'desc' => null,
                    'type' => null,
                );
            }
            $classes = \array_keys(\array_filter(array(
                'method' => true,
                'deprecated' => $info['isDeprecated'],
            )));
            $modifiers = \array_keys(\array_filter(array(
                'final' => $info['isFinal'],
                $info['visibility'] => true,
                'static' => $info['isStatic'],
            )));
            $str .= $this->debug->utilities->buildTag(
                'dd',
                array(
                    'class' => \array_merge($classes, $modifiers),
                    'data-implements' => $info['implements'],
                ),
                \implode(' ', \array_map(function ($modifier) {
                    return ''.$modifier.'';
                }, $modifiers))
                .' '.$this->debug->utilities->buildTag(
                    'span',
                    array(
                        'class' => 't_type',
                        'title' => $info['phpDoc']['return']['desc'],
                    ),
                    $info['phpDoc']['return']['type']
                )
                .' '.$this->debug->utilities->buildTag(
                    'span',
                    array(
                        'class' => 'method-name',
                        'title' => \trim($info['phpDoc']['summary']
                            .($this->debug->output->getCfg('outputMethodDescription')
                                ? "\n\n".$info['phpDoc']['description']
                                : '')),
                    ),
                    $methodName
                )
                .'('
                .$this->dumpMethodParams($info['params'])
                .')'
                .($methodName == '__toString'
                    ? '
'.$this->debug->output->html->dump($info['returnValue'])
                    : '')
            )."\n";
        }
        $str = \str_replace(' data-implements="null"', '', $str);
        $str = \str_replace(' ', '', $str);
        return $str;
    }

    /**
     * Dump method parameters as HTML
     *
     * @param array $params params as returned from getParams()
     *
     * @return string html
     */
    protected function dumpMethodParams($params)
    {
        $paramStr = '';
        foreach ($params as $info) {
            $paramStr .= '';
            if (!empty($info['type'])) {
                $paramStr .= ''.$info['type'].' ';
            }
            $paramStr .= ''.\htmlspecialchars($info['name']).'';
            if ($info['defaultValue'] !== $this->debug->abstracter->UNDEFINED) {
                $defaultValue = $info['defaultValue'];
                $paramStr .= ' = ';
                if ($info['constantName']) {
                    /*
                        only php >= 5.4.6 supports this...
                        or via @method phpDoc

                        show the constant name / hover for value
                    */
                    $title = '';
                    $type = $this->debug->abstracter->getType($defaultValue);
                    if (!\in_array($type, array('array','resource'))) {
                        $title = $this->debug->output->text->dump($defaultValue);
                        $title = \htmlspecialchars('value: '.$title);
                    }
                    $paramStr .= ''.$info['constantName'].'';
                } else {
                    /*
                        The constant's value is shown
                    */
                    if (\is_string($defaultValue)) {
                        $defaultValue = \str_replace("\n", ' ', $defaultValue);
                    }
                    $parsed = $this->debug->utilities->parseTag($this->debug->output->html->dump($defaultValue));
                    $class = \trim('t_parameter-default '.$parsed['attribs']['class']);
                    $paramStr .= ''.$parsed['innerhtml'].'';
                }
            }
            $paramStr .= ', '; // end .parameter
        }
        $paramStr = \trim($paramStr, ', ');
        return $paramStr;
    }

    /**
     * Dump object's phpDoc info as html
     *
     * @param array $phpDoc parsed phpDoc
     *
     * @return string html
     */
    protected function dumpPhpDoc($phpDoc)
    {
        $str = '';
        foreach ($phpDoc as $k => $values) {
            if (!\is_array($values)) {
                continue;
            }
            foreach ($values as $value) {
                if ($k == 'link') {
                    $value = ''
                        .\htmlspecialchars($value['desc'] ?: $value['uri'])
                        .'';
                } elseif ($k == 'see' && $value['uri']) {
                    $value = ''
                        .\htmlspecialchars($value['desc'] ?: $value['uri'])
                        .'';
                } else {
                    $value = \htmlspecialchars(\implode(' ', $value));
                }
                $str .= '
'
                    .''.$k.''
                    .': '
                    .$value
                    .'
'."\n";
            }
        }
        if ($str) {
            $str = '
phpDoc
'."\n"
                .$str;
        }
        return $str;
    }

    /**
     * Dump object properties as HTML
     *
     * @param array $abs object abstraction
     *
     * @return string
     */
    protected function dumpProperties($abs)
    {
        $label = \count($abs['properties'])
            ? 'properties'
            : 'no properties';
        if ($abs['viaDebugInfo']) {
            $label .= ' (via __debugInfo)';
        }
        $str = '
'.$label.'
'."\n";
        $magicMethods = \array_intersect(array('__get','__set'), \array_keys($abs['methods']));
        $str .= $this->magicMethodInfo($magicMethods);
        foreach ($abs['properties'] as $k => $info) {
            $vis = (array) $info['visibility'];
            $isPrivateAncestor = \in_array('private', $vis) && $info['inheritedFrom'];
            $classes = \array_keys(\array_filter(array(
                'debuginfo-value' => $info['valueFrom'] == 'debugInfo',
                'excluded' => $info['isExcluded'],
                'forceShow' => $info['forceShow'],
                'debug-value' => $info['valueFrom'] == 'debug',
                'private-ancestor' => $isPrivateAncestor,
                'property' => true,
                \implode(' ', $vis) => $info['visibility'] !== 'debug',
            )));
            $modifiers = $vis;
            if ($info['isStatic']) {
                $modifiers[] = 'static';
            }
            $str .= '
'
                .\implode(' ', \array_map(function ($modifier) {
                    return ''.$modifier.'';
                }, $modifiers))
                .($isPrivateAncestor
                    ? ' ('.$info['inheritedFrom'].')'
                    : '')
                .($info['type']
                    ? ' '.$info['type'].''
                    : '')
                .' '.$k.''
                .($info['value'] !== $this->debug->abstracter->UNDEFINED
                    ? ' = '
                        .$this->debug->output->html->dump($info['value'])
                    : '')
                .'
'."\n";
        }
        return $str;
    }

    /**
     * Generate some info regarding the given method names
     *
     * @param string[] $methods method names
     *
     * @return string
     */
    private function magicMethodInfo($methods)
    {
        if (!$methods) {
            return '';
        }
        foreach ($methods as $i => $method) {
            $methods[$i] = ''.$method.'';
        }
        $methods = $i == 0
            ? 'a '.$methods[0].' method'
            : \implode(' and ', $methods).' methods';
        return '
This object has '.$methods.'
'."\n";
    }
}