diff options
Diffstat (limited to 'html/app/controllers')
| -rw-r--r-- | html/app/controllers/Product.php | 52 | ||||
| -rw-r--r-- | html/app/controllers/Resolve.php | 91 | ||||
| -rw-r--r-- | html/app/controllers/admin/.gitkeep | 0 | ||||
| -rw-r--r-- | html/app/controllers/api/.gitkeep | 0 | ||||
| -rw-r--r-- | html/app/controllers/api/Common_api.php | 281 | ||||
| -rw-r--r-- | html/app/controllers/api/Doc_api.php | 34 | ||||
| -rw-r--r-- | html/app/controllers/cli/CliContoller.php | 85 |
7 files changed, 543 insertions, 0 deletions
diff --git a/html/app/controllers/Product.php b/html/app/controllers/Product.php new file mode 100644 index 0000000..0193ed9 --- /dev/null +++ b/html/app/controllers/Product.php @@ -0,0 +1,52 @@ +<?php + +namespace app\controllers; + +use app\models\market\Product_model; +use app\models\market\ProductCategories_model; + +class Product { + + public static function fromUrl($url, $pathArr) + { + $product = proxy([\app\models\market\Product_model::class, 'fromUrl'], + [ $url ], CACHE_PRODUCT_TTL + ); + + // TODO: + // verify path, else redirect + + if ($product === false) { + header("HTTP/1.0 404 Not Found"); + render_view('error/404', ['message' => 'Of all the things I\'ve lost, I miss my mind the most.']); + return ; + } + + // render page + load_template('products', [ 'data' => [ + 'title' => $product['Title'], + 'seo' => [], + 'tree' => proxy( + [\app\models\market\ProductCategories_model::class, 'tree'], + [], CACHE_ROOT_TTL + ), + 'hierarchyPath' => explode('.', substr($product['Hierarchy'], 1, -1)), + 'sections' => [ + [ + 'view' => 'content/product_list/Product_List_Filters', + 'key' => 'filters', + 'data' => [] + ], + [ + 'view' => 'content/product/product_details', + 'key' => 'product', + 'data' => $product + ] + ] + ] + ]); + + } + + +} diff --git a/html/app/controllers/Resolve.php b/html/app/controllers/Resolve.php new file mode 100644 index 0000000..7b9222c --- /dev/null +++ b/html/app/controllers/Resolve.php @@ -0,0 +1,91 @@ +<?php + +namespace app\controllers; + +use \Benchmark; + +/** Resolve class + * will resolve ambiguous utl-request patterns + * --- + * Then it can route the request to some controller; + * Alternatively it can proccess the request via Models + * and initiate the View engine + */ +class Resolve { + + public static function url($array) + { + $fullUrl = implode('/', $array); + $url = $array[count($array) - 1]; + + + if (!PRODUCTION) Benchmark::add_spot('proxy-page'); + + // proxying check if page with this url + $page = proxy( + [\app\models\cms\Page_model::class, 'fromUrl'], + [ $fullUrl ], CACHE_ROOT_TTL, + PROXY_CACHE_ERRORS + ); + // if page exist, construct page + if ($page !== false) { + // page exists, render page + load_template('base', [ 'page' => [ + 'title' => $page['Title'], + 'seo' => [], + 'sections' => [ + [ + 'view' => 'content/static', + 'key' => 'page', + 'data' => $page + ] + ] + ] + ]); + return ; + } + + + if (!PRODUCTION) Benchmark::add_spot('proxy-category'); + + // proxying check if products category with this url + $category = proxy( + [\app\models\market\ProductCategories_model::class, 'categoryFromUrl'], + [ $fullUrl ], CACHE_CATEGORY_TTL + ); + // if category exist, construct category + if ($category !== false) { + // render page + load_template('products', [ 'data' => [ + 'title' => $category['Title'], + 'seo' => [], + 'tree' => proxy( + [\app\models\market\ProductCategories_model::class, 'tree'], + [], CACHE_ROOT_TTL + ), + 'hierarchyPath' => explode('.', substr($category['Hierarchy'], 1, -1)), + 'sections' => [ + [ + 'view' => 'content/product_list/Product_List_Filters', + 'key' => 'filters', + 'data' => $category + ], + [ + 'view' => 'content/product_list/product_list', + 'key' => 'products', + 'data' => $category['products'] + ] + ] + ] + ]); + return ; + } + + + // not page nor category?: reply 404 + header("HTTP/1.0 404 Not Found"); + render_view('error/404', ['message' => 'Say something I\'m givin\' up on you.']); + + } + +}
\ No newline at end of file diff --git a/html/app/controllers/admin/.gitkeep b/html/app/controllers/admin/.gitkeep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/html/app/controllers/admin/.gitkeep diff --git a/html/app/controllers/api/.gitkeep b/html/app/controllers/api/.gitkeep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/html/app/controllers/api/.gitkeep diff --git a/html/app/controllers/api/Common_api.php b/html/app/controllers/api/Common_api.php new file mode 100644 index 0000000..a8bd75f --- /dev/null +++ b/html/app/controllers/api/Common_api.php @@ -0,0 +1,281 @@ +<?php + +namespace app\controllers\api; + +use \Registry; +use app\models\market\Product_model; +use app\models\market\ProductCategories_model; + +/** Common_api + * --- + * Controller for handling common API calls + * about main tables of the project; + * + * includes aglo for passing special filters and sorting + * (check parse_Where_OrderBy() method) + */ +class Common_api +{ + + /** APITables + * Method returns allowed tables to accept API calls + * The return is an array of [label => data-source] pairs + * where 'label' is a friendly name of the datasource + * (and 'datasource' the actual table/data-source) + */ + public static function APITables() + { + /** allowed tables for api call + * @return: (array) an of api-call arrays + * each api-call array has the folloing form: + * [ label => [ + * 'table' => (string) actual table in database, + * (optional) 'filter' => (array) of allowed filtering fields, + * (optional) 'sort' => (array) of allowed sorting fields, + * ] + * ] + */ + return [ + 'page' => [ + 'table' => 'pages', + 'filter' => ['Title'] + ], + 'product' => [ + 'table' => 'products', + 'filter' => [ 'ID', 'Title' ], + 'sort' => ['ID'] + ], + 'category' => [ + 'table' => 'product_categories', + 'filter' => ['Title', 'Hierarchy', 'Level'] + ] + ]; + } + + + /** select anything on any (allowed table) + * + supports filtering and sorting + * check self::parse_Where_OrderBy() for options; + * sets a limti of 1000 records + */ + public static function table($table) + { + $sources = self::APITables(); + if (array_key_exists($table, $sources)) { + + $query = Registry::get('REQUEST')->QUERY; + + // Get parametres? => parse filters + if ($query !== false) { + + $parsed = self::parse_Where_OrderBy( + $query, + ($sources[$table]['filter'] ?? []), + ($sources[$table][ 'sort' ] ?? []) + ); + $where = $parsed['filter'] ? (' WHERE ' . $parsed['filter']) : ''; + $orderBy = $parsed['sort'] ? (' ORDER BY '. $parsed['sort']) : '' ; + $bindArguments = $parsed['bind']; + + } else { + $where = ''; + $orderBy = ''; + $bindArguments = []; + } + + $db = Registry::use('database'); + $result = $db->runQuery('SELECT * + FROM '. $sources[$table]['table'] + . $where + . $orderBy + .' LIMIT 1000', + $bindArguments + ); + + reply_json([ + 'success' => true, + 'client' => Registry::get('REQUEST')->SIGNATURE, + 'result' => $result + ]); + + } else { + reply_json(['success' => false]); + } + die(); + } + + + /** get record of {table} by ID + * (if table has no ID then return false) + */ + public static function record($table, $id) + { + $sources = self::APITables(); + if (array_key_exists($table, $sources)) { + + $db = Registry::use('database'); + $result = $db->runQuery("SELECT * + FROM ". $sources[$table]['table'] ." + WHERE ID = :id", + [':id' => $id] + ); + reply_json([ + 'success' => true, + 'data' => $result[0] + ]); + + } else { + reply_json(['status' => false]); + + } + die(); + } + + + /** category_by_url + * product categorie by full-friendly-URL + * @param $url (string): full friendly url + */ + public static function category_by_url($url) + { + $category = proxy( + [\app\models\market\ProductCategories_model::class, 'categoryFromUrl'], + [ $url ], CACHE_CATEGORY_TTL + ); + if ($category !== false) { + reply_json([ + 'success' => true, + 'result' => $category + ]); + + } else { + reply_json(['status' => false]); + + } + die(); + } + + /** parse Where & OrderBy + * ------------------------------------------------------------------------- + * parses SAFELY the query string to Where {CONDITIONS} and ORDER BY clauses + * according to the specified rules. + * + * The rules: + * ** filters: ?fieldname=[operator]:value &... + * where operators:[ like | startlike | endlike | eq | gt | gteq | lt |t leq ] + * + * ** Order by: ?_sort=fieldname[:[asc|desc]][,field[,]] + * + * for example: ?id=gt:4&active=1&_sort:reputation:desc,category + * parses to WHERE id > 4 AND active = 4 ORDER BY reputation desc, catetory asc + * + * ------------------------------------------------------------------------- + * arguments: + * @param $query (string): string to be parsed + * @param $filters (array): the allowed fields to apply filters + * @param $sortings (array): the allowed fields to sort the result + * @return array('filter'=>(string) , 'sort'=>(string) , 'bind'=>(array)) + */ + private static function parse_Where_OrderBy($query, $filters=[], $sortings=[]) + { + // break query to [key => value] pairs + parse_str($query, $queryArray); + + $filterClause = []; // array to hold filter/WHERE clauses + $sortClause = []; // array to hold sort/ORDER-BY caluses + $bindings = []; // array to hold variable bindigs + + // parse filers + // --------------------------------------------------------------------- + foreach($queryArray as $filter => $value) { + + if (in_array($filter, $filters)) { + + $parts = explode(':', $value ); // that is => [operator], value + if (count($parts) == 0) { + // forget it + + } else if (count($parts) == 1) { + $filterClause[] = "{$filter} = :{$filter}"; + $bindings[$filter] = $parts[0]; + + } else { + + switch ($parts[0]) { + case 'like': + $filterClause[] = "{$filter} LIKE :{$filter}"; + $bindings[':'.$filter] = '%'.$parts[1].'%'; + break; + + case 'startlike': + $filterClause[] = "{$filter} LIKE :{$filter}"; + $bindings[':'.$filter] = $parts[1].'%'; + break; + + case 'endlike': + $filterClause[] = "{$filter} LIKE :{$filter}"; + $bindings[':'.$filter] = '%'.$parts[1]; + break; + + case 'gt': + $filterClause[] = "{$filter} > :{$filter}"; + $bindings[':'.$filter] = $parts[1]; + break; + + case 'gteq': + $filterClause[] = "{$filter} >= :{$filter}"; + $bindings[':'.$filter] = $parts[1]; + break; + + case 'lt': + $filterClause[] = "{$filter} < :{$filter}"; + $bindings[':'.$filter] = $parts[1]; + break; + + case 'lteq': + $filterClause[] = "{$filter} <= :{$filter}"; + $bindings[':'.$filter] = $parts[1]; + break; + + case 'eq': + default: + $filterClause[] = "{$filter} = :{$filter}"; + $bindings[':'.$filter] = $parts[1]; + } + } + } + } + $whereSQL = ($filterClause == []) + ? false + : implode(' AND ', $filterClause); + + + // parse sort options + // --------------------------------------------------------------------- + if (isset($queryArray['_sort'])) { + $sortTerms = explode(',', $queryArray['_sort']); + + foreach($sortTerms as $term) { + + $parts = explode(':', $term); + if ($parts != [] && in_array($parts[0], $sortings) ) { + $sortClause[] = (count($parts)==1) + ? $parts[0] + : $parts[0] .' '. (($parts[1] == 'desc') ? 'desc' : 'asc'); + } + } + + } + $sortSQL = ($sortClause == []) + ? false + : implode(', ', $sortClause); + + return ([ + 'filter' => $whereSQL, + 'sort' => $sortSQL, + 'bind' => $bindings + ]); + + } + +}
\ No newline at end of file diff --git a/html/app/controllers/api/Doc_api.php b/html/app/controllers/api/Doc_api.php new file mode 100644 index 0000000..e893552 --- /dev/null +++ b/html/app/controllers/api/Doc_api.php @@ -0,0 +1,34 @@ +<?php + +namespace app\controllers\api; + +use app\models\market\ProductCategories_model; +use app\models\Jorge; + +class Doc_api +{ + + /** */ + public static function tree($store = 904) + { + $tree = proxy( + [ProductCategories_model::class,'tree'], + [], CACHE_ROOT_TTL, + PROXY_IGNORE_CACHE + ); + reply_json([ 'success' => true, 'result' => $tree ]); + die(); + } + + + public static function dbDoc() + { + $j = new Jorge(); + reply_json([ + 'success' => true, + 'result' => $j->databaseDocumentation() + ]); + die(); + + } +}
\ No newline at end of file diff --git a/html/app/controllers/cli/CliContoller.php b/html/app/controllers/cli/CliContoller.php new file mode 100644 index 0000000..310e2ae --- /dev/null +++ b/html/app/controllers/cli/CliContoller.php @@ -0,0 +1,85 @@ +<?php + +namespace app\controllers\cli; + +/** CliContoller + * + * Re-generate cashes of commonly used entities + * like: product categories and trendy products + * (proxy calls make use of PROXY_IGNORE_CACHE) + * + * CliController calls Model methods who create + * cashed data with the exact same arguments as + * when called from the web interface; this way + * the Model::method(arguments) triplete creates + * the same keys as via the web interface. + * + * NOTE: Only Cli interface is allowed + * [if (php_sapi_name() != 'cli') return false;] + * + * The Cli-interfaced script can run manualy or + * (most usual scenario) scheduled via a cron job + * in order to refresh cashes befor expiration. + * + * --------------------------------------------- + */ +class CliContoller +{ + + + /** (re-) cacheCategoriesTree + * regenerate cache for product_categories tree + * @param (void) + */ + public static function cacheCategoriesTree() + { + // Only Cli interface is allowed + if (php_sapi_name() != 'cli') return false; + + # PROXY call: + # MARKET \ ProductCategories_model::tree(): + # + Refresh Cache + # -------------------------------------- + echo textColor("Creating category_products tree Cache ... ", \NORMAL); + $reply = proxy( + [\app\models\market\ProductCategories_model::class, 'tree'], + [], \CACHE_ROOT_TTL, + \PROXY_IGNORE_CACHE + ); + echo ($reply == false) ? textColor("Failed\n", \FAIL) : textColor("Done!\n", \SUCCESS); + } + + + + public static function getProductCategories() + { + return \Registry::use('database')->runQuery( + "SELECT * FROM product_categories", + [] + ); + } + + + + /** (re-) cacheCategoryByUrl( url ) + * regenerate cache of a category_product + * by category's friendly url + * @param $url (string): the category's friendly url + */ + public static function cacheCategoryByUrl(string $url) + { + // Only Cli interface is allowed + if (php_sapi_name() != 'cli') return false; + + # PROXY call: + # MARKET \ ProductCategories_model::tree(): + # + Refresh Cache + # -------------------------------------- + $reply = proxy( + [\app\models\market\ProductCategories_model::class, 'categoryFromUrl'], + [ $url ], \CACHE_CATEGORY_TTL, + \PROXY_IGNORE_CACHE + ); + echo ($reply == false) ? textColor("Failed\n", \FAIL) : textColor("Done!\n", \SUCCESS); + } +}
\ No newline at end of file |
