From 771b205d795e4c961fb469232e8628c04e33fcda Mon Sep 17 00:00:00 2001 From: Maxime St-Pierre Date: Mon, 21 May 2018 11:49:15 -0400 Subject: [PATCH] Adjust site to use PSR-2 coding standard --- autoload.php | 121 ++-- bootstrap.php | 2 +- controller/Actions.class.php | 2 +- controller/Controller.class.php | 326 +++++---- controller/Request.class.php | 233 ++++--- controller/Session.class.php | 148 ++--- .../action/AcquisitionActions.class.php | 205 +++--- controller/action/ContentActions.class.php | 434 ++++++------ controller/action/DeveloperActions.class.php | 136 ++-- controller/action/DownloadActions.class.php | 187 +++--- controller/action/MailActions.class.php | 97 ++- controller/action/NavActions.class.php | 54 +- controller/action/OpsActions.class.php | 162 +++-- controller/action/ReportActions.class.php | 66 +- controller/action/i18nActions.class.php | 36 +- lib/cache/Apc.class.php | 10 +- lib/exception/StopException.class.php | 1 - lib/i18n.class.php | 207 +++--- lib/routing/BadRouteException.class.php | 4 +- lib/routing/Dispatcher.class.php | 408 ++++++------ lib/routing/HandlerResolver.class.php | 15 +- .../HandlerResolverInterface.class.php | 2 +- lib/routing/HttpException.class.php | 4 +- .../HttpMethodNotAllowedException.class.php | 20 +- .../HttpRouteNotFoundException.class.php | 5 +- lib/routing/Route.class.php | 35 +- lib/routing/RouteCollector.class.php | 497 +++++++------- lib/routing/RouteData.class.php | 88 +-- lib/routing/RouteParser.class.php | 255 ++++---- lib/thirdparty/Asana.class.php | 101 ++- lib/thirdparty/Github.class.php | 261 ++++---- lib/thirdparty/LBRY.class.php | 84 ++- lib/thirdparty/Mailgun.class.php | 62 +- lib/thirdparty/Salesforce.class.php | 64 +- lib/thirdparty/Slack.class.php | 19 +- lib/tools/Config.class.php | 68 +- lib/tools/Curl.class.php | 238 +++---- lib/tools/CurlWithCache.class.php | 62 +- lib/tools/Debug.class.php | 107 ++- lib/tools/Encoding.class.php | 93 ++- lib/tools/Gzip.class.php | 49 +- lib/tools/Lock.class.php | 83 ++- lib/tools/OS.class.php | 29 +- lib/tools/Shell.class.php | 219 +++---- lib/tools/Smaz.class.php | 232 +++---- model/Post.class.php | 618 +++++++++--------- update.php | 7 +- view/Response.class.php | 541 ++++++++------- view/View.class.php | 286 ++++---- view/template/acquisition/youtube_edit.php | 2 +- view/template/acquisition/youtube_status.php | 4 +- view/template/acquisition/youtube_token.php | 6 +- view/template/bounty/list.php | 6 +- view/template/content/_bio.php | 2 +- view/template/content/_jobs.php | 2 +- view/template/content/_postList.php | 2 +- view/template/content/_postSocialShare.php | 2 +- view/template/content/credit-reports.php | 2 +- view/template/content/faq.php | 4 +- view/template/content/news.php | 2 +- view/template/content/roadmap.php | 6 +- view/template/content/rss.php | 2 +- view/template/developer/quickstart.php | 4 +- view/template/download/_list.php | 4 +- .../template/email_templates/_confirmHash.php | 2 +- view/template/form/_select.php | 2 +- view/template/internal/json.php | 2 +- view/template/internal/zip.php | 2 +- view/template/layout/_analytics.php | 2 +- view/template/layout/basic.php | 8 +- view/template/nav/_globalItems.php | 2 +- view/template/page/press-kit.php | 6 +- view/template/page/team.php | 8 +- web/index.php | 49 +- 74 files changed, 3377 insertions(+), 3739 deletions(-) diff --git a/autoload.php b/autoload.php index d25383f7..cb52baeb 100644 --- a/autoload.php +++ b/autoload.php @@ -1,80 +1,71 @@ 2) { + throw new RuntimeException('Autoloader cannot handle 2 namespaces in the same file'); + } + + $prefix = isset($namespaces[1]) && count($namespaces[1]) ? reset($namespaces[1]) . '\\' : ''; + + foreach ($classes[1] as $class) { + $mapping[strtolower($prefix . $class)] = $path; + } + return $mapping; } - - static::$classes = []; - - $dir = new RecursiveDirectoryIterator(ROOT_DIR, RecursiveDirectoryIterator::SKIP_DOTS); - $ite = new RecursiveIteratorIterator($dir); - $pathIterator = new RegexIterator($ite, '/.*\.php$/', RegexIterator::GET_MATCH); - foreach($pathIterator as $paths) - { - foreach($paths as $path) - { - static::$classes += static::parseFile($path); - } - } - - if (ini_get('apc.enabled')) - { - apc_store($key, static::$classes); - } - } - - protected static function parseFile($path) - { - $mapping = []; - $classes = []; - - preg_match_all('~^\s*(?:namespace)\s+([^;]+)~mi', file_get_contents($path), $namespaces); - preg_match_all('~^\s*(?:abstract\s+|final\s+)?(?:class|interface)\s+(\w+)~mi', file_get_contents($path), $classes); - - if (isset($namespaces[1]) && count($namespaces[1]) > 2) - { - throw new RuntimeException('Autoloader cannot handle 2 namespaces in the same file'); - } - - $prefix = isset($namespaces[1]) && count($namespaces[1]) ? reset($namespaces[1]) . '\\' : ''; - - foreach ($classes[1] as $class) - { - $mapping[strtolower($prefix . $class)] = $path; - } - return $mapping; - } } ini_set('unserialize_callback_func', 'spl_autoload_call'); diff --git a/bootstrap.php b/bootstrap.php index f5cbeeff..b18d4471 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -2,4 +2,4 @@ define('ROOT_DIR', __DIR__); date_default_timezone_set('Etc/UTC'); -require ROOT_DIR . '/autoload.php'; \ No newline at end of file +require ROOT_DIR . '/autoload.php'; diff --git a/controller/Actions.class.php b/controller/Actions.class.php index 44959bd6..65905abd 100644 --- a/controller/Actions.class.php +++ b/controller/Actions.class.php @@ -2,4 +2,4 @@ class Actions { -} \ No newline at end of file +} diff --git a/controller/Controller.class.php b/controller/Controller.class.php index 3a0893fd..84856281 100644 --- a/controller/Controller.class.php +++ b/controller/Controller.class.php @@ -2,208 +2,184 @@ class Controller { - const CACHE_CLEAR_PATH = '/clear-cache'; + const CACHE_CLEAR_PATH = '/clear-cache'; - protected static $queuedFunctions = []; + protected static $queuedFunctions = []; - public static function dispatch($uri) - { - try + public static function dispatch($uri) { - if (IS_PRODUCTION && function_exists('newrelic_name_transaction')) - { - newrelic_name_transaction(Request::getMethod() . ' ' . strtolower($uri)); - } + try { + if (IS_PRODUCTION && function_exists('newrelic_name_transaction')) { + newrelic_name_transaction(Request::getMethod() . ' ' . strtolower($uri)); + } - $viewAndParams = static::execute(Request::getMethod(), $uri); - $viewTemplate = $viewAndParams[0]; - $viewParameters = $viewAndParams[1] ?? []; - if (!IS_PRODUCTION && isset($viewAndParams[2])) - { - throw new Exception('use response::setheader instead of returning headers'); - } + $viewAndParams = static::execute(Request::getMethod(), $uri); + $viewTemplate = $viewAndParams[0]; + $viewParameters = $viewAndParams[1] ?? []; + if (!IS_PRODUCTION && isset($viewAndParams[2])) { + throw new Exception('use response::setheader instead of returning headers'); + } - if (!$viewTemplate) - { - if ($viewTemplate !== null) - { - throw new LogicException('All execute methods must return a template or NULL.'); + if (!$viewTemplate) { + if ($viewTemplate !== null) { + throw new LogicException('All execute methods must return a template or NULL.'); + } + } else { + $layout = !(isset($viewParameters['_no_layout']) && $viewParameters['_no_layout']); + unset($viewParameters['_no_layout']); + + $layoutParams = $viewParameters[View::LAYOUT_PARAMS] ?? []; + unset($viewParameters[View::LAYOUT_PARAMS]); + + $content = View::render($viewTemplate, $viewParameters + ['fullPage' => true]); + + Response::setContent($layout ? View::render('layout/basic', ['content' => $content] + $layoutParams) : $content); + } + + Response::setDefaultSecurityHeaders(); + if (Request::isGzipAccepted()) { + Response::gzipContentIfNotDisabled(); + } + + Response::send(); + } catch (StopException $e) { } - } - else - { - $layout = !(isset($viewParameters['_no_layout']) && $viewParameters['_no_layout']); - unset($viewParameters['_no_layout']); - - $layoutParams = $viewParameters[View::LAYOUT_PARAMS] ?? []; - unset($viewParameters[View::LAYOUT_PARAMS]); - - $content = View::render($viewTemplate, $viewParameters + ['fullPage' => true]); - - Response::setContent($layout ? View::render('layout/basic', ['content' => $content] + $layoutParams) : $content); - } - - Response::setDefaultSecurityHeaders(); - if (Request::isGzipAccepted()) - { - Response::gzipContentIfNotDisabled(); - } - - Response::send(); } - catch (StopException $e) + + public static function execute($method, $uri) { - + $router = static::getRouterWithRoutes(); + static::performSubdomainRedirects(); + try { + $dispatcher = new Routing\Dispatcher($router->getData()); + return $dispatcher->dispatch($method, $uri); + } catch (\Routing\HttpRouteNotFoundException $e) { + return NavActions::execute404(); + } catch (\Routing\HttpMethodNotAllowedException $e) { + Response::setStatus(405); + Response::setHeader('Allow', implode(', ', $e->getAllowedMethods())); + return ['page/405']; + } } - } - public static function execute($method, $uri) - { - $router = static::getRouterWithRoutes(); - static::performSubdomainRedirects(); - try + protected static function performSubdomainRedirects() { - $dispatcher = new Routing\Dispatcher($router->getData()); - return $dispatcher->dispatch($method, $uri); - } - catch (\Routing\HttpRouteNotFoundException $e) - { - return NavActions::execute404(); - } - catch (\Routing\HttpMethodNotAllowedException $e) - { - Response::setStatus(405); - Response::setHeader('Allow', implode(', ', $e->getAllowedMethods())); - return ['page/405']; - } - } + $subDomain = Request::getSubDomain(); - protected static function performSubdomainRedirects() - { - $subDomain = Request::getSubDomain(); - - switch($subDomain) { + switch ($subDomain) { case 'chat': case 'slack': return static::redirect('https://discord.gg/Z3bERWA'); } - } - - protected static function getRouterWithRoutes(): \Routing\RouteCollector - { - $router = new Routing\RouteCollector(); - - $router->get(['/', 'home'], 'ContentActions::executeHome'); - - $router->get(['/get', 'get'], 'DownloadActions::executeGet'); - $router->get(['/getrubin', 'getrubin'], 'DownloadActions::executeGet'); - foreach(array_keys(OS::getAll()) as $os) - { - $router->get(['/' . $os, 'get-' . $os], 'DownloadActions::executeGet'); - } - $router->get('/roadmap', 'ContentActions::executeRoadmap'); - - $router->post('/quickstart/auth', 'DeveloperActions::executeQuickstartAuth'); - $router->get('/quickstart/{step}?', 'DeveloperActions::executeQuickstart'); - $router->get('/quickstart/github/callback', 'DeveloperActions::executeQuickstartGithubCallback'); - - $router->get(['/press-kit.zip', 'press-kit'], 'ContentActions::executePressKit'); - - $router->post('/postcommit', 'OpsActions::executePostCommit'); - $router->post('/log-upload', 'OpsActions::executeLogUpload'); - $router->get(static::CACHE_CLEAR_PATH, 'OpsActions::executeClearCache'); - - $router->any('/list/subscribe', 'MailActions::executeSubscribe'); - $router->any('/list/subscribed', 'MailActions::executeSubscribed'); - $router->get('/list/unsubscribe/{email}', 'MailActions::executeUnsubscribe'); - - $router->any('/dmca', 'ReportActions::executeDmca'); - - $router->any('/youtube/sub', 'AcquisitionActions::executeYouTubeSub'); - $router->post('/youtube/edit', 'AcquisitionActions::executeYoutubeEdit'); - $router->post('/youtube/token', 'AcquisitionActions::executeYoutubeToken'); - $router->any('/youtube/status/{token}', 'AcquisitionActions::executeYoutubeStatus'); - $router->any('/youtube', 'AcquisitionActions::executeYouTube'); - $router->any('/youtube/status', 'AcquisitionActions::executeRedirectYoutube'); - - $router->get('/verify/{token}', 'AcquisitionActions::executeVerify'); - - - $router->get('/news/category/{category}', 'ContentActions::executePostCategoryFilter'); - - $router->post('/set-culture', 'i18nActions::setCulture'); - - $permanentRedirectsPath = ROOT_DIR . '/data/redirect/permanent.yaml'; - $tempRedirectsPath = ROOT_DIR . '/data/redirect/temporary.yaml'; - - $permanentRedirects = SpyC::YAMLLoadString(file_get_contents($permanentRedirectsPath)); - $tempRedirects = SpyC::YAMLLoadString(file_get_contents($tempRedirectsPath)); - - foreach ([307 => $tempRedirects, 301 => $permanentRedirects] as $code => $redirects) - { - foreach ($redirects as $src => $target) - { - $router->any($src, function () use ($target, $code) { return static::redirect($target, $code); }); - } } - $router->any('/get/lbry.pre.{ext:c}', 'DownloadActions::executeGetAppPrereleaseRedirect'); - $router->any('/get/lbry.{ext:c}', 'DownloadActions::executeGetAppRedirect'); - $router->any('/get/lbrynet.{os:c}.zip', 'DownloadActions::executeGetDaemonRedirect'); + protected static function getRouterWithRoutes(): \Routing\RouteCollector + { + $router = new Routing\RouteCollector(); - $router->get([ContentActions::URL_NEWS . '/{slug:c}?', 'news'], 'ContentActions::executeNews'); - $router->get([ContentActions::URL_FAQ . '/{slug:c}?', 'faq'], 'ContentActions::executeFaq'); - $router->get([ContentActions::URL_BOUNTY . '/{slug:c}?', 'bounty'], 'ContentActions::executeBounty'); - $router->get([ContentActions::URL_PRESS . '/{slug:c}', 'press'], 'ContentActions::executePress'); + $router->get(['/', 'home'], 'ContentActions::executeHome'); + + $router->get(['/get', 'get'], 'DownloadActions::executeGet'); + $router->get(['/getrubin', 'getrubin'], 'DownloadActions::executeGet'); + foreach (array_keys(OS::getAll()) as $os) { + $router->get(['/' . $os, 'get-' . $os], 'DownloadActions::executeGet'); + } + $router->get('/roadmap', 'ContentActions::executeRoadmap'); + + $router->post('/quickstart/auth', 'DeveloperActions::executeQuickstartAuth'); + $router->get('/quickstart/{step}?', 'DeveloperActions::executeQuickstart'); + $router->get('/quickstart/github/callback', 'DeveloperActions::executeQuickstartGithubCallback'); + + $router->get(['/press-kit.zip', 'press-kit'], 'ContentActions::executePressKit'); + + $router->post('/postcommit', 'OpsActions::executePostCommit'); + $router->post('/log-upload', 'OpsActions::executeLogUpload'); + $router->get(static::CACHE_CLEAR_PATH, 'OpsActions::executeClearCache'); + + $router->any('/list/subscribe', 'MailActions::executeSubscribe'); + $router->any('/list/subscribed', 'MailActions::executeSubscribed'); + $router->get('/list/unsubscribe/{email}', 'MailActions::executeUnsubscribe'); + + $router->any('/dmca', 'ReportActions::executeDmca'); + + $router->any('/youtube/sub', 'AcquisitionActions::executeYouTubeSub'); + $router->post('/youtube/edit', 'AcquisitionActions::executeYoutubeEdit'); + $router->post('/youtube/token', 'AcquisitionActions::executeYoutubeToken'); + $router->any('/youtube/status/{token}', 'AcquisitionActions::executeYoutubeStatus'); + $router->any('/youtube', 'AcquisitionActions::executeYouTube'); + $router->any('/youtube/status', 'AcquisitionActions::executeRedirectYoutube'); + + $router->get('/verify/{token}', 'AcquisitionActions::executeVerify'); + + + $router->get('/news/category/{category}', 'ContentActions::executePostCategoryFilter'); + + $router->post('/set-culture', 'i18nActions::setCulture'); + + $permanentRedirectsPath = ROOT_DIR . '/data/redirect/permanent.yaml'; + $tempRedirectsPath = ROOT_DIR . '/data/redirect/temporary.yaml'; + + $permanentRedirects = SpyC::YAMLLoadString(file_get_contents($permanentRedirectsPath)); + $tempRedirects = SpyC::YAMLLoadString(file_get_contents($tempRedirectsPath)); + + foreach ([307 => $tempRedirects, 301 => $permanentRedirects] as $code => $redirects) { + foreach ($redirects as $src => $target) { + $router->any($src, function () use ($target, $code) { + return static::redirect($target, $code); + }); + } + } + + $router->any('/get/lbry.pre.{ext:c}', 'DownloadActions::executeGetAppPrereleaseRedirect'); + $router->any('/get/lbry.{ext:c}', 'DownloadActions::executeGetAppRedirect'); + $router->any('/get/lbrynet.{os:c}.zip', 'DownloadActions::executeGetDaemonRedirect'); + + $router->get([ContentActions::URL_NEWS . '/{slug:c}?', 'news'], 'ContentActions::executeNews'); + $router->get([ContentActions::URL_FAQ . '/{slug:c}?', 'faq'], 'ContentActions::executeFaq'); + $router->get([ContentActions::URL_BOUNTY . '/{slug:c}?', 'bounty'], 'ContentActions::executeBounty'); + $router->get([ContentActions::URL_PRESS . '/{slug:c}', 'press'], 'ContentActions::executePress'); // $router->get([ContentActions::URL_CREDIT_REPORTS . '/{slug:c}?', 'faq'], 'ContentActions::executeFaq'); - $router->get(ContentActions::URL_CREDIT_REPORTS, 'ContentActions::executeCreditReports'); - $router->get([ContentActions::URL_CREDIT_REPORTS . '/{year:c}-q{quarter:c}', ContentActions::URL_CREDIT_REPORTS . '/{year:c}-Q{quarter:c}'], 'ContentActions::executeCreditReport'); + $router->get(ContentActions::URL_CREDIT_REPORTS, 'ContentActions::executeCreditReports'); + $router->get([ContentActions::URL_CREDIT_REPORTS . '/{year:c}-q{quarter:c}', ContentActions::URL_CREDIT_REPORTS . '/{year:c}-Q{quarter:c}'], 'ContentActions::executeCreditReport'); - $router->get('/{slug}', function (string $slug) - { - if (View::exists('page/' . $slug)) - { - Response::enableHttpCache(); - return ['page/' . $slug, []]; - } - else - { - return NavActions::execute404(); - } - }); + $router->get('/{slug}', function (string $slug) { + if (View::exists('page/' . $slug)) { + Response::enableHttpCache(); + return ['page/' . $slug, []]; + } else { + return NavActions::execute404(); + } + }); - return $router; - } - - public static function redirect($url, $statusCode = 302) - { - if (!$url) - { - throw new InvalidArgumentException('Cannot redirect to an empty URL.'); + return $router; } - $url = str_replace('&', '&', $url); - - Response::setStatus($statusCode); - - if ($statusCode == 201 || ($statusCode >= 300 && $statusCode < 400)) + public static function redirect($url, $statusCode = 302) { - Response::setHeader(Response::HEADER_LOCATION, $url); + if (!$url) { + throw new InvalidArgumentException('Cannot redirect to an empty URL.'); + } + + $url = str_replace('&', '&', $url); + + Response::setStatus($statusCode); + + if ($statusCode == 201 || ($statusCode >= 300 && $statusCode < 400)) { + Response::setHeader(Response::HEADER_LOCATION, $url); + } + + return ['internal/redirect', ['url' => $url]]; } - return ['internal/redirect', ['url' => $url]]; - } - - public static function queueToRunAfterResponse(callable $fn) - { - static::$queuedFunctions[] = $fn; - } - - public static function shutdown() - { - while ($fn = array_shift(static::$queuedFunctions)) + public static function queueToRunAfterResponse(callable $fn) { - call_user_func($fn); + static::$queuedFunctions[] = $fn; + } + + public static function shutdown() + { + while ($fn = array_shift(static::$queuedFunctions)) { + call_user_func($fn); + } } - } } diff --git a/controller/Request.class.php b/controller/Request.class.php index 5ef4804d..09dd5351 100644 --- a/controller/Request.class.php +++ b/controller/Request.class.php @@ -2,161 +2,158 @@ class Request { - const GET = 'GET'; - const POST = 'POST'; - const HEAD = 'HEAD'; - const OPTIONS = 'OPTIONS'; + const GET = 'GET'; + const POST = 'POST'; + const HEAD = 'HEAD'; + const OPTIONS = 'OPTIONS'; - protected static $method; + protected static $method; - public static function getParam(string $key, $default = null) - { - return $_POST[$key] ?? $_GET[$key] ?? $default; - } - - public static function getPostParam(string $key, $default = null) - { - return $_POST[$key] ?? $default; - } - - public static function getMethod(): string - { - if (!static::$method) + public static function getParam(string $key, $default = null) { - $method = static::getHeader('REQUEST_METHOD') ? strtoupper(static::getHeader('REQUEST_METHOD')) : null; - - static::$method = in_array($method, [static::GET, static::POST, static::HEAD, static::OPTIONS]) ? $method : static::GET; + return $_POST[$key] ?? $_GET[$key] ?? $default; } - return static::$method; - } - protected static function getHeader(string $name, $default = null) - { - return $_SERVER[strtoupper($name)] ?? $default; - } + public static function getPostParam(string $key, $default = null) + { + return $_POST[$key] ?? $default; + } - public static function getHttpHeader(string $name, $default = null) - { - $header = 'HTTP_' . strtoupper(strtr($name, '-', '_')); - return isset($_SERVER[$header]) ? static::stripSlashes($_SERVER[$header]) : $default; - } + public static function getMethod(): string + { + if (!static::$method) { + $method = static::getHeader('REQUEST_METHOD') ? strtoupper(static::getHeader('REQUEST_METHOD')) : null; - protected static function stripSlashes($value) - { - return is_array($value) ? array_map(get_called_class() . '::stripSlashes', $value) : stripslashes($value); - } + static::$method = in_array($method, [static::GET, static::POST, static::HEAD, static::OPTIONS]) ? $method : static::GET; + } + return static::$method; + } + + protected static function getHeader(string $name, $default = null) + { + return $_SERVER[strtoupper($name)] ?? $default; + } + + public static function getHttpHeader(string $name, $default = null) + { + $header = 'HTTP_' . strtoupper(strtr($name, '-', '_')); + return isset($_SERVER[$header]) ? static::stripSlashes($_SERVER[$header]) : $default; + } + + protected static function stripSlashes($value) + { + return is_array($value) ? array_map(get_called_class() . '::stripSlashes', $value) : stripslashes($value); + } - public static function isGet(): bool - { - return static::getMethod() == static::GET; - } + public static function isGet(): bool + { + return static::getMethod() == static::GET; + } - public static function isPost(): bool - { - return static::getMethod() == static::POST; - } + public static function isPost(): bool + { + return static::getMethod() == static::POST; + } - public static function isCacheableMethod(): bool - { - return in_array(static::getMethod(), [static::GET, static::HEAD]); - } + public static function isCacheableMethod(): bool + { + return in_array(static::getMethod(), [static::GET, static::HEAD]); + } - public static function getOriginalIp(): string - { - return static::getHttpHeader('X-Real-Ip') ?? + public static function getOriginalIp(): string + { + return static::getHttpHeader('X-Real-Ip') ?? (static::getHttpHeader('X-Forwarded-For') ? trim(explode(',', static::getHttpHeader('X-Forwarded-For'))[0]) : static::getHeader('REMOTE_ADDR', '')); - } + } - public static function getUserAgent(): string - { - return static::getHttpHeader('User-Agent') ?? ''; - } - - public static function getRoutingUri() - { - $host = preg_replace('/^www\./', '', static::getHost()); - switch($host) + public static function getUserAgent(): string { + return static::getHttpHeader('User-Agent') ?? ''; + } + + public static function getRoutingUri() + { + $host = preg_replace('/^www\./', '', static::getHost()); + switch ($host) { case 'betteryoutube.com': case 'lbrycreators.com': return '/youtube'; } - return static::getRelativeUri(); - } - - public static function getHost(): string - { - // apparently trailing period is legal: http://www.dns-sd.org/TrailingDotsInDomainNames.html - return static::getHttpHeader('Host') ? rtrim(static::getHttpHeader('Host'), '.') : ''; - } - - public static function getSubDomain(): string - { - $host = static::getHost(); - $domainParts = explode('.', $host); - $domainPartCount = count($domainParts); - - if (count($domainParts) < 1) - { - return ''; + return static::getRelativeUri(); } - $isLocalhost = $domainParts[$domainPartCount - 1] === 'localhost'; - - if (count($domainParts) < ($isLocalhost ? 2 : 3)) + public static function getHost(): string { - return ''; + // apparently trailing period is legal: http://www.dns-sd.org/TrailingDotsInDomainNames.html + return static::getHttpHeader('Host') ? rtrim(static::getHttpHeader('Host'), '.') : ''; } - return $isLocalhost ? + public static function getSubDomain(): string + { + $host = static::getHost(); + $domainParts = explode('.', $host); + $domainPartCount = count($domainParts); + + if (count($domainParts) < 1) { + return ''; + } + + $isLocalhost = $domainParts[$domainPartCount - 1] === 'localhost'; + + if (count($domainParts) < ($isLocalhost ? 2 : 3)) { + return ''; + } + + return $isLocalhost ? $domainParts[$domainPartCount - 2] : $domainParts[$domainPartCount - 3]; - } + } - public static function getHostAndProto(): string - { - return (static::isSSL() ? 'https' : 'http') . '://' . static::getHost(); - } + public static function getHostAndProto(): string + { + return (static::isSSL() ? 'https' : 'http') . '://' . static::getHost(); + } - public static function isSSL(): bool - { - return static::getHeader('HTTPS') || strtolower(static::getHttpHeader('X_FORWARDED_PROTO')) == 'https'; - } + public static function isSSL(): bool + { + return static::getHeader('HTTPS') || strtolower(static::getHttpHeader('X_FORWARDED_PROTO')) == 'https'; + } - public static function getServerName(): string - { - return static::getHeader('SERVER_NAME'); - } + public static function getServerName(): string + { + return static::getHeader('SERVER_NAME'); + } - public static function getReferrer(string $fallback = '/') - { - return Request::getHttpHeader('Referer', $fallback); - } + public static function getReferrer(string $fallback = '/') + { + return Request::getHttpHeader('Referer', $fallback); + } - public static function getRelativeUri(): string - { - return static::getHeader('REQUEST_URI') ? parse_url(static::getHeader('REQUEST_URI'), PHP_URL_PATH) : ''; - } + public static function getRelativeUri(): string + { + return static::getHeader('REQUEST_URI') ? parse_url(static::getHeader('REQUEST_URI'), PHP_URL_PATH) : ''; + } - public static function isGzipAccepted(): bool - { - return static::getHttpHeader('Accept-Encoding') && strpos(strtolower(static::getHttpHeader('Accept-Encoding')), 'gzip') !== false; - } + public static function isGzipAccepted(): bool + { + return static::getHttpHeader('Accept-Encoding') && strpos(strtolower(static::getHttpHeader('Accept-Encoding')), 'gzip') !== false; + } - public static function isRobot() - { - $bots = [ + public static function isRobot() + { + $bots = [ 'bot', 'spider', 'crawler', 'siteexplorer', 'yahoo', 'slurp', 'dataaccessd', 'facebook', 'twitter', 'coccoc', 'calendar', 'curl', 'wget', 'panopta', 'blogtrottr', 'zapier', 'newrelic', 'luasocket', 'okhttp', 'python' ]; - return preg_match('/(' . join('|', $bots) . ')/i', static::getUserAgent()); - } - //Method that encode html tags to special character - public static function encodeStringFromUser($string){ - return htmlspecialchars($string, ENT_QUOTES, 'UTF-8'); - } + return preg_match('/(' . join('|', $bots) . ')/i', static::getUserAgent()); + } + //Method that encode html tags to special character + public static function encodeStringFromUser($string) + { + return htmlspecialchars($string, ENT_QUOTES, 'UTF-8'); + } } diff --git a/controller/Session.class.php b/controller/Session.class.php index a0885bd6..4b303410 100644 --- a/controller/Session.class.php +++ b/controller/Session.class.php @@ -1,8 +1,8 @@ $val) + public static function get($key, $default = null, $ns = self::NAMESPACE_DEFAULT) { - static::set($key, true, static::NAMESPACE_FLASH_REMOVE); + return $_SESSION[$ns][$key] ?? $default; } - Controller::queueToRunAfterResponse([__CLASS__, 'cleanupFlashes']); - } - - public static function cleanupFlashes() - { - foreach(array_keys(static::getNamespace(static::NAMESPACE_FLASH_REMOVE)) as $flashName) + public static function set($key, $value, $ns = self::NAMESPACE_DEFAULT) { - static::unsetKey($flashName, static::NAMESPACE_FLASH); - static::unsetKey($flashName, static::NAMESPACE_FLASH_REMOVE); + $_SESSION[$ns][$key] = $value; } - } - public static function getFlash($name, $default = null) - { - return static::get($name, $default, static::NAMESPACE_FLASH); - } + public static function unsetKey($key, $ns = self::NAMESPACE_DEFAULT) + { + unset($_SESSION[$ns][$key]); + } - public static function setFlash($name, $value) - { - static::set($name, $value, static::NAMESPACE_FLASH); - static::unsetKey($name, static::NAMESPACE_FLASH_REMOVE); - } + protected static function getNamespace($ns) + { + return $_SESSION[$ns] ?? []; + } - public function persistFlashes() - { - static::unsetNamespace(static::NAMESPACE_FLASH_REMOVE); - } + protected static function setNamespace($ns, $value) + { + $_SESSION[$ns] = $value; + } + + protected static function unsetNamespace($ns) + { + unset($_SESSION[$ns]); + } + + protected static function initFlashes() + { + foreach (static::getNamespace(static::NAMESPACE_FLASH) as $key => $val) { + static::set($key, true, static::NAMESPACE_FLASH_REMOVE); + } + + Controller::queueToRunAfterResponse([__CLASS__, 'cleanupFlashes']); + } + + public static function cleanupFlashes() + { + foreach (array_keys(static::getNamespace(static::NAMESPACE_FLASH_REMOVE)) as $flashName) { + static::unsetKey($flashName, static::NAMESPACE_FLASH); + static::unsetKey($flashName, static::NAMESPACE_FLASH_REMOVE); + } + } + + public static function getFlash($name, $default = null) + { + return static::get($name, $default, static::NAMESPACE_FLASH); + } + + public static function setFlash($name, $value) + { + static::set($name, $value, static::NAMESPACE_FLASH); + static::unsetKey($name, static::NAMESPACE_FLASH_REMOVE); + } + + public function persistFlashes() + { + static::unsetNamespace(static::NAMESPACE_FLASH_REMOVE); + } } diff --git a/controller/action/AcquisitionActions.class.php b/controller/action/AcquisitionActions.class.php index e5e8cd79..3791bdfc 100644 --- a/controller/action/AcquisitionActions.class.php +++ b/controller/action/AcquisitionActions.class.php @@ -2,138 +2,133 @@ class AcquisitionActions extends Actions { - public static function executeThanks() - { - return ['acquisition/thanks']; - } - - public static function executeYouTubeSub() - { - if (!Request::isPost()) { - return Controller::redirect('/youtube'); + public static function executeThanks() + { + return ['acquisition/thanks']; } - $email = Request::getPostParam('email'); + public static function executeYouTubeSub() + { + if (!Request::isPost()) { + return Controller::redirect('/youtube'); + } - if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { - Session::setFlash('error', 'Please enter a valid email.'); - return Controller::redirect('/youtube'); + $email = Request::getPostParam('email'); + + if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { + Session::setFlash('error', 'Please enter a valid email.'); + return Controller::redirect('/youtube'); + } + + Salesforce::createContact($email, SalesForce::DEFAULT_LIST_ID, 'YouTube Campaign'); + Mailgun::sendYouTubeWarmLead(['email' => $email]); + + Session::setFlash('success', 'Thanks! We\'ll be in touch. The good kind of touch.'); + + return Controller::redirect(Request::getReferrer(), 303); } - Salesforce::createContact($email, SalesForce::DEFAULT_LIST_ID, 'YouTube Campaign'); - Mailgun::sendYouTubeWarmLead(['email' => $email]); + public static function executeYouTube() + { + if (isset($_GET['error_message'])) { + $error_message = Request::encodeStringFromUser($_GET['error_message']); + } - Session::setFlash('success', 'Thanks! We\'ll be in touch. The good kind of touch.'); - - return Controller::redirect(Request::getReferrer(), 303); - } - - public static function executeYouTube() - { - if(isset($_GET['error_message'])){ - $error_message = Request::encodeStringFromUser($_GET['error_message']); - } - - return ['acquisition/youtube', [ + return ['acquisition/youtube', [ 'reward' => LBRY::youtubeReward(), 'error_message' => $error_message ?? '' ]]; - } - - public static function executeVerify(string $token) - { - return ['acquisition/verify', ['token' => $token]]; - } - - public static function executeYoutubeToken() - { - return ['acquisition/youtube_token', ['_no_layout' => true]]; - } - - public static function executeYoutubeStatus(string $token) - { - if(isset($_GET['error_message'])){ - $error_message = Request::encodeStringFromUser($_GET['error_message']); } - $data = LBRY::statusYoutube($token); - if ($data['success'] == false){ - Controller::redirect('/youtube?error=true&error_message=' . $data['error']); + public static function executeVerify(string $token) + { + return ['acquisition/verify', ['token' => $token]]; } - return ['acquisition/youtube_status', [ + + public static function executeYoutubeToken() + { + return ['acquisition/youtube_token', ['_no_layout' => true]]; + } + + public static function executeYoutubeStatus(string $token) + { + if (isset($_GET['error_message'])) { + $error_message = Request::encodeStringFromUser($_GET['error_message']); + } + + $data = LBRY::statusYoutube($token); + if ($data['success'] == false) { + Controller::redirect('/youtube?error=true&error_message=' . $data['error']); + } + return ['acquisition/youtube_status', [ 'token' => $token, 'status_token' => $data, 'error_message' => $error_message ?? '' ]]; - } - - public static function actionYoutubeToken(string $desired_lbry_channel_name) - { - - $desired_lbry_channel_name_is_valid = static::lbry_channel_verification($desired_lbry_channel_name); - - if ($desired_lbry_channel_name_is_valid) { - $token = LBRY::connectYoutube($desired_lbry_channel_name); - if ($token['success'] == false) { - Controller::redirect('/youtube?error=true&error_message=' . $token['error']); - } - else { - Controller::redirect($token['data']); - } - } - } - public static function actionYoutubeEdit($status_token, $channel_name, $email, $sync_consent) - { - $current_value = LBRY::statusYoutube($status_token); - if($current_value['data']['email'] == $email) + + public static function actionYoutubeToken(string $desired_lbry_channel_name) { - $status = LBRY::editYoutube($status_token, $channel_name, null, $sync_consent); + $desired_lbry_channel_name_is_valid = static::lbry_channel_verification($desired_lbry_channel_name); + + if ($desired_lbry_channel_name_is_valid) { + $token = LBRY::connectYoutube($desired_lbry_channel_name); + if ($token['success'] == false) { + Controller::redirect('/youtube?error=true&error_message=' . $token['error']); + } else { + Controller::redirect($token['data']); + } + } } - else + public static function actionYoutubeEdit($status_token, $channel_name, $email, $sync_consent) { - $status = LBRY::editYoutube($status_token, $channel_name, $email, $sync_consent); + $current_value = LBRY::statusYoutube($status_token); + if ($current_value['data']['email'] == $email) { + $status = LBRY::editYoutube($status_token, $channel_name, null, $sync_consent); + } else { + $status = LBRY::editYoutube($status_token, $channel_name, $email, $sync_consent); + } + + if ($status['success'] == false) { + Controller::redirect("/youtube/status/". $status_token . "?error=true&error_message=" . $status['error']); + } else { + Controller::redirect("/youtube/status/" . $status_token); + } + } + public static function executeYoutubeEdit() + { + return ['acquisition/youtube_edit']; } - if($status['success'] == false){ - Controller::redirect("/youtube/status/". $status_token . "?error=true&error_message=" . $status['error']); + public static function executeRedirectYoutube() + { + return ['acquisition/youtube_status_redirect']; } - else{ - Controller::redirect("/youtube/status/" . $status_token); - } - } - public static function executeYoutubeEdit(){ - return ['acquisition/youtube_edit']; - } - public static function executeRedirectYoutube(){ - return ['acquisition/youtube_status_redirect']; - } - - protected static function email_verification($email) - { - if (preg_match('/\S+@\S+\.\S+/', $email)) { - return true; - } else { - return false; + protected static function email_verification($email) + { + if (preg_match('/\S+@\S+\.\S+/', $email)) { + return true; + } else { + return false; + } } - } - protected static function youtube_channel_verification($youtube_channel_id) - { - if (preg_match('/^UC[A-Za-z0-9_-]{22}$/', $youtube_channel_id)) { - return true; - } else { - return false; + protected static function youtube_channel_verification($youtube_channel_id) + { + if (preg_match('/^UC[A-Za-z0-9_-]{22}$/', $youtube_channel_id)) { + return true; + } else { + return false; + } } - } - protected static function lbry_channel_verification($lbry_channel) - { - if (preg_match('/[1-z]+/', $lbry_channel)) { - return true; - } else { - return false; + protected static function lbry_channel_verification($lbry_channel) + { + if (preg_match('/[1-z]+/', $lbry_channel)) { + return true; + } else { + return false; + } } - } } diff --git a/controller/action/ContentActions.class.php b/controller/action/ContentActions.class.php index 243c4fc1..954859b7 100644 --- a/controller/action/ContentActions.class.php +++ b/controller/action/ContentActions.class.php @@ -2,7 +2,7 @@ class ContentActions extends Actions { - const + const SLUG_RSS = 'rss.xml', SLUG_NEWS = 'news', SLUG_FAQ = 'faq', @@ -26,67 +26,62 @@ class ContentActions extends Actions VIEW_FOLDER_CREDIT_REPORTS = self::CONTENT_DIR . '/' . self::SLUG_CREDIT_REPORTS, VIEW_FOLDER_JOBS = self::CONTENT_DIR . '/' . self::SLUG_JOBS; - public static function executeHome(): array - { - Response::enableHttpCache(); - return ['page/home']; - } - - public static function executeNews(string $slug = null): array - { - Response::enableHttpCache(); - - if (!$slug || $slug == static::SLUG_RSS) + public static function executeHome(): array { - $posts = array_filter( - Post::find(static::VIEW_FOLDER_NEWS, Post::SORT_DATE_DESC), - function(Post $post) { - return !$post->getDate() || $post->getDate()->format('U') <= date('U'); - }); + Response::enableHttpCache(); + return ['page/home']; + } - if ($slug == static::SLUG_RSS) - { - Response::setHeader(Response::HEADER_CONTENT_TYPE, 'text/xml; charset=utf-8'); - return ['content/rss', [ + public static function executeNews(string $slug = null): array + { + Response::enableHttpCache(); + + if (!$slug || $slug == static::SLUG_RSS) { + $posts = array_filter( + Post::find(static::VIEW_FOLDER_NEWS, Post::SORT_DATE_DESC), + function (Post $post) { + return !$post->getDate() || $post->getDate()->format('U') <= date('U'); + } + ); + + if ($slug == static::SLUG_RSS) { + Response::setHeader(Response::HEADER_CONTENT_TYPE, 'text/xml; charset=utf-8'); + return ['content/rss', [ 'posts' => array_slice($posts, 0, 10), '_no_layout' => true ]]; - } + } - return ['content/news', [ + return ['content/news', [ 'posts' => $posts, View::LAYOUT_PARAMS => [ 'showRssLink' => true ] ]]; - } + } - try - { - $post = Post::load(static::SLUG_NEWS . '/' . ltrim($slug, '/')); - } - catch (PostNotFoundException $e) - { - return NavActions::execute404(); - } + try { + $post = Post::load(static::SLUG_NEWS . '/' . ltrim($slug, '/')); + } catch (PostNotFoundException $e) { + return NavActions::execute404(); + } - return ['content/news-post', [ + return ['content/news-post', [ 'post' => $post, View::LAYOUT_PARAMS => [ 'showRssLink' => true ] ]]; - } + } - public static function executeFaq(string $slug = null): array - { - Response::enableHttpCache(); - - if (!$slug) + public static function executeFaq(string $slug = null): array { - $allPosts = Post::find(static::VIEW_FOLDER_FAQ, Post::SORT_ORD_ASC); + Response::enableHttpCache(); - $allCategories = [ + if (!$slug) { + $allPosts = Post::find(static::VIEW_FOLDER_FAQ, Post::SORT_ORD_ASC); + + $allCategories = [ 'LBRY 101' => 'Intro to LBRY', 'getstarted' => 'Getting Started', 'setup' => 'Installing and Running LBRY', @@ -100,190 +95,173 @@ class ContentActions extends Actions 'other' => 'Other Questions', ] + Post::collectMetadata($allPosts, 'category'); - $selectedCategory = Request::getParam('category'); - $filters = array_filter([ + $selectedCategory = Request::getParam('category'); + $filters = array_filter([ 'category' => $selectedCategory && isset($allCategories[$selectedCategory]) ? $selectedCategory : null, ]); - $posts = $filters ? Post::filter($allPosts, $filters) : $allPosts; + $posts = $filters ? Post::filter($allPosts, $filters) : $allPosts; - $groups = array_fill_keys(array_keys($allCategories), []); + $groups = array_fill_keys(array_keys($allCategories), []); - foreach ($posts as $post) - { - $groups[$post->getCategory()][] = $post; - } + foreach ($posts as $post) { + $groups[$post->getCategory()][] = $post; + } - return ['content/faq', [ + return ['content/faq', [ 'categories' => $allCategories, 'selectedCategory' => $selectedCategory, 'postGroups' => $groups ]]; + } + + try { + $post = Post::load(static::SLUG_FAQ . '/' . ltrim($slug, '/')); + } catch (PostNotFoundException $e) { + return Controller::redirect('/' . static::SLUG_FAQ); + } + return ['content/faq-post', ['post' => $post]]; } - try + + public static function executeCreditReports(string $year = null, string $month = null): array { - $post = Post::load(static::SLUG_FAQ . '/' . ltrim($slug, '/')); - } - catch (PostNotFoundException $e) - { - return Controller::redirect('/' . static::SLUG_FAQ); - } - return ['content/faq-post', ['post' => $post]]; - } + Response::enableHttpCache(); + $posts = Post::find(static::VIEW_FOLDER_CREDIT_REPORTS); - public static function executeCreditReports(string $year = null, string $month = null): array - { - Response::enableHttpCache(); - - $posts = Post::find(static::VIEW_FOLDER_CREDIT_REPORTS); - - return ['content/credit-reports', [ + return ['content/credit-reports', [ 'posts' => $posts ]]; - } - - public static function executeCreditReport(string $year = null, string $quarter = null): array - { - - Response::enableHttpCache(); - - try - { - $post = Post::load(static::SLUG_CREDIT_REPORTS . '/' . $year . '-Q' . $quarter); } - catch (PostNotFoundException $e) + + public static function executeCreditReport(string $year = null, string $quarter = null): array { - return Controller::redirect('/' . static::SLUG_CREDIT_REPORTS); - } - $metadata = $post->getMetadata(); - return ['content/credit-report', [ + Response::enableHttpCache(); + + try { + $post = Post::load(static::SLUG_CREDIT_REPORTS . '/' . $year . '-Q' . $quarter); + } catch (PostNotFoundException $e) { + return Controller::redirect('/' . static::SLUG_CREDIT_REPORTS); + } + $metadata = $post->getMetadata(); + return ['content/credit-report', [ 'post' => $post, 'sheetUrl' => $metadata['sheet'] ]]; - } - - public static function executePress(string $slug = null): array - { - Response::enableHttpCache(); - try - { - $post = Post::load(static::SLUG_PRESS . '/' . ltrim($slug, '/')); } - catch (PostNotFoundException $e) + + public static function executePress(string $slug = null): array { - return NavActions::execute404(); + Response::enableHttpCache(); + try { + $post = Post::load(static::SLUG_PRESS . '/' . ltrim($slug, '/')); + } catch (PostNotFoundException $e) { + return NavActions::execute404(); + } + return ['content/press-post', ['post' => $post]]; } - return ['content/press-post', ['post' => $post]]; - } - protected static function convertBountyAmount($amount) - { - return is_numeric($amount) ? round($amount / LBRY::getLBCtoUSDRate(), -1) : $amount; - } - - public static function executeBounty(string $slug = null): array - { - Response::enableHttpCache(); - - - - if ($slug) + protected static function convertBountyAmount($amount) { - list($metadata, $postHtml) = View::parseMarkdown(ContentActions::VIEW_FOLDER_BOUNTY . '/' . $slug . '.md'); + return is_numeric($amount) ? round($amount / LBRY::getLBCtoUSDRate(), -1) : $amount; + } - $metadata['lbc_award'] = static::convertBountyAmount($metadata['award']); + public static function executeBounty(string $slug = null): array + { + Response::enableHttpCache(); - if (!$postHtml) - { - return NavActions::execute404(); - } - return ['bounty/show', [ + + if ($slug) { + list($metadata, $postHtml) = View::parseMarkdown(ContentActions::VIEW_FOLDER_BOUNTY . '/' . $slug . '.md'); + + $metadata['lbc_award'] = static::convertBountyAmount($metadata['award']); + + if (!$postHtml) { + return NavActions::execute404(); + } + + return ['bounty/show', [ 'postHtml' => $postHtml, 'metadata' => $metadata ]]; - } + } - $allBounties = Post::find(static::CONTENT_DIR . '/bounty'); + $allBounties = Post::find(static::CONTENT_DIR . '/bounty'); - $allCategories = ['' => ''] + Post::collectMetadata($allBounties, 'category'); - $allStatuses = ['' => ''] + array_merge(Post::collectMetadata($allBounties, 'status'), ['complete' => 'unavailable']); + $allCategories = ['' => ''] + Post::collectMetadata($allBounties, 'category'); + $allStatuses = ['' => ''] + array_merge(Post::collectMetadata($allBounties, 'status'), ['complete' => 'unavailable']); - $selectedStatus = Request::getParam('status', 'available'); - $selectedCategory = Request::getParam('category'); + $selectedStatus = Request::getParam('status', 'available'); + $selectedCategory = Request::getParam('category'); - $filters = array_filter([ + $filters = array_filter([ 'category' => $selectedCategory && isset($allCategories[$selectedCategory]) ? $selectedCategory : null, 'status' => $selectedStatus && isset($allStatuses[$selectedStatus]) ? $selectedStatus : null ]); - $bounties = $filters ? Post::filter($allBounties, $filters) : $allBounties; + $bounties = $filters ? Post::filter($allBounties, $filters) : $allBounties; - uasort($bounties, function($postA, $postB) { - $metadataA = $postA->getMetadata(); - $metadataB = $postB->getMetadata(); - $awardA = strpos('-', $metadataA['award']) !== false ? rtrim(explode('-', $metadataA['award'])[0], '+') : $metadataA['award']; - $awardB = strpos('-', $metadataB['award']) !== false ? rtrim(explode('-', $metadataB['award'])[0], '+') : $metadataB['award']; - if ($awardA != $awardB) - { - return $awardA > $awardB ? -1 : 1; - } - return $metadataA['title'] < $metadataB['title'] ? -1 : 1; - }); + uasort($bounties, function ($postA, $postB) { + $metadataA = $postA->getMetadata(); + $metadataB = $postB->getMetadata(); + $awardA = strpos('-', $metadataA['award']) !== false ? rtrim(explode('-', $metadataA['award'])[0], '+') : $metadataA['award']; + $awardB = strpos('-', $metadataB['award']) !== false ? rtrim(explode('-', $metadataB['award'])[0], '+') : $metadataB['award']; + if ($awardA != $awardB) { + return $awardA > $awardB ? -1 : 1; + } + return $metadataA['title'] < $metadataB['title'] ? -1 : 1; + }); - foreach($bounties as $bounty) { - $metadata = $bounty->getMetadata(); - $bounty->setMetadataItem('lbc_award', static::convertBountyAmount($metadata['award'])); - } + foreach ($bounties as $bounty) { + $metadata = $bounty->getMetadata(); + $bounty->setMetadataItem('lbc_award', static::convertBountyAmount($metadata['award'])); + } - return ['bounty/list', [ + return ['bounty/list', [ 'bounties' => $bounties, 'categories' => $allCategories, 'statuses' => $allStatuses, 'selectedCategory' => $selectedCategory, 'selectedStatus' => $selectedStatus ]]; - } - - public static function executeRoadmap() - { - $cache = !Request::getParam('nocache'); - $githubItems = Github::listRoadmapChangesets($cache); - $projectMaxVersions = []; - foreach($githubItems as $group => $items) - { - if ($items) - { - $firstItem = reset($items); - $project = $firstItem['project']; - if (!isset($projectMaxVersions[$project]) || $firstItem['sort_key'] > $projectMaxVersions[$project]) - { - $projectMaxVersions[$project] = $firstItem['sort_key']; - } - } } - $items = array_merge(Asana::listRoadmapTasks($cache), $githubItems); - return ['content/roadmap', [ + public static function executeRoadmap() + { + $cache = !Request::getParam('nocache'); + $githubItems = Github::listRoadmapChangesets($cache); + $projectMaxVersions = []; + foreach ($githubItems as $group => $items) { + if ($items) { + $firstItem = reset($items); + $project = $firstItem['project']; + if (!isset($projectMaxVersions[$project]) || $firstItem['sort_key'] > $projectMaxVersions[$project]) { + $projectMaxVersions[$project] = $firstItem['sort_key']; + } + } + } + + $items = array_merge(Asana::listRoadmapTasks($cache), $githubItems); + return ['content/roadmap', [ 'projectMaxVersions' => $projectMaxVersions, 'items' => $items ]]; - } + } - public static function executePressKit(): array - { - $zipFileName = 'lbry-press-kit-' . date('Y-m-d') . '.zip'; - $zipPath = tempnam('/tmp', $zipFileName); + public static function executePressKit(): array + { + $zipFileName = 'lbry-press-kit-' . date('Y-m-d') . '.zip'; + $zipPath = tempnam('/tmp', $zipFileName); - $zip = new ZipArchive(); - $zip->open($zipPath, ZipArchive::OVERWRITE); + $zip = new ZipArchive(); + $zip->open($zipPath, ZipArchive::OVERWRITE); // $pageHtml = View::render('page/press-kit', ['showHeader' => false]); // $html = << -// + // + // // // LBRY Press Kit // @@ -292,28 +270,26 @@ class ContentActions extends Actions // // $pageHtml // -// -//EOD; + // + //EOD; // // $zip->addFromString('press.html', $html); - foreach (glob(ROOT_DIR . '/web/img/press/*') as $productImgPath) - { - $imgPathTokens = explode('/', $productImgPath); - $imgName = $imgPathTokens[count($imgPathTokens) - 1]; - $zip->addFile($productImgPath, '/logo_and_product/' . $imgName); - } + foreach (glob(ROOT_DIR . '/web/img/press/*') as $productImgPath) { + $imgPathTokens = explode('/', $productImgPath); + $imgName = $imgPathTokens[count($imgPathTokens) - 1]; + $zip->addFile($productImgPath, '/logo_and_product/' . $imgName); + } - foreach (glob(ContentActions::CONTENT_DIR . '/bio/*.md') as $bioPath) - { - list($metadata, $bioHtml) = View::parseMarkdown($bioPath); - $zip->addFile($bioPath, '/team_bios/' . $metadata['name'] . ' - ' . $metadata['role'] . '.txt'); - } + foreach (glob(ContentActions::CONTENT_DIR . '/bio/*.md') as $bioPath) { + list($metadata, $bioHtml) = View::parseMarkdown($bioPath); + $zip->addFile($bioPath, '/team_bios/' . $metadata['name'] . ' - ' . $metadata['role'] . '.txt'); + } - /* - * team bio images are no longer included in press kit now that they've moved to spee.ch - * this should be fixed if we care about the press-kit page - */ + /* + * team bio images are no longer included in press kit now that they've moved to spee.ch + * this should be fixed if we care about the press-kit page + */ // foreach (array_filter(glob(ROOT_DIR . '/web/img/team/*.jpg'), function ($path) // { // return strpos($path, 'spooner') === false; @@ -325,35 +301,34 @@ class ContentActions extends Actions // } - $zip->close(); + $zip->close(); - Response::enableHttpCache(); - Response::setDownloadHttpHeaders($zipFileName, 'application/zip', filesize($zipPath)); + Response::enableHttpCache(); + Response::setDownloadHttpHeaders($zipFileName, 'application/zip', filesize($zipPath)); - return ['internal/zip', [ + return ['internal/zip', [ '_no_layout' => true, 'zipPath' => $zipPath ]]; - } + } - public static function prepareBioPartial(array $vars): array - { - $person = $vars['person']; - $path = 'bio/' . $person . '.md'; - list($metadata, $bioHtml) = View::parseMarkdown($path); - $imgSrc = 'https://spee.ch/@lbryteam:6/' . $person . '.jpg'; - return $vars + $metadata + [ + public static function prepareBioPartial(array $vars): array + { + $person = $vars['person']; + $path = 'bio/' . $person . '.md'; + list($metadata, $bioHtml) = View::parseMarkdown($path); + $imgSrc = 'https://spee.ch/@lbryteam:6/' . $person . '.jpg'; + return $vars + $metadata + [ 'imgSrc' => $imgSrc, 'bioHtml' => $bioHtml, 'orientation' => 'vertical' ]; + } - } - - public static function preparePostAuthorPartial(array $vars): array - { - $post = $vars['post']; - return [ + public static function preparePostAuthorPartial(array $vars): array + { + $post = $vars['post']; + return [ 'authorName' => $post->getAuthorName(), 'photoImgSrc' => $post->getAuthorPhoto(), 'authorBioHtml' => $post->getAuthorBioHtml(), @@ -361,52 +336,55 @@ class ContentActions extends Actions 'authorTwitter' => $post->getAuthorTwitterID(), 'authorEmail' => $post->getAuthorPostEmail() ]; - } + } - public static function preparePostListPartial(array $vars): array - { - $count = $vars['count'] ?? 3; - return [ + public static function preparePostListPartial(array $vars): array + { + $count = $vars['count'] ?? 3; + return [ 'posts' => array_slice(Post::find(static::VIEW_FOLDER_NEWS, Post::SORT_DATE_DESC), 0, $count) ]; - } - public static function executePostCategoryFilter(string $category) - { - Response::enableHttpCache(); + } + public static function executePostCategoryFilter(string $category) + { + Response::enableHttpCache(); - $filter_post = []; + $filter_post = []; - $posts = array_filter( + $posts = array_filter( Post::find(static::VIEW_FOLDER_NEWS, Post::SORT_DATE_DESC), - function(Post $post) use ($category) { - return (($post->getCategory() === $category) && (!$post->getDate() || $post->getDate()->format('U') <= date('U'))); - }); + function (Post $post) use ($category) { + return (($post->getCategory() === $category) && (!$post->getDate() || $post->getDate()->format('U') <= date('U'))); + } + ); - return ['content/news', [ + return ['content/news', [ 'posts' => $posts, View::LAYOUT_PARAMS => [ 'showRssLink' => true ] ]]; - } + } - public static function prepareJobsPartial(array $vars) - { - $jobs = + public static function prepareJobsPartial(array $vars) + { + $jobs = array_filter( array_map('View::parseMarkdown', glob(static::VIEW_FOLDER_JOBS . '/*')), - function($job) { return $job[0]['status'] !== 'closed'; } + function ($job) { + return $job[0]['status'] !== 'closed'; + } ); - usort($jobs, function($jobA, $jobB){ - if ($jobA[0]['status'] === 'active' xor $jobB[0]['status'] === 'active') { - return $jobA[0]['status'] === 'active' ? -1 : 1; - } - return $jobA[0]['order'] <=> $jobB[0]['order']; - }); + usort($jobs, function ($jobA, $jobB) { + if ($jobA[0]['status'] === 'active' xor $jobB[0]['status'] === 'active') { + return $jobA[0]['status'] === 'active' ? -1 : 1; + } + return $jobA[0]['order'] <=> $jobB[0]['order']; + }); - return $vars + ['jobs' => $jobs]; - } + return $vars + ['jobs' => $jobs]; + } } diff --git a/controller/action/DeveloperActions.class.php b/controller/action/DeveloperActions.class.php index 547c688b..69ab7e8f 100644 --- a/controller/action/DeveloperActions.class.php +++ b/controller/action/DeveloperActions.class.php @@ -2,115 +2,108 @@ class DeveloperActions extends Actions { - const DEVELOPER_REWARD = 10, + const DEVELOPER_REWARD = 10, API_DOC_URL = 'https://lbryio.github.io/lbry/'; - public static function executeQuickstart(string $step = null) - { - $stepLabels = [ + public static function executeQuickstart(string $step = null) + { + $stepLabels = [ '' => 'Home', 'install' => 'Installation', 'api' => 'The API', 'credits' => 'Credits' ]; - $allSteps = array_keys($stepLabels); - $currentStep = $step ?: $allSteps[0]; + $allSteps = array_keys($stepLabels); + $currentStep = $step ?: $allSteps[0]; - $viewParams = [ + $viewParams = [ 'currentStep' => $currentStep, 'stepLabels' => $stepLabels ]; - if ($currentStep !== 'all') - { - if (!isset($stepLabels[$currentStep])) - { - Controller::redirect('/quickstart'); - } + if ($currentStep !== 'all') { + if (!isset($stepLabels[$currentStep])) { + Controller::redirect('/quickstart'); + } - $stepNum = array_flip($allSteps)[$currentStep]; + $stepNum = array_flip($allSteps)[$currentStep]; - $viewParams += [ + $viewParams += [ 'stepNum' => $stepNum, 'prevStep' => $stepNum === 0 ? null : $allSteps[$stepNum - 1], 'nextStep' => $stepNum + 1 >= count($allSteps) ? null : $allSteps[$stepNum + 1], ]; + } + + return ['developer/quickstart', $viewParams]; } - return ['developer/quickstart', $viewParams]; - } - - public static function prepareQuickstartHomePartial(array $vars) - { - return $vars + [ + public static function prepareQuickstartHomePartial(array $vars) + { + return $vars + [ 'usdValue' => static::DEVELOPER_REWARD * LBRY::getLBCtoUSDRate() ]; - } + } - public static function prepareQuickstartInstallPartial(array $vars) - { - return $vars + ['versions' => [ + public static function prepareQuickstartInstallPartial(array $vars) + { + return $vars + ['versions' => [ Os::OS_LINUX => Github::getDaemonReleaseProperty(OS::OS_LINUX, 'tag_name'), Os::OS_OSX => Github::getDaemonReleaseProperty(OS::OS_OSX, 'tag_name'), Os::OS_WINDOWS => Github::getDaemonReleaseProperty(OS::OS_WINDOWS, 'tag_name'), ]]; - } + } - public static function prepareFormNewDeveloperRewardPartial(array $vars) - { - return $vars + [ + public static function prepareFormNewDeveloperRewardPartial(array $vars) + { + return $vars + [ 'defaultWalletAddress' => Session::get(Session::KEY_DEVELOPER_CREDITS_WALLET_ADDRESS), 'error' => Session::get(Session::KEY_DEVELOPER_LAST_FORM) == "new_developer" ? Session::getFlash(Session::KEY_DEVELOPER_CREDITS_ERROR) : '', 'apiUrl' => LBRY::getApiUrl('/reward/new?reward_type=new_developer') ]; - } + } - public static function prepareFormCreditsPublishPartial(array $vars) - { - return $vars + [ + public static function prepareFormCreditsPublishPartial(array $vars) + { + return $vars + [ 'defaultWalletAddress' => Session::get(Session::KEY_DEVELOPER_CREDITS_WALLET_ADDRESS), 'error' => Session::get(Session::KEY_DEVELOPER_LAST_FORM) == "new_publish" ? Session::getFlash(Session::KEY_DEVELOPER_CREDITS_ERROR) : '', 'apiUrl' => LBRY::getApiUrl('/reward/new?reward_type=first_publish') ]; - } - - public static function executeQuickstartAuth() - { - Session::set(Session::KEY_DEVELOPER_CREDITS_WALLET_ADDRESS, trim(Request::getPostParam('wallet_address'))); - Session::set(Session::KEY_DEVELOPER_LAST_FORM, Request::getPostParam('formName')); - - if (Request::getPostParam('returnUrl')) - { - Session::set(Session::KEY_DEVELOPER_RETURN_URL_SUCCESS, Request::getPostParam('returnUrl')); } - if (!Config::get(Config::GITHUB_DEVELOPER_CREDITS_CLIENT_ID)) + public static function executeQuickstartAuth() { - throw new Exception('no github client id'); - } + Session::set(Session::KEY_DEVELOPER_CREDITS_WALLET_ADDRESS, trim(Request::getPostParam('wallet_address'))); + Session::set(Session::KEY_DEVELOPER_LAST_FORM, Request::getPostParam('formName')); - $gitHubParams = [ + if (Request::getPostParam('returnUrl')) { + Session::set(Session::KEY_DEVELOPER_RETURN_URL_SUCCESS, Request::getPostParam('returnUrl')); + } + + if (!Config::get(Config::GITHUB_DEVELOPER_CREDITS_CLIENT_ID)) { + throw new Exception('no github client id'); + } + + $gitHubParams = [ 'client_id' => Config::get(Config::GITHUB_DEVELOPER_CREDITS_CLIENT_ID), 'redirect_uri' => Request::getHostAndProto() . '/quickstart/github/callback', 'scope' => 'user:email', 'allow_signup' => false ]; - return Controller::redirect('https://github.com/login/oauth/authorize?' . http_build_query($gitHubParams)); - } - - public static function executeQuickstartGithubCallback() - { - $code = Request::getParam('code'); - - if (!$code) - { - Session::setFlash(Session::KEY_DEVELOPER_CREDITS_ERROR, 'This does not appear to be a valid response from GitHub.'); + return Controller::redirect('https://github.com/login/oauth/authorize?' . http_build_query($gitHubParams)); } - else + + public static function executeQuickstartGithubCallback() { - $authResponseData = Curl::post('https://github.com/login/oauth/access_token', [ + $code = Request::getParam('code'); + + if (!$code) { + Session::setFlash(Session::KEY_DEVELOPER_CREDITS_ERROR, 'This does not appear to be a valid response from GitHub.'); + } else { + $authResponseData = Curl::post('https://github.com/login/oauth/access_token', [ 'code' => $code, 'client_id' => Config::get(Config::GITHUB_DEVELOPER_CREDITS_CLIENT_ID), 'client_secret' => Config::get(Config::GITHUB_DEVELOPER_CREDITS_CLIENT_SECRET) @@ -119,20 +112,15 @@ class DeveloperActions extends Actions 'json_response' => true ]); - if (!$authResponseData || !isset($authResponseData['access_token'])) - { - Session::setFlash(Session::KEY_DEVELOPER_CREDITS_ERROR, 'Request to GitHub failed.'); - } - elseif (isset($authResponseData['error_description'])) - { - Session::setFlash(Session::KEY_DEVELOPER_CREDITS_ERROR, 'GitHub replied: ' . $authResponseData['error_description']); - } - else - { - Session::set(Session::KEY_GITHUB_ACCESS_TOKEN, $authResponseData['access_token']); - } - } + if (!$authResponseData || !isset($authResponseData['access_token'])) { + Session::setFlash(Session::KEY_DEVELOPER_CREDITS_ERROR, 'Request to GitHub failed.'); + } elseif (isset($authResponseData['error_description'])) { + Session::setFlash(Session::KEY_DEVELOPER_CREDITS_ERROR, 'GitHub replied: ' . $authResponseData['error_description']); + } else { + Session::set(Session::KEY_GITHUB_ACCESS_TOKEN, $authResponseData['access_token']); + } + } - return Controller::redirect(Session::get(Session::KEY_DEVELOPER_RETURN_URL_SUCCESS, '/quickstart/credits')); - } + return Controller::redirect(Session::get(Session::KEY_DEVELOPER_RETURN_URL_SUCCESS, '/quickstart/credits')); + } } diff --git a/controller/action/DownloadActions.class.php b/controller/action/DownloadActions.class.php index a663b31f..d4e680d0 100644 --- a/controller/action/DownloadActions.class.php +++ b/controller/action/DownloadActions.class.php @@ -2,120 +2,109 @@ class DownloadActions extends Actions { - public static function executeGetAppRedirect(string $ext) - { - return Controller::redirect(GitHub::getAppDownloadUrl(OS::getOsForExtension($ext)) ?: '/get', 302); - } - - public static function executeGetAppPrereleaseRedirect(string $ext) - { - return Controller::redirect(GitHub::getAppPrereleaseDownloadUrl(OS::getOsForExtension($ext)) ?: '/get', 302); - } - - - public static function executeGetDaemonRedirect(string $os) - { - $uri = null; - $oses = Os::getAll(); - - if (isset($oses[$os])) + public static function executeGetAppRedirect(string $ext) { - $uri = GitHub::getDaemonDownloadUrl($os); + return Controller::redirect(GitHub::getAppDownloadUrl(OS::getOsForExtension($ext)) ?: '/get', 302); } - return Controller::redirect($uri ?: '/quickstart', 302); - } - - public static function executeGet() - { - $osChoices = OS::getAll(); - - $os = static::guessOS(); - - if(isset($os) && isset($osChoices[$os])){ - list($uri, $osTitle, $osIcon, $buttonLabel, $analyticsLabel) = $osChoices[$os]; - $asset = Github::getAppAsset($os); - $param = ['osTitle' => $osTitle, 'osIcon' => $osIcon, 'os' => $os, 'downloadUrl' => $asset ? $asset['browser_download_url'] : null]; - return ['download/get', $param]; + public static function executeGetAppPrereleaseRedirect(string $ext) + { + return Controller::redirect(GitHub::getAppPrereleaseDownloadUrl(OS::getOsForExtension($ext)) ?: '/get', 302); } - else{ - return ['download/get-no-os']; - } - } - public static function prepareListPartial(array $vars) - { - return $vars + ['osChoices' => isset($vars['excludeOs']) ? + + public static function executeGetDaemonRedirect(string $os) + { + $uri = null; + $oses = Os::getAll(); + + if (isset($oses[$os])) { + $uri = GitHub::getDaemonDownloadUrl($os); + } + + return Controller::redirect($uri ?: '/quickstart', 302); + } + + public static function executeGet() + { + $osChoices = OS::getAll(); + + $os = static::guessOS(); + + if (isset($os) && isset($osChoices[$os])) { + list($uri, $osTitle, $osIcon, $buttonLabel, $analyticsLabel) = $osChoices[$os]; + $asset = Github::getAppAsset($os); + $param = ['osTitle' => $osTitle, 'osIcon' => $osIcon, 'os' => $os, 'downloadUrl' => $asset ? $asset['browser_download_url'] : null]; + return ['download/get', $param]; + } else { + return ['download/get-no-os']; + } + } + + public static function prepareListPartial(array $vars) + { + return $vars + ['osChoices' => isset($vars['excludeOs']) ? array_diff_key(OS::getAll(), [$vars['excludeOs'] => null]) : OS::getAll() ]; - } - - protected static function guessOs() - { - //if exact OS is requested, use that - $uri = Request::getRelativeUri(); - foreach (OS::getAll() as $os => $osChoice) - { - if ($osChoice[0] == $uri) - { - return $os; - } } - if (Request::isRobot()) + protected static function guessOs() { - return null; - } - - //otherwise guess from UA - $ua = Request::getUserAgent(); - if (stripos($ua, 'OS X') !== false) - { - return strpos($ua, 'iPhone') !== false || stripos($ua, 'iPad') !== false ? OS::OS_IOS : OS::OS_OSX; - } - if (stripos($ua, 'Linux') !== false || strpos($ua, 'X11') !== false) - { - return strpos($ua, 'Android') !== false ? OS::OS_ANDROID : OS::OS_LINUX; - } - if (stripos($ua, 'Windows') !== false) - { - return OS::OS_WINDOWS; - } - } - - protected static function getEmailParam() - { - $email = Request::getParam('e'); - - if (!$email) - { - $encoded = Request::getParam('ec'); - if ($encoded) - { - $email = Smaz::decode(Encoding::base64DecodeUrlsafe($encoded), Smaz::CODEBOOK_EMAIL); - if (!filter_var($email, FILTER_VALIDATE_EMAIL)) - { - $email = null; + //if exact OS is requested, use that + $uri = Request::getRelativeUri(); + foreach (OS::getAll() as $os => $osChoice) { + if ($osChoice[0] == $uri) { + return $os; + } + } + + if (Request::isRobot()) { + return null; + } + + //otherwise guess from UA + $ua = Request::getUserAgent(); + if (stripos($ua, 'OS X') !== false) { + return strpos($ua, 'iPhone') !== false || stripos($ua, 'iPad') !== false ? OS::OS_IOS : OS::OS_OSX; + } + if (stripos($ua, 'Linux') !== false || strpos($ua, 'X11') !== false) { + return strpos($ua, 'Android') !== false ? OS::OS_ANDROID : OS::OS_LINUX; + } + if (stripos($ua, 'Windows') !== false) { + return OS::OS_WINDOWS; } - } } - return $email; - } + protected static function getEmailParam() + { + $email = Request::getParam('e'); - public static function prepareDownloadButtonPartial(array $vars) - { - $osChoices = OS::getAll(); + if (!$email) { + $encoded = Request::getParam('ec'); + if ($encoded) { + $email = Smaz::decode(Encoding::base64DecodeUrlsafe($encoded), Smaz::CODEBOOK_EMAIL); + if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { + $email = null; + } + } + } - $os = static::guessOS(); + return $email; + } - if ($os && isset($osChoices[$os])) { - list($uri, $osTitle, $osIcon, $buttonLabel, $analyticsLabel) = $osChoices[$os]; - $release = Github::getAppRelease(); - $asset = Github::getAppAsset($os); + public static function prepareDownloadButtonPartial(array $vars) + { + $osChoices = OS::getAll(); - $vars += [ + $os = static::guessOS(); + + if ($os && isset($osChoices[$os])) { + list($uri, $osTitle, $osIcon, $buttonLabel, $analyticsLabel) = $osChoices[$os]; + $release = Github::getAppRelease(); + $asset = Github::getAppAsset($os); + + $vars += [ 'analyticsLabel' => $analyticsLabel, 'buttonLabel' => $buttonLabel, 'downloadUrl' => $asset ? $asset['browser_download_url'] : null, @@ -129,8 +118,8 @@ class DownloadActions extends Actions 'version' => $release ? $release['name'] : null, 'isAuto' => Request::getParam('auto'), ]; - } + } - return $vars; - } + return $vars; + } } diff --git a/controller/action/MailActions.class.php b/controller/action/MailActions.class.php index a2cbc9b9..991dfd4f 100644 --- a/controller/action/MailActions.class.php +++ b/controller/action/MailActions.class.php @@ -2,64 +2,61 @@ class MailActions extends Actions { - public static function executeSubscribe() - { - if (!Request::isPost()) + public static function executeSubscribe() { - return ['mail/subscribe']; + if (!Request::isPost()) { + return ['mail/subscribe']; + } + + $nextUrl = Request::getPostParam('returnUrl', '/'); + if (!$nextUrl || $nextUrl[0] != '/' || !filter_var($nextUrl, FILTER_VALIDATE_URL)) { + $nextUrl = '/'; + } + + $email = Request::getPostParam('email'); + if (!$email || !filter_var($email, FILTER_VALIDATE_EMAIL)) { + Session::set( + Session::KEY_LIST_SUB_ERROR, + $email ? __('Please provide a valid email address.') : __('Please provide an email address.') + ); + + return Controller::redirect(Request::getRelativeUri()); + } + + $tag = Request::getPostParam('tag'); + + $response = LBRY::subscribe($email, $tag); + if ($response['error']) { + return ['mail/subscribe', ['error' => $response['error']]]; + } + + return ['mail/subscribe', ['subscribeSuccess' => true, 'nextUrl' => $nextUrl]]; } - $nextUrl = Request::getPostParam('returnUrl', '/'); - if (!$nextUrl || $nextUrl[0] != '/' || !filter_var($nextUrl, FILTER_VALIDATE_URL)) + public static function executeSubscribed() { - $nextUrl = '/'; + return ['mail/subscribe', ['confirmSuccess' => true, 'learnFooter' => true]]; } - $email = Request::getPostParam('email'); - if (!$email || !filter_var($email, FILTER_VALIDATE_EMAIL)) - { - Session::set(Session::KEY_LIST_SUB_ERROR, - $email ? __('Please provide a valid email address.') : __('Please provide an email address.')); - return Controller::redirect(Request::getRelativeUri()); + public static function prepareSubscribeFormPartial(array $vars) + { + $vars += ['btnClass' => 'btn-primary', 'returnUrl' => Request::getRelativeUri()]; + + $vars['error'] = Session::get(Session::KEY_LIST_SUB_ERROR); + Session::unsetKey(Session::KEY_LIST_SUB_ERROR); + + return $vars; } - $tag = Request::getPostParam('tag'); - - $response = LBRY::subscribe($email, $tag); - if ($response['error']) + public static function executeUnsubscribe(string $email) { - return ['mail/subscribe', ['error' => $response['error']]]; + $decodedEmail = Encoding::base64DecodeUrlsafe(urldecode($email)); + if (!$decodedEmail) { + return ['mail/unsubscribe', ['error' => 'Invalid unsubscribe link']]; + } + + $response = LBRY::unsubscribe($decodedEmail); + return ['mail/unsubscribe', ['error' => $response['error']]]; } - - return ['mail/subscribe', ['subscribeSuccess' => true, 'nextUrl' => $nextUrl]]; - } - - public static function executeSubscribed() - { - return ['mail/subscribe', ['confirmSuccess' => true, 'learnFooter' => true]]; - } - - - public static function prepareSubscribeFormPartial(array $vars) - { - $vars += ['btnClass' => 'btn-primary', 'returnUrl' => Request::getRelativeUri()]; - - $vars['error'] = Session::get(Session::KEY_LIST_SUB_ERROR); - Session::unsetKey(Session::KEY_LIST_SUB_ERROR); - - return $vars; - } - - public static function executeUnsubscribe(string $email) - { - $decodedEmail = Encoding::base64DecodeUrlsafe(urldecode($email)); - if (!$decodedEmail) - { - return ['mail/unsubscribe', ['error' => 'Invalid unsubscribe link']]; - } - - $response = LBRY::unsubscribe($decodedEmail); - return ['mail/unsubscribe', ['error' => $response['error']]]; - } -} \ No newline at end of file +} diff --git a/controller/action/NavActions.class.php b/controller/action/NavActions.class.php index 5ddc575a..e56ef0fa 100644 --- a/controller/action/NavActions.class.php +++ b/controller/action/NavActions.class.php @@ -2,48 +2,48 @@ class NavActions extends Actions { - protected static $navUri; + protected static $navUri; - public static function setNavUri($uri) - { - static::$navUri = $uri; - } + public static function setNavUri($uri) + { + static::$navUri = $uri; + } - public static function getNavUri() - { - return static::$navUri ?: Request::getRelativeUri(); - } + public static function getNavUri() + { + return static::$navUri ?: Request::getRelativeUri(); + } - public static function prepareFooterPartial(array $vars) - { - return $vars + [ + public static function prepareFooterPartial(array $vars) + { + return $vars + [ 'isDark' => false, 'showLearnFooter' => false, ]; - } + } - public static function prepareGlobalItemsPartial(array $vars) - { - return $vars += [ + public static function prepareGlobalItemsPartial(array $vars) + { + return $vars += [ 'selectedItem' => static::getNavUri(), 'selectedCulture' => i18n::getLanguage() . '_' . i18n::getCountry(), 'cultures' => i18n::getAllCultures() ]; - } + } - public static function execute400(array $vars) - { - Response::setStatus(400); - return ['page/400', ['error' => $vars['error'] ?? null]]; - } + public static function execute400(array $vars) + { + Response::setStatus(400); + return ['page/400', ['error' => $vars['error'] ?? null]]; + } - public static function execute404() - { + public static function execute404() + { // $uri = Request::getRelativeUri(); // Controller::queueToRunAfterResponse(function() use($uri) { // Slack::sendErrorIfProd('404 for url ' . $uri, false); // }); - Response::setStatus(404); - return ['page/404']; - } + Response::setStatus(404); + return ['page/404']; + } } diff --git a/controller/action/OpsActions.class.php b/controller/action/OpsActions.class.php index 08e5580f..e64d1ea6 100644 --- a/controller/action/OpsActions.class.php +++ b/controller/action/OpsActions.class.php @@ -2,103 +2,89 @@ class OpsActions extends Actions { - public static function executeClearCache(): array - { - if (!ini_get('apc.enabled') || !function_exists('apc_clear_cache')) + public static function executeClearCache(): array { - return View::renderJson(['success' => false, 'error' => 'Cache not enabled']); + if (!ini_get('apc.enabled') || !function_exists('apc_clear_cache')) { + return View::renderJson(['success' => false, 'error' => 'Cache not enabled']); + } + + apc_clear_cache(); + apc_clear_cache('user'); + apc_clear_cache('opcode'); + + return View::renderJson(['success' => true]); } - apc_clear_cache(); - apc_clear_cache('user'); - apc_clear_cache('opcode'); - - return View::renderJson(['success' => true]); - } - - public static function executePostCommit(): array - { - $payload = Request::getParam('payload'); - if (!$payload) + public static function executePostCommit(): array { - return NavActions::execute400(['error' => 'No payload']); + $payload = Request::getParam('payload'); + if (!$payload) { + return NavActions::execute400(['error' => 'No payload']); + } + + $payload = json_decode($payload, true); + if ($payload['ref'] === 'refs/heads/master') { + $sig = Request::getHttpHeader('X-Hub-Signature'); + if (!$sig) { + return NavActions::execute400(['error' => "HTTP header 'X-Hub-Signature' is missing."]); + } + + list($algo, $hash) = explode('=', Request::getHttpHeader('X-Hub-Signature'), 2) + ['', '']; + if (!in_array($algo, hash_algos(), true)) { + return NavActions::execute400(['error' => 'Invalid hash algorithm "' . htmlspecialchars($algo) . '"']); + } + + $rawPost = file_get_contents('php://input'); + $secret = Config::get(Config::GITHUB_KEY); + if ($hash !== hash_hmac($algo, $rawPost, $secret)) { + return NavActions::execute400(['error' => 'Hash does not match.']); + } + + file_put_contents(ROOT_DIR . '/data/writeable/NEEDS_UPDATE', ''); + } + + return [null, []]; } - $payload = json_decode($payload, true); - if ($payload['ref'] === 'refs/heads/master') + public static function executeLogUpload(): array { - $sig = Request::getHttpHeader('X-Hub-Signature'); - if (!$sig) - { - return NavActions::execute400(['error' => "HTTP header 'X-Hub-Signature' is missing."]); - } - - list($algo, $hash) = explode('=', Request::getHttpHeader('X-Hub-Signature'), 2) + ['', '']; - if (!in_array($algo, hash_algos(), true)) - { - return NavActions::execute400(['error' => 'Invalid hash algorithm "' . htmlspecialchars($algo) . '"']); - } - - $rawPost = file_get_contents('php://input'); - $secret = Config::get(Config::GITHUB_KEY); - if ($hash !== hash_hmac($algo, $rawPost, $secret)) - { - return NavActions::execute400(['error' => 'Hash does not match.']); - } - - file_put_contents(ROOT_DIR . '/data/writeable/NEEDS_UPDATE', ''); - } - - return [null, []]; - } - - public static function executeLogUpload(): array - { - $log = Request::getPostParam('log') ? urldecode(Request::getPostParam('log')) : null; - if (Request::getPostParam('name')) - { - $name = substr(trim(urldecode(Request::getPostParam('name'))), 0, 50); - } - elseif (Request::getPostParam('date')) - { - $name = substr(trim(urldecode(Request::getPostParam('date'))), 0, 20) . '_' . + $log = Request::getPostParam('log') ? urldecode(Request::getPostParam('log')) : null; + if (Request::getPostParam('name')) { + $name = substr(trim(urldecode(Request::getPostParam('name'))), 0, 50); + } elseif (Request::getPostParam('date')) { + $name = substr(trim(urldecode(Request::getPostParam('date'))), 0, 20) . '_' . substr(trim(urldecode(Request::getPostParam('hash'))), 0, 20) . '_' . substr(trim(urldecode(Request::getPostParam('sys'))), 0, 50) . '_' . substr(trim(urldecode(Request::getPostParam('type'))), 0, 20); + } else { + $name = null; + } + + $name = preg_replace('/[^A-Za-z0-9_-]+/', '', $name); + + if (!$log || !$name) { + return NavActions::execute400(['error' => "Required params: log, name"]); + } + + $awsKey = Config::get(Config::AWS_LOG_ACCESS_KEY); + $awsSecret = Config::get(Config::AWS_LOG_SECRET_KEY); + + if (!$log || !$name) { + throw new RuntimeException('Missing AWS credentials'); + } + + $tmpFile = tempnam(sys_get_temp_dir(), 'lbryinstalllog'); + file_put_contents($tmpFile, $log); + + if (filesize($tmpFile) > 1024 * 1024 * 2) { + return NavActions::execute400(['error' => 'Log file is too large']); + } + + S3::$useExceptions = true; + S3::setAuth($awsKey, $awsSecret); + S3::putObject(S3::inputFile($tmpFile, false), 'lbry-install-logs', $name); + unlink($tmpFile); + + return [null, []]; } - else - { - $name = null; - } - - $name = preg_replace('/[^A-Za-z0-9_-]+/', '', $name); - - if (!$log || !$name) - { - return NavActions::execute400(['error' => "Required params: log, name"]); - } - - $awsKey = Config::get(Config::AWS_LOG_ACCESS_KEY); - $awsSecret = Config::get(Config::AWS_LOG_SECRET_KEY); - - if (!$log || !$name) - { - throw new RuntimeException('Missing AWS credentials'); - } - - $tmpFile = tempnam(sys_get_temp_dir(), 'lbryinstalllog'); - file_put_contents($tmpFile, $log); - - if (filesize($tmpFile) > 1024 * 1024 * 2) - { - return NavActions::execute400(['error' => 'Log file is too large']); - } - - S3::$useExceptions = true; - S3::setAuth($awsKey, $awsSecret); - S3::putObject(S3::inputFile($tmpFile, false), 'lbry-install-logs', $name); - unlink($tmpFile); - - return [null, []]; - } } diff --git a/controller/action/ReportActions.class.php b/controller/action/ReportActions.class.php index e2d466a6..ddc12f9e 100644 --- a/controller/action/ReportActions.class.php +++ b/controller/action/ReportActions.class.php @@ -2,41 +2,35 @@ class ReportActions extends Actions { - public static function executeDmca() - { - Response::setHeader(Response::HEADER_CROSS_ORIGIN, "*"); - if (!Request::isPost()) + public static function executeDmca() { - return ['report/dmca']; + Response::setHeader(Response::HEADER_CROSS_ORIGIN, "*"); + if (!Request::isPost()) { + return ['report/dmca']; + } + + $values = []; + $errors = []; + + foreach (['name', 'email', 'rightsholder', 'identifier'] as $field) { + $value = Request::getPostParam($field); + + if (!$value) { + $errors[$field] = __('form_error.required'); + } elseif ($field == 'email' && !filter_var($value, FILTER_VALIDATE_EMAIL)) { + $errors[$field] = __('form_error.invalid'); + } + + $values[$field] = $value; + } + + if (!$errors) { + $values['report_id'] = Encoding::base58Encode(random_bytes(6)); + Mailgun::sendDmcaReport($values); + Session::setFlash('success', '

Report Submitted

We will respond shortly.

This ID for this report is ' . $values['report_id'] . '. Please reference this ID when contacting us regarding this report.

'); + return Controller::redirect(Request::getRelativeUri(), 303); + } + + return ['report/dmca', ['errors' => $errors, 'values' => $values]]; } - - $values = []; - $errors = []; - - foreach (['name', 'email', 'rightsholder', 'identifier'] as $field) - { - $value = Request::getPostParam($field); - - if (!$value) - { - $errors[$field] = __('form_error.required'); - } - elseif($field == 'email' && !filter_var($value, FILTER_VALIDATE_EMAIL)) - { - $errors[$field] = __('form_error.invalid'); - } - - $values[$field] = $value; - } - - if (!$errors) - { - $values['report_id'] = Encoding::base58Encode(random_bytes(6)); - Mailgun::sendDmcaReport($values); - Session::setFlash('success', '

Report Submitted

We will respond shortly.

This ID for this report is ' . $values['report_id'] . '. Please reference this ID when contacting us regarding this report.

'); - return Controller::redirect(Request::getRelativeUri(), 303); - } - - return ['report/dmca', ['errors' => $errors, 'values' => $values]]; - } -} \ No newline at end of file +} diff --git a/controller/action/i18nActions.class.php b/controller/action/i18nActions.class.php index e8de8f8b..0e9ae2ce 100644 --- a/controller/action/i18nActions.class.php +++ b/controller/action/i18nActions.class.php @@ -2,28 +2,24 @@ class i18nActions extends Actions { - public static function setCulture() - { - $culture = Request::getPostParam('culture'); - - // Validate - if ($culture && !in_array($culture, i18n::getAllCultures())) + public static function setCulture() { - $culture = null; - } + $culture = Request::getPostParam('culture'); - if ($culture) - { - Session::set(Session::KEY_USER_CULTURE, $culture); - } - else - { - Session::unsetKey(Session::KEY_USER_CULTURE); - } + // Validate + if ($culture && !in_array($culture, i18n::getAllCultures())) { + $culture = null; + } - //if session changes update domain - //english language = www + if ($culture) { + Session::set(Session::KEY_USER_CULTURE, $culture); + } else { + Session::unsetKey(Session::KEY_USER_CULTURE); + } - return Controller::redirect(Request::getReferrer()); - } + //if session changes update domain + //english language = www + + return Controller::redirect(Request::getReferrer()); + } } diff --git a/lib/cache/Apc.class.php b/lib/cache/Apc.class.php index af3fdc31..3a51a4e7 100644 --- a/lib/cache/Apc.class.php +++ b/lib/cache/Apc.class.php @@ -8,8 +8,8 @@ */ class Apc { - public static function isEnabled() - { - return extension_loaded('apc') && ini_get('apc.enabled'); - } -} \ No newline at end of file + public static function isEnabled() + { + return extension_loaded('apc') && ini_get('apc.enabled'); + } +} diff --git a/lib/exception/StopException.class.php b/lib/exception/StopException.class.php index ad3dc43d..d780b143 100644 --- a/lib/exception/StopException.class.php +++ b/lib/exception/StopException.class.php @@ -2,5 +2,4 @@ class StopException extends Exception { - } diff --git a/lib/i18n.class.php b/lib/i18n.class.php index 482d0d67..6314a6cc 100644 --- a/lib/i18n.class.php +++ b/lib/i18n.class.php @@ -5,134 +5,119 @@ */ function __($msg, $args = []) { - return strtr(i18n::translate($msg), $args); + return strtr(i18n::translate($msg), $args); } class i18n { - protected static - $language = null, - $translations = [], - $country = null, - $cultures = ['pt_PT', 'en_US']; + protected static $language = null; + protected static $translations = []; + protected static $country = null; + protected static $cultures = ['pt_PT', 'en_US']; - public static function register() /*needed to trigger class include, presumably setup would happen here*/ - { - $culture = static::deduceCulture(); - - list($language, $country) = explode('_', $culture); - static::$language = $language; - static::$country = $country; - - setlocale(LC_MONETARY, $culture . '.UTF-8'); - } - - public static function getLanguage() - { - return static::$language; - } - - public static function getCountry() - { - return static::$country; - } - - public static function getAllCultures() - { - return static::$cultures; - } - - public static function formatCurrency($amount, $currency = 'USD') - { - return '' . money_format('%.2n', $amount) . ''; - } - - public static function formatCredits($amount) - { - return '' . (is_numeric($amount) ? number_format($amount, 1) : $amount) . ' LBC'; - } - - public static function translate($token, $language = null) - { - $language = $language === null ? static::$language : $language; - if (!isset(static::$translations[$language])) + public static function register() /*needed to trigger class include, presumably setup would happen here*/ { - $path = ROOT_DIR . '/data/i18n/' . $language . '.yaml'; + $culture = static::deduceCulture(); - static::$translations[$language] = file_exists($path) ? Spyc::YAMLLoadString(file_get_contents($path)) : []; - } - $scope = static::$translations[$language]; - foreach (explode('.', $token) as $level) - { - if (isset($scope[$level])) - { - $scope = $scope[$level]; - } - else - { - $scope = []; - } - } - if (!$scope && $language != 'en') - { - return static::translate($token, 'en'); - } - return $scope ?: $token; - } + list($language, $country) = explode('_', $culture); + static::$language = $language; + static::$country = $country; - protected static function deduceCulture() - { - $candidates = []; - - //url trumps everything - $urlTokens = Request::getHost() ? explode('.', Request::getHost()) : []; - $code = $urlTokens ? reset($urlTokens) : null; - if ($code !== 'www') - { - $candidates[] = $code; + setlocale(LC_MONETARY, $culture . '.UTF-8'); } - //then session - $candidates[] = Session::get(Session::KEY_USER_CULTURE); - - // then headers - // http://www.thefutureoftheweb.com/blog/use-accept-language-header - if (Request::getHttpHeader('Accept-Language')) + public static function getLanguage() { - // break up string into pieces (languages and q factors) - preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', Request::getHttpHeader('Accept-Language'), $languages); + return static::$language; + } - if (isset($languages[1]) && count($languages[1])) - { - // create a list like "en" => 0.8 - $langs = array_combine($languages[1], $languages[4]); + public static function getCountry() + { + return static::$country; + } - // set default to 1 for any without q factor - foreach ($langs as $lang => $val) - { - if ($val === '') - { - $langs[$lang] = 1; - } + public static function getAllCultures() + { + return static::$cultures; + } + + public static function formatCurrency($amount, $currency = 'USD') + { + return '' . money_format('%.2n', $amount) . ''; + } + + public static function formatCredits($amount) + { + return '' . (is_numeric($amount) ? number_format($amount, 1) : $amount) . ' LBC'; + } + + public static function translate($token, $language = null) + { + $language = $language === null ? static::$language : $language; + if (!isset(static::$translations[$language])) { + $path = ROOT_DIR . '/data/i18n/' . $language . '.yaml'; + + static::$translations[$language] = file_exists($path) ? Spyc::YAMLLoadString(file_get_contents($path)) : []; + } + $scope = static::$translations[$language]; + foreach (explode('.', $token) as $level) { + if (isset($scope[$level])) { + $scope = $scope[$level]; + } else { + $scope = []; + } + } + if (!$scope && $language != 'en') { + return static::translate($token, 'en'); + } + return $scope ?: $token; + } + + protected static function deduceCulture() + { + $candidates = []; + + //url trumps everything + $urlTokens = Request::getHost() ? explode('.', Request::getHost()) : []; + $code = $urlTokens ? reset($urlTokens) : null; + if ($code !== 'www') { + $candidates[] = $code; } - arsort($langs, SORT_NUMERIC); + //then session + $candidates[] = Session::get(Session::KEY_USER_CULTURE); - $candidates = array_merge($candidates, array_keys($langs)); - } - } + // then headers + // http://www.thefutureoftheweb.com/blog/use-accept-language-header + if (Request::getHttpHeader('Accept-Language')) { + // break up string into pieces (languages and q factors) + preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', Request::getHttpHeader('Accept-Language'), $languages); - foreach ($candidates as $candidate) - { - foreach (static::getAllCultures() as $culture) - { - if ($candidate === $culture || substr($culture, 0, 2) === $candidate) - { - return $culture; + if (isset($languages[1]) && count($languages[1])) { + // create a list like "en" => 0.8 + $langs = array_combine($languages[1], $languages[4]); + + // set default to 1 for any without q factor + foreach ($langs as $lang => $val) { + if ($val === '') { + $langs[$lang] = 1; + } + } + + arsort($langs, SORT_NUMERIC); + + $candidates = array_merge($candidates, array_keys($langs)); + } } - } - } - return 'en_US'; - } + foreach ($candidates as $candidate) { + foreach (static::getAllCultures() as $culture) { + if ($candidate === $culture || substr($culture, 0, 2) === $candidate) { + return $culture; + } + } + } + + return 'en_US'; + } } diff --git a/lib/routing/BadRouteException.class.php b/lib/routing/BadRouteException.class.php index 33d07f1e..1b5d7240 100644 --- a/lib/routing/BadRouteException.class.php +++ b/lib/routing/BadRouteException.class.php @@ -2,4 +2,6 @@ namespace Routing; -class RouterBadRouteException extends \LogicException {} +class RouterBadRouteException extends \LogicException +{ +} diff --git a/lib/routing/Dispatcher.class.php b/lib/routing/Dispatcher.class.php index 44574109..e19fb4bf 100644 --- a/lib/routing/Dispatcher.class.php +++ b/lib/routing/Dispatcher.class.php @@ -4,227 +4,205 @@ namespace Routing; class Dispatcher { + private $staticRouteMap; + private $variableRouteData; + private $filters; + private $handlerResolver; + public $matchedRoute; - 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) + /** + * Create a new route dispatcher. + * + * @param RouteDataInterface $data + * @param HandlerResolverInterface $resolver + */ + public function __construct(RouteData $data, HandlerResolverInterface $resolver = null) { - $this->handlerResolver = new HandlerResolver(); - } - else - { - $this->handlerResolver = $resolver; - } - } + $this->staticRouteMap = $data->getStaticRoutes(); - /** - * 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, '/')); + $this->variableRouteData = $data->getVariableRoutes(); - list($beforeFilter, $afterFilter) = $this->parseFilters($filters); + $this->filters = $data->getFilters(); - 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(array_keys($routes), 'Method not allowed'); - } - - /** - * 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]); + if ($resolver === null) { + $this->handlerResolver = new HandlerResolver(); + } else { + $this->handlerResolver = $resolver; } - else - { - $routes[$httpMethod][2][$varName] = $matches[$i + 1]; - } - } - - return $routes[$httpMethod]; } - throw new HttpRouteNotFoundException('Route ' . $uri . ' does not exist'); - } + /** + * 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(array_keys($routes), 'Method not allowed'); + } + + /** + * 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'); + } } diff --git a/lib/routing/HandlerResolver.class.php b/lib/routing/HandlerResolver.class.php index 9918d2f7..7421d098 100644 --- a/lib/routing/HandlerResolver.class.php +++ b/lib/routing/HandlerResolver.class.php @@ -4,13 +4,12 @@ namespace Routing; class HandlerResolver implements HandlerResolverInterface { - public function resolve($handler) - { - if (is_array($handler) && is_string($handler[0])) + public function resolve($handler) { - $handler[0] = new $handler[0]; - } + if (is_array($handler) && is_string($handler[0])) { + $handler[0] = new $handler[0]; + } - return $handler; - } -} \ No newline at end of file + return $handler; + } +} diff --git a/lib/routing/HandlerResolverInterface.class.php b/lib/routing/HandlerResolverInterface.class.php index 059c8d0f..7bc8d15d 100644 --- a/lib/routing/HandlerResolverInterface.class.php +++ b/lib/routing/HandlerResolverInterface.class.php @@ -4,5 +4,5 @@ namespace Routing; interface HandlerResolverInterface { - public function resolve($handler); + public function resolve($handler); } diff --git a/lib/routing/HttpException.class.php b/lib/routing/HttpException.class.php index e3e13232..c09f88f8 100644 --- a/lib/routing/HttpException.class.php +++ b/lib/routing/HttpException.class.php @@ -2,4 +2,6 @@ namespace Routing; -class HttpException extends \Exception {} +class HttpException extends \Exception +{ +} diff --git a/lib/routing/HttpMethodNotAllowedException.class.php b/lib/routing/HttpMethodNotAllowedException.class.php index cc393a3c..dbc8fedb 100644 --- a/lib/routing/HttpMethodNotAllowedException.class.php +++ b/lib/routing/HttpMethodNotAllowedException.class.php @@ -4,16 +4,16 @@ namespace Routing; class HttpMethodNotAllowedException extends HttpException { - protected $allowedMethods; + protected $allowedMethods; - public function __construct(array $allowedMethods, $message = "", $code = 0, Exception $previous = null) - { - $this->allowedMethods = $allowedMethods; - parent::__construct($message, $code, $previous); - } + public function __construct(array $allowedMethods, $message = "", $code = 0, Exception $previous = null) + { + $this->allowedMethods = $allowedMethods; + parent::__construct($message, $code, $previous); + } - public function getAllowedMethods() - { - return $this->allowedMethods; - } + public function getAllowedMethods() + { + return $this->allowedMethods; + } } diff --git a/lib/routing/HttpRouteNotFoundException.class.php b/lib/routing/HttpRouteNotFoundException.class.php index b7dc994a..4b461750 100644 --- a/lib/routing/HttpRouteNotFoundException.class.php +++ b/lib/routing/HttpRouteNotFoundException.class.php @@ -2,5 +2,6 @@ namespace Routing; -class HttpRouteNotFoundException extends HttpException {} - +class HttpRouteNotFoundException extends HttpException +{ +} diff --git a/lib/routing/Route.class.php b/lib/routing/Route.class.php index de39eb55..ef8b9a6b 100644 --- a/lib/routing/Route.class.php +++ b/lib/routing/Route.class.php @@ -4,23 +4,22 @@ namespace Routing; class Route { - /** - * Constants for before and after filters - */ - const BEFORE = 'before'; - const AFTER = 'after'; - const PREFIX = 'prefix'; + /** + * 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'; + /** + * 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'; } - diff --git a/lib/routing/RouteCollector.class.php b/lib/routing/RouteCollector.class.php index aead6524..ce60f0d7 100644 --- a/lib/routing/RouteCollector.class.php +++ b/lib/routing/RouteCollector.class.php @@ -4,266 +4,247 @@ namespace Routing; class RouteCollector { - const DEFAULT_CONTROLLER_ROUTE = 'index'; + const DEFAULT_CONTROLLER_ROUTE = 'index'; - const APPROX_CHUNK_SIZE = 10; + 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 RouteParser + */ + private $routeParser; + /** + * @var array + */ + private $filters = []; + /** + * @var array + */ + private $staticRoutes = []; + /** + * @var array + */ + private $regexToRoutesMap = []; + /** + * @var array + */ + private $reverse = []; - /** - * @var array - */ - private $globalFilters = []; + /** + * @var array + */ + private $globalFilters = []; - /** - * @var string - */ - private $globalRoutePrefix = ''; + /** + * @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) + public function __construct(RouteParser $routeParser = null) { - if (!$part['variable']) - { - $url[] = $part['value']; - } - elseif (isset($replacements[$variable])) - { - if ($part['optional']) - { - $url[] = '/'; + $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']}'"); + } } - $url[] = $replacements[$variable++]; - } - elseif (!$part['optional']) - { - throw new BadRouteException("Expecting route variable '{$part['name']}'"); - } + return implode('', $url); } - return implode('', $url); - } - - public function addRoute(string $httpMethod, $route, $handler, array $filters = []): RouteCollector - { - - if (is_array($route)) + public function addRoute(string $httpMethod, $route, $handler, array $filters = []): RouteCollector { - list($route, $name) = $route; - } + if (is_array($route)) { + list($route, $name) = $route; + } - $route = $this->addPrefix($this->trim($route)); + $route = $this->addPrefix($this->trim($route)); - list($routeData, $reverseData) = $this->routeParser->parse($route); + list($routeData, $reverseData) = $this->routeParser->parse($route); - if (isset($name)) - { - $this->reverse[$name] = $reverseData; - } + if (isset($name)) { + $this->reverse[$name] = $reverseData; + } - $filters = array_merge_recursive($this->globalFilters, $filters); + $filters = array_merge_recursive($this->globalFilters, $filters); - isset($routeData[1]) ? + 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'"); + return $this; } - foreach ($this->regexToRoutesMap as $regex => $routes) + private function addStaticRoute(string $httpMethod, $routeData, $handler, $filters) { - 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'"); - } + $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, []]; } - $this->staticRoutes[$routeStr][$httpMethod] = [$handler, $filters, []]; - } - - private function addVariableRoute(string $httpMethod, $routeData, $handler, $filters) - { - list($regex, $variables) = $routeData; - - if (isset($this->regexToRoutesMap[$regex][$httpMethod])) + private function addVariableRoute(string $httpMethod, $routeData, $handler, $filters) { - throw new BadRouteException("Cannot register two routes matching '$regex' for method '$httpMethod'"); + 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]; } - $this->regexToRoutesMap[$regex][$httpMethod] = [$handler, $filters, $variables]; - } + public function group(array $filters, \Closure $callback) + { + $oldGlobalFilters = $this->globalFilters; - public function group(array $filters, \Closure $callback) - { - $oldGlobalFilters = $this->globalFilters; + $oldGlobalPrefix = $this->globalRoutePrefix; - $oldGlobalPrefix = $this->globalRoutePrefix; - - $this->globalFilters = + $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; + $newPrefix = isset($filters[Route::PREFIX]) ? $this->trim($filters[Route::PREFIX]) : null; - $this->globalRoutePrefix = $this->addPrefix($newPrefix); + $this->globalRoutePrefix = $this->addPrefix($newPrefix); - $callback($this); + $callback($this); - $this->globalFilters = $oldGlobalFilters; + $this->globalFilters = $oldGlobalFilters; - $this->globalRoutePrefix = $oldGlobalPrefix; - } + $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) + private function addPrefix(string $route) { - foreach ($validMethods as $valid) - { - if (stripos($method->name, $valid) === 0) - { - $methodName = $this->camelCaseToDashed(substr($method->name, strlen($valid))); + return $this->trim($this->trim($this->globalRoutePrefix) . '/' . $route); + } - $params = $this->buildControllerParameters($method); + public function filter(string $name, $handler) + { + $this->filters[$name] = $handler; + } - 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); + public function get($route, $handler, array $filters = []) + { + return $this->addRoute(Route::GET, $route, $handler, $filters); + } - break; + 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; } - return $this; - } - - private function buildControllerParameters(ReflectionMethod $method): string - { - $params = ''; - - foreach ($method->getParameters() as $param) + private function buildControllerParameters(ReflectionMethod $method): string { - $params .= "/{" . $param->getName() . "}" . ($param->isOptional() ? '?' : ''); + $params = ''; + + foreach ($method->getParameters() as $param) { + $params .= "/{" . $param->getName() . "}" . ($param->isOptional() ? '?' : ''); + } + + return $params; } - return $params; - } + private function camelCaseToDashed(string $string): string + { + return strtolower(preg_replace('/([A-Z])/', '-$1', lcfirst($string))); + } - private function camelCaseToDashed(string $string): string - { - return strtolower(preg_replace('/([A-Z])/', '-$1', lcfirst($string))); - } - - public function getValidMethods(): array - { - return [ + public function getValidMethods(): array + { + return [ Route::ANY, Route::GET, Route::POST, @@ -273,53 +254,51 @@ class RouteCollector 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]; - } + 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]; + } } diff --git a/lib/routing/RouteData.class.php b/lib/routing/RouteData.class.php index 1a53709f..3f022ac6 100644 --- a/lib/routing/RouteData.class.php +++ b/lib/routing/RouteData.class.php @@ -4,56 +4,56 @@ namespace Routing; class RouteData { - /** - * @var array - */ - private $variableRoutes; + /** + * @var array + */ + private $variableRoutes; - /** - * @var array - */ - private $staticRoutes; + /** + * @var array + */ + private $staticRoutes; - /** - * @var array - */ - private $filters; + /** + * @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; + /** + * @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->variableRoutes = $variableRoutes; - $this->filters = $filters; - } + $this->filters = $filters; + } - /** - * @return array - */ - public function getStaticRoutes() - { - return $this->staticRoutes; - } + /** + * @return array + */ + public function getStaticRoutes() + { + return $this->staticRoutes; + } - /** - * @return array - */ - public function getVariableRoutes() - { - return $this->variableRoutes; - } + /** + * @return array + */ + public function getVariableRoutes() + { + return $this->variableRoutes; + } - /** - * @return mixed - */ - public function getFilters() - { - return $this->filters; - } + /** + * @return mixed + */ + public function getFilters() + { + return $this->filters; + } } diff --git a/lib/routing/RouteParser.class.php b/lib/routing/RouteParser.class.php index c6d9ad17..79a34323 100644 --- a/lib/routing/RouteParser.class.php +++ b/lib/routing/RouteParser.class.php @@ -22,7 +22,7 @@ class RouteParser * * Finally we look for an optional '?' which is used to signify an optional route. */ - const VARIABLE_REGEX = + const VARIABLE_REGEX = "~\{ \s* ([a-zA-Z0-9_]*) \s* (?: @@ -30,185 +30,176 @@ class RouteParser )? \}\??~x"; - /** - * The default parameter character restriction (One or more characters that is not a '/'). - */ - const DEFAULT_DISPATCH_REGEX = '[^/]+'; + /** + * The default parameter character restriction (One or more characters that is not a '/'). + */ + const DEFAULT_DISPATCH_REGEX = '[^/]+'; - private $parts; + private $parts; - private $reverseParts; + private $reverseParts; - private $partsCounter; + private $partsCounter; - private $variables; + private $variables; - private $regexOffset; + private $regexOffset; - /** - * Handy parameter type restrictions. - * - * @var array - */ - private $regexShortcuts = [ + /** + * 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)) + /** + * Parse a route returning the correct data format to pass to the dispatch engine. + * + * @param $route + * + * @return array + */ + public function parse($route) { - $reverse = [ + $this->reset(); + + $route = strtr($route, $this->regexShortcuts); + + if (!$matches = $this->extractVariableRouteParts($route)) { + $reverse = [ 'variable' => false, 'value' => $route ]; - return [[$route], [$reverse]]; - } + return [[$route], [$reverse]]; + } - foreach ($matches as $set) - { + foreach ($matches as $set) { + $this->staticParts($route, $set[0][1]); - $this->staticParts($route, $set[0][1]); + $this->validateVariable($set[1][0]); - $this->validateVariable($set[1][0]); + $regexPart = (isset($set[2]) ? trim($set[2][0]) : self::DEFAULT_DISPATCH_REGEX); - $regexPart = (isset($set[2]) ? trim($set[2][0]) : self::DEFAULT_DISPATCH_REGEX); + $this->regexOffset = $set[0][1] + strlen($set[0][0]); - $this->regexOffset = $set[0][1] + strlen($set[0][0]); + $match = '(' . $regexPart . ')'; - $match = '(' . $regexPart . ')'; + $isOptional = substr($set[0][0], -1) === '?'; - $isOptional = substr($set[0][0], -1) === '?'; + if ($isOptional) { + $match = $this->makeOptional($match); + } - if ($isOptional) - { - $match = $this->makeOptional($match); - } - - $this->reverseParts[$this->partsCounter] = [ + $this->reverseParts[$this->partsCounter] = [ 'variable' => true, 'optional' => $isOptional, 'name' => $set[1][0] ]; - $this->parts[$this->partsCounter++] = $match; + $this->parts[$this->partsCounter++] = $match; + } + + $this->staticParts($route, strlen($route)); + + return [[implode('', $this->parts), $this->variables], array_values($this->reverseParts)]; } - $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)) + /** + * Reset the parser ready for the next route. + */ + private function reset() { - return $matches; + $this->parts = []; + + $this->reverseParts = []; + + $this->partsCounter = 0; + + $this->variables = []; + + $this->regexOffset = 0; } - } - /** - * @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) + /** + * Return any variable route portions from the given route. + * + * @param $route + * + * @return mixed + */ + private function extractVariableRouteParts($route) { - if ($staticPart) - { - $quotedPart = $this->quote($staticPart); + if (preg_match_all(self::VARIABLE_REGEX, $route, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) { + return $matches; + } + } - $this->parts[$this->partsCounter] = $quotedPart; + /** + * @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); - $this->reverseParts[$this->partsCounter] = [ + 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++; - } + $this->partsCounter++; + } + } } - } - /** - * @param $varName - */ - private function validateVariable($varName) - { - if (isset($this->variables[$varName])) + /** + * @param $varName + */ + private function validateVariable($varName) { - throw new BadRouteException("Cannot use the same placeholder '$varName' twice"); + if (isset($this->variables[$varName])) { + throw new BadRouteException("Cannot use the same placeholder '$varName' twice"); + } + + $this->variables[$varName] = $varName; } - $this->variables[$varName] = $varName; - } - - /** - * @param $match - * - * @return string - */ - private function makeOptional($match) - { - $previous = $this->partsCounter - 1; - - if (isset($this->parts[$previous]) && $this->parts[$previous] === '/') + /** + * @param $match + * + * @return string + */ + private function makeOptional($match) { - $this->partsCounter--; - $match = '(?:/' . $match . ')'; + $previous = $this->partsCounter - 1; + + if (isset($this->parts[$previous]) && $this->parts[$previous] === '/') { + $this->partsCounter--; + $match = '(?:/' . $match . ')'; + } + + return $match . '?'; } - return $match . '?'; - } - - /** - * @param $part - * - * @return string - */ - private function quote($part) - { - return preg_quote($part, '~'); - } + /** + * @param $part + * + * @return string + */ + private function quote($part) + { + return preg_quote($part, '~'); + } } diff --git a/lib/thirdparty/Asana.class.php b/lib/thirdparty/Asana.class.php index a250c2b9..6f969332 100644 --- a/lib/thirdparty/Asana.class.php +++ b/lib/thirdparty/Asana.class.php @@ -2,82 +2,77 @@ class Asana { - protected static $curlOptions = ['json_response' => true, 'cache' => true, 'timeout' => 10]; + protected static $curlOptions = ['json_response' => true, 'cache' => true, 'timeout' => 10]; - public static function listRoadmapTasks($cache = true) - { - // Use print_r(static::get('/projects')) to get project IDs + public static function listRoadmapTasks($cache = true) + { + // Use print_r(static::get('/projects')) to get project IDs - $roadmapProjectId = 502841492992874; - $tasks = []; + $roadmapProjectId = 502841492992874; + $tasks = []; - $allTasks = array_reduce( + $allTasks = array_reduce( static::get('/projects/' . $roadmapProjectId . '/tasks', [], $cache), - function($carry, $task) use($cache) { - $fullTask = static::get('/tasks/' . $task['id'], [], $cache); - if ($fullTask['name']) { - $carry[] = $fullTask; - } - return $carry; + function ($carry, $task) use ($cache) { + $fullTask = static::get('/tasks/' . $task['id'], [], $cache); + if ($fullTask['name']) { + $carry[] = $fullTask; + } + return $carry; }, [] ); - foreach ($allTasks as $task) - { - $badge = "Planned"; - if ($task['completed']) - { - $badge = "Complete"; - } - else if (in_array("In Progress", array_map(function($tag) { return $tag['name']; }, $task['tags'] ?? []))) - { - $badge = "In Progress"; - } - $taskDueTime = strtotime($task['due_on']); - $year = date('Y', $taskDueTime); - $tasks[' ' . $year . ' '][] = array_intersect_key($task, ['name' => null]) + [ + foreach ($allTasks as $task) { + $badge = "Planned"; + if ($task['completed']) { + $badge = "Complete"; + } elseif (in_array("In Progress", array_map(function ($tag) { + return $tag['name']; + }, $task['tags'] ?? []))) { + $badge = "In Progress"; + } + $taskDueTime = strtotime($task['due_on']); + $year = date('Y', $taskDueTime); + $tasks[' ' . $year . ' '][] = array_intersect_key($task, ['name' => null]) + [ 'badge' => $badge, 'date' => $task['due_on'] ?? null, 'body' => nl2br($task['notes']), // 'assignee' => $fullTask['assignee'] ? ucwords($fullTask['assignee']['name']) : '', 'quarter_date' => 'Q' . static::dateToQuarter($task['due_on']) . ' ' . $year ]; - } - - foreach ($tasks as &$groupTasks) - { - usort($groupTasks, function ($tA, $tB) - { - if ($tA['date'] xor $tB['date']) - { - return $tA['date'] ? -1 : 1; } - return $tA['date'] < $tB['date'] ? -1 : 1; - }); + + foreach ($tasks as &$groupTasks) { + usort($groupTasks, function ($tA, $tB) { + if ($tA['date'] xor $tB['date']) { + return $tA['date'] ? -1 : 1; + } + return $tA['date'] < $tB['date'] ? -1 : 1; + }); + } + + return $tasks; } - return $tasks; - } + protected static function get($endpoint, array $data = [], $cache = true) + { + $apiKey = Config::get(Config::ASANA_KEY); - protected static function get($endpoint, array $data = [], $cache = true) - { - $apiKey = Config::get(Config::ASANA_KEY); - - $options = [ + $options = [ 'headers' => ['Authorization: Bearer ' . $apiKey], 'cache' => $cache ] + static::$curlOptions; - $responseData = CurlWithCache::get('https://app.asana.com/api/1.0' . $endpoint, $data, $options); - return $responseData['data'] ?? []; - } + $responseData = CurlWithCache::get('https://app.asana.com/api/1.0' . $endpoint, $data, $options); + return $responseData['data'] ?? []; + } - // Converts date to quarter - protected static function dateToQuarter($date) - { - return (string)ceil(date('m', strtotime($date))/3); - } + // Converts date to quarter + protected static function dateToQuarter($date) + { + return (string)ceil(date('m', strtotime($date))/3); + } } class AsanaException extends Exception diff --git a/lib/thirdparty/Github.class.php b/lib/thirdparty/Github.class.php index 61147e82..5d2fd6f2 100644 --- a/lib/thirdparty/Github.class.php +++ b/lib/thirdparty/Github.class.php @@ -2,155 +2,135 @@ class Github { - protected static function findReleaseAssetForOs(array $release, string $os) - { - if (!in_array($os, array_keys(OS::getAll()))) + protected static function findReleaseAssetForOs(array $release, string $os) { - throw new DomainException('Unknown OS'); - } + if (!in_array($os, array_keys(OS::getAll()))) { + throw new DomainException('Unknown OS'); + } - foreach ($release['assets'] as $asset) - { - $ext = substr($asset['name'], -4); - if ( + foreach ($release['assets'] as $asset) { + $ext = substr($asset['name'], -4); + if ( ($os == OS::OS_LINUX && ($ext == '.deb' || in_array($asset['content_type'], ['application/x-debian-package', 'application/x-deb']))) || ($os == OS::OS_OSX && ($ext == '.dmg' || in_array($asset['content_type'], ['application/x-diskcopy', 'application/x-apple-diskimage']))) || ($os == OS::OS_WINDOWS && $ext == '.exe') - ) - { - return $asset; - } - } - } - - public static function getAppRelease($cache = true) - { - try - { - return static::get('/repos/lbryio/lbry-app/releases/latest', [], $cache); - } - catch (Exception $e) - { + ) { + return $asset; + } + } } - return null; - } - - public static function getAppAsset($os, $cache = true) - { - $release = static::getAppRelease($cache); - return $release ? static::findReleaseAssetForOs($release, $os) : null; - } - - - public static function getAppDownloadUrl($os, $cache = true) - { - $asset = static::getAppAsset($os, $cache); - return $asset ? $asset['browser_download_url'] : null; - } - - public static function getAppPrereleaseDownloadUrl($os, $cache = true) - { - try + public static function getAppRelease($cache = true) { - $releases = static::get('/repos/lbryio/lbry-app/releases', [], $cache); - if (count($releases)) - { - $asset = static::findReleaseAssetForOs($releases[0], $os); + try { + return static::get('/repos/lbryio/lbry-app/releases/latest', [], $cache); + } catch (Exception $e) { + } + + return null; + } + + public static function getAppAsset($os, $cache = true) + { + $release = static::getAppRelease($cache); + return $release ? static::findReleaseAssetForOs($release, $os) : null; + } + + + public static function getAppDownloadUrl($os, $cache = true) + { + $asset = static::getAppAsset($os, $cache); return $asset ? $asset['browser_download_url'] : null; - } - } - catch (Exception $e) - { } - return null; - } - - - public static function getDaemonReleaseProperty($os, $property, $isAssetProperty = false, $cache = true) - { - if (!in_array($os, array_keys(OS::getAll()))) + public static function getAppPrereleaseDownloadUrl($os, $cache = true) { - throw new DomainException('Unknown OS'); + try { + $releases = static::get('/repos/lbryio/lbry-app/releases', [], $cache); + if (count($releases)) { + $asset = static::findReleaseAssetForOs($releases[0], $os); + return $asset ? $asset['browser_download_url'] : null; + } + } catch (Exception $e) { + } + + return null; } - try + + public static function getDaemonReleaseProperty($os, $property, $isAssetProperty = false, $cache = true) { - $releaseData = static::get('/repos/lbryio/lbry/releases/latest', [], $cache); - foreach ($releaseData['assets'] as $asset) - { - if ( + if (!in_array($os, array_keys(OS::getAll()))) { + throw new DomainException('Unknown OS'); + } + + try { + $releaseData = static::get('/repos/lbryio/lbry/releases/latest', [], $cache); + foreach ($releaseData['assets'] as $asset) { + if ( ($os == OS::OS_LINUX && stripos($asset['browser_download_url'], 'linux') !== false) || ($os == OS::OS_OSX && stripos($asset['browser_download_url'], 'macos') !== false) || ($os == OS::OS_WINDOWS && strpos($asset['browser_download_url'], 'windows') !== false) - ) - { - return $isAssetProperty ? $asset[$property] : $releaseData[$property]; + ) { + return $isAssetProperty ? $asset[$property] : $releaseData[$property]; + } + } + } catch (Exception $e) { } - } - } - catch (Exception $e) - { + + return null; } - return null; - } - - public static function getDaemonDownloadUrl($os, $cache = true) - { - return static::getDaemonReleaseProperty($os, 'browser_download_url', true); - } - - public static function get($endpoint, array $params = [], $cache = true) - { - $twoHoursInSeconds = 7200; - return CurlWithCache::get('https://api.github.com' . $endpoint . '?' . http_build_query($params), [], - ['headers' => ['Accept: application/vnd.github.v3.html+json'],'user_agent' => 'LBRY', 'json_response' => true, 'cache' => $cache === true ? $twoHoursInSeconds : $cache]); - } - - public static function listRoadmapChangesets($cache = true) - { - $sets = []; - $allReleases = []; - - $projects = ['lbry' => 'LBRY Protocol', 'lbry-app' => 'LBRY App']; - - foreach($projects as $project => $projectLabel) + public static function getDaemonDownloadUrl($os, $cache = true) { - $page = 1; - do - { - $releases = static::get('/repos/lbryio/' . $project . '/releases', ['page' => $page], $cache); - $page++; - $allReleases = array_merge($allReleases, array_map(function ($release) use ($project, $projectLabel) - { - return $release + ['project' => $projectLabel]; - }, array_filter($releases, function ($release) - { - return isset($release['tag_name']) && isset($release['published_at']) && $release['published_at']; - }))); - } while (count($releases) >= 30); + return static::getDaemonReleaseProperty($os, 'browser_download_url', true); } - /** - * This logic is likely overly convoluted at this point. It used to group releases by project before going - * to strictly by time. - Jeremy - */ - - foreach ($allReleases as $release) + public static function get($endpoint, array $params = [], $cache = true) { - $group = null; - $matches = null; - if (preg_match('/^v(\d+)\.(\d+)\./', $release['tag_name'] ?? '', $matches)) - { - $group = $release['project'] . ' v' . $matches[1] . '.' . $matches[2]; - } - if ($group) - { - $sets[$group][] = array_intersect_key($release, [ + $twoHoursInSeconds = 7200; + return CurlWithCache::get( + 'https://api.github.com' . $endpoint . '?' . http_build_query($params), + [], + ['headers' => ['Accept: application/vnd.github.v3.html+json'],'user_agent' => 'LBRY', 'json_response' => true, 'cache' => $cache === true ? $twoHoursInSeconds : $cache] + ); + } + + public static function listRoadmapChangesets($cache = true) + { + $sets = []; + $allReleases = []; + + $projects = ['lbry' => 'LBRY Protocol', 'lbry-app' => 'LBRY App']; + + foreach ($projects as $project => $projectLabel) { + $page = 1; + do { + $releases = static::get('/repos/lbryio/' . $project . '/releases', ['page' => $page], $cache); + $page++; + $allReleases = array_merge($allReleases, array_map(function ($release) use ($project, $projectLabel) { + return $release + ['project' => $projectLabel]; + }, array_filter($releases, function ($release) { + return isset($release['tag_name']) && isset($release['published_at']) && $release['published_at']; + }))); + } while (count($releases) >= 30); + } + + /** + * This logic is likely overly convoluted at this point. It used to group releases by project before going + * to strictly by time. - Jeremy + */ + + foreach ($allReleases as $release) { + $group = null; + $matches = null; + if (preg_match('/^v(\d+)\.(\d+)\./', $release['tag_name'] ?? '', $matches)) { + $group = $release['project'] . ' v' . $matches[1] . '.' . $matches[2]; + } + if ($group) { + $sets[$group][] = array_intersect_key($release, [ 'prerelease' => null, 'tag_name' => null, 'published_at' => null, 'project' => null ]) + [ 'created_at' => strtotime($release['created_at']), @@ -165,23 +145,22 @@ class Github 'version' => $matches[1] . '.' . $matches[2] . '.' . (isset($matches[3]) ? $matches[3] : ''), 'body' => $release['body_html'] ]; - } + } + } + + uasort($sets, function ($sA, $sB) { + if ($sA[0]['project'] != $sB[0]['project']) { + return $sA[0]['project'] < $sB[0]['project'] ? -1 : 1; + } + return $sB[0]['sort_key'] <=> $sA[0]['sort_key']; + }); + + foreach ($sets as $group => &$groupSet) { + usort($groupSet, function ($rA, $rB) { + return $rB['created_at'] <=> $rA['created_at']; + }); + } + + return $sets; } - - uasort($sets, function ($sA, $sB) - { - if ($sA[0]['project'] != $sB[0]['project']) - { - return $sA[0]['project'] < $sB[0]['project'] ? -1 : 1; - } - return $sB[0]['sort_key'] <=> $sA[0]['sort_key']; - }); - - foreach ($sets as $group => &$groupSet) - { - usort($groupSet, function ($rA, $rB) { return $rB['created_at'] <=> $rA['created_at']; }); - } - - return $sets; - } -} \ No newline at end of file +} diff --git a/lib/thirdparty/LBRY.class.php b/lib/thirdparty/LBRY.class.php index 90ef6ae2..472cd571 100644 --- a/lib/thirdparty/LBRY.class.php +++ b/lib/thirdparty/LBRY.class.php @@ -3,58 +3,56 @@ class LBRY { - public static function getApiUrl($endpoint) - { - return Config::get(Config::LBRY_API_SERVER) . $endpoint; - } + public static function getApiUrl($endpoint) + { + return Config::get(Config::LBRY_API_SERVER) . $endpoint; + } - public static function getLBCtoUSDRate() - { - $response = CurlWithCache::get(static::getApiUrl('/lbc/exchange_rate'), [], [ + public static function getLBCtoUSDRate() + { + $response = CurlWithCache::get(static::getApiUrl('/lbc/exchange_rate'), [], [ 'cache' => 3600, //one hour 'json_response' => true ]); - return $response['data']['lbc_usd'] ?? 0; - } + return $response['data']['lbc_usd'] ?? 0; + } - public static function subscribe($email, $tag = null) - { - return Curl::post(static::getApiUrl('/list/subscribe'), array_filter([ + public static function subscribe($email, $tag = null) + { + return Curl::post(static::getApiUrl('/list/subscribe'), array_filter([ 'email' => $email, 'tag' => $tag, ]), ['json_response' => true]); - } - - public static function unsubscribe($email) - { - return Curl::post(static::getApiUrl('/list/unsubscribe'), ['email' => $email], ['json_response' => true]); - } - - public static function connectYoutube($channel_name) - { - $type = 'sync'; - return Curl::post(static::getApiUrl('/yt/new'), ['desired_lbry_channel_name' => $channel_name, 'type' => $type], ['json_response' => true]); - } - - // Check the sync status - public static function statusYoutube($status_token) - { - return Curl::get(static::getApiUrl('/yt/status'), ['status_token' => $status_token], ['json_response' => true]); - } - - public static function youtubeReward() - { - return CurlWithCache::post(static::getApiUrl('/yt/rewards'), [], ['cache' => 3600, 'json_response' => true]); - } - - public static function editYouTube($status_token, $channel_name, $email, $sync_consent) - { - if ($email == null){ - return Curl::post(static::getApiUrl("/yt/update"),['status_token' => $status_token, 'new_preferred_channel' => $channel_name, 'sync_consent' => $sync_consent],['json_response' => true]); } - else{ - return Curl::post(static::getApiUrl("/yt/update"), ['status_token' => $status_token, 'new_email' => $email, 'new_preferred_channel' => $channel_name, 'sync_consent' => $sync_consent], ['json_response' => true]); + public static function unsubscribe($email) + { + return Curl::post(static::getApiUrl('/list/unsubscribe'), ['email' => $email], ['json_response' => true]); + } + + public static function connectYoutube($channel_name) + { + $type = 'sync'; + return Curl::post(static::getApiUrl('/yt/new'), ['desired_lbry_channel_name' => $channel_name, 'type' => $type], ['json_response' => true]); + } + + // Check the sync status + public static function statusYoutube($status_token) + { + return Curl::get(static::getApiUrl('/yt/status'), ['status_token' => $status_token], ['json_response' => true]); + } + + public static function youtubeReward() + { + return CurlWithCache::post(static::getApiUrl('/yt/rewards'), [], ['cache' => 3600, 'json_response' => true]); + } + + public static function editYouTube($status_token, $channel_name, $email, $sync_consent) + { + if ($email == null) { + return Curl::post(static::getApiUrl("/yt/update"), ['status_token' => $status_token, 'new_preferred_channel' => $channel_name, 'sync_consent' => $sync_consent], ['json_response' => true]); + } else { + return Curl::post(static::getApiUrl("/yt/update"), ['status_token' => $status_token, 'new_email' => $email, 'new_preferred_channel' => $channel_name, 'sync_consent' => $sync_consent], ['json_response' => true]); + } } - } } diff --git a/lib/thirdparty/Mailgun.class.php b/lib/thirdparty/Mailgun.class.php index cf898cda..2e098c3b 100644 --- a/lib/thirdparty/Mailgun.class.php +++ b/lib/thirdparty/Mailgun.class.php @@ -2,16 +2,16 @@ class Mailgun { - const BASE_URL = 'https://api.mailgun.net/v3'; + const BASE_URL = 'https://api.mailgun.net/v3'; - const TOP_DOMAIN = 'lbry.io'; - const MAIL_DOMAIN = 'mail.lbry.io'; + const TOP_DOMAIN = 'lbry.io'; + const MAIL_DOMAIN = 'mail.lbry.io'; - const LIST_GENERAL = 'lbryians@lbry.io'; + const LIST_GENERAL = 'lbryians@lbry.io'; - public static function sendDmcaReport($data) - { - list($status, $headers, $body) = static::post('/' . static::MAIL_DOMAIN . '/messages', [ + public static function sendDmcaReport($data) + { + list($status, $headers, $body) = static::post('/' . static::MAIL_DOMAIN . '/messages', [ 'from' => 'LBRY ', 'to' => 'help@lbry.io', 'subject' => 'DMCA Report #' . $data['report_id'], @@ -20,12 +20,12 @@ class Mailgun 'o:tracking-opens' => 'no' ]); - return $status == 200; - } + return $status == 200; + } - public static function sendYouTubeWarmLead($data) - { - list($status, $headers, $body) = static::post('/' . static::MAIL_DOMAIN . '/messages', [ + public static function sendYouTubeWarmLead($data) + { + list($status, $headers, $body) = static::post('/' . static::MAIL_DOMAIN . '/messages', [ 'from' => 'LBRY ', 'to' => 'reilly@lbry.io', 'subject' => 'Interested YouTuber', @@ -34,32 +34,32 @@ class Mailgun 'o:tracking-opens' => 'no' ]); - return $status == 200; - } + return $status == 200; + } - protected static function post($endpoint, $data) - { - return static::request(Curl::POST, $endpoint, $data); - } + protected static function post($endpoint, $data) + { + return static::request(Curl::POST, $endpoint, $data); + } - protected static function put($endpoint, $data) - { - return static::request(Curl::PUT, $endpoint, $data); - } + protected static function put($endpoint, $data) + { + return static::request(Curl::PUT, $endpoint, $data); + } - protected static function request($method, $endpoint, $data) - { - return Curl::doCurl($method, self::BASE_URL . $endpoint, $data, [ + protected static function request($method, $endpoint, $data) + { + return Curl::doCurl($method, self::BASE_URL . $endpoint, $data, [ 'headers' => [ 'Authorization: Basic ' . base64_encode('api:' . Config::get(Config::MAILGUN_API_KEY)) ], 'retry' => 3, ]); - } + } - protected static function inlineCss($html, $css = '') - { - $e = new \Pelago\Emogrifier($html, $css); - return trim($e->emogrify()); - } + protected static function inlineCss($html, $css = '') + { + $e = new \Pelago\Emogrifier($html, $css); + return trim($e->emogrify()); + } } diff --git a/lib/thirdparty/Salesforce.class.php b/lib/thirdparty/Salesforce.class.php index fdb5f714..4601087b 100644 --- a/lib/thirdparty/Salesforce.class.php +++ b/lib/thirdparty/Salesforce.class.php @@ -2,21 +2,20 @@ class Salesforce { - const + const API_URL = 'https://api.salesforceiq.com/v2/', DEFAULT_LIST_ID = '58387a94e4b0a1fea2c76f4a'; - protected static - $curlOptions = [ + protected static $curlOptions = [ 'headers' => ['Accept: application/json', 'Content-type: application/json'], 'json_response' => true, 'json_data' => true, 'timeout' => 10 ]; - public static function createContact(string $email, string $initialListId = null, string $acquisitionChannel = null) - { - $contactData = [ + public static function createContact(string $email, string $initialListId = null, string $acquisitionChannel = null) + { + $contactData = [ 'properties' => [ 'email' => [[ 'value' => $email, @@ -25,54 +24,51 @@ class Salesforce ] ]; - $contactId = static::post('contacts?_upsert=email', $contactData)['id'] ?? null; + $contactId = static::post('contacts?_upsert=email', $contactData)['id'] ?? null; - if ($initialListId) - { - if (!$contactId) - { - throw new SalesforceException('Failed to generate or update contact'); - } + if ($initialListId) { + if (!$contactId) { + throw new SalesforceException('Failed to generate or update contact'); + } - static::post('lists/' . $initialListId . '/listitems?_upsert=contactIds', [ + static::post('lists/' . $initialListId . '/listitems?_upsert=contactIds', [ 'contactIds' => [$contactId], 'fieldValues' => (object)[ '2' => [['raw' => $acquisitionChannel]] ] ]); + } } - } - protected static function getApiUserPassword() - { - $userpw = Config::get(Config::SALESFORCE_KEY) . ':' . Config::get(Config::SALESFORCE_SECRET); - if ($userpw[0] === ':' || substr($userpw, -1) === ':') + protected static function getApiUserPassword() { - throw new SalesforceException('Salesforce key and/or secret not configured correctly'); + $userpw = Config::get(Config::SALESFORCE_KEY) . ':' . Config::get(Config::SALESFORCE_SECRET); + if ($userpw[0] === ':' || substr($userpw, -1) === ':') { + throw new SalesforceException('Salesforce key and/or secret not configured correctly'); + } + return $userpw; } - return $userpw; - } - public static function get($endpoint, array $data = []) - { - $options = [ + public static function get($endpoint, array $data = []) + { + $options = [ 'password' => static::getApiUserPassword(), ] + static::$curlOptions; - $responseData = Curl::get(static::API_URL . $endpoint, $data, $options); + $responseData = Curl::get(static::API_URL . $endpoint, $data, $options); - return $responseData ?? []; - } + return $responseData ?? []; + } - public static function post($endpoint, array $data = [], array $options = []) - { - $options += [ + public static function post($endpoint, array $data = [], array $options = []) + { + $options += [ 'password' => static::getApiUserPassword(), ] + static::$curlOptions; - $responseData = Curl::post(static::API_URL . $endpoint, $data, $options); - return $responseData ?? []; - } + $responseData = Curl::post(static::API_URL . $endpoint, $data, $options); + return $responseData ?? []; + } } class SalesforceException extends Exception diff --git a/lib/thirdparty/Slack.class.php b/lib/thirdparty/Slack.class.php index 616b4983..9264e89d 100644 --- a/lib/thirdparty/Slack.class.php +++ b/lib/thirdparty/Slack.class.php @@ -2,18 +2,15 @@ class Slack { - - public static function sendErrorIfProd($e, $alert = true) - { - if ($e instanceof Throwable) + public static function sendErrorIfProd($e, $alert = true) { - $e = Debug::exceptionToString($e); - } + if ($e instanceof Throwable) { + $e = Debug::exceptionToString($e); + } - $slackErrorNotificationUrl = Config::get(Config::SLACK_ERROR_NOTIFICATION_URL); - if ($slackErrorNotificationUrl) - { - Curl::post($slackErrorNotificationUrl, ['text' => ($alert ? ' ' : '') . Request::getRelativeUri() . "\n" . $e], ['json_data' => true]); + $slackErrorNotificationUrl = Config::get(Config::SLACK_ERROR_NOTIFICATION_URL); + if ($slackErrorNotificationUrl) { + Curl::post($slackErrorNotificationUrl, ['text' => ($alert ? ' ' : '') . Request::getRelativeUri() . "\n" . $e], ['json_data' => true]); + } } - } } diff --git a/lib/tools/Config.class.php b/lib/tools/Config.class.php index 32dfed23..da0610d7 100644 --- a/lib/tools/Config.class.php +++ b/lib/tools/Config.class.php @@ -2,45 +2,43 @@ class Config { - const HELP_CONTACT_EMAIL = 'josh@lbry.io'; + const HELP_CONTACT_EMAIL = 'josh@lbry.io'; - //Constant to help with managing strings - const IS_PROD = "is_prod"; - const GITHUB_KEY = "github_key"; - const GITHUB_DEVELOPER_CREDITS_CLIENT_ID = "github_developer_credits_client_id"; - const GITHUB_DEVELOPER_CREDITS_CLIENT_SECRET = "github_developer_credits_client_secret"; - const LBRY_API_SERVER = "lbry_api_server"; - const MAILCHIMP_KEY = "mailchimp_key"; - const ASANA_KEY = "asana_key"; - const AWS_LOG_ACCESS_KEY = "aws_log_access_key"; - const AWS_LOG_SECRET_KEY = "aws_log_secret_key"; - const MAILGUN_API_KEY = "mailgun_api_key"; - const SALESFORCE_KEY = "salesforce_key"; - const SALESFORCE_SECRET = "salesforce_secret"; - const SLACK_ERROR_NOTIFICATION_URL = "slack_error_notification_url"; + //Constant to help with managing strings + const IS_PROD = "is_prod"; + const GITHUB_KEY = "github_key"; + const GITHUB_DEVELOPER_CREDITS_CLIENT_ID = "github_developer_credits_client_id"; + const GITHUB_DEVELOPER_CREDITS_CLIENT_SECRET = "github_developer_credits_client_secret"; + const LBRY_API_SERVER = "lbry_api_server"; + const MAILCHIMP_KEY = "mailchimp_key"; + const ASANA_KEY = "asana_key"; + const AWS_LOG_ACCESS_KEY = "aws_log_access_key"; + const AWS_LOG_SECRET_KEY = "aws_log_secret_key"; + const MAILGUN_API_KEY = "mailgun_api_key"; + const SALESFORCE_KEY = "salesforce_key"; + const SALESFORCE_SECRET = "salesforce_secret"; + const SLACK_ERROR_NOTIFICATION_URL = "slack_error_notification_url"; - protected static $loaded = false; - protected static $data = []; + protected static $loaded = false; + protected static $data = []; - public static function get($name, $default = null) - { - static::load(); - return array_key_exists($name, static::$data) ? static::$data[$name] : $default; - } - - - protected static function load() - { - if (!static::$loaded) + public static function get($name, $default = null) { - $dataFile = ROOT_DIR.'/data/config.php'; - if (!is_readable($dataFile)) - { - throw new RuntimeException('config file is missing or not readable'); - } - static::$data = require $dataFile; - static::$loaded = true; + static::load(); + return array_key_exists($name, static::$data) ? static::$data[$name] : $default; + } + + + protected static function load() + { + if (!static::$loaded) { + $dataFile = ROOT_DIR.'/data/config.php'; + if (!is_readable($dataFile)) { + throw new RuntimeException('config file is missing or not readable'); + } + static::$data = require $dataFile; + static::$loaded = true; + } } - } } diff --git a/lib/tools/Curl.class.php b/lib/tools/Curl.class.php index 95efef12..2dc0ce6f 100644 --- a/lib/tools/Curl.class.php +++ b/lib/tools/Curl.class.php @@ -2,40 +2,40 @@ class Curl { - const + const GET = 'GET', POST = 'POST', PUT = 'PUT', DELETE = 'DELETE'; - public static function get($url, $params = [], $options = []) - { - list ($status, $headers, $body) = static::doCurl(static::GET, $url, $params, $options); - return $body; - } + public static function get($url, $params = [], $options = []) + { + list($status, $headers, $body) = static::doCurl(static::GET, $url, $params, $options); + return $body; + } - public static function post($url, $params = [], $options = []) - { - list ($status, $headers, $body) = static::doCurl(static::POST, $url, $params, $options); - return $body; - } + public static function post($url, $params = [], $options = []) + { + list($status, $headers, $body) = static::doCurl(static::POST, $url, $params, $options); + return $body; + } - public static function put($url, $params = [], $options = []) - { - list ($status, $headers, $body) = static::doCurl(static::PUT, $url, $params, $options); - return $body; - } + public static function put($url, $params = [], $options = []) + { + list($status, $headers, $body) = static::doCurl(static::PUT, $url, $params, $options); + return $body; + } - public static function delete($url, $params = [], $options = []) - { - list ($status, $headers, $body) = static::doCurl(static::DELETE, $url, $params, $options); - return $body; - } + public static function delete($url, $params = [], $options = []) + { + list($status, $headers, $body) = static::doCurl(static::DELETE, $url, $params, $options); + return $body; + } - public static function doCurl($method, $url, $params = [], $options = []) - { - $defaults = [ + public static function doCurl($method, $url, $params = [], $options = []) + { + $defaults = [ 'headers' => [], 'verify' => true, 'timeout' => 5, @@ -49,41 +49,36 @@ class Curl 'retry' => false, ]; - $invalid = array_diff_key($options, $defaults); - if ($invalid) - { - throw new DomainException('Invalid curl options: ' . join(', ', array_keys($invalid))); - } + $invalid = array_diff_key($options, $defaults); + if ($invalid) { + throw new DomainException('Invalid curl options: ' . join(', ', array_keys($invalid))); + } - if (!in_array($method, [static::GET, static::POST, static::PUT])) - { - throw new DomainException('Invalid method: ' . $method); - } + if (!in_array($method, [static::GET, static::POST, static::PUT])) { + throw new DomainException('Invalid method: ' . $method); + } - $options = array_merge($defaults, $options); + $options = array_merge($defaults, $options); - if ($options['headers'] && $options['headers'] !== array_values($options['headers'])) // associative array - { - throw new DomainException('Headers must not be an associative array. Its a simple array with values of the form "Header: value"'); - } + if ($options['headers'] && $options['headers'] !== array_values($options['headers'])) { // associative array + throw new DomainException('Headers must not be an associative array. Its a simple array with values of the form "Header: value"'); + } - $ch = curl_init(); + $ch = curl_init(); - curl_setopt($ch, CURLOPT_VERBOSE, true); - curl_setopt($ch, CURLOPT_STDERR, fopen(sys_get_temp_dir().'/curl-debug-'.date('Ymd-His'), 'w+')); + curl_setopt($ch, CURLOPT_VERBOSE, true); + curl_setopt($ch, CURLOPT_STDERR, fopen(sys_get_temp_dir().'/curl-debug-'.date('Ymd-His'), 'w+')); - if ($ch === false || $ch === null) - { - throw new LogicException('Unable to initialize cURL'); - } + if ($ch === false || $ch === null) { + throw new LogicException('Unable to initialize cURL'); + } - $urlWithParams = $url; - if ($method == static::GET && $params) - { - $urlWithParams .= (strpos($urlWithParams, '?') === false ? '?' : '&') . http_build_query($params); - } + $urlWithParams = $url; + if ($method == static::GET && $params) { + $urlWithParams .= (strpos($urlWithParams, '?') === false ? '?' : '&') . http_build_query($params); + } - curl_setopt_array($ch, [ + curl_setopt_array($ch, [ CURLOPT_URL => $urlWithParams, CURLOPT_HTTPHEADER => $options['headers'], CURLOPT_RETURNTRANSFER => true, @@ -96,97 +91,82 @@ class Curl CURLOPT_USERAGENT => $options['user_agent'], ]); - if ($method == static::POST) - { - curl_setopt($ch, CURLOPT_POST, true); - } - elseif ($method == static::PUT) - { - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); - } + if ($method == static::POST) { + curl_setopt($ch, CURLOPT_POST, true); + } elseif ($method == static::PUT) { + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); + } - if (in_array($method, [static::PUT, static::POST])) - { - curl_setopt($ch, CURLOPT_POSTFIELDS, $options['json_data'] ? json_encode($params) : http_build_query($params)); - } + if (in_array($method, [static::PUT, static::POST])) { + curl_setopt($ch, CURLOPT_POSTFIELDS, $options['json_data'] ? json_encode($params) : http_build_query($params)); + } - if ($options['proxy']) - { - curl_setopt($ch, CURLOPT_PROXY, $options['proxy']); - curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5); - } + if ($options['proxy']) { + curl_setopt($ch, CURLOPT_PROXY, $options['proxy']); + curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5); + } - if ($options['password']) - { - curl_setopt($ch, CURLOPT_USERPWD, $options['password']); - } + if ($options['password']) { + curl_setopt($ch, CURLOPT_USERPWD, $options['password']); + } - if ($options['cookie']) - { - curl_setopt($ch, CURLOPT_COOKIE, $options['cookie']); - } + if ($options['cookie']) { + curl_setopt($ch, CURLOPT_COOKIE, $options['cookie']); + } - $startingResponse = false; - $headers = []; - curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($ch, $h) use (&$headers, &$startingResponse) - { - $value = trim($h); - if ($value === '') - { - $startingResponse = true; - } - elseif ($startingResponse) - { $startingResponse = false; - $headers = [$value]; - } - else - { - $headers[] = $value; - } - return strlen($h); - }); + $headers = []; + curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($ch, $h) use (&$headers, &$startingResponse) { + $value = trim($h); + if ($value === '') { + $startingResponse = true; + } elseif ($startingResponse) { + $startingResponse = false; + $headers = [$value]; + } else { + $headers[] = $value; + } + return strlen($h); + }); - $rawResponse = curl_exec($ch); + $rawResponse = curl_exec($ch); - if ($options['json_response']) - { - $responseContent = $rawResponse ? json_decode($rawResponse, true) : []; + if ($options['json_response']) { + $responseContent = $rawResponse ? json_decode($rawResponse, true) : []; + } else { + $responseContent = $rawResponse; + } + + $statusCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); + + if (curl_errno($ch)) { + if ($options['retry'] && is_numeric($options['retry']) && $options['retry'] > 0) { + $options['retry'] -= 1; + return static::doCurl($method, $url, $params, $options); + } + throw new CurlException($ch); + } + + curl_close($ch); + + return [$statusCode, $headers, $responseContent]; } - else - { - $responseContent = $rawResponse; - } - - $statusCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); - - if (curl_errno($ch)) - { - if ($options['retry'] && is_numeric($options['retry']) && $options['retry'] > 0) - { - $options['retry'] -= 1; - return static::doCurl($method, $url, $params, $options); - } - throw new CurlException($ch); - } - - curl_close($ch); - - return [$statusCode, $headers, $responseContent]; - } } class CurlException extends Exception { - protected $errno, $error, $info, $handle; + protected $errno; + protected $error; + protected $info; + protected $handle; - public function __construct($curlHandle, Exception $previous = null) - { - $this->handle = $curlHandle; - $this->errno = curl_errno($curlHandle); - $this->error = curl_error($curlHandle); - $this->info = curl_getinfo($curlHandle); + public function __construct($curlHandle, Exception $previous = null) + { + $this->handle = $curlHandle; + $this->errno = curl_errno($curlHandle); + $this->error = curl_error($curlHandle); + $this->info = curl_getinfo($curlHandle); - parent::__construct($this->error, $this->errno, $previous); - } + parent::__construct($this->error, $this->errno, $previous); + } } diff --git a/lib/tools/CurlWithCache.class.php b/lib/tools/CurlWithCache.class.php index f7b52d09..63b896c4 100644 --- a/lib/tools/CurlWithCache.class.php +++ b/lib/tools/CurlWithCache.class.php @@ -2,40 +2,34 @@ class CurlWithCache extends Curl { - const DEFAULT_CACHE = 600000; + const DEFAULT_CACHE = 600000; - public static function doCurl($method, $url, $params = [], $options = []) - { - $cacheAllowed = $options['cache'] ?? true; - $cacheEnabled = Apc::isEnabled(); - - $cacheTimeout = is_numeric($options['cache'] ?? null) ? $options['cache'] : static::DEFAULT_CACHE; - unset($options['cache']); - - $cacheKey = $cacheEnabled ? md5($url . $method . serialize($options) . serialize($params)) : null; - if ($cacheAllowed && $cacheKey) + public static function doCurl($method, $url, $params = [], $options = []) { - $cachedData = apc_fetch($cacheKey); - if ($cachedData) - { - return $cachedData; - } + $cacheAllowed = $options['cache'] ?? true; + $cacheEnabled = Apc::isEnabled(); + + $cacheTimeout = is_numeric($options['cache'] ?? null) ? $options['cache'] : static::DEFAULT_CACHE; + unset($options['cache']); + + $cacheKey = $cacheEnabled ? md5($url . $method . serialize($options) . serialize($params)) : null; + if ($cacheAllowed && $cacheKey) { + $cachedData = apc_fetch($cacheKey); + if ($cachedData) { + return $cachedData; + } + } + + $response = parent::doCurl($method, $url, $params, $options); + + if ($cacheEnabled) { + if ($cacheAllowed) { + apc_store($cacheKey, $response, $cacheTimeout); + } else { + apc_delete($cacheKey); + } + } + + return $response; } - - $response = parent::doCurl($method, $url, $params, $options); - - if ($cacheEnabled) - { - if ($cacheAllowed) - { - apc_store($cacheKey, $response, $cacheTimeout); - } - else - { - apc_delete($cacheKey); - } - } - - return $response; - } -} \ No newline at end of file +} diff --git a/lib/tools/Debug.class.php b/lib/tools/Debug.class.php index 6db68aac..5c71e7f6 100644 --- a/lib/tools/Debug.class.php +++ b/lib/tools/Debug.class.php @@ -2,74 +2,61 @@ class Debug { - public static function exceptionToString(Throwable $e) - { - return static::getExceptionMessageWithoutTrace($e) . "\n" . static::getFullTrace($e); - } - - public static function getExceptionMessageWithoutTrace(Throwable $e) - { - return 'exception \'' . get_class($e) . '\' with message \'' . $e->getMessage() . '\' in ' . $e->getFile() . ':' . $e->getLine(); - } - - /** - * Same as the normal getTraceAsString(), but does not truncate long lines. - * @param Throwable $exception - * @return string - * @see http://stackoverflow.com/questions/1949345/how-can-i-get-the-full-string-of-phps-gettraceasstring/6076667#6076667 - * @see https://gist.github.com/1437966 - */ - public static function getFullTrace(Throwable $exception) - { - $rtn = ''; - foreach ($exception->getTrace() as $count => $frame) + public static function exceptionToString(Throwable $e) { - $args = isset($frame['args']) ? static::exceptionFrameArgsToString($frame['args']) : ''; + return static::getExceptionMessageWithoutTrace($e) . "\n" . static::getFullTrace($e); + } - $rtn .= sprintf("#%s %s(%s): %s(%s)\n", + public static function getExceptionMessageWithoutTrace(Throwable $e) + { + return 'exception \'' . get_class($e) . '\' with message \'' . $e->getMessage() . '\' in ' . $e->getFile() . ':' . $e->getLine(); + } + + /** + * Same as the normal getTraceAsString(), but does not truncate long lines. + * @param Throwable $exception + * @return string + * @see http://stackoverflow.com/questions/1949345/how-can-i-get-the-full-string-of-phps-gettraceasstring/6076667#6076667 + * @see https://gist.github.com/1437966 + */ + public static function getFullTrace(Throwable $exception) + { + $rtn = ''; + foreach ($exception->getTrace() as $count => $frame) { + $args = isset($frame['args']) ? static::exceptionFrameArgsToString($frame['args']) : ''; + + $rtn .= sprintf( + "#%s %s(%s): %s(%s)\n", $count, $frame['file'] ?? 'unknown file', $frame['line'] ?? 'unknown line', isset($frame['class']) ? $frame['class'].$frame['type'].$frame['function'] : $frame['function'], - $args); + $args + ); + } + return $rtn; } - return $rtn; - } - public static function exceptionFrameArgsToString(array $args) - { - $ret = []; - foreach ($args as $arg) + public static function exceptionFrameArgsToString(array $args) { - if (is_string($arg)) - { - $ret[] = "'" . $arg . "'"; - } - elseif (is_array($arg)) - { - $ret[] = 'Array(' . count($arg) . ')'; - } - elseif (is_null($arg)) - { - $ret[] = 'NULL'; - } - elseif (is_bool($arg)) - { - $ret[] = ($arg) ? 'true' : 'false'; - } - elseif (is_object($arg)) - { - $ret[] = get_class($arg) . (!($arg instanceof Closure) && isset($arg->id) ? "({$arg->id})" : ''); - } - elseif (is_resource($arg)) - { - $ret[] = get_resource_type($arg); - } - else - { - $ret[] = $arg; - } + $ret = []; + foreach ($args as $arg) { + if (is_string($arg)) { + $ret[] = "'" . $arg . "'"; + } elseif (is_array($arg)) { + $ret[] = 'Array(' . count($arg) . ')'; + } elseif (is_null($arg)) { + $ret[] = 'NULL'; + } elseif (is_bool($arg)) { + $ret[] = ($arg) ? 'true' : 'false'; + } elseif (is_object($arg)) { + $ret[] = get_class($arg) . (!($arg instanceof Closure) && isset($arg->id) ? "({$arg->id})" : ''); + } elseif (is_resource($arg)) { + $ret[] = get_resource_type($arg); + } else { + $ret[] = $arg; + } + } + return join(', ', $ret); } - return join(', ', $ret); - } } diff --git a/lib/tools/Encoding.class.php b/lib/tools/Encoding.class.php index 3f6b6dc7..b30ab762 100644 --- a/lib/tools/Encoding.class.php +++ b/lib/tools/Encoding.class.php @@ -2,63 +2,58 @@ class Encoding { - public static function base64EncodeUrlsafe($data) - { - return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); // equals sign is just for padding, can be safely removed - } - - public static function base64DecodeUrlsafe($data) - { - return base64_decode(strtr($data, '-_', '+/')); // dont worry about replacing equals sign - } - - public static function base58Encode($byteString) - { - $alphabet = '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ'; - return static::convertBase($byteString, join('', array_map('chr', range(0, 255))), $alphabet); - } - - protected static function convertBase($numberInput, $sourceAlphabet, $targetAlphabet) - { - if ($sourceAlphabet == $targetAlphabet) + public static function base64EncodeUrlsafe($data) { - return $numberInput; + return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); // equals sign is just for padding, can be safely removed } - $decimalAlphabet = '0123456789'; - - $fromBase = str_split($sourceAlphabet); - $toBase = str_split($targetAlphabet); - $number = str_split($numberInput); - - $fromLen = strlen($sourceAlphabet); - $toLen = strlen($targetAlphabet); - $numberLen = strlen($numberInput); - - if ($targetAlphabet == $decimalAlphabet) + public static function base64DecodeUrlsafe($data) { - $decimal = 0; - for ($i = 1; $i <= $numberLen; $i++) - { - $decimal = bcadd($decimal, bcmul(array_search($number[$i - 1], $fromBase), bcpow($fromLen, $numberLen - $i))); - } - return $decimal; + return base64_decode(strtr($data, '-_', '+/')); // dont worry about replacing equals sign } - $base10 = $sourceAlphabet == $decimalAlphabet ? $numberInput : static::convertBase($numberInput, $sourceAlphabet, $decimalAlphabet); - - if ($base10 < strlen($targetAlphabet)) + public static function base58Encode($byteString) { - return $toBase[$base10]; + $alphabet = '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ'; + return static::convertBase($byteString, join('', array_map('chr', range(0, 255))), $alphabet); } - $retval = ''; - while ($base10 != '0') + protected static function convertBase($numberInput, $sourceAlphabet, $targetAlphabet) { - $retval = $toBase[bcmod($base10, $toLen)] . $retval; - $base10 = bcdiv($base10, $toLen, 0); - } + if ($sourceAlphabet == $targetAlphabet) { + return $numberInput; + } - return $retval; - } -} \ No newline at end of file + $decimalAlphabet = '0123456789'; + + $fromBase = str_split($sourceAlphabet); + $toBase = str_split($targetAlphabet); + $number = str_split($numberInput); + + $fromLen = strlen($sourceAlphabet); + $toLen = strlen($targetAlphabet); + $numberLen = strlen($numberInput); + + if ($targetAlphabet == $decimalAlphabet) { + $decimal = 0; + for ($i = 1; $i <= $numberLen; $i++) { + $decimal = bcadd($decimal, bcmul(array_search($number[$i - 1], $fromBase), bcpow($fromLen, $numberLen - $i))); + } + return $decimal; + } + + $base10 = $sourceAlphabet == $decimalAlphabet ? $numberInput : static::convertBase($numberInput, $sourceAlphabet, $decimalAlphabet); + + if ($base10 < strlen($targetAlphabet)) { + return $toBase[$base10]; + } + + $retval = ''; + while ($base10 != '0') { + $retval = $toBase[bcmod($base10, $toLen)] . $retval; + $base10 = bcdiv($base10, $toLen, 0); + } + + return $retval; + } +} diff --git a/lib/tools/Gzip.class.php b/lib/tools/Gzip.class.php index 260d4dcd..50488b4d 100644 --- a/lib/tools/Gzip.class.php +++ b/lib/tools/Gzip.class.php @@ -2,34 +2,29 @@ class Gzip { - public static function compressFile($source, $level = 1) - { - $compressedPath = $source . '.gz'; - $mode = 'wb' . $level; - - $fpOut = gzopen($compressedPath, $mode); - if (!$fpOut) + public static function compressFile($source, $level = 1) { - return false; - } + $compressedPath = $source . '.gz'; + $mode = 'wb' . $level; - $fpIn = fopen($source, 'rb'); - $error = false; - if ($fpIn) - { - while (!feof($fpIn)) - { - gzwrite($fpOut, fread($fpIn, 1024 * 512)); - } - fclose($fpIn); - } - else - { - $error = true; - } + $fpOut = gzopen($compressedPath, $mode); + if (!$fpOut) { + return false; + } - gzclose($fpOut); + $fpIn = fopen($source, 'rb'); + $error = false; + if ($fpIn) { + while (!feof($fpIn)) { + gzwrite($fpOut, fread($fpIn, 1024 * 512)); + } + fclose($fpIn); + } else { + $error = true; + } - return $error ? false : $compressedPath; - } -} \ No newline at end of file + gzclose($fpOut); + + return $error ? false : $compressedPath; + } +} diff --git a/lib/tools/Lock.class.php b/lib/tools/Lock.class.php index 4c5affbe..4f7b78b5 100644 --- a/lib/tools/Lock.class.php +++ b/lib/tools/Lock.class.php @@ -16,55 +16,50 @@ else { class Lock { - /** - * Creates a lockfile and acquires an exclusive lock on it. - * - * @param string $name The name of the lockfile. - * @param boolean $blocking Block until lock becomes available (default: don't block, just fail) - * @return mixed Returns the lockfile, or FALSE if a lock could not be acquired. - */ - public static function getLock($name, $blocking = false) - { - if (!preg_match('/^[A-Za-z0-9\.\-_]+$/', $name)) + /** + * Creates a lockfile and acquires an exclusive lock on it. + * + * @param string $name The name of the lockfile. + * @param boolean $blocking Block until lock becomes available (default: don't block, just fail) + * @return mixed Returns the lockfile, or FALSE if a lock could not be acquired. + */ + public static function getLock($name, $blocking = false) { - throw new InvalidArgumentException('Invalid lock name: "' . $name . '"'); + if (!preg_match('/^[A-Za-z0-9\.\-_]+$/', $name)) { + throw new InvalidArgumentException('Invalid lock name: "' . $name . '"'); + } + + $filename = static::getLockDir() . '/' . $name; + if (!preg_match('/\.lo?ck$/', $filename)) { + $filename .= '.lck'; + } + if (!file_exists($filename)) { + file_put_contents($filename, ''); + chmod($filename, 0666); // if the file cant be opened for writing later, getting the lock will fail + } + $lockFile = fopen($filename, 'w+'); + if (!flock($lockFile, $blocking ? LOCK_EX : LOCK_EX|LOCK_NB)) { + fclose($lockFile); + return false; + } + return $lockFile; } - $filename = static::getLockDir() . '/' . $name; - if (!preg_match('/\.lo?ck$/', $filename)) + /** + * Free a lock. + * + * @param resource $lockFile + */ + public static function freeLock($lockFile) { - $filename .= '.lck'; + if ($lockFile) { + flock($lockFile, LOCK_UN); + fclose($lockFile); + } } - if (!file_exists($filename)) - { - file_put_contents($filename, ''); - chmod($filename, 0666); // if the file cant be opened for writing later, getting the lock will fail - } - $lockFile = fopen($filename, 'w+'); - if (!flock($lockFile, $blocking ? LOCK_EX : LOCK_EX|LOCK_NB)) - { - fclose($lockFile); - return false; - } - return $lockFile; - } - /** - * Free a lock. - * - * @param resource $lockFile - */ - public static function freeLock($lockFile) - { - if ($lockFile) + public static function getLockDir() { - flock($lockFile, LOCK_UN); - fclose($lockFile); + return sys_get_temp_dir(); } - } - - public static function getLockDir() - { - return sys_get_temp_dir(); - } -} \ No newline at end of file +} diff --git a/lib/tools/OS.class.php b/lib/tools/OS.class.php index 64249608..bf5df116 100644 --- a/lib/tools/OS.class.php +++ b/lib/tools/OS.class.php @@ -2,32 +2,31 @@ class OS { - /* - * if changing below constants, you should add permanent redirects for old OS names used in URLs - */ - const OS_ANDROID = 'android', + /* + * if changing below constants, you should add permanent redirects for old OS names used in URLs + */ + const OS_ANDROID = 'android', OS_IOS = 'ios', OS_LINUX = 'linux', OS_OSX = 'osx', OS_WINDOWS = 'windows'; - public static function getAll() - { - //url, English name, icon class, partial name - //yes, this is probably a bad pattern - return [ + public static function getAll() + { + //url, English name, icon class, partial name + //yes, this is probably a bad pattern + return [ OS::OS_WINDOWS => ['/windows', 'Windows', 'icon-windows', __("Download for Windows"), "Windows"], OS::OS_OSX => ['/osx', 'macOS', 'icon-apple', __("Download for macOS"), "OSX"], OS::OS_LINUX => ['/linux', 'Linux', 'icon-linux', __("Download .deb"), "Linux"], OS::OS_ANDROID => ['/android', 'Android', 'icon-android', false, false], OS::OS_IOS => ['/ios', 'iOS', 'icon-mobile', false, false] ]; - } + } - public static function getOsForExtension($ext) - { - switch ($ext) + public static function getOsForExtension($ext) { + switch ($ext) { case 'deb': return OS::OS_LINUX; @@ -42,5 +41,5 @@ class OS default: throw new LogicException("Unknown ext $ext"); } - } -} \ No newline at end of file + } +} diff --git a/lib/tools/Shell.class.php b/lib/tools/Shell.class.php index ff2036bd..f2a51394 100644 --- a/lib/tools/Shell.class.php +++ b/lib/tools/Shell.class.php @@ -14,140 +14,121 @@ */ class Shell { - /** - * Execute a command. Returns the command's exit code, output, and error output. - * - * @param string $cmd The command to execute - * @param array $options Available options: - * - echo (bool) - If true, print the command's output/error to stdout/stderr of this process - * - echo_errors (bool) - If you want to control printing to stderr separately, use this. If not provided, will - * default to the value of 'echo' - * - live_callback (callable) - Will be called as soon as data is read. Use this for custom handling - * of live output. Callable signature: fn(string $text, bool $isError) - * - * @return array [exit code, output, errorOutput] - */ - public static function exec($cmd, array $options = []) - { - $options = array_merge([ + /** + * Execute a command. Returns the command's exit code, output, and error output. + * + * @param string $cmd The command to execute + * @param array $options Available options: + * - echo (bool) - If true, print the command's output/error to stdout/stderr of this process + * - echo_errors (bool) - If you want to control printing to stderr separately, use this. If not provided, will + * default to the value of 'echo' + * - live_callback (callable) - Will be called as soon as data is read. Use this for custom handling + * of live output. Callable signature: fn(string $text, bool $isError) + * + * @return array [exit code, output, errorOutput] + */ + public static function exec($cmd, array $options = []) + { + $options = array_merge([ 'echo' => false, 'echo_errors' => null, 'live_callback' => null, ], $options); - if ($options['echo_errors'] === null) - { - $options['echo_errors'] = $options['echo']; - } + if ($options['echo_errors'] === null) { + $options['echo_errors'] = $options['echo']; + } - if ($options['live_callback'] && !is_callable($options['live_callback'])) - { - throw new InvalidArgumentException('live_callback option must be a valid callback'); - } + if ($options['live_callback'] && !is_callable($options['live_callback'])) { + throw new InvalidArgumentException('live_callback option must be a valid callback'); + } - $descriptorSpec = [ + $descriptorSpec = [ 1 => ['pipe', 'w'], // stdout 2 => ['pipe', 'w'], // stderr ]; - $process = proc_open($cmd, $descriptorSpec, $pipes); - if (!is_resource($process)) - { - throw new RuntimeException('proc_open failed'); + $process = proc_open($cmd, $descriptorSpec, $pipes); + if (!is_resource($process)) { + throw new RuntimeException('proc_open failed'); + } + + stream_set_blocking($pipes[1], false); + stream_set_blocking($pipes[2], false); + + $output = ''; + $err = ''; + + while (!feof($pipes[1]) || !feof($pipes[2])) { + foreach ($pipes as $key => $pipe) { + $data = fread($pipe, 1024); + if (!$data) { + continue; + } + + if (1 == $key) { + $output .= $data; + if ($options['echo']) { + fprintf(STDOUT, "%s", $data); + } + if ($options['live_callback']) { + call_user_func($options['live_callback'], $data, false); + } + } else { + $err .= $data; + if ($options['echo_errors']) { + fprintf(STDERR, "%s", $data); + } + if ($options['live_callback']) { + call_user_func($options['live_callback'], $data, true); + } + } + } + + usleep(100000); + } + + fclose($pipes[1]); + fclose($pipes[2]); + + $exitCode = proc_close($process); + + return [$exitCode, $output, $err]; } - stream_set_blocking($pipes[1], false); - stream_set_blocking($pipes[2], false); - - $output = ''; - $err = ''; - - while (!feof($pipes[1]) || !feof($pipes[2])) + /** + * Convenience method to build a command to execute. Will escape everything as necessary. + * + * @param string $executable The path to the executable + * @param array $arguments An array of arguments + * @param array $options An associative array of flags in the form flagName => flagValue. + * Short and long flags are supported. + * If flagValue === true, the flag have no value. + * If flagValue === false, the flag will be skipped. + * + * @return string An executable command + */ + public static function buildCmd($executable, array $arguments = [], array $options = []) { - foreach ($pipes as $key => $pipe) - { - $data = fread($pipe, 1024); - if (!$data) - { - continue; + $shellArgs = []; + + foreach ($options as $key => $value) { + if ($value === false) { + continue; + } + + if (strlen($key) === 1) { + $shellArgs[] = '-'.$key; + if ($value !== true) { + $shellArgs[] = $value; + } + } else { + $shellArgs[] = '--' . $key . ($value !== true ? '=' . $value : ''); + } } - if (1 == $key) - { - $output .= $data; - if ($options['echo']) - { - fprintf(STDOUT, "%s", $data); - } - if ($options['live_callback']) - { - call_user_func($options['live_callback'], $data, false); - } - } - else - { - $err .= $data; - if ($options['echo_errors']) - { - fprintf(STDERR, "%s", $data); - } - if ($options['live_callback']) - { - call_user_func($options['live_callback'], $data, true); - } - } - } + $shellArgs = array_merge($shellArgs, array_values($arguments)); - usleep(100000); + return $executable . ' ' . join(' ', array_map('escapeshellarg', $shellArgs)); } - - fclose($pipes[1]); - fclose($pipes[2]); - - $exitCode = proc_close($process); - - return [$exitCode, $output, $err]; - } - - /** - * Convenience method to build a command to execute. Will escape everything as necessary. - * - * @param string $executable The path to the executable - * @param array $arguments An array of arguments - * @param array $options An associative array of flags in the form flagName => flagValue. - * Short and long flags are supported. - * If flagValue === true, the flag have no value. - * If flagValue === false, the flag will be skipped. - * - * @return string An executable command - */ - public static function buildCmd($executable, array $arguments = [], array $options = []) - { - $shellArgs = []; - - foreach ($options as $key => $value) - { - if ($value === false) - { - continue; - } - - if (strlen($key) === 1) - { - $shellArgs[] = '-'.$key; - if ($value !== true) - { - $shellArgs[] = $value; - } - } - else - { - $shellArgs[] = '--' . $key . ($value !== true ? '=' . $value : ''); - } - } - - $shellArgs = array_merge($shellArgs, array_values($arguments)); - - return $executable . ' ' . join(' ', array_map('escapeshellarg', $shellArgs)); - } -} \ No newline at end of file +} diff --git a/lib/tools/Smaz.class.php b/lib/tools/Smaz.class.php index c27b7bee..3a2b8783 100644 --- a/lib/tools/Smaz.class.php +++ b/lib/tools/Smaz.class.php @@ -13,14 +13,14 @@ */ class Smaz { - const CODEBOOK_DEFAULT = 'default'; - const CODEBOOK_EMAIL = 'email'; + const CODEBOOK_DEFAULT = 'default'; + const CODEBOOK_EMAIL = 'email'; - const VERBATIM_CHAR = 254; - const VERBATIM_STR = 255; + const VERBATIM_CHAR = 254; + const VERBATIM_STR = 255; - protected static $encodeBooks = []; - protected static $decodeBooks = [ + protected static $encodeBooks = []; + protected static $decodeBooks = [ self::CODEBOOK_DEFAULT => [ " ", "the", "e", "t", "a", "of", "o", "and", "i", "n", "s", "e ", "r", " th", " t", "in", "he", "th", "h", "he ", "to", "\r\n", "l", "s ", "d", " a", "an", @@ -67,145 +67,123 @@ class Smaz ] ]; - protected static function getEncodeBook($codebook) - { - if (!isset(static::$encodeBooks[$codebook])) + protected static function getEncodeBook($codebook) { - static::$encodeBooks[$codebook] = array_flip(static::getDecodeBook($codebook)); + if (!isset(static::$encodeBooks[$codebook])) { + static::$encodeBooks[$codebook] = array_flip(static::getDecodeBook($codebook)); + } + return static::$encodeBooks[$codebook]; } - return static::$encodeBooks[$codebook]; - } - protected static function getDecodeBook($codebook) - { - if (!static::$decodeBooks[$codebook]) + protected static function getDecodeBook($codebook) { - throw new Exception('decodebook ' . $codebook . ' does not exist'); + if (!static::$decodeBooks[$codebook]) { + throw new Exception('decodebook ' . $codebook . ' does not exist'); + } + if (count(static::$decodeBooks[$codebook]) > static::VERBATIM_CHAR) { + throw new Exception('decodebook ' . $codebook . ' must be at most ' . static::VERBATIM_CHAR . 'entries'); + } + return static::$decodeBooks[$codebook]; } - if (count(static::$decodeBooks[$codebook]) > static::VERBATIM_CHAR) + + + /** + * @param string $str + * @param string $codebook + * + * @return string + */ + public static function encode($str, $codebook = self::CODEBOOK_DEFAULT) { - throw new Exception('decodebook ' . $codebook . ' must be at most ' . static::VERBATIM_CHAR . 'entries'); - } - return static::$decodeBooks[$codebook]; - } + $encodeBook = static::getEncodeBook($codebook); + $inLen = strlen($str); + $inIdx = 0; + $output = ''; + $verbatim = ''; + $maxItemLen = max(array_map('strlen', array_keys($encodeBook))); - /** - * @param string $str - * @param string $codebook - * - * @return string - */ - public static function encode($str, $codebook = self::CODEBOOK_DEFAULT) - { - $encodeBook = static::getEncodeBook($codebook); + while ($inIdx < $inLen) { + $encode = false; - $inLen = strlen($str); - $inIdx = 0; - $output = ''; - $verbatim = ''; - $maxItemLen = max(array_map('strlen', array_keys($encodeBook))); - - while ($inIdx < $inLen) - { - $encode = false; - - for ($j = min($maxItemLen, $inLen - $inIdx); $j > 0; $j--) - { - $code = isset($encodeBook[substr($str, $inIdx, $j)]) ? $encodeBook[substr($str, $inIdx, $j)] : null; - if ($code !== null) - { + for ($j = min($maxItemLen, $inLen - $inIdx); $j > 0; $j--) { + $code = isset($encodeBook[substr($str, $inIdx, $j)]) ? $encodeBook[substr($str, $inIdx, $j)] : null; + if ($code !== null) { // echo substr($str, $inIdx, $j) . " = $code\n"; - if (strlen($verbatim)) - { - $output .= static::flushVerbatim($verbatim); - $verbatim = ''; - } - $output .= chr($code); - $inIdx += $j; - $encode = true; - break; - } - } + if (strlen($verbatim)) { + $output .= static::flushVerbatim($verbatim); + $verbatim = ''; + } + $output .= chr($code); + $inIdx += $j; + $encode = true; + break; + } + } - if (!$encode) - { + if (!$encode) { // echo "VERBATIM\n"; - $verbatim .= $str[$inIdx]; - $inIdx++; - if (strlen($verbatim) == 255) // any longer, and we can't represent the length as a single char - { - $output .= static::flushVerbatim($verbatim); - $verbatim = ''; + $verbatim .= $str[$inIdx]; + $inIdx++; + if (strlen($verbatim) == 255) { // any longer, and we can't represent the length as a single char + $output .= static::flushVerbatim($verbatim); + $verbatim = ''; + } + } } - } + + if (strlen($verbatim)) { + $output .= static::flushVerbatim($verbatim); + } + return $output; } - if (strlen($verbatim)) + /** + * @param string $str + * @param string $codebook + * + * @return string + */ + public static function decode($str, $codebook = self::CODEBOOK_DEFAULT) { - $output .= static::flushVerbatim($verbatim); + $decodeBook = static::getDecodeBook($codebook); + + $output = ''; + $i = 0; + + while ($i < strlen($str)) { + $code = ord($str[$i]); + if ($code == static::VERBATIM_CHAR) { + $output .= $str[$i + 1]; + $i += 2; + } elseif ($code == static::VERBATIM_STR) { + $len = ord($str[$i + 1]); + $output .= substr($str, $i + 2, $len); + $i += 2 + $len; + } elseif (!isset($decodeBook[$code])) { + return null; // decode error. throw exception? + } else { + $output .= $decodeBook[$code]; + $i++; + } + } + return $output; } - return $output; - } - /** - * @param string $str - * @param string $codebook - * - * @return string - */ - public static function decode($str, $codebook = self::CODEBOOK_DEFAULT) - { - $decodeBook = static::getDecodeBook($codebook); - - $output = ''; - $i = 0; - - while ($i < strlen($str)) + protected static function flushVerbatim($verbatim) { - $code = ord($str[$i]); - if ($code == static::VERBATIM_CHAR) - { - $output .= $str[$i + 1]; - $i += 2; - } - elseif ($code == static::VERBATIM_STR) - { - $len = ord($str[$i + 1]); - $output .= substr($str, $i + 2, $len); - $i += 2 + $len; - } - elseif (!isset($decodeBook[$code])) - { - return null; // decode error. throw exception? - } - else - { - $output .= $decodeBook[$code]; - $i++; - } - } - return $output; - } + $output = ''; + if (!strlen($verbatim)) { + return $output; + } - protected static function flushVerbatim($verbatim) - { - $output = ''; - if (!strlen($verbatim)) - { - return $output; + if (strlen($verbatim) > 1) { + $output .= chr(static::VERBATIM_STR); + $output .= chr(strlen($verbatim)); + } else { + $output .= chr(static::VERBATIM_CHAR); + } + $output .= $verbatim; + return $output; } - - if (strlen($verbatim) > 1) - { - $output .= chr(static::VERBATIM_STR); - $output .= chr(strlen($verbatim)); - } - else - { - $output .= chr(static::VERBATIM_CHAR); - } - $output .= $verbatim; - return $output; - } -} \ No newline at end of file +} diff --git a/model/Post.class.php b/model/Post.class.php index f54a6a88..93811d83 100644 --- a/model/Post.class.php +++ b/model/Post.class.php @@ -1,312 +1,310 @@ path = $path; - $this->postType = $postType; - $this->slug = $slug; - $this->markdown = $markdown; - $this->metadata = $frontMatter; - $this->title = $frontMatter['title'] ?? null; - $this->author = $frontMatter['author'] ?? null; - $this->date = isset($frontMatter['date']) ? new DateTime($frontMatter['date']) : null; - $this->cover = $frontMatter['cover'] ?? null; - $this->isCoverLight = isset($frontMatter['cover-light']) && $frontMatter['cover-light'] == 'true'; - $this->category = $frontMatter['category'] ?? null; - } - - public static function find($folder, $sort = null) - { - $posts = []; - foreach(glob(rtrim($folder, '/') . '/*.md') as $file) - { - $posts[] = static::load($file); + $this->path = $path; + $this->postType = $postType; + $this->slug = $slug; + $this->markdown = $markdown; + $this->metadata = $frontMatter; + $this->title = $frontMatter['title'] ?? null; + $this->author = $frontMatter['author'] ?? null; + $this->date = isset($frontMatter['date']) ? new DateTime($frontMatter['date']) : null; + $this->cover = $frontMatter['cover'] ?? null; + $this->isCoverLight = isset($frontMatter['cover-light']) && $frontMatter['cover-light'] == 'true'; + $this->category = $frontMatter['category'] ?? null; } - if ($sort) + public static function find($folder, $sort = null) { - switch ($sort) - { + $posts = []; + foreach (glob(rtrim($folder, '/') . '/*.md') as $file) { + $posts[] = static::load($file); + } + + if ($sort) { + switch ($sort) { case static::SORT_DATE_DESC: - usort($posts, function(Post $a, Post $b) { - return strcasecmp($b->getDate()->format('Y-m-d'), $a->getDate()->format('Y-m-d')); + usort($posts, function (Post $a, Post $b) { + return strcasecmp($b->getDate()->format('Y-m-d'), $a->getDate()->format('Y-m-d')); }); break; case static::SORT_ORD_ASC: - usort($posts, function(Post $a, Post $b) { - $aMeta = $a->getMetadata(); - $bMeta = $b->getMetadata(); - if (!isset($aMeta['order']) && !isset($bMeta['order'])) - { - return $a->getTitle() < $b->getTitle() ? -1 : 1; - } - if (isset($aMeta['order']) && isset($bMeta['order'])) - { - return $aMeta['order'] < $bMeta['order'] ? -1 : 1; - } - return isset($aMeta['order']) ? -1 : 1; + usort($posts, function (Post $a, Post $b) { + $aMeta = $a->getMetadata(); + $bMeta = $b->getMetadata(); + if (!isset($aMeta['order']) && !isset($bMeta['order'])) { + return $a->getTitle() < $b->getTitle() ? -1 : 1; + } + if (isset($aMeta['order']) && isset($bMeta['order'])) { + return $aMeta['order'] < $bMeta['order'] ? -1 : 1; + } + return isset($aMeta['order']) ? -1 : 1; }); break; } + } + return $posts; } - return $posts; - } - public static function filter(array $posts, array $filters) - { - return array_filter($posts, function(Post $post) use($filters) { - $metadata = $post->getMetadata(); - foreach($filters as $filterAttr => $filterValue) - { - if (!isset($metadata[$filterAttr]) || ( + public static function filter(array $posts, array $filters) + { + return array_filter($posts, function (Post $post) use ($filters) { + $metadata = $post->getMetadata(); + foreach ($filters as $filterAttr => $filterValue) { + if (!isset($metadata[$filterAttr]) || ( ($metadata[$filterAttr] != $filterValue) && (!is_array($metadata[$filterAttr]) || !in_array($filterValue, $metadata[$filterAttr])) - )) - { - return false; + )) { + return false; + } + } + return true; + }); + } + + public function getMetadata() + { + return $this->metadata; + } + + public function getMetadataItem($key, $default = null) + { + return $this->metadata[$key] ?? $default; + } + + public function setMetadataItem($key, $value) + { + $this->metadata[$key] = $value; + } + + public function getRelativeUrl() + { + return '/' . $this->postType . '/' . $this->slug; + } + + public function getSlug() + { + return $this->slug; + } + + public function getTitle() + { + return $this->title; + } + + public function getAuthor() + { + return $this->author; + } + + public function getAuthorGithubID() + { + $post = ContentActions::prepareBioPartial(['person' =>$this->author]); + if (array_key_exists("github", $post)) { + return $post["github"]; } - } - return true; - }); - } - - public function getMetadata() - { - return $this->metadata; - } - - public function getMetadataItem($key, $default = null) - { - return $this->metadata[$key] ?? $default; - } - - public function setMetadataItem($key, $value) - { - $this->metadata[$key] = $value; - } - - public function getRelativeUrl() - { - return '/' . $this->postType . '/' . $this->slug; - } - - public function getSlug() - { - return $this->slug; - } - - public function getTitle() - { - return $this->title; - } - - public function getAuthor() - { - return $this->author; - } - - public function getAuthorGithubID() - { - $post = ContentActions::prepareBioPartial(['person' =>$this->author]); - if(array_key_exists("github", $post)){ - return $post["github"]; - } - } - -public function getAuthorTwitterID() -{ - $post = ContentActions::prepareBioPartial(['person' =>$this->author]); - if(array_key_exists("twitter", $post)){ - return $post["twitter"]; } -} -public function getAuthorEmail() -{ - $post = ContentActions::prepareBioPartial(['person' =>$this->author]); - if(array_key_exists("email", $post)){ - return $post["email"]; - } -} - - public function getDate() - { - return $this->date; - } - - public function getCover() - { - return $this->cover; - } - - public function getIsCoverLight() - { - return $this->isCoverLight; - } - - public function getCategory() - { - return $this->category; - } - - public function getContentText($wordLimit = null, $appendEllipsis = false) - { - if ($this->markdown && !$this->contentText) + public function getAuthorTwitterID() { + $post = ContentActions::prepareBioPartial(['person' =>$this->author]); + if (array_key_exists("twitter", $post)) { + return $post["twitter"]; + } + } + + public function getAuthorEmail() + { + $post = ContentActions::prepareBioPartial(['person' =>$this->author]); + if (array_key_exists("email", $post)) { + return $post["email"]; + } + } + + public function getDate() + { + return $this->date; + } + + public function getCover() + { + return $this->cover; + } + + public function getIsCoverLight() + { + return $this->isCoverLight; + } + + public function getCategory() + { + return $this->category; + } + + public function getContentText($wordLimit = null, $appendEllipsis = false) + { + if ($this->markdown && !$this->contentText) { // $this->contentText = $this->markdownToText(trim($this->markdown)); - $this->contentText = html_entity_decode(str_replace(' ', ' ', strip_tags($this->getContentHtml())), ENT_COMPAT, 'utf-8'); + $this->contentText = html_entity_decode(str_replace(' ', ' ', strip_tags($this->getContentHtml())), ENT_COMPAT, 'utf-8'); + } + + return $wordLimit === null ? $this->contentText : $this->limitWords($this->contentText, $wordLimit, $appendEllipsis); } - return $wordLimit === null ? $this->contentText : $this->limitWords($this->contentText, $wordLimit, $appendEllipsis); - } - - public function getContentHtml() - { - if ($this->markdown && !$this->contentHtml) + public function getContentHtml() { - $this->contentHtml = ParsedownExtra::instance()->text(trim($this->markdown)); + if ($this->markdown && !$this->contentHtml) { + $this->contentHtml = ParsedownExtra::instance()->text(trim($this->markdown)); + } + return $this->contentHtml; } - return $this->contentHtml; - } - public function getPostNum() - { - return array_search($this->getSlug(), array_keys(static::getSlugMap($this->postType))); - } - - public function getPrevPost() - { - $slugs = array_keys(Post::getSlugMap($this->postType)); - $postNum = $this->getPostNum(); - return $postNum === false || $postNum === 0 ? null : Post::load($this->postType . '/' . $slugs[$postNum-1]); - } - - public function getNextPost() - { - $slugs = array_keys(Post::getSlugMap($this->postType)); - $postNum = $this->getPostNum(); - return $postNum === false || $postNum >= count($slugs)-1 ? null : Post::load($this->postType . '/' . $slugs[$postNum+1]); - } - - public function hasAuthor() - { - return $this->author !== null; - } - - public function hasDate() - { - return $this->date !== null; - } - - public function getAuthorName() - { - $post = ContentActions::prepareBioPartial(['person' =>$this->author]); - - return $post["name"]; - } - - public function getAuthorPostEmail() - { - $post = ContentActions::prepareBioPartial(['person' =>$this->author]); - - return $post["email"]; - - } - - public function getAuthorPhoto() - { - $post = ContentActions::prepareBioPartial(['person' =>$this->author]); - - return $post['imgSrc']; - } - - public function getAuthorBioHtml() - { - $post = ContentActions::prepareBioPartial(['person' =>$this->author]); - - return $post["bioHtml"]; - } - - public function getCoverBackgroundStyle($maxStyles) - { - return $this->getPostNum() % $maxStyles + 1; - } - - public function getImageUrls() - { - $urls = []; - - $cover = $this->getCover(); - if ($cover) + public function getPostNum() { - $urls[] = 'https://' . Request::getHost() . '/img/blog-covers/' . $cover; + return array_search($this->getSlug(), array_keys(static::getSlugMap($this->postType))); } - $matches = []; - preg_match_all('/!\[.*?\]\((.*?)\)/', $this->markdown, $matches); - - if ($matches) + public function getPrevPost() { - $urls = array_merge($urls, $matches[1]); + $slugs = array_keys(Post::getSlugMap($this->postType)); + $postNum = $this->getPostNum(); + return $postNum === false || $postNum === 0 ? null : Post::load($this->postType . '/' . $slugs[$postNum-1]); } - return $urls; - } + public function getNextPost() + { + $slugs = array_keys(Post::getSlugMap($this->postType)); + $postNum = $this->getPostNum(); + return $postNum === false || $postNum >= count($slugs)-1 ? null : Post::load($this->postType . '/' . $slugs[$postNum+1]); + } - protected function markdownToText($markdown) - { - $replacements = [ + public function hasAuthor() + { + return $this->author !== null; + } + + public function hasDate() + { + return $this->date !== null; + } + + public function getAuthorName() + { + $post = ContentActions::prepareBioPartial(['person' =>$this->author]); + + return $post["name"]; + } + + public function getAuthorPostEmail() + { + $post = ContentActions::prepareBioPartial(['person' =>$this->author]); + + return $post["email"]; + } + + public function getAuthorPhoto() + { + $post = ContentActions::prepareBioPartial(['person' =>$this->author]); + + return $post['imgSrc']; + } + + public function getAuthorBioHtml() + { + $post = ContentActions::prepareBioPartial(['person' =>$this->author]); + + return $post["bioHtml"]; + } + + public function getCoverBackgroundStyle($maxStyles) + { + return $this->getPostNum() % $maxStyles + 1; + } + + public function getImageUrls() + { + $urls = []; + + $cover = $this->getCover(); + if ($cover) { + $urls[] = 'https://' . Request::getHost() . '/img/blog-covers/' . $cover; + } + + $matches = []; + preg_match_all('/!\[.*?\]\((.*?)\)/', $this->markdown, $matches); + + if ($matches) { + $urls = array_merge($urls, $matches[1]); + } + + return $urls; + } + + protected function markdownToText($markdown) + { + $replacements = [ // '/<(.*?)>/' => '$1', // HTML tags '/^[=\-]{2,}\s*$/' => '', // setext-style headers '/\[\^.+?\](\: .*?$)?/' => '', // footnotes @@ -327,65 +325,61 @@ public function getAuthorEmail() '/\n{2,}/' => '\n\n', // multiple newlines ]; - return preg_replace(array_keys($replacements), array_values($replacements), strip_tags($markdown)); - } - - protected function limitWords($string, $wordLimit, $appendEllipsis = false) - { - $regexp = '/\s+/u'; - $words = preg_split($regexp, $string, $wordLimit + 1); - $numWords = count($words); - - # TBB: if there are $wordLimit words or less, this check is necessary - # to prevent the last word from being lost. - if ($numWords > $wordLimit) - { - array_pop($words); + return preg_replace(array_keys($replacements), array_values($replacements), strip_tags($markdown)); } - $string = implode(' ', $words); - - if ($appendEllipsis && $numWords > $wordLimit) + protected function limitWords($string, $wordLimit, $appendEllipsis = false) { - $ellipsis = '…'; - $string .= $ellipsis; + $regexp = '/\s+/u'; + $words = preg_split($regexp, $string, $wordLimit + 1); + $numWords = count($words); + + # TBB: if there are $wordLimit words or less, this check is necessary + # to prevent the last word from being lost. + if ($numWords > $wordLimit) { + array_pop($words); + } + + $string = implode(' ', $words); + + if ($appendEllipsis && $numWords > $wordLimit) { + $ellipsis = '…'; + $string .= $ellipsis; + } + + return $string; } - return $string; - } - - public static function getSlugFromFilename($filename) - { - return strtolower(preg_replace('#^\d{1,3}\-#', '', basename(trim($filename), '.md'))); - } - - public static function collectMetadata(array $posts, $field) - { - $values = array_unique(array_map(function(Post $post) use($field) { - $metadata = $post->getMetadata(); - return $metadata[$field] ?? null; - }, $posts)); - sort($values); - return array_combine($values, $values); - } - - public static function getSlugMap($postType) - { - if (!isset(static::$slugMap[$postType])) + public static function getSlugFromFilename($filename) { - static::$slugMap[$postType] = []; - $files = glob(ContentActions::CONTENT_DIR . '/' . $postType . '/*.md'); - usort($files, 'strnatcasecmp'); - foreach($files as $file) - { - static::$slugMap[$postType][static::getSlugFromFilename($file)] = $file; - } + return strtolower(preg_replace('#^\d{1,3}\-#', '', basename(trim($filename), '.md'))); } - return static::$slugMap[$postType]; - } - public function getGithubEditUrl() - { - return 'https://github.com/lbryio/lbry.io/tree/master' . str_replace(ROOT_DIR, '', $this->path); - } + public static function collectMetadata(array $posts, $field) + { + $values = array_unique(array_map(function (Post $post) use ($field) { + $metadata = $post->getMetadata(); + return $metadata[$field] ?? null; + }, $posts)); + sort($values); + return array_combine($values, $values); + } + + public static function getSlugMap($postType) + { + if (!isset(static::$slugMap[$postType])) { + static::$slugMap[$postType] = []; + $files = glob(ContentActions::CONTENT_DIR . '/' . $postType . '/*.md'); + usort($files, 'strnatcasecmp'); + foreach ($files as $file) { + static::$slugMap[$postType][static::getSlugFromFilename($file)] = $file; + } + } + return static::$slugMap[$postType]; + } + + public function getGithubEditUrl() + { + return 'https://github.com/lbryio/lbry.io/tree/master' . str_replace(ROOT_DIR, '', $this->path); + } } diff --git a/update.php b/update.php index 24806682..9a3216cb 100755 --- a/update.php +++ b/update.php @@ -7,10 +7,9 @@ $options = getopt('f'); $force = isset($options['f']); // update even if no NEEDS_UPDATE file exists $needsUpdateFile = ROOT_DIR . '/data/writeable/NEEDS_UPDATE'; -if (!$force && !file_exists($needsUpdateFile)) -{ - echo "No update necessary\n"; - return; +if (!$force && !file_exists($needsUpdateFile)) { + echo "No update necessary\n"; + return; } @unlink($needsUpdateFile); diff --git a/view/Response.class.php b/view/Response.class.php index 95916112..9b4526ba 100644 --- a/view/Response.class.php +++ b/view/Response.class.php @@ -2,321 +2,302 @@ class Response { - const HEADER_STATUS = 'Status'; - const HEADER_LOCATION = 'Location'; + const HEADER_STATUS = 'Status'; + const HEADER_LOCATION = 'Location'; - const HEADER_CACHE_CONTROL = 'Cache-Control'; - const HEADER_LAST_MODIFIED = 'Last-Modified'; - const HEADER_ETAG = 'Etag'; + const HEADER_CACHE_CONTROL = 'Cache-Control'; + const HEADER_LAST_MODIFIED = 'Last-Modified'; + const HEADER_ETAG = 'Etag'; - const HEADER_CONTENT_TYPE = 'Content-Type'; - const HEADER_CONTENT_LENGTH = 'Content-Length'; - const HEADER_CONTENT_DISPOSITION = 'Content-Disposition'; - const HEADER_CONTENT_TYPE_OPTIONS = 'X-Content-Type-Options'; - const HEADER_CONTENT_ENCODING = 'Content-Encoding'; - const HEADER_CROSS_ORIGIN = 'Access-Control-Allow-Origin'; + const HEADER_CONTENT_TYPE = 'Content-Type'; + const HEADER_CONTENT_LENGTH = 'Content-Length'; + const HEADER_CONTENT_DISPOSITION = 'Content-Disposition'; + const HEADER_CONTENT_TYPE_OPTIONS = 'X-Content-Type-Options'; + const HEADER_CONTENT_ENCODING = 'Content-Encoding'; + const HEADER_CROSS_ORIGIN = 'Access-Control-Allow-Origin'; - protected static - $metaDescription = '', - $metaTitle = '', - $jsCalls = [], - $assets = [ + protected static $metaDescription = ''; + protected static $metaTitle = ''; + protected static $jsCalls = []; + protected static $assets = [ 'js' => [ '/js/jquery-3.3.1.min.js', '/js/global.js' ], 'css' => ['/css/all.css'] - ], - $headers = [], - $headersSent = false, - $content = '', - $contentSent = false, - $isHeadersOnly = false, - $gzipResponseContent = true, - $metaImages = [], - $facebookAnalyticsType = "PageView"; + ]; + protected static $headers = []; + protected static $headersSent = false; + protected static $content = ''; + protected static $contentSent = false; + protected static $isHeadersOnly = false; + protected static $gzipResponseContent = true; + protected static $metaImages = []; + protected static $facebookAnalyticsType = "PageView"; - public static function setMetaDescription($description) - { - static::$metaDescription = $description; - } - - public static function addMetaImage($url) - { - static::$metaImages[] = $url; - } - - public static function addMetaImages(array $urls) - { - foreach ($urls as $url) + public static function setMetaDescription($description) { - static::addMetaImage($url); + static::$metaDescription = $description; } - } - public static function getMetaDescription() - { - return static::$metaDescription ?: 'A Content Revolution'; - } - - public static function getMetaImages() - { - return static::$metaImages ?: [Request::getHostAndProto() . '/img/lbry-green-meta-1200x900.png']; - } - - public static function setMetaTitle($title) - { - static::$metaTitle = $title; - } - - public static function getMetaTitle() - { - return static::$metaTitle; - } - - public static function guessMetaTitle($content) - { - $title = ''; - preg_match_all('/]*>([^<]+) $headerValue) + public static function addMetaImage($url) { - if ($headerValue == '1' || !$title) - { - $title = $titleMatches[2][$matchIndex]; - if ($headerValue == '1') - { - return $title; + static::$metaImages[] = $url; + } + + public static function addMetaImages(array $urls) + { + foreach ($urls as $url) { + static::addMetaImage($url); } - } } - return $title; - } - public static function getJsCalls() - { - return static::$jsCalls; - } - - public static function jsOutputCallback($js) - { - static::$jsCalls[] = $js; - return ''; - } - - public static function addJsAsset($src) - { - static::$assets['js'][$src] = $src; - } - - public static function addCssAsset($src) - { - static::$assets['css'][$src] = $src; - } - - public static function getJsAssets() - { - return static::$assets['js']; - } - - public static function getCssAssets() - { - return static::$assets['css']; - } - - public static function setCssAssets(array $assets = []){ - static::$assets['css'] = $assets; - } - public static function setGzipResponseContent($gzip = true) - { - static::$gzipResponseContent = $gzip; - } - - public static function gzipContentIfNotDisabled() - { - if (static::$gzipResponseContent) + public static function getMetaDescription() { - $content = static::getContent(); - if (strlen($content) > 256) // not worth it for really short content - { - $compressed = gzencode($content, 1); - static::setContent($compressed); - static::setHeader(static::HEADER_CONTENT_LENGTH, strlen($compressed)); - static::setHeader(static::HEADER_CONTENT_ENCODING, 'gzip'); - } + return static::$metaDescription ?: 'A Content Revolution'; } - } - public static function send() - { - static::sendHeaders(); - static::sendContent(); - } - - public static function setContent(string $content) - { - static::$content = $content; - } - - public static function getContent(): string - { - return static::$content; - } - - public static function sendContent() - { - if (static::$contentSent) + public static function getMetaImages() { - throw new LogicException('Content has already been sent. It cannot be sent twice'); + return static::$metaImages ?: [Request::getHostAndProto() . '/img/lbry-green-meta-1200x900.png']; } - if (!static::$isHeadersOnly) + public static function setMetaTitle($title) { - echo static::$content; + static::$metaTitle = $title; } - static::$contentSent = true; - } + public static function getMetaTitle() + { + return static::$metaTitle; + } - public static function setIsHeadersOnly(bool $isHeadersOnly = true) - { - static::$isHeadersOnly = $isHeadersOnly; - } + public static function guessMetaTitle($content) + { + $title = ''; + preg_match_all('/]*>([^<]+) $headerValue) { + if ($headerValue == '1' || !$title) { + $title = $titleMatches[2][$matchIndex]; + if ($headerValue == '1') { + return $title; + } + } + } + return $title; + } - public static function setDownloadHttpHeaders($name, $type = null, $size = null, $noSniff = true) - { - static::setBinaryHttpHeaders($type, $size, $noSniff); - static::setHeader('Content-Disposition', 'attachment;filename=' . $name); - } + public static function getJsCalls() + { + return static::$jsCalls; + } - public static function setBinaryHttpHeaders($type, $size = null, $noSniff = true) - { - static::setGzipResponseContent(false); // in case its already compressed - static::setHeaders(array_filter([ + public static function jsOutputCallback($js) + { + static::$jsCalls[] = $js; + return ''; + } + + public static function addJsAsset($src) + { + static::$assets['js'][$src] = $src; + } + + public static function addCssAsset($src) + { + static::$assets['css'][$src] = $src; + } + + public static function getJsAssets() + { + return static::$assets['js']; + } + + public static function getCssAssets() + { + return static::$assets['css']; + } + + public static function setCssAssets(array $assets = []) + { + static::$assets['css'] = $assets; + } + public static function setGzipResponseContent($gzip = true) + { + static::$gzipResponseContent = $gzip; + } + + public static function gzipContentIfNotDisabled() + { + if (static::$gzipResponseContent) { + $content = static::getContent(); + if (strlen($content) > 256) { // not worth it for really short content + $compressed = gzencode($content, 1); + static::setContent($compressed); + static::setHeader(static::HEADER_CONTENT_LENGTH, strlen($compressed)); + static::setHeader(static::HEADER_CONTENT_ENCODING, 'gzip'); + } + } + } + + public static function send() + { + static::sendHeaders(); + static::sendContent(); + } + + public static function setContent(string $content) + { + static::$content = $content; + } + + public static function getContent(): string + { + return static::$content; + } + + public static function sendContent() + { + if (static::$contentSent) { + throw new LogicException('Content has already been sent. It cannot be sent twice'); + } + + if (!static::$isHeadersOnly) { + echo static::$content; + } + + static::$contentSent = true; + } + + public static function setIsHeadersOnly(bool $isHeadersOnly = true) + { + static::$isHeadersOnly = $isHeadersOnly; + } + + public static function setDownloadHttpHeaders($name, $type = null, $size = null, $noSniff = true) + { + static::setBinaryHttpHeaders($type, $size, $noSniff); + static::setHeader('Content-Disposition', 'attachment;filename=' . $name); + } + + public static function setBinaryHttpHeaders($type, $size = null, $noSniff = true) + { + static::setGzipResponseContent(false); // in case its already compressed + static::setHeaders(array_filter([ static::HEADER_CONTENT_TYPE => $type, static::HEADER_CONTENT_LENGTH => $size ?: null, static::HEADER_CONTENT_TYPE_OPTIONS => $noSniff ? 'nosniff' : null, ])); - } - - public static function setContentEtag() - { - static::setHeader(static::HEADER_ETAG, md5(static::getContent())); - } - - public static function enableHttpCache(int $seconds = 300) - { - static::addCacheControlHeader('max-age', $seconds); - static::setHeader('Pragma', 'public'); - } - - public static function addCacheControlHeader(string $name, $value = null) - { - $cacheControl = static::getHeader(static::HEADER_CACHE_CONTROL); - $currentHeaders = []; - if ($cacheControl) - { - foreach (preg_split('/\s*,\s*/', $cacheControl) as $tmp) - { - $tmp = explode('=', $tmp); - $currentHeaders[$tmp[0]] = $tmp[1] ?? null; - } - } - $currentHeaders[strtr(strtolower($name), '_', '-')] = $value; - - $headers = []; - foreach ($currentHeaders as $key => $currentVal) - { - $headers[] = $key . ($currentVal !== null ? '=' . $currentVal : ''); } - static::setHeader(static::HEADER_CACHE_CONTROL, implode(', ', $headers)); - } - - public static function setHeader($name, $value) - { - static::$headers[$name] = $value; - } - - public static function setHeaders($headers, $overwrite = true) - { - foreach ($headers as $name => $value) + public static function setContentEtag() { - if ($overwrite || !static::getHeader($name)) - { - static::setHeader($name, $value); - } + static::setHeader(static::HEADER_ETAG, md5(static::getContent())); } - } - public static function getHeader($name, $default = null) - { - return static::$headers[$name] ?? $default; - } + public static function enableHttpCache(int $seconds = 300) + { + static::addCacheControlHeader('max-age', $seconds); + static::setHeader('Pragma', 'public'); + } - public static function getHeaders(): array - { - return static::$headers; - } + public static function addCacheControlHeader(string $name, $value = null) + { + $cacheControl = static::getHeader(static::HEADER_CACHE_CONTROL); + $currentHeaders = []; + if ($cacheControl) { + foreach (preg_split('/\s*,\s*/', $cacheControl) as $tmp) { + $tmp = explode('=', $tmp); + $currentHeaders[$tmp[0]] = $tmp[1] ?? null; + } + } + $currentHeaders[strtr(strtolower($name), '_', '-')] = $value; - public static function setStatus($status) - { - static::setHeader(static::HEADER_STATUS, $status); - } + $headers = []; + foreach ($currentHeaders as $key => $currentVal) { + $headers[] = $key . ($currentVal !== null ? '=' . $currentVal : ''); + } - public static function setDefaultSecurityHeaders() - { - $defaultHeaders = [ + static::setHeader(static::HEADER_CACHE_CONTROL, implode(', ', $headers)); + } + + public static function setHeader($name, $value) + { + static::$headers[$name] = $value; + } + + public static function setHeaders($headers, $overwrite = true) + { + foreach ($headers as $name => $value) { + if ($overwrite || !static::getHeader($name)) { + static::setHeader($name, $value); + } + } + } + + public static function getHeader($name, $default = null) + { + return static::$headers[$name] ?? $default; + } + + public static function getHeaders(): array + { + return static::$headers; + } + + public static function setStatus($status) + { + static::setHeader(static::HEADER_STATUS, $status); + } + + public static function setDefaultSecurityHeaders() + { + $defaultHeaders = [ 'Content-Security-Policy' => "frame-ancestors 'none'", 'X-Frame-Options' => 'DENY', 'X-XSS-Protection' => '1', ]; - if (IS_PRODUCTION) - { - $defaultHeaders['Strict-Transport-Security'] = 'max-age=31536000'; + if (IS_PRODUCTION) { + $defaultHeaders['Strict-Transport-Security'] = 'max-age=31536000'; + } + + static::setHeaders($defaultHeaders, false); } - static::setHeaders($defaultHeaders, false); - } - - public static function sendHeaders() - { - if (static::$headersSent) + public static function sendHeaders() { - throw new LogicException('Headers have already been sent. They cannot be sent twice'); + if (static::$headersSent) { + throw new LogicException('Headers have already been sent. They cannot be sent twice'); + } + + if (!static::getHeader(static::HEADER_CONTENT_TYPE)) { + static::setHeader(static::HEADER_CONTENT_TYPE, 'text/html'); + } + + $headers = static::getHeaders(); + + if (isset($headers[static::HEADER_STATUS])) { + $status = 'HTTP/1.0 ' . $headers[static::HEADER_STATUS] . ' ' . static::getStatusTextForCode($headers[static::HEADER_STATUS]); + header($status); + + if (substr(php_sapi_name(), 0, 3) == 'cgi') { + // fastcgi servers cannot send this status information because it was sent by them already due to the HTT/1.0 line + // so we can safely unset them. see ticket #3191 + unset($headers[static::HEADER_STATUS]); + } + } + + foreach ($headers as $name => $value) { + header($name . ': ' . $value); + } + + static::$headersSent = true; } - if (!static::getHeader(static::HEADER_CONTENT_TYPE)) + public static function getStatusTextForCode($code) { - static::setHeader(static::HEADER_CONTENT_TYPE, 'text/html'); - } - - $headers = static::getHeaders(); - - if (isset($headers[static::HEADER_STATUS])) - { - $status = 'HTTP/1.0 ' . $headers[static::HEADER_STATUS] . ' ' . static::getStatusTextForCode($headers[static::HEADER_STATUS]); - header($status); - - if (substr(php_sapi_name(), 0, 3) == 'cgi') - { - // fastcgi servers cannot send this status information because it was sent by them already due to the HTT/1.0 line - // so we can safely unset them. see ticket #3191 - unset($headers[static::HEADER_STATUS]); - } - } - - foreach ($headers as $name => $value) - { - header($name . ': ' . $value); - } - - static::$headersSent = true; - } - - public static function getStatusTextForCode($code) - { - $statusTexts = [ + $statusTexts = [ '100' => 'Continue', '101' => 'Switching Protocols', '200' => 'OK', @@ -364,28 +345,32 @@ class Response '505' => 'HTTP Version Not Supported', ]; - return $statusTexts[$code] ?? null; - } + return $statusTexts[$code] ?? null; + } - public static function setFacebookPixelAnalyticsType($type){ - static::$facebookAnalyticsType = $type; - } + public static function setFacebookPixelAnalyticsType($type) + { + static::$facebookAnalyticsType = $type; + } - public static function getFacebookPixelAnalyticsType(){ - return static::$facebookAnalyticsType; - } + public static function getFacebookPixelAnalyticsType() + { + return static::$facebookAnalyticsType; + } - protected static function normalizeHeaderName($name): string - { - return preg_replace_callback( + protected static function normalizeHeaderName($name): string + { + return preg_replace_callback( '/\-(.)/', - function ($matches) { return '-' . strtoupper($matches[1]); }, + function ($matches) { + return '-' . strtoupper($matches[1]); + }, strtr(ucfirst(strtolower($name)), '_', '-') ); - } + } -// public static function addBodyCssClass($classOrClasses) + // public static function addBodyCssClass($classOrClasses) // { // static::$bodyCssClasses = array_unique(array_merge(static::$bodyCssClasses, (array)$classOrClasses)); // } diff --git a/view/View.class.php b/view/View.class.php index 2a595e9a..34c1f29f 100644 --- a/view/View.class.php +++ b/view/View.class.php @@ -2,195 +2,179 @@ function js_start() { - ob_start('Response::jsOutputCallback'); + ob_start('Response::jsOutputCallback'); } function js_end() { - ob_end_flush(); + ob_end_flush(); } class View { - const LAYOUT_PARAMS = '_layout_params'; + const LAYOUT_PARAMS = '_layout_params'; - const WEB_DIR = ROOT_DIR . '/web'; - const SCSS_DIR = self::WEB_DIR . '/scss'; - const CSS_DIR = self::WEB_DIR . '/css'; - const JS_DIR = self::WEB_DIR . '/js'; + const WEB_DIR = ROOT_DIR . '/web'; + const SCSS_DIR = self::WEB_DIR . '/scss'; + const CSS_DIR = self::WEB_DIR . '/css'; + const JS_DIR = self::WEB_DIR . '/js'; - public static function render($template, array $vars = []): string - { - if (static::isMarkdown($template)) + public static function render($template, array $vars = []): string { - return static::markdownToHtml(static::getFullPath($template)); + if (static::isMarkdown($template)) { + return static::markdownToHtml(static::getFullPath($template)); + } + + if (!static::exists($template) || substr_count($template, '/') !== 1) { + throw new InvalidArgumentException(sprintf('The template "%s" does not exist or is unreadable.', $template)); + } + + list($module, $view) = explode('/', $template); + + $isPartial = $view[0] === '_'; + $actionClass = ucfirst($module) . 'Actions'; + $method = 'prepare' . ucfirst(ltrim($view, '_')) . ($isPartial ? 'Partial' : ''); + + if (method_exists($actionClass, $method)) { + $vars = $actionClass::$method($vars); + } + + if ($vars === null) { + return []; + } + + return static::interpolateTokens(static::getTemplateSafely($template, $vars)); } - if (!static::exists($template) || substr_count($template, '/') !== 1) + /** + * This is its own function because we don't want in-scope variables to leak into the template + * + * @param string $___template + * @param array $___vars + * + * @return string + * @throws Throwable + */ + protected static function getTemplateSafely(string $___template, array $___vars): string { - throw new InvalidArgumentException(sprintf('The template "%s" does not exist or is unreadable.', $template)); + extract($___vars); + ob_start(); + ob_implicit_flush(0); + + try { + require(static::getFullPath($___template)); + return ob_get_clean(); + } catch (Throwable $e) { + // need to end output buffering before throwing the exception + ob_end_clean(); + throw $e; + } } - list($module, $view) = explode('/', $template); - - $isPartial = $view[0] === '_'; - $actionClass = ucfirst($module) . 'Actions'; - $method = 'prepare' . ucfirst(ltrim($view, '_')) . ($isPartial ? 'Partial' : ''); - - if (method_exists($actionClass, $method)) + public static function markdownToHtml($path): string { - $vars = $actionClass::$method($vars); + return ParsedownExtra::instance()->text(trim(file_get_contents($path))); } - if ($vars === null) + public static function exists($template): bool { - return []; + return is_readable(static::getFullPath($template)); } - return static::interpolateTokens(static::getTemplateSafely($template, $vars)); - } - - /** - * This is its own function because we don't want in-scope variables to leak into the template - * - * @param string $___template - * @param array $___vars - * - * @return string - * @throws Throwable - */ - protected static function getTemplateSafely(string $___template, array $___vars): string - { - extract($___vars); - ob_start(); - ob_implicit_flush(0); - - try + protected static function isMarkdown($nameOrPath): bool { - require(static::getFullPath($___template)); - return ob_get_clean(); - } - catch (Throwable $e) - { - // need to end output buffering before throwing the exception - ob_end_clean(); - throw $e; - } - } - - public static function markdownToHtml($path): string - { - return ParsedownExtra::instance()->text(trim(file_get_contents($path))); - } - - public static function exists($template): bool - { - return is_readable(static::getFullPath($template)); - } - - protected static function isMarkdown($nameOrPath): bool - { - return strlen($nameOrPath) > 3 && substr($nameOrPath, -3) == '.md'; - } - - protected static function getFullPath($template): string - { - if ($template && $template[0] == '/') - { - return $template; + return strlen($nameOrPath) > 3 && substr($nameOrPath, -3) == '.md'; } - if (static::isMarkdown($template)) + protected static function getFullPath($template): string { - return ContentActions::CONTENT_DIR . '/' . $template; + if ($template && $template[0] == '/') { + return $template; + } + + if (static::isMarkdown($template)) { + return ContentActions::CONTENT_DIR . '/' . $template; + } + + return ROOT_DIR . '/view/template/' . $template . '.php'; } - return ROOT_DIR . '/view/template/' . $template . '.php'; - } - - public static function imagePath($image): string - { - return '/img/' . $image; - } - - public static function parseMarkdown($template): array - { - $path = static::getFullPath($template); - list($ignored, $frontMatter, $markdown) = explode('---', file_get_contents($path), 3); - $metadata = Spyc::YAMLLoadString(trim($frontMatter)); - $html = ParsedownExtra::instance()->text(trim($markdown)); - return [$metadata, $html]; - } - - public static function compileCss() - { - $scssCompiler = new \Leafo\ScssPhp\Compiler(); - - $scssCompiler->setImportPaths([self::SCSS_DIR]); - - $compress = true; - if ($compress) + public static function imagePath($image): string { - $scssCompiler->setFormatter('Leafo\ScssPhp\Formatter\Crunched'); - } - else - { - $scssCompiler->setFormatter('Leafo\ScssPhp\Formatter\Expanded'); - $scssCompiler->setLineNumberStyle(Leafo\ScssPhp\Compiler::LINE_COMMENTS); + return '/img/' . $image; } - $all_css = $scssCompiler->compile(file_get_contents(self::SCSS_DIR . '/all.scss')); - file_put_contents(self::CSS_DIR . '/all.css', $all_css); - - $youtube_css = $scssCompiler->compile(file_get_contents(self::SCSS_DIR . '/youtube.scss')); - file_put_contents(self::CSS_DIR . '/youtube.css', $youtube_css); - } - - public static function gzipAssets() - { - foreach ([self::CSS_DIR => 'css', self::JS_DIR => 'js'] as $dir => $ext) + public static function parseMarkdown($template): array { - foreach (glob("$dir/*.$ext") as $file) - { - Gzip::compressFile($file); - } + $path = static::getFullPath($template); + list($ignored, $frontMatter, $markdown) = explode('---', file_get_contents($path), 3); + $metadata = Spyc::YAMLLoadString(trim($frontMatter)); + $html = ParsedownExtra::instance()->text(trim($markdown)); + return [$metadata, $html]; } - } - protected static function interpolateTokens($html): string - { - return preg_replace_callback('/{{[\w\.]+}}/is', function ($m) + public static function compileCss() { - return i18n::translate(trim($m[0], '}{')); - }, $html); - } + $scssCompiler = new \Leafo\ScssPhp\Compiler(); - protected static function escapeOnce($value): string - { - return preg_replace('/&([a-z]+|(#\d+)|(#x[\da-f]+));/i', '&$1;', htmlspecialchars((string)$value, ENT_QUOTES, 'utf-8')); - } + $scssCompiler->setImportPaths([self::SCSS_DIR]); - protected static function attributesToHtml($attributes): string - { - return implode('', array_map(function ($k, $v) + $compress = true; + if ($compress) { + $scssCompiler->setFormatter('Leafo\ScssPhp\Formatter\Crunched'); + } else { + $scssCompiler->setFormatter('Leafo\ScssPhp\Formatter\Expanded'); + $scssCompiler->setLineNumberStyle(Leafo\ScssPhp\Compiler::LINE_COMMENTS); + } + + $all_css = $scssCompiler->compile(file_get_contents(self::SCSS_DIR . '/all.scss')); + file_put_contents(self::CSS_DIR . '/all.css', $all_css); + + $youtube_css = $scssCompiler->compile(file_get_contents(self::SCSS_DIR . '/youtube.scss')); + file_put_contents(self::CSS_DIR . '/youtube.css', $youtube_css); + } + + public static function gzipAssets() { - return $v === true ? " $k" : ($v === false || $v === null || ($v === '' && $k != 'value') ? '' : sprintf(' %s="%s"', $k, static::escapeOnce($v))); - }, array_keys($attributes), array_values($attributes))); - } + foreach ([self::CSS_DIR => 'css', self::JS_DIR => 'js'] as $dir => $ext) { + foreach (glob("$dir/*.$ext") as $file) { + Gzip::compressFile($file); + } + } + } - public static function renderTag($tag, $attributes = []): string - { - return $tag ? sprintf('<%s%s />', $tag, static::attributesToHtml($attributes)) : ''; - } + protected static function interpolateTokens($html): string + { + return preg_replace_callback('/{{[\w\.]+}}/is', function ($m) { + return i18n::translate(trim($m[0], '}{')); + }, $html); + } - public static function renderContentTag($tag, $content = null, $attributes = []): string - { - return $tag ? sprintf('<%s%s>%s', $tag, static::attributesToHtml($attributes), $content, $tag) : ''; - } + protected static function escapeOnce($value): string + { + return preg_replace('/&([a-z]+|(#\d+)|(#x[\da-f]+));/i', '&$1;', htmlspecialchars((string)$value, ENT_QUOTES, 'utf-8')); + } - public static function renderJson($data): array - { - Response::setHeader(Response::HEADER_CONTENT_TYPE, 'application/json'); - return ['internal/json', ['json' => $data, '_no_layout' => true]]; - } -} \ No newline at end of file + protected static function attributesToHtml($attributes): string + { + return implode('', array_map(function ($k, $v) { + return $v === true ? " $k" : ($v === false || $v === null || ($v === '' && $k != 'value') ? '' : sprintf(' %s="%s"', $k, static::escapeOnce($v))); + }, array_keys($attributes), array_values($attributes))); + } + + public static function renderTag($tag, $attributes = []): string + { + return $tag ? sprintf('<%s%s />', $tag, static::attributesToHtml($attributes)) : ''; + } + + public static function renderContentTag($tag, $content = null, $attributes = []): string + { + return $tag ? sprintf('<%s%s>%s', $tag, static::attributesToHtml($attributes), $content, $tag) : ''; + } + + public static function renderJson($data): array + { + Response::setHeader(Response::HEADER_CONTENT_TYPE, 'application/json'); + return ['internal/json', ['json' => $data, '_no_layout' => true]]; + } +} diff --git a/view/template/acquisition/youtube_edit.php b/view/template/acquisition/youtube_edit.php index 74e26a40..a85fbb2d 100644 --- a/view/template/acquisition/youtube_edit.php +++ b/view/template/acquisition/youtube_edit.php @@ -5,7 +5,7 @@ $email = Request::encodeStringFromUser($_POST['new_email']); $sync_consent = isset($_POST['sync_consent']); -if(!preg_match("/@[A-Za-z0-9_-]+$/", $channel_name)){ +if (!preg_match("/@[A-Za-z0-9_-]+$/", $channel_name)) { $channel_name = "@" . $channel_name; } diff --git a/view/template/acquisition/youtube_status.php b/view/template/acquisition/youtube_status.php index bdfe26b0..e67fe638 100644 --- a/view/template/acquisition/youtube_status.php +++ b/view/template/acquisition/youtube_status.php @@ -104,7 +104,7 @@ endif;?>
- > + >
@@ -114,7 +114,7 @@
diff --git a/view/template/acquisition/youtube_token.php b/view/template/acquisition/youtube_token.php index 78bc9287..a1da4d37 100644 --- a/view/template/acquisition/youtube_token.php +++ b/view/template/acquisition/youtube_token.php @@ -1,10 +1,8 @@ @@ -41,12 +41,12 @@
- + getMetadata() ?>
" class="link-primary"> - + diff --git a/view/template/content/_jobs.php b/view/template/content/_jobs.php index ddba2568..1e88b73f 100644 --- a/view/template/content/_jobs.php +++ b/view/template/content/_jobs.php @@ -1,5 +1,5 @@
- + $job[0], 'jobHtml' => $job[1] diff --git a/view/template/content/_postList.php b/view/template/content/_postList.php index f723d56c..4b96b63c 100644 --- a/view/template/content/_postList.php +++ b/view/template/content/_postList.php @@ -1,7 +1,7 @@

Latest News

diff --git a/view/template/content/_postSocialShare.php b/view/template/content/_postSocialShare.php index 07baa08a..c7294a32 100644 --- a/view/template/content/_postSocialShare.php +++ b/view/template/content/_postSocialShare.php @@ -14,7 +14,7 @@ - getContentText(50,true)) ?>" + getContentText(50, true)) ?>" title="Email a Friend"> diff --git a/view/template/content/credit-reports.php b/view/template/content/credit-reports.php index c21b0081..703d333f 100644 --- a/view/template/content/credit-reports.php +++ b/view/template/content/credit-reports.php @@ -15,7 +15,7 @@ - + getSlug()) ?> Report diff --git a/view/template/content/faq.php b/view/template/content/faq.php index 19fc4262..4e257b1d 100644 --- a/view/template/content/faq.php +++ b/view/template/content/faq.php @@ -18,10 +18,10 @@ $('#faq-filter-form').change(function() { $(this).submit(); }); - $posts): ?> + $posts): ?>

