diff options
| author | George Halkiadakis <gchalkiadakis@sklavenitis.co.gr> | 2023-04-27 03:47:30 +0300 |
|---|---|---|
| committer | George Halkiadakis <gchalkiadakis@sklavenitis.co.gr> | 2023-04-27 03:47:30 +0300 |
| commit | 059e0d95d0c28bc5060e87e146eaf7411f51bf90 (patch) | |
| tree | 7c21ac432f16dde2329a0d5954d70711eceb48e6 /public/app/models | |
| parent | 26cd8ee99659ef1926c96a049c93645ffc9b169d (diff) | |
| download | gyraf1gov-059e0d95d0c28bc5060e87e146eaf7411f51bf90.tar.gz gyraf1gov-059e0d95d0c28bc5060e87e146eaf7411f51bf90.tar.bz2 gyraf1gov-059e0d95d0c28bc5060e87e146eaf7411f51bf90.zip | |
skeleton commit; based on an anom project
Diffstat (limited to 'public/app/models')
| -rw-r--r-- | public/app/models/.gitkeep | 0 | ||||
| -rw-r--r-- | public/app/models/Jorge.php | 172 | ||||
| -rw-r--r-- | public/app/models/_info.md | 150 | ||||
| -rw-r--r-- | public/app/models/admin/History_model.php | 34 | ||||
| -rw-r--r-- | public/app/models/admin/Privilege_model.php | 42 | ||||
| -rw-r--r-- | public/app/models/admin/User_model.php | 299 | ||||
| -rw-r--r-- | public/app/models/cms/Course_model.php | 314 | ||||
| -rw-r--r-- | public/app/models/cms/Media_model.php | 36 | ||||
| -rw-r--r-- | public/app/models/cms/Page_model.php | 18 | ||||
| -rw-r--r-- | public/app/models/market/Market_repository.php | 235 | ||||
| -rw-r--r-- | public/app/models/market/Payment_template.txt | 98 | ||||
| -rw-r--r-- | public/app/models/market/ProductCategories_model.php | 199 | ||||
| -rw-r--r-- | public/app/models/market/Product_model.php | 49 | ||||
| -rw-r--r-- | public/app/models/todo.md | 132 |
14 files changed, 1778 insertions, 0 deletions
diff --git a/public/app/models/.gitkeep b/public/app/models/.gitkeep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/public/app/models/.gitkeep diff --git a/public/app/models/Jorge.php b/public/app/models/Jorge.php new file mode 100644 index 0000000..5d79007 --- /dev/null +++ b/public/app/models/Jorge.php @@ -0,0 +1,172 @@ +<?php + +namespace app\models; + +use \Registry; + +/** Jorge + * a database reference and adminitration toolkit + * --- + * The name Jorge comes from Uberto Eco's book: 'il nome della rosa', + * (Jorge is the blind monk who supervises the monastery's library ;) + */ +class Jorge +{ + + /** tables + * array to keep all database-tables' critical information. + * @key create (array) includes all 'CREATE TABLE' statements + * @key relate (array) includes relations between tables + * @key fields (array) includes all important fields (when no select*) + * @key filter (array) includes all fields used to filter results + */ + private $tables = [ + 'create' => [], + 'relate' => [], + 'fields' => [] + ]; + + public function __construct() + { + $db = Registry::use('database'); + + // get all tables + $tableList = $db->runQuery("SHOW TABLES", []); + + // get all CREATE TABLE statements + foreach($tableList as $tableArray) { + $table = array_shift($tableArray); + $create = $db->runQuery("SHOW CREATE TABLE {$table}", []); + $this->tables['create'][$table] = $create[0]['Create Table']; + } + + // define relations + $this->tables['relate'] = [ + + 'lesson' => [ + 'course' => 'course.id = lesson.course_id', + 'lesson_media' => 'lesson_media.lesson_id = lesson.id', + 'lesson_privilege' => 'lesson_privilege.lesson_id = lesson.id' + ], + + 'page' => [ + 'page_media' => 'page_media.page_id = page.id' + ], + + 'user' => [ + 'user_privilege' => 'user_privilege.user_id = user.id' + ], + + 'privilege' => [ + 'user_privilege' => 'user_privilege.privilege_id = privilege.id' + ], + + ]; + + $this->extractTableFields(); + + // return true; + } + + + /** Create... + * one or more database tables + * --- + * @param $datasource (string|void) : table name or none + * if none then all tables will be created + * @return (boolean) + * + * NOTE: + * for safety reasons this method is not executing SQL; + * it just echoes the SQL that needs to be executed in + * order to crate all database tables. + */ + public function create($datasource = '') + { + // if no datasource passed then datasource is all tables + if ($datasource == '') { + $datasource = array_keys(self::$tables['create']); + + } else { + // if datasource exist, make it an array + if (array_key_exists($datasource, self::$tables['create'])) { + $datasource = [ $datasource ]; + + } else { + // table does not exist + return fasle; + } + } + + $sql = ""; + foreach( $datasource as $table ) { + $sql .= "-- CREATE ". $table .";\n". self::$tables['create'][$table] . "\n\n"; + } + + return ['sql' => $sql]; + } + + + /** Calculate fields of every table + * --- + * Parse every CREATE SQL statement and extract list of fields; + * Algorithm implements linear-parsing (faster than a recursive one) + */ + private function extractTableFields() + { + // words used by SQL that can not be field names + $nonFields = ['PRIMARY', 'KEY', '(', ')', 'CONSTRAINT', 'UNIQUE']; // reduced list (used on CREATE) + + foreach( $this->tables['create'] as $table => $sqlCreate ) { + + $fields = []; // variable to hold the extracted fields + + // get text between first open-parentesis and last close-parentesis + // this is waht lies between 'CREATE TABLE table(' and ') [ENGINE whatever]' + // (including the patenteses) + preg_match_all( + "/\((((?>[^()]+)|(?R))*)\)/", + str_replace("\n", '', $sqlCreate), + $match + ); + // remove 1st+last parentesis + $mainDefinitions = substr($match[0][0], 1, -1); + + // replace comma (,) on sub-parenthesis + // ex: 'decimal(18, 2)' turns to 'decimal(18: 2)' + // or: 'PRIMARY KEY (id1, id2)' turns to 'PRIMARY KEY (id1: id2)' + $sentences = preg_replace( + '/\\(([0-9a-zA-Z_`]*)[ ,]([0-9a-zA-Z_` ]*)\\)/', + '($1:$2)', + $mainDefinitions, + -1 + ); + + // devide the banth of sentences to field definitions + $defines = explode(',', $sentences); + + // now each denine holds a full field definition + // like: `ID` int(11) NOT NULL AUTO_INCREMENT + + foreach($defines as $def) { + // check the first term of each sentence + $terms = explode(' ', trim($def,)); + $term = array_shift($terms); + + if (!in_array($term, $nonFields)) { + $fields[ str_replace('`', '', $term) ] = implode(' ', $terms); + } + } + + $this->tables['fields'][$table] = $fields; + } + + return; + } + + public function databaseDocumentation() + { + return $this->tables['fields']; + } + +}
\ No newline at end of file diff --git a/public/app/models/_info.md b/public/app/models/_info.md new file mode 100644 index 0000000..5504523 --- /dev/null +++ b/public/app/models/_info.md @@ -0,0 +1,150 @@ + +# info about the structrure of the models + +According to the former impementation (atcom) the project will need to handle 200+ tables. +The former project includes 209 tables with 5 or more rows (and 325 tables totaly) + + + +## Database schema + +Main tables and recomended constraints: + +``` +SET NAMES utf8; +SET time_zone = '+00:00'; +SET foreign_key_checks = 0; +SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO'; + +SET NAMES utf8mb4; + +DROP TABLE IF EXISTS `course`; +CREATE TABLE `course` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `parent_id` int(11) NOT NULL COMMENT 'if 0 then this is a root course', + `label` varchar(140) COLLATE utf8mb4_unicode_ci NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + + +DROP TABLE IF EXISTS `lesson`; +CREATE TABLE `lesson` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `course_id` int(11) NOT NULL, + `title` varchar(140) COLLATE utf8mb4_unicode_ci NOT NULL, + `body` text COLLATE utf8mb4_unicode_ci NOT NULL, + `published` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0 = unpublished, 1 = published', + PRIMARY KEY (`id`), + KEY `course_id` (`course_id`), + CONSTRAINT `lesson_ibfk_1` FOREIGN KEY (`course_id`) REFERENCES `course` (`id`), + CONSTRAINT `lesson_ibfk_2` FOREIGN KEY (`course_id`) REFERENCES `course` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + + +DROP TABLE IF EXISTS `lesson_media`; +CREATE TABLE `lesson_media` ( + `lesson_id` int(11) NOT NULL, + `media_id` int(11) NOT NULL, + PRIMARY KEY (`lesson_id`,`media_id`), + KEY `media_id` (`media_id`), + CONSTRAINT `lesson_media_ibfk_1` FOREIGN KEY (`lesson_id`) REFERENCES `lesson` (`id`), + CONSTRAINT `lesson_media_ibfk_2` FOREIGN KEY (`lesson_id`) REFERENCES `lesson` (`id`), + CONSTRAINT `lesson_media_ibfk_3` FOREIGN KEY (`lesson_id`) REFERENCES `lesson` (`id`), + CONSTRAINT `lesson_media_ibfk_4` FOREIGN KEY (`media_id`) REFERENCES `media` (`id`), + CONSTRAINT `lesson_media_ibfk_5` FOREIGN KEY (`lesson_id`) REFERENCES `lesson` (`id`) ON DELETE NO ACTION, + CONSTRAINT `lesson_media_ibfk_6` FOREIGN KEY (`media_id`) REFERENCES `media` (`id`) ON DELETE NO ACTION, + CONSTRAINT `lesson_media_ibfk_7` FOREIGN KEY (`lesson_id`) REFERENCES `lesson` (`id`) ON DELETE NO ACTION, + CONSTRAINT `lesson_media_ibfk_8` FOREIGN KEY (`media_id`) REFERENCES `media` (`id`) ON DELETE NO ACTION +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + + +DROP TABLE IF EXISTS `lesson_privilege`; +CREATE TABLE `lesson_privilege` ( + `lesson_id` int(11) NOT NULL, + `privilege_id` int(11) NOT NULL COMMENT 'minimum privilege required to access the lesson', + KEY `lesson_id` (`lesson_id`), + KEY `privilege_id` (`privilege_id`), + CONSTRAINT `lesson_privilege_ibfk_1` FOREIGN KEY (`lesson_id`) REFERENCES `lesson` (`id`), + CONSTRAINT `lesson_privilege_ibfk_2` FOREIGN KEY (`privilege_id`) REFERENCES `privilege` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + + +DROP TABLE IF EXISTS `media`; +CREATE TABLE `media` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `label` varchar(140) COLLATE utf8mb4_unicode_ci NOT NULL, + `type` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL, + `path` varchar(320) COLLATE utf8mb4_unicode_ci NOT NULL, + `referable` tinyint(4) NOT NULL DEFAULT '1' COMMENT '0 = hidden, 1 = referable', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + + +DROP TABLE IF EXISTS `page`; +CREATE TABLE `page` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `title` varchar(140) COLLATE utf8mb4_unicode_ci NOT NULL, + `body` text COLLATE utf8mb4_unicode_ci NOT NULL, + `published` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0 = unpublished; 1 = published', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + + +DROP TABLE IF EXISTS `page_media`; +CREATE TABLE `page_media` ( + `page_id` int(11) NOT NULL, + `media_id` int(11) NOT NULL, + KEY `page_id` (`page_id`), + KEY `media_id` (`media_id`), + CONSTRAINT `page_media_ibfk_1` FOREIGN KEY (`page_id`) REFERENCES `page` (`id`) ON DELETE NO ACTION, + CONSTRAINT `page_media_ibfk_2` FOREIGN KEY (`media_id`) REFERENCES `media` (`id`) ON DELETE NO ACTION +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + + +DROP TABLE IF EXISTS `privilege`; +CREATE TABLE `privilege` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `alias` varchar(8) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'keep it simple; use latin', + `label` varchar(140) COLLATE utf8mb4_unicode_ci NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `alias` (`alias`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + + +DROP TABLE IF EXISTS `privilege_inherit`; +CREATE TABLE `privilege_inherit` ( + `higher_id` int(11) NOT NULL COMMENT 'higher priviledges inherit (include) lower ones', + `lower_id` int(11) NOT NULL, + PRIMARY KEY (`higher_id`,`lower_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + + +DROP TABLE IF EXISTS `user`; +CREATE TABLE `user` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `first_name` int(11) NOT NULL, + `last_name` int(11) NOT NULL, + `email` int(11) NOT NULL, + `expiration` int(11) NOT NULL COMMENT 'account expiration date; 0 = never', + `password` int(11) NOT NULL, + `salt` int(11) NOT NULL, + `otp` int(11) NOT NULL COMMENT 'one time password for reset password', + `otp_expiration` int(11) NOT NULL, + `active` int(11) NOT NULL COMMENT 'account flag; 1=active, 0=inactive', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + + +DROP TABLE IF EXISTS `user_privilege`; +CREATE TABLE `user_privilege` ( + `user_id` int(11) NOT NULL, + `privilege_id` int(11) NOT NULL, + PRIMARY KEY (`user_id`,`privilege_id`), + KEY `privilege_id` (`privilege_id`), + CONSTRAINT `user_privilege_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`), + CONSTRAINT `user_privilege_ibfk_2` FOREIGN KEY (`privilege_id`) REFERENCES `privilege` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +``` + + diff --git a/public/app/models/admin/History_model.php b/public/app/models/admin/History_model.php new file mode 100644 index 0000000..04ce630 --- /dev/null +++ b/public/app/models/admin/History_model.php @@ -0,0 +1,34 @@ +<?php +namespace app\models\admin; + +use \Registry; + + +class History_model +{ + + /** track user access + * + */ + public static function trackUserAccess($user_id, $type, $message, $note='_') + { + + Registry::use('database')->query( + "INSERT INTO history + (user_id, `type`, `message`, `note`, `ip`) + VALUES + (:uid, :type, :msg, :note, :ip)", + [ + ':uid' => $user_id, + ':type' => $type, + ':msg' => $message, + ':note' => $note, + ':ip' => Registry::get('REQUEST')->IP + ] + )->lastInsertID(); + + } + + + +}
\ No newline at end of file diff --git a/public/app/models/admin/Privilege_model.php b/public/app/models/admin/Privilege_model.php new file mode 100644 index 0000000..c385484 --- /dev/null +++ b/public/app/models/admin/Privilege_model.php @@ -0,0 +1,42 @@ +<?php + +namespace app\models\admin; + +use Registry; +use Render; + + +class Privilege_model { + + /** privileges + * --- + * get all privileges + * + * @param void + * @return privileges (array) + */ + public static function privileges() + { + return Registry::use('database')->runQuery( + "SELECT * FROM privilege",[] + ); + } + + /** edit privileges + * --- + */ + public static function edit_privileges() + { + // TODO: ... + } + + + /** update course + * --- + */ + public static function update_course() + { + // TODO: ... + } + +}
\ No newline at end of file diff --git a/public/app/models/admin/User_model.php b/public/app/models/admin/User_model.php new file mode 100644 index 0000000..2885dc1 --- /dev/null +++ b/public/app/models/admin/User_model.php @@ -0,0 +1,299 @@ +<?php + +namespace app\models\admin; + +use \Registry; +use app\models\admin\History_model as History; + +class User_model +{ + + /** check user (by index key) + * + * @param $indexKey (string) : key for user identification + * @param $value (string) + * + * @param $email (string): user's email + */ + public static function checkUser($email) + { + $user = Registry::use('database')->query( + "SELECT * FROM user WHERE email = :email AND active = 1", + [ ':email' => $email ] + )->getFirst(); + + // if no user, return false + if ($user === false) return false; + + return $user; + } + + + /** get user (by index key) + * + * user detailed array + * includes all user properties + granted roles + privileges + * + * @param $id (int) : user id + * @param $value (string) + */ + public static function getUser($id) + { + $user = Registry::use('database')->query( + "SELECT user.*, + ( -- construct array (json) of roles granted to user + SELECT CONCAT( + '[', + GROUP_CONCAT(role.id), + ']' + ) + FROM `role` + WHERE role.id IN ( + SELECT user_role.role_id + FROM user_role + WHERE user_role.user_id = :id + ) + ) AS Roles_json, + ( -- construct array of (root-)privileges granted to user + SELECT CONCAT( + '[', + GROUP_CONCAT(privilege.id), + ']' + ) + FROM privilege + WHERE privilege.id IN ( + SELECT user_privilege.privilege_id + FROM user_privilege + WHERE user_privilege.user_id = :id + ) + ) AS RootPrivileges_json, + ( -- construct array of (root-)privileges granted to user + SELECT CONCAT( + '[', + GROUP_CONCAT(privilege.includes), + ']' + ) + FROM privilege + WHERE privilege.id IN ( + SELECT user_privilege.privilege_id + FROM user_privilege + WHERE user_privilege.user_id = :id + ) + ) AS SubPrivileges_json + FROM user + WHERE id = :id", + [ ':id' => $id ] + )->getFirst(); + + + // if no user, return false + if ($user === false) return false; + + + // TODO: + // * merge root+sub privilede lists + // * convert json strings to php arrays + + + // TODO: + // cache user super array + + return $user; + } + + + /** create user + * + * creates user record; + * assigns privileged (usualy defaults); + * creates activation_code + * + * @param $data (array): Request->POST array + * @param $password (string): secure hashed password + * + * @return $activation_code + * + */ + public static function registerUser($data, $password, $privileges = DEFAULT_PRIVILEGES) + { + $required_fields = [ + 'name', + 'surname', + 'email', + 'password' + ]; + + // check required fields + $isOK = true; + foreach($required_fields as $fi) { + if (empty($data[$fi])) $isOK = false; + } + // if empty required fields exists ... return false + if (!$isOK) { + return [ "success" => false, 'error' => EMPTY_REQUIRED_FIELDS ]; + } + + + // TODO: + // check if email exists + // ... + + // create an activation code + $activation_code = md5($data['email'].time().rand(0, 10000)); + + // if isOK go on and... + // create user record + $new_user_id = Registry::use('database')->query( + "INSERT INTO user + (`first_name`, `last_name`, `email`, `password`, `active`, `activation`) + VALUES + (:nam, :surname, :email, :pass, :act, :actcode)", + [ + ':nam' => $data['name'], + ':surname' => $data['surname'], + ':email' => $data['email'], + ':pass' => $password, + ':act' => 0, // needs email confirmation to be activated ... + ':actcode' => $activation_code // ... with the activation code + ] + )->lastInsertID(); + + // set default privileges + self::set_user_privileges($new_user_id, $privileges); + + // set reader role + self::set_user_role($new_user_id, 5); + + // update history + History::trackUserAccess($new_user_id, TRACK_ACCOUNT, 'Create User Account'); + + // return success and user id + return [ + "success" => true, + 'id' => $new_user_id , + 'activation' => $activation_code + ]; + } + + + /** set_user_privileges + * + * @param $privileges (array) + */ + public static function set_user_privileges($user, $privileges) + { + $db = Registry::use('database'); // database connection + foreach($privileges as $pri) { // pri = privilege id + $db->runQuery( + "INSERT INTO user_privilege (user_id, privilege_id) VALUES (:user, :pri)", + [ ':user' => $user, ":pri" => $pri ] + ); + } + return true; + } + + + /** set_user_role + * + * user, role are (int) IDs + */ + public static function set_user_role($user, $role) + { + + Registry::use('database')->runQuery( + "INSERT INTO user_role (user_id, role_id) VALUES (:user, :role)", + [ ':user' => $user, ":role" => $role ] + ); + + return true; + } + + + /** activate + * + * check if activation code is valid; + * if valid, set account active; + * + * @param $ticket (hex/MD5): activation code; + * + */ + public static function activate($ticket) + { + $user = Registry::use('database')->query( + "SELECT * FROM user WHERE activation = :ticket", + [ 'ticket' => $ticket ] + )->getFirst(); + + // if no user with this activation code, return false + if ($user === false) return false; + + // remove activation code from user record + Registry::use('database')->runQuery( + "UPDATE user + SET active = 1, `activation` = NULL + WHERE activation = :ticket", + [ 'ticket' => $ticket ] + ); + + // update history + History::trackUserAccess($user['id'], TRACK_ACCOUNT, 'User Account Activated'); + + return true; + + } + + +} + +/* example query getUser (super-array) +--- -- -- - - - + +SELECT user.*, +( -- array (json) of roles granted to user + SELECT CONCAT('[', GROUP_CONCAT(role.id), ']') + FROM `role` + WHERE role.id IN ( + SELECT user_role.role_id + FROM user_role + WHERE user_role.user_id = 1 + ) +) AS Roles_json, +( + SELECT CONCAT( + '[', + GROUP_CONCAT(privilege.id), + ']' + ) + FROM privilege + WHERE privilege.id IN ( + SELECT user_privilege.privilege_id + FROM user_privilege + WHERE user_privilege.user_id = 1 + ) +) AS RootPrivileges_json, +( + SELECT CONCAT( -- array of array of sub-privileges + '[', + GROUP_CONCAT( -- array (json) of subprivileges + ( + SELECT CONCAT( + '[', + GROUP_CONCAT(included_id), + ']' + ) + FROM privilege_includes + WHERE privilege_id = privilege.id + ) + ), + ']' + ) + FROM privilege + WHERE privilege.id IN ( + SELECT user_privilege.privilege_id + FROM user_privilege + WHERE user_id = 1 + ) +) AS SubPrivileges_json +FROM user +WHERE id = 1 +--- */
\ No newline at end of file diff --git a/public/app/models/cms/Course_model.php b/public/app/models/cms/Course_model.php new file mode 100644 index 0000000..6614993 --- /dev/null +++ b/public/app/models/cms/Course_model.php @@ -0,0 +1,314 @@ +<?php +namespace app\models\cms; + +use \Registry; + + +class Course_model { + + /** COURSES + * ------------------------------------------------------------------------- + */ + + /** get all categories + * + * raw table data (simplest SELECT) + * + */ + public static function get_categories() + { + return Registry::use('database')->runQuery( + "SELECT * FROM course ORDER BY label", + [] + ); + } + + /** courses struct + * + * a super array with almost any info needed about courses + * + * @return (array) ['tree' => ..., 'breadcrumbs' => ... ] + */ + public static function courses_struct() + { + $tree = self::category_tree(); + return [ + 'tree' => $tree, + 'breadcrumbs' => self::breadcrumbs($tree) + ]; + } + + + /** constuct a category_tree + * + * returns a tree representation of the categories + * + * NOTE: + * category_tree() is an expensive method; + * it calls 2 other methods implementing recursive algorithms + * thus it uses many sources to run (particularly RAM). + * Caching the result is strogly recommended. + * + */ + public static function category_tree() + { + $categories = self::get_categories(); // get all categories + + $tree = self::to_tree($categories); // format to a tree + + $tree_wParents = self::tree_parents($tree); // add parents section for each tree-node + + return $tree_wParents; + } + + + /** to_tree + * + * constructs a tree from raw-table data; + * this is a private method and uses a recursive algorithm + * + * @param $dataset (array): flar array of records with id/parent-id pairs + * @return $root (array): id of root category + * + * (**) each node has 2 parts: + * .... .. rec : all record attributes/data as passed into $dataset + * .... .. childs : array of (children) nodes + */ + private static function to_tree($dataset, $root = 0) + { + $return = []; + + // loop data ; search for direct children of root + foreach($dataset as $key => $rec) { + + $child = $rec['id']; + $parent = $rec['parent_id']; + + if ($parent == $root) { // a direct child is found + + unset($dataset[$key]); // remove item (no need to traverse again) + + // Append the child into result array ; parse its children + $return[] = [ + 'rec' => [ + 'id' => $rec['id'], + 'label' => $rec['label'], + 'order' => $rec['order'], + 'parent' => $rec['parent_id'] + ], + 'childs' => self::to_tree($dataset, $child) // recursively + ]; + } + } + return empty($return) ? [] : $return; + } + + + /** tree_parents + * + * adds a section to each tree node with all parents of each node + * + * @param $tree (array) : nodes array (each node has `rec` and `childs` sections ) + * @param $parents (array); DO NOT SET IT (takes values automaticaly) + * @return array of nodes with an extra node[parents] section + * + */ + private static function tree_parents($tree, $parents = []) + { + $tree_with_parents = []; + + foreach($tree as $key => $node) { + // parents to be pushed for node's children + $push_parents = $parents; // parents so far + $push_parents[] = $node['rec']; // this record will be a new parent + + $tree_with_parents[$key] = [ + 'rec' => $node['rec'], + 'parents' => $parents, + 'childs' => ($node['childs'] == []) + ? [] + : self::tree_parents($node['childs'], $push_parents) + ]; + } + + return $tree_with_parents; + } + + + /** all_breadcrumbs + * ------------------------------------------------------------------------- + * + * returns an array of all breadcrumbs + * where array-key of each record is category[id] + * + * NOTE: + * --- + * Course_model::all_breadcrumbs returns an indexed super-array; + * each array item includes a banch of information: [ + * breadcrumb, + * rec: [ id , title ], + * parents: [ [id, title] , ... ] + * childs: [ [id, title] , ... ], + * level + * ] + * + * Use Cases: + * --- + * as a super-array, the output can be used in many cases + * for example... + * into <select-option> form elements + * .. while selecting category for a post + * .. or editing a category + * or directry referring to category's parents/childs + * + * Arguments: + * --- + * @param $tree (array) : category tree (with childs and parents parts) + * @param $detimiter (string, optional) : string to split breadcrumb's path-nodes + * @param $exception (int, optional) : id of category to exclude (subcategories shall be excluded too) + * @param $l (int, not-pass) : depth level of the node; DO NOT SET (takes values automaticaly) + * @return array of breadcrumbs + * ------------------------------------------------------------------------- + */ + static public function breadcrumbs($tree, $delimiter = " / ", $exception = 0, $l = 0) + { + $all = []; // results array + + foreach($tree as $node) { // loop through all nodes + + if (intval($node['rec']['id']) != $exception) { // if node is not exception + + // construct breadcrumb html of node + // --- -- -- - - - + $breadcrumb = ""; + foreach($node['parents'] as $par) { // first: join path titles + $breadcrumb .= $par['label'] . $delimiter; + } + $breadcrumb .= $node['rec']['label']; // last: append title + + // make a new super record + // --- -- -- - - - + $all[$node['rec']['id']] = [ // set record is as key + 'breadcrumb' => $breadcrumb, // add breadcrump to results + 'rec' => $node['rec'], // + node info + 'parents' => $node['parents'], // + parents array + 'childs' => self::first_level_childs($node), // + direct childs + 'level' => $l // + level + ]; + + // recursively traverse children nodes + // --- -- -- - - - + if (isset($node['childs']) && $node['childs'] != []) { + $child_breadcrumbs = self::breadcrumbs( + $node['childs'], + $delimiter, + $exception, + $l+1 + ); + + $all = $all + $child_breadcrumbs; // concatenate arrays (keep array-keys) + } + } + + } + return $all; + + } + + /** first_level_childs + * --- -- -- - - - + * used by all_breadcrumbs() + */ + static private function first_level_childs($node) + { + $childs = []; + if ($node['childs'] == []) { + return []; + } + foreach($node['childs'] as $key => $kid) { + $childs[] = [ + 'id' => $kid['rec']['id'], + 'label' => $kid['rec']['label'] + ]; + } + return $childs; + } + + + + + /** LESSONS + * ------------------------------------------------------------------------- + */ + + + /** lessons of course + * + * @param $id (int) : course_id + */ + public static function lessons_of_course($id) + { + // TODO: order results in some way + + return Registry::use('database')->runQuery( + "SELECT lesson.*, lesson_privilege.privilege_id FROM lesson + LEFT JOIN lesson_privilege ON lesson_privilege.lesson_id = lesson.id + WHERE lesson.status = 1 AND lesson.course_id = :id + ORDER BY lesson_privilege.privilege_id ASC", + [':id' => $id] + ); + } + + /** lesson + * + * @param $id (int) : lesson id + */ + public static function lesson($id) + { + // TODO: order results in some way + + return Registry::use('database')->query( + "SELECT lesson.*, lesson_privilege.privilege_id FROM lesson + LEFT JOIN lesson_privilege ON lesson_privilege.lesson_id = lesson.id + WHERE lesson.status = 1 AND lesson.id = :id", + [':id' => $id] + )->getFirst(); + } + + + + /** files + * return all files + * @param void + * @return array + */ + public static function files() + { + return Registry::use('database')->runQuery("SELECT * from media", []); + } + + + /** pages + * + * return all pages as array; + * array key of each item shall be the page-id + * + * @param void + * @return array + */ + public static function pages() + { + $result = []; + $pages = Registry::use('database')->runQuery("SELECT * FROM page", []); + foreach($pages as $key => $page) { + $result[ $page['id'] ] = [ + 'title' => $page['title'], + 'body' => $page['body'], + 'status' => $page['status'] + ]; + } + // print_r($result); die(); + return $result; + } + +} diff --git a/public/app/models/cms/Media_model.php b/public/app/models/cms/Media_model.php new file mode 100644 index 0000000..6a4a681 --- /dev/null +++ b/public/app/models/cms/Media_model.php @@ -0,0 +1,36 @@ +<?php + +namespace app\models\cms; + +use \Registry; + + +class Media_model +{ + public static function serve($type, $id, $ticket) + { + + // get media data + // [ type =>, rights =>, path => ] + + // validate ticket + // ValidateAccess::for($ticket) + + // serve + // header("Content-type: type"); + // passthru('cat $media_path'); + + + return Registry::use('database')->query( + "SELECT * FROM pages + WHERE FullFriendlyUrl = :url AND IsActive = 1", + [ ':url' => $url ] + )->getFirst(); + } + +} + + + +// check: +// https://stackoverflow.com/questions/1353850/serve-image-with-php-script-vs-direct-loading-an-image
\ No newline at end of file diff --git a/public/app/models/cms/Page_model.php b/public/app/models/cms/Page_model.php new file mode 100644 index 0000000..fda5fb1 --- /dev/null +++ b/public/app/models/cms/Page_model.php @@ -0,0 +1,18 @@ +<?php + +namespace app\models\cms; + +use \Registry; + +class Page_model +{ + public static function fromUrl($url) + { + return Registry::use('database')->query( + "SELECT * FROM pages + WHERE FullFriendlyUrl = :url AND IsActive = 1", + [ ':url' => $url ] + )->getFirst(); + } + +}
\ No newline at end of file diff --git a/public/app/models/market/Market_repository.php b/public/app/models/market/Market_repository.php new file mode 100644 index 0000000..1338426 --- /dev/null +++ b/public/app/models/market/Market_repository.php @@ -0,0 +1,235 @@ +<?php + +namespace app\models\market; + +use \Repository; + +/** Market_repository + * (thematic-entity repository) + * --- + * SQL entity templates for Market + * + * Every '_base' is a repository of common 'prepare' SQL-queries; + * They usually include holders for attaching/binding data safely; + * These queries are expensive in syntax but fast on run time and + * are used to collect multi-joined amounts ot data. + * + * Organize yous repositories in logical/thematic groups (partitioning). + * This way your repository is managable and uses very few sources. + * + * This class acts like a namespaced repository for geting your code readable + * Avoid pushing very simple queries into repository classes. A record like... + * 'CARS_by:Age' => "SELECT * FROM cars WHERE age = :Age" + * ... won't make your code sexier nor easier to read; it will just increase + * your label's entropy and make trickier to pick your next sensable label; + * + * repository classes include two methods (inherited by the abstract, so + * you do not need to implement something more than just construct + * the $repository array) + * ** pull: for pulling a query by a friendly name + * ** echo: optional method used by some devolopment tools + * + * TODO: development tool using echo is still in progress + */ +class Market_repository extends Repository +{ + //// use Repository; // repository trait includes + //// // a method to pull an entity of the gathering + //// // and method to (TODO:) ... + + /** repository is an array of Prepare SWL queries; + * each query is attached to the array via a friendly label; + * + * NOTE: (PROPOSAL) + * --- + * about the label: + * 1. keep names short descriptive but not too long + * 2. main entity/entities shall be UPPERCASED + * 3. determinands/adjectives shall be Cappitalized + * 4. words are connected with underscore (_) + * 5. if SQL inludes bind-holderd, label shall be suffixed with '_by:' + * followed by holder names connected with undercores (case-sensitively) + * + * about the data-bind holders (if present): + * 1. all holders are prefixed with ':' + * 2. holder name shall begin with a letter and contain one word. + * + * example: + * 'Active_EMPLOYEES_by:Age_CityID' => "SELECT Em.*, c.* + * FROM employee Em LEFT JOIN city c ON c.id = Em.city_id + * WHERE c.id = :CityID AND Em.age = :Age" + * + * It is recommended to leave a descriptive comment befor each declaration + * and you may also include sql-comments inside the query. + */ + public static $repository = [ + + // PRODUCTS by: storeID, ProductUrl + // picks all product properties + // + default image + price for certain store + 'Active_PRODUCT_by:storeID_ProductUrl' => + "SELECT p.*, + pc.ID AS ProductCategory, + pc.Hierarchy, + pc.FullFriendlyUrl AS `Path`, + prices.ProductPrice_OriginalPrice AS Price, + prices.ProductPrice_DiscountedPrice AS DiscountPrice, + prices.UnitMeasurePrice_OriginalPrice AS PerUnitPrice, + prices.UnitMeasurePrice_DiscountedPrice AS PerUnitDiscountPrice, + b.Title BrandName + FROM products p + LEFT JOIN products_to_product_categories p2pc ON p.ID = p2pc.SimpleProductID + LEFT JOIN product_categories pc ON p2pc.ProductCategoryID = pc.ID + LEFT JOIN prices ON p.SKU = prices.SKU + LEFT JOIN products_to_images p2i ON p2i.SimpleProductID = p.ID + LEFT JOIN assets ON assets.ID = p2i.ImageID + LEFT JOIN brands b ON b.ID = p.BrandID + WHERE pc.IsActive = 1 AND pc.IsCurrentlyActive = 1 + AND p2i.Order = 1 + AND p.IsActive = 1 + AND p.Published = 1 + AND prices.PriceListID = :storeID + AND p.FriendlyUrl = :ProductUrl" + , + + // PRODUCTS by: storeID, ProductID + // picks all product properties + // + default image + price for certain store + 'Active_PRODUCT_by:storeID_ProductID' => + "SELECT p.*, + pc.ID AS ProductCategory, + pc.Hierarchy, + pc.FullFriendlyUrl AS `Path`, + prices.ProductPrice_OriginalPrice AS Price, + prices.ProductPrice_DiscountedPrice AS DiscountPrice, + prices.UnitMeasurePrice_OriginalPrice AS PerUnitPrice, + prices.UnitMeasurePrice_DiscountedPrice AS PerUnitDiscountPrice, + b.Title BrandName + FROM products p + LEFT JOIN products_to_product_categories p2pc ON p.ID = p2pc.SimpleProductID + LEFT JOIN product_categories pc ON p2pc.ProductCategoryID = pc.ID + LEFT JOIN prices ON p.SKU = prices.SKU + LEFT JOIN products_to_images p2i ON p2i.SimpleProductID = p.ID + LEFT JOIN assets ON assets.ID = p2i.ImageID + LEFT JOIN brands b ON b.ID = p.BrandID + WHERE pc.IsActive = 1 AND pc.IsCurrentlyActive = 1 + AND p2i.Order = 1 + AND p.IsActive = 1 + AND p.Published = 1 + AND prices.PriceListID = :storeID + AND p.ID = :ProductID" + , + + // CATEGORY-PRODUCTS by: storeID, likeHierarchy + // traverses all products from category and subcategories + // picke price and default image + 'CATEGORY-PRODUCTS_by:storeID_likeHierarchy' => + "SELECT p.*, + assets.Url AS ImageUrl, + pc.FullFriendlyUrl AS `Path`, + prices.ProductPrice_OriginalPrice AS Price, + prices.ProductPrice_DiscountedPrice AS DiscountPrice, + prices.UnitMeasurePrice_OriginalPrice AS PerUnitPrice, + prices.UnitMeasurePrice_DiscountedPrice AS PerUnitDiscountPrice + FROM products p + LEFT JOIN prices ON p.SKU = prices.SKU + LEFT JOIN products_to_product_categories p2pc ON p2pc.SimpleProductID = p.ID + LEFT JOIN product_categories pc ON pc.ID = p2pc.ProductCategoryID + LEFT JOIN products_to_images p2i ON p2i.SimpleProductID = p.ID + LEFT JOIN assets ON assets.ID = p2i.ImageID + WHERE p2pc.ProductCategoryID IN ( + SELECT `ID` FROM product_categories + WHERE Hierarchy LIKE :likeHierarchy + ) + AND p2i.Order = 1 + AND p.IsActive = 1 + AND p.Published = 1 + AND prices.PriceListID = :storeID" + , + + // Count Products + // of all last-lever Product categories + // by StoreID + // --- + // used on creating product_categories tree + // ProductCategories_model::tree() + 'Count_Last_Level_PRODUCTS_by:storeID' => + "SELECT pc.ID, COUNT(p.ID) as `Counter` + FROM product_categories pc + LEFT JOIN products_to_product_categories p2pc ON pc.ID = p2pc.ProductCategoryID + LEFT JOIN products p ON p2pc.SimpleProductID = p.ID + LEFT JOIN prices ON p.SKU = prices.SKU + LEFT JOIN products_to_images p2i ON p2i.SimpleProductID = p.ID + LEFT JOIN assets ON assets.ID = p2i.ImageID + WHERE pc.IsActive = 1 AND pc.IsCurrentlyActive = 1 + AND p2i.Order = 1 + AND pc.Level = 2 + AND p.IsActive = 1 + AND p.Published = 1 + AND prices.PriceListID = :storeID + GROUP BY pc.ID" + , + + // PRODUCT by: ProductID + // Join images array (json string) + // Join prices for all stores (json string) + 'Active_PRODUCT_complete_by:ProductID' => + "SELECT p.*, pc.ID AS ProductCategory, + pc.Hierarchy, pc.FullFriendlyUrl AS `Path`, + ( -- get all store prices as Json + SELECT JSON_ARRAYAGG(JSON_OBJECT( + 'Store', prices.PriceListID, + 'Price', prices.ProductPrice_OriginalPrice, + 'DiscountPrice', prices.ProductPrice_DiscountedPrice, + 'PerUnitPrice', prices.UnitMeasurePrice_OriginalPrice, + 'PerUnitDiscountPrice', prices.UnitMeasurePrice_DiscountedPrice + )) + FROM prices + WHERE prices.SKU = p.SKU + ) as pricesJson, + ( -- get all images as Json + SELECT JSON_ARRAYAGG(JSON_OBJECT( + 'Order', p2i.Order, + 'Url', assets.Url, + 'Title', assets.Title, + 'Description', assets.Description + )) + FROM products_to_images p2i + LEFT JOIN assets ON assets.ID = p2i.ImageID + WHERE p2i.SimpleProductID = p.ID + ) AS imagesJson, + b.Title BrandName + FROM products p + LEFT JOIN products_to_product_categories p2pc ON p.ID = p2pc.SimpleProductID + LEFT JOIN product_categories pc ON p2pc.ProductCategoryID = pc.ID + LEFT JOIN products_to_images p2i ON p2i.SimpleProductID = p.ID + LEFT JOIN assets ON assets.ID = p2i.ImageID + LEFT JOIN brands b ON b.ID = p.BrandID + WHERE pc.IsActive = 1 AND pc.IsCurrentlyActive = 1 + AND p2i.Order = 1 + AND p.IsActive = 1 + AND p.Published = 1 + AND p.ID = :ProductID" + , + + // Product images by: ProductID + // (sorted by 'order' field) + 'PRODUCT_IMAGES_by:productID' => + "SELECT a.Url, a.Title, a.Description + FROM products p + LEFT JOIN products_to_images p2i ON p2i.SimpleProductID = p.ID + LEFT JOIN assets a ON a.ID = p2i.ImageID + WHERE p.IsActive = 1 + AND p.Published = 1 + AND p.ID = :productID + ORDER BY p2i.Order" + + ]; + + /* no need to impement anything else; + * you can overide if needed the default methods: + * + public static function pull($entity){ } + * + public static function echo($content =false){ } + */ + +} diff --git a/public/app/models/market/Payment_template.txt b/public/app/models/market/Payment_template.txt new file mode 100644 index 0000000..4a66a1a --- /dev/null +++ b/public/app/models/market/Payment_template.txt @@ -0,0 +1,98 @@ +class WC_Piraeusbank_Gateway extends WC_Payment_Gateway { + + public function __construct() + + function pb_get_pages($title = false, $indent = true) + + function pb_get_installments($title = false, $indent = true) + + function generate_piraeusbank_form($order_id) + + function process_payment($order_id) + + function receipt_page($order) + + function check_piraeusbank_response() + + function piraeusbank_message() + + function woocommerce_add_piraeusbank_gateway($methods) + + function piraeusbank_plugin_action_links($links, $file) + +} + + + +class WC_NBG_Gateway extends WC_Payment_Gateway { + + public function __construct() + + public function admin_options() + + function init_form_fields() + + function nbg_get_pages($title = false, $indent = true) + + function nbg_get_installments($title = false, $indent = true) + + function generate_nbg_form($order_id) + + function process_payment($order_id) + + function receipt_page($order) { + + function check_nbg_response() + + function nbg_message() + + function woocommerce_add_nbg_gateway($methods) + + function nbg_plugin_action_links($links, $file) + +} + + + +global $wc; + + $this->id = 'nbg_gateway'; + $this->icon = apply_filters('nbg_icon', plugins_url('assets/nbg.png', __FILE__)); + $this->has_fields = false; + $this->notify_url = WC()->api_request_url('WC_NBG_Gateway'); + $this->method_description = __('National Bank Greece Payment Gateway allows you to accept payment through various channels such as Maestro, Mastercard and Visa cards On your Woocommerce Powered Site.', 'woocommerce-nbg-payment-gateway'); + $this->redirect_page_id = $this->get_option('redirect_page_id'); + $this->method_title = 'National Bank of Greece Gateway'; + + // Load the form fields. + $this->init_form_fields(); + + //dhmioyrgia vashs + + global $wpdb; + + if ($wpdb->get_var("SHOW TABLES LIKE '" . $wpdb->prefix . "nbg_transactions'") === $wpdb->prefix . 'nbg_transactions') { + // The database table exist + } else { + // Table does not exist + $query = 'CREATE TABLE IF NOT EXISTS ' . $wpdb->prefix . 'nbg_transactions (id int(11) unsigned NOT NULL AUTO_INCREMENT,merchantreference varchar(30) not null, reference varchar(100) not null, orderid varchar(100) not null , timestamp datetime default null, PRIMARY KEY (id))'; + $wpdb->query($query); + } + + + // Load the settings. + $this->init_settings(); + + + // Define user set variables + $this->title = $this->get_option('title'); + $this->description = $this->get_option('description'); + $this->nbg_Username = $this->get_option('nbg_Username'); + $this->nbg_Password = $this->get_option('nbg_Password'); + + $this->nbg_description= $this->get_option('nbg_description'); + $this->mode = $this->get_option('mode'); + $this->nbg_installments= $this->get_option('nbg_installments'); + //Actions + add_action('woocommerce_receipt_nbg_gateway', array($this, 'receipt_page')); + add_action('woocommerce_update_options_payment_gateways_' . $this->id, array($this diff --git a/public/app/models/market/ProductCategories_model.php b/public/app/models/market/ProductCategories_model.php new file mode 100644 index 0000000..d8c274e --- /dev/null +++ b/public/app/models/market/ProductCategories_model.php @@ -0,0 +1,199 @@ +<?php + +namespace app\models\market; + +use \Registry; +use app\models\market\Market_repository; + + +/** ProductCategories_model + * --- + * undertakes to collect any product_categories data + * from the database and prepare needed data structures. + * + */ +class ProductCategories_model +{ + + /** Get (product_) category from (full-friendly_) URL + * --- (self explanatory) + * @param $url (string): Full-Friendly-URL + * @return $category (array); also includes all category products + */ + public static function categoryFromUrl($url) + { + $db = Registry::use('database'); + $category = $db->query( "SELECT * FROM product_categories + WHERE FullFriendlyUrl = :url AND IsActive = 1", + [ ':url' => $url ] + )->getFirst(); + + if ($category === false) { return false; } // no category? -> false + + // GET PRODUCTS of category + $category['products'] = self::categoryProductsByHierarchy($category['Hierarchy']); + return $category; + } + + + /** Get (product_) category from ID + * --- (self explanatory) + * @param $id (int): Catgory ID (usualy from products) + * @return $category (array); also includes all category products + */ + public static function categoryFromID($id) + { + return Registry::use('database')->query( "SELECT * FROM product_categories + WHERE `ID` = :url AND IsActive = 1", + [':id' => $id] + )->getFirst(); + + } + + + /** categoryProductsByHierarchy + * --- + * This method returns the products belonging to a certain + * category (and all it's the children/sub-categories); + * It uses category.Hierarchy in a WHERE LIKE condition to + * make it fast. + * + * The method is category.Level agnostic + * + * @param $category (array) + * @return $products (array) + */ + public static function categoryProductsByHierarchy($hierarchy, $store = 904) + { + // GET PRODUCTS of category + // NOTE: + // category.Hierarchy is used + + return Registry::use('database')->runQuery( + Market_repository::pull('CATEGORY-PRODUCTS_by:storeID_likeHierarchy'), + [ + ':likeHierarchy' => $hierarchy.'%', + ':storeID' => 904 + ] + ); + } + + + # depricated: + # now Proxy has the responsibility to cache whatever + # --- + # /** GET tree of product_categories + # * --- + # * get from cache or cache it after creation + # */ + # public static function tree($store = 904) + # { + # $cacheKey = get_called_class() . $store .'/tree'; + # + # // IF category CACHED + # $cache = Registry::use('cache'); + # $tree = $cache->get($cacheKey); + # if ($tree !== false) { + # return $tree; + # } + # + # // (not cached) Create and Cache it + # $tree = self::createCategoriesTree(); + # $cache->set($cacheKey, $tree, CACHE_ROOT_TTL); + # + # return $tree; + # } + + + /** CREATE product_categories tree + * NOTE: includes active categories only; + * + * Algorith uses 'Hierarchy' for category path-positioning, + * implementing a linear conctruction of the requested tree; + * (faster and less source-consuming than a recursive algo) + */ + public static function tree($store = 904) + { + $tree = []; // variable to hold results + + $db = Registry::use('database'); + + // get raw categories data --------------------------------------------- + + $raw = $db->runQuery( "SELECT `ID`, Title, `Level`, Hierarchy, + ParentID, `Order`, FullFriendlyUrl + FROM product_categories pc + WHERE IsActive = 1 AND IsCurrentlyActive = 1 + ORDER BY pc.Level asc, pc.Order asc, pc.Hierarchy asc", + [] + ); + + // count products per 3rd level category ------------------------------- + + $countProducts = $db->runQuery( + Market_repository::pull('Count_Last_Level_PRODUCTS_by:storeID'), + [ ':storeID' => $store ] + ); + // map categoryID -> Counter + $mapCounter = []; + foreach($countProducts as $rec) $mapCounter[$rec['ID']] = $rec['Counter']; + + // parse raw data to hierarchical tree (2 pass) ------------------------ + + // 1st pass: parse raw data, construct and fill the $tree array ........ + foreach($raw as $rec) { + + // create category path from Hierarchy (format: .10.10100. ) + // so remove 1st and last dots (.) from Hierarchy string + // then explode via dot-character (.) + $categoryPath = explode('.', substr($rec['Hierarchy'], 1, -1)); + + switch ($rec['Level']) { + case 0: + $tree[$categoryPath[0]] = [ + 'info' => $rec, + 'childs' => [] + ]; break; + + case 1: + $tree[$categoryPath[0]]['childs'][$categoryPath[1]] = [ + 'info' => $rec, + 'childs' => [] + ]; break; + + case 2: + $tree[$categoryPath[0]]['childs'][$categoryPath[1]]['childs'][$categoryPath[2]] = [ + 'info' => $rec, + 'count' => isset($mapCounter[$categoryPath[2]]) ? $mapCounter[$categoryPath[2]] : 0 + ]; break; + + default: + // nothing... + } + + } + + // 2nd pass: remove categories with no products ........................ + foreach($tree as $id0 => $rec0) { + $count0 = 0; + + foreach($rec0['childs'] as $id1 => $rec1) { + $count1 = 0; + + foreach($rec1['childs'] as $id2 => $rec2) { + if ($rec2['count']>0) $count1++; + else unset($tree[$id0]['childs'][$id1]['childs'][$id2]); + } + + if ($count1 > 0) $count0++; + else unset($tree[$id0]['childs'][$id1]); + } + + if (!$count0) unset($tree[$id0]); + } + + return $tree; + } + + +}
\ No newline at end of file diff --git a/public/app/models/market/Product_model.php b/public/app/models/market/Product_model.php new file mode 100644 index 0000000..aa554a1 --- /dev/null +++ b/public/app/models/market/Product_model.php @@ -0,0 +1,49 @@ +<?php + +namespace app\models\market; + +use \Registry; +use app\models\market\Market_repository as Market; + +class Product_model +{ + public static function fromUrl($url, $store = 904) + { + $product = Registry::use('database')->query( + Market::pull('Active_PRODUCT_by:storeID_ProductUrl'), + [ + ':storeID' => $store, + ':ProductUrl' => $url + ] + )->getFirst(); + + // if no product, return false + if ($product === false) return false; + + // inject product's images + $product['Images'] = self::getProductImages($product['ID']); + + return $product; + } + + + public static function getProductImages($id) + { + ## return Registry::use('database')->runQuery( + ## "SELECT a.Url, a.Title, a.Description + ## FROM products p + ## LEFT JOIN products_to_images p2i ON p2i.SimpleProductID = p.ID + ## LEFT JOIN assets a ON a.ID = p2i.ImageID + ## WHERE p.IsActive = 1 AND p.Published = 1 + ## AND p.ID = :id + ## ORDER BY p2i.Order", + ## [ ':id' => $id ] + ## ); + + return Registry::use('database')->runQuery( + Market::pull('PRODUCT_IMAGES_by:productID'), + ['productID' => $id ] + ); + } + +}
\ No newline at end of file diff --git a/public/app/models/todo.md b/public/app/models/todo.md new file mode 100644 index 0000000..defd21e --- /dev/null +++ b/public/app/models/todo.md @@ -0,0 +1,132 @@ + + +Privilege +--- + +Privilege::list_of_privileges( privilege_id ) + +Privilege::privileges_tree() + + + + + +User +--- + +User::get_privileges + + +User::has_privilege( privilege_id ) + + +User::reset_password() + -> create otp + -> send email + + +User::set_privilege( privilege_id ) + + +User::track_action() + -> user_id + -> action : Contoller::method(args) + -> timestamp + + + +Lesson: +--- + +Lesson::create_lesson( POST ) + +Lesson::get_lesson( lesson_id ) + + + +Course +--- + +Course::create_course( POST ) + +Course::courses_tree() + + + +Upgrades (??) +--- + + +A1 + +A2 : includes A1 + +A3 : includes A1, A2 + +A4 : includes A1, A2, A3 + + +A1 100 +A2 200 +A3 350 +A4 500 + +B1 100 +B2 200 +B3 400 +B4 800 + + +:1 -> 0% +:2 -> 15% +:3 -> 20% + + + + +A1 -> A2 +A1 -> A3 +A1 -> A4 + +A2 -> A3 +A2 -> A4 + +A3 -> A4 + +A1 < A2 < A3 < A4 + + + +B1 -> B2 +B1 -> B3 +B1 -> B4 + +B2 -> B3 +B2 -> B4 + +B3 -> B4 + + +A1+B1 + +A2+B2 + +A3+B3 + + +A1+B1 -> A2+B2 +A1+B1 -> A3+B3 +A1+B1 -> A4+B4 + +A2+B2 -> A3+B3 +A2+B2 -> A4+B4 + +A3+B3 -> A4+B4 + + + +UPGRADE PRICE = upper_priv_price - own_priv_price + +buy 1 item : -0% +buy 2 items : -10% +buy 3 items : -15% |
