├── .gitignore ├── .htaccess ├── .scrutinizer.yml ├── LICENSE ├── README.md ├── _installation ├── 01 database.sql ├── 02 tables.sql ├── 03 triggers.sql └── 04 users.sql ├── app ├── .htaccess ├── backups │ └── .gitignore ├── config │ ├── config.php │ └── javascript.php ├── controllers │ ├── AdminController.php │ ├── CommentsController.php │ ├── DownloadsController.php │ ├── ErrorsController.php │ ├── FilesController.php │ ├── LoginController.php │ ├── NewsFeedController.php │ ├── PostsController.php │ ├── TodoController.php │ └── UserController.php ├── core │ ├── App.php │ ├── Component.php │ ├── Config.php │ ├── Controller.php │ ├── Cookie.php │ ├── Email.php │ ├── Encryption.php │ ├── Environment.php │ ├── Handler.php │ ├── Logger.php │ ├── Redirector.php │ ├── Request.php │ ├── Response.php │ ├── Session.php │ ├── View.php │ └── components │ │ ├── AuthComponent.php │ │ └── SecurityComponent.php ├── logs │ └── log.txt ├── models │ ├── Admin.php │ ├── Comment.php │ ├── Database.php │ ├── File.php │ ├── Login.php │ ├── Model.php │ ├── NewsFeed.php │ ├── Pagination.php │ ├── Permission.php │ ├── Post.php │ ├── Todo.php │ ├── Uploader.php │ ├── User.php │ └── Validation.php ├── uploads │ └── .gitignore ├── utility │ └── Utility.php └── views │ ├── admin │ ├── backups.php │ └── users │ │ ├── index.php │ │ ├── users.php │ │ └── viewUser.php │ ├── alerts │ ├── errors.php │ └── success.php │ ├── bugs │ └── index.php │ ├── dashboard │ ├── index.php │ └── updates.php │ ├── errors │ ├── 400.php │ ├── 401.php │ ├── 403.php │ ├── 404.php │ └── 500.php │ ├── files │ ├── files.php │ └── index.php │ ├── layout │ ├── default │ │ ├── footer.php │ │ ├── header.php │ │ └── navigation.php │ ├── errors │ │ ├── footer.php │ │ └── header.php │ ├── login │ │ ├── footer.php │ │ └── header.php │ └── todo │ │ ├── footer.php │ │ └── header.php │ ├── login │ ├── index.php │ ├── passwordUpdated.php │ ├── updatePassword.php │ └── userVerified.php │ ├── newsfeed │ ├── index.php │ ├── newsfeed.php │ └── updateForm.php │ ├── pagination │ ├── comments.php │ └── default.php │ ├── posts │ ├── commentUpdateForm.php │ ├── comments.php │ ├── index.php │ ├── newPost.php │ ├── post.php │ ├── postUpdateForm.php │ ├── posts.php │ └── viewPost.php │ ├── todo │ └── index.php │ └── user │ └── profile.php ├── composer.json └── public ├── .htaccess ├── css ├── bootstrap.min.css ├── font-awesome.min.css ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ └── fontawesome-webfont.woff └── sb-admin-2.css ├── img ├── backgrounds │ └── background.png ├── icons │ ├── favicon.ico │ ├── favicon.png │ ├── icon114.png │ ├── icon120.png │ ├── icon144.png │ ├── icon152.png │ ├── icon57.png │ ├── icon72.png │ └── icon76.png └── profile_pictures │ ├── author.jpg │ └── default.png ├── index.php └── js ├── bootstrap.min.js ├── jquery.min.js ├── main.js └── sb-admin-2.js /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | -------------------------------------------------------------------------------- /.htaccess: -------------------------------------------------------------------------------- 1 | # Disable server signature 2 | ServerSignature Off 3 | 4 | 5 | RewriteEngine on 6 | RewriteRule ^$ public/ [L] 7 | RewriteRule (.*) public/$1 [L] 8 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | checks: 2 | php: 3 | code_rating: true 4 | duplication: true -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Omar El Gabry 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /_installation/01 database.sql: -------------------------------------------------------------------------------- 1 | -- 2 | -- Create database 3 | -- 4 | 5 | CREATE DATABASE IF NOT EXISTS miniphp_68dozftn CHARACTER SET utf8 COLLATE utf8_general_ci 6 | -------------------------------------------------------------------------------- /_installation/02 tables.sql: -------------------------------------------------------------------------------- 1 | -- 2 | -- Table structure for table `users` 3 | -- 4 | 5 | CREATE TABLE `users` ( 6 | `id` int(11) NOT NULL AUTO_INCREMENT, 7 | `session_id` varchar(48) DEFAULT NULL, 8 | `cookie_token` varchar(128) DEFAULT NULL, 9 | `name` varchar(48) NOT NULL, 10 | `role` varchar(16) NOT NULL DEFAULT 'user', 11 | `hashed_password` varchar(128) NOT NULL, 12 | `email` varchar(64) NOT NULL, 13 | `is_email_activated` tinyint(1) NOT NULL DEFAULT '0', 14 | `email_token` varchar(48) DEFAULT NULL, 15 | `email_last_verification` int(11) DEFAULT NULL COMMENT 'unix timestamp', 16 | `pending_email` varchar(64) DEFAULT NULL COMMENT 'temporary email that will be used when user updates his current one', 17 | `pending_email_token` varchar(48) DEFAULT NULL, 18 | `profile_picture` varchar(48) NOT NULL DEFAULT 'default.png' COMMENT 'The base name for the image. Its not always unique because of default.jpg', 19 | PRIMARY KEY (`id`), 20 | UNIQUE KEY `email` (`email`) 21 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci; 22 | 23 | -- 24 | -- Table structure for table `failed_logins` 25 | -- 26 | 27 | CREATE TABLE `failed_logins` ( 28 | `id` int(11) NOT NULL AUTO_INCREMENT, 29 | `user_email` varchar(64) NOT NULL COMMENT 'It doesnt reference email in table users, this will prevent even unregistered users as well', 30 | `last_failed_login` int(11) DEFAULT NULL COMMENT 'unix timestamp of last failed login', 31 | `failed_login_attempts` int(11) NOT NULL DEFAULT '0', 32 | PRIMARY KEY (`id`), 33 | UNIQUE KEY `user_email` (`user_email`) 34 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci; 35 | 36 | -- 37 | -- Table structure for table `ip_failed_logins` 38 | -- 39 | 40 | CREATE TABLE `ip_failed_logins` ( 41 | `ip` varchar(48) NOT NULL, 42 | `user_email` varchar(64) NOT NULL COMMENT 'It doesnt reference email in table users', 43 | PRIMARY KEY (`ip`,`user_email`) 44 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci; 45 | 46 | -- 47 | -- Table structure for table `blocked_ips` 48 | -- 49 | 50 | CREATE TABLE `blocked_ips` ( 51 | `ip` varchar(48) NOT NULL, 52 | PRIMARY KEY (`ip`) 53 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci; 54 | 55 | -- 56 | -- Table structure for table `forgotten_passwords` 57 | -- 58 | 59 | CREATE TABLE `forgotten_passwords` ( 60 | `id` int(11) NOT NULL AUTO_INCREMENT, 61 | `user_id` int(11) NOT NULL, 62 | `password_token` varchar(48) DEFAULT NULL, 63 | `password_last_reset` int(11) DEFAULT NULL COMMENT 'unix timestamp of last password reset request', 64 | `forgotten_password_attempts` int(11) NOT NULL DEFAULT '0', 65 | PRIMARY KEY (`id`), 66 | UNIQUE KEY `forgotten_passwords_user` (`user_id`), 67 | FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE 68 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci; 69 | 70 | 71 | -- 72 | -- Table structure for table `posts` 73 | -- 74 | 75 | CREATE TABLE `posts` ( 76 | `id` int(11) NOT NULL AUTO_INCREMENT, 77 | `user_id` int(11) NOT NULL, 78 | `title` varchar(128) NOT NULL, 79 | `content` text NOT NULL, 80 | `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 81 | PRIMARY KEY (`id`), 82 | FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE 83 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci; 84 | 85 | -- 86 | -- Table structure for table `comments` 87 | -- 88 | 89 | CREATE TABLE `comments` ( 90 | `id` int(11) NOT NULL AUTO_INCREMENT, 91 | `user_id` int(11) NOT NULL, 92 | `post_id` int(11) NOT NULL, 93 | `content` varchar(512) NOT NULL, 94 | `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 95 | PRIMARY KEY (`id`), 96 | FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, 97 | FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE 98 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci; 99 | 100 | -- 101 | -- Table structure for table `newsfeed` 102 | -- 103 | 104 | CREATE TABLE `newsfeed` ( 105 | `id` int(11) NOT NULL AUTO_INCREMENT, 106 | `user_id` int(11) NOT NULL, 107 | `content` varchar(512) NOT NULL, 108 | `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 109 | PRIMARY KEY (`id`), 110 | FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE 111 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci; 112 | 113 | -- 114 | -- Table structure for table `files` 115 | -- 116 | 117 | CREATE TABLE `files` ( 118 | `id` int(11) NOT NULL AUTO_INCREMENT, 119 | `user_id` int(11) NOT NULL, 120 | `filename` varchar(48) NOT NULL COMMENT 'original file name', 121 | `hashed_filename` varchar(48) NOT NULL COMMENT 'The hashed file name generated from hash(filename . extension)', 122 | `extension` varchar(8) NOT NULL COMMENT 'The file extension', 123 | `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 124 | PRIMARY KEY (`id`), 125 | UNIQUE KEY `hashed_filename` (`hashed_filename`), 126 | FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE 127 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci; 128 | 129 | -- 130 | -- Table structure for table `notifications` 131 | -- 132 | 133 | CREATE TABLE `notifications` ( 134 | `user_id` int(11) NOT NULL, 135 | `target` varchar(16) NOT NULL COMMENT 'Represents the target of the notification, like files, posts, ...etc', 136 | `count` int(11) NOT NULL DEFAULT '0', 137 | PRIMARY KEY (`user_id`,`target`), 138 | FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE 139 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci; 140 | 141 | -- 142 | -- Table structure for table `todo` 143 | -- 144 | 145 | CREATE TABLE `todo` ( 146 | `id` int(11) NOT NULL AUTO_INCREMENT, 147 | `user_id` int(11) NOT NULL, 148 | `content` varchar(512) NOT NULL, 149 | PRIMARY KEY (`id`), 150 | FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE 151 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci; -------------------------------------------------------------------------------- /_installation/03 triggers.sql: -------------------------------------------------------------------------------- 1 | -- 2 | -- Triggers `users` 3 | -- 4 | DROP TRIGGER IF EXISTS `initialize_notifications`; 5 | DELIMITER // 6 | CREATE TRIGGER `initialize_notifications` AFTER INSERT ON `users` 7 | FOR EACH ROW INSERT INTO notifications (user_id, target, count) VALUES (NEW.id, "newsfeed", 0), (NEW.id, "files", 0), (NEW.id, "posts", 0) 8 | // 9 | DELIMITER ; 10 | 11 | -- 12 | -- Triggers `posts` 13 | -- 14 | DROP TRIGGER IF EXISTS `increment_notifications_posts`; 15 | DELIMITER // 16 | CREATE TRIGGER `increment_notifications_posts` AFTER INSERT ON `posts` 17 | FOR EACH ROW UPDATE notifications SET count = count+1 18 | 19 | WHERE notifications.user_id != NEW.user_id 20 | AND notifications.target = "posts" 21 | // 22 | DELIMITER ; 23 | 24 | -- 25 | -- Triggers `newsfeed` 26 | -- 27 | DROP TRIGGER IF EXISTS `increment_notifications_newsfeed`; 28 | DELIMITER // 29 | CREATE TRIGGER `increment_notifications_newsfeed` AFTER INSERT ON `newsfeed` 30 | FOR EACH ROW UPDATE notifications SET count = count+1 31 | 32 | WHERE notifications.user_id != NEW.user_id 33 | AND notifications.target = "newsfeed" 34 | // 35 | DELIMITER ; 36 | 37 | -- 38 | -- Triggers `files` 39 | -- 40 | DROP TRIGGER IF EXISTS `increment_notifications_files`; 41 | DELIMITER // 42 | CREATE TRIGGER `increment_notifications_files` AFTER INSERT ON `files` 43 | FOR EACH ROW UPDATE notifications SET count = count+1 44 | 45 | WHERE notifications.user_id != NEW.user_id 46 | AND notifications.target = "files" 47 | // 48 | DELIMITER ; 49 | 50 | -------------------------------------------------------------------------------- /_installation/04 users.sql: -------------------------------------------------------------------------------- 1 | -- 2 | -- Add initial admin and normal user 3 | -- 4 | 5 | INSERT INTO `miniphp_68dozftn`.`users` (`id`, `session_id`, `cookie_token`, `name`, `role`, `hashed_password`, `email`, `is_email_activated`, `email_token`, `email_last_verification`, `profile_picture`) VALUES ('1', NULL, NULL, 'Anna Collier', 'admin', '$2y$10$oomRp.tNyq2sG/3YE3jtMO3lyCzBwI3dxWxEsz956a7Cherfp7h4K', 'admin@demo.com', '1', NULL, NULL, 'default.png'), 6 | ('2', NULL, NULL, 'Evans Fuller', 'user', '$2y$10$CUiHm3w/waT3xlY0mJBm/uYNbOQnHcXYaZifekyUGxJhCxDdjWsV6', 'user@demo.com', '1', NULL, NULL, 'default.png'); 7 | -------------------------------------------------------------------------------- /app/.htaccess: -------------------------------------------------------------------------------- 1 | deny from all -------------------------------------------------------------------------------- /app/backups/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore -------------------------------------------------------------------------------- /app/config/config.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | 11 | return array( 12 | 13 | /** 14 | * Configuration for: Database Connection 15 | * Define database constants to establish a connection. 16 | * 17 | * It's important to set the charset for the database 18 | * 19 | * In the real world, you will have a database user with limited privileges(usually only CRUD). 20 | * 21 | */ 22 | "DB_HOST" =>"localhost", 23 | "DB_NAME" => "miniphp_68dozftn", 24 | "DB_USER" => "root", 25 | "DB_PASS" => "", 26 | 'DB_CHARSET' => 'utf8', 27 | 28 | /** 29 | * Configuration for: Paths 30 | * Paths from views directory 31 | */ 32 | "VIEWS_PATH" => APP . "views/", 33 | "ERRORS_PATH" => APP . "views/errors/", 34 | "LOGIN_PATH" => APP . "views/login/", 35 | "ADMIN_VIEWS_PATH" => APP . "views/admin/", 36 | 37 | /** 38 | * Configuration for: Cookies 39 | * 40 | * COOKIE_RUNTIME: How long should a cookie be valid by seconds. 41 | * - 1209600 means 2 weeks 42 | * - 604800 means 1 week 43 | * COOKIE_DOMAIN: The domain where the cookie is valid for. 44 | * COOKIE_DOMAIN mightn't work with "localhost", ".localhost", "127.0.0.1", or ".127.0.0.1". If so, leave it as empty string, false or null. 45 | * @see http://stackoverflow.com/questions/1134290/cookies-on-localhost-with-explicit-domain 46 | * @see http://php.net/manual/en/function.setcookie.php#73107 47 | * 48 | * COOKIE_PATH: The path where the cookie is valid for. If set to '/', the cookie will be available within the entire COOKIE_DOMAIN. 49 | * COOKIE_SECURE: If the cookie will be transferred through secured connection(SSL). It's highly recommended to set it to true if you have secured connection 50 | * COOKIE_HTTP: If set to true, Cookies that can't be accessed by JS - Highly recommended! 51 | * COOKIE_SECRET_KEY: A random value to make the cookie more secure. 52 | * 53 | */ 54 | "COOKIE_EXPIRY" => 1209600, 55 | "SESSION_COOKIE_EXPIRY" => 604800, 56 | "COOKIE_DOMAIN" => '', 57 | "COOKIE_PATH" => '/', 58 | "COOKIE_SECURE" => false, 59 | "COOKIE_HTTP" => true, 60 | "COOKIE_SECRET_KEY" => "af&70-GF^!a{f64r5@g38l]#kQ4B+43%", 61 | 62 | /** 63 | * Configuration for: Encryption Keys 64 | * 65 | */ 66 | "ENCRYPTION_KEY" => "3¥‹a0cd@!$251Êìcef08%&", 67 | "HMAC_SALT" => "a8C7n7^Ed0%8Qfd9K4m6d$86Dab", 68 | "HASH_KEY" => "z4D8Mp7Jm5cH", 69 | 70 | /** 71 | * Configuration for: Email server credentials 72 | * Emails are sent using SMTP, Don't use built-in mail() function in PHP. 73 | * 74 | */ 75 | "EMAIL_SMTP_DEBUG" => 2, 76 | "EMAIL_SMTP_AUTH" => true, 77 | "EMAIL_SMTP_SECURE" => "ssl", 78 | "EMAIL_SMTP_HOST" => "YOURSMTPHOST", 79 | "EMAIL_SMTP_PORT" => 465, 80 | "EMAIL_SMTP_USERNAME" => "YOURUSERNAME", 81 | "EMAIL_SMTP_PASSWORD" => "YOURPASSWORD", 82 | "EMAIL_FROM" => "info@YOURDOMAIN.com", 83 | "EMAIL_FROM_NAME" => "mini PHP", 84 | "EMAIL_REPLY_TO" => "no-reply@YOURDOMAIN.com", 85 | "ADMIN_EMAIL" => "YOUREMAIL", 86 | 87 | /** 88 | * Configuration for: Email Verification 89 | * 90 | * EMAIL_EMAIL_VERIFICATION_URL: Full URL must be provided 91 | * 92 | */ 93 | "EMAIL_EMAIL_VERIFICATION" => "1", 94 | "EMAIL_EMAIL_VERIFICATION_URL" => PUBLIC_ROOT . "Login/verifyUser", 95 | "EMAIL_EMAIL_VERIFICATION_SUBJECT" => "[IMP] Please verify your account", 96 | 97 | /** 98 | * Configuration for: Revoke email change 99 | * 100 | * EMAIL_REVOKE_EMAIL_URL: Full URL must be provided 101 | * 102 | */ 103 | "EMAIL_REVOKE_EMAIL" => "2", 104 | "EMAIL_REVOKE_EMAIL_URL" => PUBLIC_ROOT . "User/revokeEmail", 105 | "EMAIL_REVOKE_EMAIL_SUBJECT" => "[IMP] Your email has been changed", 106 | 107 | /** 108 | * Configuration for: Confirm pending(updated) email 109 | * 110 | * EMAIL_UPDATE_EMAIL_URL: Full URL must be provided 111 | * 112 | */ 113 | "EMAIL_UPDATE_EMAIL" => "3", 114 | "EMAIL_UPDATE_EMAIL_URL" => PUBLIC_ROOT . "User/updateEmail", 115 | "EMAIL_UPDATE_EMAIL_SUBJECT" => "[IMP] Please confirm your new email", 116 | 117 | /** 118 | * Configuration for: Reset Password 119 | * 120 | * EMAIL_PASSWORD_RESET_URL: Full URL must be provided 121 | * 122 | */ 123 | "EMAIL_PASSWORD_RESET" => "4", 124 | "EMAIL_PASSWORD_RESET_URL" => PUBLIC_ROOT . "Login/resetPassword", 125 | "EMAIL_PASSWORD_RESET_SUBJECT" => "[IMP] Reset your password", 126 | 127 | /** 128 | * Configuration for: Report Bug, Feature, or Enhancement 129 | */ 130 | "EMAIL_REPORT_BUG" => "5", 131 | "EMAIL_REPORT_BUG_SUBJECT" => "Request", 132 | 133 | /** 134 | * Configuration for: Hashing strength 135 | * 136 | * It defines the strength of the password hashing/salting. "10" is the default value by PHP. 137 | * @see http://php.net/manual/en/function.password-hash.php 138 | * 139 | */ 140 | "HASH_COST_FACTOR" => "10", 141 | 142 | /** 143 | * Configuration for: Pagination 144 | * 145 | */ 146 | "PAGINATION_DEFAULT_LIMIT" => 10 147 | 148 | ); 149 | -------------------------------------------------------------------------------- /app/config/javascript.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | 11 | return array( 12 | 13 | 'root' => PUBLIC_ROOT, /* public root used in ajax calls and redirection from client-side */ 14 | 'fileSizeOverflow' => 10485760 /* max file size, this is important to avoid overflow in files with big size */ 15 | ); 16 | -------------------------------------------------------------------------------- /app/controllers/AdminController.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | */ 10 | 11 | class AdminController extends Controller { 12 | 13 | /** 14 | * A method that will be triggered before calling action method. 15 | * Any changes here will reflect then on Controller::triggerComponents() method 16 | * 17 | */ 18 | public function beforeAction(){ 19 | 20 | parent::beforeAction(); 21 | 22 | $action = $this->request->param('action'); 23 | $actions = ['getUsers', 'updateUserInfo', 'deleteUser']; 24 | 25 | // define the action methods that needs to be triggered only through POST & Ajax request. 26 | $this->Security->requireAjax($actions); 27 | $this->Security->requirePost($actions); 28 | 29 | // You need to explicitly define the form fields that you expect to be returned in POST request, 30 | // if form field wasn't defined, this will detected as form tampering attempt. 31 | switch($action){ 32 | case "getUsers": 33 | $this->Security->config("form", [ 'fields' => ['name', 'email', 'role', 'page']]); 34 | break; 35 | case "updateUserInfo": 36 | $this->Security->config("form", [ 'fields' => ['user_id', 'name', 'password', 'role']]); 37 | break; 38 | case "deleteUser": 39 | $this->Security->config("form", [ 'fields' => ['user_id']]); 40 | break; 41 | case "updateBackup": 42 | case "restoreBackup": 43 | $this->Security->config("validateCsrfToken", true); 44 | break; 45 | } 46 | } 47 | 48 | /** 49 | * show all users 50 | * 51 | */ 52 | public function users(){ 53 | 54 | Config::setJsConfig('curPage', "users"); 55 | $this->view->renderWithLayouts(Config::get('VIEWS_PATH') . "layout/default/", Config::get('ADMIN_VIEWS_PATH') . 'users/index.php'); 56 | } 57 | 58 | /** 59 | * get users by name, email & role 60 | * 61 | */ 62 | public function getUsers(){ 63 | 64 | $name = $this->request->data("name"); 65 | $email = $this->request->data("email"); 66 | $role = $this->request->data("role"); 67 | $pageNum = $this->request->data("page"); 68 | 69 | $usersData = $this->admin->getUsers($name, $email, $role, $pageNum); 70 | 71 | if(!$usersData){ 72 | $this->view->renderErrors($this->admin->errors()); 73 | } else{ 74 | 75 | $usersHTML = $this->view->render(Config::get('ADMIN_VIEWS_PATH') . 'users/users.php', array("users" => $usersData["users"])); 76 | $paginationHTML = $this->view->render(Config::get('VIEWS_PATH') . 'pagination/default.php', array("pagination" => $usersData["pagination"])); 77 | $this->view->renderJson(array("data" => ["users" => $usersHTML, "pagination" => $paginationHTML])); 78 | } 79 | } 80 | 81 | /** 82 | * view a user 83 | * 84 | * @param integer|string $userId 85 | */ 86 | public function viewUser($userId = 0){ 87 | 88 | $userId = Encryption::decryptId($userId); 89 | 90 | if(!$this->user->exists($userId)){ 91 | return $this->error(404); 92 | } 93 | 94 | Config::setJsConfig('curPage', "users"); 95 | Config::setJsConfig('userId', Encryption::encryptId($userId)); 96 | 97 | $this->view->renderWithLayouts(Config::get('VIEWS_PATH') . "layout/default/", Config::get('ADMIN_VIEWS_PATH') . 'users/viewUser.php', array("userId" => $userId)); 98 | } 99 | 100 | /** 101 | * update user profile info(name, password, role) 102 | * 103 | */ 104 | public function updateUserInfo(){ 105 | 106 | $userId = Encryption::decryptId($this->request->data("user_id")); 107 | $name = $this->request->data("name"); 108 | $password = $this->request->data("password"); 109 | $role = $this->request->data("role"); 110 | 111 | if(!$this->user->exists($userId)){ 112 | return $this->error(404); 113 | } 114 | 115 | $result = $this->admin->updateUserInfo($userId, Session::getUserId(), $name, $password, $role); 116 | 117 | if(!$result){ 118 | $this->view->renderErrors($this->admin->errors()); 119 | }else{ 120 | $this->view->renderSuccess("Profile has been updated."); 121 | } 122 | } 123 | 124 | /** 125 | * delete a user 126 | * 127 | */ 128 | public function deleteUser(){ 129 | 130 | $userId = Encryption::decryptIdWithDash($this->request->data("user_id")); 131 | 132 | if(!$this->user->exists($userId)){ 133 | return $this->error(404); 134 | } 135 | 136 | $this->admin->deleteUser(Session::getUserId(), $userId); 137 | $this->view->renderJson(array("success" => true)); 138 | } 139 | 140 | /** 141 | * view backups if exist 142 | * 143 | */ 144 | public function backups(){ 145 | 146 | Config::setJsConfig('curPage', "backups"); 147 | $this->view->renderWithLayouts(Config::get('VIEWS_PATH') . "layout/default/", Config::get('ADMIN_VIEWS_PATH') . 'backups.php'); 148 | } 149 | 150 | /** 151 | * update backup 152 | * 153 | */ 154 | public function updateBackup(){ 155 | 156 | $this->admin->updateBackup(); 157 | 158 | Session::set('backup-success', "Backup has been updated"); 159 | return $this->redirector->root("Admin/Backups"); 160 | } 161 | 162 | /** 163 | * restore backup 164 | * 165 | */ 166 | public function restoreBackup(){ 167 | 168 | $result = $this->admin->restoreBackup(); 169 | 170 | if(!$result){ 171 | Session::set('backup-errors', $this->admin->errors()); 172 | return $this->redirector->root("Admin/Backups"); 173 | }else{ 174 | Session::set('backup-success', "Backup has been restored successfully"); 175 | return $this->redirector->root("Admin/Backups"); 176 | } 177 | } 178 | 179 | /** 180 | * Is user authorized for admin controller & requested action method? 181 | * 182 | * @return bool 183 | */ 184 | public function isAuthorized(){ 185 | 186 | $role = Session::getUserRole(); 187 | if(isset($role) && $role === "admin"){ 188 | return true; 189 | } 190 | return false; 191 | } 192 | 193 | } 194 | -------------------------------------------------------------------------------- /app/controllers/CommentsController.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | class CommentsController extends Controller{ 11 | 12 | public function beforeAction(){ 13 | 14 | parent::beforeAction(); 15 | 16 | $action = $this->request->param('action'); 17 | $actions = ['getAll', 'create', 'getUpdateForm', 'update', 'getById', 'delete']; 18 | $this->Security->requireAjax($actions); 19 | $this->Security->requirePost($actions); 20 | 21 | switch($action){ 22 | case "getAll": 23 | $this->Security->config("form", [ 'fields' => ['post_id', 'page', 'comments_created']]); 24 | break; 25 | case "create": 26 | $this->Security->config("form", [ 'fields' => ['post_id', 'content']]); 27 | break; 28 | case "getUpdateForm": 29 | $this->Security->config("form", [ 'fields' => ['comment_id']]); 30 | break; 31 | case "update": 32 | $this->Security->config("form", [ 'fields' => ['comment_id', 'content']]); 33 | break; 34 | case "getById": 35 | case "delete": 36 | $this->Security->config("form", [ 'fields' => ['comment_id']]); 37 | break; 38 | } 39 | } 40 | 41 | /** 42 | * get all comments 43 | * 44 | */ 45 | public function getAll(){ 46 | 47 | $postId = Encryption::decryptId($this->request->data("post_id")); 48 | 49 | $pageNum = $this->request->data("page"); 50 | $commentsCreated = (int)$this->request->data("comments_created"); 51 | 52 | $commentsData = $this->comment->getAll($postId, $pageNum, $commentsCreated); 53 | 54 | $commentsHTML = $this->view->render(Config::get('VIEWS_PATH') . 'posts/comments.php', array("comments" => $commentsData["comments"])); 55 | $paginationHTML = $this->view->render(Config::get('VIEWS_PATH') . 'pagination/comments.php', array("pagination" => $commentsData["pagination"])); 56 | 57 | $this->view->renderJson(array("data" => ["comments" => $commentsHTML, "pagination" => $paginationHTML])); 58 | } 59 | 60 | public function create(){ 61 | 62 | $postId = Encryption::decryptId($this->request->data("post_id")); 63 | 64 | $content = $this->request->data("content"); 65 | 66 | $comment = $this->comment->create(Session::getUserId(), $postId, $content); 67 | 68 | if(!$comment){ 69 | $this->view->renderErrors($this->comment->errors()); 70 | }else{ 71 | 72 | $html = $this->view->render(Config::get('VIEWS_PATH') . 'posts/comments.php', array("comments" => $comment)); 73 | $this->view->renderJson(array("data" => $html)); 74 | } 75 | } 76 | 77 | /** 78 | * whenever the user hits 'edit' button, 79 | * a request will be sent to get the update form of that comment, 80 | * so that the user can 'update' or even 'cancel' the edit request. 81 | * 82 | */ 83 | public function getUpdateForm(){ 84 | 85 | $commentId = Encryption::decryptIdWithDash($this->request->data("comment_id")); 86 | 87 | if(!$this->comment->exists($commentId)){ 88 | return $this->error(404); 89 | } 90 | 91 | $comment = $this->comment->getById($commentId); 92 | 93 | $commentsHTML = $this->view->render(Config::get('VIEWS_PATH') . 'posts/commentUpdateForm.php', array("comment" => $comment[0])); 94 | $this->view->renderJson(array("data" => $commentsHTML)); 95 | } 96 | 97 | /** 98 | * update comment 99 | * 100 | */ 101 | public function update(){ 102 | 103 | $commentId = Encryption::decryptIdWithDash($this->request->data("comment_id")); 104 | $content = $this->request->data("content"); 105 | 106 | if(!$this->comment->exists($commentId)){ 107 | return $this->error(404); 108 | } 109 | 110 | $comment = $this->comment->update($commentId, $content); 111 | 112 | if(!$comment){ 113 | $this->view->renderErrors($this->comment->errors()); 114 | }else{ 115 | 116 | $html = $this->view->render(Config::get('VIEWS_PATH') . 'posts/comments.php', array("comments" => $comment)); 117 | $this->view->renderJson(array("data" => $html)); 118 | } 119 | } 120 | 121 | /** 122 | * get comment by Id 123 | * 124 | */ 125 | public function getById(){ 126 | 127 | $commentId = Encryption::decryptIdWithDash($this->request->data("comment_id")); 128 | 129 | if(!$this->comment->exists($commentId)){ 130 | return $this->error(404); 131 | } 132 | 133 | $comment = $this->comment->getById($commentId); 134 | 135 | $commentsHTML = $this->view->render(Config::get('VIEWS_PATH') . 'posts/comments.php', array("comments" => $comment)); 136 | $this->view->renderJson(array("data" => $commentsHTML)); 137 | } 138 | 139 | public function delete(){ 140 | 141 | $commentId = Encryption::decryptIdWithDash($this->request->data("comment_id")); 142 | 143 | if(!$this->comment->exists($commentId)){ 144 | return $this->error(404); 145 | } 146 | 147 | $this->comment->deleteById($commentId); 148 | 149 | $this->view->renderJson(array("success" => true)); 150 | } 151 | 152 | public function isAuthorized(){ 153 | 154 | $action = $this->request->param('action'); 155 | $role = Session::getUserRole(); 156 | $resource = "comments"; 157 | 158 | // only for admins 159 | Permission::allow('admin', $resource, ['*']); 160 | 161 | // only for normal users 162 | Permission::allow('user', $resource, ['getAll', 'getById', 'create']); 163 | Permission::allow('user', $resource, ['update', 'delete', 'getUpdateForm'], 'owner'); 164 | 165 | $commentId = $this->request->data("comment_id"); 166 | if(!empty($commentId)){ 167 | $commentId = Encryption::decryptIdWithDash($commentId); 168 | } 169 | 170 | $config = [ 171 | "user_id" => Session::getUserId(), 172 | "table" => "comments", 173 | "id" => $commentId]; 174 | 175 | return Permission::check($role, $resource, $action, $config); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /app/controllers/DownloadsController.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | class DownloadsController extends Controller { 10 | 11 | 12 | public function beforeAction(){ 13 | 14 | parent::beforeAction(); 15 | 16 | $actions = ['download', 'users']; 17 | $this->Security->requireGet($actions); 18 | 19 | // if you want to add csrf_token in the URL of file download 20 | // So, it will be something like this: http://localhost/miniPHP/downloads/download/f850749b62bf3badfb6c0?csrf_token=21eb0f2c6b4fddce8a7f3 21 | // $this->Security->config("validateCsrfToken", true); 22 | } 23 | 24 | /** 25 | * download a file provided by it's hashed name 26 | * the url should be something like: http://localhost/miniPHP/downloads/download/f850749b62bf3ba57b6380b67c6f3096bcdfb6c0 27 | * 28 | * @param string $hashedFileName 29 | */ 30 | public function download($hashedFileName = ''){ 31 | 32 | $fullPath = APP . "uploads/" ; 33 | $file = $this->file->getByHashedName($hashedFileName); 34 | 35 | if(empty($file)){ 36 | return $this->error(404); 37 | } 38 | 39 | $fullPath .= $hashedFileName . "." . $file["extension"]; 40 | $file["basename"] = $file["filename"] . "." . $file["extension"]; 41 | 42 | if(!Uploader::isFileExists($fullPath)){ 43 | return $this->error(404); 44 | } 45 | 46 | $this->response->download($fullPath, ["basename" => $file["basename"], "extension" => $file["extension"]]); 47 | } 48 | 49 | /** 50 | * download users data as csv file 51 | * 52 | */ 53 | public function users(){ 54 | 55 | $data = $this->admin->getUsersData(); 56 | $this->response->csv(["cols" => $data["cols"], "rows" => $data["rows"]], ["filename" => $data["filename"]]); 57 | } 58 | 59 | public function isAuthorized(){ 60 | 61 | $action = $this->request->param('action'); 62 | $role = Session::getUserRole(); 63 | $resource = "downloads"; 64 | 65 | //only for admin 66 | Permission::allow('admin', $resource, "*"); 67 | 68 | //only for normal users 69 | Permission::allow('user', $resource, "download"); 70 | 71 | return Permission::check($role, $resource, $action); 72 | 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/controllers/ErrorsController.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | 15 | class ErrorsController extends Controller{ 16 | 17 | /** 18 | * Initialization method. 19 | * 20 | */ 21 | public function initialize(){ 22 | } 23 | 24 | public function NotFound(){ 25 | $this->view->renderWithLayouts(Config::get('VIEWS_PATH') . "layout/errors/", Config::get('ERRORS_PATH') . "404.php"); 26 | } 27 | 28 | public function Unauthenticated(){ 29 | $this->view->renderWithLayouts(Config::get('VIEWS_PATH') . "layout/errors/", Config::get('ERRORS_PATH') . "401.php"); 30 | } 31 | 32 | public function Unauthorized(){ 33 | $this->view->renderWithLayouts(Config::get('VIEWS_PATH') . "layout/errors/", Config::get('ERRORS_PATH') . "403.php"); 34 | } 35 | 36 | public function BadRequest(){ 37 | $this->view->renderWithLayouts(Config::get('VIEWS_PATH') . "layout/errors/", Config::get('ERRORS_PATH') . "400.php"); 38 | } 39 | 40 | public function System(){ 41 | $this->view->renderWithLayouts(Config::get('VIEWS_PATH') . "layout/errors/", Config::get('ERRORS_PATH') . "500.php"); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/controllers/FilesController.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | class FilesController extends Controller { 11 | 12 | public function beforeAction(){ 13 | 14 | parent::beforeAction(); 15 | 16 | Config::setJsConfig('curPage', "files"); 17 | 18 | $action = $this->request->param('action'); 19 | $actions = ['create', 'delete']; 20 | $this->Security->requireAjax($actions); 21 | $this->Security->requirePost($actions); 22 | 23 | switch($action){ 24 | case "create": 25 | $this->Security->config("form", [ 'fields' => ['file']]); 26 | break; 27 | case "delete": 28 | $this->Security->config("form", [ 'fields' => ['file_id']]); 29 | break; 30 | } 31 | } 32 | 33 | public function index(){ 34 | 35 | // clear all notifications whenever you hit 'files' in the navigation bar 36 | $this->user->clearNotifications(Session::getUserId(), $this->file->table); 37 | 38 | $pageNum = $this->request->query("page"); 39 | 40 | $this->view->renderWithLayouts(Config::get('VIEWS_PATH') . "layout/default/", Config::get('VIEWS_PATH') . 'files/index.php', ['pageNum' => $pageNum]); 41 | } 42 | 43 | public function create(){ 44 | 45 | $fileData = $this->request->data("file"); 46 | 47 | $file = $this->file->create(Session::getUserId(), $fileData); 48 | 49 | if(!$file){ 50 | $this->view->renderErrors($this->file->errors()); 51 | }else{ 52 | 53 | $fileHTML = $this->view->render(Config::get('VIEWS_PATH') . 'files/files.php', array("files" => $file)); 54 | $this->view->renderJson(array("data" => $fileHTML)); 55 | } 56 | } 57 | 58 | public function delete(){ 59 | 60 | $fileId = Encryption::decryptIdWithDash($this->request->data("file_id")); 61 | 62 | if(!$this->file->exists($fileId)){ 63 | return $this->error(404); 64 | } 65 | 66 | $this->file->deleteById($fileId); 67 | 68 | $this->view->renderJson(array("success" => true)); 69 | } 70 | 71 | public function isAuthorized(){ 72 | 73 | $action = $this->request->param('action'); 74 | $role = Session::getUserRole(); 75 | $resource = "files"; 76 | 77 | // only for admins 78 | Permission::allow('admin', $resource, ['*']); 79 | 80 | // only for normal users 81 | Permission::allow('user', $resource, ['index', 'create']); 82 | Permission::allow('user', $resource, ['delete'], 'owner'); 83 | 84 | $fileId = $this->request->data("file_id"); 85 | if(!empty($fileId)){ 86 | $fileId = Encryption::decryptIdWithDash($fileId); 87 | } 88 | 89 | $config = [ 90 | "user_id" => Session::getUserId(), 91 | "table" => "files", 92 | "id" => $fileId 93 | ]; 94 | 95 | return Permission::check($role, $resource, $action, $config); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /app/controllers/NewsFeedController.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | class NewsFeedController extends Controller{ 11 | 12 | public function beforeAction(){ 13 | 14 | parent::beforeAction(); 15 | 16 | Config::setJsConfig('curPage', "newsfeed"); 17 | 18 | $action = $this->request->param('action'); 19 | $actions = ['create', 'getUpdateForm', 'update', 'getById', 'delete']; 20 | $this->Security->requirePost($actions); 21 | 22 | switch($action){ 23 | case "create": 24 | $this->Security->config("form", [ 'fields' => ['content']]); 25 | break; 26 | case "getUpdateForm": 27 | $this->Security->config("form", [ 'fields' => ['newsfeed_id']]); 28 | break; 29 | case "update": 30 | $this->Security->config("form", [ 'fields' => ['newsfeed_id', 'content']]); 31 | break; 32 | case "getById": 33 | case "delete": 34 | $this->Security->config("form", [ 'fields' => ['newsfeed_id']]); 35 | break; 36 | } 37 | } 38 | 39 | public function index(){ 40 | 41 | $this->user->clearNotifications(Session::getUserId(), $this->newsfeed->table); 42 | 43 | $pageNum = $this->request->query("page"); 44 | 45 | $this->view->renderWithLayouts(Config::get('VIEWS_PATH') . "layout/default/", Config::get('VIEWS_PATH') . 'newsfeed/index.php', ['pageNum' => $pageNum]); 46 | } 47 | 48 | public function create(){ 49 | 50 | $content = $this->request->data("content"); 51 | 52 | $newsfeed = $this->newsfeed->create(Session::getUserId(), $content); 53 | 54 | if(!$newsfeed){ 55 | 56 | Session::set('newsfeed-errors', $this->newsfeed->errors()); 57 | return $this->redirector->root("NewsFeed"); 58 | 59 | }else{ 60 | 61 | return $this->redirector->root("NewsFeed"); 62 | } 63 | } 64 | 65 | public function getUpdateForm(){ 66 | 67 | $newsfeedId = Encryption::decryptIdWithDash($this->request->data("newsfeed_id")); 68 | 69 | if(!$this->newsfeed->exists($newsfeedId)){ 70 | return $this->error(404); 71 | } 72 | 73 | $newsfeed = $this->newsfeed->getById($newsfeedId); 74 | 75 | $html = $this->view->render(Config::get('VIEWS_PATH') . 'newsfeed/updateForm.php', array("newsfeed" => $newsfeed[0])); 76 | $this->view->renderJson(array("data" => $html)); 77 | } 78 | 79 | public function update(){ 80 | 81 | // Remember? each news feed has an id that looks like this: feed-51b2cfa 82 | $newsfeedId = Encryption::decryptIdWithDash($this->request->data("newsfeed_id")); 83 | $content = $this->request->data("content"); 84 | 85 | if(!$this->newsfeed->exists($newsfeedId)){ 86 | return $this->error(404); 87 | } 88 | 89 | $newsfeed = $this->newsfeed->update($newsfeedId, $content); 90 | if(!$newsfeed){ 91 | $this->view->renderErrors($this->newsfeed->errors()); 92 | }else{ 93 | 94 | $html = $this->view->render(Config::get('VIEWS_PATH') . 'newsfeed/newsfeed.php', array("newsfeed" => $newsfeed)); 95 | $this->view->renderJson(array("data" => $html)); 96 | } 97 | } 98 | 99 | public function getById(){ 100 | 101 | $newsfeedId = Encryption::decryptIdWithDash($this->request->data("newsfeed_id")); 102 | 103 | if(!$this->newsfeed->exists($newsfeedId)){ 104 | return $this->error(404); 105 | } 106 | 107 | $newsfeed = $this->newsfeed->getById($newsfeedId); 108 | 109 | $html = $this->view->render(Config::get('VIEWS_PATH') . 'newsfeed/newsfeed.php', array("newsfeed" => $newsfeed)); 110 | $this->view->renderJson(array("data" => $html)); 111 | } 112 | 113 | public function delete(){ 114 | 115 | $newsfeedId = Encryption::decryptIdWithDash($this->request->data("newsfeed_id")); 116 | 117 | $this->newsfeed->deleteById($newsfeedId); 118 | $this->view->renderJson(array("success" => true)); 119 | } 120 | 121 | public function isAuthorized(){ 122 | 123 | $action = $this->request->param('action'); 124 | $role = Session::getUserRole(); 125 | $resource = "newsfeed"; 126 | 127 | // only for admins 128 | Permission::allow('admin', $resource, ['*']); 129 | 130 | // only for normal users 131 | Permission::allow('user', $resource, ['index', 'getById', 'create']); 132 | Permission::allow('user', $resource, ['update', 'delete', 'getUpdateForm'], 'owner'); 133 | 134 | $newsfeedId = $this->request->data("newsfeed_id"); 135 | if(!empty($newsfeedId)){ 136 | $newsfeedId = Encryption::decryptIdWithDash($newsfeedId); 137 | } 138 | 139 | $config = [ 140 | "user_id" => Session::getUserId(), 141 | "table" => "newsfeed", 142 | "id" => $newsfeedId]; 143 | 144 | return Permission::check($role, $resource, $action, $config); 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /app/controllers/PostsController.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | class PostsController extends Controller{ 11 | 12 | 13 | public function beforeAction(){ 14 | 15 | parent::beforeAction(); 16 | 17 | Config::setJsConfig('curPage', "posts"); 18 | 19 | $action = $this->request->param('action'); 20 | $actions = ['create', 'update']; 21 | $this->Security->requirePost($actions); 22 | 23 | switch($action){ 24 | case "create": 25 | $this->Security->config("form", [ 'fields' => ['title', 'content']]); 26 | break; 27 | case "update": 28 | $this->Security->config("form", [ 'fields' => ['post_id', 'title', 'content']]); 29 | break; 30 | case "delete": 31 | $this->Security->config("validateCsrfToken", true); 32 | $this->Security->config("form", [ 'fields' => ['post_id']]); 33 | break; 34 | } 35 | } 36 | 37 | /** 38 | * show posts page 39 | * 40 | */ 41 | public function index(){ 42 | 43 | // clear all notifications 44 | $this->user->clearNotifications(Session::getUserId(), $this->post->table); 45 | 46 | $pageNum = $this->request->query("page"); 47 | 48 | $this->view->renderWithLayouts(Config::get('VIEWS_PATH') . "layout/default/", Config::get('VIEWS_PATH') . 'posts/index.php', ['pageNum' => $pageNum]); 49 | } 50 | 51 | /** 52 | * view a post 53 | * 54 | * @param integer|string $postId 55 | */ 56 | public function view($postId = 0){ 57 | 58 | $postId = Encryption::decryptId($postId); 59 | 60 | if(!$this->post->exists($postId)){ 61 | return $this->error(404); 62 | } 63 | 64 | Config::setJsConfig('curPage', ["posts", "comments"]); 65 | Config::setJsConfig('postId', Encryption::encryptId($postId)); 66 | 67 | $action = $this->request->query('action'); 68 | $this->view->renderWithLayouts(Config::get('VIEWS_PATH') . "layout/default/", Config::get('VIEWS_PATH') . 'posts/viewPost.php', ["action"=> $action, "postId" => $postId]); 69 | } 70 | 71 | /** 72 | * show new post form 73 | */ 74 | public function newPost(){ 75 | $this->view->renderWithLayouts(Config::get('VIEWS_PATH') . "layout/default/", Config::get('VIEWS_PATH') . 'posts/newPost.php'); 76 | } 77 | 78 | /** 79 | * creates a new post 80 | * 81 | */ 82 | public function create(){ 83 | 84 | $title = $this->request->data("title"); 85 | $content = $this->request->data("content"); 86 | 87 | $result = $this->post->create(Session::getUserId(), $title, $content); 88 | 89 | if(!$result){ 90 | Session::set('posts-errors', $this->post->errors()); 91 | }else{ 92 | Session::set('posts-success', "Post has been created"); 93 | } 94 | 95 | return $this->redirector->root("Posts/newPost"); 96 | } 97 | 98 | /** 99 | * update a post 100 | * 101 | */ 102 | public function update(){ 103 | 104 | $postId = $this->request->data("post_id"); 105 | $title = $this->request->data("title"); 106 | $content = $this->request->data("content"); 107 | 108 | $postId = Encryption::decryptId($postId); 109 | 110 | if(!$this->post->exists($postId)){ 111 | return $this->error(404); 112 | } 113 | 114 | $post = $this->post->update($postId, $title, $content); 115 | 116 | if(!$post){ 117 | 118 | Session::set('posts-errors', $this->post->errors()); 119 | return $this->redirector->root("Posts/View/" . urlencode(Encryption::encryptId($postId)) . "?action=update"); 120 | 121 | }else{ 122 | return $this->redirector->root("Posts/View/" . urlencode(Encryption::encryptId($postId))); 123 | } 124 | } 125 | 126 | public function delete($postId = 0){ 127 | 128 | $postId = Encryption::decryptId($postId); 129 | 130 | if(!$this->post->exists($postId)){ 131 | return $this->error(404); 132 | } 133 | 134 | $this->post->deleteById($postId); 135 | 136 | return $this->redirector->root("Posts"); 137 | } 138 | 139 | public function isAuthorized(){ 140 | 141 | $action = $this->request->param('action'); 142 | $role = Session::getUserRole(); 143 | $resource = "posts"; 144 | 145 | // only for admins 146 | Permission::allow('admin', $resource, ['*']); 147 | 148 | // only for normal users 149 | Permission::allow('user', $resource, ['index', 'view', 'newPost', 'create']); 150 | Permission::allow('user', $resource, ['update', 'delete'], 'owner'); 151 | 152 | $postId = ($action === "delete")? $this->request->param("args")[0]: $this->request->data("post_id"); 153 | if(!empty($postId)){ 154 | $postId = Encryption::decryptId($postId); 155 | } 156 | 157 | $config = [ 158 | "user_id" => Session::getUserId(), 159 | "table" => "posts", 160 | "id" => $postId 161 | ]; 162 | 163 | return Permission::check($role, $resource, $action, $config); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /app/controllers/TodoController.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | class TodoController extends Controller{ 11 | 12 | // override this method to perform any logic before calling action method as explained above 13 | public function beforeAction(){ 14 | 15 | parent::beforeAction(); 16 | 17 | // define the actions in this Controller 18 | $action = $this->request->param('action'); 19 | 20 | // restrict the request to action methods 21 | // $this->Security->requireAjax(['create', 'delete']); 22 | $this->Security->requirePost(['create', 'delete']); 23 | 24 | // define the expected form fields for every action if exist 25 | switch($action){ 26 | case "create": 27 | // you can exclude form fields if you don't care if they were sent with form fields or not 28 | $this->Security->config("form", [ 'fields' => ['content']]); 29 | break; 30 | case "delete": 31 | // If you want to disable validation for form tampering 32 | // $this->Security->config("validateForm", false); 33 | $this->Security->config("form", [ 'fields' => ['todo_id']]); 34 | break; 35 | } 36 | } 37 | 38 | public function index(){ 39 | 40 | // display todo list 41 | $this->view->renderWithLayouts(Config::get('VIEWS_PATH') . "layout/todo/", Config::get('VIEWS_PATH') . 'todo/index.php'); 42 | } 43 | 44 | public function create(){ 45 | 46 | $content = $this->request->data("content"); 47 | $todo = $this->todo->create(Session::getUserId(), $content); 48 | 49 | if(!$todo){ 50 | 51 | // in case of normal post request 52 | Session::set('errors', $this->todo->errors()); 53 | return $this->redirector->root("Todo"); 54 | 55 | // in case of ajax 56 | // $this->view->renderErrors($this->todo->errors()); 57 | 58 | }else{ 59 | 60 | // in case of normal post request 61 | Session::set('success', "Todo has been created"); 62 | return $this->redirector->root("Todo"); 63 | 64 | // in case of ajax 65 | // $this->view->renderJson(array("success" => "Todo has been created")); 66 | } 67 | } 68 | 69 | public function delete(){ 70 | 71 | $todoId = Encryption::decryptIdWithDash($this->request->data("todo_id")); 72 | $this->todo->delete($todoId); 73 | 74 | // in case of normal post request 75 | Session::set('success', "Todo has been deleted"); 76 | return $this->redirector->root("Todo"); 77 | 78 | // in case of ajax 79 | // $this->view->renderJson(array("success" => "Todo has been deleted")); 80 | } 81 | 82 | public function isAuthorized(){ 83 | 84 | $action = $this->request->param('action'); 85 | $role = Session::getUserRole(); 86 | $resource = "todo"; 87 | 88 | // only for admins 89 | Permission::allow('admin', $resource, ['*']); 90 | 91 | // only for normal users 92 | Permission::allow('user', $resource, ['delete'], 'owner'); 93 | 94 | $todoId = $this->request->data("todo_id"); 95 | 96 | if(!empty($todoId)){ 97 | $todoId = Encryption::decryptIdWithDash($todoId); 98 | } 99 | 100 | $config = [ 101 | "user_id" => Session::getUserId(), 102 | "table" => "todo", 103 | "id" => $todoId]; 104 | 105 | return Permission::check($role, $resource, $action, $config); 106 | } 107 | } -------------------------------------------------------------------------------- /app/controllers/UserController.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | class UserController extends Controller{ 11 | 12 | public function beforeAction(){ 13 | 14 | parent::beforeAction(); 15 | 16 | $action = $this->request->param('action'); 17 | $actions = ['updateProfileInfo', 'updateProfilePicture', 'reportBug']; 18 | $this->Security->requirePost($actions); 19 | 20 | switch($action){ 21 | case "updateProfileInfo": 22 | $this->Security->config("form", [ 'fields' => ['name', 'password', 'email', 'confirm_email']]); 23 | break; 24 | case "updateProfilePicture": 25 | $this->Security->config("form", [ 'fields' => ['file']]); 26 | break; 27 | case "reportBug": 28 | $this->Security->config("form", [ 'fields' => ['subject', 'label', 'message']]); 29 | break; 30 | } 31 | } 32 | 33 | /** 34 | * show dashboard page 35 | * 36 | */ 37 | public function index(){ 38 | 39 | Config::setJsConfig('curPage', "dashboard"); 40 | $this->view->renderWithLayouts(Config::get('VIEWS_PATH') . "layout/default/", Config::get('VIEWS_PATH') . 'dashboard/index.php'); 41 | } 42 | 43 | public function profile(){ 44 | 45 | Config::setJsConfig('curPage', "profile"); 46 | $this->view->renderWithLayouts(Config::get('VIEWS_PATH') . "layout/default/", Config::get('VIEWS_PATH') . 'user/profile.php'); 47 | } 48 | 49 | public function updateProfileInfo(){ 50 | 51 | $name = $this->request->data("name"); 52 | $password = $this->request->data("password"); 53 | $email = $this->request->data("email"); 54 | $confirmEmail = $this->request->data("confirm_email"); 55 | 56 | $result = $this->user->updateProfileInfo(Session::getUserId(), $name, $password, $email, $confirmEmail); 57 | 58 | if(!$result){ 59 | 60 | Session::set('profile-info-errors', $this->user->errors()); 61 | 62 | }else{ 63 | 64 | $message = "Your Profile has been updated. "; 65 | $message .= $result["emailUpdated"]? "Please check your new email to confirm the changes, or your current email to revoke the changes": ""; 66 | 67 | Session::set('profile-info-success', $message); 68 | } 69 | 70 | return $this->redirector->root("User/Profile"); 71 | } 72 | 73 | public function updateProfilePicture(){ 74 | 75 | $fileData = $this->request->data("file"); 76 | $image = $this->user->updateProfilePicture(Session::getUserId(), $fileData); 77 | 78 | if(!$image){ 79 | Session::set('profile-picture-errors', $this->user->errors()); 80 | } 81 | 82 | return $this->redirector->root("User/Profile"); 83 | } 84 | 85 | /** 86 | * revoke email updates 87 | * 88 | * You must be logged in with your current email 89 | */ 90 | public function revokeEmail(){ 91 | 92 | $userId = $this->request->query("id"); 93 | $userId = empty($userId)? null: Encryption::decryptId($this->request->query("id")); 94 | $token = $this->request->query("token"); 95 | 96 | $result = $this->user->revokeEmail($userId, $token); 97 | 98 | if(!$result){ 99 | return $this->error(404); 100 | }else{ 101 | $this->view->renderWithLayouts(Config::get('VIEWS_PATH') . "layout/default/", Config::get('VIEWS_PATH') . 'user/profile.php', ["emailUpdates" => ["success" => "Your email updates has been revoked successfully."]]); 102 | } 103 | } 104 | 105 | /** 106 | * confirm on email updates 107 | * 108 | * You must be logged in with your current email 109 | */ 110 | public function updateEmail(){ 111 | 112 | $userId = $this->request->query("id"); 113 | $userId = empty($userId)? null: Encryption::decryptId($this->request->query("id")); 114 | $token = $this->request->query("token"); 115 | 116 | $result = $this->user->updateEmail($userId, $token); 117 | $errors = $this->user->errors(); 118 | 119 | if(!$result && empty($errors)){ 120 | return $this->error(404); 121 | }else if(!$result && !empty($errors)){ 122 | $this->view->renderWithLayouts(Config::get('VIEWS_PATH') . "layout/default/", Config::get('VIEWS_PATH') . 'user/profile.php', ["emailUpdates" => ["errors" => $this->user->errors()]]); 123 | }else{ 124 | $this->view->renderWithLayouts(Config::get('VIEWS_PATH') . "layout/default/", Config::get('VIEWS_PATH') . 'user/profile.php', 125 | ["emailUpdates" => ["success" => "Your email updates has been updated successfully."]]); 126 | } 127 | } 128 | 129 | /** 130 | * users can report bugs, features, or enhancement 131 | * - Bug is an error you encountered 132 | * - Feature is a new functionality you suggest to add 133 | * - Enhancement is an existing feature, but you want to improve 134 | * 135 | */ 136 | public function bugs(){ 137 | Config::setJsConfig('curPage', "bugs"); 138 | $this->view->renderWithLayouts(Config::get('VIEWS_PATH') . "layout/default/", Config::get('VIEWS_PATH') . 'bugs/index.php'); 139 | } 140 | 141 | /** 142 | * send email to admin for reporting any bugs, features, or enhancement 143 | * 144 | */ 145 | public function reportBug(){ 146 | 147 | $subject = $this->request->data("subject"); 148 | $label = $this->request->data("label"); 149 | $message = $this->request->data("message"); 150 | 151 | $result = $this->user->reportBug(Session::getUserId(), $subject, $label, $message); 152 | 153 | if(!$result){ 154 | Session::set('report-bug-errors', $this->user->errors()); 155 | }else{ 156 | Session::set('report-bug-success', "Email has been sent successfully, We will consider your report."); 157 | } 158 | 159 | return $this->redirector->root("User/Bugs"); 160 | } 161 | 162 | public function isAuthorized(){ 163 | return true; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /app/core/Component.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class Component{ 14 | 15 | /** 16 | * controller 17 | * 18 | * @var Controller 19 | */ 20 | protected $controller; 21 | 22 | /** 23 | * request 24 | * 25 | * @var Request 26 | */ 27 | protected $request; 28 | 29 | /** 30 | * Default configurations data 31 | * 32 | * @var array 33 | */ 34 | protected $config = []; 35 | 36 | 37 | /** 38 | * Constructor 39 | * 40 | * @param Controller $controller 41 | * @param array $config user-provided config 42 | */ 43 | public function __construct(Controller $controller, array $config = []){ 44 | $this->controller = $controller; 45 | $this->request = $controller->request; 46 | $this->config = array_merge($this->config, $config); 47 | } 48 | 49 | /** 50 | * set and get configurations data 51 | * 52 | * @param string $key 53 | * @param mixed $value 54 | * @return mixed 55 | */ 56 | public function config($key, $value = null){ 57 | 58 | // set 59 | if($value !== null){ 60 | $this->config = array_merge($this->config, [$key => $value]); 61 | return $this; 62 | } 63 | 64 | // get 65 | return array_key_exists($key, $this->config)? $this->config[$key]: null; 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /app/core/Config.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | 11 | class Config{ 12 | 13 | /** 14 | * Array of configurations 15 | * 16 | * @var array 17 | */ 18 | private static $config = []; 19 | 20 | /** 21 | * Prefixes used to load specific configurations. 22 | * 23 | * @var array 24 | */ 25 | private static $prefix = [ 26 | 'default' => 'config', 27 | 'js' => 'javascript' 28 | ]; 29 | 30 | /** 31 | * Get default configuration value(s) 32 | * 33 | * @param $key string 34 | * @return string|array|null 35 | */ 36 | public static function get($key){ 37 | return self::_get($key, self::$prefix['default']); 38 | } 39 | 40 | /** 41 | * Set or add a default configuration value 42 | * 43 | * @param $key string 44 | */ 45 | public static function set($key, $value){ 46 | self::_set($key, $value, self::$prefix['default']); 47 | } 48 | 49 | /** 50 | * Get javascript configuration value(s) 51 | * 52 | * @param $key string 53 | * @return string|array|null 54 | */ 55 | public static function getJsConfig($key = ""){ 56 | return self::_get($key, self::$prefix['js']); 57 | } 58 | 59 | /** 60 | * Set or add a javascript configuration value 61 | * 62 | * @param string $key 63 | * @param mixed $value 64 | */ 65 | public static function setJsConfig($key, $value){ 66 | self::_set($key, $value, self::$prefix['js']); 67 | } 68 | 69 | /** 70 | * Get a configuration value(s) 71 | * 72 | * @param $key string 73 | * @param $source string 74 | * @return string|null 75 | * @throws Exception if configuration file doesn't exist 76 | */ 77 | private static function _get($key, $source){ 78 | 79 | if (!isset(self::$config[$source])) { 80 | 81 | $config_file = APP . 'config/' . $source . '.php'; 82 | 83 | if (!file_exists($config_file)) { 84 | throw new Exception("Configuration file " . $source . " doesn't exist"); 85 | } 86 | 87 | self::$config[$source] = require $config_file . ""; 88 | } 89 | 90 | if(empty($key)){ 91 | return self::$config[$source]; 92 | } else if(isset(self::$config[$source][$key])){ 93 | return self::$config[$source][$key]; 94 | } 95 | 96 | return null; 97 | } 98 | 99 | /** 100 | * Set or adds a configuration value 101 | * 102 | * @param $key string 103 | * @param $value string 104 | * @param $source string 105 | */ 106 | private static function _set($key, $value, $source){ 107 | 108 | // load configurations if not already loaded 109 | if (!isset(self::$config[$source])) { 110 | self::_get($key, $source); 111 | } 112 | 113 | if($key && $source){ 114 | self::$config[$source][$key] = $value; 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /app/core/Cookie.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | class Cookie{ 11 | 12 | /** 13 | * @access public 14 | * @var string User ID 15 | */ 16 | private static $userId = null; 17 | 18 | /** 19 | * @access public 20 | * @var string Cookie Token 21 | */ 22 | private static $token = null; 23 | 24 | /** 25 | * @access public 26 | * @var string Hashed Token = hash(User ID . ":" . Token . Cookie Secret) 27 | */ 28 | private static $hashedCookie = null; 29 | 30 | /** 31 | * This is the constructor for Cookie object. 32 | * 33 | * @access private 34 | */ 35 | private function __construct() {} 36 | 37 | /** 38 | * Getters for $userId 39 | * 40 | * @access public 41 | * @static static method 42 | * @return string User ID 43 | */ 44 | public static function getUserId(){ 45 | return (int)self::$userId; 46 | } 47 | 48 | /** 49 | * Extract and validate cookie 50 | * 51 | * @access public 52 | * @static static method 53 | * @return bool 54 | */ 55 | public static function isCookieValid(){ 56 | 57 | // "auth" or "remember me" cookie 58 | if(empty($_COOKIE['auth'])) { 59 | return false; 60 | } 61 | 62 | // check the count before using explode 63 | $cookie_auth = explode(':', $_COOKIE['auth']); 64 | if(count ($cookie_auth) !== 3){ 65 | self::remove(); 66 | return false; 67 | } 68 | 69 | list ($encryptedUserId, self::$token, self::$hashedCookie) = $cookie_auth; 70 | 71 | // Remember? $hashedCookie was generated from the original user Id, NOT from the encrypted one. 72 | self::$userId = Encryption::decrypt($encryptedUserId); 73 | 74 | if (self::$hashedCookie === hash('sha256', self::$userId . ':' . self::$token . Config::get('COOKIE_SECRET_KEY')) && !empty(self::$token) && !empty(self::$userId)) { 75 | 76 | $database = Database::openConnection(); 77 | $query = "SELECT id, cookie_token FROM users WHERE id = :id AND cookie_token = :cookie_token LIMIT 1"; 78 | $database->prepare($query); 79 | $database->bindValue(':id', self::$userId); 80 | $database->bindValue(':cookie_token', self::$token); 81 | $database->execute(); 82 | 83 | $isValid = $database->countRows() === 1? true: false; 84 | 85 | }else{ 86 | 87 | $isValid = false; 88 | } 89 | 90 | if(!$isValid){ 91 | 92 | Logger::log("COOKIE", self::$userId . " is trying to login using invalid cookie: " . self::$token, __FILE__, __LINE__); 93 | self::remove(self::$userId); 94 | } 95 | 96 | return $isValid; 97 | } 98 | 99 | /** 100 | * Remove cookie from the database of a user(if exists), 101 | * and also from the browser. 102 | * 103 | * @static static method 104 | * @param string $userId 105 | * 106 | */ 107 | public static function remove($userId = null){ 108 | 109 | if(!empty($userId)){ 110 | 111 | $database = Database::openConnection(); 112 | $query = "UPDATE users SET cookie_token = NULL WHERE id = :id"; 113 | $database->prepare($query); 114 | $database->bindValue(":id", $userId); 115 | $result = $database->execute(); 116 | 117 | if(!$result) { 118 | Logger::log("COOKIE", "Couldn't remove cookie from the database for user ID: " . $userId, __FILE__, __LINE__); 119 | } 120 | } 121 | 122 | self::$userId = self::$token = self::$hashedCookie = null; 123 | 124 | // How to kill/delete a cookie in a browser? 125 | setcookie('auth', false, time() - (3600 * 3650), Config::get('COOKIE_PATH'), Config::get('COOKIE_DOMAIN'), Config::get('COOKIE_SECURE'), Config::get('COOKIE_HTTP')); 126 | } 127 | 128 | /** 129 | * Reset Cookie, 130 | * resetting is done by updating the database, 131 | * and resetting the "auth" cookie in the browser 132 | * 133 | * @static static method 134 | * @param string $userId 135 | */ 136 | public static function reset($userId){ 137 | 138 | self::$userId = $userId; 139 | self::$token = hash('sha256', mt_rand()); 140 | $database = Database::openConnection(); 141 | 142 | $query = "UPDATE users SET cookie_token = :cookie_token WHERE id = :id"; 143 | $database->prepare($query); 144 | 145 | // generate random hash for cookie token (64 char string) 146 | $database->bindValue(":cookie_token", self::$token); 147 | $database->bindValue(":id", self::$userId); 148 | $result = $database->execute(); 149 | 150 | if(!$result) { 151 | Logger::log("COOKIE", "Couldn't remove cookie from the database for user ID: " . $userId, __FILE__, __LINE__); 152 | } 153 | 154 | // generate cookie string(remember me) 155 | // Don't expose the original user id in the cookie, Encrypt It! 156 | $cookieFirstPart = Encryption::encrypt(self::$userId) . ':' . self::$token ; 157 | 158 | // $hashedCookie generated from the original user Id, NOT from the encrypted one. 159 | self::$hashedCookie = hash('sha256', self::$userId . ':' . self::$token . Config::get('COOKIE_SECRET_KEY')); 160 | $authCookie = $cookieFirstPart . ':' . self::$hashedCookie; 161 | 162 | setcookie('auth', $authCookie, time() + Config::get('COOKIE_EXPIRY'), Config::get('COOKIE_PATH'), Config::get('COOKIE_DOMAIN'), Config::get('COOKIE_SECURE'), Config::get('COOKIE_HTTP')); 163 | } 164 | 165 | } 166 | -------------------------------------------------------------------------------- /app/core/Environment.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | 11 | class Environment{ 12 | 13 | /** 14 | * Constructor 15 | * 16 | */ 17 | private function __construct(){} 18 | 19 | /** 20 | * Gets an environment variable from $_SERVER, $_ENV, or using getenv() 21 | * 22 | * @param $key string 23 | * @return string|null 24 | */ 25 | public static function get($key){ 26 | 27 | $val = null; 28 | if (isset($_SERVER[$key])) { 29 | $val = $_SERVER[$key]; 30 | } elseif (isset($_ENV[$key])) { 31 | $val = $_ENV[$key]; 32 | } elseif (getenv($key) !== false) { 33 | $val = getenv($key); 34 | } 35 | 36 | return $val; 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /app/core/Handler.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | 13 | class Handler{ 14 | 15 | /** 16 | * Constructor 17 | * 18 | */ 19 | private function __construct(){} 20 | 21 | /** 22 | * Register the error and exception handlers. 23 | * Must be called at the beginning of your application 24 | * 25 | * @return void 26 | */ 27 | public static function register(){ 28 | 29 | // turn off all error reporting as well, 30 | // because we will take care of it 31 | error_reporting(0); 32 | 33 | set_error_handler(__CLASS__ . "::handleError"); 34 | set_exception_handler(__CLASS__ .'::handleException'); 35 | register_shutdown_function(__CLASS__ ."::handleFatalError" ); 36 | } 37 | 38 | /** 39 | * Handle fatal errors 40 | * 41 | * @return void 42 | */ 43 | public static function handleFatalError(){ 44 | 45 | if (PHP_SAPI === 'cli') { return; } 46 | $error = error_get_last(); 47 | 48 | if (!is_array($error)) { return; } 49 | 50 | $fatals = [E_USER_ERROR, E_ERROR, E_PARSE]; 51 | 52 | if (!in_array($error['type'], $fatals, true)) { 53 | return; 54 | } 55 | 56 | // self::handleError($error['type'], $error['message'], $error['file'], $error['line'], null); 57 | self::handleException(new ErrorException($error['message'], 0, $error['type'], $error['file'], $error['line'])); 58 | } 59 | 60 | /** 61 | * Handle errors 62 | * 63 | * @return void 64 | * @throws ErrorException 65 | */ 66 | public static function handleError($errno, $errmsg, $filename, $linenum, $vars){ 67 | throw new ErrorException($errmsg, 0, $errno, $filename, $linenum); 68 | } 69 | 70 | /** 71 | * Handle & log exceptions 72 | * 73 | * @param Throwable $e 74 | * @return void 75 | * @see http://php.net/manual/en/function.set-exception-handler.php 76 | */ 77 | public static function handleException($e) { 78 | Logger::Log(get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()); 79 | self::render($e)->send(); 80 | } 81 | 82 | /** 83 | * display system error page as result of an error or exception 84 | * 85 | * @param Throwable $e 86 | * @return Response 87 | */ 88 | private static function render($e){ 89 | 90 | if($e->getCode() === 400){ 91 | return (new ErrorsController())->error(400); 92 | } 93 | 94 | return (new ErrorsController())->error(500); 95 | } 96 | 97 | /** 98 | * Map an error code to error text 99 | * 100 | * @param int $errno 101 | * @return string error text 102 | */ 103 | private static function errorType($errno){ 104 | 105 | // define an assoc array of error string 106 | $errortype = array ( 107 | E_ERROR => 'Error', 108 | E_WARNING => 'Warning', 109 | E_PARSE => 'Parsing Error', 110 | E_NOTICE => 'Notice', 111 | E_CORE_ERROR => 'Core Error', 112 | E_CORE_WARNING => 'Core Warning', 113 | E_COMPILE_ERROR => 'Compile Error', 114 | E_COMPILE_WARNING => 'Compile Warning', 115 | E_USER_ERROR => 'User Error', 116 | E_USER_WARNING => 'User Warning', 117 | E_USER_NOTICE => 'User Notice', 118 | E_STRICT => 'Runtime Notice', 119 | E_RECOVERABLE_ERROR => 'Catchable Fatal Error' 120 | ); 121 | 122 | return $errortype[$errno]; 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /app/core/Logger.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | 12 | class Logger{ 13 | 14 | /** 15 | * Constructor 16 | * 17 | */ 18 | private function __construct(){} 19 | 20 | /** 21 | * log 22 | * 23 | * @access public 24 | * @static static method 25 | * @param string $header 26 | * @param string $message 27 | * @param string $filename 28 | * @param string $linenum 29 | */ 30 | public static function log($header="", $message="", $filename="", $linenum=""){ 31 | 32 | $logfile = APP . "logs/log.txt"; 33 | $date = date("d/m/Y G:i:s"); 34 | $err = $date." | ".$filename." | ".$linenum." | ".$header. "\n"; 35 | 36 | $message = is_array($message)? implode("\n", $message): $message; 37 | $err .= $message . "\n*******************************************************************\n\n"; 38 | 39 | // log/write error to log file 40 | error_log($err, 3, $logfile); 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /app/core/Redirector.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | 12 | class Redirector{ 13 | 14 | /** 15 | * Constructor 16 | * 17 | */ 18 | public function __construct(){ 19 | } 20 | 21 | /** 22 | * Redirect to the given location 23 | * 24 | * @param string $location 25 | */ 26 | public function to($location, $query = ""){ 27 | 28 | if(!empty($query)){ 29 | $query = '?' . http_build_query((array)$query, null, '&'); 30 | } 31 | 32 | $response = new Response('', 302, ["Location" => $location . $query]); 33 | return $response; 34 | } 35 | 36 | /** 37 | * Redirect to the given location from the root 38 | * 39 | * @param string $location 40 | */ 41 | public function root($location = "", $query = ""){ 42 | return $this->to(PUBLIC_ROOT . $location, $query); 43 | } 44 | 45 | /** 46 | * Redirect to the dashboard 47 | */ 48 | public function dashboard(){ 49 | return $this->to(PUBLIC_ROOT . "User"); 50 | } 51 | 52 | /** 53 | * Redirect to the login page 54 | * $redirect_url is to send the user back to where he/she came from after login 55 | * 56 | * @param string|null $redirect_url 57 | */ 58 | public function login($redirect_url = null){ 59 | if(!empty($redirect_url)){ 60 | return $this->to(PUBLIC_ROOT . "?redirect=" . urlencode($redirect_url)); 61 | }else{ 62 | return $this->to(PUBLIC_ROOT); 63 | } 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /app/core/components/AuthComponent.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | 12 | class AuthComponent extends Component{ 13 | 14 | /** 15 | * Default configurations 16 | * 17 | * @var array 18 | */ 19 | protected $config = [ 20 | 'authenticate' => [], 21 | 'authorize' => [] 22 | ]; 23 | 24 | /** 25 | * Auth startup 26 | * All authentication and authorization checking are done in this method 27 | * 28 | */ 29 | public function startup(){ 30 | 31 | // authenticate 32 | if(!empty($this->config["authenticate"])){ 33 | if(!$this->authenticate()){ 34 | return $this->unauthenticated(); 35 | } 36 | } 37 | 38 | // authorize 39 | if(!empty($this->config["authorize"])){ 40 | if(!$this->authorize()){ 41 | return $this->unauthorized(); 42 | } 43 | } 44 | } 45 | 46 | /** 47 | * Handles unauthenticated access attempt. 48 | * 49 | */ 50 | public function unauthenticated(){ 51 | 52 | $this->controller->login->logOut(Session::getUserId()); 53 | 54 | if($this->request->isAjax()) { 55 | return $this->controller->error(401); 56 | }else{ 57 | $redirect = $this->controller->request->isGet()? $this->controller->request->uri(): ""; 58 | return $this->controller->redirector->login($redirect); 59 | } 60 | } 61 | 62 | /** 63 | * Handles unauthorized access attempt. 64 | * 65 | */ 66 | public function unauthorized(){ 67 | return $this->controller->error(403); 68 | } 69 | 70 | /** 71 | * authenticate the user using the defined methods in $config 72 | * 73 | * @return boolean 74 | */ 75 | public function authenticate(){ 76 | return $this->check($this->config["authenticate"], "authenticate"); 77 | } 78 | 79 | /** 80 | * authorize the user using the defined methods in $config 81 | * 82 | * @return boolean 83 | */ 84 | public function authorize(){ 85 | return $this->check($this->config["authorize"], "authorize"); 86 | } 87 | 88 | /** 89 | * check for authentication or authorization 90 | * 91 | * @param array $config 92 | * @param string $type 93 | * @throws Exception if $config is empty or method doesn't exists 94 | * @return boolean 95 | */ 96 | private function check($config, $type){ 97 | 98 | if (empty($config)) { 99 | throw new Exception($type . ' methods arent initialized yet in config'); 100 | } 101 | 102 | $auth = Utility::normalize($config); 103 | 104 | foreach($auth as $method => $config){ 105 | 106 | $method = "_" . ucfirst($method) . ucfirst($type); 107 | 108 | if (!method_exists(__CLASS__, $method)) { 109 | throw new Exception('Auth Method doesnt exists: ' . $method); 110 | } 111 | 112 | if($this->{$method}($config) === false){ 113 | return false; 114 | } 115 | } 116 | return true; 117 | } 118 | 119 | /** 120 | * Is user is already logged in via session or cookie? 121 | * 122 | * @return boolean 123 | */ 124 | public function isLoggedIn(){ 125 | 126 | if(Session::getIsLoggedIn() === true){ 127 | return true; 128 | } 129 | 130 | if(Cookie::isCookieValid()){ 131 | return true; 132 | } 133 | 134 | return false; 135 | } 136 | 137 | /** 138 | * Is user authorized for the requested Controller & Action method? 139 | * 140 | * @param array $config configuration data 141 | * @throws Exception if isAuthorized method doesn't exists in the controller class 142 | * @return boolean 143 | */ 144 | private function _ControllerAuthorize($config){ 145 | 146 | if (!method_exists($this->controller, 'isAuthorized')) { 147 | throw new Exception(sprintf('%s does not implement an isAuthorized() method.', get_class($this->controller))); 148 | } 149 | return (bool)$this->controller->isAuthorized(); 150 | } 151 | 152 | /** 153 | * Is user authenticated? 154 | * It checks for: 155 | * - concurrent session 156 | * - user credentials in session & cookies 157 | * - cookies theft and manipulations 158 | * - session Hijacking and fixation. 159 | * 160 | * @param array $config configuration data 161 | * @return boolean 162 | */ 163 | private function _UserAuthenticate($config){ 164 | 165 | if($this->concurentSession()){ 166 | return false; 167 | } 168 | 169 | if(!$this->loggedIn()){ 170 | return false; 171 | } 172 | 173 | return true; 174 | } 175 | 176 | /** 177 | * Checks if user is logged in or not. 178 | * It uses Session and Cookies to validate the current user. 179 | * 180 | * @access public 181 | * @static static method 182 | * @return boolean 183 | * 184 | */ 185 | private function loggedIn(){ 186 | 187 | if (Session::isSessionValid($this->request->clientIp(), $this->request->userAgent())) { 188 | return true; 189 | } 190 | 191 | if (Cookie::isCookieValid()) { 192 | 193 | // get role from user class, because cookies don't store roles 194 | $role = $this->controller->user->getProfileInfo(Cookie::getUserId())["role"]; 195 | Session::reset(["user_id" => Cookie::getUserId(), "role" => $role, "ip" => $this->request->clientIp(), "user_agent" => $this->request->userAgent()]); 196 | 197 | // reset cookie, Cookie token is usable only once 198 | Cookie::reset(Session::getUserId()); 199 | 200 | return true; 201 | } 202 | 203 | return false; 204 | } 205 | 206 | private function concurentSession(){ 207 | return Session::isConcurrentSessionExists(); 208 | } 209 | 210 | } 211 | -------------------------------------------------------------------------------- /app/logs/log.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmarElgabry/miniPHP/7ddf0c9db29b936418c1992e3d94cdf4918a1e93/app/logs/log.txt -------------------------------------------------------------------------------- /app/models/Comment.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | 9 | class Comment extends Model{ 10 | 11 | /** 12 | * get all comments of a post 13 | * 14 | * @access public 15 | * @param array $postId 16 | * @param integer $pageNum 17 | * @param integer $commentsCreated 18 | * @return array Associative array of the comments, and Pagination Object(View More). 19 | * 20 | */ 21 | public function getAll($postId, $pageNum = 1, $commentsCreated = 0){ 22 | 23 | // Only for comments, We use $commentsCreated 24 | // What's it? Whenever we create a comment, It will be added in-place to the current comments in current .php page, 25 | // So, we need to track of those were created, and skip them in the Pagination($offset & $totalCount). 26 | 27 | $options = "WHERE comments.post_id = :post_id "; 28 | $pagination = Pagination::pagination("comments", $options, [":post_id" => $postId], $pageNum, $commentsCreated); 29 | $offset = $pagination->getOffset() + $commentsCreated; 30 | $limit = $pagination->perPage; 31 | 32 | $database = Database::openConnection(); 33 | $query = "SELECT comments.id AS id, users.profile_picture, users.id AS user_id, users.name AS user_name, comments.content, comments.date "; 34 | $query .= "FROM users, posts, comments "; 35 | $query .= "WHERE comments.post_id = :post_id "; 36 | $query .= "AND posts.id = comments.post_id "; 37 | $query .= "AND users.id = comments.user_id "; 38 | $query .= "ORDER BY comments.date DESC "; 39 | $query .= "LIMIT $limit OFFSET $offset"; 40 | 41 | $database->prepare($query); 42 | $database->bindValue(':post_id', (int)$postId); 43 | $database->execute(); 44 | $comments = $database->fetchAllAssociative(); 45 | 46 | // you can have post with no comments yet! 47 | return array("comments" => $comments, "pagination" => $pagination); 48 | } 49 | 50 | /** 51 | * get comment by Id 52 | * 53 | * @access public 54 | * @param string $commentId 55 | * @return array Array holds the data of the comment 56 | * 57 | */ 58 | public function getById($commentId){ 59 | 60 | $database = Database::openConnection(); 61 | $query = "SELECT comments.id AS id, users.profile_picture, users.id AS user_id, users.name AS user_name, comments.content, comments.date "; 62 | $query .= "FROM users, posts, comments "; 63 | $query .= "WHERE comments.id = :id "; 64 | $query .= "AND posts.id = comments.post_id "; 65 | $query .= "AND users.id = comments.user_id LIMIT 1"; 66 | 67 | $database->prepare($query); 68 | $database->bindValue(':id', (int)$commentId); 69 | $database->execute(); 70 | 71 | $comment = $database->fetchAllAssociative(); 72 | return $comment; 73 | } 74 | 75 | /** 76 | * create Comment. 77 | * 78 | * @access public 79 | * @param string $userId 80 | * @param string $postId 81 | * @param string $content 82 | * @return array Array holds the created comment 83 | * @throws Exception If comment couldn't be created 84 | * 85 | */ 86 | public function create($userId, $postId, $content){ 87 | 88 | $validation = new Validation(); 89 | if(!$validation->validate([ 'Content' => [$content, 'required|minLen(1)|maxLen(300)']])) { 90 | $this->errors = $validation->errors(); 91 | return false; 92 | } 93 | 94 | $database = Database::openConnection(); 95 | $query = "INSERT INTO comments (user_id, post_id, content) VALUES (:user_id, :post_id, :content)"; 96 | 97 | $database->prepare($query); 98 | $database->bindValue(':user_id', $userId); 99 | $database->bindValue(':post_id', $postId); 100 | $database->bindValue(':content', $content); 101 | $database->execute(); 102 | 103 | if($database->countRows() !== 1){ 104 | throw new Exception ("Couldn't add comment"); 105 | } 106 | 107 | $commentId = $database->lastInsertedId(); 108 | $comment = $this->getById($commentId); 109 | return $comment; 110 | } 111 | 112 | /** 113 | * update Comment 114 | * 115 | * @access public 116 | * @param string $commentId 117 | * @param string $content 118 | * @return array Array holds the updated comment 119 | * @throws Exception If comment couldn't be updated 120 | * 121 | */ 122 | public function update($commentId, $content){ 123 | 124 | $validation = new Validation(); 125 | if(!$validation->validate([ 'Content' => [$content, 'required|minLen(1)|maxLen(300)']])) { 126 | $this->errors = $validation->errors(); 127 | return false; 128 | } 129 | 130 | $database = Database::openConnection(); 131 | $query = "UPDATE comments SET content = :content WHERE id = :id LIMIT 1 "; 132 | $database->prepare($query); 133 | $database->bindValue(':content', $content); 134 | $database->bindValue(':id', $commentId); 135 | $result = $database->execute(); 136 | 137 | if(!$result){ 138 | throw new Exception("Couldn't update comment of ID: " . $commentId); 139 | } 140 | 141 | $comment = $this->getById($commentId); 142 | return $comment; 143 | } 144 | 145 | /** 146 | * counting the number of comments of a post. 147 | * 148 | * @access public 149 | * @static static method 150 | * @param string $postId 151 | * @return integer number of comments 152 | * 153 | */ 154 | public static function countComments($postId){ 155 | 156 | $database = Database::openConnection(); 157 | $database->prepare("SELECT COUNT(*) AS count FROM comments WHERE post_id = :post_id"); 158 | $database->bindValue(":post_id", $postId); 159 | $database->execute(); 160 | 161 | return (int)$database->fetchAssociative()["count"]; 162 | } 163 | 164 | } -------------------------------------------------------------------------------- /app/models/File.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | class File extends Model{ 11 | 12 | /** 13 | * get all files. 14 | * 15 | * @access public 16 | * @param integer $pageNum 17 | * @return array 18 | * 19 | */ 20 | public function getAll($pageNum = 1){ 21 | 22 | // get pagination object 23 | $pagination = Pagination::pagination("files", "", [], $pageNum); 24 | $offset = $pagination->getOffset(); 25 | $limit = $pagination->perPage; 26 | 27 | $database = Database::openConnection(); 28 | $query = "SELECT files.id AS id, files.filename, users.id AS user_id, users.name AS user_name, files.extension AS format, files.hashed_filename, files.date "; 29 | $query .= "FROM users, files "; 30 | $query .= "WHERE users.id = files.user_id "; 31 | $query .= "ORDER BY files.date DESC "; 32 | $query .= "LIMIT $limit OFFSET $offset"; 33 | 34 | $database->prepare($query); 35 | $database->execute(); 36 | $files = $database->fetchAllAssociative(); 37 | 38 | return array("files" => $files, "pagination" => $pagination); 39 | } 40 | 41 | /** 42 | * get file by Id. 43 | * 44 | * @access public 45 | * @param string $fileId 46 | * @return array Array holds the data of the file 47 | */ 48 | public function getById($fileId){ 49 | 50 | $database = Database::openConnection(); 51 | $query = "SELECT files.id AS id, files.filename, users.id AS user_id, users.name AS user_name, files.extension AS format, files.hashed_filename, files.date "; 52 | $query .= "FROM users, files "; 53 | $query .= "WHERE files.id = :id "; 54 | $query .= "AND users.id = files.user_id LIMIT 1 "; 55 | 56 | $database->prepare($query); 57 | $database->bindValue(':id', (int)$fileId); 58 | $database->execute(); 59 | 60 | $file = $database->fetchAllAssociative(); 61 | return $file; 62 | } 63 | 64 | /** 65 | * get file by hashed name. 66 | * files are unique by the hashed file name(= hash(original filename . extension)). 67 | * 68 | * @access public 69 | * @param string $hashedFileName 70 | * @return array Array holds the data of the file 71 | */ 72 | public function getByHashedName($hashedFileName){ 73 | 74 | $database = Database::openConnection(); 75 | 76 | $query = "SELECT files.id AS id, files.filename, files.extension, files.hashed_filename "; 77 | $query .= "FROM files "; 78 | $query .= "WHERE hashed_filename = :hashed_filename "; 79 | $query .= "LIMIT 1 "; 80 | 81 | $database->prepare($query); 82 | $database->bindValue(':hashed_filename', $hashedFileName); 83 | $database->execute(); 84 | 85 | $file = $database->fetchAssociative(); 86 | return $file; 87 | } 88 | 89 | /** 90 | * create file. 91 | * 92 | * @access public 93 | * @param integer $userId 94 | * @param array $fileData 95 | * @return array Array holds the created file 96 | * @throws Exception If file couldn't be created 97 | */ 98 | public function create($userId, $fileData){ 99 | 100 | // upload 101 | $file = Uploader::uploadFile($fileData); 102 | 103 | if(!$file) { 104 | $this->errors = Uploader::errors(); 105 | return false; 106 | } 107 | 108 | $database = Database::openConnection(); 109 | 110 | $query = "INSERT INTO files (user_id, filename, hashed_filename, extension) VALUES (:user_id, :filename, :hashed_filename, :extension)"; 111 | 112 | $database->prepare($query); 113 | $database->bindValue(':user_id', $userId); 114 | $database->bindValue(':filename', $file["filename"]); 115 | $database->bindValue(':hashed_filename', $file["hashed_filename"]); 116 | $database->bindValue(':extension', strtolower($file["extension"])); 117 | $database->execute(); 118 | 119 | // if insert failed, then delete the file 120 | if($database->countRows() !== 1){ 121 | Uploader::deleteFile(APP ."uploads/" . $file["basename"]); 122 | throw new Exception ("Couldn't upload file"); 123 | } 124 | 125 | $fileId = $database->lastInsertedId(); 126 | $file = $this->getById($fileId); 127 | return $file; 128 | } 129 | 130 | /** 131 | * deletes file. 132 | * This method overrides the deleteById() method in Model class. 133 | * 134 | * @access public 135 | * @param array $id 136 | * @throws Exception If failed to delete the file 137 | * 138 | */ 139 | public function deleteById($id){ 140 | 141 | $database = Database::openConnection(); 142 | 143 | $database->getById("files", $id); 144 | $file = $database->fetchAssociative(); 145 | 146 | // start a transaction to guarantee the file will be deleted from both; database and filesystem 147 | $database->beginTransaction(); 148 | $database->deleteById("files", $id); 149 | 150 | if($database->countRows() !== 1){ 151 | $database->rollBack(); 152 | throw new Exception ("Couldn't delete file"); 153 | } 154 | 155 | $basename = $file["hashed_filename"] . "." . $file["extension"]; 156 | Uploader::deleteFile(APP ."uploads/" . $basename); 157 | 158 | $database->commit(); 159 | } 160 | 161 | } -------------------------------------------------------------------------------- /app/models/Model.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | 12 | class Model { 13 | 14 | /** 15 | * Table name for the Model. 16 | * 17 | * @var string 18 | */ 19 | public $table = false; 20 | 21 | /** 22 | * Array of validation errors 23 | * 24 | * @var array 25 | */ 26 | protected $errors = []; 27 | 28 | /** 29 | * Constructor 30 | * 31 | */ 32 | public function __construct(){ 33 | if($this->table === false){ 34 | $this->table = $this->pluralize(get_class($this)); 35 | } 36 | } 37 | 38 | /** 39 | * pluralize for table names 40 | * 41 | * Automatically selects a database table name based on a pluralized lowercase object class name 42 | * (i.e. class 'User' => table 'users') 43 | * 44 | * @param string $word 45 | * @return string 46 | */ 47 | private function pluralize($word){ 48 | 49 | $word = strtolower($word); 50 | $plural = [ 51 | "newsfeed" => "newsfeed", 52 | "man" => "men", 53 | "woman" => "women" 54 | ]; 55 | 56 | return isset($plural[$word])? $plural[$word]: $word . "s"; 57 | } 58 | 59 | /** 60 | * delete record by id 61 | * 62 | * @param string $id 63 | * @return bool 64 | * @throws Exception if feed couldn't be deleted 65 | */ 66 | public function deleteById($id){ 67 | 68 | $database = Database::openConnection(); 69 | $database->deleteById($this->table, $id); 70 | 71 | if($database->countRows() !== 1){ 72 | throw new Exception ("Couldn't delete news feed"); 73 | } 74 | } 75 | 76 | /** 77 | * get errors 78 | * 79 | * @return array errors 80 | */ 81 | public function errors(){ 82 | return $this->errors; 83 | } 84 | 85 | /** 86 | * is record exists? 87 | * 88 | * Almost all methods in model needs you to pass the current user id, 89 | * Another approach is to create in Model class a property call id(will be inherited by all extending classes) 90 | * Ex: 91 | * Inside postsController: 92 | * post->id = $postId 93 | * post->updatePost(....) -> Inside updatePost() you can get the post id by: $this->id 94 | * 95 | * @param string $id 96 | * @return bool 97 | */ 98 | public function exists($id){ 99 | 100 | $database = Database::openConnection(); 101 | $database->getById($this->table, $id); 102 | 103 | return $database->countRows() === 1; 104 | } 105 | 106 | /** 107 | * Counting the number of a current model's table. 108 | * 109 | * @return integer 110 | */ 111 | public function countAll(){ 112 | 113 | $database = Database::openConnection(); 114 | return $database->countAll($this->table); 115 | } 116 | 117 | /** 118 | * adds parts to current query using an array. 119 | * 120 | * The array will have $key which is the field value, and the $value is the query to be added to the current query 121 | * Only fields with existing value(false, and 0 are accepted as valid values) will be considered in our query. 122 | * 123 | * @param array $options 124 | * @param string $implodeBy 125 | * @return string 126 | */ 127 | public function applyOptions(array $options, $implodeBy){ 128 | 129 | $queries = []; 130 | 131 | foreach($options as $key => $value){ 132 | if(!empty($key) || $key === false || $key === 0){ 133 | $queries[] = $value; 134 | } 135 | } 136 | return implode($implodeBy, $queries); 137 | } 138 | 139 | } 140 | -------------------------------------------------------------------------------- /app/models/NewsFeed.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | class NewsFeed extends Model{ 10 | 11 | /** 12 | * get all news feed. 13 | * 14 | * @access public 15 | * @param integer $pageNum 16 | * @return array 17 | * 18 | */ 19 | public function getAll($pageNum = 1){ 20 | 21 | $pagination = Pagination::pagination("newsfeed", "", [], $pageNum); 22 | $offset = $pagination->getOffset(); 23 | $limit = $pagination->perPage; 24 | 25 | $database = Database::openConnection(); 26 | $query = "SELECT newsfeed.id AS id, users.profile_picture, users.id AS user_id, users.name AS user_name, newsfeed.content, newsfeed.date "; 27 | $query .= "FROM users, newsfeed "; 28 | $query .= "WHERE users.id = newsfeed.user_id "; 29 | $query .= "ORDER BY newsfeed.date DESC "; 30 | $query .= "LIMIT $limit OFFSET $offset"; 31 | 32 | $database->prepare($query); 33 | $database->execute(); 34 | $newsfeed = $database->fetchAllAssociative(); 35 | 36 | return array("newsfeed" => $newsfeed, "pagination" => $pagination); 37 | } 38 | 39 | /** 40 | * get news feed by Id. 41 | * 42 | * @param string $newsfeedId 43 | * @return array 44 | */ 45 | public function getById($newsfeedId){ 46 | 47 | $database = Database::openConnection(); 48 | $query = "SELECT newsfeed.id AS id, users.profile_picture, users.id AS user_id, users.name AS user_name, newsfeed.content, newsfeed.date "; 49 | $query .= "FROM users, newsfeed "; 50 | $query .= "WHERE newsfeed.id = :id "; 51 | $query .= "AND users.id = newsfeed.user_id LIMIT 1 "; 52 | 53 | $database->prepare($query); 54 | $database->bindValue(':id', (int)$newsfeedId); 55 | $database->execute(); 56 | 57 | $feed = $database->fetchAllAssociative(); 58 | return $feed; 59 | } 60 | 61 | /** 62 | * create news feed. 63 | * 64 | * @param integer $userId 65 | * @param string $content 66 | * @return array feed created 67 | * @throws Exception if feed couldn't be created 68 | */ 69 | public function create($userId, $content){ 70 | 71 | $validation = new Validation(); 72 | if(!$validation->validate(['Content' => [$content, "required|minLen(4)|maxLen(300)"]])) { 73 | $this->errors = $validation->errors(); 74 | return false; 75 | } 76 | 77 | $database = Database::openConnection(); 78 | $query = "INSERT INTO newsfeed (user_id, content) VALUES (:user_id, :content)"; 79 | 80 | $database->prepare($query); 81 | $database->bindValue(':user_id', $userId); 82 | $database->bindValue(':content', $content); 83 | $database->execute(); 84 | 85 | if($database->countRows() !== 1){ 86 | throw new Exception("Couldn't add news feed"); 87 | } 88 | 89 | $newsfeedId = $database->lastInsertedId(); 90 | $feed = $this->getById($newsfeedId); 91 | return $feed; 92 | } 93 | 94 | /** 95 | * update news feed. 96 | * 97 | * @param string $newsfeedId 98 | * @param string $content 99 | * @return array feed created 100 | * @throws Exception if feed couldn't be updated 101 | */ 102 | public function update($newsfeedId, $content){ 103 | 104 | $validation = new Validation(); 105 | if(!$validation->validate(['Content' => [$content, "required|minLen(4)|maxLen(300)"]])) { 106 | $this->errors = $validation->errors(); 107 | return false; 108 | } 109 | 110 | $database = Database::openConnection(); 111 | $query = "UPDATE newsfeed SET content = :content WHERE id = :id LIMIT 1"; 112 | 113 | $database->prepare($query); 114 | $database->bindValue(':content', $content); 115 | $database->bindValue(':id', $newsfeedId); 116 | $result = $database->execute(); 117 | 118 | if(!$result){ 119 | throw new Exception("Couldn't update newsfeed of ID: " . $newsfeedId); 120 | } 121 | 122 | $feed = $this->getById($newsfeedId); 123 | return $feed; 124 | } 125 | 126 | } -------------------------------------------------------------------------------- /app/models/Pagination.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | class Pagination { 11 | 12 | /** 13 | * @access public 14 | * @var integer Current Page 15 | */ 16 | public $currentPage; 17 | 18 | /** 19 | * @access public 20 | * @var integer Number of items(newsfeed, posts, ..etc.) to be displayed per page 21 | */ 22 | public $perPage; 23 | 24 | /** 25 | * @access public 26 | * @var integer Total count of items(newsfeed, posts, ..etc.) 27 | */ 28 | public $totalCount; 29 | 30 | /** 31 | * This is the constructor for Pagination Object. 32 | * 33 | * @access public 34 | * @param integer $currentPage 35 | * @param integer $totalCount 36 | * @param integer $perPage Number of items per page 37 | */ 38 | public function __construct($currentPage = 1, $totalCount = 0, $perPage = 0){ 39 | $this->currentPage = (empty($currentPage))? 1: (int)$currentPage; 40 | $this->totalCount = (empty($totalCount))? 0: (int)$totalCount; 41 | $this->perPage = (empty($perPage))? Config::get('PAGINATION_DEFAULT_LIMIT'): (int)$perPage; 42 | } 43 | 44 | /** 45 | * get pagination object by executing COUNT(*) query. 46 | * 47 | * @access public 48 | * @param string $table 49 | * @param string $options 50 | * @param array $values array of data 51 | * @param integer $pageNum 52 | * @param integer $extraOffset check comment class 53 | * @return Pagination 54 | */ 55 | public static function pagination($table, $options, $values, $pageNum, $extraOffset = 0){ 56 | 57 | $database = Database::openConnection(); 58 | $query = "SELECT COUNT(*) AS count FROM {$table} "; 59 | $query .= $options; 60 | 61 | $database->prepare($query); 62 | $database->execute($values); 63 | $totalCount = $database->fetchAssociative()["count"]; 64 | $extraOffset = ((int)$extraOffset > $totalCount)? 0: (int)$extraOffset; 65 | 66 | return new Pagination((int)$pageNum, $totalCount - $extraOffset); 67 | } 68 | 69 | /** 70 | * Get the offset. 71 | * 72 | * @access public 73 | * @return integer 74 | */ 75 | public function getOffset() { 76 | return ($this->currentPage - 1) * $this->perPage; 77 | } 78 | 79 | /** 80 | * Get number of total pages. 81 | * 82 | * @access public 83 | * @return integer 84 | */ 85 | public function totalPages() { 86 | return ceil($this->totalCount/$this->perPage); 87 | } 88 | 89 | /** 90 | * Get the number of the previous page. 91 | * 92 | * @access public 93 | * @return integer Number of previous page 94 | */ 95 | public function previousPage() { 96 | return $this->currentPage - 1; 97 | } 98 | 99 | /** 100 | * Get the number of the next page. 101 | * 102 | * @access public 103 | * @return integer Number of next page 104 | */ 105 | public function nextPage() { 106 | return $this->currentPage + 1; 107 | } 108 | 109 | /** 110 | * checks if there is a previous page or not 111 | * 112 | * @access public 113 | * @return boolean 114 | */ 115 | public function hasPreviousPage() { 116 | return $this->previousPage() >= 1 ? true : false; 117 | } 118 | 119 | /** 120 | * checks if there is a next page or not 121 | * 122 | * @access public 123 | * @return boolean 124 | */ 125 | public function hasNextPage() { 126 | return $this->totalPages() >= $this->nextPage()? true : false; 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /app/models/Permission.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | class Permission { 17 | 18 | /** 19 | * allowed permissions for actions on specific resources 20 | * 21 | * $perms[] = [ 22 | * 'role' => 'student', //AROs 23 | * 'resource' => 'Post' //ACOs - actions could be ACOs instead. 24 | * 'actions' => ['edit', 'delete'], 25 | * 'conditions' => ['owner'] - things that you validate against if the user has access to the action 26 | * ]; 27 | * 28 | * @var array 29 | */ 30 | public static $perms = []; 31 | 32 | /** 33 | * check if the $role has access to $action on $resource 34 | * 35 | * @param string $role 36 | * @param string $resource 37 | * @param string $action if set to "*", then check if $actions parameter was assigned to "*" when using allow() method 38 | * This indicates the $role has access to all actions on $resource 39 | * @param array $config configuration data to be passed to condition methods 40 | * @throws Exception if $config is empty or method doesn't exists 41 | * @return boolean 42 | */ 43 | public static function check($role, $resource, $action = "*", array $config = []){ 44 | 45 | // checks if action was allowed at least once 46 | $allowed = false; 47 | $action = strtolower($action); 48 | 49 | foreach(self::$perms as $perm){ 50 | if($perm['role'] === $role && $perm['resource'] === $resource){ 51 | 52 | if(in_array($action, $perm["actions"], true) || $perm["actions"] === ["*"]){ 53 | 54 | $allowed = true; 55 | 56 | foreach($perm["conditions"] as $condition){ 57 | 58 | if (!method_exists(__CLASS__, $condition)) { 59 | throw new Exception("Permission, Method doesnt exists: " . $condition); 60 | } 61 | 62 | if(self::$condition($config) === false){ 63 | Logger::log("Permission", $role . " is not allowed to perform '" . $action . "' action on " . $resource . " because of " . $condition, __FILE__, __LINE__); 64 | return false; 65 | } 66 | } 67 | } 68 | } 69 | } 70 | 71 | if(!$allowed){ 72 | Logger::log("Permission", $role . " is not allowed to perform '" . $action . "' action on " . $resource, __FILE__, __LINE__); 73 | } 74 | 75 | return $allowed; 76 | } 77 | 78 | /** 79 | * Add new rule: allow a $role for $actions on $resource, 80 | * You may add additional $conditions that must be fulfilled as well. 81 | * 82 | * @param string $role 83 | * @param string $resource 84 | * @param mixed $actions 85 | * @param mixed $conditions 86 | */ 87 | public static function allow($role, $resource, $actions = "*", $conditions = []){ 88 | 89 | $actions = array_map("strtolower", (array)$actions); 90 | 91 | self::$perms[] = ['role' => $role, 'resource' => $resource, 'actions' => $actions, 'conditions' => (array)$conditions]; 92 | } 93 | 94 | /** 95 | * deny or remove $actions for a $role on $resource 96 | * 97 | * @param string $role 98 | * @param string $resource 99 | * @param mixed $actions 100 | */ 101 | public static function deny($role, $resource, $actions = "*"){ 102 | 103 | $actions = array_map("strtolower", (array)$actions); 104 | 105 | foreach(self::$perms as $key => &$perm){ 106 | if($perm['role'] === $role && $perm['resource'] === $resource){ 107 | foreach($perm['actions'] as $index => $action){ 108 | if(in_array($action, $actions, true) || $actions === ["*"]){ 109 | unset($perm['actions'][$index]); 110 | } 111 | } 112 | 113 | if(empty($perm['actions'])){ 114 | unset(self::$perms[$key]); 115 | } 116 | } 117 | } 118 | } 119 | 120 | /** *********************************************** **/ 121 | /** ************** Conditions ************** **/ 122 | /** *********************************************** **/ 123 | 124 | /** 125 | * checks if user is owner 126 | * 127 | * @param array $config 128 | * @return bool 129 | */ 130 | private static function owner($config){ 131 | 132 | $database = Database::openConnection(); 133 | 134 | $database->prepare('SELECT * FROM '.$config["table"]. ' WHERE id = :id AND user_id = :user_id LIMIT 1'); 135 | $database->bindValue(':id', (int)$config["id"]); 136 | $database->bindValue(':user_id', (int)$config["user_id"]); 137 | $database->execute(); 138 | 139 | return $database->countRows() === 1; 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /app/models/Post.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | class Post extends Model{ 11 | 12 | /** 13 | * get all posts 14 | * 15 | * @access public 16 | * @param integer $pageNum 17 | * @return array Associative array of the posts, and Pagination Object. 18 | * 19 | */ 20 | public function getAll($pageNum = 1){ 21 | 22 | $pagination = Pagination::pagination("posts", "", [], $pageNum); 23 | $offset = $pagination->getOffset(); 24 | $limit = $pagination->perPage; 25 | 26 | $database = Database::openConnection(); 27 | $query = "SELECT posts.id AS id, users.profile_picture, users.id AS user_id, users.name AS user_name, posts.title, posts.content, posts.date "; 28 | $query .= "FROM users, posts "; 29 | $query .= "WHERE users.id = posts.user_id "; 30 | $query .= "ORDER BY posts.date DESC "; 31 | $query .= "LIMIT $limit OFFSET $offset"; 32 | 33 | $database->prepare($query); 34 | $database->execute(); 35 | $posts = $database->fetchAllAssociative(); 36 | 37 | $this->appendNumberOfComments($posts, $database); 38 | 39 | return array("posts" => $posts, "pagination" => $pagination); 40 | } 41 | 42 | /** 43 | * append number of comments to the array of posts for each post. 44 | * 45 | * @access private 46 | * @param array 47 | * 48 | */ 49 | private function appendNumberOfComments(&$posts){ 50 | 51 | $postId = 0; 52 | $database = Database::openConnection(); 53 | 54 | $query = "SELECT COUNT(*) AS comments FROM comments WHERE post_id = :post_id "; 55 | $database->prepare($query); 56 | $database->bindParam(':post_id', $postId); 57 | 58 | foreach($posts as $key => $post){ 59 | $postId = (int)$posts[$key]["id"]; 60 | $database->execute(); 61 | $posts[$key]["comments"] = $database->fetchAssociative()["comments"]; 62 | } 63 | } 64 | 65 | /** 66 | * get post by Id. 67 | * 68 | * @access public 69 | * @param integer $postId 70 | * @return array Array holds the data of the post 71 | */ 72 | public function getById($postId){ 73 | 74 | $database = Database::openConnection(); 75 | $query = "SELECT posts.id AS id, users.profile_picture, users.id AS user_id, users.name AS user_name, posts.title, posts.content, posts.date "; 76 | $query .= "FROM users, posts "; 77 | $query .= "WHERE posts.id = :id "; 78 | $query .= "AND users.id = posts.user_id LIMIT 1 "; 79 | 80 | $database->prepare($query); 81 | $database->bindValue(':id', $postId); 82 | $database->execute(); 83 | 84 | $post = $database->fetchAssociative(); 85 | return $post; 86 | } 87 | 88 | /** 89 | * create post 90 | * 91 | * @access public 92 | * @param integer $userId 93 | * @param string $title 94 | * @param string $content 95 | * @return bool 96 | * @throws Exception If post couldn't be created 97 | * 98 | */ 99 | public function create($userId, $title, $content){ 100 | 101 | $validation = new Validation(); 102 | if(!$validation->validate([ 103 | 'Title' => [$title, "required|minLen(2)|maxLen(60)"], 104 | 'Content' => [$content, "required|minLen(4)|maxLen(1800)"]])) { 105 | $this->errors = $validation->errors(); 106 | return false; 107 | } 108 | 109 | $database = Database::openConnection(); 110 | $query = "INSERT INTO posts (user_id, title, content) VALUES (:user_id, :title, :content)"; 111 | 112 | $database->prepare($query); 113 | $database->bindValue(':user_id', $userId); 114 | $database->bindValue(':title', $title); 115 | $database->bindValue(':content', $content); 116 | $database->execute(); 117 | 118 | if($database->countRows() !== 1){ 119 | throw new Exception ("Couldn't add news feed"); 120 | } 121 | 122 | return true; 123 | } 124 | 125 | /** 126 | * update Post 127 | * 128 | * @access public 129 | * @static static method 130 | * @param string $postId 131 | * @param string $title 132 | * @param string $content 133 | * @return array Array of the updated post 134 | * @throws Exception If post couldn't be updated 135 | * 136 | */ 137 | public function update($postId, $title, $content){ 138 | 139 | $validation = new Validation(); 140 | if(!$validation->validate([ 141 | 'Title' => [$title, "required|minLen(2)|maxLen(60)"], 142 | 'Content' => [$content, "required|minLen(4)|maxLen(1800)"]])) { 143 | $this->errors = $validation->errors(); 144 | return false; 145 | } 146 | 147 | $database = Database::openConnection(); 148 | $query = "UPDATE posts SET title = :title, content = :content WHERE id = :id LIMIT 1"; 149 | 150 | $database->prepare($query); 151 | $database->bindValue(':title', $title); 152 | $database->bindValue(':content', $content); 153 | $database->bindValue(':id', $postId); 154 | $result = $database->execute(); 155 | 156 | if(!$result){ 157 | throw new Exception("Couldn't update post of ID: " . $postId); 158 | } 159 | 160 | $post = $this->getById($postId); 161 | return $post; 162 | } 163 | 164 | } 165 | -------------------------------------------------------------------------------- /app/models/Todo.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | class Todo extends Model{ 11 | 12 | public function getAll(){ 13 | 14 | $database = Database::openConnection(); 15 | $query = "SELECT todo.id AS id, users.id AS user_id, users.name AS user_name, todo.content "; 16 | $query .= "FROM users, todo "; 17 | $query .= "WHERE users.id = todo.user_id "; 18 | 19 | $database->prepare($query); 20 | $database->execute(); 21 | $todo = $database->fetchAllAssociative(); 22 | 23 | return $todo; 24 | } 25 | 26 | public function create($userId, $content){ 27 | 28 | // using validation class 29 | $validation = new Validation(); 30 | if(!$validation->validate(['Content' => [$content, "required|minLen(4)|maxLen(300)"]])) { 31 | $this->errors = $validation->errors(); 32 | return false; 33 | } 34 | 35 | // using database class to insert new todo 36 | $database = Database::openConnection(); 37 | $query = "INSERT INTO todo (user_id, content) VALUES (:user_id, :content)"; 38 | $database->prepare($query); 39 | $database->bindValue(':user_id', $userId); 40 | $database->bindValue(':content', $content); 41 | $database->execute(); 42 | 43 | if($database->countRows() !== 1){ 44 | throw new Exception("Couldn't create todo"); 45 | } 46 | 47 | return true; 48 | } 49 | 50 | public function delete($id){ 51 | 52 | $database = Database::openConnection(); 53 | $database->deleteById("todo", $id); 54 | 55 | if($database->countRows() !== 1){ 56 | throw new Exception ("Couldn't delete todo"); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /app/uploads/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore -------------------------------------------------------------------------------- /app/utility/Utility.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class Utility{ 12 | 13 | private function __construct(){} 14 | 15 | /** 16 | * Normalizes an array, and converts it to a standard format. 17 | * 18 | * @param array $arr 19 | * @return array normalized array 20 | */ 21 | public static function normalize($arr){ 22 | 23 | $keys = array_keys($arr); 24 | $count = count($keys); 25 | 26 | $newArr = []; 27 | for ($i = 0; $i < $count; $i++) { 28 | if (is_int($keys[$i])) { 29 | $newArr[$arr[$keys[$i]]] = null; 30 | } else { 31 | $newArr[$keys[$i]] = $arr[$keys[$i]]; 32 | } 33 | } 34 | return $newArr; 35 | } 36 | 37 | /** 38 | * returns a string by separating array elements with commas 39 | * 40 | * @param array $arr 41 | * @return array 42 | */ 43 | public static function commas($arr){ 44 | return implode(",", (array)$arr); 45 | } 46 | 47 | /** 48 | * Merging two arrays 49 | * 50 | * @param mixed $arr1 51 | * @param mixed $arr2 52 | * @return array The merged array 53 | * 54 | */ 55 | public static function merge($arr1, $arr2){ 56 | return array_merge((array)$arr1, (array)$arr2); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /app/views/admin/backups.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 |

