MOON
Server: Apache
System: Linux res.emeff.ca 3.10.0-962.3.2.lve1.5.24.10.el7.x86_64 #1 SMP Wed Mar 20 07:36:02 EDT 2019 x86_64
User: accemeff (1004)
PHP: 7.0.33
Disabled: NONE
Upload Files
File: /home/accemeff/vendor/craftcms/cms/src/services/Routes.php
<?php
/**
 * @link https://craftcms.com/
 * @copyright Copyright (c) Pixel & Tonic, Inc.
 * @license https://craftcms.github.io/license/
 */

namespace craft\services;

use Craft;
use craft\events\DeleteSiteEvent;
use craft\events\RouteEvent;
use craft\helpers\ArrayHelper;
use craft\helpers\StringHelper;
use yii\base\Component;

/**
 * Routes service.
 * An instance of the Routes service is globally accessible in Craft via [[\craft\base\ApplicationTrait::getRoutes()|`Craft::$app->routes`]].
 *
 * @author Pixel & Tonic, Inc. <support@pixelandtonic.com>
 * @since 3.0
 */
class Routes extends Component
{
    // Constants
    // =========================================================================

    /**
     * @event RouteEvent The event that is triggered before a route is saved.
     */
    const EVENT_BEFORE_SAVE_ROUTE = 'beforeSaveRoute';

    /**
     * @event RouteEvent The event that is triggered after a route is saved.
     */
    const EVENT_AFTER_SAVE_ROUTE = 'afterSaveRoute';

    /**
     * @event RouteEvent The event that is triggered before a route is deleted.
     */
    const EVENT_BEFORE_DELETE_ROUTE = 'beforeDeleteRoute';

    /**
     * @event RouteEvent The event that is triggered after a route is deleted.
     */
    const EVENT_AFTER_DELETE_ROUTE = 'afterDeleteRoute';

    const CONFIG_ROUTES_KEY = 'routes';

    // Properties
    // =========================================================================
    /**
     * @var array|null all the routes in project config for current site
     */
    private $_projectConfigRoutes;

    // Public Methods
    // =========================================================================

    /**
     * Returns the routes defined in `config/routes.php`
     *
     * @return array
     */
    public function getConfigFileRoutes(): array
    {
        $path = Craft::$app->getPath()->getConfigPath() . DIRECTORY_SEPARATOR . 'routes.php';

        if (!file_exists($path)) {
            return [];
        }

        $routes = require $path;

        if (!is_array($routes)) {
            return [];
        }

        // Check for any site-specific routes
        $sitesService = Craft::$app->getSites();
        foreach ($sitesService->getAllSites() as $site) {
            if (
                isset($routes[$site->handle]) &&
                is_array($routes[$site->handle]) &&
                !isset($routes[$site->handle]['route']) &&
                !isset($routes[$site->handle]['template'])
            ) {
                $siteRoutes = ArrayHelper::remove($routes, $site->handle);

                /** @noinspection PhpUnhandledExceptionInspection */
                if ($site->handle === $sitesService->getCurrentSite()->handle) {
                    // Merge them so that the localized routes come first
                    $routes = array_merge($siteRoutes, $routes);
                }
            }
        }

        return $routes;
    }

    /**
     * Returns the routes defined in the CP.
     *
     * @return array
     * @deprecated in 3.1. Use [[\craft\services\Routes::getProjectConfigRoutes()]] instead.
     */
    public function getDbRoutes(): array
    {
        return $this->getProjectConfigRoutes();
    }

    /**
     * Returns the routes defined in the project config.
     *
     * @return array
     */
    public function getProjectConfigRoutes(): array
    {
        if ($this->_projectConfigRoutes === null) {
            $routes = Craft::$app->getProjectConfig()->get(self::CONFIG_ROUTES_KEY) ?? [];
            $currentSiteUid = Craft::$app->getSites()->getCurrentSite()->uid;
            $this->_projectConfigRoutes = [];

            $sortedRoutes = [];

            foreach ($routes as $route) {
                $sortedRoutes[$route['sortOrder']] = $route;
            }

            ksort($sortedRoutes);
            $routes = array_values($sortedRoutes);

            foreach ($routes as $route) {
                if (empty($route['site']) || $route['site'] == $currentSiteUid) {
                    $this->_projectConfigRoutes[$route['uriPattern']] = ['template' => $route['template']];
                }
            }
        }

        return $this->_projectConfigRoutes;
    }

