diff options
| author | George Halkiadakis <gchalkiadakis@sklavenitis.co.gr> | 2023-03-12 07:01:30 +0200 |
|---|---|---|
| committer | George Halkiadakis <gchalkiadakis@sklavenitis.co.gr> | 2023-03-12 07:01:30 +0200 |
| commit | 47cbb529f5723b246125ae083a193e11481b89ef (patch) | |
| tree | 31ab20b2fbf12bee1cd994c3d6fb822fa52622e3 /helpers | |
| download | classroom-47cbb529f5723b246125ae083a193e11481b89ef.tar.gz classroom-47cbb529f5723b246125ae083a193e11481b89ef.tar.bz2 classroom-47cbb529f5723b246125ae083a193e11481b89ef.zip | |
initializing classroom structure using anom framework
Diffstat (limited to 'helpers')
| -rw-r--r-- | helpers/autoload.php | 43 | ||||
| -rw-r--r-- | helpers/design_patterns.php | 104 | ||||
| -rw-r--r-- | helpers/error_handling.php | 119 | ||||
| -rw-r--r-- | helpers/render.php | 338 |
4 files changed, 604 insertions, 0 deletions
diff --git a/helpers/autoload.php b/helpers/autoload.php new file mode 100644 index 0000000..c4359c6 --- /dev/null +++ b/helpers/autoload.php @@ -0,0 +1,43 @@ +<?php +/** register class-autoloader + * + * NOTE: + * Composer is strongly recommended; it is a much better option + * and takes care for many more than just autoloading classes; + * + * This's a fair autoloader in case composer is not available; + * supports single and namespaced classes. + */ +spl_autoload_register( + function($className) { + + // in order to support common namespaced classes + // replace '\' with '/' to get the path + $classPath = str_replace('\\', DIRECTORY_SEPARATOR, $className); + + // check all possible classPaths for class + foreach(CLASSPATHS as $key => $basePath) { + + $file = APP_ROOT . $basePath . $classPath .'.php'; + + // if class-file exist, include it + if (file_exists($file)) { + require_once $file; + break; + } + + } + + // // Feel free to extend the fanctionality + // // example: + // // custom (exception) namespaced classes loader + // $customNSclasses = array( + // '\\George\\Class' => 'path/to/George/Class', + // '\\Mary\\Class' => 'path/to/Marias/Class', + // ... + // ); + // if array_key_exists($className) { + // require_once $customNSclasses[$className]; + // } + } +); diff --git a/helpers/design_patterns.php b/helpers/design_patterns.php new file mode 100644 index 0000000..d66ffa9 --- /dev/null +++ b/helpers/design_patterns.php @@ -0,0 +1,104 @@ +<?php +/** proxy + * implements the proxy design pattern + * + * uses: Registry::cache + * + * algorithm: + * - 1: proxy gets (Class::method, Arguments) and calculates a unique key + * - 2: if a valid Cache for the triplete's key exists, it is served immediately + * - 3: otherwise calls Class::Method(Arguments), caches then serves response + * + * @param $class_method (array): a 2 (string) items array in the form + * : [ 'fully\qualifies\ClassName' , 'methodName' ] + * : Using the ClassName::class notation is strongly recommended. + * @param $args: array of arguments (passed to the Class::Method()) + * @param $ttl: time (in sec) where the data will remain valid (not expired) + * @param $options: bitwise flags* + * --- + * @return $data (mixed) + * + * (*) option flags + * all option flags represend non default behaviour of proxy + * --- + * PROXY_CACHE_ERRORS : cache value even if callback returns error + * PROXY_IGNORE_CACHE : ignore if cache exist; get result from callback + * PROXY_DO_NOT_CACHE : do not cache the result (even if from callback) + */ +function proxy(array $callable, array $args, int $ttl, int $options=0) +{ + // resolve option flags + $cache_errors = ($options&PROXY_CACHE_ERRORS) ? true : false; + $ignore_cache = ($options&PROXY_IGNORE_CACHE) ? true : false; + $do_not_cache = ($options&PROXY_DO_NOT_CACHE) ? true : false; + + // we're gonna use cache; + $cache = Registry::use('cache'); + + + // check validity of callable argument + // ------------------------------------------------------------------------- + if ((!is_callable($callable)) || (!is_array($callable))) { + + throw new Exception( + 'Proxy\'s first parameter has to be a callable array [fully\\qualified\\ClassName, method], passed:'.print_r($callable, true) + ); + + } + + // create a unique key + // ------------------------------------------------------------------------- + list($class, $method) = $callable; + $key = sha1( + $class .':'. $method .':'. json_encode( + $args, JSON_UNESCAPED_LINE_TERMINATORS|JSON_UNESCAPED_UNICODE + ) + ); + + // search cache if data for this key exist + // ------------------------------------------------------------------------- + if (!$ignore_cache) { + $data = $cache->get($key); + + } else { $data = false; } // ignore cache? pass false + + + if ($data !== false) { // if exists, serve cached data + + // if cashed data exist + // --------------------------------------------------------------------- + # if (!PRODUCTION) Benchmark::add_spot('proxy-cached'); + if ($data == '_ERROR_') return false; // previous cached error + else return $data; // previous cache () + + + } else { // else (not exist)... + + // no data on cache -> get data from Class::method( arguments ) + // --------------------------------------------------------------------- + $data = call_user_func_array($callable, $args); + + // handle newly created data + // --------------------------------------------------------------------- + + // if faulty data, return false + if ($data == false) { + + if ($cache_errors && !$do_not_cache) { // cache the error + $cache->set($key, '_ERROR_', $ttl); + } + + # if (!PRODUCTION) Benchmark::add_spot('proxy-new-error'); + return false; + } + + // store to cache + if (!$do_not_cache) { + $cache->set($key, $data, $ttl); + } + + # if (!PRODUCTION) Benchmark::add_spot('proxy-new'); + return $data; // serve data + } + +} diff --git a/helpers/error_handling.php b/helpers/error_handling.php new file mode 100644 index 0000000..bc60968 --- /dev/null +++ b/helpers/error_handling.php @@ -0,0 +1,119 @@ +<?php +/** ERROR HANDLING SECTION + * handles exceptions and errors (even fatal) + * ----------------------------------------------------------------------------- + * The code... + * + Catches and handles all Errors and Throwable exceptions; + * - creates the error messages (for the requester and developer) + * ? TODO: routes the error messages to the logging channels + */ + + +// Global array to save errors +$anom_ERRORS = []; // ok, Globals are not recommended, but... + // this one collects only errors (about the application); + // it is oriented for the developer; not for the request. + + +/** Error handler, + * passes flow over the exception logger with new ErrorException. + */ +function handle_error(int $type, string $message, string $file, int $line) +{ + handle_exception( new ErrorException($message, 0, $type, $file, $line) ); +} + + +/** Uncaught exception handler. +*/ +function handle_exception(Throwable $e) +{ + global $anom_ERRORS; + + // full message description to be logged + $message = get_class($e) .": ". $e->getMessage() + . " on file '". $e->getFile() . "' at line {$e->getLine()}"; + + $anom_ERRORS[] = $message; // store the error +} + + +/** + * Catch fatal error work-around + * (set_error_handler not working on fatal errors). + */ +function on_shutdown_check_for_fatal() +{ + global $anom_ERRORS; + + $error = error_get_last(); + + if ($error != null) { // ignore 'normal' system shutdowns [exit|die]() + + if ( $error["type"] == E_ERROR ) { // get as much error-info + handle_error( + $error["type"] ?? -1, + $error["message"] ?? 'unknown error', + $error["file"] ?? 'unknown file', + $error["line"] ?? -1 + ); + } + } + + if (($anom_ERRORS != [])) { + + // TODO: + // Implemetnig the design/layout of the error-messages + // output should not be an error's-handling task, thus + // shall be assigned to the Rendes/View level; + // Same about the log service channels + + // TODO: + // FORWARD ERRORS into LOG-SYSTEM + // ex. into some file... + // file_put_contents("logs/thowable.log", $message_template .PHP_EOL, FILE_APPEND); + // or ...some other channer (Email, Slack, Database etc). + // Channes can be decided according to error severity; + // a file-system error loggin for backup is recommended + // partitioniong of errors onto file-system can be useful + + // TODO: + // 1st: Log Errors + // 2nd: Show Errors + + if (PRODUCTION) { + if (!defined('VIEW_LOADED')) { + // throw a prety-error message + render_view('error/general', ['message' => 'Please forgive me, I know not what I do']); + } + + } else { + + if (!defined('VIEW_LOADED')) { + // no template loaded; render erros in general-error template + render_view('error/general', ['message' => implode("<br/>And:<br/>", $anom_ERRORS)]); + + } else { + echo '<div style="padding:1em;background:#933;width:100%;color:#fff"> + Errors:<br />'. implode("<br/>And:<br/>", $anom_ERRORS) .'</div>'; + } + } + } +} + + +// register callback on shutdown +register_shutdown_function( "on_shutdown_check_for_fatal" ); + +// register custom error handler callback +set_error_handler( "handle_error" ); + +// set custom exception handler function +set_exception_handler( "handle_exception" ); + +// send errors to stderr (instead of stdout) +ini_set( "display_errors", "off" ); + +// thow out all errors +error_reporting( E_ALL ); + diff --git a/helpers/render.php b/helpers/render.php new file mode 100644 index 0000000..9dcd34a --- /dev/null +++ b/helpers/render.php @@ -0,0 +1,338 @@ +<?php +/** Rendering System + * --- + * this is the View part of the MVC framework. + * Decided not to make it a Class + ***/ + + +/** load template + * --- + * TODO: + * explain/document the difference between a template and a view + * + * TODO: + * Implementing the Render/View operation as a class, using the same- + * name methods with Laraver or CodeIgniter could be good idea; + * and get rid of if(!defined('VIEW_LOADED')) define('VIEW_LOADED',1); + */ +function load_template($template, $data) { + + $file = VIEWS_DIRECTORY . $template . '.php'; + + if (file_exists($file)) { + if (!defined('VIEW_LOADED')) define('VIEW_LOADED', 1); + + ob_start(); + + extract( sanitize_output($data) ); + require( $file ); + + ob_flush(); + + } else if (!PRODUCTION) { + + echo "<!-- view ". $file ." is missing -->"; + } + +} + +/** parse_sections + * --- + * parse a group of views (sections) + * @param $sections: an array of sections + * + * each section group is an array with view, key and data properties + * + view: defines the view template/file + * + key: the variable name that view uses to parse data OR empty-string* + * * if key is an empty then $data should be an array (which + * includes all [variable-name:data] pairs utilized by the view) + * + data: holds the actual data + */ + +function parse_sections($sections) { + + foreach($sections as $sect) { + + if ($sect['key'] == '') { + render_view( $sect['view'], $sect['data'] ); + + } else { + render_view( $sect['view'], [ $sect['key'] => $sect['data']] ); + } + } +} + + +/** render function + * --- + * uses php's short-tag syntax for templating system + * extract data into template + * @param $view: view-template filename + * @param $data: data to embed into view-template + * @param $sanitize: of true then sanitize data. + * important NOTE: data is an array [key => value] + */ +function render_view($view, $data, $sanitize = false) { + + $file = VIEWS_DIRECTORY . $view . '.php'; + + if (file_exists($file)) { + if (!defined('VIEW_LOADED')) define('VIEW_LOADED', 1); + + extract( $sanitize ? sanitize_output($data) : $data ); + require( $file ); + + } else if (!PRODUCTION) { + + echo "<!-- view ". $file ." is missing -->"; + } + +} + + +/** render asap + * --- + * render_view then output code + * so that client will get html to render + * while server calculates next html + */ +function render_asap($view, $data, $sanitize = false) { + render_view($view, $data, $sanitize); + ob_flush(); +} + + +/** render text + * --- + * This function simply echoes text + * with Content-Type and Cache Headers + * @param $data : the text to echo + * @param $cType : Content-Type header + * @param $ttl : cache-contol headers; if false response is not-cached; else (int) chache for $ttl seconds + */ +function render_text( $data, $contentType = 'text/html; charset=UTF-8', $ttl = false ) { + header('Content-Type: '. $contentType); + if ($ttl) { + $ts = gmdate("D, d M Y H:i:s", time() + $ttl) . " GMT"; + header("Expires: {$ts}"); + header("Pragma: cache"); + header("Cache-Control: max-age={$ttl}"); + + } else { + $ts = gmdate("D, d M Y H:i:s") . " GMT"; + header("Expires: {$ts}"); + header("Last-Modified: {$ts}"); + header("Pragma: no-cache"); + header("Cache-Control: no-cache, must-revalidate"); + } + echo htmlspecialchars($data); +} + + +/** reply_json + * --- + * transform a php-array to json and echo to client + * Can be used for API calls + * + * @param $data : the php array) + * @param $ttl : cache-contol headers; if false response is not-cached; else chache for $ttl seconds + */ +function reply_json( $data, $ttl = false ) { + header('Content-Type: application/json'); + if ($ttl) { + $ts = gmdate("D, d M Y H:i:s", time() + $ttl) . " GMT"; + header("Expires: {$ts}"); + header("Pragma: cache"); + header("Cache-Control: max-age={$ttl}"); + + } else { + $ts = gmdate("D, d M Y H:i:s") . " GMT"; + header("Expires: {$ts}"); + header("Last-Modified: {$ts}"); + header("Pragma: no-cache"); + header("Cache-Control: no-cache, must-revalidate"); + } + echo json_encode($data, JSON_UNESCAPED_UNICODE); +} + + + + +// HELPER FUNCTIONS +// ----------------------------------------------------------------------------- + + +/** link asset + * --- + * the function defines the assets (css or js) + * to be loaded on the client (creates the HTML) + * @param $type : type of asset [css|js] + * @param $assetIDs : an array of ('assetID' => 'paramaters') + * + * example calls: + * load_asset('css', ['main' => 'media="all"', 'filters' => 'media="all"']); + * load_asset( 'js', ['jquery' => '', 'lazyloader' => 'async']); + */ +function link_asset( $type, $assetIDs, $paramatres = '' ) { + + $code = ""; // code to return + + // make sure $assetIDs is array (for coding simplicity) + if (!is_array($assetIDs)) { + $assetIDs = [ $assetIDs ]; + } + + // check asset type and construct all asset inserts + switch ($type) { + + case 'font': + foreach($assetIDs as $asset) { + $code .= "\n\t<link href='". FONTS_DIR . FONT_FILES[$asset] ."' as='font' type='font/woff2' {$paramatres}/>"; + } + break; + + case 'js': + foreach($assetIDs as $asset) { + $code .= "\n\t<script src='". JS_DIR . JS_LIBRARIES[$asset] ."' id='{$asset}' {$paramatres}></script>"; + } + break; + + case 'css': + default: + foreach($assetIDs as $asset) { + $code .= "\n\t<link rel='stylesheet' href='". CSS_DIR . CSS_FILES[$asset] ."' id='{$asset}' {$paramatres} />"; + } + } + + echo $code; +} + + +/** sanitize_output (recursive) + * --- + * Sanitizes data that are about to rendered + * (usually when render_view() is called). + * NOTE: mitigates XSS attachs + * @param $data: (array) + */ +function sanitize_output($data) { + //// // check https://stackoverflow.com/questions/2002710/php-how-to-perform-htmlspecialchar-on-an-array-of-arrays + + //// $output = array_map("myFunc", $data); + global $secure; + + $output = array(); + foreach($data as $key => $val) { + + if (is_string($val)) { + $output[$key] = htmlspecialchars(remove_invisible_characters($val)); + + } else if (is_array($val)) { + $output[$key] = sanitize_output($val); + + } else { + $output[$key] = $val; + } + } + return $output; +} + + +/** remove_invisible_characters() + * --- + * @used by sanitize_output() + */ +function remove_invisible_characters($str, $url_encoded = TRUE) +{ + $non_displayables = array(); + + // every control character except newline (dec 10), + // carriage return (dec 13) and horizontal tab (dec 09) + if ($url_encoded) { + $non_displayables[] = '/%0[0-8bcef]/i'; // url encoded 00-08, 11, 12, 14, 15 + $non_displayables[] = '/%1[0-9a-f]/i'; // url encoded 16-31 + $non_displayables[] = '/%7f/i'; // url encoded 127 + } + + $non_displayables[] = '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S'; // 00-08, 11, 12, 14-31, 127 + + do { + $str = preg_replace($non_displayables, '', $str, -1, $count); + } while ($count); + + return $str; +} + + +/** html + * --- + * outputs code as html + * + * @param $str (string) + * @return html5 (string) + */ +function html($str) { + if (!isset($str) || $str== null) return; + if (($str == '') || is_numeric($str)) return $str; + return htmlspecialchars_decode($str, ENT_QUOTES|ENT_HTML5); +} + + +/** set_headers + * --- + * set custom response-Headers + * + * @param $contentType + * @param $ttl: int or false) + * @param $more: array of (content-type => content-value) pairs + */ +function set_headers($contentType = 'text/html; charset=UTF-8', $ttl = false, $more = [] ) { + + // handle common content-type shorcuts + switch ($contentType) { + case 'text': + case 'html': + $contentType = 'text/html; charset=UTF-8'; + break; + case 'json': + $contentType = 'application/json; charset=utf-8'; + break; + case 'js': + $contentType = 'application/javascript; charset=utf-8'; + break; + case 'css': + $contentType = 'text/css'; + break; + default: + // $contentType stays as-is + break; + } + + // send headers + header('Content-Type: '. $contentType); + if ($ttl) { + $ts = gmdate("D, d M Y H:i:s", time() + $ttl) . " GMT"; + header("Expires: {$ts}"); + header("Pragma: cache"); + header("Cache-Control: max-age={$ttl}"); + + } else { + $ts = gmdate("D, d M Y H:i:s") . " GMT"; + header("Expires: {$ts}"); + header("Last-Modified: {$ts}"); + header("Pragma: no-cache"); + header("Cache-Control: no-cache, must-revalidate"); + } + + // send more headers + if ($more != []) { + foreach($more as $header => $value) { + header($header .': '. $value); + } + } + +} + + + |
