file_get_contents(base64_decode($url))); // Setting robots file in config Core\Route::set('robots', fn() => file_get_contents(Controller\System::config('robots'))); Core\Route::set('tnew', function() { $this->header('X-Robots-Tag', 'noindex'); Render::set('after', function($d) { foreach($d->find('//body/footer//a[starts-with(@href, "/")]/@href') as $link) $link->value = "https://mcachicago.org{$link}"; }); return new DOM\Document('
'); }, ['layout' => 'layout/tnew.html']); // Post Request from AWS. Though message is JSON, request type is text/plain; manually decode Core\Route::set('press', new Core\Controller('Controller\Press')); // TODO revisit and turn into a general sitemap Core\Route::set('corpus', function($type = 'href') { $query = '//ol[@title]/li' . Model::config('query'); if ($this->request->type == 'xml') { $this->header('cache-control', sprintf('max-age=%d', 86400)); $this->pages = Model::DB()->map($query, fn($item) => Model\Vertex::Factory($item->getAttribute('id'), 'link')); return DOM\Document::open('layout/sitemap.xml'); } $lines = []; foreach(Model::DB($query, 'find') as $node) { $id = substr_replace($node->getAttribute('id'), '/', 2, 0); $link = '../data/links/' . $id; if ($ref = $type == 'href' ? (is_link($link) ? substr(readlink($link), 11, -5) : false) : "../{$id}.md") { $lines[] = [$ref, $node->nodeValue, $node->select('../@title')->value]; } } return json_encode($lines); }); Core\Route::taxonomy(function() { $model = DOM\Document::open('../data/model.xml'); $summary = new DOM\Document('
'); $deets = function($doc, $ctx, $open = false, $q = './*[*/@id]') use(&$deets) { foreach($doc->find($q) as $node) { $d = $ctx->appendChild(new DOM\Element('details')); $d->appendChild(new DOM\Element('summary', (string)$node['@id'])); if ($open) $d->setAttribute('open', 'true'); if ($node->select($q)) { $deets($node, $d); } else { $list = $d->appendChild(new DOM\Element('ol')); foreach ($node->find('li') as $page) { $list->appendChild(new DOM\Element('li', $page->nodeValue)); } } } }; $deets($model->select('/'), $summary->firstChild, true); return $summary; }); Core\Route::set('search', function($corpus = false) { $config = Util\API::config('google'); $this->query = $this->input['query'] ?? throw $this->status(404); $request = HTTP::GET($config['url'])->send([ 'q' => urlencode($this->query), 'cx' => $config['cx'], 'key' => $config['key'], 'fields' => 'items(link,title,snippet)', ]); $this->items = array_filter(array_map(fn($r) => [ 'link' => str_replace('https://mcachicago.org', '', $r['link']), 'title' => preg_replace('/(?: - )?MCA(?: - )?/', '', $r['title']), 'snippet' => $r['snippet'], // 'img' => $r['pagemap']['cse_image'][0]['src'] ?? null, ], $request->resource['items'] ?? []), fn($d) => $d['link'] != '/' || ! str_starts_with($d['link'], 'https://visit.mca')); $this->header('X-Robots-Tag', 'noindex'); return DOM\Document::open('layout/landing/search.html'); }); // TODO consider moving webhooks into controller\System Core\Route::set('reporting', function($type = 'request', ?array $input = null) { if(! $input) { $this->status(200); // bypass the next action by setting the status to success now $this->form = ['bug' => 'bugreporting', 'request' => 'add_change_content'][$type]; return DOM\Document::open('forms/formstack-embed.html'); } Auth\Token::HMAC($this, 'X_FS_SIGNATURE') ?: throw $this->status(404); return ['data' => $input, 'type' => $type]; })->then(function($data, $type) { Util\Bench::Task([new Service\Formstack($data, $type), 'process']); return new DOM\Document('

ok