Backups

6 |
7 | 8 |
9 | 10 |
11 |
12 |
13 |
14 |
15 | Backups 16 |
17 | 18 |
19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | controller->admin->getBackups(); 33 | $isBackupExists = true; 34 | if(empty($data["basename"]) || empty($data["filename"])){ 35 | $data["filename"] = "Not Available"; 36 | $data["date"] = "-"; 37 | $isBackupExists = false; 38 | } 39 | ?> 40 | 44 | 45 | 54 | 63 | 64 | 65 |
FileLast UpdateUpdateRestore
41 | "> 42 | 43 | 46 | 47 | 50 | 51 | 52 | 53 | 55 | 56 | 59 | 60 | 61 | 62 |
66 | renderErrors(Session::getAndDestroy('backup-errors')); 69 | }else if(!empty(Session::get('backup-success'))){ 70 | echo $this->renderSuccess(Session::getAndDestroy('backup-success')); 71 | } 72 | ?> 73 |
74 | 75 |
76 | 77 |
78 |
79 | 80 |
81 | 82 |
83 | 84 | 85 | -------------------------------------------------------------------------------- /app/views/admin/users/index.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 |

Users

6 |
7 | 8 |
9 | 10 |
11 |
12 |
13 |
14 | 22 |
23 |
24 |
25 |
26 |
27 | 28 | 29 |
30 |
31 | 32 | 33 |
34 |
35 | 36 | 41 |
42 |
43 | 44 |
45 |
46 |
47 | 48 |
49 | 50 |
51 | 52 |
53 | 54 |
55 | 56 |
57 | 58 |
59 | Users 60 |
61 | 62 |
63 |
64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | controller->admin->getUsers(); 76 | echo $this->render(Config::get('ADMIN_VIEWS_PATH') . "users/users.php", array("users" => $usersData["users"])); 77 | ?> 78 | 79 |
NameRoleEmail
80 |
81 |
82 |
83 |
    84 | render(Config::get('VIEWS_PATH') . "pagination/default.php", array("pagination" => $usersData["pagination"])); 86 | ?> 87 |
