summaryrefslogtreecommitdiff
path: root/public/app/controllers
diff options
context:
space:
mode:
authorGeorge Halkiadakis <gchalkiadakis@sklavenitis.co.gr>2023-04-17 12:12:22 +0300
committerGeorge Halkiadakis <gchalkiadakis@sklavenitis.co.gr>2023-04-17 12:12:22 +0300
commit68fc9e55e538e03f94509731ab41a0c8cf96710f (patch)
tree3edb1e81864ac3eb35ba0fe8087ed24da1b812bf /public/app/controllers
parent84b2da85a1b452922dadb6a609533b9945afe51d (diff)
downloadclassroom-68fc9e55e538e03f94509731ab41a0c8cf96710f.tar.gz
classroom-68fc9e55e538e03f94509731ab41a0c8cf96710f.tar.bz2
classroom-68fc9e55e538e03f94509731ab41a0c8cf96710f.zip
setup a real server environment (apache DocumentRoot=/var/www/public)
Diffstat (limited to 'public/app/controllers')
-rw-r--r--public/app/controllers/Auth.php330
-rw-r--r--public/app/controllers/Product.php52
-rw-r--r--public/app/controllers/Resolve.php91
-rw-r--r--public/app/controllers/admin/.gitkeep0
-rw-r--r--public/app/controllers/api/.gitkeep0
-rw-r--r--public/app/controllers/api/Common_api.php281
-rw-r--r--public/app/controllers/api/Doc_api.php34
-rw-r--r--public/app/controllers/cli/CliContoller.php85
8 files changed, 873 insertions, 0 deletions
diff --git a/public/app/controllers/Auth.php b/public/app/controllers/Auth.php
new file mode 100644
index 0000000..f215388
--- /dev/null
+++ b/public/app/controllers/Auth.php
@@ -0,0 +1,330 @@
+<?php
+namespace app\controllers;
+
+use Registry;
+use Render;
+
+// user classes and models
+use app\extends\Classroom_user;
+use app\extends\Classroom_manager;
+use app\models\admin\User_model;
+
+use app\extends\Send_mail;
+use app\extends\Mail_jet;
+
+
+/** class Auth
+ *
+ * handles user's Authentication and Authorizarion
+ *
+ */
+class Auth {
+
+ /** login
+ *
+ * checks visitor's credentials;
+ * if valid, authenticates user
+ *
+ */
+ public static function login()
+ {
+ $req = Registry::get('REQUEST');
+
+ // get the record of the target user
+ $record = User_model::checkUser($req->POST['email']);
+
+ // if no user exists, return false
+ if ($record === false) return false;
+
+ // user is valid; check user password
+ // create a user object
+ $user = (new Classroom_user())
+ ->setID($record['id'])
+ ->setUserName($record['email'])
+ ->setPassword($record['password'])
+ ->setEnabled($record['active']);
+
+ // let user manager to validate user credentials
+ $userManager = new Classroom_manager();
+
+ if ($userManager->isPasswordValid($user, $req->POST['password'])) {
+
+ // get user's security attributes
+ $attributes = User_Model::getUser($record['id']);
+ $user
+ ->setRoles(json_decode($attributes['Roles_json']))
+ ->setPrivileges(
+ array_merge(
+ json_decode($attributes['RootPrivileges_json']),
+ self::merge_lists_array(
+ json_decode($attributes['SubPrivileges_json'])
+ )
+ )
+ );
+
+ // regeneration session ID (prevent session fixation)
+ session_regenerate_id();
+ // set cookie for connected user
+ setcookie(
+ 'cluser',
+ 'connected',
+ time()+60*60*8, // 8 hours
+ '/'
+ );
+
+
+ // login OK, set Token in session
+ $userManager->createUserToken($user);
+ return true;
+
+ } else {
+ return false;
+ }
+ }
+
+
+ /**
+ * merges an array of lists to one list
+ */
+ private static function merge_lists_array( $list )
+ {
+ $current = [];
+ foreach($list as $sublist) {
+ $current = array_merge($current, $sublist);
+ }
+ return $current;
+ }
+
+
+ /** activate
+ * resolves a call like: /account/activate?ticket=ca42d68cfba5fbbafeacc010b8e3a551
+ */
+ public static function activate()
+ {
+ $req = Registry::get('REQUEST');
+
+ // get the record of the target user
+ $check = User_model::activate($req->GET['ticket']);
+
+ if ($check == true) {
+ Render::view('/error/general', [
+ 'title' => ACCOUNT_ACTIVATED_TITLE,
+ 'message' => ACCOUNT_ACTIVATED_MESSAGE
+ ]);
+
+ } else {
+ Render::view('/error/general', [
+ 'title' => NOT_VALID_ACTIVATION_TITLE,
+ 'message' => NOT_VALID_ACTIVATION_MESSAGE
+ ]);
+ }
+
+ }
+
+ /** register
+ *
+ * Method for new user registration
+ *
+ */
+ public static function register()
+ {
+ $userManager = new Classroom_manager();
+ $req = Registry::get('REQUEST');
+
+ // create a salted password hash
+ $password = $userManager->cryptPassword($req->POST['password']);
+
+ // echo $password; print_r($req->POST); die(); // OK!
+
+ $user = (new Classroom_user())
+ ->setUserName($req->POST['email'])
+ ->setPassword($password)
+ ->setRoles([ READER ]) // Role: authorized reader
+ ->setPrivileges([]); // none privilege until acount confirmation
+
+ // create user record
+ $activation_code = User_model::registerUser($req->POST, $password);
+
+ // TODO:
+ // handle error on user registration
+ // ...
+ //
+ // if ($activatopn_code[] == -1) {
+ // return [
+ // 'success' => false,
+ // 'message' => REGISTRATION_USER_EXISTS
+ // ];
+ // }
+
+ $send_mail = Send_mail::send_activation_code([
+ 'email' => $req->POST['email'],
+ 'name' => $req->POST['name'] .' '. $req->POST['surname'],
+ 'code' => $activation_code['activation']
+ ]);
+
+ // Send replies
+ if ($send_mail) {
+ return [
+ 'success' => true,
+ 'message' => REGISTRATION_SUCCESS
+ ];
+
+ } else {
+ return [
+ 'success' => false,
+ 'message' => 'error on sending email'
+ ];
+ }
+
+ }
+
+
+ public static function is_connected()
+ {
+ $manager = new Classroom_manager();
+ if ($manager->hasUserToken()) {
+
+ echo 'user is connected';
+ $token = $manager->getUserToken();
+ $user = $token->getUser();
+
+ return $user;
+
+ } else {
+ echo 'user is not connected';
+ return false;
+ }
+ }
+
+
+
+
+
+
+ public static function logout()
+ {
+ $userManager = new UserManager();
+ $userManager->logout();
+
+ // remove user-conected cookie
+ if (isset($_COOKIE['cluser'])) {
+ unset($_COOKIE['cluser']);
+ setcookie('cluser', null, -1, '/');
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+
+
+ /** isGranted( ROLE )
+ *
+ * checks if the user is granted (some of) the specified role(s)
+ * to access the source
+ *
+ * NOTE:
+ * if no roles are specified then user is granted
+ * (because every user is granted the 'no-role')
+ *
+ * @param $roles (array): array of roles to check (if any is granted)
+ *
+ */
+ public static function isGranted($roles = [])
+ {
+ // no role required ? user is granted access
+ if ($roles == []) return true;
+
+ // else, UserManager knows if user isGranted
+ $userManager = new UserManager();
+ if ($userManager->isGranted($roles)) {
+ return true;
+
+ } else {
+ return false;
+ }
+ }
+
+
+ /** hasPermition( PERMIT )
+ *
+ * checks if the user owns the specified permition
+ * to access the source
+ *
+ */
+ public static function hasPermition($permit = [])
+ {
+ if ($permit == []) return true;
+ }
+
+
+ /** isAuthenticated()
+ *
+ * chechs if the user's roles and permitions
+ * satisfy the specified requirements
+ * to access the source
+ *
+ * @param $requirements (array of rules-array)
+ *
+ * example:
+ * [
+ * [
+ * role => ['editor','designer']
+ * permition => ['10', '12', '18']
+ * ],
+ * [
+ * role => ['admin' , 'developερ']
+ * ],
+ * [
+ * permition => [ 3 ]
+ * ]
+ * ]
+ *
+ * defines (and parses to) a requirements rule of:
+ * [
+ * user should be editor or designer
+ * and have permition 10 or 12 or 18
+ * ]
+ * OR
+ * [
+ * user should be an administratoe or developer
+ * ]
+ * OR
+ * [
+ * user should have permition #3
+ * ]
+ *
+ *
+ */
+ public static function isAuthorized($requirements)
+ {
+ $authorized = false;
+ foreach($requirements as $required) {
+ if ( (self::isGranted($required['role'] ?? []))
+ && (self::hasPermition($required['permition'] ?? [])) ) {
+ $authorized = true;
+ }
+ }
+ return $authorized;
+ }
+
+
+
+
+ public static function forgot_pass()
+ {
+ }
+
+
+
+ public static function validate_otp()
+ {
+ }
+
+
+
+}
+
+
+// NOTE:
+// check: https://netcorecloud.com/tutorials/send-an-email-via-gmail-smtp-server-using-php/ \ No newline at end of file
diff --git a/public/app/controllers/Product.php b/public/app/controllers/Product.php
new file mode 100644
index 0000000..0193ed9
--- /dev/null
+++ b/public/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/public/app/controllers/Resolve.php b/public/app/controllers/Resolve.php
new file mode 100644
index 0000000..7b9222c
--- /dev/null
+++ b/public/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/public/app/controllers/admin/.gitkeep b/public/app/controllers/admin/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/public/app/controllers/admin/.gitkeep
diff --git a/public/app/controllers/api/.gitkeep b/public/app/controllers/api/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/public/app/controllers/api/.gitkeep
diff --git a/public/app/controllers/api/Common_api.php b/public/app/controllers/api/Common_api.php
new file mode 100644
index 0000000..a8bd75f
--- /dev/null
+++ b/public/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/public/app/controllers/api/Doc_api.php b/public/app/controllers/api/Doc_api.php
new file mode 100644
index 0000000..e893552
--- /dev/null
+++ b/public/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/public/app/controllers/cli/CliContoller.php b/public/app/controllers/cli/CliContoller.php
new file mode 100644
index 0000000..310e2ae
--- /dev/null
+++ b/public/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