summaryrefslogtreecommitdiff
path: root/html/app/controllers
diff options
context:
space:
mode:
Diffstat (limited to 'html/app/controllers')
-rw-r--r--html/app/controllers/Product.php52
-rw-r--r--html/app/controllers/Resolve.php91
-rw-r--r--html/app/controllers/admin/.gitkeep0
-rw-r--r--html/app/controllers/api/.gitkeep0
-rw-r--r--html/app/controllers/api/Common_api.php281
-rw-r--r--html/app/controllers/api/Doc_api.php34
-rw-r--r--html/app/controllers/cli/CliContoller.php85
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