88 |
89 |
90 | 91 |
92 |
93 | 94 |
95 | 96 |
97 | 98 | 99 | -------------------------------------------------------------------------------- /app/views/admin/users/users.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | There is no users 4 | 5 | 7 | "> 8 | 9 | 10 | 11 | Not Available 12 | 13 | encodeHTML($user["email"]); ?> 14 | 15 | 16 | 17 | " class="btn btn-default"> 18 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/views/admin/users/viewUser.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 |

Users

6 |
7 | 8 |
9 | 10 |
11 |
12 |
13 |
14 | controller->user->getProfileInfo($userId); 16 | ?> 17 |
18 | Update Profile 19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | " class="img-circle profile-pic-lg"> 27 |

28 |

29 | 30 |

31 |
32 |
33 |
34 | 35 | " class="form-control" required maxlength="30" placeholder="Name.."> 36 |

The maximum number of characters allowed is 30

37 |
38 |
39 | 40 | " disabled class="form-control" maxlength="50" placeholder="Email.."> 41 | 42 | 43 | 44 |

Email is not activated

45 | 46 |

Email has been verified

47 | 48 | 49 |
50 |
51 | 52 | 53 |

Please enter a complex password

54 |
55 |
56 | 57 | 67 |
68 |
69 | 72 |
73 |
74 | 75 |
76 | 77 |
78 | 79 |
80 | 81 |
82 |
83 | 84 |
85 | 86 |
87 | 88 | -------------------------------------------------------------------------------- /app/views/alerts/errors.php: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | System Error! 7 |
8 | Oops! There was an error, Please try again later or report a bug 9 |
10 | 11 | 12 | 13 | Heads Up! 14 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/views/alerts/success.php: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | encodeHTML($success);?> 5 |
-------------------------------------------------------------------------------- /app/views/bugs/index.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 |