'); }); Core\Route::set('calendar', function(?int $year = null, ?int $month = null, ?string $slug = null) { array_push($this->data['breadcrumbs'], ['url' => '/calendar', 'name' => 'Calendar']); if ($this->request->origin != '/calendar') { $this->yield('breadcrumbs' , 'layout/breadcrumbs.html'); } if (! $slug && ($year || $month || key_exists('when', $this->input))) { $basic = ['when' => null, 'page' => 1, 'filter' => null, 'tag' => null, 'limit' => 36, 'order' => 'asc']; $query = array_replace($basic, array_intersect_key($this->input, $basic)); $query += ['type' => 'events', 'year' => $year, 'month' => $month]; $this->type = 'events'; // $this->heading = $query['when'] ?? 'Events' ; $events = Model\Content\Calendar::Build(...$query); if ($group = $this->input['group'] ?? false) { $this->calendar = \dict\group($group, $events, 'ksort'); return DOM\Document::open('layout/calendar-group.html'); } $this->calendar = $events; return DOM\Document::open('layout/calendar.html', cache: false); } array_push($this->data['breadcrumbs'], [ 'url' => sprintf('/calendar/%s/%s', $year ?? date('Y'), $month ?? date('m')), 'name' => date('F Y', mktime(0, 0, 0, $month, 1, $year)), ]); if ($this->request->type == 'ics' && ($event = Model\Vertex::Factory('/'.$this->request->origin)) && $event instanceof Model\Content\Calendar) { $idx = $this->input['idx'] ?? 0; return new \vendor\ICS([ 'description' => wordwrap($event->document->select(Render::META['description'])->nodeValue ?? '', 75, "\\n"), 'dtstart' => $event->occurrence('start', $idx)->format(\Datetime::RFC3339), 'dtend' => $event->occurrence('end', $idx)->format(\Datetime::RFC3339), 'location' => join(' ', array_map(fn($e) => $e->title, $event->locations)), 'summary' => $event->title, 'url' => $this::config('host') . $event->permalink, ]); } })->catch(function($e) { error_log("/{$this->request->origin}: {$e->getMessage()}"); throw new RuntimeException("URL parameters are incorrect", 400); }); Core\Route::set('exhibitions', function($year = null, ?string $slug = null) { array_push($this->data['breadcrumbs'], ['url' => '/exhibitions', 'name' => 'Exhibitions']); if ($year != $slug) { $this->yield('breadcrumbs', 'layout/breadcrumbs.html'); } if ($year == 'archive') { $current = floor(date('Y') / 10) * 10; $slug ??= $current; $decades = []; $decade = 1950; while ($decade < $current) { $decades[] = [ 'decade' => $decade += 10, 'link' => "/exhibitions/archive/{$decade}", 'active' => $decade == $slug ? 'active' : null, ]; } $this->decades = $decades; $this->decade = $slug; $this->exhibitions = array_reverse(dict\group('year', Model\Content\Calendar::Exhibitions([$slug, $slug + 10]), true)); $this->size = count($this->exhibitions); return DOM\Document::open('layout/exhibitions.html'); } if ($year && ! is_numeric($year)) { if ($year == 'series') { if ($slug) { array_push($this->data['breadcrumbs'], ['url' => '/exhibitions/series', 'name' => 'Series']); } else { $this->series = \Model\Agent\Topic::List('series')->filter(fn($m) => str_contains($m->permalink, 'exhibitions')); $this->yield('series', 'layout/exhibition-series.html'); } } } else { if (! $slug && ($year || key_exists('when', $this->input))) { $basic = ['when' => null, 'page' => 1, 'filter' => null, 'order' => 'asc']; $query = array_replace($basic, array_intersect_key($this->input, $basic)); $query += ['type' => 'exhibitions', 'year' => $year]; $this->type = 'exhibitions'; // $this->heading = 'TODO: better headings'; $this->calendar = Model\Content\Calendar::Build(...$query); if ($this->calendar->length > 0) { $doc = DOM\Document::open('layout/calendar.html', cache: false); } else { $doc = new \DOM\Document(''); } return $doc; } } }); Core\Route::set('graphics', function(...$path) { $this->header('cache-control', sprintf('max-age=%d', 86400)); $this->data += $this->input + ['fill' => '#000']; $path = 'ux/graphics/' . join('/', $path); if ($this->request->type == 'css') { $tmp = " --%s: url('data:image/svg+xml;base64,%s');\n"; $out = array_reduce(glob($path . '/*.svg'), fn($c, $i) => $c .= sprintf($tmp, basename($i, '.svg'), base64_encode(\DOM\Template::load($i, $this->data))), ''); return ":root {\n{$out}\n}"; } return \DOM\Document::open($path . '.svg'); }); Core\Route::set('metadata', function(string $type, string $id = null) { $data = \DOM\Render\Plain::check(\DOM\Template::load("layout/api/{$type}.xml", \Model\Vertex::Factory($id, 'links')), 'json'); return $this->request->type == 'js' ? $data : new \DOM\Document(""); }); Core\Route::set('olx', function(string $version, string $id, ?string $round = null) { $this->form_id = $id; $this->yield('form', "forms/support/olx/{$version}.html"); if ($round) { $this->yield('round', 'forms/support/olx/round.html'); } return \DOM\Document::open("forms/support/olx/layout.html"); }); # I M P L E M E N T A I O N try { $request = new Core\Request(headers: $_SERVER, root: realpath('.')); $data = [ 'breadcrumbs' => [['name' => 'MCA', 'url' => '/']], 'expires' => fn($t, $c = 'today') => strtotime($t) < strtotime($c) ? 'inactive' : "through {$t}", 'prefs' => $request->origin == 'index' ? null : $_COOKIE['prefs'] ?? 'theme-auto', // homepage is cached! ]; Controller\System::load($request); $response = new Core\Response($request, $data); Render::set('after', function($d) use($response) { $exp = "/html/body/header/nav/ul/li/span[a[@href!='/{$response->route}']]/following-sibling::ul"; foreach($d->find($exp) as $node) $node->remove(); }); $response = Core\Route::delegate($response); } catch (Core\Status $notice) { $code = $notice->getCode(); $response = match((int)floor($code / 100)) { 2, 5 => new Core\Response($notice->request), // original response is unnecessary, boom boom 3 => $notice->request->redirect($notice->getMessage(), $code, $notice->response), default => $notice->response ?? Controller\System::respond($notice->request, ['error', $code], $data + ['message' => $notice->getMessage()]), }; } catch (RuntimeException $e) { $response = Controller\System::respond($request, ['error', $e->getCode() ?: 503], ['message' => $e->getMessage()]); } catch (Exception | Error $e) { $response = Controller\System::respond($request, ['error', $e->getCode() ?: 500], Core\Status::trace($e)); } finally { ob_start(); echo $response; ob_end_flush(); flush(); if (function_exists('fastcgi_finish_request')) fastcgi_finish_request(); Util\Bench::Clear(); }