";
}
}
/** 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
*/
public static function sections($sections) {
foreach($sections as $sect) {
if ($sect['key'] == '') {
self::view( $sect['view'], $sect['data'] );
} else {
self::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]
*/
public static function view($view, $data=[], $sanitize = false) {
$file = VIEWS_DIRECTORY . $view . '.php';
if (file_exists($file)) {
if (!defined('OUTPUT_STARTED')) define('OUTPUT_STARTED', 1);
extract( $sanitize ? self::sanitize_output($data) : $data );
require( $file );
} else if (!PRODUCTION) {
echo "";
}
}
/** render asap
* ---
* render_view then output code
* so that client will get html to render
* while server calculates next html
*/
public static function asap($view, $data, $sanitize = false) {
self::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
*/
public static function 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
*/
public static function 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']);
*/
public static function 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";
}
break;
case 'js':
foreach($assetIDs as $asset) {
$code .= "\n\t";
}
break;
case 'css':
default:
foreach($assetIDs as $asset) {
$code .= "\n\t";
}
}
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)
*/
public static 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(self::remove_invisible_characters($val));
} else if (is_array($val)) {
$output[$key] = self::sanitize_output($val);
} else {
$output[$key] = $val;
}
}
return $output;
}
/** remove_invisible_characters()
* ---
* @used by sanitize_output()
*/
public static 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)
*/
public static 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
*/
public static 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);
}
}
}
}