PHP Classes

File: src/view_functions.php

Recommend this page to a friend!
  Classes of Scott Arciszewski   CMS Airship   src/view_functions.php   Download  
File: src/view_functions.php
Role: Example script
Content type: text/plain
Description: Example script
Class: CMS Airship
Content management system with security features
Author: By
Last change: If currently using HTTPS, force canon_url to use HTTPS.
Date: 6 years ago
Size: 20,336 bytes
 

Contents

Class file image Download
<?php declare(strict_types=1); namespace Airship\ViewFunctions; use Airship\Engine\{ AutoPilot, Cache\ViewCache, Gadgets, Gears, Security\CSRF, Security\Util, State }; use Airship\Engine\Security\Permissions; use ParagonIE\CSPBuilder\CSPBuilder; use ParagonIE\ConstantTime\{ Base64, Base64UrlSafe, Hex }; use ParagonIE\Halite\{ Asymmetric\SignaturePublicKey, Asymmetric\SignatureSecretKey, File, HiddenString, Symmetric\AuthenticationKey, Util as CryptoUtil }; /** * Return a reusable CSRF prevention token, for AJAX requests. * * @param string $lockTo * @return string * @throws \TypeError */ function ajax_token($lockTo = '') { static $csrf = null; if ($csrf === null) { /** @var CSRF $csrf */ $csrf = Gears::get('CSRF'); } if (!($csrf instanceof CSRF)) { throw new \TypeError('Incorrect type for CSRF class'); } return $csrf->ajaxToken($lockTo); } /** * Get the base template (normally "base.twig") * * @return string */ function base_template() { $state = State::instance(); return $state->base_template; } /** * READ-ONLY access to the state global * * @param string $name * @return array */ function cabin_config(string $name = \CABIN_NAME): array { $state = State::instance(); foreach ($state->cabins as $route => $cabin) { if ($cabin['name'] === $name) { return $cabin; } } return []; } /** * READ-ONLY access to the cabin settings * * @param string $name * @return array */ function cabin_custom_config(string $name = \CABIN_NAME): array { return \Airship\loadJSON( ROOT . '/Cabin/' . $name . '/config/config.json' ); } /** * Get the canon URL for a given Cabin * * @param string $cabin * @return string * * @throws \Exception */ function cabin_url(string $cabin = \CABIN_NAME): string { static $lookup = []; $noArgs = \func_num_args() === 0; if (!empty($lookup[$cabin])) { // It was cached return $lookup[$cabin]; } $state = State::instance(); foreach ($state->cabins as $c) { if ($c['name'] === $cabin) { if (isset($c['canon_url'])) { $lookup[$cabin] = \rtrim($c['canon_url'], '/') . '/'; if (AutoPilot::isHTTPSConnection() && $cabin === \CABIN_NAME) { $lookup[$cabin] = \Airship\makeHttps($lookup[$cabin]); } return $lookup[$cabin]; } $lookup[$cabin] = '/'; return $lookup[$cabin]; } } return ''; } /** * Used in our cachebust filter. This is mostly useful for HTML5 app caching * * @param $relative_path * @return string */ function cachebust($relative_path) { if ($relative_path[0] !== '/') { $relative_path = '/' . $relative_path; } $absolute = $_SERVER['DOCUMENT_ROOT'] . $relative_path; if (\is_readable($absolute)) { // Halite's File::checksum() uses less memory than reading the entire // file into memory. $key = new AuthenticationKey( new HiddenString( CryptoUtil::raw_hash( (string) \filemtime($absolute) ) ) ); return $relative_path . '?' . Base64UrlSafe::encode( File::checksum( $absolute, $key, true ) ); } // Special value return $relative_path . '?404NotFound'; } /** * Permission Look-Up * * @param string $label * @param string $context_regex * @param string $domain * @param int $user_id * @return bool * @throws \Airship\Alerts\Database\DBException */ function can( string $label, string $context_regex = '', string $domain = \CABIN_NAME, int $user_id = 0 ): bool { static $perm = null; if ($perm === null) { $perm = Gears::get( 'Permissions', \Airship\get_database() ); } if (!($perm instanceof Permissions)) { return false; } return $perm->can($label, $context_regex, $domain, $user_id); } /** * Wrapper for `Gadgets::unloadCargo()` * * @param string $name * @param int $offset * @return array */ function cargo(string $name, int $offset = 0): array { return Gadgets::unloadCargo($name, $offset); } /** * Hash a file and store its hash in the Content-Security-Policy header * * @param string $file * @param string $dir * @param string $algo * @return string */ function csp_hash( string $file, string $dir = 'script-src', string $algo = 'sha384' ): string { $state = State::instance(); if (isset($state->CSP)) { list ($dirName, $lastPiece) = ViewCache::getFile( 'Content Security Policy Hash:', 'csp_hash', $file ); $fileName = $dirName . '/' . $lastPiece; if (\file_exists($fileName)) { if ($state->CSP instanceof CSPBuilder) { $prehash = \file_get_contents($fileName); if (!\is_string($prehash)) { // Network connection errors return $file; } $state->CSP->preHash( $dir, $prehash, $algo ); } return $file; } // Cache miss. if (\preg_match('#^([A-Za-z]+):\/\/#', $file)) { $absolute = $file; } else { if ($file[0] !== '/') { $file = '/' . $file; } $absolute = $_SERVER['DOCUMENT_ROOT'] . $file; if (!\file_exists($absolute)) { return $file; } } if ($state->CSP instanceof CSPBuilder) { $contents = \file_get_contents($absolute); if (!\is_string($contents)) { // Network connection errors return $file; } $preHash = Base64::encode( \hash($algo, \file_get_contents($absolute), true) ); $state->CSP->preHash($dir, $preHash, $algo); if (!\is_dir($dirName)) { \mkdir($dirName, 0775, true); } \file_put_contents( $fileName, $preHash ); return $file; } } return $file; } /** * Hash a string and store its hash in the Content-Security-Policy header * * @param string $str The data we are hashing * @param string $dir The CSP Directive * @param string $algo Which hash algorithm? * @return string $str */ function csp_hash_str( string $str, string $dir = 'script-src', string $algo = 'sha384' ): string { $state = State::instance(); if (isset($state->CSP)) { if ($state->CSP instanceof CSPBuilder) { $preHash = \hash($algo, $str, true); $state->CSP->preHash( $dir, Base64::encode($preHash), $algo ); return $str; } } return $str; } /** * Generate a nonce, add to the CSP header * * @param string $dir * @return string */ function csp_nonce(string $dir = 'script-src'): string { $state = State::instance(); if (isset($state->CSP)) { if ($state->CSP instanceof CSPBuilder) { return (string) $state->CSP->nonce($dir); } } return 'noCSPInstalled'; } /** * Insert a CSRF prevention token * * @param string $lockTo * @return string * @throws \TypeError */ function form_token($lockTo = '') { static $csrf = null; if ($csrf === null) { /** @var CSRF $csrf */ $csrf = Gears::get('CSRF'); if (!($csrf instanceof CSRF)) { throw new \TypeError('Incorrect type for CSRF class'); } } return $csrf->insertToken($lockTo); } /** * Given a URL, only grab the path component (and, optionally, the query) * * @param string $url * @param bool $includeQuery * @return string */ function get_path_url(string $url, bool $includeQuery = false): string { $path = \parse_url($url, PHP_URL_PATH); if ($path) { if ($includeQuery) { $query = \parse_url($url, PHP_URL_QUERY); if ($query) { return $url . '?' . $query; } } return $path; } return ''; } /** * Display the notary <meta> tag. * * @param SignaturePublicKey $pk */ function display_notary_tag(SignaturePublicKey $pk = null) { $state = State::instance(); $notary = $state->universal['notary']; if (!empty($notary['enabled'])) { if (!$pk) { $sk = $state->keyring['notary.online_signing_key']; if (!($sk instanceof SignatureSecretKey)) { return; } $pk = $sk ->derivePublicKey() ->getRawKeyMaterial(); } echo '<meta name="airship-notary" content="' . Base64UrlSafe::encode($pk) . '; channel=' . Util::noHTML($notary['channel']) . '; url=' . cabin_url('Bridge') . 'notary' . '" />'; } } /** * Get supported languages. Eventually there will be more than one. */ function get_languages(): array { return [ 'en-us' => 'English (U.S.)' ]; } /** * Get an author profile's avatar * * @param int $authorId * @param string $which * @return string */ function get_avatar(int $authorId, string $which): string { static $cache = []; static $db = null; if (!$db) { $db = \Airship\get_database(); } // If someone comments 100 times, we only want to look up their avatar once. $key = CryptoUtil::hash( \http_build_query([ 'author' => $authorId, 'which' => $which ]) ); if (!isset($cache[$key])) { $file = $db->row( "SELECT f.*, a.slug FROM hull_blog_author_photos p JOIN hull_blog_authors a ON p.author = a.authorid JOIN hull_blog_photo_contexts c ON p.context = c.contextid JOIN airship_files f ON p.file = f.fileid WHERE c.label = ? AND a.authorid = ? ", $which, $authorId ); if (empty($file)) { $cache[$key] = ''; } else { if (empty($file['directory'])) { $cabin = $file['cabin']; } else { $dirId = $file['directory']; do { $dir = $db->row( "SELECT parent, cabin FROM airship_dirs WHERE directoryid = ?", $dirId ); $dirId = $dir['parent']; } while (!empty($dirId)); $cabin = $dir['cabin']; } $cache[$key] = \Airship\ViewFunctions\cabin_url($cabin) . 'files/author/' . $file['slug'] . '/photos/' . $file['filename']; } } return $cache[$key]; } /** * Purify a string using HTMLPurifier * * @param $string * @return string * @throws \TypeError */ function get_purified(string $string = '') { $gear = get_viewcache_obj(); return $gear::purify($string); } /** * @return ViewCache * @throws \TypeError */ function get_viewcache_obj(): ViewCache { static $gear = null; if (!$gear) { /** * @var ViewCache */ $gear = Gears::get('ViewCache'); if (!$gear instanceof ViewCache) { throw new \TypeError(); } } return $gear; } /** * READ-ONLY access to the state global * * @param string $key * @return array */ function global_config(string $key): array { $state = State::instance(); switch ($key) { case 'active_cabin': return [ $state->{$key} ]; case 'base_template': case 'cabins': case 'cargo': case 'motifs': case 'gears': case 'lang': case 'universal': return $state->{$key}; default: return []; } } /** * Is this user an administrator? * * @param int $userID * @return bool * @throws \Airship\Alerts\Database\DBException */ function is_admin(int $userID = 0): bool { static $perm = null; if ($perm === null) { $perm = Gears::get( 'Permissions', \Airship\get_database() ); if (!($perm instanceof Permissions)) { return false; } } if ($userID < 1) { $userID = \Airship\ViewFunctions\userid(); } return $perm->isSuperUser($userID); } /** * Json_encode and Echo * * @param mixed $data * @param int $indents */ function je($data, int $indents = 0) { if ($indents > 0) { $left = \str_repeat(' ', $indents); echo \implode( "\n" . $left, \explode( "\n", \json_encode($data, JSON_PRETTY_PRINT) ) ); return; } echo \json_encode($data, JSON_PRETTY_PRINT); } /** * Return the user's logout token. This is to prevent logout via CSRF. * * @return string */ function logout_token(): string { if (\array_key_exists('logout_token', $_SESSION)) { return $_SESSION['logout_token']; } $_SESSION['logout_token'] = Hex::encode( \random_bytes(16) ); return $_SESSION['logout_token']; } /** * Get information about the motifs * * @return array */ function motifs() { $state = State::instance(); return $state->motifs; } /** * Unload the "next" cargo, using an internal iterator. * * @param string $cargoName * @return array */ function next_cargo(string $cargoName) { return Gadgets::unloadNextCargo($cargoName); } /** * Render user input, with CommonMark. * * @param string $string * @param bool $return * @output HTML * @return string */ function render_markdown(string $string = '', bool $return = false): string { $gear = get_viewcache_obj(); return $gear::markdown($string, $return); } /** * Renders ReStructuredText * * @param string $string * @param bool $return * @output HTML * @return string */ function render_rst(string $string = '', bool $return = false): string { $gear = get_viewcache_obj(); return $gear::rst($string, $return); } /** * Purify a string using HTMLPurifier. Echo its contents. * * @param $string * @return void */ function purify(string $string = '') { echo get_purified($string); } /** * Markdown then HTMLPurifier * * @param string $string * @param bool $return * @return string|null */ function render_purified_markdown(string $string = '', bool $return = false) { if ($return) { \ob_start(); } \Airship\ViewFunctions\purify( \Airship\ViewFunctions\render_markdown($string, true) ); if ($return) { return \ob_get_clean(); } return null; } /** * ReStructuredText then HTMLPurifier * * @param string $string * @param bool $return * @return string|null */ function render_purified_rest(string $string = '', bool $return = false) { if ($return) { \ob_start(); } \Airship\ViewFunctions\purify( \Airship\ViewFunctions\render_rst($string, true) ); if ($return) { return \ob_get_clean(); } return null; } /** * Get the current user's ID. Returns 0 if not logged in. * * @return int */ function userid(): int { return \array_key_exists('userid', $_SESSION) ? (int) $_SESSION['userid'] : 0; } /** * Get all of a user's author profiles * * @param int|null $userId * @return array * @throws \Airship\Alerts\Database\DBException */ function user_authors(int $userId = null): array { if (empty($userId)) { $userId = \Airship\ViewFunctions\userid(); } $db = \Airship\get_database(); $authors = $db->run( 'SELECT * FROM view_hull_users_authors WHERE userid = ?', $userId ); if (empty($authors)) { return []; } return $authors; } /** * Get all of a user's author profiles * * @param int|null $userId * @return array * @throws \Airship\Alerts\Database\DBException */ function user_author_ids(int $userId = null): array { if (empty($userId)) { $userId = \Airship\ViewFunctions\userid(); } $db = \Airship\get_database(); $authors = $db->first( 'SELECT authorid FROM hull_blog_author_owners WHERE userid = ?', $userId ); if (empty($authors)) { return []; } return $authors; } /** * Get the user's public display name. * * @param int|null $userId * @return string * @throws \Airship\Alerts\Database\DBException */ function user_display_name(int $userId = null): string { if (empty($userId)) { $userId = \Airship\ViewFunctions\userid(); } $db = \Airship\get_database(); $displayName = $db->cell( "SELECT COALESCE( display_name, real_name, username ) FROM airship_users WHERE userid = ? ", $userId ); if (empty($displayName)) { return ''; } return get_purified($displayName); } /** * Get the user's selected Motif * * @param int|null $userId * @param string $cabin * @return array */ function user_motif(int $userId = null, string $cabin = \CABIN_NAME): array { static $userCache = []; $state = State::instance(); if (\count($state->motifs) === 0) { return []; } if (empty($userId)) { $userId = \Airship\ViewFunctions\userid(); if (empty($userId)) { $k = \array_keys($state->motifs)[0]; return $state->motifs[$k] ?? []; } } // Did we cache these preferences? if (isset($userCache[$userId])) { return $state->motifs[$userCache[$userId]]; } $db = \Airship\get_database(); $userPrefs = $db->cell( 'SELECT preferences FROM airship_user_preferences WHERE userid = ?', $userId ); if (empty($userPrefs)) { // Default $k = \array_keys($state->motifs)[0]; $userCache[$userId] = $k; return $state->motifs[$k] ?? []; } $userPrefs = \Airship\parseJSON($userPrefs, true); if (isset($userPrefs['motif'][$cabin])) { $split = \explode('/', $userPrefs['motif'][$cabin]); foreach ($state->motifs as $k => $motif) { if (empty($motif['config'])) { continue; } if ( $motif['supplier'] === $split[0] && $motif['name'] === $split[1] ) { // We've found a match: $userCache[$userId] = $k; return $state->motifs[$k]; } } } // When all else fails, go with the first one $k = \array_keys($state->motifs)[0]; $userCache[$userId] = $k; return $state->motifs[$k] ?? []; } /** * Get a user's username, given their user ID * * @param int|null $userId * @return string * @throws \Airship\Alerts\Database\DBException */ function user_name(int $userId = null): string { if (empty($userId)) { $userId = \Airship\ViewFunctions\userid(); } $db = \Airship\get_database(); return get_purified( $db->cell( 'SELECT username FROM airship_users WHERE userid = ?', $userId ) ); } /** * Get the user's public display name. * * @param int|null $userId * @return string * @throws \Airship\Alerts\Database\DBException */ function user_unique_id(int $userId = null): string { if (empty($userId)) { $userId = \Airship\ViewFunctions\userid(); } $db = \Airship\get_database(); return get_purified( $db->cell( 'SELECT uniqueid FROM airship_users WHERE userid = ?', $userId ) ); }