Bugs

6 |
7 | 8 |
9 | 10 |
11 |
12 |
13 |
14 |
15 | Report Bugs, Features & Enhancements 16 |
17 |
18 |
19 |
20 | 21 | 22 |
23 |
24 | 25 | 26 |
27 | 28 |
29 | 30 | 35 |

Bug is an error you encountered

36 |

Feature is a new functionality you suggest to add

37 |

Enhancement is an existing feature, but you want to improve

38 |
39 | 40 |
41 | 42 | 43 |

The maximum number of characters allowed is 1800

44 |
45 |
46 | 47 |
48 |
49 | 52 |
53 |
54 | renderSuccess(Session::getAndDestroy('report-bug-success')); } ?> 55 | renderErrors(Session::getAndDestroy('report-bug-errors')); 58 | } 59 | ?> 60 |
61 | 62 |
63 | 64 |
65 | 66 |
67 |
68 | 69 |
70 | 71 |
72 | 73 | -------------------------------------------------------------------------------- /app/views/dashboard/index.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 |

Dashboard

6 |
7 | 8 |
9 | 10 |
11 |
12 |
13 |
14 | controller->user->dashboard(); 16 | $updates = $data["updates"]; 17 | $stats = $data["stats"]; 18 | ?> 19 |
20 |
21 | 22 |
23 |
24 |
25 |
News Feed
26 |
27 |
28 |
29 | "> 30 | 35 | 36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | 44 |
45 |
46 |
47 |
Posts
48 |
49 |
50 |
51 | "> 52 | 57 | 58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | 66 |
67 |
68 |
69 |
Files
70 |
71 |
72 |
73 | "> 74 | 79 | 80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | 88 |
89 |
90 |
91 |
Users
92 |
93 |
94 |
95 | 96 | "> 97 | 98 | 99 | 100 | 105 | 106 |
107 |
108 |
109 | 110 | 111 |
112 | 113 |
114 |
115 |
116 |
117 |
118 |
119 | Live 120 |
121 | Latest Updates 122 |
123 | 124 |
125 |
126 | render(Config::get('VIEWS_PATH') . "dashboard/updates.php", array("updates" => $updates));?> 127 |
128 | 129 |
130 | 131 |
132 | 133 |
134 | 135 |
136 | 137 |
138 | 139 | 140 | -------------------------------------------------------------------------------- /app/views/dashboard/updates.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
There is no updates yet!
4 | 5 | 6 | 7 | 8 |
9 | "; 14 | break; 15 | case "posts": 16 | $logo = ""; 17 | break; 18 | case "files": 19 | $logo = ""; 20 | break; 21 | } 22 | ?> 23 | 24 | 25 | 26 | truncate($this->encodeHTML($update["title"]),75); ?>
   27 | By 28 | timestamp($update["date"]);?> 29 |
30 | -------------------------------------------------------------------------------- /app/views/errors/400.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |
6 |
7 |

400

8 |

Bad Request

9 |
10 |
11 | 12 |
13 | 14 |
15 | 16 |
17 | 18 | -------------------------------------------------------------------------------- /app/views/errors/401.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |
6 |
7 |

401

8 |

Sorry, you aren't authenticated. Please login with valid credentials

9 |
10 |
11 | 12 |
13 | 14 |
15 | 16 |
17 | 18 | -------------------------------------------------------------------------------- /app/views/errors/403.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |
6 |
7 |

403

8 |

Sorry, you aren't authenticated to perform this action!

9 |
10 |
11 | 12 |
13 | 14 |
15 | 16 |
17 | 18 | -------------------------------------------------------------------------------- /app/views/errors/404.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |
6 |
7 |

404

8 |

Oops, we are sorry but the page you are looking for was not found!

9 |
10 |
11 | 12 |
13 | 14 |
15 | 16 |
17 | 18 | -------------------------------------------------------------------------------- /app/views/errors/500.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |
6 |
7 |

500

8 |

Oops, we are sorry but our system encountered an internal error
But do not worry, we are on it

9 |
10 |
11 | 12 |
13 | 14 |
15 | 16 |
17 | -------------------------------------------------------------------------------- /app/views/files/files.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | There is no files! 4 | 5 | 7 | 8 | "> 9 |
10 | timestamp($file["date"]);?>
11 | 12 | 13 | "> 14 | truncate($this->encodeHTML($file["filename"]),20); ?> 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 25 | 26 | -------------------------------------------------------------------------------- /app/views/files/index.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 |