- + diff --git a/view/template/content/news.php b/view/template/content/news.php index 100e7b04..ff247462 100644 --- a/view/template/content/news.php +++ b/view/template/content/news.php @@ -10,7 +10,7 @@
- +

getTitle() ?>

diff --git a/view/template/content/roadmap.php b/view/template/content/roadmap.php index 5cc3dbc7..16bc87c2 100644 --- a/view/template/content/roadmap.php +++ b/view/template/content/roadmap.php @@ -26,7 +26,7 @@
- $groupItems): ?> + $groupItems): ?>

"> @@ -40,7 +40,7 @@ $maxItems): ?> - +
> @@ -49,7 +49,7 @@ - +

diff --git a/view/template/content/rss.php b/view/template/content/rss.php index 477ec5ef..d4a95a22 100644 --- a/view/template/content/rss.php +++ b/view/template/content/rss.php @@ -6,7 +6,7 @@ {{rss.description}} https://github.com/lbryio/lbry.io {{rss.lang}} - Sat, 07 Sep 2002 09:42:31 GMT ?> + Sat, 07 Sep 2002 09:42:31 GMT?> diff --git a/view/template/developer/quickstart.php b/view/template/developer/quickstart.php index 5f1ace82..21c47e2e 100644 --- a/view/template/developer/quickstart.php +++ b/view/template/developer/quickstart.php @@ -11,7 +11,7 @@

Quickstart

- +
@@ -24,7 +24,7 @@
    - $stepLabel): ?> + $stepLabel): ?>
  1. diff --git a/view/template/download/_list.php b/view/template/download/_list.php index 8be30acb..ae656726 100644 --- a/view/template/download/_list.php +++ b/view/template/download/_list.php @@ -4,7 +4,7 @@ - +

    @@ -14,7 +14,7 @@

    - +
    ', $bucketRow) ?>
    diff --git a/view/template/email_templates/_confirmHash.php b/view/template/email_templates/_confirmHash.php index 1750b9ea..c9b45091 100644 --- a/view/template/email_templates/_confirmHash.php +++ b/view/template/email_templates/_confirmHash.php @@ -13,4 +13,4 @@ - ob_get_clean()]); \ No newline at end of file + ob_get_clean()]); diff --git a/view/template/form/_select.php b/view/template/form/_select.php index dc3193a7..b4829d17 100644 --- a/view/template/form/_select.php +++ b/view/template/form/_select.php @@ -1,5 +1,5 @@