mirror of
https://github.com/LBRYFoundation/lbry.com.git
synced 2025-08-23 17:47:26 +00:00
routing
This commit is contained in:
parent
a05ac0bfa9
commit
0bc617a0b9
23 changed files with 1100 additions and 196 deletions
|
@ -6,8 +6,8 @@ class Controller
|
|||
{
|
||||
try
|
||||
{
|
||||
$viewAndParams = static::execute($uri);
|
||||
$viewTemplate = $viewAndParams[0];
|
||||
$viewAndParams = static::execute(Request::getMethod(), $uri);
|
||||
$viewTemplate = $viewAndParams[0];
|
||||
$viewParameters = $viewAndParams[1] ?? [];
|
||||
if (!IS_PRODUCTION && isset($viewAndParams[2]))
|
||||
{
|
||||
|
@ -46,96 +46,86 @@ class Controller
|
|||
}
|
||||
}
|
||||
|
||||
public static function execute($uri)
|
||||
public static function execute($method, $uri)
|
||||
{
|
||||
switch($uri)
|
||||
$router = static::getRouterWithRoutes();
|
||||
try
|
||||
{
|
||||
case '/':
|
||||
return ContentActions::executeHome();
|
||||
case '/get':
|
||||
case '/windows':
|
||||
case '/ios':
|
||||
case '/android':
|
||||
case '/linux':
|
||||
case '/osx':
|
||||
return DownloadActions::executeGet();
|
||||
case '/postcommit':
|
||||
return OpsActions::executePostCommit();
|
||||
case '/log-upload':
|
||||
return OpsActions::executeLogUpload();
|
||||
case '/list-subscribe':
|
||||
return MailActions::executeListSubscribe();
|
||||
case '/press-kit.zip':
|
||||
return ContentActions::executePressKit();
|
||||
case '/LBRY-deck.pdf':
|
||||
case '/deck.pdf':
|
||||
return static::redirect('https://www.dropbox.com/s/0xj4vgucsbi8rtv/lbry-deck.pdf?dl=1');
|
||||
case '/pln.pdf':
|
||||
case '/plan.pdf':
|
||||
return static::redirect('https://www.dropbox.com/s/uevjrwnyr672clj/lbry-pln.pdf?dl=1');
|
||||
case '/lbry-osx-latest.dmg':
|
||||
case '/lbry-linux-latest.deb':
|
||||
case '/dl/lbry_setup.sh':
|
||||
return static::redirect('/get', 301);
|
||||
case '/get/lbry.dmg':
|
||||
return static::redirect(DownloadActions::getDownloadUrl(DownloadActions::OS_OSX) ?: '/get');
|
||||
case '/get/lbry.deb':
|
||||
return static::redirect(DownloadActions::getDownloadUrl(DownloadActions::OS_LINUX) ?: '/get');
|
||||
case '/art':
|
||||
return static::redirect('/what', 301);
|
||||
case '/why':
|
||||
case '/feedback':
|
||||
return static::redirect('/learn', 301);
|
||||
case '/faq/when-referral-payouts':
|
||||
return static::redirect('/faq/referrals', 301);
|
||||
$dispatcher = new Routing\Dispatcher($router->getData());
|
||||
return $dispatcher->dispatch($method, $uri);
|
||||
}
|
||||
|
||||
$newsPattern = '#^' . ContentActions::URL_NEWS . '(/|$)#';
|
||||
if (preg_match($newsPattern, $uri))
|
||||
catch (\Routing\HttpRouteNotFoundException $e)
|
||||
{
|
||||
Response::enableHttpCache();
|
||||
$slug = preg_replace($newsPattern, '', $uri);
|
||||
if ($slug == ContentActions::RSS_SLUG)
|
||||
return NavActions::execute404();
|
||||
}
|
||||
}
|
||||
|
||||
protected static function getRouterWithRoutes(): \Routing\RouteCollector
|
||||
{
|
||||
$router = new Routing\RouteCollector();
|
||||
|
||||
$router->get(['/', 'home'], 'ContentActions::executeHome');
|
||||
|
||||
$router->get(['/get', 'get'], 'DownloadActions::executeGet');
|
||||
$router->get(['/windows', 'get-windows'], 'DownloadActions::executeGet');
|
||||
$router->get(['/linux', 'get-linux'], 'DownloadActions::executeGet');
|
||||
$router->get(['/osx', 'get-osx'], 'DownloadActions::executeGet');
|
||||
$router->get(['/android', 'get-android'], 'DownloadActions::executeGet');
|
||||
$router->get(['/ios', 'get-ios'], 'DownloadActions::executeGet');
|
||||
|
||||
$router->get(['/press-kit.zip', 'press-kit'], 'ContentActions::executePressKit');
|
||||
|
||||
$router->post('/postcommit', 'OpsActions::executePostCommit');
|
||||
$router->post('/log-upload', 'OpsActions::executeLogUpload');
|
||||
$router->post(['/list-subscribe', 'list-subscribe'], 'MailActions::executeListSubscribe');
|
||||
|
||||
$permanentRedirects = [
|
||||
'/lbry-osx-latest.dmg' => '/get',
|
||||
'/lbry-linux-latest.deb' => '/get',
|
||||
'/dl/lbry_setup.sh' => '/get',
|
||||
'/art' => '/what',
|
||||
'/why' => '/learn',
|
||||
'/feedback' => '/learn',
|
||||
'/faq/when-referral-payouts' => '/faq/referrals',
|
||||
];
|
||||
|
||||
$tempRedirects = [
|
||||
'/LBRY-deck.pdf' => 'https://www.dropbox.com/s/0xj4vgucsbi8rtv/lbry-deck.pdf?dl=1',
|
||||
'/deck.pdf' => 'https://www.dropbox.com/s/0xj4vgucsbi8rtv/lbry-deck.pdf?dl=1',
|
||||
'/pln.pdf' => 'https://www.dropbox.com/s/uevjrwnyr672clj/lbry-pln.pdf?dl=1',
|
||||
'/plan.pdf' => 'https://www.dropbox.com/s/uevjrwnyr672clj/lbry-pln.pdf?dl=1',
|
||||
'/get/lbry.dmg' => DownloadActions::getDownloadUrl(DownloadActions::OS_OSX) ?: '/get',
|
||||
'/get/lbry.deb' => DownloadActions::getDownloadUrl(DownloadActions::OS_LINUX) ?: '/get',
|
||||
];
|
||||
|
||||
foreach ([302 => $tempRedirects, 301 => $permanentRedirects] as $code => $redirects)
|
||||
{
|
||||
foreach ($redirects as $src => $target)
|
||||
{
|
||||
return ContentActions::executeRss();
|
||||
$router->any($src, function () use ($target, $code) { return static::redirect($target, $code); });
|
||||
}
|
||||
return $slug ? ContentActions::executeNewsPost($uri) : ContentActions::executeNews();
|
||||
}
|
||||
|
||||
$faqPattern = '#^' . ContentActions::URL_FAQ . '(/|$)#';
|
||||
if (preg_match($faqPattern, $uri))
|
||||
{
|
||||
Response::enableHttpCache();
|
||||
$slug = preg_replace($faqPattern, '', $uri);
|
||||
return $slug ? ContentActions::executeFaqPost($uri) : ContentActions::executeFaq();
|
||||
}
|
||||
$router->get([ContentActions::URL_NEWS . '/{slug:c}?', 'news'], 'ContentActions::executeNews');
|
||||
$router->get([ContentActions::URL_FAQ . '/{slug:c}?', 'faq'], 'ContentActions::executeFaq');
|
||||
$router->get([BountyActions::URL_BOUNTY . '/{slug:c}?', 'bounty'], 'BountyActions::executeShow');
|
||||
|
||||
$bountyPattern = '#^' . BountyActions::URL_BOUNTY_LIST . '(/|$)#';
|
||||
if (preg_match($bountyPattern, $uri))
|
||||
{
|
||||
Response::enableHttpCache();
|
||||
$slug = preg_replace($bountyPattern, '', $uri);
|
||||
return $slug ? BountyActions::executeShow($uri) : BountyActions::executeList($uri);
|
||||
}
|
||||
$router->any(['/signup{whatever}?', 'signup'], 'DownloadActions::executeSignup');
|
||||
|
||||
$accessPattern = '#^/signup#';
|
||||
if (preg_match($accessPattern, $uri))
|
||||
$router->get('/{slug}', function (string $slug)
|
||||
{
|
||||
return DownloadActions::executeSignup();
|
||||
}
|
||||
if (View::exists('page/' . $slug))
|
||||
{
|
||||
Response::enableHttpCache();
|
||||
return ['page/' . $slug, []];
|
||||
}
|
||||
else
|
||||
{
|
||||
return NavActions::execute404();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$noSlashUri = ltrim($uri, '/');
|
||||
if (View::exists('page/' . $noSlashUri))
|
||||
{
|
||||
Response::enableHttpCache();
|
||||
return ['page/' . $noSlashUri, []];
|
||||
}
|
||||
else
|
||||
{
|
||||
Response::setStatus(404);
|
||||
return ['page/404', []];
|
||||
}
|
||||
return $router;
|
||||
}
|
||||
|
||||
public static function redirect($url, $statusCode = 302)
|
||||
|
|
|
@ -54,7 +54,7 @@ class Request
|
|||
|
||||
public static function getRelativeUri(): string
|
||||
{
|
||||
return $_SERVER['REQUEST_URI'] ?? '';
|
||||
return isset($_SERVER['REQUEST_URI']) ? parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) : '';
|
||||
}
|
||||
|
||||
public static function isGzipAccepted(): bool
|
||||
|
|
|
@ -2,10 +2,29 @@
|
|||
|
||||
class BountyActions extends Actions
|
||||
{
|
||||
const URL_BOUNTY_LIST = '/bounty';
|
||||
const
|
||||
SLUG_BOUNTY = 'bounty',
|
||||
URL_BOUNTY = '/' . self::SLUG_BOUNTY,
|
||||
VIEW_FOLDER_FAQ = ROOT_DIR . '/posts/' . self::SLUG_BOUNTY;
|
||||
|
||||
public static function executeList()
|
||||
public static function executeList(string $slug = null): array
|
||||
{
|
||||
Response::enableHttpCache();
|
||||
|
||||
if ($slug)
|
||||
{
|
||||
list($metadata, $postHtml) = View::parseMarkdown(static::VIEW_FOLDER_FAQ . '/' . $slug . '.md');
|
||||
if (!$postHtml)
|
||||
{
|
||||
return NavActions::execute404();
|
||||
}
|
||||
|
||||
return ['bounty/show', [
|
||||
'postHtml' => $postHtml,
|
||||
'metadata' => $metadata
|
||||
]];
|
||||
}
|
||||
|
||||
$allBounties = Post::find(ROOT_DIR . '/posts/bounty');
|
||||
|
||||
$allCategories = ['' => ''] + Post::collectMetadata($allBounties, 'category');
|
||||
|
@ -41,17 +60,4 @@ class BountyActions extends Actions
|
|||
'selectedStatus' => $selectedStatus
|
||||
]];
|
||||
}
|
||||
|
||||
public static function executeShow($relativeUri)
|
||||
{
|
||||
list($metadata, $postHtml) = View::parseMarkdown(ROOT_DIR . '/posts/' . $relativeUri . '.md');
|
||||
if (!$postHtml)
|
||||
{
|
||||
return ['page/404', []];
|
||||
}
|
||||
return ['bounty/show', [
|
||||
'postHtml' => $postHtml,
|
||||
'metadata' => $metadata
|
||||
]];
|
||||
}
|
||||
}
|
|
@ -2,11 +2,16 @@
|
|||
|
||||
class ContentActions extends Actions
|
||||
{
|
||||
const RSS_SLUG = 'rss.xml',
|
||||
URL_NEWS = '/news',
|
||||
URL_FAQ = '/faq',
|
||||
VIEW_FOLDER_NEWS = ROOT_DIR . '/posts/news',
|
||||
VIEW_FOLDER_FAQ = ROOT_DIR . '/posts/faq';
|
||||
const
|
||||
SLUG_RSS = 'rss.xml',
|
||||
SLUG_NEWS = 'news',
|
||||
SLUG_FAQ = 'faq',
|
||||
|
||||
URL_NEWS = '/' . self::SLUG_NEWS,
|
||||
URL_FAQ = '/' . self::SLUG_FAQ,
|
||||
|
||||
VIEW_FOLDER_NEWS = ROOT_DIR . '/posts/' . self::SLUG_NEWS,
|
||||
VIEW_FOLDER_FAQ = ROOT_DIR . '/posts/' . self::SLUG_FAQ;
|
||||
|
||||
public static function executeHome(): array
|
||||
{
|
||||
|
@ -14,104 +19,107 @@ class ContentActions extends Actions
|
|||
return ['page/home'];
|
||||
}
|
||||
|
||||
public static function executeFaq(): array
|
||||
public static function executeNews(string $slug = null): array
|
||||
{
|
||||
$allPosts = Post::find(static::VIEW_FOLDER_FAQ);
|
||||
Response::enableHttpCache();
|
||||
|
||||
$allCategories = array_merge(['' => ''] + Post::collectMetadata($allPosts, 'category'), [
|
||||
'getstarted' => 'Getting Started',
|
||||
'install' => 'Installing LBRY',
|
||||
'running' => 'Running LBRY',
|
||||
'wallet' => 'The LBRY Wallet',
|
||||
'hosting' => 'Hosting Content',
|
||||
'mining' => 'Mining LBC',
|
||||
'policy' => 'Policies',
|
||||
'developer' => 'Developers',
|
||||
'other' => 'Other Questions',
|
||||
]);
|
||||
$selectedCategory = static::param('category');
|
||||
$filters = array_filter([
|
||||
'category' => $selectedCategory && isset($allCategories[$selectedCategory]) ? $selectedCategory : null,
|
||||
]);
|
||||
|
||||
asort($allCategories);
|
||||
|
||||
$posts = $filters ? Post::filter($allPosts, $filters) : $allPosts ;
|
||||
|
||||
|
||||
$groups = array_fill_keys(array_keys($allCategories), []);
|
||||
|
||||
foreach($posts as $post)
|
||||
if (!$slug)
|
||||
{
|
||||
$groups[$post->getCategory()][] = $post;
|
||||
$posts = Post::find(static::VIEW_FOLDER_NEWS, Post::SORT_DATE_DESC);
|
||||
return ['content/news', [
|
||||
'posts' => $posts,
|
||||
View::LAYOUT_PARAMS => [
|
||||
'showRssLink' => true
|
||||
]
|
||||
]];
|
||||
}
|
||||
|
||||
return ['content/faq', [
|
||||
'categories' => $allCategories,
|
||||
'selectedCategory' => $selectedCategory,
|
||||
'postGroups' => $groups
|
||||
]];
|
||||
}
|
||||
if ($slug == static::SLUG_RSS)
|
||||
{
|
||||
$posts = Post::find(static::VIEW_FOLDER_NEWS, Post::SORT_DATE_DESC);
|
||||
Response::setHeader(Response::HEADER_CONTENT_TYPE, 'text/xml; charset=utf-8');
|
||||
return ['content/rss', [
|
||||
'posts' => array_slice($posts, 0, 10),
|
||||
'_no_layout' => true
|
||||
]];
|
||||
}
|
||||
|
||||
public static function executeNews(): array
|
||||
{
|
||||
$posts = Post::find(static::VIEW_FOLDER_NEWS, Post::SORT_DATE_DESC);
|
||||
return ['content/news', [
|
||||
'posts' => $posts,
|
||||
View::LAYOUT_PARAMS => [
|
||||
'showRssLink' => true
|
||||
]
|
||||
]];
|
||||
}
|
||||
|
||||
|
||||
public static function executeRss(): array
|
||||
{
|
||||
$posts = Post::find(static::VIEW_FOLDER_NEWS, Post::SORT_DATE_DESC);
|
||||
Response::setHeader(Response::HEADER_CONTENT_TYPE, 'text/xml; charset=utf-8');
|
||||
return ['content/rss', [
|
||||
'posts' => array_slice($posts, 0, 10),
|
||||
'_no_layout' => true
|
||||
]];
|
||||
}
|
||||
|
||||
public static function executeNewsPost($relativeUri): array
|
||||
{
|
||||
try
|
||||
{
|
||||
$post = Post::load(ltrim($relativeUri, '/'));
|
||||
$post = Post::load(static::SLUG_NEWS . '/' . ltrim($slug, '/'));
|
||||
}
|
||||
catch (PostNotFoundException $e)
|
||||
{
|
||||
return ['page/404', []];
|
||||
return NavActions::execute404();
|
||||
}
|
||||
|
||||
return ['content/news-post', [
|
||||
'post' => $post,
|
||||
'post' => $post,
|
||||
View::LAYOUT_PARAMS => [
|
||||
'showRssLink' => true
|
||||
]
|
||||
]];
|
||||
}
|
||||
|
||||
public static function executeFaqPost($relativeUri): array
|
||||
|
||||
public static function executeFaq(string $slug = null): array
|
||||
{
|
||||
Response::enableHttpCache();
|
||||
|
||||
if (!$slug)
|
||||
{
|
||||
$allPosts = Post::find(static::VIEW_FOLDER_FAQ);
|
||||
|
||||
$allCategories = array_merge(['' => ''] + Post::collectMetadata($allPosts, 'category'), [
|
||||
'getstarted' => 'Getting Started',
|
||||
'install' => 'Installing LBRY',
|
||||
'running' => 'Running LBRY',
|
||||
'wallet' => 'The LBRY Wallet',
|
||||
'hosting' => 'Hosting Content',
|
||||
'mining' => 'Mining LBC',
|
||||
'policy' => 'Policies',
|
||||
'developer' => 'Developers',
|
||||
'other' => 'Other Questions',
|
||||
]);
|
||||
$selectedCategory = static::param('category');
|
||||
$filters = array_filter([
|
||||
'category' => $selectedCategory && isset($allCategories[$selectedCategory]) ? $selectedCategory : null,
|
||||
]);
|
||||
|
||||
asort($allCategories);
|
||||
|
||||
$posts = $filters ? Post::filter($allPosts, $filters) : $allPosts;
|
||||
|
||||
|
||||
$groups = array_fill_keys(array_keys($allCategories), []);
|
||||
|
||||
foreach ($posts as $post)
|
||||
{
|
||||
$groups[$post->getCategory()][] = $post;
|
||||
}
|
||||
|
||||
return ['content/faq', [
|
||||
'categories' => $allCategories,
|
||||
'selectedCategory' => $selectedCategory,
|
||||
'postGroups' => $groups
|
||||
]];
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$post = Post::load(ltrim($relativeUri, '/'));
|
||||
$post = Post::load(static::SLUG_FAQ . '/' . ltrim($slug, '/'));
|
||||
}
|
||||
catch (PostNotFoundException $e)
|
||||
{
|
||||
return ['page/404', []];
|
||||
return NavActions::execute404();
|
||||
}
|
||||
return ['content/faq-post', [
|
||||
'post' => $post,
|
||||
]];
|
||||
return ['content/faq-post', ['post' => $post,]];
|
||||
}
|
||||
|
||||
public static function executePressKit(): array
|
||||
{
|
||||
$zipFileName = 'lbry-press-kit-' . date('Y-m-d') . '.zip';
|
||||
$zipPath = tempnam('/tmp', $zipFileName);
|
||||
$zipPath = tempnam('/tmp', $zipFileName);
|
||||
|
||||
$zip = new ZipArchive();
|
||||
$zip->open($zipPath, ZipArchive::OVERWRITE);
|
||||
|
@ -133,25 +141,26 @@ class ContentActions extends Actions
|
|||
//
|
||||
// $zip->addFromString('press.html', $html);
|
||||
|
||||
foreach(glob(ROOT_DIR . '/web/img/press/*') as $productImgPath)
|
||||
foreach (glob(ROOT_DIR . '/web/img/press/*') as $productImgPath)
|
||||
{
|
||||
$imgPathTokens = explode('/', $productImgPath);
|
||||
$imgName = $imgPathTokens[count($imgPathTokens) - 1];
|
||||
$imgName = $imgPathTokens[count($imgPathTokens) - 1];
|
||||
$zip->addFile($productImgPath, '/logo_and_product/' . $imgName);
|
||||
}
|
||||
|
||||
foreach(glob(ROOT_DIR . '/posts/bio/*.md') as $bioPath)
|
||||
foreach (glob(ROOT_DIR . '/posts/bio/*.md') as $bioPath)
|
||||
{
|
||||
list($metadata, $bioHtml) = View::parseMarkdown($bioPath);
|
||||
$zip->addFile($bioPath, '/team_bios/' . $metadata['name'] . ' - ' . $metadata['role'] . '.txt');
|
||||
}
|
||||
|
||||
foreach(array_filter(glob(ROOT_DIR . '/web/img/team/*.jpg'), function($path) {
|
||||
foreach (array_filter(glob(ROOT_DIR . '/web/img/team/*.jpg'), function ($path)
|
||||
{
|
||||
return strpos($path, 'spooner') === false;
|
||||
}) as $bioImgPath)
|
||||
{
|
||||
$imgPathTokens = explode('/', $bioImgPath);
|
||||
$imgName = str_replace('644x450', 'lbry', $imgPathTokens[count($imgPathTokens) - 1]);
|
||||
$imgName = str_replace('644x450', 'lbry', $imgPathTokens[count($imgPathTokens) - 1]);
|
||||
$zip->addFile($bioImgPath, '/team_photos/' . $imgName);
|
||||
}
|
||||
|
||||
|
@ -163,20 +172,20 @@ class ContentActions extends Actions
|
|||
|
||||
return ['internal/zip', [
|
||||
'_no_layout' => true,
|
||||
'zipPath' => $zipPath
|
||||
'zipPath' => $zipPath
|
||||
]];
|
||||
}
|
||||
|
||||
public static function prepareBioPartial(array $vars): array
|
||||
{
|
||||
$person = $vars['person'];
|
||||
$path = 'bio/' . $person . '.md';
|
||||
$path = 'bio/' . $person . '.md';
|
||||
list($metadata, $bioHtml) = View::parseMarkdown($path);
|
||||
$relativeImgSrc = '/img/team/' . $person . '-644x450.jpg';
|
||||
$imgSrc = file_exists(ROOT_DIR . '/web' . $relativeImgSrc) ? $relativeImgSrc : '/img/team/spooner-644x450.jpg';
|
||||
$imgSrc = file_exists(ROOT_DIR . '/web' . $relativeImgSrc) ? $relativeImgSrc : '/img/team/spooner-644x450.jpg';
|
||||
return $vars + $metadata + [
|
||||
'imgSrc' => $imgSrc,
|
||||
'bioHtml' => $bioHtml,
|
||||
'imgSrc' => $imgSrc,
|
||||
'bioHtml' => $bioHtml,
|
||||
'orientation' => 'vertical'
|
||||
];
|
||||
}
|
||||
|
@ -185,8 +194,8 @@ class ContentActions extends Actions
|
|||
{
|
||||
$post = $vars['post'];
|
||||
return [
|
||||
'authorName' => $post->getAuthorName(),
|
||||
'photoImgSrc' => $post->getAuthorPhoto(),
|
||||
'authorName' => $post->getAuthorName(),
|
||||
'photoImgSrc' => $post->getAuthorPhoto(),
|
||||
'authorBioHtml' => $post->getAuthorBioHtml()
|
||||
];
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ class DownloadActions extends Actions
|
|||
|
||||
if (!Session::get(Session::KEY_DOWNLOAD_ALLOWED))
|
||||
{
|
||||
return ['download/get', ['os' => static::guessOs()]];
|
||||
return ['download/get'];
|
||||
}
|
||||
|
||||
$osChoices = static::getOses();
|
||||
|
@ -169,7 +169,7 @@ class DownloadActions extends Actions
|
|||
protected static function guessOs()
|
||||
{
|
||||
//if exact OS is requested, use that
|
||||
$uri = strtok(Request::getRelativeUri(), '?');
|
||||
$uri = Request::getRelativeUri();
|
||||
foreach (static::getOses() as $os => $osChoice)
|
||||
{
|
||||
if ($osChoice[0] == $uri)
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Description of NavActions
|
||||
*
|
||||
* @author jeremy
|
||||
*/
|
||||
class NavActions extends Actions
|
||||
{
|
||||
protected static $navUri;
|
||||
|
@ -39,4 +34,10 @@ class NavActions extends Actions
|
|||
'isDark' => true
|
||||
];
|
||||
}
|
||||
|
||||
public static function execute404()
|
||||
{
|
||||
Response::setStatus(404);
|
||||
return ['page/404'];
|
||||
}
|
||||
}
|
|
@ -72,8 +72,7 @@ learn:
|
|||
100: LBRY in 100 Seconds
|
||||
art: Art in the Internet Age
|
||||
essay: Read the Essay
|
||||
exchange: Bittrex Exchange
|
||||
exchange2: Poloniex Exchange
|
||||
exchange_faq: Buy/Sell LBRY Credits
|
||||
explore: Explore
|
||||
explorer: Block Explorer
|
||||
how: Learn how LBRY will forever improve how we create and share with one another.
|
||||
|
|
5
lib/routing/BadRouteException.class.php
Normal file
5
lib/routing/BadRouteException.class.php
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?php
|
||||
|
||||
namespace Routing;
|
||||
|
||||
class RouterBadRouteException extends \LogicException {}
|
230
lib/routing/Dispatcher.class.php
Normal file
230
lib/routing/Dispatcher.class.php
Normal file
|
@ -0,0 +1,230 @@
|
|||
<?php
|
||||
|
||||
namespace Routing;
|
||||
|
||||
class Dispatcher
|
||||
{
|
||||
|
||||
private $staticRouteMap;
|
||||
private $variableRouteData;
|
||||
private $filters;
|
||||
private $handlerResolver;
|
||||
public $matchedRoute;
|
||||
|
||||
/**
|
||||
* Create a new route dispatcher.
|
||||
*
|
||||
* @param RouteDataInterface $data
|
||||
* @param HandlerResolverInterface $resolver
|
||||
*/
|
||||
public function __construct(RouteData $data, HandlerResolverInterface $resolver = null)
|
||||
{
|
||||
$this->staticRouteMap = $data->getStaticRoutes();
|
||||
|
||||
$this->variableRouteData = $data->getVariableRoutes();
|
||||
|
||||
$this->filters = $data->getFilters();
|
||||
|
||||
if ($resolver === null)
|
||||
{
|
||||
$this->handlerResolver = new HandlerResolver();
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->handlerResolver = $resolver;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch a route for the given HTTP Method / URI.
|
||||
*
|
||||
* @param $httpMethod
|
||||
* @param $uri
|
||||
*
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function dispatch($httpMethod, $uri)
|
||||
{
|
||||
list($handler, $filters, $vars) = $this->dispatchRoute($httpMethod, trim($uri, '/'));
|
||||
|
||||
list($beforeFilter, $afterFilter) = $this->parseFilters($filters);
|
||||
|
||||
if (($response = $this->dispatchFilters($beforeFilter)) !== null)
|
||||
{
|
||||
return $response;
|
||||
}
|
||||
|
||||
$resolvedHandler = $this->handlerResolver->resolve($handler);
|
||||
|
||||
$response = call_user_func_array($resolvedHandler, $vars);
|
||||
|
||||
return $this->dispatchFilters($afterFilter, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch a route filter.
|
||||
*
|
||||
* @param $filters
|
||||
* @param null $response
|
||||
*
|
||||
* @return mixed|null
|
||||
*/
|
||||
private function dispatchFilters($filters, $response = null)
|
||||
{
|
||||
while ($filter = array_shift($filters))
|
||||
{
|
||||
$handler = $this->handlerResolver->resolve($filter);
|
||||
|
||||
if (($filteredResponse = call_user_func($handler, $response)) !== null)
|
||||
{
|
||||
return $filteredResponse;
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalise the array filters attached to the route and merge with any global filters.
|
||||
*
|
||||
* @param $filters
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function parseFilters($filters)
|
||||
{
|
||||
$beforeFilter = [];
|
||||
$afterFilter = [];
|
||||
|
||||
if (isset($filters[Route::BEFORE]))
|
||||
{
|
||||
$beforeFilter = array_intersect_key($this->filters, array_flip((array)$filters[Route::BEFORE]));
|
||||
}
|
||||
|
||||
if (isset($filters[Route::AFTER]))
|
||||
{
|
||||
$afterFilter = array_intersect_key($this->filters, array_flip((array)$filters[Route::AFTER]));
|
||||
}
|
||||
|
||||
return [$beforeFilter, $afterFilter];
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the route dispatching. Check static routes first followed by variable routes.
|
||||
*
|
||||
* @param $httpMethod
|
||||
* @param $uri
|
||||
*
|
||||
* @throws Exception\HttpRouteNotFoundException
|
||||
*/
|
||||
private function dispatchRoute($httpMethod, $uri)
|
||||
{
|
||||
if (isset($this->staticRouteMap[$uri]))
|
||||
{
|
||||
return $this->dispatchStaticRoute($httpMethod, $uri);
|
||||
}
|
||||
|
||||
return $this->dispatchVariableRoute($httpMethod, $uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the dispatching of static routes.
|
||||
*
|
||||
* @param $httpMethod
|
||||
* @param $uri
|
||||
*
|
||||
* @return mixed
|
||||
* @throws Exception\HttpMethodNotAllowedException
|
||||
*/
|
||||
private function dispatchStaticRoute($httpMethod, $uri)
|
||||
{
|
||||
$routes = $this->staticRouteMap[$uri];
|
||||
|
||||
if (!isset($routes[$httpMethod]))
|
||||
{
|
||||
$httpMethod = $this->checkFallbacks($routes, $httpMethod);
|
||||
}
|
||||
|
||||
return $routes[$httpMethod];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check fallback routes: HEAD for GET requests followed by the ANY attachment.
|
||||
*
|
||||
* @param $routes
|
||||
* @param $httpMethod
|
||||
*
|
||||
* @throws Exception\HttpMethodNotAllowedException
|
||||
*/
|
||||
private function checkFallbacks($routes, $httpMethod)
|
||||
{
|
||||
$additional = [Route::ANY];
|
||||
|
||||
if ($httpMethod === Route::HEAD)
|
||||
{
|
||||
$additional[] = Route::GET;
|
||||
}
|
||||
|
||||
foreach ($additional as $method)
|
||||
{
|
||||
if (isset($routes[$method]))
|
||||
{
|
||||
return $method;
|
||||
}
|
||||
}
|
||||
|
||||
$this->matchedRoute = $routes;
|
||||
|
||||
throw new HttpMethodNotAllowedException('Allow: ' . implode(', ', array_keys($routes)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the dispatching of variable routes.
|
||||
*
|
||||
* @param $httpMethod
|
||||
* @param $uri
|
||||
*
|
||||
* @throws Exception\HttpMethodNotAllowedException
|
||||
* @throws Exception\HttpRouteNotFoundException
|
||||
*/
|
||||
private function dispatchVariableRoute($httpMethod, $uri)
|
||||
{
|
||||
foreach ($this->variableRouteData as $data)
|
||||
{
|
||||
if (!preg_match($data['regex'], $uri, $matches))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$count = count($matches);
|
||||
|
||||
while (!isset($data['routeMap'][$count++]))
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
$routes = $data['routeMap'][$count - 1];
|
||||
|
||||
if (!isset($routes[$httpMethod]))
|
||||
{
|
||||
$httpMethod = $this->checkFallbacks($routes, $httpMethod);
|
||||
}
|
||||
|
||||
foreach (array_values($routes[$httpMethod][2]) as $i => $varName)
|
||||
{
|
||||
if (!isset($matches[$i + 1]) || $matches[$i + 1] === '')
|
||||
{
|
||||
unset($routes[$httpMethod][2][$varName]);
|
||||
}
|
||||
else
|
||||
{
|
||||
$routes[$httpMethod][2][$varName] = $matches[$i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
return $routes[$httpMethod];
|
||||
}
|
||||
|
||||
throw new HttpRouteNotFoundException('Route ' . $uri . ' does not exist');
|
||||
}
|
||||
}
|
16
lib/routing/HandlerResolver.class.php
Normal file
16
lib/routing/HandlerResolver.class.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Routing;
|
||||
|
||||
class HandlerResolver implements HandlerResolverInterface
|
||||
{
|
||||
public function resolve($handler)
|
||||
{
|
||||
if (is_array($handler) && is_string($handler[0]))
|
||||
{
|
||||
$handler[0] = new $handler[0];
|
||||
}
|
||||
|
||||
return $handler;
|
||||
}
|
||||
}
|
8
lib/routing/HandlerResolverInterface.class.php
Normal file
8
lib/routing/HandlerResolverInterface.class.php
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace Routing;
|
||||
|
||||
interface HandlerResolverInterface
|
||||
{
|
||||
public function resolve($handler);
|
||||
}
|
5
lib/routing/HttpException.class.php
Normal file
5
lib/routing/HttpException.class.php
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?php
|
||||
|
||||
namespace Routing;
|
||||
|
||||
class HttpException extends \Exception {}
|
5
lib/routing/HttpMethodNotAllowedException.class.php
Normal file
5
lib/routing/HttpMethodNotAllowedException.class.php
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?php
|
||||
|
||||
namespace Routing;
|
||||
|
||||
class HttpMethodNotAllowedException extends HttpException {}
|
6
lib/routing/HttpRouteNotFoundException.class.php
Normal file
6
lib/routing/HttpRouteNotFoundException.class.php
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Routing;
|
||||
|
||||
class HttpRouteNotFoundException extends HttpException {}
|
||||
|
26
lib/routing/Route.class.php
Normal file
26
lib/routing/Route.class.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace Routing;
|
||||
|
||||
class Route
|
||||
{
|
||||
/**
|
||||
* Constants for before and after filters
|
||||
*/
|
||||
const BEFORE = 'before';
|
||||
const AFTER = 'after';
|
||||
const PREFIX = 'prefix';
|
||||
|
||||
/**
|
||||
* Constants for common HTTP methods
|
||||
*/
|
||||
const ANY = 'ANY';
|
||||
const GET = 'GET';
|
||||
const HEAD = 'HEAD';
|
||||
const POST = 'POST';
|
||||
const PUT = 'PUT';
|
||||
const PATCH = 'PATCH';
|
||||
const DELETE = 'DELETE';
|
||||
const OPTIONS = 'OPTIONS';
|
||||
}
|
||||
|
325
lib/routing/RouteCollector.class.php
Normal file
325
lib/routing/RouteCollector.class.php
Normal file
|
@ -0,0 +1,325 @@
|
|||
<?php
|
||||
|
||||
namespace Routing;
|
||||
|
||||
class RouteCollector
|
||||
{
|
||||
const DEFAULT_CONTROLLER_ROUTE = 'index';
|
||||
|
||||
const APPROX_CHUNK_SIZE = 10;
|
||||
|
||||
/**
|
||||
* @var RouteParser
|
||||
*/
|
||||
private $routeParser;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $filters = [];
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $staticRoutes = [];
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $regexToRoutesMap = [];
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $reverse = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $globalFilters = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $globalRoutePrefix = '';
|
||||
|
||||
public function __construct(RouteParser $routeParser = null)
|
||||
{
|
||||
$this->routeParser = $routeParser ?: new RouteParser();
|
||||
}
|
||||
|
||||
public function hasRoute(string $name): bool
|
||||
{
|
||||
return isset($this->reverse[$name]);
|
||||
}
|
||||
|
||||
public function route(string $name, array $args = null): string
|
||||
{
|
||||
$url = [];
|
||||
|
||||
$replacements = is_null($args) ? [] : array_values($args);
|
||||
|
||||
$variable = 0;
|
||||
|
||||
foreach ($this->reverse[$name] as $part)
|
||||
{
|
||||
if (!$part['variable'])
|
||||
{
|
||||
$url[] = $part['value'];
|
||||
}
|
||||
elseif (isset($replacements[$variable]))
|
||||
{
|
||||
if ($part['optional'])
|
||||
{
|
||||
$url[] = '/';
|
||||
}
|
||||
|
||||
$url[] = $replacements[$variable++];
|
||||
}
|
||||
elseif (!$part['optional'])
|
||||
{
|
||||
throw new BadRouteException("Expecting route variable '{$part['name']}'");
|
||||
}
|
||||
}
|
||||
|
||||
return implode('', $url);
|
||||
}
|
||||
|
||||
public function addRoute(string $httpMethod, $route, $handler, array $filters = []): RouteCollector
|
||||
{
|
||||
|
||||
if (is_array($route))
|
||||
{
|
||||
list($route, $name) = $route;
|
||||
}
|
||||
|
||||
$route = $this->addPrefix($this->trim($route));
|
||||
|
||||
list($routeData, $reverseData) = $this->routeParser->parse($route);
|
||||
|
||||
if (isset($name))
|
||||
{
|
||||
$this->reverse[$name] = $reverseData;
|
||||
}
|
||||
|
||||
$filters = array_merge_recursive($this->globalFilters, $filters);
|
||||
|
||||
isset($routeData[1]) ?
|
||||
$this->addVariableRoute($httpMethod, $routeData, $handler, $filters) :
|
||||
$this->addStaticRoute($httpMethod, $routeData, $handler, $filters);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function addStaticRoute(string $httpMethod, $routeData, $handler, $filters)
|
||||
{
|
||||
$routeStr = $routeData[0];
|
||||
|
||||
if (isset($this->staticRoutes[$routeStr][$httpMethod]))
|
||||
{
|
||||
throw new BadRouteException("Cannot register two routes matching '$routeStr' for method '$httpMethod'");
|
||||
}
|
||||
|
||||
foreach ($this->regexToRoutesMap as $regex => $routes)
|
||||
{
|
||||
if (isset($routes[$httpMethod]) && preg_match('~^' . $regex . '$~', $routeStr))
|
||||
{
|
||||
throw new BadRouteException("Static route '$routeStr' is shadowed by previously defined variable route '$regex' for method '$httpMethod'");
|
||||
}
|
||||
}
|
||||
|
||||
$this->staticRoutes[$routeStr][$httpMethod] = [$handler, $filters, []];
|
||||
}
|
||||
|
||||
private function addVariableRoute(string $httpMethod, $routeData, $handler, $filters)
|
||||
{
|
||||
list($regex, $variables) = $routeData;
|
||||
|
||||
if (isset($this->regexToRoutesMap[$regex][$httpMethod]))
|
||||
{
|
||||
throw new BadRouteException("Cannot register two routes matching '$regex' for method '$httpMethod'");
|
||||
}
|
||||
|
||||
$this->regexToRoutesMap[$regex][$httpMethod] = [$handler, $filters, $variables];
|
||||
}
|
||||
|
||||
public function group(array $filters, \Closure $callback)
|
||||
{
|
||||
$oldGlobalFilters = $this->globalFilters;
|
||||
|
||||
$oldGlobalPrefix = $this->globalRoutePrefix;
|
||||
|
||||
$this->globalFilters =
|
||||
array_merge_recursive($this->globalFilters, array_intersect_key($filters, [Route::AFTER => 1, Route::BEFORE => 1]));
|
||||
|
||||
$newPrefix = isset($filters[Route::PREFIX]) ? $this->trim($filters[Route::PREFIX]) : null;
|
||||
|
||||
$this->globalRoutePrefix = $this->addPrefix($newPrefix);
|
||||
|
||||
$callback($this);
|
||||
|
||||
$this->globalFilters = $oldGlobalFilters;
|
||||
|
||||
$this->globalRoutePrefix = $oldGlobalPrefix;
|
||||
}
|
||||
|
||||
private function addPrefix(string $route)
|
||||
{
|
||||
return $this->trim($this->trim($this->globalRoutePrefix) . '/' . $route);
|
||||
}
|
||||
|
||||
public function filter(string $name, $handler)
|
||||
{
|
||||
$this->filters[$name] = $handler;
|
||||
}
|
||||
|
||||
|
||||
public function get($route, $handler, array $filters = [])
|
||||
{
|
||||
return $this->addRoute(Route::GET, $route, $handler, $filters);
|
||||
}
|
||||
|
||||
public function head($route, $handler, array $filters = [])
|
||||
{
|
||||
return $this->addRoute(Route::HEAD, $route, $handler, $filters);
|
||||
}
|
||||
|
||||
public function post($route, $handler, array $filters = [])
|
||||
{
|
||||
return $this->addRoute(Route::POST, $route, $handler, $filters);
|
||||
}
|
||||
|
||||
public function put($route, $handler, array $filters = [])
|
||||
{
|
||||
return $this->addRoute(Route::PUT, $route, $handler, $filters);
|
||||
}
|
||||
|
||||
public function patch($route, $handler, array $filters = [])
|
||||
{
|
||||
return $this->addRoute(Route::PATCH, $route, $handler, $filters);
|
||||
}
|
||||
|
||||
public function delete($route, $handler, array $filters = [])
|
||||
{
|
||||
return $this->addRoute(Route::DELETE, $route, $handler, $filters);
|
||||
}
|
||||
|
||||
public function options($route, $handler, array $filters = [])
|
||||
{
|
||||
return $this->addRoute(Route::OPTIONS, $route, $handler, $filters);
|
||||
}
|
||||
|
||||
public function any($route, $handler, array $filters = [])
|
||||
{
|
||||
return $this->addRoute(Route::ANY, $route, $handler, $filters);
|
||||
}
|
||||
|
||||
|
||||
public function controller(string $route, string $classname, array $filters = []): RouteCollector
|
||||
{
|
||||
$reflection = new ReflectionClass($classname);
|
||||
|
||||
$validMethods = $this->getValidMethods();
|
||||
|
||||
$sep = $route === '/' ? '' : '/';
|
||||
|
||||
foreach ($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method)
|
||||
{
|
||||
foreach ($validMethods as $valid)
|
||||
{
|
||||
if (stripos($method->name, $valid) === 0)
|
||||
{
|
||||
$methodName = $this->camelCaseToDashed(substr($method->name, strlen($valid)));
|
||||
|
||||
$params = $this->buildControllerParameters($method);
|
||||
|
||||
if ($methodName === self::DEFAULT_CONTROLLER_ROUTE)
|
||||
{
|
||||
$this->addRoute($valid, $route . $params, [$classname, $method->name], $filters);
|
||||
}
|
||||
|
||||
$this->addRoute($valid, $route . $sep . $methodName . $params, [$classname, $method->name], $filters);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function buildControllerParameters(ReflectionMethod $method): string
|
||||
{
|
||||
$params = '';
|
||||
|
||||
foreach ($method->getParameters() as $param)
|
||||
{
|
||||
$params .= "/{" . $param->getName() . "}" . ($param->isOptional() ? '?' : '');
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
private function camelCaseToDashed(string $string): string
|
||||
{
|
||||
return strtolower(preg_replace('/([A-Z])/', '-$1', lcfirst($string)));
|
||||
}
|
||||
|
||||
public function getValidMethods(): array
|
||||
{
|
||||
return [
|
||||
Route::ANY,
|
||||
Route::GET,
|
||||
Route::POST,
|
||||
Route::PUT,
|
||||
Route::PATCH,
|
||||
Route::DELETE,
|
||||
Route::HEAD,
|
||||
Route::OPTIONS,
|
||||
];
|
||||
}
|
||||
|
||||
public function getData(): RouteData
|
||||
{
|
||||
return new RouteData($this->staticRoutes, $this->regexToRoutesMap ? $this->generateVariableRouteData() : [], $this->filters);
|
||||
}
|
||||
|
||||
private function trim(string $route): string
|
||||
{
|
||||
return trim($route, '/');
|
||||
}
|
||||
|
||||
private function generateVariableRouteData(): array
|
||||
{
|
||||
$chunkSize = $this->computeChunkSize(count($this->regexToRoutesMap));
|
||||
$chunks = array_chunk($this->regexToRoutesMap, $chunkSize, true);
|
||||
return array_map([$this, 'processChunk'], $chunks);
|
||||
}
|
||||
|
||||
private function computeChunkSize(int $count): float
|
||||
{
|
||||
$numParts = max(1, round($count / self::APPROX_CHUNK_SIZE));
|
||||
return ceil($count / $numParts);
|
||||
}
|
||||
|
||||
private function processChunk($regexToRoutesMap): array
|
||||
{
|
||||
$routeMap = [];
|
||||
$regexes = [];
|
||||
$numGroups = 0;
|
||||
foreach ($regexToRoutesMap as $regex => $routes)
|
||||
{
|
||||
$firstRoute = reset($routes);
|
||||
$numVariables = count($firstRoute[2]);
|
||||
$numGroups = max($numGroups, $numVariables);
|
||||
|
||||
$regexes[] = $regex . str_repeat('()', $numGroups - $numVariables);
|
||||
|
||||
foreach ($routes as $httpMethod => $route)
|
||||
{
|
||||
$routeMap[$numGroups + 1][$httpMethod] = $route;
|
||||
}
|
||||
|
||||
$numGroups++;
|
||||
}
|
||||
|
||||
$regex = '~^(?|' . implode('|', $regexes) . ')$~';
|
||||
return ['regex' => $regex, 'routeMap' => $routeMap];
|
||||
}
|
||||
}
|
59
lib/routing/RouteData.class.php
Normal file
59
lib/routing/RouteData.class.php
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace Routing;
|
||||
|
||||
class RouteData
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $variableRoutes;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $staticRoutes;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $filters;
|
||||
|
||||
/**
|
||||
* @param array $staticRoutes
|
||||
* @param array $variableRoutes
|
||||
* @param array $filters
|
||||
*/
|
||||
public function __construct(array $staticRoutes, array $variableRoutes, array $filters)
|
||||
{
|
||||
$this->staticRoutes = $staticRoutes;
|
||||
|
||||
$this->variableRoutes = $variableRoutes;
|
||||
|
||||
$this->filters = $filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getStaticRoutes()
|
||||
{
|
||||
return $this->staticRoutes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getVariableRoutes()
|
||||
{
|
||||
return $this->variableRoutes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getFilters()
|
||||
{
|
||||
return $this->filters;
|
||||
}
|
||||
}
|
214
lib/routing/RouteParser.class.php
Normal file
214
lib/routing/RouteParser.class.php
Normal file
|
@ -0,0 +1,214 @@
|
|||
<?php
|
||||
|
||||
namespace Routing;
|
||||
|
||||
/**
|
||||
* Parses routes of the following form:
|
||||
*
|
||||
* "/user/{name}/{id:[0-9]+}?"
|
||||
*/
|
||||
class RouteParser
|
||||
{
|
||||
|
||||
/**
|
||||
* Search through the given route looking for dynamic portions.
|
||||
*
|
||||
* Using ~ as the regex delimiter.
|
||||
*
|
||||
* We start by looking for a literal '{' character followed by any amount of whitespace.
|
||||
* The next portion inside the parentheses looks for a parameter name containing alphanumeric characters or underscore.
|
||||
*
|
||||
* After this we look for the ':\d+' and ':[0-9]+' style portion ending with a closing '}' character.
|
||||
*
|
||||
* Finally we look for an optional '?' which is used to signify an optional route.
|
||||
*/
|
||||
const VARIABLE_REGEX =
|
||||
"~\{
|
||||
\s* ([a-zA-Z0-9_]*) \s*
|
||||
(?:
|
||||
: \s* ([^{]+(?:\{.*?\})?)
|
||||
)?
|
||||
\}\??~x";
|
||||
|
||||
/**
|
||||
* The default parameter character restriction (One or more characters that is not a '/').
|
||||
*/
|
||||
const DEFAULT_DISPATCH_REGEX = '[^/]+';
|
||||
|
||||
private $parts;
|
||||
|
||||
private $reverseParts;
|
||||
|
||||
private $partsCounter;
|
||||
|
||||
private $variables;
|
||||
|
||||
private $regexOffset;
|
||||
|
||||
/**
|
||||
* Handy parameter type restrictions.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $regexShortcuts = [
|
||||
':i}' => ':[0-9]+}',
|
||||
':a}' => ':[0-9A-Za-z]+}',
|
||||
':h}' => ':[0-9A-Fa-f]+}',
|
||||
':c}' => ':[a-zA-Z0-9+_\-\.]+}'
|
||||
];
|
||||
|
||||
/**
|
||||
* Parse a route returning the correct data format to pass to the dispatch engine.
|
||||
*
|
||||
* @param $route
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function parse($route)
|
||||
{
|
||||
$this->reset();
|
||||
|
||||
$route = strtr($route, $this->regexShortcuts);
|
||||
|
||||
if (!$matches = $this->extractVariableRouteParts($route))
|
||||
{
|
||||
$reverse = [
|
||||
'variable' => false,
|
||||
'value' => $route
|
||||
];
|
||||
|
||||
return [[$route], [$reverse]];
|
||||
}
|
||||
|
||||
foreach ($matches as $set)
|
||||
{
|
||||
|
||||
$this->staticParts($route, $set[0][1]);
|
||||
|
||||
$this->validateVariable($set[1][0]);
|
||||
|
||||
$regexPart = (isset($set[2]) ? trim($set[2][0]) : self::DEFAULT_DISPATCH_REGEX);
|
||||
|
||||
$this->regexOffset = $set[0][1] + strlen($set[0][0]);
|
||||
|
||||
$match = '(' . $regexPart . ')';
|
||||
|
||||
$isOptional = substr($set[0][0], -1) === '?';
|
||||
|
||||
if ($isOptional)
|
||||
{
|
||||
$match = $this->makeOptional($match);
|
||||
}
|
||||
|
||||
$this->reverseParts[$this->partsCounter] = [
|
||||
'variable' => true,
|
||||
'optional' => $isOptional,
|
||||
'name' => $set[1][0]
|
||||
];
|
||||
|
||||
$this->parts[$this->partsCounter++] = $match;
|
||||
}
|
||||
|
||||
$this->staticParts($route, strlen($route));
|
||||
|
||||
return [[implode('', $this->parts), $this->variables], array_values($this->reverseParts)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the parser ready for the next route.
|
||||
*/
|
||||
private function reset()
|
||||
{
|
||||
$this->parts = [];
|
||||
|
||||
$this->reverseParts = [];
|
||||
|
||||
$this->partsCounter = 0;
|
||||
|
||||
$this->variables = [];
|
||||
|
||||
$this->regexOffset = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return any variable route portions from the given route.
|
||||
*
|
||||
* @param $route
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function extractVariableRouteParts($route)
|
||||
{
|
||||
if (preg_match_all(self::VARIABLE_REGEX, $route, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER))
|
||||
{
|
||||
return $matches;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $route
|
||||
* @param $nextOffset
|
||||
*/
|
||||
private function staticParts($route, $nextOffset)
|
||||
{
|
||||
$static = preg_split('~(/)~u', substr($route, $this->regexOffset, $nextOffset - $this->regexOffset), 0, PREG_SPLIT_DELIM_CAPTURE);
|
||||
|
||||
foreach ($static as $staticPart)
|
||||
{
|
||||
if ($staticPart)
|
||||
{
|
||||
$quotedPart = $this->quote($staticPart);
|
||||
|
||||
$this->parts[$this->partsCounter] = $quotedPart;
|
||||
|
||||
$this->reverseParts[$this->partsCounter] = [
|
||||
'variable' => false,
|
||||
'value' => $staticPart
|
||||
];
|
||||
|
||||
$this->partsCounter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $varName
|
||||
*/
|
||||
private function validateVariable($varName)
|
||||
{
|
||||
if (isset($this->variables[$varName]))
|
||||
{
|
||||
throw new BadRouteException("Cannot use the same placeholder '$varName' twice");
|
||||
}
|
||||
|
||||
$this->variables[$varName] = $varName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $match
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function makeOptional($match)
|
||||
{
|
||||
$previous = $this->partsCounter - 1;
|
||||
|
||||
if (isset($this->parts[$previous]) && $this->parts[$previous] === '/')
|
||||
{
|
||||
$this->partsCounter--;
|
||||
$match = '(?:/' . $match . ')';
|
||||
}
|
||||
|
||||
return $match . '?';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $part
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function quote($part)
|
||||
{
|
||||
return preg_quote($part, '~');
|
||||
}
|
||||
}
|
|
@ -7,3 +7,6 @@ We are listed on several exchanges. You can buy or sell credits at one of these:
|
|||
|
||||
- [Bittrex](https://bittrex.com/Market/Index?MarketName=BTC-LBC)
|
||||
- [Poloniex](https://poloniex.com/exchange#btc_lbc)
|
||||
- [Shapeshift](https://shapeshift.io)
|
||||
- [Changelly](https://changelly.com/exchange/BTC/LBC/1)
|
||||
- [BitSquare](https://bitsquare.io/)
|
|
@ -7,7 +7,7 @@
|
|||
<generator>https://github.com/lbryio/lbry.io</generator>
|
||||
<language>{{rss.lang}}</language>
|
||||
<?php //<lastBuildDate>Sat, 07 Sep 2002 09:42:31 GMT</lastBuildDate> ?>
|
||||
<atom:link href="https://lbry.io<?php echo ContentActions::URL_NEWS . '/' . ContentActions::RSS_SLUG ?>" rel="self" type="application/rss+xml" />
|
||||
<atom:link href="https://lbry.io<?php echo ContentActions::URL_NEWS . '/' . ContentActions::SLUG_RSS ?>" rel="self" type="application/rss+xml" />
|
||||
<?php foreach ($posts as $post): ?>
|
||||
<item>
|
||||
<title><?php echo htmlspecialchars($post->getTitle()) ?></title>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php Response::setMetaDescription(__('description.get')) ?>
|
||||
<?php Resp nse::setMetaDescription(__('description.get')) ?>
|
||||
<?php NavActions::setNavUri('/get') ?>
|
||||
<?php echo View::render('nav/_header', ['isDark' => false]) ?>
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
<link rel="icon" type="image/png" href="/img/fav/favicon-16x16.png" sizes="16x16">
|
||||
<link rel="manifest" href="/img/fav/manifest.json">
|
||||
<?php if(isset($showRssLink) && $showRssLink): ?>
|
||||
<link rel="alternate" type="application/rss+xml" title="LBRY News" href="<?php echo ContentActions::URL_NEWS . '/' . ContentActions::RSS_SLUG ?>" />
|
||||
<link rel="alternate" type="application/rss+xml" title="LBRY News" href="<?php echo ContentActions::URL_NEWS . '/' . ContentActions::SLUG_RSS ?>" />
|
||||
<?php endif ?>
|
||||
|
||||
<meta name="description" content="<?php echo Response::getMetaDescription() ?>">
|
||||
|
|
|
@ -30,10 +30,7 @@
|
|||
<a href="http://explorer.lbry.io" class="link-primary">{{learn.explorer}}</a>
|
||||
</div>
|
||||
<div class="spacer1">
|
||||
<a href="https://bittrex.com/Market/Index?MarketName=BTC-LBC" class="link-primary">{{learn.exchange}}</a>
|
||||
</div>
|
||||
<div class="spacer1">
|
||||
<a href="https://poloniex.com/exchange#btc_lbc" class="link-primary">{{learn.exchange2}}</a>
|
||||
<a href="/faq/exchanges" class="link-primary">{{learn.exchange_faq}}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="span6">
|
||||
|
|
Loading…
Add table
Reference in a new issue