From 059e0d95d0c28bc5060e87e146eaf7411f51bf90 Mon Sep 17 00:00:00 2001
From: George Halkiadakis
'; + $suffix = ''; + break; + case 'comment': + default: + $open = ''; + $divider = ":"; + $prefix = ''; + $suffix = ''; + break; + } + + echo $prefix; + + // TIME REPORT + // --------------------------------------------------------------------- + echo "\n\n{$open} Timings {$close}"; + + // valid from php 7.3 + // $start = self::$timeSpots[ array_key_first(self::$timespots) ]; + // $end = self::$timeSpots[ array_key_last(self::$timespots) ]; + $start = self::$timeSpots['start']; + $end = self::$timeSpots['end']; + $all_dt = number_format($end - $start , 4)*1000 ." ms"; + + + + // if more than 2 timespots + // echo dt between each spot + if (count(self::$timeSpots) > 2) { + + // calculate dt between spots + $time_results = array(); + $prev_key = ''; + $prev_time = 0; + foreach(self::$timeSpots as $key => $t) { + if (!$prev_time) { + $prev_time = $t; + $prev_key = $key; + } + else { + $dt = number_format($t - $prev_time , 4)*1000 ." ms"; + $time_results[] = array( + 'part' => "{$prev_key}[..{$key}]", ///$key, + 'dt' => $dt + ); + $prev_key = $key; //// "{$prev_key}[..{$key}]"; + $prev_time = $t; + } + } + + // echo timings + foreach ($time_results as $key => $val) { + echo "\n\t{$open} {$val['part']} {$divider} {$val['dt']} {$close}"; + } + } + + // echo total time + echo "\n\t{$open} total {$divider} {$all_dt} {$close}"; + + + // MEMORY REPORT + // --------------------------------------------------------------------- + echo "\n\n{$open} Memory Usage {$close}"; + + if (count(self::$memoryUse) > 2) { + + foreach (self::$memoryUse as $key => $val) { + echo "\n\t{$open} {$key} {$divider} {$val}MB {$close}"; + } + + } else { + echo "\n\t{$open} memory usage {$divider} ". round(memory_get_usage()/(1024*1024),2) ."MB {$close}"; + } + + echo $suffix; + + } + + } +} diff --git a/core/classes/Cart.php b/core/classes/Cart.php new file mode 100644 index 0000000..4e6f585 --- /dev/null +++ b/core/classes/Cart.php @@ -0,0 +1,71 @@ +uid], 10) + // + // oranize in results = [ + // { + // oid, + // prods: [ + // { + // pid, + // prodlabel, + // count + // }, + // ... + // ] + // }, + // ..., + // ] + // + // return $results + + } +} + + + +/* --- +orders: + : id + : uid (user id) + +order_products + : id + : oid (order id) + : pid (product id) + -- */ \ No newline at end of file diff --git a/core/classes/Database.php b/core/classes/Database.php new file mode 100644 index 0000000..8cac479 --- /dev/null +++ b/core/classes/Database.php @@ -0,0 +1,333 @@ +throw_errors = PRODUCTION ? false : true; + + if (null == $this->connection) { + try { + $this->connection = new PDO( + "mysql:" . PDO_HOST . ";" . "dbname=" . DB_NAME, + DB_USER, + DB_PASS, + array( + PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8'", + PDO::MYSQL_ATTR_LOCAL_INFILE => true + ) + ); + + // handle error reporting + if ($this->throw_errors) { + $this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + } + $this->setTimezone(); + + } catch (PDOException $exc) { handle_exception($exc); } + } + # return $this->connection; + } + + + /** disconnect + * CHECK: not sure if needed + */ + public function disconnect() + { + $this->connection = null; + } + + + /** set Timezone + * (Self-explanatory) + */ + public function setTimezone($timezone = DB_TIMEZONE) { + // $this->connection->prepare($timezone)->execute(); + } + + + /** lastInsertID + * returns the ID of last inserted record + */ + public function lastInsertID() + { + return $this->connection->lastInsertId(); + } + + + /** query + * --- + * set and execute a query safely; + * save results as associative array; DO NOT RETURN RESULTS + * + * @param $sql (string): SQL query + * @param $args (array): array of values to bind into SQL + * @param $pypass (boolean): flag to bypass security check + * + * @return $this (database handler) + */ + public function query($sql, $args=[]) + { + try { + + $stmt = $this->connection->prepare($sql); + + if ($args == []) { + $result = $stmt->execute(); + + } else { + $result = $stmt->execute($args); + + } + + $result = $stmt->fetchAll(PDO::FETCH_ASSOC); + + $this->result = $result; + + return $this; + + } catch (PDOException $exc) { handle_exception($exc); } + + } + + + /** getAll + * --- + * return all resulted records + * use it after db->query(); + */ + public function getAll() + { + return ($this->result === null) ? false : $this->result; + } + + + /** getFirst + * --- + * get first row of the resulted query; + * used when one row is expected + * ex. $db->query('SELECT * FROM users WHERE id = :id',['id'=>1])->getFirst(); + */ + public function getFirst() + { + if (($this->result === null) || ($this->result == [])) { + return false; + + } else { return $this->result[0]; } + } + + + /** getOnly + * --- + * return the first column value of the first row + * used when only one value is needed + * ex. $db->query('SELECT Count(id) FROM table',[])->getOnly(); + */ + public function getOnly() + { + if (($this->result === null) || ($this->result == [])) { + return false; + + } else { return array_values($this->result[0])[0]; } + } + + + /** runQuery + * --- (shortcut method) + * execute a query safely; + * return results as associative array + * @param $sql (string): SQL query + * @param $args (array): array of values to bind into SQL + * @param $pypass (boolean): flag to bypass security check + */ + public function runQuery($sql, $args=[]) + { + return $this->query($sql, $args)->getAll(); + } + + + /** runLimitQuery( sql, args, limit=100, offset = null ) + * set LIMIT / OFFSET clauses in a secure way + * @param $sql (string): SQL query + * @param $args (array): array of values to bind into SQL + * @param $limit (int): LIMIT number + * @param $offset (int): OFFSET number + */ + public function runLimitQuery($sql, $args, $limit = 100, $offset = null) + { + $limitStr = $offsetStr = ""; + + // construct LIMIT clause + if (is_int($limit)) { + $limitStr = " LIMIT {$limit}"; + + // construct OFFSET clause (when a LIMIT pre-exists) + if (is_int($offset)) { + $offsetStr =" OFFSET {$offset}"; + } + } + + $sql = $sql . $limitStr . $offsetStr; + + return $this->runQuery($sql, $args); + } + + + /** insert + * @param $table (string): name of table + * @param $values: an associative of (fieldName => value) pairs + * + * example call: + * --- + * $db->insert('products', + * [ + * 'title' => 'My Dark Chocolate 200g', + * 'text' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit ...', + * 'isFood' => 1, + * 'isToxic' => 0 + * ] + * ); + * + * ...which prepares the SQL query: + * INSERT INTO products (title, text, isFood, isToxic) + * VALUES (:title, :text, :isFood, :isToxic) + * + * ...and injects the values: [ :title => 'My Dark Chocolate 200g' , ... ] + */ + public function insert($table, array $values) + { + $fieldSets = []; + $valueSets = []; + $bindSets = []; + + foreach($values as $key => $val) { + $fieldSets[] = $key; + $valueSets[] =':'. $key; + $bindSets[':'. $key] = $val; + } + + $sql = "INSERT INTO {$table} (". implode(', ', $fieldSets) .") + VALUES (". implode(', ', $valueSets) .")"; + + return $this->runQuery($sql, $bindSets); + } + + + /** update + * @param $table (string): name of table + * @param $values: an associative of (fieldName => value) pairs + * @param $id: an associative of (fieldName => value) index fields + * + * example call: + * --- + * $db->update('products', + * [ 'title' => 'My Chocolate','isFood' => 1 ], + * [ 'id' => 123 ] + * ); + * + * ...which prepares the SQL query: + * UPDATE products SET `title` = :title, `isFood` = :isFood WHERE id = :id + * + * ...and injects: [':title'=> 'My Chocolate' , ':isFood'=> 1 , ':id'=> 123] + */ + public function update( $table, array $values, array $identity) + { + $fieldSets = []; // array of field names + $idSets = []; // array of data-holders + $bindSets = []; // array of data-bindings + + foreach($values as $key => $val) { + $fieldSets[] = "{$key} = :{$key}"; + $bindSets[':'. $key] = $val; + } + + foreach($identity as $key => $val) { + $idSets = "{$key} = :{$key}"; + $bindSets[':'. $key] = $val; + } + + $sql = "UPDATE {$table} SET ". implode(', ', $fieldSets) + ." WHERE ". implode(" AND ", $idSets); + + return $this->runQuery($sql, $bindsArray); + } + + + /** multiInsert( table, fields , values ) + * Construct a multiple-insert clause + * + * @param $table (string): name of table + * @param $fields (array): array with field-names + * @param $values (array): array of value-arrays + * + * example call: + * --- + * $db->multiInsert('order_products', + * [ 'orderID', 'productID', 'unitPrice', 'quantity', 'note' ], + * [ + * [ 124, 102030, 1.25, 5, '' ], + * [ 124, 102040, 10.50, 2, 'some note about product #102040' ], + * [ 124, 102050, 7.20, 3, '' ], + * [ 124, 102060, 3.25, 1, 'some other note' ] + * ] + * ); + */ + public function multiInsert($table, array $fieldsArray, array $valuesArray) + { + if (count($fieldsArray) != count($valuesArray[0])) { + throw new Exception('Fields and value arrays don\'t match.'); + } + + // setup fieldsSet + // ex. "(Title, Price, Status)" + $fieldsSet = ' (`'. implode( + '`, `', // make sure fieldnames are not SQL-bound terms + str_replace('`', '', $fieldsArray) // clean fieldnames + ) .'`) '; + + // setup holders array and bind-values array + // ex. "(:Title1, :Price1, :Status1), (:Title2, :Price2, :Status2), ...", + $holdersArray = []; + $bindsArray = []; + $counter = 1; + foreach($valuesArray as $key => $rowArray) { + $rowHolders = []; + + foreach($itemArray as $key => $val) { + $rowHolders = ':'. $fieldsArray[$key] . $counter; + $bindsArray[ ':'. $fieldsArray[$key] . $counter ] = $val; + } + $holdersArray[] = '('. implode(', ', $rowHolders ) .')'; + $counter++; + } + + $sql = "INSERT INTO {$table}" . $fieldsSet + . ' VALUES '. impload(', ', $holdersArray); + + return $this->runQuery($sql, $bindsArray); + } + +} diff --git a/core/classes/Registry.php b/core/classes/Registry.php new file mode 100644 index 0000000..047fd54 --- /dev/null +++ b/core/classes/Registry.php @@ -0,0 +1,135 @@ +"; + } + + } + + /** 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); + + // NOTE: + // after Rendering a JSON ... + // you probably do not need to export anything else; + die(); + } + + + + + // 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); + } + } + + } + + + /** render file + * + */ + public static function file($path, $madia_type) + { + $content = file_get_contents($path); + header("Content-Type: {$media_type}"); + echo $content; + } + + +} \ No newline at end of file diff --git a/core/classes/Repository.php b/core/classes/Repository.php new file mode 100644 index 0000000..c01d0c1 --- /dev/null +++ b/core/classes/Repository.php @@ -0,0 +1,43 @@ +URL = $_SERVER['REQUEST_URI']; + $this->PATH = (!empty($parsed['path'])) ? urldecode($parsed['path']) : ''; + $this->QUERY = (!empty($parsed['query'])) ? urldecode($parsed['query']) : false; + $this->HOST = $_SERVER['HTTP_HOST']; + $this->PORT = $_SERVER['SERVER_PORT']; + $this->TIME = $_SERVER['REQUEST_TIME']; + $this->IP = $_SERVER['REMOTE_ADDR']; + + $this->METHOD = strtolower($_SERVER['REQUEST_METHOD']); + + $this->GET = $_GET; // $_GET should only used + // to request data or specify options (never to perform + // system-changes) thus should not need any validation; + // * If (for any reason) you requide $_GET sanitization + // enable it later on the method's code + + $this->FILES = $_FILES; // TODO: sanitize the files array + + // $this->INTERFACE = php_sapi_name(); + + $this->AGENT = $_SERVER['HTTP_USER_AGENT'] ?? 'unknown'; + $this->SIGNATURE = sha1( + $_SERVER['HTTP_USER_AGENT'] ?? 'unknown' + . $_SERVER['HTTP_ACCEPT'] ?? '' + . $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? '' + . $_SERVER['HTTP_ACCEPT_ENCODING'] ?? '' + ); + + // sanitize user input + // if (isset($_GET)) { $this->GET = $this->sanitize($_GET); } + $this->GET = $this->sanitize($_GET); + if (isset($_POST)) { $this->POST = $this->sanitize($_POST); } + if (isset($_COOKIE)) { $this->COOKIE = $this->sanitize($_COOKIE); } + + // check anti-CSRF token if needed + // (again, GET requests should not need CSRF cheking) + if (in_array($this->METHOD, ['post', 'put', 'patch', 'delete'])) { + // TODO: only if CSRF protection enabled... + $this->checkCsrfToken(); + } + + } + + + /** + * Check: + * + * 1. + * https://dev.to/anastasionico/good-practices-how-to-sanitize-validate-and-escape-in-php-3-methods-139b + * + * 2. + * https://benhoyt.com/writings/dont-sanitize-do-escape/ + * + */ + private function sanitize($array) + { + // TODO: + // ... + return $array; + } + + + public function checkCsrfToken() + { + // TODO: + // ... + // if SCRF-token is not valideted, serve 403 + return true; + } + + + + + public function isAjax(): bool + { + // check headers 'XMLHttpRequest' == $this->headers->get('X-Requested-With'); + } + + + public function isSecure(): bool + { + // chech if HTTPS + } + + + public function hasSession(): bool + { + // chech if Session exist + } + + + /** + * Get the user making the request. + * + * @param string|null $guard + * @return mixed + */ + public function user($guard = null) + { + // return call_user_func($this->getUserResolver(), $guard); + } + + public function getUserResolver() + { + // return $this->userResolver ?: function () { + // + // }; + } + + /** + * Set the user resolver callback. + * + * @param \Closure $callback + * @return $this + */ + public function setUserResolver(Closure $callback) + { + // $this->userResolver = $callback; + // return $this; + } + + + +} + diff --git a/core/classes/Route.php b/core/classes/Route.php new file mode 100644 index 0000000..072723c --- /dev/null +++ b/core/classes/Route.php @@ -0,0 +1,97 @@ + $expression, + 'function' => $function, + 'method' => strtolower($method) + )); + } + + + /** notFound($function) + * --- + * @param $function : call back function to be executed + */ + public static function notFound($function) + { + self::$notFound = $function; + } + + + /** run() + * --- + * Parse request ; Find mathing route ; + * then call route's function + * usualy a Controller::method([poarametres]) + */ + public static function run(Request $request) + { + // $request = Registry::get('REQUEST'); + $path = $request->PATH; // request path + $method = $request->METHOD; // request method + + $path_match_found = false; + $route_match_found = false; + + foreach(self::$routes as $route) { + + // If method matched check the path + if ($route['method'] == $method || $method == 'any') { + + // Add 'find string start' automatically + $route['expression'] = '^'.$route['expression']; + + // Add 'find string end' automatically + $route['expression'] = $route['expression'].'$'; + + // Check path match + if (preg_match('#'. $route['expression'] .'#', $path, $matches)) { + + $route_match_found = true; + + array_shift($matches); // Always remove first element. This contains the whole string + + call_user_func_array($route['function'], $matches); + break; // Do not check other routes + + } + } + } + + // No matching route was found + if (!$route_match_found) { + header("HTTP/1.0 404 Not Found"); + if (self::$notFound) { + call_user_func_array(self::$notFound, []); + } + } + + } + +} diff --git a/core/classes/Security.php b/core/classes/Security.php new file mode 100644 index 0000000..61979db --- /dev/null +++ b/core/classes/Security.php @@ -0,0 +1,923 @@ +', '<', '>', + "'", '"', '&', '$', '#', + '{', '}', '[', ']', '=', + ';', '?', '%20', '%22', + '%3c', // < + '%253c', // < + '%3e', // > + '%0e', // > + '%28', // ( + '%29', // ) + '%2528', // ( + '%26', // & + '%24', // $ + '%3f', // ? + '%3b', // ; + '%3d' // = + ); + + public $charset = 'UTF-8'; // Character set @var (string) Will be overridden by the constructor. + + protected $_xss_hash; // XSS Hash: @var (string) Random Hash for protecting URLs. + + protected $_csrf_hash; // CSRF Hash: @var (string) Random hash for Cross Site Request Forgery protection cookie + + // CSRF Expire time @var (int) + // Expiration time for Cross Site Request Forgery protection cookie. + protected $_csrf_expire = 7200; // defaults to 2hours (=7200 seconds) + + // CSRF Token name: @var (string) Token name for Cross Site Request Forgery protection cookie. + protected $_csrf_token_name = 'pi_csrf_token'; + + // CSRF Cookie name: @var (string) Cookie name for Cross Site Request Forgery protection cookie. + protected $_csrf_cookie_name = 'pi_csrf_token'; + + // List of never allowed strings @var (array) + protected $_never_allowed_str = array( + 'document.cookie' => '[removed]', + '(document).cookie' => '[removed]', + 'document.write' => '[removed]', + '(document).write' => '[removed]', + '.parentNode' => '[removed]', + '.innerHTML' => '[removed]', + '-moz-binding' => '[removed]', + '' => '-->', + ' '<![CDATA[', + '