├── .htaccess ├── LICENSE ├── README.md ├── app ├── controllers │ ├── MainController.php │ ├── PostsController.php │ └── UsersController.php ├── models │ ├── PostsModel.php │ └── UsersModel.php └── views │ ├── footer.php │ ├── header.php │ ├── pagination.php │ ├── posts │ ├── add.php │ ├── edit.php │ ├── post.php │ └── posts.php │ └── users │ ├── edit.php │ ├── panel.php │ ├── sign-in.php │ ├── sign-up.php │ ├── user.php │ └── users.php ├── core ├── App.php ├── classes │ ├── Controller.php │ ├── Model.php │ ├── Sql.php │ └── ValidationException.php ├── config │ ├── database.php │ └── session.php └── helpers │ ├── Email.php │ ├── Errors.php │ ├── File.php │ ├── Site.php │ ├── Str.php │ ├── Time.php │ └── User.php ├── database.sql ├── error.log ├── index.php ├── install.php ├── public ├── assets │ ├── css │ │ └── style.css │ └── js │ │ └── main.js └── uploads │ └── .empty ├── screenshot.png └── version.json /.htaccess: -------------------------------------------------------------------------------- 1 | # 2 | # Server config 3 | # 4 | # @package CRUD MVC OOP PDO 5 | # @link https://github.com/utoyvo/crud-mvc-oop-pdo/blob/master/.htaccess 6 | # 7 | 8 | # Default charset. 9 | AddDefaultCharset UTF-8 10 | 11 | # Default index page. 12 | DirectoryIndex index.php 13 | 14 | # Default language 15 | DefaultLanguage en-US 16 | 17 | # Server timezone 18 | SetEnv TZ America/Washington 19 | 20 | # Server admin email 21 | SetEnv SERVER_ADMIN default@example.com 22 | 23 | # Don't show directory listings for URLs which map to a directory. 24 | Options -Indexes 25 | 26 | # Rewriting all of requests to endpoint /index.php 27 | RewriteEngine on 28 | RewriteCond %{REQUEST_FILENAME} !-f 29 | RewriteCond %{REQUEST_FILENAME} !-d 30 | RewriteRule ^(.*)$ index.php/$1 [L] 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Oleksandr Klochko 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CRUD MVC OOP PDO 2 | 3 | A simple and intuitive CRUD system using the MVC pattern in OOP paradigm. To connect to the database using PDO. 4 | 5 |  6 | 7 | ## STRUCTURE 8 | 9 | ``` 10 | ├── app 11 | │ ├── controllers 12 | | | ├── MainController.php 13 | | | ├── PostsController.php 14 | | | └── UsersController.php 15 | │ ├── models 16 | | | ├── PostsModel.php 17 | | | └── UsersModel.php 18 | | └── views 19 | | ├── posts 20 | | │ ├── add.php 21 | | │ ├── edit.php 22 | | │ ├── post.php 23 | | | └── posts.php 24 | | ├── users 25 | | │ ├── edit.php 26 | | │ ├── panel.php 27 | | │ ├── sign-in.php 28 | | │ ├── sign-up.php 29 | | │ ├── user.php 30 | | | └── users.php 31 | | ├── footer.php 32 | | ├── header.php 33 | | └── pagination.php 34 | ├── core 35 | │ ├── classes 36 | | │ ├── Controller.php 37 | | | ├── Model.php 38 | | | ├── Sql.php 39 | | | └── ValidationException.php 40 | │ ├── config 41 | | │ ├── database.php 42 | | | └── session.php 43 | │ ├── helpers 44 | | │ ├── Email.php 45 | | │ ├── Errors.php 46 | | │ ├── File.php 47 | | │ ├── Site.php 48 | | │ ├── Str.php 49 | | │ ├── Time.php 50 | | | └── User.php 51 | | └── App.php 52 | ├── public 53 | │ ├── assets 54 | │ │ ├── css 55 | │ │ │ └── style.css 56 | │ | └── js 57 | │ │ └── main.js 58 | │ └── uploads 59 | │ └── .empty 60 | ├── .htaccess 61 | ├── LICENSE 62 | ├── README.md 63 | ├── database.sql 64 | ├── error.log 65 | ├── index.php 66 | ├── install.php 67 | ├── screenshot.png 68 | └── version.json 69 | ``` 70 | 71 | ## REQUIREMENTS 72 | 73 | ### System requirements 74 | 75 | - Apache. 76 | - PHP 7.2.0 or higher. 77 | 78 | ### PHP extensions 79 | 80 | - PHP [Data Objects (PDO)](https://www.php.net/manual/en/book.pdo.php) module for accessing databases. 81 | - PHP [mbstring](http://php.net/manual/en/book.mbstring.php) module for full UTF-8 support. 82 | - PHP [gd](http://php.net/manual/en/book.image.php) or [ImageMagick](http://php.net/manual/en/book.imagick.php) module for image processing. 83 | - PHP [JSON](https://php.net/manual/en/book.json.php) module for JSON manipulation. 84 | - PHP [Fileinfo](https://www.php.net/manual/en/book.fileinfo.php). 85 | - PHP [SPL](https://www.php.net/manual/en/book.spl.php). 86 | - PHP [DOM](https://www.php.net/manual/ru/class.domdocument.php). 87 | 88 | ### Database 89 | 90 | - [MySQL](https://www.mysql.com/) or [MariaDB](https://mariadb.com/). 91 | 92 | ## CONFIG 93 | 94 | ### Database 95 | 96 | ```php 97 | $this->config['db'] = array( 98 | 'driver' => 'mysql', 99 | 'host' => 'localhost', 100 | 'username' => 'root', 101 | 'password' => '', 102 | 'name' => 'crud-mvc-oop-pdo' 103 | ); 104 | ``` 105 | 106 | ### Session 107 | 108 | ```php 109 | $this->config['session-name'] = 'SID'; 110 | ``` 111 | 112 | ## DATABASE 113 | 114 | ### Posts 115 | 116 | | Field | Type | Null | Key | Default | Extra | 117 | |:-------------|:-------------|:-----|:----|:------------------|:----------------------------| 118 | | post_id | INT(11) | NO | PRI | NULL | AUTO_INCREMENT | 119 | | post_created | DATETIME | NO | | CURRENT_TIMESTAMP | | 120 | | post_updated | DATETIME | NO | | CURRENT_TIMESTAMP | ON UPDATE CURRENT_TIMESTAMP | 121 | | post_title | VARCHAR(255) | NO | | NULL | | 122 | | post_author | INT(11) | NO | | NULL | | 123 | | post_content | TEXT | NO | | NULL | | 124 | | post_cover | VARCHAR(255) | NO | | NULL | | 125 | 126 | ### Users 127 | 128 | | Field | Type | Null | Key | Default | Extra | 129 | |:--------------|:-------------|:-----|:----|:------------------|:----------------------------| 130 | | user_id | INT(11) | NO | PRI | NULL | AUTO_INCREMENT | 131 | | user_created | DATETIME | NO | | CURRENT_TIMESTAMP | | 132 | | user_updated | DATETIME | NO | | CURRENT_TIMESTAMP | ON UPDATE CURRENT_TIMESTAMP | 133 | | user_name | VARCHAR(255) | NO | | NULL | | 134 | | user_email | VARCHAR(255) | NO | | NULL | | 135 | | user_password | VARCHAR(255) | NO | | NULL | | 136 | | user_role | VARCHAR(20) | NO | | NULL | | 137 | 138 | ## CONTRIBUTOR 139 | 140 | Oleksandr Klochko [@utoyvo](https://github.com/utoyvo) 141 | 142 | ## LICENSE 143 | 144 | Code released under the [MIT License](LICENSE). 145 | -------------------------------------------------------------------------------- /app/controllers/MainController.php: -------------------------------------------------------------------------------- 1 | model( 'PostsModel' ); 20 | 21 | $pagination = $this->PostsModel->pagination( 'posts', ( int )$_GET['page'], 5 ); 22 | $sort = $this->PostsModel->sort( 'post_', ( string )$_GET['sort'], array( 'title', 'time', 'content', 'author' ) ); 23 | 24 | $posts = $this->PostsModel->readPosts( $pagination, $sort ); 25 | 26 | $data = array( 27 | 'title' => 'Posts', 28 | 'posts' => $posts, 29 | 'pagination' => $pagination, 30 | ); 31 | 32 | $this->view( 'posts/posts', $data ); 33 | } 34 | 35 | /** 36 | * Post 37 | * 38 | * http://localhost/posts/post/[$post_id] 39 | */ 40 | public function post( $post_id = 0 ) : void 41 | { 42 | $this->model( 'PostsModel' ); 43 | 44 | $post = $this->PostsModel->readPost( ( int )$post_id ); 45 | 46 | $data = array( 47 | 'title' => $post['post_title'] ?? '', 48 | 'post' => $post, 49 | ); 50 | 51 | $this->view( 'posts/post', $data ); 52 | } 53 | 54 | /** 55 | * Add 56 | * 57 | * http://localhost/posts/add 58 | */ 59 | public function add() : void 60 | { 61 | $this->model( 'PostsModel' ); 62 | 63 | if ( ! User::login() ) { 64 | Site::redirect( '/users/sign_in' ); 65 | } 66 | 67 | if ( isset( $_POST['post-add'] ) ) { 68 | try { 69 | $this->PostsModel->addPost( array( 70 | 'title' => $_POST['post-title'], 71 | 'author' => $_SESSION['user']['id'], 72 | 'content' => $_POST['post-content'], 73 | 'cover' => $_FILES['post-cover'], 74 | ) ); 75 | } catch ( ValidationException $e ) { 76 | $errors = $e->getError(); 77 | } 78 | } 79 | 80 | $data = array( 81 | 'title' => 'Add Post', 82 | 'errors' => $errors, 83 | ); 84 | 85 | $this->view( 'posts/add', $data ); 86 | } 87 | 88 | /** 89 | * Edit 90 | * 91 | * http://localhost/posts/edit/[$post_id] 92 | */ 93 | public function edit( $post_id = 0 ) : void 94 | { 95 | $this->model( 'PostsModel' ); 96 | 97 | $post = $this->PostsModel->readPost( ( int )$post_id ); 98 | 99 | if ( ! User::author( ( int )$post['post_author'] ) && ! User::role( array( 'admin', 'editor' ) ) ) { 100 | Site::redirect( '/' ); 101 | } 102 | 103 | if ( isset( $_POST['post-edit'] ) ) { 104 | try { 105 | $this->PostsModel->editPost( array( 106 | 'id' => ( int )$post_id, 107 | 'title' => $_POST['post-title'], 108 | 'content' => $_POST['post-content'], 109 | 'cover' => $_FILES['post-cover'], 110 | ) ); 111 | } catch ( ValidationException $e ) { 112 | $errors = $e->getError(); 113 | } 114 | } 115 | 116 | $data = array( 117 | 'title' => 'Edit ' . $post['post_title'], 118 | 'post' => $post, 119 | 'errors' => $errors, 120 | ); 121 | 122 | $this->view( 'posts/edit', $data ); 123 | } 124 | 125 | /** 126 | * Delete 127 | * 128 | * http://localhost/posts/delete/[$post_id] 129 | */ 130 | public function delete( $post_id = 0 ) : void 131 | { 132 | $this->model( 'PostsModel' ); 133 | 134 | $post = $this->PostsModel->readPost( ( int )$post_id ); 135 | 136 | if ( ! User::author( $post['post_author'] ) && ! User::role( array( 'admin' ) ) ) { 137 | Site::redirect( '/' ); 138 | } 139 | 140 | if ( ! empty( $post['post_cover'] ) ) { 141 | File::delete( $post['post_cover'] ); 142 | } 143 | 144 | $this->PostsModel->deletePost( ( int )$post_id ); 145 | } 146 | 147 | } 148 | 149 | class_alias( 'PostsController', 'Posts' ); 150 | -------------------------------------------------------------------------------- /app/controllers/UsersController.php: -------------------------------------------------------------------------------- 1 | model( 'UsersModel' ); 20 | 21 | $pagination = $this->UsersModel->pagination( 'users', ( int )$_GET['page'], 5 ); 22 | $sort = $this->UsersModel->sort( 'user_', ( string )$_GET['sort'], array( 'name', 'email', 'role', 'time' ) ); 23 | 24 | $users = $this->UsersModel->readUsers( $pagination, $sort ); 25 | 26 | $data = array( 27 | 'title' => 'Users', 28 | 'users' => $users, 29 | 'pagination' => $pagination, 30 | ); 31 | 32 | $this->view( 'users/users', $data ); 33 | } 34 | 35 | /** 36 | * User 37 | * 38 | * http://localhost/users/user/[$user_id] 39 | */ 40 | public function user( $user_id = 0 ) : void 41 | { 42 | $this->model( 'UsersModel' ); 43 | 44 | $user = $this->UsersModel->readUser( ( int )$user_id ); 45 | 46 | $data = array( 47 | 'title' => '@' . $user['user_name'], 48 | 'user' => $user, 49 | ); 50 | 51 | $this->view( 'users/user', $data ); 52 | } 53 | 54 | /** 55 | * Sign Up 56 | * 57 | * http://localhost/users/sign_up 58 | */ 59 | public function sign_up() : void 60 | { 61 | $this->model( 'UsersModel' ); 62 | 63 | if ( User::login() ) { 64 | Site::redirect( '/' ); 65 | } 66 | 67 | $role = file_exists( 'install.php' ) ? 'admin' : 'user'; 68 | 69 | if ( isset( $_POST['sign-up'] ) ) { 70 | try { 71 | $this->UsersModel->signUp( array( 72 | 'name' => $_POST['user-name'], 73 | 'email' => $_POST['user-email'], 74 | 'password' => $_POST['user-password'], 75 | 'role' => $role, 76 | ) ); 77 | } catch ( ValidationException $e ) { 78 | $errors = $e->getError(); 79 | } 80 | } 81 | 82 | $data = array( 83 | 'title' => 'Sign Up', 84 | 'errors' => $errors, 85 | ); 86 | 87 | $this->view( 'users/sign-up', $data ); 88 | } 89 | 90 | /** 91 | * Sign In 92 | * 93 | * http://localhost/users/sign_in 94 | */ 95 | public function sign_in() : void 96 | { 97 | $this->model( 'UsersModel' ); 98 | 99 | if ( User::login() ) { 100 | Site::redirect( '/' ); 101 | } 102 | 103 | if ( isset( $_POST['sign-in'] ) ) { 104 | try { 105 | $this->UsersModel->signIn( array( 106 | 'name' => $_POST['user-name'], 107 | 'password' => $_POST['user-password'], 108 | ) ); 109 | } catch ( ValidationException $e ) { 110 | $errors = $e->getError(); 111 | } 112 | } 113 | 114 | $data = array( 115 | 'title' => 'Sign In', 116 | 'errors' => $errors, 117 | ); 118 | 119 | $this->view( 'users/sign-in', $data ); 120 | } 121 | 122 | /** 123 | * Sign Out 124 | * 125 | * http://localhost/users/sign_out 126 | */ 127 | public function sign_out() : void 128 | { 129 | $this->model( 'UsersModel' ); 130 | 131 | if ( User::login() ) { 132 | $this->UsersModel->signOut(); 133 | } 134 | } 135 | 136 | /** 137 | * Edit 138 | * 139 | * http://localhost/users/edit/[$user_id] 140 | */ 141 | public function edit( $user_id = 0 ) : void 142 | { 143 | $this->model( 'UsersModel' ); 144 | 145 | $user = $this->UsersModel->readUser( ( int )$user_id ); 146 | 147 | if ( ! User::author( ( int )$user_id ) && ! User::role( array( 'admin' ) ) ) { 148 | Site::redirect( '/' ); 149 | } 150 | 151 | if ( isset( $_POST['user-edit'] ) ) { 152 | try { 153 | $this->UsersModel->editUser( array( 154 | 'id' => ( int )$user_id, 155 | 'name' => $_POST['user-name'], 156 | 'email' => $_POST['user-email'], 157 | 'role' => $_POST['user-role'], 158 | ) ); 159 | } catch ( ValidationException $e ) { 160 | $errors = $e->getError(); 161 | } 162 | } 163 | 164 | if ( ! User::author( $user_id ) ) { 165 | if ( isset( $_POST['user-change-role'] ) ) { 166 | try { 167 | $this->UsersModel->changeUserRole( array( 168 | 'id' => ( int )$user_id, 169 | 'role' => $_POST['user-role'], 170 | ) ); 171 | } catch ( ValidationException $e ) { 172 | $errors = $e->getError(); 173 | } 174 | } 175 | } 176 | 177 | if ( isset( $_POST['user-change-password'] ) ) { 178 | try { 179 | $this->UsersModel->changeUserPassword( array( 180 | 'id' => ( int )$user_id, 181 | 'old-password' => $_POST['old-password'], 182 | 'new-password' => $_POST['new-password'], 183 | 'confirm-new-password' => $_POST['confirm-new-password'], 184 | ) ); 185 | } catch ( ValidationException $e ) { 186 | $errors = $e->getError(); 187 | } 188 | } 189 | 190 | $data = array( 191 | 'title' => 'Edit @' . $user['user_name'], 192 | 'user' => $user, 193 | 'errors' => $errors, 194 | ); 195 | 196 | $this->view( 'users/edit', $data ); 197 | } 198 | 199 | /** 200 | * Delete 201 | * 202 | * http://localhost/users/delete/[$user_id] 203 | */ 204 | public function delete( $user_id = 0 ) : void 205 | { 206 | $this->model( 'UsersModel' ); 207 | 208 | if ( User::author( ( int )$user_id ) || User::role( array( 'admin' ) ) ) { 209 | $this->UsersModel->deleteUser( ( int )$user_id ); 210 | } 211 | } 212 | 213 | } 214 | 215 | class_alias( 'UsersController', 'Users' ); 216 | -------------------------------------------------------------------------------- /app/models/PostsModel.php: -------------------------------------------------------------------------------- 1 | select( 'post_id, post_created, post_updated, post_title, post_content, post_author, post_cover, user_id, user_name, user_email' ) 19 | ->from( 'posts' ) 20 | ->innerJoin( 'users' ) 21 | ->on( 'posts.post_author = users.user_id' ) 22 | ->orderBy( $sort['by'] . ' ' . $sort['order'] ) 23 | ->limit( $pagination['start'] . ', ' . $pagination['perpage'] ) 24 | ->get(); 25 | 26 | $stmt = $this->db->prepare( $sql ); 27 | $stmt->execute(); 28 | 29 | return $stmt->fetchAll( PDO::FETCH_ASSOC ); 30 | } 31 | 32 | /** 33 | * Read Post 34 | */ 35 | public function readPost( int $post_id ) 36 | { 37 | $sql = Sql::query() 38 | ->select( 'post_id, post_created, post_updated, post_title, post_content, post_author, post_cover, user_id, user_name, user_email' ) 39 | ->from( 'posts' ) 40 | ->innerJoin( 'users' ) 41 | ->on( 'posts.post_author = users.user_id' ) 42 | ->where( 'post_id = :id' ) 43 | ->get(); 44 | 45 | $data = array( 46 | 'id' => $post_id, 47 | ); 48 | 49 | $stmt = $this->db->prepare( $sql ); 50 | $this->bind( $stmt, $data ); 51 | $stmt->execute(); 52 | 53 | return $stmt->fetch( PDO::FETCH_ASSOC ); 54 | } 55 | 56 | /** 57 | * Add Post 58 | */ 59 | public function addPost( array $post ) : void 60 | { 61 | Str::validate( $post['title'], true, 255 ); 62 | Str::validate( $post['content'] ); 63 | File::validate( $post['cover'], array( 'image/jpg', 'image/jpeg', 'image/png', 'image/gif' ), 5000000 ); 64 | 65 | $post_title = Str::clean( $post['title'] ); 66 | $post_author = Str::clean( $post['author'] ); 67 | $post_content = Str::clean( $post['content'], false ); 68 | $post_cover = File::upload( $post['cover'] ); 69 | 70 | $sql = Sql::query() 71 | ->insertInto( 'posts ( post_title, post_author, post_content, post_cover )' ) 72 | ->values( '( :title, :author, :content, :cover )' ) 73 | ->get(); 74 | 75 | $data = array( 76 | 'title' => $post_title, 77 | 'author' => $post_author, 78 | 'content' => $post_content, 79 | 'cover' => $post_cover, 80 | ); 81 | 82 | $stmt = $this->db->prepare( $sql ); 83 | $this->bind( $stmt, $data ); 84 | $stmt->execute(); 85 | 86 | Site::redirect( '/' ); 87 | } 88 | 89 | /** 90 | * Edit Post 91 | */ 92 | public function editPost( array $post ) : void 93 | { 94 | Str::validate( $post['title'], true, 255 ); 95 | Str::validate( $post['content'] ); 96 | File::validate( $post['cover'], array( 'image/jpg', 'image/jpeg', 'image/png', 'image/gif' ), 5000000 ); 97 | 98 | $post_id = $post['id']; 99 | $post_title = Str::clean( $post['title'] ); 100 | $post_content = Str::clean( $post['content'], false ); 101 | 102 | $current_post = $this->readPost( $post_id ); 103 | if ( $post['cover']['error'] === 0 ) { 104 | File::delete( $current_post['post_cover'] ); 105 | $post_cover = File::upload( $post['cover'] ); 106 | } elseif ( $post['cover']['error'] === 4 ) { 107 | File::delete( $current_post['post_cover'] ); 108 | $post_cover = ''; 109 | } 110 | 111 | $sql = Sql::query() 112 | ->update( 'posts' ) 113 | ->set( 'post_title = :title, post_content = :content, post_cover = :cover' ) 114 | ->where( 'post_id = :id' ) 115 | ->get(); 116 | 117 | $data = array( 118 | 'id' => $post_id, 119 | 'title' => $post_title, 120 | 'content' => $post_content, 121 | 'cover' => $post_cover, 122 | ); 123 | 124 | $stmt = $this->db->prepare( $sql ); 125 | $this->bind( $stmt, $data ); 126 | $stmt->execute(); 127 | 128 | Site::redirect( '/posts/edit/' . $post_id ); 129 | } 130 | 131 | /** 132 | * Delete Post 133 | */ 134 | public function deletePost( int $post_id ) : void 135 | { 136 | $sql = Sql::query() 137 | ->delete() 138 | ->from( 'posts' ) 139 | ->where( 'post_id = :id' ) 140 | ->get(); 141 | 142 | $data = array( 143 | 'id' => $post_id, 144 | ); 145 | 146 | $stmt = $this->db->prepare( $sql ); 147 | $this->bind( $stmt, $data ); 148 | $stmt->execute(); 149 | 150 | Site::redirect( '/' ); 151 | } 152 | 153 | } 154 | -------------------------------------------------------------------------------- /app/models/UsersModel.php: -------------------------------------------------------------------------------- 1 | select( 'user_id, user_created, user_updated, user_name, user_email, user_role' ) 19 | ->from( 'users' ) 20 | ->orderBy( $sort['by'] . ' ' . $sort['order'] ) 21 | ->limit( $pagination['start'] . ', ' . $pagination['perpage'] ) 22 | ->get(); 23 | 24 | $stmt = $this->db->prepare( $sql ); 25 | $stmt->execute(); 26 | 27 | return $stmt->fetchAll( PDO::FETCH_ASSOC ); 28 | } 29 | 30 | /** 31 | * Read User 32 | */ 33 | public function readUser( int $user_id ) 34 | { 35 | $sql = Sql::query() 36 | ->select( 'user_id, user_created, user_updated, user_name, user_email, user_role' ) 37 | ->from( 'users' ) 38 | ->where( 'user_id = :id' ) 39 | ->get(); 40 | 41 | $data = array( 42 | 'id' => $user_id, 43 | ); 44 | 45 | $stmt = $this->db->prepare( $sql ); 46 | $this->bind( $stmt, $data ); 47 | $stmt->execute(); 48 | 49 | return $stmt->fetch( PDO::FETCH_ASSOC ); 50 | } 51 | 52 | /** 53 | * Sign Up 54 | */ 55 | public function signUp( array $user ) : void 56 | { 57 | Str::validate( $user['name'], true, 255 ); 58 | Email::validate( $user['email'] ); 59 | Str::validate( $user['password'], true, 255 ); 60 | 61 | $user_name = Str::clean( $user['name'] ); 62 | $user_email = Str::clean( $user['email'] ); 63 | $user_password = password_hash( $user['password'], PASSWORD_DEFAULT ); 64 | $user_role = in_array( $user['role'], array( 'admin', 'editor', 'user' ) ) ? $user['role'] : 'user'; 65 | 66 | if ( ! empty( $user_name ) && ! empty( $user_email ) ) { 67 | $sql = Sql::query() 68 | ->select( 'user_name, user_email' ) 69 | ->from( 'users' ) 70 | ->where( 'user_name = :name' ) 71 | ->or( 'user_email = :email' ) 72 | ->get(); 73 | 74 | $data = array( 75 | 'name' => $user_name, 76 | 'email' => $user_email, 77 | ); 78 | 79 | $stmt = $this->db->prepare( $sql ); 80 | $this->bind( $stmt, $data ); 81 | $stmt->execute(); 82 | 83 | if ( ! empty( $stmt->fetch( PDO::FETCH_ASSOC ) ) ) { 84 | $errors[] = 'Username or Email already exists.'; 85 | throw new ValidationException( $errors ); 86 | } 87 | } 88 | 89 | $sql = Sql::query() 90 | ->insertInto( 'users ( user_name, user_email, user_password, user_role )' ) 91 | ->values( '( :name, :email, :password, :role )' ) 92 | ->get(); 93 | 94 | $data = array( 95 | 'name' => $user_name, 96 | 'email' => $user_email, 97 | 'password' => $user_password, 98 | 'role' => $user_role, 99 | ); 100 | 101 | $stmt = $this->db->prepare( $sql ); 102 | $this->bind( $stmt, $data ); 103 | $stmt->execute(); 104 | 105 | if ( file_exists( 'install.php' ) ) { 106 | unlink( 'install.php' ); 107 | } 108 | 109 | Site::redirect( '/users/sign_in' ); 110 | } 111 | 112 | /** 113 | * Sign In 114 | */ 115 | public function signIn( array $user ) : void 116 | { 117 | Str::validate( $user['name'], true, 255 ); 118 | Str::validate( $user['password'], true, 255 ); 119 | 120 | $user_name = Str::clean( $user['name'] ); 121 | $user_password = Str::clean( $user['password'] ); 122 | 123 | $sql = Sql::query() 124 | ->select( 'user_id, user_name, user_email, user_password, user_role' ) 125 | ->from( 'users' ) 126 | ->where( 'user_name = :name' ) 127 | ->get(); 128 | 129 | $data = array( 130 | 'name' => $user_name, 131 | ); 132 | 133 | $stmt = $this->db->prepare( $sql ); 134 | $this->bind( $stmt, $data ); 135 | $stmt->execute(); 136 | 137 | $user = $stmt->fetch( PDO::FETCH_ASSOC ); 138 | 139 | if ( ! empty( $user ) ) { 140 | if ( password_verify( $user_password, $user['user_password'] ) ) { 141 | $_SESSION['user']['id'] = $user['user_id']; 142 | $_SESSION['user']['name'] = $user['user_name']; 143 | $_SESSION['user']['email'] = $user['user_email']; 144 | $_SESSION['user']['role'] = $user['user_role']; 145 | 146 | Site::redirect( '/users/user/' . $user['user_id'] ); 147 | } else { 148 | $errors[] = 'Invalid password'; 149 | } 150 | } else { 151 | $errors[] = 'User does not exist'; 152 | } 153 | 154 | if ( ! empty( $errors ) ) { 155 | throw new ValidationException( $errors ); 156 | } 157 | } 158 | 159 | /** 160 | * Sign Out 161 | */ 162 | public function signOut( $redirect = '/' ) : void 163 | { 164 | $_SESSION = []; 165 | session_unset(); 166 | 167 | Site::redirect( $redirect ); 168 | } 169 | 170 | /** 171 | * Edit User 172 | */ 173 | public function editUser( array $user ) : void 174 | { 175 | Str::validate( $user['name'], true, 255 ); 176 | Email::validate( $user['email'] ); 177 | 178 | $user_id = ( int )$user['id']; 179 | $user_name = Str::clean( $user['name'] ); 180 | $user_email = Str::clean( $user['email'] ); 181 | 182 | if ( ! empty( $user_name ) && ! empty( $user_email ) ) { 183 | $sql = Sql::query() 184 | ->select( 'user_name, user_email' ) 185 | ->from( 'users' ) 186 | ->where( 'user_name = :name' ) 187 | ->or( 'user_email = :email' ) 188 | ->get(); 189 | 190 | $data = array( 191 | 'name' => $user_name, 192 | 'email' => $user_email, 193 | ); 194 | 195 | $stmt = $this->db->prepare( $sql ); 196 | $this->bind( $stmt, $data ); 197 | $stmt->execute(); 198 | 199 | if ( ! empty( $stmt->fetch( PDO::FETCH_ASSOC ) ) ) { 200 | $errors[] = 'Username or Email already exists.'; 201 | throw new ValidationException( $errors ); 202 | } 203 | } 204 | 205 | if ( User::author( $user['id'] ) ) { 206 | $_SESSION['user']['name'] = $user_name; 207 | $_SESSION['user']['email'] = $user_email; 208 | } 209 | 210 | $sql = Sql::query() 211 | ->update( 'users' ) 212 | ->set( 'user_name = :name, user_email = :email' ) 213 | ->where( 'user_id = :id' ) 214 | ->get(); 215 | 216 | $data = array( 217 | 'id' => $user_id, 218 | 'name' => $user_name, 219 | 'email' => $user_email, 220 | ); 221 | 222 | $stmt = $this->db->prepare( $sql ); 223 | $this->bind( $stmt, $data ); 224 | $stmt->execute(); 225 | 226 | Site::redirect( '/users/edit/' . $user_id ); 227 | } 228 | 229 | /** 230 | * Change User Role 231 | */ 232 | public function changeUserRole( array $user ) : void 233 | { 234 | Str::validate( $user['role'], true, 20 ); 235 | 236 | $user_id = ( int )$user['id']; 237 | $user_role = in_array( $user['role'], array( 'admin', 'editor', 'user' ) ) ? $user['role'] : 'user'; 238 | 239 | $sql = Sql::query() 240 | ->update( 'users' ) 241 | ->set( 'user_role = :role' ) 242 | ->where( 'user_id = :id' ) 243 | ->get(); 244 | 245 | $data = array( 246 | 'id' => $user_id, 247 | 'role' => $user_role, 248 | ); 249 | 250 | $stmt = $this->db->prepare( $sql ); 251 | $this->bind( $stmt, $data ); 252 | $stmt->execute(); 253 | 254 | Site::redirect( '/users/edit/' . $user_id ); 255 | } 256 | 257 | /** 258 | * Change User Password 259 | */ 260 | public function changeUserPassword( array $user ) : void 261 | { 262 | Str::validate( $user['old-password'], true, 255 ); 263 | Str::validate( $user['new-password'], true, 255 ); 264 | Str::validate( $user['confirm-new-password'], true, 255 ); 265 | 266 | $user_id = ( int )$user['id']; 267 | $user_old_password = Str::clean( $user['old-password'] ); 268 | $user_new_password = Str::clean( $user['new-password'] ); 269 | $user_confirm_new_password = Str::clean( $user['confirm-new-password'] ); 270 | 271 | $sql = Sql::query() 272 | ->select( 'user_password' ) 273 | ->from( 'users' ) 274 | ->where( 'user_id = :id' ) 275 | ->get(); 276 | 277 | $data = array( 278 | 'id' => $user_id, 279 | ); 280 | 281 | $stmt = $this->db->prepare( $sql ); 282 | $this->bind( $stmt, $data ); 283 | $stmt->execute(); 284 | 285 | $user = $stmt->fetch( PDO::FETCH_ASSOC ); 286 | 287 | if ( password_verify( $user_old_password, $user['user_password'] ) ) { 288 | if ( $user_new_password == $user_confirm_new_password ) { 289 | $user_password = password_hash( $user_new_password, PASSWORD_DEFAULT ); 290 | 291 | $sql = Sql::query() 292 | ->update( 'users' ) 293 | ->set( 'user_password = :password' ) 294 | ->where( 'user_id = :id' ) 295 | ->get(); 296 | 297 | $data = array( 298 | 'id' => $user_id, 299 | 'password' => $user_password, 300 | ); 301 | 302 | $stmt = $this->db->prepare( $sql ); 303 | $this->bind( $stmt, $data ); 304 | $stmt->execute(); 305 | 306 | $this->signOut( '/users/sign_in' ); 307 | } else { 308 | $errors[] = 'New password does not match.'; 309 | } 310 | } else { 311 | $errors[] = 'Old password is entered incorrectly.'; 312 | } 313 | 314 | if ( ! empty( $errors ) ) { 315 | throw new ValidationException( $errors ); 316 | } 317 | } 318 | 319 | /** 320 | * Delete User 321 | */ 322 | public function deleteUser( int $user_id ) : void 323 | { 324 | $sql = Sql::query() 325 | ->delete() 326 | ->from( 'users' ) 327 | ->where( 'user_id = :id' ) 328 | ->get(); 329 | 330 | $data = array( 331 | 'id' => $user_id, 332 | ); 333 | 334 | $stmt = $this->db->prepare( $sql ); 335 | $this->bind( $stmt, $data ); 336 | $stmt->execute(); 337 | 338 | if ( User::author( $user_id ) ) { 339 | $this->signOut(); 340 | } elseif ( User::role( array( 'admin' ) ) ) { 341 | Site::redirect( '/users' ); 342 | } 343 | } 344 | 345 | } 346 | -------------------------------------------------------------------------------- /app/views/footer.php: -------------------------------------------------------------------------------- 1 | 10 | 11 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |