diff --git a/components/footer.php b/components/footer.php deleted file mode 100644 index 3f75eec..0000000 --- a/components/footer.php +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - diff --git a/makai.php b/makai.php index 0c5a50c..cb95964 100644 --- a/makai.php +++ b/makai.php @@ -18,6 +18,7 @@ define('MKI_DIR_LIB', MKI_ROOT . '/lib'); define('MKI_DIR_PUB', MKI_ROOT . '/public'); define('MKI_DIR_PAGES', MKI_ROOT . '/pages'); define('MKI_DIR_CONFIG', MKI_ROOT . '/config'); +define('MKI_DIR_TEMPLATES', MKI_ROOT . '/tpl'); require_once MKI_DIR_LIB . '/index-new/index.php'; @@ -47,3 +48,5 @@ if(!empty($dbConfig)) if(empty($db)) $db = (new NullDbBackend)->createConnection(new NullDbConnectionInfo); + +$ctx = new MakaiContext($db); diff --git a/pages/contact.php b/pages/contact.php deleted file mode 100644 index f06996b..0000000 --- a/pages/contact.php +++ /dev/null @@ -1,50 +0,0 @@ -get('/contact.php', mkiRedirect('/contact')); -$router->get('/contact.html', mkiRedirect('/contact')); -$router->get('/nintendo', mkiRedirect('/contact')); -$router->get('/nintendo.php', mkiRedirect('/contact')); - -$router->get('/contact', function() use ($db) { - $contacts = (new Contacts($db))->getAll(); - - $body = fm_component('header', [ - 'title' => 'flash.moe / contact', - ]); - - $body .= << -
-
-

Contacts

-
- -
-HTML; - - foreach($contacts as $contact) { - $body .= '
'; - - if($contact->hasLink()) { - $body .= ''; - } else { - $body .= ''; - } - - $body .= <<
-
- - -
-
-HTML; - } - - $body .= ''; - - $body .= fm_component('footer'); - - return $body; -}); diff --git a/pages/index.php b/pages/index.php deleted file mode 100644 index 0dd1a50..0000000 --- a/pages/index.php +++ /dev/null @@ -1,270 +0,0 @@ -get('/about', mkiRedirect('/')); -$router->get('/about.html', mkiRedirect('/')); -$router->get('/about.php', mkiRedirect('/')); -$router->get('/index.php', mkiRedirect('/')); -$router->get('/index.html', mkiRedirect('/')); -$router->get('/related.php', mkiRedirect('/')); -$router->get('/related.html', mkiRedirect('/')); -$router->get('/friends', mkiRedirect('/')); -$router->get('/friends.php', mkiRedirect('/')); -$router->get('/friends.html', mkiRedirect('/')); -$router->get('/related', mkiRedirect('/')); -$router->get('/etc.php', mkiRedirect('/')); -$router->get('/etc.html', mkiRedirect('/')); -$router->get('/etcetera', mkiRedirect('/')); -$router->get('/etcetera.html', mkiRedirect('/')); -$router->get('/etcetera.php', mkiRedirect('/')); -$router->get('/misc', mkiRedirect('/')); -$router->get('/misc.html', mkiRedirect('/')); -$router->get('/misc.php', mkiRedirect('/')); -$router->get('/etc', mkiRedirect('/')); -$router->get('/365', mkiRedirect('/')); -$router->get('/donate', mkiRedirect('/')); -$router->get('/blog.php', mkiRedirect('/')); -$router->get('/blog-post.php', mkiRedirect('/')); -$router->get('/blog/:id', mkiRedirect('/')); -$router->get('/old-blog', mkiRedirect('/')); -$router->get('/old-blog/:id', mkiRedirect('/')); - -$router->get('/header-bgs.json', function() { - return json_encode(FM_BGS); -}); - -$router->get('/now-listening', function() { - $offset = (int)filter_input(INPUT_GET, 'offset', FILTER_SANITIZE_NUMBER_INT); - - $body = fm_component('header', [ - 'title' => 'flash.moe / now listening', - 'do_fullscreen_header' => true, - 'is_now_playing' => true, - 'offset' => $offset, - ]); - - $body .= fm_component('footer', [ - 'hide' => true, - 'onload' => [ - ['fm.initIndex', 10], - ], - ]); - - return $body; -}); - -$router->get('/now-listening.json', function() { - $lfmInfo = cache_output('lastfm', 10, function() { - return json_decode(file_get_contents('https://now.flash.moe/get.php?u=flashwave_')); - }); - - if(empty($lfmInfo[0]->name)) - return []; - - $lfmInfo = $lfmInfo[0]; - - return [ - 'name' => strval($lfmInfo->name), - 'now_playing' => !empty($lfmInfo->nowplaying), - 'url' => strval($lfmInfo->url), - 'cover' => !empty($lfmInfo->images->large) ? strval($lfmInfo->images->large) : '', - 'artist' => [ - 'name' => !empty($lfmInfo->artist->name) ? strval($lfmInfo->artist->name) : '', - 'url' => explode('/_/', strval($lfmInfo->url))[0], - ], - ]; -}); - -$router->get('/np.php', function() { - header('Content-Type: text/xml'); - return cache_output('lastfm-xml', 10, function() { - return file_get_contents('https://now.flash.moe/get.php?u=flashwave_&f=xml'); - }); -}); - -$router->get('/home', function() { - $body = fm_component('header', [ - 'title' => 'flash.moe / homepage', - 'do_fullscreen_header' => true, - ]); - - $body .= << -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
--
-
:
-
--
-
-
- Week - -- -  —  - ----------- -
-
-
- - -HTML; - - $body .= fm_component('footer', [ - 'hide' => true, - 'skip_analytics' => true, - 'onload' => [ - ['fm.initClock'], - ['fm.initIndex', 10], - ], - ]); - - return $body; -}); - -$router->get('/', function() use ($db) { - $legacyPage = (string)filter_input(INPUT_GET, 'p'); - if(!empty($legacyPage)) { - $legacyPages = [ - 'projects' => '/projects', - 'contact' => '/contact', - 'about' => '/', - 'etc' => '/etc', - 'hosted' => '/etc', - 'friends' => '/related', - ]; - - if(isset($legacyPages[$legacyPage])) { - header('Location: ' . $legacyPages[$legacyPage]); - return 302; - } - } - - $projects = (new Projects($db))->getFeatured(); - $languages = new Languages($db); - $contacts = (new Contacts($db))->getHomePage(); - - $body = fm_component('header', [ - 'title' => 'flash.moe', - 'is_index' => true, - ]); - - $body .= '
'; - - for($i = 1; $i < count(FM_NAV); ++$i) { - $link = FM_NAV[$i]; - $body .= " - '; - } - - $body .= << -
-
- -
Contact
-
More
-
-
-HTML; - - foreach($contacts as $contact) { - $body .= "
getName()}\" style=\"--social-colour: {$contact->getColourHex()}\">"; - - if($contact->hasLink()) { - $body .= "getLink()}\" class=\"social-background\" target=\"_blank\" rel=\"noopener\">"; - } else { - $body .= "
"; - } - - $body .= <<
- -
-HTML; - } - - $body .= '
'; - - $body .= fm_component('footer', [ - 'is_index' => true, - 'onload' => [ - ['fm.initIndex'], - ], - ]); - - return $body; -}); diff --git a/pages/projects.php b/pages/projects.php deleted file mode 100644 index c18c8f7..0000000 --- a/pages/projects.php +++ /dev/null @@ -1,126 +0,0 @@ -get('/projects.php', mkiRedirect('/projects')); -$router->get('/projects.html', mkiRedirect('/projects')); -$router->get('/utilities', mkiRedirect('/projects')); -$router->get('/utilities.php', mkiRedirect('/projects')); -$router->get('/utilities.html', mkiRedirect('/projects')); - -$router->get('/projects', function() use ($db) { - $projects = (new Projects($db))->getAll(); - $languages = new Languages($db); - - $sections = [ - 'projects' => [ - 'title' => 'Active Projects', - 'desc' => 'Projects that I work on on a fairly regular basis.', - 'items' => [], - ], - 'tools' => [ - 'title' => 'Tools', - 'desc' => 'Personal quality of life tools that I update when I need something new from them.', - 'items' => [], - ], - 'archives' => [ - 'title' => 'Archived Projects', - 'desc' => 'Past projects that I no longer work on.', - 'items' => [], - ], - ]; - - foreach($projects as $project) { - if($project->isArchived()) - $sections['archives']['items'][] = $project; - else { - if($project->isTool()) - $sections['tools']['items'][] = $project; - else - $sections['projects']['items'][] = $project; - } - } - - $body = fm_component('header', [ - 'title' => 'flash.moe / projects', - ]); - - foreach($sections as $sectionId => $section) { - $body .= << -
-
-

{$section['title']}

-

{$section['desc']}

-
- -HTML; - - foreach($section['items'] as $project) { - $links = []; - if($project->hasHomePageUrl()) - $links[] = ['class' => 'homepage', 'text' => 'Homepage', 'url' => $project->getHomePageUrl()]; - if($project->hasSourceUrl()) - $links[] = ['class' => 'repository', 'text' => 'Source', 'url' => $project->getSourceUrl()]; - if($project->hasDiscussionUrl()) - $links[] = ['class' => 'forum', 'text' => 'Discussion', 'url' => $project->getDiscussionUrl()]; - - $descLines = $project->hasDescription() ? $project->getDescription()->trim()->split("\n") : []; - - $langs = $languages->getByProject($project); - - if($project->hasColour()) - $colour = $project->getColour(); - else - foreach($langs as $lang) - if($lang->hasColour()) { - $colour = $lang->getColour(); - break; - } - - $colour = str_pad(dechex($colour), 6, '0', STR_PAD_LEFT); - - $body .= << -
-
-

{$project->getName()}
-HTML; - - foreach($langs as $lang) { - $langColour = str_pad(dechex($lang->getColour() ?? 0), 6, '0', STR_PAD_LEFT); - $body .= "
{$lang->getName()}
"; - } - - $body .= '

'; - - if($project->hasSummary()) - $body .= "

{$project->getSummary()}

"; - - foreach($descLines as $line) { - $line = $line->trim(); - - if($line->isEmpty()) - continue; - - $body .= "

{$line}

"; - } - - $body .= '
'; - - if(!empty($links)) { - $body .= ''; - } - - $body .= '
'; - } - } - - $body .= fm_component('footer'); - - return $body; -}); diff --git a/pages/ssh.php b/pages/ssh.php deleted file mode 100644 index 35f4b6a..0000000 --- a/pages/ssh.php +++ /dev/null @@ -1,92 +0,0 @@ -get('/ssh.php', function() { - $query = ''; - - $minLevel = (int)filter_input(INPUT_GET, 'l', FILTER_SANITIZE_NUMBER_INT); - if($minLevel > 0) - $query .= sprintf('l=%d&', $minLevel); - - if(!empty($_GET['c'])) - $query .= 'c=1&'; - - if(!empty($_GET['j'])) - $query .= 'j=1&'; - - if($query !== '') - $query = '?' . substr($query, 0, -1); - - header('Location: /ssh_keys' . $query); - return 302; -}); - -$router->get('/ssh_keys', function() use ($db) { - $minLevel = (int)filter_input(INPUT_GET, 'l', FILTER_SANITIZE_NUMBER_INT); - $includeComment = !empty($_GET['c']); - $json = !empty($_GET['j']); - - $keys = (new SSHKeys($db))->getKeys($minLevel); - - if($json) { - $items = []; - - foreach($keys as $key) { - $items[] = $item = new \stdClass; - $item->algo = $key->getAlgorithm(); - $item->key = $key->getBody(); - if($includeComment) { - $item->comment = (string)$key->getComment(); - $item->created = $key->getCreatedAt()->format(\DateTime::ATOM); - $item->level = $key->getLevel(); - } - } - - return $items; - } - - header('Content-Type: text/plain; charset=us-ascii'); - - $body = ''; - - foreach($keys as $key) - $body .= $key->toString($includeComment) . "\n"; - - return $body; -}); - -$router->get('/authorized_keys', function() use ($db) { - $keys = (new SSHKeys($db))->getKeys(500); - - header('Content-Type: text/plain; charset=us-ascii'); - - $body = ''; - foreach($keys as $key) - $body .= $key->toString(true) . "\n"; - - return $body; -}); - -$router->get('/git_keys_ro', function() use ($db) { - $keys = (new SSHKeys($db))->getKeys(100); - - header('Content-Type: text/plain; charset=us-ascii'); - - $body = ''; - foreach($keys as $key) - $body .= $key->toString(false) . "\n"; - - return $body; -}); - -$router->get('/git_keys_rw', function() use ($db) { - $keys = (new SSHKeys($db))->getKeys(200); - - header('Content-Type: text/plain; charset=us-ascii'); - - $body = ''; - foreach($keys as $key) - $body .= $key->toString(false) . "\n"; - - return $body; -}); diff --git a/public/index.php b/public/index.php index 999f2aa..5c491ab 100644 --- a/public/index.php +++ b/public/index.php @@ -1,204 +1,268 @@ 'Home', 'link' => '/'], - ['title' => 'Projects', 'link' => '/projects'], - ['title' => 'Contact', 'link' => '/contact'], +define('MKI_REDIRS', [ + '/about' => '/', + '/about.html' => '/', + '/about.php' => '/', + '/index.php' => '/', + '/index.html' => '/', + '/related.php' => '/', + '/related.html' => '/', + '/friends' => '/', + '/friends.php' => '/', + '/friends.html' => '/', + '/related' => '/', + '/etc.php' => '/', + '/etc.html' => '/', + '/etcetera' => '/', + '/etcetera.html' => '/', + '/etcetera.php' => '/', + '/misc' => '/', + '/misc.html' => '/', + '/misc.php' => '/', + '/etc' => '/', + '/365' => '/', + '/donate' => '/', + '/blog.php' => '/', + '/blog-post.php' => '/', + '/blog/:id' => '/', + '/old-blog' => '/', + '/old-blog/:id' => '/', + '/projects.php' => '/projects', + '/projects.html' => '/projects', + '/utilities' => '/projects', + '/utilities.php' => '/projects', + '/utilities.html' => '/projects', + '/contact.php' => '/contact', + '/contact.html' => '/contact', + '/nintendo' => '/contact', + '/nintendo.php' => '/contact', ]); -define('FM_BGS', [ - '/assets/headers/krk-000.jpg', '/assets/headers/krk-001.jpg', '/assets/headers/krk-002.jpg', - '/assets/headers/krk-003.jpg', '/assets/headers/krk-004.jpg', '/assets/headers/krk-005.jpg', - '/assets/headers/krk-006.jpg', '/assets/headers/krk-007.jpg', '/assets/headers/krk-008.jpg', +$router = new HttpFx; - '/assets/headers/mkt-000.jpg', '/assets/headers/mkt-001.jpg', '/assets/headers/mkt-002.jpg', - '/assets/headers/mkt-003.jpg', '/assets/headers/mkt-004.jpg', '/assets/headers/mkt-005.jpg', - '/assets/headers/mkt-006.jpg', '/assets/headers/mkt-007.jpg', '/assets/headers/mkt-008.jpg', - '/assets/headers/mkt-009.jpg', '/assets/headers/mkt-010.jpg', '/assets/headers/mkt-011.jpg', - '/assets/headers/mkt-012.jpg', '/assets/headers/mkt-013.jpg', '/assets/headers/mkt-014.jpg', - '/assets/headers/mkt-015.jpg', '/assets/headers/mkt-016.jpg', '/assets/headers/mkt-017.jpg', - '/assets/headers/mkt-018.jpg', '/assets/headers/mkt-019.jpg', '/assets/headers/mkt-020.jpg', - '/assets/headers/mkt-021.jpg', '/assets/headers/mkt-022.jpg', '/assets/headers/mkt-023.jpg', - '/assets/headers/mkt-024.jpg', '/assets/headers/mkt-025.jpg', '/assets/headers/mkt-026.jpg', - '/assets/headers/mkt-027.jpg', '/assets/headers/mkt-028.jpg', '/assets/headers/mkt-029.jpg', - '/assets/headers/mkt-030.jpg', '/assets/headers/mkt-031.jpg', '/assets/headers/mkt-032.jpg', - '/assets/headers/mkt-033.jpg', '/assets/headers/mkt-034.jpg', '/assets/headers/mkt-035.jpg', - '/assets/headers/mkt-036.jpg', '/assets/headers/mkt-037.jpg', '/assets/headers/mkt-038.jpg', - '/assets/headers/mkt-039.jpg', '/assets/headers/mkt-040.jpg', '/assets/headers/mkt-041.jpg', - '/assets/headers/mkt-042.jpg', '/assets/headers/mkt-043.jpg', '/assets/headers/mkt-044.jpg', - '/assets/headers/mkt-045.jpg', '/assets/headers/mkt-046.jpg', '/assets/headers/mkt-047.jpg', - '/assets/headers/mkt-048.jpg', '/assets/headers/mkt-049.jpg', '/assets/headers/mkt-050.jpg', - '/assets/headers/mkt-051.jpg', '/assets/headers/mkt-052.jpg', '/assets/headers/mkt-053.jpg', - '/assets/headers/mkt-054.jpg', '/assets/headers/mkt-055.jpg', '/assets/headers/mkt-056.jpg', - '/assets/headers/mkt-057.jpg', '/assets/headers/mkt-058.jpg', '/assets/headers/mkt-059.jpg', - '/assets/headers/mkt-060.jpg', '/assets/headers/mkt-061.jpg', '/assets/headers/mkt-062.jpg', - '/assets/headers/mkt-063.jpg', -]); - -define('FM_FEET', [ - 'if it ain\'t broke, i\'ll break it', -]); - -define('FM_ERRS' , [ - 403 => [ - 'code' => 403, - 'title' => 'Access Denied', - 'image' => '/assets/errors/403.jpg', - 'desc' => 'You are not supposed to be here.', - ], - 404 => [ - 'code' => 404, - 'title' => 'Not Found', - 'image' => '/assets/errors/404.jpg', - 'desc' => 'Whatever you\'re looking for is no longer here, or might not have been here in the first place.', - ], - 405 => [ - 'code' => 405, - 'title' => 'Method Not Supported', - 'image' => '/assets/errors/405.jpg', - 'desc' => 'You\'re up to something, aren\'t you?', - ], -]); - -function fm_component(string $_component_name, array $vars = []): string { - extract($vars); - - ob_start(); - require __DIR__ . '/../components/' . $_component_name . '.php'; - $meow = ob_get_contents(); - ob_clean(); - - return $meow; -} - -function first_paragraph(string $text, string $delimiter = "\n"): string { - $index = mb_strpos($text, $delimiter); - return $index === false ? $text : mb_substr($text, 0, $index); -} - -function time_elapsed(int $since, bool $full = false): string { - $now = new DateTime; - $since = new DateTime(date('c', $since)); - $diff = $now->diff($since); - - $diff->w = floor($diff->d / 7); - $diff->d -= $diff->w * 7; - - $string = [ - 'y' => 'year', - 'm' => 'month', - 'w' => 'week', - 'd' => 'day', - 'h' => 'hour', - 'i' => 'minute', - 's' => 'second', - ]; - - foreach($string as $key => &$value) { - if($diff->{$key}) - $value = $diff->{$key} . ' ' . $value . ($diff->{$key} > 1 ? 's' : ''); - else - unset($string[$key]); - } - - if(!$full) - $string = array_slice($string, 0, 1); - return $string ? implode(', ', $string) . ' ago' : 'just now'; -} - -function cache_output(string $name, int $lifetime, callable $callable) { - $path = sys_get_temp_dir() . '/fm-' . $name . '.cache'; - if(!is_file($path) || (filemtime($path) + $lifetime) < time()) - file_put_contents($path, serialize($callable())); - return unserialize(file_get_contents($path)); -} - -$reqMethod = (string)filter_input(INPUT_SERVER, 'REQUEST_METHOD'); -$reqPath = '/' . trim(parse_url((string)filter_input(INPUT_SERVER, 'REQUEST_URI'), PHP_URL_PATH), '/'); -$reqHead = false; - -if($reqMethod == 'HEAD') { - $reqMethod = 'GET'; - $reqHead = true; -} - -$router = new Router; - -$router->get('/error/:code', function(string $code) use ($reqHead) { - $errorInfo = FM_ERRS[$code ?? 404] ?? FM_ERRS[404]; - http_response_code($errorInfo['code']); - - if(!$reqHead) { - $body = fm_component('header', ['title' => $errorInfo['title']]); - - $body .= << -

{$errorInfo['title']}

- {$errorInfo['image']} -
{$errorInfo['desc']}
- -HTML; - - $body .= fm_component('footer'); - - return $body; - } +$router->setDefaultErrorHandler(function($response, $request, $code, $text) use ($ctx) { + $path = 'errors/' . $code; + $tpl = $ctx->getTemplating(); + $response->setContent($tpl->render($tpl->exists($path) ? $path : 'errors/master', [ + 'http_error_code' => $code, + 'http_error_title' => $text, + ])); }); -function mkiRedirect(string $to): callable { - return function() use ($to) { - header('Location: ' . $to); - return 302; - }; -} +$router->get('/error/:code', function($response, $request, $code) { + return intval($code); +}); -require_once MKI_DIR_PAGES . '/contact.php'; -require_once MKI_DIR_PAGES . '/index.php'; -require_once MKI_DIR_PAGES . '/projects.php'; -require_once MKI_DIR_PAGES . '/ssh.php'; +foreach(MKI_REDIRS as $source => $target) + $router->get($source, function($response) use ($target) { + $response->redirect($target); + }); -header('X-Powered-By: Makai'); +$router->use('/', function($response) { + $response->setPoweredBy('Makai+Index'); +}); -try { - $handlers = $router->resolve($reqMethod, $reqPath); -} catch(RoutePathNotFoundException $ex) { - $handlers = $router->resolve('GET', '/error/404'); -} catch(RouteMethodNotSupportedException $ex) { - $handlers = $router->resolve('GET', '/error/405'); -} +$router->get('/header-bgs.json', function() { + return json_encode(FM_BGS); +}); -$result = $handlers->run(); +$router->get('/now-listening', function() use ($ctx) { + return $ctx->getTemplating()->render('np', [ + 'header_offset' => (int)filter_input(INPUT_GET, 'offset', FILTER_SANITIZE_NUMBER_INT), + ]); +}); -if(is_int($result) && $result >= 400 && $result < 600) - $result = $router->resolve('GET', "/error/{$result}")->run(); +$router->get('/now-listening.json', function() { + $lfmInfo = json_decode(file_get_contents('https://now.flash.moe/get.php?u=flashwave_')); -if($result !== null && !is_bool($result)) { - if(is_int($result)) { - http_response_code($result); - header('Content-Type: text/plain; charset=us-ascii'); - echo $result; - } elseif(is_array($result) - || $result instanceof \stdClass - || $result instanceof \JsonSerializable) { - http_response_code(200); - header('Content-Type: application/json; charset=utf-8'); - echo json_encode($result); - } else { - $result = (string)$result; + if(empty($lfmInfo[0]->name)) + return []; - if(strtolower(substr($result, 0, 14)) === ' strval($lfmInfo->name), + 'now_playing' => !empty($lfmInfo->nowplaying), + 'url' => strval($lfmInfo->url), + 'cover' => !empty($lfmInfo->images->large) ? strval($lfmInfo->images->large) : '', + 'artist' => [ + 'name' => !empty($lfmInfo->artist->name) ? strval($lfmInfo->artist->name) : '', + 'url' => explode('/_/', strval($lfmInfo->url))[0], + ], + ]; +}); + +$router->get('/home', function() use ($ctx) { + return $ctx->getTemplating()->render('home/index'); +}); + +$router->get('/', function() use ($ctx) { + $legacyPage = (string)filter_input(INPUT_GET, 'p'); + if(!empty($legacyPage)) { + $legacyPages = [ + 'projects' => '/projects', + 'contact' => '/contact', + 'about' => '/', + 'etc' => '/etc', + 'hosted' => '/etc', + 'friends' => '/related', + ]; + + if(isset($legacyPages[$legacyPage])) { + header('Location: ' . $legacyPages[$legacyPage]); + return 302; } } -} + + $db = $ctx->getDatabase(); + + return $ctx->getTemplating()->render('index', [ + 'projects' => (new Projects($db))->getFeatured(), + 'languages' => new Languages($db), + 'contacts' => (new Contacts($db))->getHomePage(), + ]); +}); + +$router->get('/contact', function() use ($ctx) { + return $ctx->getTemplating()->render('contact', [ + 'contacts' => (new Contacts($ctx->getDatabase()))->getAll(), + ]); +}); + +$router->get('/projects', function() use ($ctx) { + $db = $ctx->getDatabase(); + $projects = (new Projects($db))->getAll(); + + $sections = [ + 'projects' => [ + 'title' => 'Active Projects', + 'desc' => 'Projects that I work on on a fairly regular basis.', + 'items' => [], + ], + 'tools' => [ + 'title' => 'Tools', + 'desc' => 'Personal quality of life tools that I update when I need something new from them.', + 'items' => [], + ], + 'archives' => [ + 'title' => 'Archived Projects', + 'desc' => 'Past projects that I no longer work on.', + 'items' => [], + ], + ]; + + foreach($projects as $project) { + if($project->isArchived()) + $sections['archives']['items'][] = $project; + else { + if($project->isTool()) + $sections['tools']['items'][] = $project; + else + $sections['projects']['items'][] = $project; + } + } + + return $ctx->getTemplating()->render('projects', [ + 'sections' => $sections, + 'languages' => new Languages($db), + ]); +}); + +$router->get('/ssh.php', function() { + $query = ''; + + $minLevel = (int)filter_input(INPUT_GET, 'l', FILTER_SANITIZE_NUMBER_INT); + if($minLevel > 0) + $query .= sprintf('l=%d&', $minLevel); + + if(!empty($_GET['c'])) + $query .= 'c=1&'; + + if(!empty($_GET['j'])) + $query .= 'j=1&'; + + if($query !== '') + $query = '?' . substr($query, 0, -1); + + header('Location: /ssh_keys' . $query); + return 302; +}); + +$router->get('/ssh_keys', function() use ($db) { + $minLevel = (int)filter_input(INPUT_GET, 'l', FILTER_SANITIZE_NUMBER_INT); + $includeComment = !empty($_GET['c']); + $json = !empty($_GET['j']); + + $keys = (new SSHKeys($db))->getKeys($minLevel); + + if($json) { + $items = []; + + foreach($keys as $key) { + $items[] = $item = new \stdClass; + $item->algo = $key->getAlgorithm(); + $item->key = $key->getBody(); + if($includeComment) { + $item->comment = (string)$key->getComment(); + $item->created = $key->getCreatedAt()->format(\DateTime::ATOM); + $item->level = $key->getLevel(); + } + } + + return $items; + } + + header('Content-Type: text/plain; charset=us-ascii'); + + $body = ''; + + foreach($keys as $key) + $body .= $key->toString($includeComment) . "\n"; + + return $body; +}); + +$router->get('/authorized_keys', function() use ($db) { + $keys = (new SSHKeys($db))->getKeys(500); + + header('Content-Type: text/plain; charset=us-ascii'); + + $body = ''; + foreach($keys as $key) + $body .= $key->toString(true) . "\n"; + + return $body; +}); + +$router->get('/git_keys_ro', function() use ($db) { + $keys = (new SSHKeys($db))->getKeys(100); + + header('Content-Type: text/plain; charset=us-ascii'); + + $body = ''; + foreach($keys as $key) + $body .= $key->toString(false) . "\n"; + + return $body; +}); + +$router->get('/git_keys_rw', function() use ($db) { + $keys = (new SSHKeys($db))->getKeys(200); + + header('Content-Type: text/plain; charset=us-ascii'); + + $body = ''; + foreach($keys as $key) + $body .= $key->toString(false) . "\n"; + + return $body; +}); + + +$router->dispatch(); diff --git a/src/MakaiContext.php b/src/MakaiContext.php new file mode 100644 index 0000000..2cd4deb --- /dev/null +++ b/src/MakaiContext.php @@ -0,0 +1,74 @@ +db = $db; + } + + public function getDatabase(): IDbConnection { + return $this->db; + } + + public function getTemplating(): TemplateContext { + if($this->tpl === null) { + $this->tpl = new TemplateContext(MKI_DIR_TEMPLATES); + $this->tpl->setGlobal('makai', $this); + $this->tpl->setGlobal('header_nav', $this->getDefaultNavigation()); + $this->tpl->setGlobal('header_bgs', $this->getDefaultHeaders()); + $this->tpl->setGlobal('footer_quotes', $this->getFooterQuotes()); + } + + return $this->tpl; + } + + public function getDefaultNavigation(): array { + return [ + ['title' => 'Home', 'link' => '/'], + ['title' => 'Projects', 'link' => '/projects'], + ['title' => 'Contact', 'link' => '/contact'], + ]; + } + + public function getDefaultHeaders(): array { + return [ + '/assets/headers/krk-000.jpg', '/assets/headers/krk-001.jpg', '/assets/headers/krk-002.jpg', + '/assets/headers/krk-003.jpg', '/assets/headers/krk-004.jpg', '/assets/headers/krk-005.jpg', + '/assets/headers/krk-006.jpg', '/assets/headers/krk-007.jpg', '/assets/headers/krk-008.jpg', + + '/assets/headers/mkt-000.jpg', '/assets/headers/mkt-001.jpg', '/assets/headers/mkt-002.jpg', + '/assets/headers/mkt-003.jpg', '/assets/headers/mkt-004.jpg', '/assets/headers/mkt-005.jpg', + '/assets/headers/mkt-006.jpg', '/assets/headers/mkt-007.jpg', '/assets/headers/mkt-008.jpg', + '/assets/headers/mkt-009.jpg', '/assets/headers/mkt-010.jpg', '/assets/headers/mkt-011.jpg', + '/assets/headers/mkt-012.jpg', '/assets/headers/mkt-013.jpg', '/assets/headers/mkt-014.jpg', + '/assets/headers/mkt-015.jpg', '/assets/headers/mkt-016.jpg', '/assets/headers/mkt-017.jpg', + '/assets/headers/mkt-018.jpg', '/assets/headers/mkt-019.jpg', '/assets/headers/mkt-020.jpg', + '/assets/headers/mkt-021.jpg', '/assets/headers/mkt-022.jpg', '/assets/headers/mkt-023.jpg', + '/assets/headers/mkt-024.jpg', '/assets/headers/mkt-025.jpg', '/assets/headers/mkt-026.jpg', + '/assets/headers/mkt-027.jpg', '/assets/headers/mkt-028.jpg', '/assets/headers/mkt-029.jpg', + '/assets/headers/mkt-030.jpg', '/assets/headers/mkt-031.jpg', '/assets/headers/mkt-032.jpg', + '/assets/headers/mkt-033.jpg', '/assets/headers/mkt-034.jpg', '/assets/headers/mkt-035.jpg', + '/assets/headers/mkt-036.jpg', '/assets/headers/mkt-037.jpg', '/assets/headers/mkt-038.jpg', + '/assets/headers/mkt-039.jpg', '/assets/headers/mkt-040.jpg', '/assets/headers/mkt-041.jpg', + '/assets/headers/mkt-042.jpg', '/assets/headers/mkt-043.jpg', '/assets/headers/mkt-044.jpg', + '/assets/headers/mkt-045.jpg', '/assets/headers/mkt-046.jpg', '/assets/headers/mkt-047.jpg', + '/assets/headers/mkt-048.jpg', '/assets/headers/mkt-049.jpg', '/assets/headers/mkt-050.jpg', + '/assets/headers/mkt-051.jpg', '/assets/headers/mkt-052.jpg', '/assets/headers/mkt-053.jpg', + '/assets/headers/mkt-054.jpg', '/assets/headers/mkt-055.jpg', '/assets/headers/mkt-056.jpg', + '/assets/headers/mkt-057.jpg', '/assets/headers/mkt-058.jpg', '/assets/headers/mkt-059.jpg', + '/assets/headers/mkt-060.jpg', '/assets/headers/mkt-061.jpg', '/assets/headers/mkt-062.jpg', + '/assets/headers/mkt-063.jpg', + ]; + } + + public function getFooterQuotes(): array { + return [ + 'if it ain\'t broke, i\'ll break it', + ]; + } +} diff --git a/src/Template.php b/src/Template.php new file mode 100644 index 0000000..16019c9 --- /dev/null +++ b/src/Template.php @@ -0,0 +1,47 @@ +vars->setVar($name, $value); + } + public function removeVar(string $name): void { + $this->vars->removeVar($name); + } + + public function render(array $vars = [], ?TemplateSelf $self = null): string { + if(!is_file($this->path)) + throw new \RuntimeException('Template file does not exist: ' . $this->path); + + $self = new TemplateSelf($this->context->getFunctions() + $vars + $this->vars->getVars(), $self); + $self->tplPath = $this->path; + + $self->var = fn(string $name, mixed $default = null) => $this->vars->getVar($name, $default); + $self->setVar = fn(string $name, mixed $value) => $this->vars->setVar($name, $value); + $self->remVar = fn(string $name) => $this->vars->removeVar($name); + + $self->block = fn(string $name, mixed $contents) => $this->context->setBlock($name, new TemplateBlock($self, $contents)); + + $self->ctx = $this->context; + $self->extends = fn(string $name) => $self->extends = $name; + + ob_start(); + (static function() use ($self) { include $self->tplPath; })(); + $buffer = ob_get_contents(); + ob_end_clean(); + + if(is_string($self->extends)) { + if(!empty($buffer)) + throw new \RuntimeException('You cannot output from templates that extend another.'); + $buffer = $this->context->render($self->extends, [], $self); + } + + return $buffer; + } +} diff --git a/src/TemplateBlock.php b/src/TemplateBlock.php new file mode 100644 index 0000000..4c2e750 --- /dev/null +++ b/src/TemplateBlock.php @@ -0,0 +1,22 @@ +body)) { + ob_start(); + ($this->body)($this->self); + $body = ob_get_contents(); + ob_end_clean(); + } else $body = strval($this->body); + + return $body; + } +} diff --git a/src/TemplateContext.php b/src/TemplateContext.php new file mode 100644 index 0000000..44aa7b9 --- /dev/null +++ b/src/TemplateContext.php @@ -0,0 +1,54 @@ +pathFormat = rtrim($path, '\\/') . DIRECTORY_SEPARATOR . '%s' . self::EXT; + $this->vars = new TemplateVars; + } + + public function setGlobal(string $name, mixed $value): void { + $this->vars->setVar($name, $value); + } + public function removeGlobal(string $name): void { + $this->vars->removeVar($name); + } + + public function getFunctions(): array { + return [ + 'global' => $this->vars->getVar(...), + 'getBlock' => $this->getBlock(...), + 'x' => fn(string $string) => htmlspecialchars($string, ENT_QUOTES, 'utf-8', true), + 'e' => function() { return ''; }, // reserve for the HTML builder + ]; + } + + public function create(string $path): Template { + return new Template($this, new TemplateVars($this->vars), sprintf($this->pathFormat, $path)); + } + + public function exists(string $path): bool { + return is_file(sprintf($this->pathFormat, $path)); + } + + public function render(string $path, array $vars = [], ?TemplateSelf $self = null): string { + return $this->create($path)->render($vars, $self); + } + + public function getBlock(string $name, mixed $default = ''): string { + return ($this->blocks[$name] ?? new TemplateBlock(new \stdClass, $default))->render(); + } + + public function setBlock(string $name, TemplateBlock $block): void { + $this->blocks[$name] = $block; + } +} diff --git a/src/TemplateSelf.php b/src/TemplateSelf.php new file mode 100644 index 0000000..23bed2f --- /dev/null +++ b/src/TemplateSelf.php @@ -0,0 +1,32 @@ +members += $inherit->members; + } + + public function __get(string $name): mixed { + return $this->members[$name]; + } + + public function __set(string $name, mixed $value): void { + $this->members[$name] = $value; + } + + public function __isset(string $name): bool { + return isset($this->members[$name]); + } + + public function __unset(string $name): void { + unset($this->members[$name]); + } + + public function __call(string $name, array $args): mixed { + return ($this->members[$name])(...$args); + } +} diff --git a/src/TemplateVars.php b/src/TemplateVars.php new file mode 100644 index 0000000..756bddf --- /dev/null +++ b/src/TemplateVars.php @@ -0,0 +1,32 @@ +vars; + if($this->parent !== null) + $vars += $this->parent->getVars(); + return $vars; + } + + public function getVar(string $name, mixed $default = null): mixed { + if(isset($this->vars[$name])) + return $this->vars[$name]; + if($this->parent !== null) + return $this->parent->getVar($name, $default); + return $default; + } + + public function setVar(string $name, mixed $value): void { + $this->vars[$name] = $value; + } + + public function removeVar(string $name): void { + unset($this->vars[$name]); + } +} diff --git a/tpl/contact.php b/tpl/contact.php new file mode 100644 index 0000000..90d6e3a --- /dev/null +++ b/tpl/contact.php @@ -0,0 +1,33 @@ +extends('home/master'); + +$self->header_title = 'flash.moe / contact'; + +$self->block('container', function($self) { +?> +
+
+
+

Contacts

+
+
+
+ contacts as $contact): ?> +
+ + hasLink()): ?> + + + + + + + +
+ +
+extends('errors/master'); + +$self->http_error_image = '/assets/errors/403.jpg'; +$self->http_error_desc = 'You are not supposed to be here.'; diff --git a/tpl/errors/404.php b/tpl/errors/404.php new file mode 100644 index 0000000..a6f4765 --- /dev/null +++ b/tpl/errors/404.php @@ -0,0 +1,5 @@ +extends('errors/master'); + +$self->http_error_image = '/assets/errors/404.jpg'; +$self->http_error_desc = 'Whatever you\'re looking for is no longer here, or might not have been here in the first place.'; diff --git a/tpl/errors/405.php b/tpl/errors/405.php new file mode 100644 index 0000000..aa43fdd --- /dev/null +++ b/tpl/errors/405.php @@ -0,0 +1,5 @@ +extends('errors/master'); + +$self->http_error_image = '/assets/errors/405.jpg'; +$self->http_error_desc = 'You\'re up to something, aren\'t you?'; diff --git a/tpl/errors/master.php b/tpl/errors/master.php new file mode 100644 index 0000000..4023f32 --- /dev/null +++ b/tpl/errors/master.php @@ -0,0 +1,14 @@ +extends('master'); + +$self->block('container', function($self) { +?> +
+

http_error_title ?? ('Unknown Error #' . $self->http_error_code));?>

+ http_error_image)): ?> + <?=$self->http_error_image;?> + +
http_error_desc ?? 'No additional information is available.');?>
+
+extends('home/master'); + +$self->header_title = 'flash.moe / homepage'; +$self->header_full = true; +$self->footer_onload = [['fm.initClock'], ['fm.initIndex', 10]]; + +$self->block('container', function($self) { +?> +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
--
+
:
+
--
+
+
+ Week + -- +  —  + ----------- +
+
+
+ +
+extends('master'); diff --git a/tpl/index.php b/tpl/index.php new file mode 100644 index 0000000..9ddc003 --- /dev/null +++ b/tpl/index.php @@ -0,0 +1,85 @@ +extends('master'); + +$self->header_title = 'flash.moe'; +$self->header_is_index = true; +$self->footer_onload = [['fm.initIndex']]; + +$self->block('container', function($self) { +?> +
+ header_nav); ++$i): $link = $self->header_nav[$i]; ?> + + +
+ + + - - - - - <?=($title ?? 'flash.moe');?> - - - - - - - - - -
-
- -
-
0) echo " style=\"padding-bottom: {$offset}px\""; ?>> - -
-
-
-
-
-
- -
-
-
-
-
-
-
- - > - -
-
-
-
-
+ + + + + <?=($self->header_title ?? 'flash.moe');?> + + + + + + +
+
+ +
+
header_offset) && $self->header_offset > 0) echo " style=\"padding-bottom: {$self->header_offset}px\""; ?>> + +
+
+
+
+
+
+ +
+
+
+
+
+
+
+header_nav as $link): ?> + > + +
+
+
+
+
+ getBlock('container');?> +
+ +footer_onload) && is_array($self->footer_onload)): ?> + + + + + diff --git a/tpl/np.php b/tpl/np.php new file mode 100644 index 0000000..243911e --- /dev/null +++ b/tpl/np.php @@ -0,0 +1,7 @@ +extends('master'); + +$self->header_title = 'flash.moe / now listening'; +$self->header_full = true; +$self->header_now_playing = true; +$self->footer_onload = [['fm.initIndex', 10]]; diff --git a/tpl/projects.php b/tpl/projects.php new file mode 100644 index 0000000..9b5d817 --- /dev/null +++ b/tpl/projects.php @@ -0,0 +1,85 @@ +extends('home/master'); + +$self->header_title = 'flash.moe / projects'; + +$self->block('container', function($self) { +?> + sections as $sectionId => $section): ?> +
+
+
+

+

+
+
+ + hasHomePageUrl()) + $links[] = ['class' => 'homepage', 'text' => 'Homepage', 'url' => $project->getHomePageUrl()]; + if($project->hasSourceUrl()) + $links[] = ['class' => 'repository', 'text' => 'Source', 'url' => $project->getSourceUrl()]; + if($project->hasDiscussionUrl()) + $links[] = ['class' => 'forum', 'text' => 'Discussion', 'url' => $project->getDiscussionUrl()]; + + $descLines = $project->hasDescription() ? $project->getDescription()->trim()->split("\n") : []; + + $langs = $self->languages->getByProject($project); + + if($project->hasColour()) + $colour = $project->getColour(); + else + foreach($langs as $lang) + if($lang->hasColour()) { + $colour = $lang->getColour(); + break; + } + + $colour = str_pad(dechex($colour), 6, '0', STR_PAD_LEFT); + ?> + +
+
+
+

getName();?>
+ getColour() ?? 0), 6, '0', STR_PAD_LEFT); + ?> +
getName();?>
+ +

+ + hasSummary()): ?> +

getSummary();?>

+ + + trim(); + if($line->isEmpty()) + continue; + ?> +

+ + +
+ + + + + +
+
+ + +