diff options
40 files changed, 682 insertions, 915 deletions
diff --git a/core/classes/session/DatabaseSession.php b/core/classes/session/DatabaseSession.php index aa31b62..fed6353 100644 --- a/core/classes/session/DatabaseSession.php +++ b/core/classes/session/DatabaseSession.php @@ -66,7 +66,7 @@ class DatabaseSession implements SessionHandlerInterface public function close() : bool { - // depricated: the database object is shared (don;t clode it!) + // DEPRECATED: the database object is shared (don;t clode it!) // // Close the database connection // if ($this->db = null) { return true; } // else return false; diff --git a/public/app/config/init_routes.php b/public/app/config/init_routes.php index 22d6ec0..1d697b8 100644 --- a/public/app/config/init_routes.php +++ b/public/app/config/init_routes.php @@ -5,7 +5,7 @@ require_once 'app/routes/api.php'; // API: =/api/{table}/{id};/api/* -require_once 'app/routes/backend.php'; // Backend: =/admin/* +// require_once 'app/routes/backend.php'; // Backend: =/admin/* require_once 'app/routes/user.php'; // User account management diff --git a/public/app/controllers/Auth.php b/public/app/controllers/Auth.php index f356dfe..e0d84fc 100644 --- a/public/app/controllers/Auth.php +++ b/public/app/controllers/Auth.php @@ -166,6 +166,7 @@ class Auth { // acivate target user and set password $check = Access_model::activate_set_password($req->POST, $password); + // TODO: Cache_service::update_teachers(); if ($check != 0) { Render::json([ @@ -201,6 +202,7 @@ class Auth { $req->POST, $uid ); + // TODO: Cache_service::update_teachers(); // user properties changed, so update user's session token self::reset_user_token($uid); diff --git a/public/app/controllers/CmsAdmin.php b/public/app/controllers/CmsAdmin.php deleted file mode 100644 index 9629741..0000000 --- a/public/app/controllers/CmsAdmin.php +++ /dev/null @@ -1,676 +0,0 @@ -<?php - -namespace app\controllers; - -use Registry; -use Render; -use app\models\Cms_model; -use app\extends\Cache_service; - -class CmsAdmin { - - - ## ------------------------------------------------------------------------- - ## - ## COURSE (category) METHODS - ## - ## ------------------------------------------------------------------------- - - - /** breadcrumbs - * --- - * responds to ajax GET: /admin/api/categories - */ - public static function breadcrumbs() - { - Render::json( Cache_service::courses_struct()['breadcrumbs'] ); - } - - /** add course - * --- - * @param void (get data from POST) - */ - public static function add_course() - { - // insert new category; id = new category id - $id = Registry::use('database')->query( - "INSERT INTO course (parent_id, label) VALUES (:parent, :label)", - [ - ':parent' => Registry::get('REQUEST')->POST['parent_id'], - ':label' => Registry::get('REQUEST')->POST['label'] - ] - )->lastInsertID(); - - $cache = self::update_courses_cache(); // update category caches - return $cache['breadcrumbs']; // return - } - - - /** update course - * --- - * @param void (get data from POST) - */ - public static function update_course() - { - Registry::use('database')->query( - "UPDATE course SET parent_id = :parent, label = :label - WHERE id = :id", - [ - ':id' => Registry::get('REQUEST')->POST['id'], - ':parent' => Registry::get('REQUEST')->POST['parent_id'], - ':label' => Registry::get('REQUEST')->POST['label'] - ] - ); - $cache = self::update_courses_cache(); - return $cache['breadcrumbs']; - } - - - /** update courses cache - * --- -- -- - - - - * Forces re-caching of courses tree - * Shall run if anything changes to courses - * - * @param void - * @return (array): ['tree' => ..., 'breadcrumbs' => ... ] - */ - public static function update_courses_cache() - { - return Cache_service::courses_struct(PROXY_IGNORE_CACHE); - } - - - ## ------------------------------------------------------------------------- - ## - ## LESSON METHODS - ## - ## ------------------------------------------------------------------------- - - - /** all lessons - * - */ - public static function all_lessons() - { - Render::json(Registry::use('database')->runQuery( - "SELECT lesson.*, lesson_privilege.privilege_id FROM lesson - LEFT JOIN lesson_privilege ON lesson_privilege.lesson_id = lesson.id", - [] - )); - } - - - /** get_lesson - * - * @param $id (int) : lesson's id - */ - public static function get_lesson($id) - { - // get (first) lesson with id = $id; render as json - Render::json(Registry::use('database')->query( - "SELECT lesson.*, - ( SELECT - CONCAT('[', GROUP_CONCAT(JSON_OBJECT( - 'id', media.id, - 'label', media.label, - 'type', media.type, - 'path', media.path )), - ']') - FROM media - LEFT JOIN lesson_media ON lesson_media.media_id = media.id - WHERE lesson_media.lesson_id = :id - ) AS medias_json, - lesson_privilege.privilege_id - FROM lesson - LEFT JOIN lesson_privilege ON lesson_privilege.lesson_id = lesson.id - WHERE lesson.id = :id", - [ ':id' => $id] - )->getFirst()); - } - - - /** add_lesson - * insert a new lesson - * - * all parametres are passed via $_POST array - * - * POST @param title - * POST @param course_id - * POST @param intro - * POST @param body - * POST @param published - */ - public static function add_lesson() - { - $post = Registry::get('REQUEST')->POST; - - // insert lesson content into daabase - $id = Registry::use('database')->query( - "INSERT INTO lesson (title, course_id, intro, `body`, `status`) - VALUES (:title, :courseid, :intro, :body, :status)", - [ - ':title' => $post['title'], - ':courseid' => $post['course_id'], - ':intro' => $post['intro'], - ':body' => $post['body'], - 'status' => $post['status'] - ] - )->lastInsertID(); - - // set lesson privileges to database - Registry::use('database')->runQuery( - "INSERT INTO lesson_privilege (lesson_id, privilege_id) - VALUES (:lesson_id, :privilege_id)", - [ - ':lesson_id' => $id, - ':privilege_id' => $post['privilege_id'] - ] - ); - - if (isset($post['media'])) { - // update media's privileges; NOTE: media table - self::update_medias_privileges($post['media'], $post['privilege_id']); - - // set lesson's media files; NOTE: lesson_media table - // ERROR: self::create_medias_for_lesson($post['media'], $id, $post['privilege_id']); - self::create_medias_for_lesson($post['media'], $id); - - // recache media - Cache_service::files_attributes(PROXY_IGNORE_CACHE); - } - - return true; - } - - - public static function update_lesson() - { - $post = Registry::get('REQUEST')->POST; - - // print_r($post); die(); - - // update lesson's content - Registry::use('database')->query( - "UPDATE lesson - SET title = :title, - course_id = :courseid, - intro = :intro, `body` = :body, - `status` = :status - WHERE id = :id", - [ - ':id' => $post['id'], - ':title' => $post['title'], - ':courseid' => $post['course_id'], - ':intro' => $post['intro'], - ':body' => $post['body'], - ':status' => $post['status'] - ] - ); - - // update lesson's privileges - Registry::use('database')->runQuery( - "UPDATE lesson_privilege SET privilege_id = :privilege_id - WHERE lesson_id = :lesson_id", - [ - ':lesson_id' => $post['id'], - ':privilege_id' => $post['privilege_id'] - ] - ); - - if (isset($post['media'])) { - // update media's privileges - self::update_medias_privileges($post['media'], $post['privilege_id']); - } - - // remove all old lesson's links to media files - Registry::use('database')->runQuery( - "DELETE from lesson_media WHERE lesson_id = :lesson", - [ ':lesson' => $post['id'] ] - ); - - if (isset($post['media'])) { - // update lesson's media files - // ERROR: self::create_medias_for_lesson($post['media'], $post['id'], $post['privilege_id']); - self::create_medias_for_lesson($post['media'], $post['id']); - - // recache media - Cache_service::files_attributes(PROXY_IGNORE_CACHE); - } - - return true; - } - - - ## ------------------------------------------------------------------------- - ## - ## FILE METHODS - ## - ## ------------------------------------------------------------------------- - - - /** files - * echo all files - * - * @return (array) - */ - public static function files() - { - return Cache_service::files_attributes(); - } - - - /** upload_file - * upload the file to the file system - * - * the method reads the POST and FILES array - * to retrieve all needed parametres - * - * FILES @param file - * POST @param folder : lesson's ID or somthing random - * POST @param reference : reference type - * POST @param - */ - public static function upload_file() - { - $request = Registry::get('REQUEST'); - - $uploaded = self::upload_to_fs(); // upload file to file-system - - if ($uploaded['success']) { - - $media_id = self::define_media([ // define media in database; get id - 'title' => $request->POST['title'], - 'type' => $uploaded['type'], - 'path' => $uploaded['path'] - ]); - - Render::json([ // render results as json - 'success' => true, - 'id' => $media_id, - 'title' => $request->POST['title'], - 'path' => $uploaded['path'], - 'type' => $uploaded['type'] - ]); - - } else { - Render::json(['success' => false ]); - } - } - - - /** upload to fs - * upload file to File-System - * - * POST @param folder - * FILES @param file - */ - private static function upload_to_fs() - { - $post = Registry::get('REQUEST')->POST; - $files = Registry::get('REQUEST')->FILES; - - - // Checks before uploading the file - //////////////////////////////////////////////////////////////////////// - - // ** 1: file is upladed to temporary folder --------------------------- - if (! is_uploaded_file($files['file']['tmp_name'])) { - return ['success' => false]; // bye! - } - - // ** 2: File belongs to the allowed MIME types ------------------------ - $allowed_file_types = array( - 'application/pdf', - 'image/png', 'image/jpeg', - 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' - ); - // Recomended MIME type checking via mime_content_type(): - $mime_type = mime_content_type($files['file']['tmp_name']); - if (! in_array($mime_type, $allowed_file_types)) { // File type NOT allowed ... - return ['success' => false]; // bye! - } - - $file_name = $files['file']['name']; - $file_type = $files['file']['type']; // do not take it for granted - $file_size = $files['file']['size']; - $file_tmp = $files['file']['tmp_name']; - - $bare_name = pathinfo($file_name, PATHINFO_FILENAME); - $file_ext = pathinfo($file_name, PATHINFO_EXTENSION); - - - // ** 3: filename or size checks may be added -------------------------- - if ($file_name == "") { - return ['success' => false]; // bye! - } - - - // READY to finaly save/upload the file to CDN ///////////////////////// - - $folder = MEDIA_STORAGE_ROOT . $post['folder']; - if (!file_exists($folder)) { // create folder if not exists - mkdir($folder, 0757, true); - } - - // print_r([ - // 'dir' => $folder, - // 'file' => $bare_name, - // 'ext' => $file_ext, - // 'type' => $file_type - // ]); die(); - - $relative_filename = $post['folder'] - .'/' - . strtolower(self::clear_file_name($bare_name) .'.'. $file_ext); - $store_filename = MEDIA_STORAGE_ROOT . $relative_filename; - - if (move_uploaded_file($files["file"]["tmp_name"], $store_filename)) { - return [ - 'success' => true, - 'path' => $relative_filename, - 'type' => $mime_type - ]; - - } else { return ['success' => false]; } - - } - - - /** clear_file_name - * replace greek characters and strip symbols - */ - private static function clear_file_name($str) - { - $el = mb_split( "ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩαβγδεζηθικλμνξοπρστυφχψωάέήίόύώϊϋς ", ""); - $en = str_split("ABGDEZHUIKLMNJOPRSTYFXCVabgdezhuiklmnjoprstyfxcvaehioyviys-"); - $strip = str_split("!@#$%^&*()+~`[]{};'/<>?=\""); - - return str_replace($strip, '', str_replace($el, $en, $str)); - } - - - /** define_media - * - * create a record in media table - * - * @param $data (array): [title => , path => , type => mime-type] - * @return id (int): id of created media record - */ - private static function define_media($data) - { - $request = Registry::get('REQUEST'); - - $media_id = Registry::use('database')->query( - "INSERT INTO media (label, `type`, `path`) - VALUES (:label, :mimetype, :filepath)", - [ - 'label' => $data['title'], - 'mimetype' => $data['type'], - 'filepath' => $data['path'] - ] - )->lastInsertID(); - return $media_id; - } - - - /** create media for lesson - * - * links lesson to each media-file of the media `id`s array - * - * @param $media (array): a list of media-file `id`s - * @param $lesson_id (int) - */ - private static function create_medias_for_lesson($medias, $lesson_id) - { - foreach($medias as $key => $medi) { - self::link_media_to_lesson($medi, $lesson_id); // link to lesson - } - return true; - } - - - /** create media for page - * - * links page to each media-file of the media `id`s array - * - * @param $media (array): a list of media-file `id`s - * @param $page_id (int) - */ - private static function create_medias_for_page($medias, $page_id) - { - foreach($medias as $key => $medi) { - self::link_media_to_page($medi, $page_id); // link to page - } - return true; - } - - - /** link one media-file to a specific post - * - * NOTE: - * the method does not check if media is linked already - * so be sure that the pair of (media_id,post_id) not exist - * - * @param $media_id (int) - * @param $lesson_id (int) - */ - private static function link_media_to_lesson( $media_id, $lesson_id ) - { - Registry::use('database')->runQuery( - "INSERT INTO lesson_media (lesson_id, media_id) - VALUES (:lesson, :media)", - [ - 'lesson' => $lesson_id, - 'media' => $media_id, - ] - ); - return true; - } - - /** link one media-file to a specific page - * - * NOTE: - * the method does not check if media is linked already - * so be sure that the pair of (media_id, page_id) not exist - * - * @param $media_id (int) - * @param $page_id (int) - */ - private static function link_media_to_page( $media_id, $page_id ) - { - Registry::use('database')->runQuery( - "INSERT INTO page_media (page_id, media_id) - VALUES (:page, :media)", - [ - 'page' => $page_id, - 'media' => $media_id, - ] - ); - return true; - } - - - /** update medias privileges - * - * UPDATE media SET privige WHERE IN {list} - * - * @param $medias (array) : array of media id(s) - * @param $privilege (int) : privilege id - * @return true (always) - */ - private static function update_medias_privileges($medias, $privilege) - { - // create WHERE IN (LIST) holders and params for prepare statement - $params = [ ':privilege' => $privilege]; - $holders = []; - foreach($medias as $key => $media) { - $params[':id'.$key] = $media; - $holders[] = ':id'.$key; - } - $list = '('. implode(',', $holders) .')'; - - // print_r(['params' => $params, 'list' => $list]); die(); - - Registry::use('database')->runQuery( - "UPDATE media SET privilege_id = :privilege - WHERE id IN {$list}", - $params - ); - - return true; - } - - - - - - - ## ------------------------------------------------------------------------- - ## - ## PAGE METHODS - ## - ## ------------------------------------------------------------------------- - - - /** all pages - * - */ - public static function all_pages() - { - Render::json(Registry::use('database')->runQuery( - "SELECT * FROM page", [] - )); - } - - - /** get_page - * - * @param $id (int) : page's id - */ - public static function get_page($id) - { - // get (first) page with id = $id; render as json - Render::json(Registry::use('database')->query( - "SELECT page.*, - ( SELECT - CONCAT('[', GROUP_CONCAT(JSON_OBJECT( - 'id', media.id, - 'label', media.label, - 'type', media.type, - 'path', media.path )), - ']') - FROM media - LEFT JOIN page_media ON page_media.media_id = media.id - WHERE page_media.page_id = :id - ) AS medias_json - FROM page - WHERE page.id = :id", - [ ':id' => $id] - )->getFirst()); - } - - - /** add_page - * insert a new page - * - * all parametres are passed via $_POST array - * - * POST @param title - * POST @param body - * POST @param status (int): 0 = draft , 1 = published - */ - public static function add_page() - { - $post = Registry::get('REQUEST')->POST; - - // insert page content into daabase - $id = Registry::use('database')->query( - "INSERT INTO page (title, `body`, `status`) - VALUES (:title, :body, :status)", - [ - ':title' => $post['title'], - ':body' => $post['body'], - 'status' => $post['status'] - ] - )->lastInsertID(); - - - if (isset($post['media'])) { - // update media's privileges; - // NOTE: media table; pages have public files (privilege_id=0) - self::update_medias_privileges($post['media'], 0); - - // set page's media files - // NOTE: page_media table - self::create_medias_for_page($post['media'], $id); - } - - self::update_pages_cache(); // update pages cache - - return true; - } - - - /** update_page - * - * POST @param id (int) - * POST @param title - * POST @param body - * POST @param status (int): 0 = draft , 1 = published - */ - public static function update_page() - { - $post = Registry::get('REQUEST')->POST; - - // print_r($post); die(); - - // update page's content - Registry::use('database')->query( - "UPDATE page - SET title = :title, `body` = :body, `status` = :status - WHERE id = :id", - [ - ':id' => $post['id'], - ':title' => $post['title'], - ':body' => $post['body'], - ':status' => $post['status'] - ] - ); - - if (isset($post['media'])) { - // update media's privileges - self::update_medias_privileges($post['media'], 0); - } - - // remove all old page's links to media files - Registry::use('database')->runQuery( - "DELETE from page_media WHERE page_id = :page", - [ ':page' => $post['id'] ] - ); - - if (isset($post['media'])) { - // update page's media files - self::create_medias_for_page($post['media'], $post['id']); - } - - self::update_pages_cache(); // update pages cache - - return true; - } - - - /** update pages cache - * --- -- -- - - - - * Forces re-caching of pages tree - * Shall run if anything changes to courses - * - * @param void - * @return (array): ['tree' => ..., 'breadcrumbs' => ... ] - */ - public static function update_pages_cache() - { - return Cache_service::pages_list(PROXY_IGNORE_CACHE); - } - -}
\ No newline at end of file diff --git a/public/app/controllers/Office.php b/public/app/controllers/Office.php index 79f76d8..4161efc 100644 --- a/public/app/controllers/Office.php +++ b/public/app/controllers/Office.php @@ -7,6 +7,7 @@ use Render; use app\controllers\Auth; use app\extends\App_manager; use app\models\Content_model; +use app\models\Access_model; use app\extends\SendMail_service; use app\extends\Cache_service; @@ -225,7 +226,7 @@ class Office { // #3.1: get all teachers $teachers_array = Registry::use('database')->runQuery( "SELECT concat(last_name, ' ', first_name) as TeacherName - FROM user ORDER BY TeacherName", [] + FROM user WHERE deprecated IS NULL ORDER BY last_name, first_name", [] ); $teachers = []; // constuct teacher names as a simple array foreach($teachers_array as $key => $person) { @@ -323,18 +324,9 @@ class Office { } - public static function all_petitions() - { - if (Auth::in_admin_group()) { - Render::json([ - 'success' => true, - 'data' => Content_model::all_petitions() - ]); - } - } - ## SEND INVITATION + ## SEND INVITATION(s) ## ------------------------------------------------------------------------- public static function send_invitation() @@ -359,11 +351,12 @@ class Office { "SELECT * FROM user WHERE id IN ( $all_handlers ) AND ( otp_expiration < :now OR otp_expiration IS NULL - )", + ) AND deprecated IS NULL", $binds ); $sent = []; + $failed = []; foreach($recipients as $key => $rec) { $invitation = md5( @@ -373,36 +366,34 @@ class Office { . rand(0, 999999) ); - /* $gone = SendMail_service::send_invitation([ 'email' => $rec['email'], - 'name' => $rec['last_name'] .' '. $rec['fisrt_name'], + 'name' => $rec['last_name'] .' '. $rec['first_name'], 'code' => $invitation - ]) + ]); - if ($gone) { - // update user record with invitation - Registry::use('database')->runQuery( - "UPDATE user SET invitatopm = :inv WHERE id = :id", - [ ':inv' => $invitation, ':id' => $rec['id'] ] - ); + if ($gone === true) { + // update user record with invitation; + Access_model::invite_user($rec['id'], $invitation, $expiration); - $sent[] = $rec['first_name'] - ." ". $rec['last_name'] + $sent[] = $rec['last_name'] // keep data for echoing + ." ". $rec['first_name'] ." (". $rec['id'] .")" .": ". $invitation; - } - */ - $sent[] = $rec['first_name'] - ." ". $rec['last_name'] - ." (". $rec['id'] .")" - .": ". $invitation; + } else { + $failed[] = [ + 'rec' => $rec['last_name'] ." ". $rec['first_name'] + ." (". $rec['id'] .")", + 'mdg' => $gone + ]; + } } return Render::json([ - 'success' => true, + 'success' => (empty($failed)), 'sent' => implode(",\n\n", $sent), + 'failed' => $failed ]); } diff --git a/public/app/controllers/Petition.php b/public/app/controllers/Petition.php deleted file mode 100644 index fa73cf0..0000000 --- a/public/app/controllers/Petition.php +++ /dev/null @@ -1,63 +0,0 @@ -<?php - -namespace app\controllers; - -use Registry; -use Render; -use app\controllers\Auth; -use app\extends\App_manager; -use app\models\Cms_model; -use app\extends\Cache_service; - - -class Petition { - - private static function render_common_petition($opts) - { - - } - - private static function render_penalty_form($opts) - { - $user_id = $opts['user']->getID(); - - $form_setup = json_decode(json_encode(PENALTY_FORM, JSON_UNESCAPED_UNICODE)); - - // get all teachers - $teachers_array = Registry::use('database')->runQuery( - "SELECT concat(last_name, ' ', first_name) as TeacherName - FROM user ORDER BY TeacherName", [] - ); - $teachers = []; // constuct teacher names as a simple array - foreach($teachers_array as $key => $person) { - $teachers[] = $person['TeacherName']; - } - // print_r( $teachers); die(); - - // get $key of rapporteur, president and members inside the form_setup->form array - for($i=0; $i < sizeof($form_setup->form) ; $i++) { - if (isset($form_setup->form[$i]->name)) { - if ($form_setup->form[$i]->name == 'rapporteur') { $keyRapporteur = $i; } - if ($form_setup->form[$i]->name == 'president') { $keyPresident = $i; } - if ($form_setup->form[$i]->name == 'members') { $keyMembers = $i; } - } - } - - // set option sources for rapporteur, president and members - $form_setup->form[$keyRapporteur]->options = $teachers; - $form_setup->form[$keyPresident]->options = $teachers; - $form_setup->form[$keyMembers]->options = $teachers; - - // get Form's HTML and Jsvascript - $form = JsonToForm::json_form($form_setup, [ - ['name' => 'user_id', 'value' => $user_id] // pass user identity - ]); - - Render::view('templates/penalty', ['form' => $form]); - - } - - - - -} diff --git a/public/app/extends/SendMail_service.php b/public/app/extends/SendMail_service.php index 64366e0..b80e790 100644 --- a/public/app/extends/SendMail_service.php +++ b/public/app/extends/SendMail_service.php @@ -167,7 +167,9 @@ class SendMail_service $response = json_decode($server_output); - return ($response->Messages[0]->Status == 'success'); + return ($response->Messages[0]->Status == 'success') + ? true + : $response; } diff --git a/public/app/models/Access_model.php b/public/app/models/Access_model.php index c98b6c4..90aac11 100644 --- a/public/app/models/Access_model.php +++ b/public/app/models/Access_model.php @@ -22,7 +22,8 @@ class Access_model public static function checkUser($email) { $user = Registry::use('database')->query( - "SELECT * FROM user WHERE email = :email AND active = 1", + "SELECT * FROM user + WHERE email = :email AND active = 1 AND deprecated IS NULL", [ ':email' => $email ] )->getFirst(); @@ -39,7 +40,8 @@ class Access_model public static function getUser_byID($uid) { $user = Registry::use('database')->query( - "SELECT * FROM user WHERE id = :uid AND active = 1", + "SELECT * FROM user + WHERE id = :uid AND active = 1 AND deprecated IS NULL", [ ':uid' => $uid ] )->getFirst(); @@ -56,7 +58,8 @@ class Access_model public static function getUserByInvitation($invitation) { return Registry::use('database')->query( - "SELECT * FROM user WHERE invitation = :invitation", + "SELECT * FROM user + WHERE invitation = :invitation AND deprecated IS NULL", [ 'invitation' => $invitation ] )->getFirst(); } @@ -177,7 +180,6 @@ class Access_model History_model::trackUserAccess($post['id'], TRACK_ACCOUNT, 'User Account Activated'); return $rowCount; - } @@ -263,6 +265,69 @@ class Access_model return $rowCount; } + /** deprecate user account + * by @param int $uid: user-id + */ + public static function deprecate_user($uid) + { + Registry::use('database')->runQuery( + "UPDATE user + SET active = :active, + deprecated = :deprecated + WHERE id = :id", + [ + ':active' => 0, + ':deprecated' => 1, + ':id' => $uid + ] + ); + return true; + } + + + /** invite user + * + * update user with invitation data + * + * @param int $uid: user-id + * @param hash $invitation: MD5 hash invitation + * @param int expiration: expiration timestamp + */ + public static function invite_user($uid, $invitation, $expiration) + { + Registry::use('database')->runQuery( + "UPDATE user + SET invitation = :inv, + otp_expiration = :expiration + WHERE id = :id", + [ + ':inv' => $invitation, + ':expiration' => $expiration, + ':id' => $uid + ] + ); + } + + + /** activate user account + * by @param int $uid: user-id + */ + public static function activate_user($uid) + { + Registry::use('database')->runQuery( + "UPDATE user + SET active = :active, + deprecated = :deprecated + WHERE id = :id", + [ + ':active' => 1, + ':deprecated' => NULL, + ':id' => $uid + ] + ); + return true; + } + ## Set permission methods diff --git a/public/app/routes/api.php b/public/app/routes/api.php index dd0fecf..e8dbafe 100644 --- a/public/app/routes/api.php +++ b/public/app/routes/api.php @@ -12,7 +12,7 @@ use app\controllers\api\Doc_api; */ -// depricated: (not needed) +// DEPRECATED: (not needed) // api: database documentation // Route::add('/api/doc', diff --git a/public/app/routes/frontend.php b/public/app/routes/frontend.php index 48d5da3..d58d483 100644 --- a/public/app/routes/frontend.php +++ b/public/app/routes/frontend.php @@ -1,51 +1,61 @@ <?php use app\controllers\Auth; -use app\controllers\App_user; -use app\controllers\Office; -use app\extends\Cache_service; // Home/Welcome //////////////////////////////////////////////////////////////////////////////// -// Route::add('/', function() { Render::view('welcome'); }); -Route::add('/', function() { Render::view('user/login'); }); +// welcome -> redirecto to login +Route::add('/', function() { header("Location: /login");; }); -// invitation +// Error pages //////////////////////////////////////////////////////////////////////////////// -// request invitation (for account activation) -// --- -- -- - - - -Route::add('/invitation/([0-9a-f]*)', function($id) { - if ($id == "" || $id == "0") { Render::view('error/404'); - } else { - Auth::invitation($id); - } +Route::notFound( function() { // 404 error page + header("HTTP/1.0 404 Not Found"); + Render::view('error/404', ['message' => 'nobody knows it (but you got a secret smile;)']); }); -// request activation (submit invitation; POST:form is sent) -// --- -- -- - - - -Route::add('/account/activate', function() { Auth::activate(); }, 'post'); -// DEPRICATED: user sends a registration form -// --- -- -- - - - -# Route::add('/account/register', function() { -# $response = Auth::register(); -# Render::json($response); -# },'post'); +// NOTE: this set of routes defines all valid NON-AUTHenticated requests +//////////////////////////////////////////////////////////////////////////////// +if (!Auth::is_connected()) { -// Error pages -//////////////////////////////////////////////////////////////////////////////// + // request login + // render Form -> will POST: /account/check-login + // --- -- -- - - - + Route::add('/login', function() { Render::view('user/login'); }); -Route::notFound( function() { // 404 error page - header("HTTP/1.0 404 Not Found"); - Render::view('error/404', ['message' => 'nobody knows it (but you got a secret smile;)']); -}); + + // user sends login form + // --- -- -- - - - + Route::add('/account/check-login', function() { + $response = Auth::login(); + Render::json(['status' => $response]); + }, 'post'); + + + // request invitation (for account activation) + // render Form -> will POST /account/activate + // --- -- -- - - - + Route::add('/invitation/([0-9a-f]*)', function($id) { + if ($id == "" || $id == "0") { Render::view('error/404'); + } else { + Auth::invitation($id); + } + }); + + + // POST invitation = activate + // --- -- -- - - - + Route::add('/account/activate', function() { Auth::activate(); }, 'post'); + +} diff --git a/public/app/routes/user.php b/public/app/routes/user.php index 622dc57..63e4e07 100644 --- a/public/app/routes/user.php +++ b/public/app/routes/user.php @@ -1,8 +1,9 @@ <?php use app\controllers\Auth; -use app\controllers\Classroom_user; use app\controllers\Office; +use app\models\Content_model; +use app\models\Access_model; /** User Connection Management Routes @@ -12,14 +13,6 @@ use app\controllers\Office; // common requests (GET method) // --- -- -- - - - -// request login ... -> then sends to POST:/account/check-login -Route::add('/login', function() { - if (Auth::is_connected()) { header('Location: /panel'); - } else { - Render::view('user/login'); - } -}); - // request logout Route::add('/logout', function() { Auth::logout(); @@ -27,53 +20,20 @@ Route::add('/logout', function() { }); -// TODO: -// // request password reset -// Route::add('/account/reset_password', function() { Auth::reset_password(); } ); - - -// Replies to common requests (POST method) -// --- -- -- - - - - -// user sends login form -Route::add('/account/check-login', function() { - $response = Auth::login(); - Render::json(['status' => $response]); - }, - 'post' -); - - - -/* DEPRICATED: - // Account Management - //////////////////////////////////////////////////////////////////////////////// - - // user profile - Route::add('/account/profile', function() { - $user = Auth::is_connected(); - if ($user === false) { - Render::view('error/general', - [ - 'title' => 'Nope!', - 'message' => "<h2>No profile</h2>User is not connected" - ] - ); - - } else { - Render::json(['success' => true, 'status' => 'user is connected']); - } - } - ); ---- */ - // routes for connected users //////////////////////////////////////////////////////////////////////////////// if (Auth::is_connected()) { - // serve content + // login while authenticated ... -> redirect to panel + Route::add('/login', function() { header('Location: /panel'); }); + + // set invitation while authenticated ... -> redirect to panel + Route::add('/invitation/([0-9a-f]*)', function($id) { header('Location: /panel'); }); + + + // SERVE content // ------------------------------------------------------------------------- // control panel @@ -159,7 +119,6 @@ if (Auth::is_connected()) { Office::add_petition(); }, 'post'); - } @@ -176,9 +135,27 @@ if (Auth::in_admin_group()) { }); - // GET JSON: request RESULTS list my petitions + // GET HTML: administer users (page) + // --- -- -- - - - + Route::add('/admin/users', function() { + Render::view('templates/admin_users'); + }); + + + // GET JSON: request RESULTS list all petitions Route::add('/admin/get/petitions', function() { - Office::all_petitions(); + Render::json([ + 'success' => true, + 'data' => Content_model::all_petitions() + ]); + }); + + + // GET JSON: request RESULTS list all petitions + Route::add('/admin/get/users', function() { + Render::json( Registry::use('database')->runQuery( + "SELECT * FROM user ORDER BY last_name, first_name", [] + )); }); @@ -188,22 +165,42 @@ if (Auth::in_admin_group()) { }, 'post'); + // GET AJAX: activate user (with user-id) + Route::add('/admin/user/activate/([0-9]*)', function( $uid ) { + Render::json([ 'success' => Access_model::activate_user($uid) ]); + // TODO: Cache_service::update_teachers(); + }); + + + // GET AJAX: depricate user (with user-id) + Route::add('/admin/user/deprecate/([0-9]*)', function( $uid ) { + Render::json([ 'success' => Access_model::deprecate_user($uid) ]); + // TODO: Cache_service::update_teachers(); + }); + + // invite user(s) // --- -- -- - - - Route::add('/admin/invite', function() { $teachers = Registry::use('database')->runQuery( "SELECT id, concat(last_name, ' ', first_name) as TeacherName - FROM user ORDER BY TeacherName - WHERE otp_expiration < :now OR otp_expiration IS NULL", + FROM user + WHERE ( + otp_expiration < :now OR otp_expiration IS NULL + ) AND deprecated IS NULL + ORDER BY TeacherName", [ ':now' => time() ] ); Render::view('templates/invite', ['teachers' => $teachers]); }); + // POST AJAX: set petition protocol + // --- -- -- - - - Route::add('/admin/send/invitation', function() { Office::send_invitation(); }, 'post'); + } @@ -214,9 +211,8 @@ if (Auth::in_admin_group()) { // test connection if (!PRODUCTION) { - Route::add('/check/connection', function() { Auth::is_connected(); }); + Route::add('/check/connection', function() { + Render::json( Auth::is_connected() ); + }); } - - - diff --git a/public/app/views/components/header_includes.php b/public/app/views/components/header_includes.php index bd72cb6..c096bd5 100644 --- a/public/app/views/components/header_includes.php +++ b/public/app/views/components/header_includes.php @@ -2,6 +2,10 @@ if (!isset($administration)) { $administration = false; } + + if (!isset($no_dataTables)) { + $no_dataTables = false; + } ?> <!-- FONTS and LIBRARY CSS (self-hosted) @@ -52,13 +56,15 @@ <script src="<?=SITE_URL?>/assets/js/pdfmake/pdfmake.min.js"></script> <script src="<?=SITE_URL?>/assets/js/pdfmake/vfs_fonts.js"></script> +<?php if (!$no_dataTables) : ?> <!-- DataTables js --> <script src="/assets/js/DataTables/datatables.min.js"></script> +<?php endif; ?> <!-- select2-4.1.0-rc.0 js --> <script src="/assets/js/select2-4.1.0-rc0/js/select2.min.js"></script> -<?php /* DEPRICATED: +<?php /* DEPRECATED: <!-- marked --> <script src="/assets/js/marked/marked.min.js"></script> */ diff --git a/public/app/views/components/menu.php b/public/app/views/components/menu.php index 30ed673..08d459b 100644 --- a/public/app/views/components/menu.php +++ b/public/app/views/components/menu.php @@ -21,6 +21,7 @@ <?php if ($is_admin) : ?> <li class="list-group-item separator">Διαχείριση</li> <li class="list-group-item"><a href="/admin/petitions">Κατάσταση πρακτικών & αιτήσεων</a></li> + <li class="list-group-item"><a href="/admin/users">Διαχείριση Χρηστών</a></li> <li class="list-group-item"><a href="/admin/invite">Αποστολή προσκλήσεων</a></li> <?php endif; ?> diff --git a/public/app/views/components/theme.php b/public/app/views/components/theme.php index 4e95193..a769dce 100644 --- a/public/app/views/components/theme.php +++ b/public/app/views/components/theme.php @@ -1,9 +1,29 @@ <?php +/** themes + * --- -- -- - - - + * auto select one id of L = array.LENGTH, per month: + * ( ( (month*31 + day) % L*2 ) div 2 ) + * this will allow theme exchange every 2 days + * + * + * 4-day rollup: + * --- -- -- - - - + * supports up to 21 themes/season + * = 84 themes/year + * + * algo: + * ( ( ( month*31 + day) % L*4 ) div 4 ) + * change seasonal theme every 4 days + * ... Catch Exceptions: + * if (month == 12) : month = 0 + * if (month == 6 && day == 1) then day = 2 + * + */ -// decide a theme according to month -$month = idate('m'); +// decide a theme season according to month +$mo = intval(date('m')); -switch ($month) { +switch ($mo) { case 12: case 1: case 2: $season = 'winter'; break; @@ -25,27 +45,6 @@ switch ($month) { } -/** themes - * --- -- -- - - - - * auto select one id of L = array.LENGTH, per month: - * ( ( (month*31 + day) % L*2 ) div 2 ) - * this will allow theme exchange every 2 days - * - * - * 4-day rollup: - * --- -- -- - - - - * supports up to 21 themes/season - * = 84 themes/year - * - * algo: - * ( ( ( month*31 + day) % L*4 ) div 4 ) - * change seasonal theme every 4 days - * ... Catch Exceptions: - * if (month == 12) : month = 0 - * if (month == 6 && day == 1) then day = 2 - * - */ - $theme_def = [ # --- summer --- @@ -76,15 +75,40 @@ $theme_def = [ 'label' => 'Doc Searls: Amazing Santa Barbara sunset (2014/01)' ], + 'f9' => [ + 'css' => 'fall-09', + 'url' => 'https://www.freepik.com/free-photo/flock-birds-flying-during-sunset_13962565.htm', + 'label' => 'wirestock: View of a flock of birds flying into a beautiful sky during sunset' + ], + # --- winter --- + 'w3' => [ + 'css' => 'winter-03', + 'url' => 'https://unsplash.com/photos/yzkoleKww6w', + 'label' => 'Raimond Klavins: Snow covered mountain under blue sky during daytime, Himalayas' + ], + + 'w8' => [ + 'css' => 'winter-08', + 'url' => 'https://www.freepik.com/free-photo/3d-iceberg-blue-sea_3585791.htm', + 'label' => 'kjpargeter: 3d iceberg in blue sea' + ], + 'w9' => [ 'css' => 'winter-09', 'url' => 'https://www.freepik.com/free-photo/abstract-water-waves-with-ink-dots_5068293.htm', 'label' => 'freepik: Abstract water waves with ink dots' ], + 'w10' => [ + 'css' => 'winter-10', + 'url' => 'https://unsplash.com/photos/D1eFDB4CMj0', + 'label' => 'Vidar Nordli-Mathisen: Skogsøya, Norway' + ], + + # --- spring --- 'g1' => [ @@ -111,21 +135,28 @@ $theme_def = [ $themes = [ - 'summer' => [ 's1', 's5', 'g9', 's2', 'g1' ], + 'summer' => [ 's1', 's2', 's5', 'g9', 'g1' ], - 'fall' => [ 'f8', 's5', 'g4', 's2' ], + 'fall' => [ 'f8', 's5', 'g4', 's2', 'f9' ], - 'winter' => [ 'w9' ], + 'winter' => [ 'w3', 'w8', 'w9', 'w10' ], - 'spring' => [ 'g1', 'g4', 'g9' ] + 'spring' => [ 'g1', 'g4', 'w3', 'g9', 'f9' ] ]; +/** check algo in the begining of this file */ +$day = intval(date('d')); +$themesLen = count($themes[$season]); +if ($mo == 12) { $mo = 0; } +if ($mo == 6 && $day == 1) { $day == 2; } +$imageID = intdiv( ( ( $mo*31 + $day) % $themesLen*4 ), 4 ); -define('THEME', $theme_def[ $themes['summer'][0] ]); +define('THEME', $theme_def[ $themes[ $season ][ $imageID ] ]); +// testing: define('THEME', $theme_def[ $themes[ 'winter' ][ 1 ] ]); ?> <!-- theme overides --> diff --git a/public/app/views/js/credits.php b/public/app/views/js/credits.php index ca0f15c..5dd3fe6 100644 --- a/public/app/views/js/credits.php +++ b/public/app/views/js/credits.php @@ -5,6 +5,30 @@ ic_div.innerHTML = '<a href="<?=THEME['url']?>" target="_blank" title="Attributi ic_div.className = 'image-credits'; // append image-credits div to body -document.querySelector('body').appendChild(ic_div); +document.querySelector('body').appendChild(ic_div); + +// make sure... +// the user agrees with cookie-policy on his device +let cookieExp = 3600*24*200; // cookie expiration (200 days) +if (!cookieExists('operational') || (readCookie('operational') == 'no')) { + let confirm_cookies = confirm([ + 'Ο ιστότοπος χρησιμοποιεί cookies τα οποία έχουν αποκλειστικά λειτουργικό ρόλο.', + ' ', + 'Ειδικότερα:', + '– ένα Session cookie που εγγυάται την ασφάλεια της σύνδεσής σας', + '– ένα Http-only cookie που διατηρεί κωδικοποιημένη την κατάσταση της σύνδεσής σας μετά την είσοδό σας στο σύστημα, και', + '– ένα cookie που εξετάζει αν έχετε αποδεχθεί τη χρήση αυτών των cookies.', + ' ', + 'Και τα 3 αυτά cookies είναι Secure, Same-site, Non-Tracking cookies.', + ' ', + 'Συμφωνείτε με τη διατήρηση αυτών των cookies;' + ].join('\n')) + if (!confirm_cookies) { + window.location.href = 'https://en.wikipedia.org/wiki/HTTP_cookie'; + + } else { + createCookie('operational', 'yes', cookieExp); + } +} </script>
\ No newline at end of file diff --git a/public/app/views/js/submit/application-form.php b/public/app/views/js/submit/application-form.php index 272fbcb..d34b249 100644 --- a/public/app/views/js/submit/application-form.php +++ b/public/app/views/js/submit/application-form.php @@ -70,7 +70,7 @@ // bolditalics: 'https://example.com/fonts/fontFile4.ttf' }, FiraMono: { - normal: '<?=SITE_URL?>/assets/fonts/FiraMono-Regular.ttf', + normal: '<?=SITE_URL?>/assets/fonts/FiraMono-Regular.ttf' } } diff --git a/public/app/views/templates/admin_users.php b/public/app/views/templates/admin_users.php new file mode 100644 index 0000000..baa2e58 --- /dev/null +++ b/public/app/views/templates/admin_users.php @@ -0,0 +1,134 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <title><?=SITE_TITLE?> - Control Panel</title> + + <?php // header includes + //////////////////////////////////////////////////////////////////////// + Render::view('components/header_includes'); + ?> +<style> +:root { /* login-form overides */ + --form-content-font-size: 1rem; + --form-input-height: 34px; + --select2-arrow-top: 1px; +} +</style> +</head> +<body class="office central-form left-menu"> + + <div class='container'> + <div class='content row'> + + <div class="col-sm-12"> + +<div class="manage"> + <!-- title bar --> + <div class="title-bar"> + <div class="row"> + <h5 class="col-md-10">Διαχείριση Χρηστών</h5> + <div class="col-md-2"> + <a type="button" class="btn btn-sm btn-back2panel pull-right" href="/panel"> + Επιστροφή + </a> + </div> + </div> + + </div> + + + <div class="row"> + <div class="col-md-12"> + + <div id="petitions"> + + <table id="dt-petitions" class="table table-responsive dt-table" style="width:100%"> + <thead> + <th class="dt-last_name">Επίθετο</th> + <th class="dt-first_name">Όνομα</th> + <th class="dt-email">email</th> + <th class="dt-sector">Τομέας</th> + <th class="dt-status">Status</th> + <th class="dt-actions"></th> + </thead> + </table> + + </div> + + </div> + </div> + +</div><!-- /manage --> + + </div> + + </div> + </div> + + + + + <!-- MODAL for set protocol number --> + <div class="modal fade" id="managePetition" aria-labelledby="myModalLabel" aria-hidden="true"> + <div class="modal-dialog modal-lg" role="document"> + <div class="modal-content"> + + <form method="post" action=""> + + <div class="modal-header"> + <h4 class="modal-title" id="myModalLabel">Ενημέρωση αριθμού πρωτόκολλου</h4> + <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> + </div><!-- /modal-header --> + + <!-- modal-body --> + <div class="modal-body"> + + <div class="row form-group"> + <label class="control-label col-sm-12" for="protocol">Αριθμός Πρψτοκόλλου</label> + <div class="col-sm-12"> + <input class="form-control form-control-sm" type="text" name="protocol" value="" required=""> + </div> + </div> + + <div class="row form-group"><!-- hiddens --> + <input type="hidden" name="id" value="0"><!-- petition-id --> + <input type="hidden" name="signature" value=""><!-- signature --> + </div> + + </div><!-- /modal body --> + + <div class="modal-footer"> + <div class="row form-actions" style="width: 50%;"> + + <div class="col-sm-4"> + <button type="button" class="btn btn-default js-modal-close col-12" data-bs-dismiss="modal">Κλείσιμο</button> + </div> + <div class="col-sm-8"> + <button class="btn btn-primary col-12" type="submit">Καταχώριση</button> + </div> + + </div> + </div><!-- /modal-footer --> + + </form><!-- /form --> + + </div> + </div> + </div> + + </body> + +<script src="/assets/js/panel/admin_users.js"></script> + + <!-- scripts + * handle show petition request + + if admin: + * modal + give protocol-number + --> +<?php // credits + ////////////////////////////////////////////////////////////////////////////// + Render::view('js/credits'); +?> +</html> diff --git a/public/app/views/templates/application.php b/public/app/views/templates/application.php index 35b1435..72c9251 100644 --- a/public/app/views/templates/application.php +++ b/public/app/views/templates/application.php @@ -6,7 +6,7 @@ <?php // header includes //////////////////////////////////////////////////////////////////////// - Render::view('components/header_includes'); + Render::view('components/header_includes', [ 'no_dataTables' => true ]); ?> </head> diff --git a/public/app/views/templates/create_pdf.php b/public/app/views/templates/create_pdf.php index 19b2894..6b5c164 100644 --- a/public/app/views/templates/create_pdf.php +++ b/public/app/views/templates/create_pdf.php @@ -58,7 +58,7 @@ pdfMake.fonts = { // bolditalics: 'https://example.com/fonts/fontFile4.ttf' }, FiraMono: { - normal: '<?=SITE_URL?>/assets/fonts/FiraMono-Regular.ttf', + normal: '<?=SITE_URL?>/assets/fonts/FiraMono-Regular.ttf' } } diff --git a/public/app/views/templates/invite.php b/public/app/views/templates/invite.php index d7947da..ab0f12c 100644 --- a/public/app/views/templates/invite.php +++ b/public/app/views/templates/invite.php @@ -59,7 +59,7 @@ </form> </div> - <div class="info"> + <div class="info clear-read"> <p> Χρησιμοποιήστε τη φόρμα για να προσκαλέσετε νέους χρήστες ή για reset ενός account. @@ -120,7 +120,11 @@ $(document).ready(function() { window.location.href = '/panel'; } else { - alert('Τα στοιχεία πρόσβασης που δώσατε είναι λάθος. Παρακαλώ προσπαθήστε ξανά.'); + alert([ + 'Κατάσταση αποστολής προσλήσεων:\\n\n', + data.sent, + 'Υπήρξαν προβλήματα στην αποστολή κάποιων προσκλήσεων.' + ].join('')); } }); diff --git a/public/app/views/templates/penalty.php b/public/app/views/templates/penalty.php index cad9221..232e8a8 100644 --- a/public/app/views/templates/penalty.php +++ b/public/app/views/templates/penalty.php @@ -6,7 +6,7 @@ <?php // header includes //////////////////////////////////////////////////////////////////////// - Render::view('components/header_includes'); + Render::view('components/header_includes', [ 'no_dataTables' => true ]); ?> </head> @@ -93,7 +93,7 @@ -<!-- DEPRICATED: +<!-- DEPRECATED: <script> $(document).ready(function() { diff --git a/public/app/views/user/invitation.php b/public/app/views/user/invitation.php index 328c621..64e5bcb 100644 --- a/public/app/views/user/invitation.php +++ b/public/app/views/user/invitation.php @@ -6,7 +6,7 @@ <?php // header includes //////////////////////////////////////////////////////////////////////// - Render::view('components/header_includes'); + Render::view('components/header_includes', [ 'no_dataTables' => true ]); ?> </head> <body class="office central-form"> @@ -37,7 +37,7 @@ </form> </div> - <div class="info"> + <div class="info clear-read"> <p> Απαιτείται η συμπλήρωση όλων των στοιχείων προκειμένου να γίνεται γρήγορα η σύνταξη των εγγράφων. diff --git a/public/app/views/user/login.php b/public/app/views/user/login.php index 6bf3b0a..a9517c6 100644 --- a/public/app/views/user/login.php +++ b/public/app/views/user/login.php @@ -45,7 +45,7 @@ </form> </div> - <div class="info"> + <div class="info clear-read"> <!-- <p>Αν ξεχάσατε τον κωδικό πρόσβασης μπορείτε να δημιουργήσετε ένα νέο (υπο κατασκευή).</p> --> @@ -53,10 +53,6 @@ Εαν δικαιούστε αλλά δεν έχετε πρόσβαση στο site, παρακαλούμε επικοινωνήστε με τη διεύθυνση του σχολείου προκειμένου να σας αποσταλεί σχετική πρόσκληση. </p> - <p> - Ο ιστότοπος χρησιμοποιεί cookies τα οποία έχουν αποκλειστικά λειτουργικό ρόλο. - Η χρήση των υπηρεσιών του site συνεπάγεται την αποδοχή διατήρησης αυτών των cookies. - </p> </div> </div> diff --git a/public/app/views/user/update_password.php b/public/app/views/user/update_password.php index 38494ad..b415f7c 100644 --- a/public/app/views/user/update_password.php +++ b/public/app/views/user/update_password.php @@ -6,7 +6,7 @@ <?php // header includes //////////////////////////////////////////////////////////////////////// - Render::view('components/header_includes'); + Render::view('components/header_includes', [ 'no_dataTables' => true ]); ?> </head> <body class="office central-form"> diff --git a/public/assets/css/class.css b/public/assets/css/class.css index 6c0c1bf..7aa3a2a 100644 --- a/public/assets/css/class.css +++ b/public/assets/css/class.css @@ -25,6 +25,7 @@ /* layer backgrounds */ --form-layer-bgr: #dddb; /* form layer background */ --backlayer-bgr: transparent; + --clear-read-bgr: #fff5; /* select2 */ --select2-arrow-top: 10px; @@ -350,6 +351,8 @@ input.form-control-sm { height: var(--form-input-height); } } - .btn-xs { height: 24px; padding: 1px 8px; font-size: 14px; } + +.clear-read p { padding: 8px; border-radius: 6px; } +.clear-read p:hover, .clear-read p:active { background: var(--clear-read-bgr); }
\ No newline at end of file diff --git a/public/assets/css/themes/fall-09.css b/public/assets/css/themes/fall-09.css new file mode 100644 index 0000000..05681ef --- /dev/null +++ b/public/assets/css/themes/fall-09.css @@ -0,0 +1,8 @@ +:root { + --background-image: url(/assets/media/fall-09.webp); + --form-layer-bgr: #cccc; + --bs-heading-color: #657; + --menu-hover-bgr: #fff0dc; + --info-text-color: #112; + --img-credit-hover-color: #334; +} diff --git a/public/assets/css/themes/spring-09.css b/public/assets/css/themes/spring-09.css index 97fe4bb..39c3ed3 100644 --- a/public/assets/css/themes/spring-09.css +++ b/public/assets/css/themes/spring-09.css @@ -1,7 +1,7 @@ :root { --background-image: url(/assets/media/spring-09.webp); --bs-heading-color: #230; - --menu-hover-bgr: #bee; + --menu-hover-bgr: #d6f899; --info-text-color: #334; --img-credit-hover-color: #334; } diff --git a/public/assets/css/themes/winter-03.css b/public/assets/css/themes/winter-03.css new file mode 100644 index 0000000..b8e9df7 --- /dev/null +++ b/public/assets/css/themes/winter-03.css @@ -0,0 +1,13 @@ +:root { + --background-image: url(/assets/media/winter-03.webp); + --bs-heading-color: #035; + --menu-hover-bgr: #cef; + --info-text-color: #223; + + --img-credit-hover-color: #334; + --img-credit-bgr: transparent; + --img-credit-color: #9aa; +} + +.central-form form label { color: #313; } +tbody, td, tfoot, th, thead, tr { border-color: #def7; }
\ No newline at end of file diff --git a/public/assets/css/themes/winter-08.css b/public/assets/css/themes/winter-08.css new file mode 100644 index 0000000..c589e34 --- /dev/null +++ b/public/assets/css/themes/winter-08.css @@ -0,0 +1,11 @@ +:root { + --background-image: url(/assets/media/winter-08.webp); + --bs-heading-color: #035; + --menu-hover-bgr: #cef; + --info-text-color: #223; + + --img-credit-hover-color: #334; +} + +.central-form form label { color: #313; } +tbody, td, tfoot, th, thead, tr { border-color: #def7; }
\ No newline at end of file diff --git a/public/assets/css/themes/winter-10.css b/public/assets/css/themes/winter-10.css new file mode 100644 index 0000000..0673bd0 --- /dev/null +++ b/public/assets/css/themes/winter-10.css @@ -0,0 +1,25 @@ +/* dark theme */ +:root { + --background-image: url(/assets/media/winter-10.webp); + --light-grey-shade: 1px 5px 7px #2227; + --form-layer-bgr: #878b; + --clear-read-bgr: transparent; + + --table-row-bgr: #eee2; + --table-odd-row-bgr: #fff3; + --table-head-bgr: #fff5; + --table-row-hover-bgr: #0137; + + --bs-heading-color: #dcb; + --info-text-color: #c8b5a3; + --menu-hover-bgr: #fcc; + + --img-credit-hover-color: #334; + --img-credit-bgr: transparent; + --img-credit-color: #9aa; +} + +.central-form form label { color: #fed; } + +tbody, td, tfoot, th, thead, tr { border-color: transparent; } +table.dt-table tr > td, table.dt-table tr > th { color: #fff; } diff --git a/public/assets/js/panel/admin_users.js b/public/assets/js/panel/admin_users.js new file mode 100644 index 0000000..62a2aff --- /dev/null +++ b/public/assets/js/panel/admin_users.js @@ -0,0 +1,177 @@ +// Globals +// ----------------------------------------------------------------------------- +var petitions = []; +var table; + + + +// Supplamentary functions +// ----------------------------------------------------------------------------- + +// array.indexOf polyfill +// --- -- -- - - - +if (!Array.prototype.indexOf) +{ + Array.prototype.indexOf = function(elt /*, from*/) + { + var len = this.length >>> 0; + + var from = Number(arguments[1]) || 0; + from = (from < 0) + ? Math.ceil(from) + : Math.floor(from); + if (from < 0) + from += len; + + for (; from < len; from++) + { + if (from in this && + this[from] === elt) + return from; + } + return -1; + }; +} + + +// return petitions user by user-id +// --- -- -- - - - +function user_record(id) { + var record = false; + petitions.forEach(el => { if (el.id == id) record = el; }); + + return record; +} + + +// create actions html (edit and delete buttons) +// --- -- -- - - - +function create_actions_html(el) { + if (el.deprecated == null) { + icon = '<i class="fa-solid fa-minus"></i>'; + action = 'deprecate'; + title = 'κατάργηση'; + type = 'warning'; + + } else { + icon = '<i class="fa-solid fa-plus"></i>'; + action = 'activate'; + title = 'προσθήκη'; + type = 'primary'; + } + + return [ + '<button type="button" class="btn btn-xs btn-'+ type +'" title="'+ title +'"', + 'data-id="'+ el.id +'" data-action="'+ action +'" >', + icon, + '</button>' + ].join('\n'); + +} + + + +$(document).ready(function() { + + // When document is ready + // ------------------------------------------------------------------------- + + + /** init_table + * --- -- -- - - - + * get categories + * constuct categories array + * render dataTable + */ + function init_table() { + + // get all categories from server (ajax) + $.getJSON( "/admin/get/users") + .done(function( json ) { + // then ... + console.log(json); + + // reset categories + petitions.length = 0; + + // construct petitions data + json.forEach( el => { + rec = { + id: parseInt(el.id), + first_name: el.first_name, + last_name: el.last_name, + email: el.email, + sector: el.sector, + status: (el.deprecated == 1) + ? ('<i class="fa-regular fa-eye-slash"></i> / deprecated') + : ('<i class="fa-regular fa-eye"></i> / ' + ((el.active == 1) + ? 'active' + : 'pending') + ), + deprecated: el.deprecated + } + rec.actions = create_actions_html(rec); + petitions.push(rec); + }); + + + // destroy previous datatable table instances + $('#dt-petitions').dataTable().fnClearTable(); + $('#dt-petitions').dataTable().fnDestroy(); + + // (re-)create categories datatable + table = $('#dt-petitions').DataTable({ + language: { url: '/assets/js/DataTables/localization/Greek.json' }, + data: petitions, + // ordering: false, + columns: [ + { data: 'last_name' }, + { data: 'first_name'}, + { data: 'email' }, + { data: 'sector' }, + { data: 'status' }, + { data: 'actions', class: 'dt-actions' } + ] + }); + + }) + .fail(function( jqxhr, textStatus, error ) { + console.log( "Request Failed (" + error +")" ); + }); + + } + init_table(); // init table on first run + + + + /** When click to action button + * ------------------------------------------------------------------------- + */ + $('body').on('click', '.dt-actions button', event => { + event.preventDefault(); + var button; + + if ($(event.target).data('id')) { // click on button + button = $(event.target) + + } else { // click on button's content + button = $(event.target).parent('button'); + } + + var id = button.data('id'); // user-id from data-id + var action = button.data('action'); // action to take + + console.log(action, id); + + var request = '/admin/user/'+ action +'/'+ id; + + $.get(request) + .done(function(data) { + init_table(); + }); + + }) + + + +}); diff --git a/public/assets/media/credits.txt b/public/assets/media/credits.txt index ba6120d..adcf74d 100644 --- a/public/assets/media/credits.txt +++ b/public/assets/media/credits.txt @@ -59,7 +59,6 @@ https://unsplash.com/photos/2s3fI3M1lO0 +? https://unsplash.com/photos/OF7jUVrEDJQ ++ https://unsplash.com/photos/DxRXjBQAb8I : Nykvåg, Norway -> text:#fee ++ https://unsplash.com/photos/NWANKTV5yHU : Leknes, Norway -++ https://unsplash.com/photos/D1eFDB4CMj0 : -> text:#dcb ++ https://unsplash.com/photos/wyM1KmMUSbA : Vittangi, Sweden ++ https://unsplash.com/photos/6gVOt8qOJ08 : Ljungskile, Sweden ++ https://unsplash.com/photos/AkUR27wtaxs : Högakustenbron, Sweden -> text:#fed @@ -192,7 +191,6 @@ winter+spring winter -++ https://www.freepik.com/free-photo/3d-iceberg-blue-sea_3585791.htm ++ https://www.freepik.com/free-photo/fantastic-winter-landscape-mountains-magical-sunset_9144399.htm +++ https://www.freepik.com/free-photo/cottage-glen-etive-scotland_16462487.htm @@ -292,7 +290,7 @@ https://www.flickr.com/photos/138402863@N02/23445581921/ !++ https://www.flickr.com/photos/78423546@N06/43429924691/ = PD https://www.flickr.com/photos/78423546@N06/42135640880/ = PD https://www.flickr.com/photos/78423546@N06/52780304947/ = PD -https://www.flickr.com/photos/tomcollinsphoto/40178410432/ !++ +https://www.flickr.com/photos/tomcollinsphoto/40178410432/ !+++ https://www.flickr.com/photos/164417177@N06/44004978630/ ? https://www.flickr.com/photos/94284791@N05/39229046194/ ! https://www.flickr.com/photos/hoanghaithinh/45678059161/ @@ -306,3 +304,12 @@ https://www.flickr.com/photos/129030830@N02/37032688484/ https://www.flickr.com/photos/drtonygeorge/1474585836/ ! */ + + + +[downloaded] +wirestock +Breathtaking shot of the lake wanaka in wanaka village, new zealand +breathtaking-shot-lake-wanaka-wanaka-village-new-zealand.jpg +https://www.freepik.com/free-photo/breathtaking-shot-lake-wanaka-wanaka-village-new-zealand_11111328.htm + diff --git a/public/assets/media/fall-09.webp b/public/assets/media/fall-09.webp Binary files differnew file mode 100644 index 0000000..09795e1 --- /dev/null +++ b/public/assets/media/fall-09.webp diff --git a/public/assets/media/summer-0.jpg b/public/assets/media/summer-0.jpg Binary files differdeleted file mode 100644 index e4deb86..0000000 --- a/public/assets/media/summer-0.jpg +++ /dev/null diff --git a/public/assets/media/summer-01.jpg b/public/assets/media/summer-01.jpg Binary files differdeleted file mode 100644 index b769bf5..0000000 --- a/public/assets/media/summer-01.jpg +++ /dev/null diff --git a/public/assets/media/winter-03.jpg b/public/assets/media/winter-03.jpg Binary files differdeleted file mode 100644 index 383cb91..0000000 --- a/public/assets/media/winter-03.jpg +++ /dev/null diff --git a/public/assets/media/winter-03.webp b/public/assets/media/winter-03.webp Binary files differnew file mode 100644 index 0000000..aa1303e --- /dev/null +++ b/public/assets/media/winter-03.webp diff --git a/public/assets/media/winter-08.jpg b/public/assets/media/winter-08.jpg Binary files differdeleted file mode 100644 index 291bc9d..0000000 --- a/public/assets/media/winter-08.jpg +++ /dev/null diff --git a/public/assets/media/winter-08.webp b/public/assets/media/winter-08.webp Binary files differnew file mode 100644 index 0000000..c06cc6c --- /dev/null +++ b/public/assets/media/winter-08.webp diff --git a/public/assets/media/winter-10.webp b/public/assets/media/winter-10.webp Binary files differnew file mode 100644 index 0000000..7885773 --- /dev/null +++ b/public/assets/media/winter-10.webp |