Files

6 |
7 | 8 |
9 | 10 |
11 |
12 |
13 | 14 |
15 |
16 | Upload File 17 |
18 |
19 |
20 |
21 |
22 |
23 | 24 | 25 |

Only PDF, PPT, DOCX & ZIP Files

26 |

Max File Size: 5MB

27 |
28 | 31 |
32 | 35 |
36 |
37 |
38 | 39 |
40 | 41 |
42 | 43 |
44 | 45 |
46 |
47 | Files 48 |
49 | 50 |
51 |
52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | controller->file->getAll(empty($pageNum)? 1: $pageNum); 64 | echo $this->render(Config::get('VIEWS_PATH') . "files/files.php", array("files" => $filesData["files"])); 65 | ?> 66 | 67 |
AuthorFileFormat
68 |
69 | 70 |
71 |
72 |
    73 | render(Config::get('VIEWS_PATH') . "pagination/default.php", 75 | ["pagination" => $filesData["pagination"], "link" => "Files"]); 76 | ?> 77 |
78 |
79 |
80 | 81 |
82 |
83 | 84 |
85 |
86 | 87 | -------------------------------------------------------------------------------- /app/views/layout/default/footer.php: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/views/layout/default/header.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | mini PHP 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
37 | -------------------------------------------------------------------------------- /app/views/layout/default/navigation.php: -------------------------------------------------------------------------------- 1 | controller->user->getNotifications(Session::getUserId()); 4 | $newsfeed = $posts = $files = ""; 5 | foreach($notifications as $notification){ 6 | if($notification["count"] > 0){ 7 | // $$notification["target"] = $notification["count"]; // DEPRECATED IN PHP 7 8 | ${$notification["target"]} = $notification["count"]; 9 | } 10 | } 11 | 12 | $info = $this->controller->user->getProfileInfo(Session::getUserId()); 13 | 14 | ?> 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/views/layout/errors/footer.php: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | -------------------------------------------------------------------------------- /app/views/layout/errors/header.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | mini PHP 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 26 | 27 | 28 | 29 | 30 | 31 |
-------------------------------------------------------------------------------- /app/views/layout/login/footer.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/views/layout/login/header.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | mini PHP 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /app/views/layout/todo/footer.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/views/layout/todo/header.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | mini PHP 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 45 | 46 | -------------------------------------------------------------------------------- /app/views/login/passwordUpdated.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |
6 | 17 |
18 |
19 |
20 | 21 | -------------------------------------------------------------------------------- /app/views/login/updatePassword.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |
6 | 43 |
44 |
45 |
46 | 47 | -------------------------------------------------------------------------------- /app/views/login/userVerified.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |
6 | 17 |
18 |
19 |
20 | 21 | -------------------------------------------------------------------------------- /app/views/newsfeed/index.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 |

News Feed

6 |
7 | 8 |
9 | 10 |
11 |
12 |
13 |
14 |
15 | Share something ... 16 |
17 |
18 |
19 |
20 |
21 |
22 | 23 | 24 |

The maximum number of characters allowed is 300

25 |
26 |
27 | 28 |
29 |
30 | 33 |
34 |
35 | renderErrors(Session::getAndDestroy('newsfeed-errors')); 38 | } 39 | ?> 40 |
41 | 42 |
43 | 44 |
45 | 46 |
47 | 48 |
49 | 50 |
51 | 52 |
53 | News Feed 54 |
55 | 56 |
57 |
    58 | controller->newsfeed->getAll(empty($pageNum)? 1: $pageNum); 60 | echo $this->render(Config::get('VIEWS_PATH') . "newsfeed/newsfeed.php", array("newsfeed" => $newsfeedData["newsfeed"])); 61 | ?> 62 |
63 | 64 |
65 |
66 |
    67 | render(Config::get('VIEWS_PATH') . "pagination/default.php", 69 | ["pagination" => $newsfeedData["pagination"], "link" => "NewsFeed"]); 70 | ?> 71 |
72 |
73 |
74 | 75 |
76 | 77 |
78 | 79 |
80 | 81 |
-------------------------------------------------------------------------------- /app/views/newsfeed/newsfeed.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
  • There is no news feed!!
  • 4 | 5 | 7 |
  • " class="left clearfix"> 8 | 9 | " alt="User Picture" class="img-circle profile-pic-sm"> 10 | 11 |
    12 |
    13 | 14 | timestamp($feed["date"]);?> 15 | 16 | 17 | 18 | 19 | 20 | 21 |
    22 |

    autoLinks($this->encodeHTMLWithBR($feed["content"])); ?>

    23 |
    24 |
  • 25 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/views/newsfeed/updateForm.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | " alt="User Picture" class="img-circle profile-pic-sm"> 5 | 6 |
    7 |
    8 | 9 | timestamp($newsfeed["date"]);?> 10 |
    11 |
    " method="post" > 12 |
    13 | 14 | 16 |

    The maximum number of characters allowed is 300

    17 |
    18 |
    19 | 20 | 21 |
    22 |
    23 |
    24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/views/pagination/comments.php: -------------------------------------------------------------------------------- 1 | totalPages(); 6 | $currentPage = $pagination->currentPage; 7 | 8 | if($totalPages > 1 && $pagination->hasNextPage()) { 9 | $html .= "
  • View more..
  • "; 10 | } 11 | } 12 | echo $html; 13 | ?> -------------------------------------------------------------------------------- /app/views/pagination/default.php: -------------------------------------------------------------------------------- 1 | totalPages(); 8 | $currentPage = $pagination->currentPage; 9 | 10 | $linkExist = empty($link)? false: true; 11 | $url = empty($link)? "": PUBLIC_ROOT . $link . "?page="; 12 | 13 | if($totalPages > 1) { 14 | // 1. 15 | if($pagination->hasPreviousPage()) { 16 | 17 | $link = ($linkExist == false)? "javascript:void(0)": $url . ($currentPage - 1); 18 | $html .= "
  • "; 19 | } 20 | 21 | // 2. 22 | $i = (($currentPage - 4) > 1)? ($currentPage - 4): 1; 23 | $end = (($currentPage + 4) < $totalPages)? ($currentPage + 4): $totalPages; 24 | for(; $i <= $end; $i++) { 25 | 26 | $link = ($linkExist == false)? "javascript:void(0)": $url . ($i); 27 | 28 | if($i == $currentPage) { 29 | $html .= "
  • ".$i."
  • "; 30 | } else { 31 | $html .= "
  • ".$i."
  • "; 32 | } 33 | } 34 | 35 | // 3. 36 | if($pagination->hasNextPage()) { 37 | 38 | $link = ($linkExist == false)? "javascript:void(0)": $url . ($currentPage + 1); 39 | $html .= "
  • "; 40 | } 41 | } 42 | 43 | } 44 | 45 | echo $html; 46 | 47 | ?> -------------------------------------------------------------------------------- /app/views/posts/commentUpdateForm.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | " alt="User Picture" class="img-circle profile-pic-sm"> 5 | 6 | 7 |
    8 |
    9 | 10 | timestamp($comment["date"]) ?> 11 |
    12 |
    " method="post"> 13 |
    14 | 16 |

    The maximum number of characters allowed is 300

    17 |
    18 |
    19 | 20 | 21 |
    22 |
    23 |
    24 | 25 | 26 | -------------------------------------------------------------------------------- /app/views/posts/comments.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
  • There is no comments!
  • 5 | 6 | 8 | 9 |
  • " class="left clearfix"> 10 | 11 | " alt="User Picture" class="img-circle profile-pic-sm"> 12 | 13 | 14 |
    15 |
    16 | 17 | timestamp($comment["date"]) ?> 18 | 19 | 20 | 21 | 22 | 23 | 24 |
    25 |

    autoLinks($this->encodeHTMLWithBR($comment["content"])); ?>

    26 |
    27 |
  • 28 | 30 | -------------------------------------------------------------------------------- /app/views/posts/index.php: -------------------------------------------------------------------------------- 1 | 2 |
    3 |
    4 |
    5 |

    Posts

    6 |
    7 | 8 |
    9 | 10 |
    11 |
    12 |
    13 | 14 |
    15 |
    16 | Posts 17 |
    18 | 22 |
    23 |
    24 | 25 |
    26 |
    27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | controller->post->getAll(empty($pageNum)? 1: $pageNum); 38 | echo $this->render(Config::get('VIEWS_PATH') . "posts/posts.php", array("posts" => $postsData["posts"])); 39 | ?> 40 | 41 |
    AuthorPost
    42 |
    43 | 44 |
    45 |
    46 |
      47 | render(Config::get('VIEWS_PATH') . "pagination/default.php", 49 | ["pagination" => $postsData["pagination"], "link"=> "Posts"]); 50 | ?> 51 |
    52 |
    53 |
    54 | 55 |
    56 |
    57 | 58 |
    59 | 60 |
    61 | 62 | 63 | -------------------------------------------------------------------------------- /app/views/posts/newPost.php: -------------------------------------------------------------------------------- 1 | 2 |
    3 |
    4 |
    5 |

    Posts

    6 |
    7 | 8 |
    9 | 10 |
    11 |
    12 |
    13 |
    14 |
    15 | New Post 16 |
    17 |
    18 |
    19 |
    20 | 21 | 22 |
    23 |
    24 | 25 | 26 |
    27 |
    28 | 29 | 30 |

    The maximum number of characters allowed is 1800

    31 |
    32 |
    33 | 34 |
    35 |
    36 | 39 |
    40 |
    41 | renderSuccess(Session::getAndDestroy('posts-success')); } ?> 42 | renderErrors(Session::getAndDestroy('posts-errors')); 45 | } 46 | ?> 47 |
    48 | 49 |
    50 | 51 |
    52 | 53 |
    54 |
    55 | 56 |
    57 | 58 |
    59 | 60 | 61 | -------------------------------------------------------------------------------- /app/views/posts/post.php: -------------------------------------------------------------------------------- 1 | 2 |
    3 | 4 | 12 | 13 |
    14 |
    15 | 16 |
    17 |
    18 |
    19 | 20 | 21 | 22 | 25 | 29 | 30 | 31 |
    23 | " alt="User Picture" class="img-circle profile-pic-sm"> 24 | 26 | By
    27 | timestamp($post["date"]);?> 28 |
    32 |
    33 |

    autoLinks($this->encodeHTMLWithBR($post["content"]));?>

    34 |
    35 | 36 |
    37 | 38 |
    39 | 40 | -------------------------------------------------------------------------------- /app/views/posts/postUpdateForm.php: -------------------------------------------------------------------------------- 1 | 2 |
    3 |
    4 |
    5 | 6 |
    7 |
    8 |
    9 | 10 | 11 | 12 | 15 | 19 | 20 | 21 |
    13 | " alt="User Picture" class="img-circle profile-pic-sm"> 14 | 16 | By
    17 | timestamp($post["date"]);?> 18 |
    22 |
    23 |
    24 |
    25 | 26 | " class="form-control" required maxlength="80" placeholder="Title"> 27 |
    28 |
    29 | 30 | 31 |

    The maximum number of characters allowed is 1800

    32 |
    33 |
    34 | " /> 35 |
    36 |
    37 | 38 |
    39 |
    40 | "> 41 | 42 | 43 | 44 |
    45 |
    46 | renderErrors(Session::getAndDestroy('posts-errors')); 49 | } 50 | ?> 51 |
    52 | 53 |
    54 | 55 |
    56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /app/views/posts/posts.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | There is no posts! 5 | 6 | 8 | 9 |
    timestamp($post["date"]); ?>
    10 | 11 | "> 12 | truncate($this->encodeHTML($post["title"]),25); ?> 13 |
    14 | truncate($this->encodeHTML($post["content"]),30); ?> 15 | 16 |
    17 | 18 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/views/posts/viewPost.php: -------------------------------------------------------------------------------- 1 | 2 |
    3 |
    4 |
    5 |

    Posts

    6 |
    7 | 8 |
    9 | 10 |
    11 |
    12 |
    13 |
    14 | controller->post->getById($postId); 16 | 17 | if(empty($action)){ 18 | echo $this->render(Config::get('VIEWS_PATH') . "posts/post.php", array("post" => $post)); 19 | }else if($action === "update"){ 20 | echo $this->render(Config::get('VIEWS_PATH') . 'posts/postUpdateForm.php', array("post" => $post)); 21 | } 22 | ?> 23 |
    24 | 25 |
    26 |
    27 | Comments 28 |
    29 |
    30 |
      31 | controller->comment->getAll($postId); 33 | echo $this->render(Config::get('VIEWS_PATH') . "posts/comments.php", array("comments" => $commentsData["comments"])); 34 | ?> 35 |
    36 | 37 |
    38 |
    39 |
    40 | 41 |

    The maximum number of characters allowed is 300

    42 |
    43 |
    44 | 47 |
    48 |
    49 | 50 |
    51 |
      52 | render(Config::get('VIEWS_PATH') . "pagination/comments.php", array("pagination" => $commentsData["pagination"])); 54 | ?> 55 |
    56 |
    57 | 58 |
    59 | 60 |
    61 |
    62 | 63 |
    64 | 65 |
    66 | 67 | 68 | -------------------------------------------------------------------------------- /app/views/todo/index.php: -------------------------------------------------------------------------------- 1 |
    2 | 3 |

    TODO Application

    4 | 5 | 6 |
    " method="post"> 7 | 8 | 9 | 10 | 11 |
    12 | 13 | 14 | 21 | 22 |
    23 | renderSuccess(Session::getAndDestroy('success')); 28 | }else if(!empty(Session::get('errors'))){ 29 | echo $this->renderErrors(Session::getAndDestroy('errors')); 30 | } 31 | 32 | ?> 33 | 34 |


    35 | 36 | 61 | 62 |
    -------------------------------------------------------------------------------- /app/views/user/profile.php: -------------------------------------------------------------------------------- 1 | 2 |
    3 |
    4 |
    5 |

    Profile

    6 |
    7 | 8 |
    9 | 10 |
    11 |
    12 |
    13 |
    14 |
    15 | Update Profile 16 |
    17 |
    18 |
    19 |
    20 |
    21 |
    22 |
    23 | " class="img-circle profile-pic-lg"> 24 |

    25 |

    26 | 27 |

    28 |
    29 |
    30 |
    31 | 32 | " class="form-control" required maxlength="30" placeholder="Your Name.."> 33 |

    The maximum number of characters allowed is 30

    34 |
    35 |
    36 | 37 | 38 |

    Please enter a complex password

    39 |
    40 |
    41 | 42 | " class="form-control" maxlength="50" placeholder="Your Email.."> 43 |
    44 |
    45 | 46 |

    Please enter your email again.

    47 |
    48 |
    49 | 50 |
    51 |
    52 | 55 |
    56 |
    57 | 58 | renderErrors(Session::getAndDestroy('profile-info-errors')); 61 | }else if(!empty(Session::get('profile-info-success'))){ 62 | echo $this->renderSuccess(Session::getAndDestroy('profile-info-success')); 63 | } 64 | ?> 65 | 66 | 67 |
    68 |
    69 | 70 |
    71 |
    72 | 73 |
    74 |
    75 | Heads Up! 76 |
    77 |
    78 |
    79 | 80 | 81 | 82 |
    83 | 84 |
    86 |
    87 | 88 | 89 |

    Only JPEG, JPG, PNG & GIF Files

    90 |

    Max File Size: 2MB

    91 |
    92 | 93 | 96 |
    97 | 98 |
    99 |
    100 | 103 |
    104 |
    105 | renderErrors(Session::getAndDestroy('profile-picture-errors')); 108 | } 109 | ?> 110 | 111 |
    112 | 113 |
    114 | 115 |
    116 | 117 |
    118 |
    119 | 120 |
    121 | 122 |
    123 | 124 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "omarelgabry/miniphp", 3 | "type": "project", 4 | "description": "A small, simple PHP MVC framework skeleton that encapsulates a lot of features surrounded with powerful security layers.", 5 | "keywords": ["framework", "authentication", "authorization", "mvc", "security", "login", "encryption", "validation", "files", "ajax"], 6 | "homepage": "https://github.com/OmarElGabry/miniPHP", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Omar El Gabry", 11 | "email": "omar.elgabry.93@gmail.com", 12 | "role": "Developer" 13 | } 14 | ], 15 | "support": { 16 | "issues": "https://github.com/OmarElGabry/miniPHP/issues", 17 | "source": "https://github.com/OmarElGabry/miniPHP" 18 | }, 19 | "require": { 20 | "php": ">=5.5.0", 21 | "phpmailer/phpmailer": "~5.2", 22 | "gregwar/captcha": "~1.1.1" 23 | }, 24 | "autoload": { 25 | "psr-4": { "": ["app/core/", "app/models/", "app/utility/", "app/controllers/", "app/core/components/"] } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | # Disable server signature 2 | ServerSignature Off 3 | 4 | Options -MultiViews 5 | Options -Indexes 6 | 7 | RewriteEngine On 8 | 9 | RewriteCond %{REQUEST_FILENAME} !-d 10 | RewriteCond %{REQUEST_FILENAME} !-f 11 | RewriteCond %{REQUEST_FILENAME} !-l 12 | RewriteRule ^(.+)$ index.php?url=$1 [QSA,L] -------------------------------------------------------------------------------- /public/css/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmarElgabry/miniPHP/7ddf0c9db29b936418c1992e3d94cdf4918a1e93/public/css/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /public/css/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmarElgabry/miniPHP/7ddf0c9db29b936418c1992e3d94cdf4918a1e93/public/css/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /public/css/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmarElgabry/miniPHP/7ddf0c9db29b936418c1992e3d94cdf4918a1e93/public/css/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /public/css/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmarElgabry/miniPHP/7ddf0c9db29b936418c1992e3d94cdf4918a1e93/public/css/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /public/img/backgrounds/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmarElgabry/miniPHP/7ddf0c9db29b936418c1992e3d94cdf4918a1e93/public/img/backgrounds/background.png -------------------------------------------------------------------------------- /public/img/icons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmarElgabry/miniPHP/7ddf0c9db29b936418c1992e3d94cdf4918a1e93/public/img/icons/favicon.ico -------------------------------------------------------------------------------- /public/img/icons/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmarElgabry/miniPHP/7ddf0c9db29b936418c1992e3d94cdf4918a1e93/public/img/icons/favicon.png -------------------------------------------------------------------------------- /public/img/icons/icon114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmarElgabry/miniPHP/7ddf0c9db29b936418c1992e3d94cdf4918a1e93/public/img/icons/icon114.png -------------------------------------------------------------------------------- /public/img/icons/icon120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmarElgabry/miniPHP/7ddf0c9db29b936418c1992e3d94cdf4918a1e93/public/img/icons/icon120.png -------------------------------------------------------------------------------- /public/img/icons/icon144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmarElgabry/miniPHP/7ddf0c9db29b936418c1992e3d94cdf4918a1e93/public/img/icons/icon144.png -------------------------------------------------------------------------------- /public/img/icons/icon152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmarElgabry/miniPHP/7ddf0c9db29b936418c1992e3d94cdf4918a1e93/public/img/icons/icon152.png -------------------------------------------------------------------------------- /public/img/icons/icon57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmarElgabry/miniPHP/7ddf0c9db29b936418c1992e3d94cdf4918a1e93/public/img/icons/icon57.png -------------------------------------------------------------------------------- /public/img/icons/icon72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmarElgabry/miniPHP/7ddf0c9db29b936418c1992e3d94cdf4918a1e93/public/img/icons/icon72.png -------------------------------------------------------------------------------- /public/img/icons/icon76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmarElgabry/miniPHP/7ddf0c9db29b936418c1992e3d94cdf4918a1e93/public/img/icons/icon76.png -------------------------------------------------------------------------------- /public/img/profile_pictures/author.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmarElgabry/miniPHP/7ddf0c9db29b936418c1992e3d94cdf4918a1e93/public/img/profile_pictures/author.jpg -------------------------------------------------------------------------------- /public/img/profile_pictures/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmarElgabry/miniPHP/7ddf0c9db29b936418c1992e3d94cdf4918a1e93/public/img/profile_pictures/default.png -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | request->root()); 68 | define('PUBLIC_ROOT', $app->request->root()); 69 | 70 | /* 71 | |-------------------------------------------------------------------------- 72 | | Run The Application 73 | |-------------------------------------------------------------------------- 74 | | 75 | | Once we have the application instance, we can handle the incoming request 76 | | and send a response back to the client's browser. 77 | | 78 | */ 79 | 80 | $app->run(); -------------------------------------------------------------------------------- /public/js/sb-admin-2.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | //$('#side-menu').metisMenu(); 4 | 5 | }); 6 | 7 | //Loads the correct sidebar on window load, 8 | //collapses the sidebar on window resize. 9 | // Sets the min-height of #page-wrapper to window size 10 | $(function() { 11 | $(window).bind("load resize", function() { 12 | topOffset = 50; 13 | width = (this.window.innerWidth > 0) ? this.window.innerWidth : this.screen.width; 14 | if (width < 768) { 15 | $('div.navbar-collapse').addClass('collapse'); 16 | topOffset = 100; // 2-row-menu 17 | } else { 18 | $('div.navbar-collapse').removeClass('collapse'); 19 | } 20 | 21 | height = ((this.window.innerHeight > 0) ? this.window.innerHeight : this.screen.height) - 1; 22 | height = height - topOffset; 23 | if (height < 1) height = 1; 24 | if (height > topOffset) { 25 | $("#page-wrapper").css("min-height", (height) + "px"); 26 | } 27 | }); 28 | }); 29 | --------------------------------------------------------------------------------