    /**
     * Saves a new or existing route.
     *
     * @param array $uriParts The URI as defined by the user. This is an array where each element is either a
     * string or an array containing the name of a subpattern and the subpattern
     * @param string $template The template to route matching requests to
     * @param string|null $siteUid The site UID the route should be limited to, if any
     * @param string|null $routeUid The route UID, if editing an existing route
     * @return string $routeUid The route UID.
     */
    public function saveRoute(array $uriParts, string $template, string $siteUid = null, string $routeUid = null): string
    {
        // Fire a 'beforeSaveRoute' event
        if ($this->hasEventHandlers(self::EVENT_BEFORE_SAVE_ROUTE)) {
            $this->trigger(self::EVENT_BEFORE_SAVE_ROUTE, new RouteEvent([
                'uriParts' => $uriParts,
                'template' => $template,
                'siteUid' => $siteUid,
            ]));
        }

        $projectConfig = Craft::$app->getProjectConfig();

        if ($routeUid !== null) {
            $sortOrder = $projectConfig->get(self::CONFIG_ROUTES_KEY . '.' . $routeUid . '.sortOrder') ?? $this->_getMaxSortOrder();
        } else {
            $routeUid = StringHelper::UUID();
            $sortOrder = $this->_getMaxSortOrder();;
        }

        // Compile the URI parts into a regex pattern
        $uriPattern = '';
        $uriParts = array_filter($uriParts);
        $subpatternNameCounts = [];

        foreach ($uriParts as $part) {
            if (is_string($part)) {
                $uriPattern .= $part;
            } else if (is_array($part)) {
                // Is the name a valid handle?
                if (preg_match('/^[a-zA-Z]\w*$/', $part[0])) {
                    $subpatternName = $part[0];
                } else {
                    $subpatternName = 'any';
                }

                // Make sure it's unique
                if (isset($subpatternNameCounts[$subpatternName])) {
                    $subpatternNameCounts[$subpatternName]++;

                    // Append the count to the end of the name
                    $subpatternName .= $subpatternNameCounts[$subpatternName];
                } else {
                    $subpatternNameCounts[$subpatternName] = 1;
                }

                // Add the var as a named subpattern
                $uriPattern .= "<{$subpatternName}:{$part[1]}>";
            }
        }

        $configData = [
            'template' => $template,
            'uriParts' => $uriParts,
            'uriPattern' => $uriPattern,
            'sortOrder' => $sortOrder,
            'siteUid' => $siteUid
        ];

        $projectConfig->set(self::CONFIG_ROUTES_KEY . '.' . $routeUid, $configData);

        // Fire an 'afterSaveRoute' event
        if ($this->hasEventHandlers(self::EVENT_AFTER_SAVE_ROUTE)) {
            $this->trigger(self::EVENT_AFTER_SAVE_ROUTE, new RouteEvent([
                'uriParts' => $uriParts,
                'template' => $template,
                'siteUid' => $siteUid,
            ]));
        }

        return $routeUid;
    }

    /**
     * Deletes a route by its ID.
     *
     * @param int $routeId
     * @return bool
     */
    public function deleteRouteByUid(string $routeUid): bool
    {
        $route = Craft::$app->getProjectConfig()->get(self::CONFIG_ROUTES_KEY . '.' . $routeUid);

        if ($route) {
            // Fire a 'beforeDeleteRoute' event
            if ($this->hasEventHandlers(self::EVENT_BEFORE_DELETE_ROUTE)) {
                $this->trigger(self::EVENT_BEFORE_DELETE_ROUTE, new RouteEvent([
                    'uriParts' => $route['uriParts'],
                    'template' => $route['template'],
                    'siteUid' => $route['siteUid'],
                ]));
            }

            $route = Craft::$app->getProjectConfig()->remove(self::CONFIG_ROUTES_KEY . '.' . $routeUid);

            // Fire an 'afterDeleteRoute' event
            if ($this->hasEventHandlers(self::EVENT_AFTER_DELETE_ROUTE)) {
                $this->trigger(self::EVENT_AFTER_DELETE_ROUTE, new RouteEvent([
                    'uriParts' => $route['uriParts'],
                    'template' => $route['template'],
                    'siteUid' => $route['siteUid'],
                ]));
            }
        }

        return true;
    }

    /**
     * Handle a deleted site when it affects existing routes
     *
     * @param DeleteSiteEvent $event
     */
    public function handleDeletedSite(DeleteSiteEvent $event)
    {
        $projectConfig = Craft::$app->getProjectConfig();
        $routes = $projectConfig->get(self::CONFIG_ROUTES_KEY) ?? [];

        foreach ($routes as $routeUid => $route) {
            if ($route['siteUid'] === $event->site->uid) {
                $projectConfig->remove(self::CONFIG_ROUTES_KEY . '.' . $routeUid);
            }
        }
    }

    /**
     * Updates the route order.
     *
     * @param array $routeIds An array of each of the route IDs, in their new order.
     */
    public function updateRouteOrder(array $routeUids)
    {
        foreach ($routeUids as $order => $routeUid) {
            Craft::$app->getProjectConfig()->set(self::CONFIG_ROUTES_KEY . '.' . $routeUid . '.sortOrder', $order + 1);
        }
    }

    /**
     * Return the current max sort order for routes.
     *
     * @return int
     */
    private function _getMaxSortOrder(): int
    {
        $routes = Craft::$app->getProjectConfig()->get(self::CONFIG_ROUTES_KEY) ?? [];
        $max = 0;

        foreach ($routes as $route) {
            $max = max($max, $route['sortOrder']);
        }

        return (int)$max + 1;
    }
}