diff --git a/README.md b/README.md index 542fa54..621e2c7 100644 --- a/README.md +++ b/README.md @@ -11,48 +11,58 @@ A small, simple PHP MVC framework skeleton that encapsulates a lot of features surrounded with powerful security layers. -miniPHP is a very simple application, useful for small projects, helps to understand the PHP MVC skeleton, know how to authenticate and authorize, encrypt data and apply security concepts, sanitization and validation, make Ajax calls and more. +miniPHP is a very simple application, useful for small projects, helps to understand the PHP MVC skeleton, know how to authenticate and authorize, encrypt data and apply security concepts, sanitization and validation, make ajax calls and more. It's not a full framework, nor a very basic one but it's not complicated. You can easily install, understand, and use it in any of your projects. -It's indented to remove the complexity of the frameworks. I've been digging into the internals of some frameworks for a while. Things like authentication, and authorization that you will see here is not something I've invented from the scratch, Some of it, is aggregation of concepts applied already be frameworks, but, built in a much simpler way, So, you can understand it, and take it further. +It's indented to remove the complexity of the frameworks. Things like routing, authentication, authorization, manage user session and cookies, and so on are not something I've invented from the scratch, however, they are aggregation of concepts already implemented in other frameworks, but, built in a much simpler way, So, you can understand it, and take it further. If you need to build bigger application, and take the advantage of most of the features available in frameworks, you can see [CakePHP](http://cakephp.org/), [Laravel](http://laravel.com/), [Symphony](http://symfony.com/). -Either way, I believe it's important to understand the PHP MVC skeleton, and know how to authenticate and authorize, learn about security issues and how can you defeat against, and how to build you own features([News Feed, Posts](#newsfeed-posts-comments), [Files](#files), ...etc) and merge them with these core features. +Either way, It's important to understand the PHP MVC skeleton, and know how to authenticate and authorize, learn about security issues and how can you defeat against, and how to build you own application using the framework. ## Index + [Installation](#installation) -+ [Routing, Controller, & View](#life-cycle) -+ [Components](#components) -+ [Authentication](#authentication) - - [Session](#session) - - [Cookies](#cookies) -+ [Authorization](#authorization) -+ [Security](#security) - - [HTTP Methods](#http-method) - - [Domain Validation](#referer) - - [Form Tampering](#form-tampering) - - [CSRF](#csrf) - - [htaccess](#htaccess) -+ [Login](#login) - - [User Verification](#user-verification) - - [Forgotten Password](#forgotten-password) - - [Brute-Force attack](#brute-force) - - [Captcha](#captcha) - - [Block IP Addresses](#block-ip) -+ [Database](#database) -+ [Encryption](#encryption) -+ [Validation](#validation) -+ [Error](#error) -+ [Logger](#logger) -+ [Email](#email) -+ [News Feed & Posts & Comments](#newsfeed-posts-comments) -+ [Files](#files) -+ [Profile](#profile) -+ [Notifications](#notifications) -+ [Users](#users) -+ [Backups](#backups) ++ [How It Works?](#how-it-works) + + [Routing](#routing) + + [Controller](#controller) + + [Components](#components) + + [Authentication](#authentication) + - [Session](#session) + - [Cookies](#cookies) + + [Authorization](#authorization) + + [Security](#security) + - [HTTP Methods](#http-method) + - [Domain Validation](#referer) + - [Form Tampering](#form-tampering) + - [CSRF](#csrf) + - [htaccess](#htaccess) + + [Views](#views) + + [Models](#models) + + [JavaScript & Ajax](#js) + + [Login](#login) + - [User Verification](#user-verification) + - [Forgotten Password](#forgotten-password) + - [Brute-Force attack](#brute-force) + - [Captcha](#captcha) + - [Block IP Addresses](#block-ip) + + [Database](#database) + + [Encryption](#encryption) + + [Validation](#validation) + + [Error](#error) + + [Logger](#logger) + + [Email](#email) ++ [How to Use?](#how-to-use) + + [Intro](#intro) + + [ToDo Application](#todo) ++ [Additional Features](#features) + + [User Profile](#profile) + + [Files](#files) + + [News Feed & Posts & Comments](#newsfeed-posts-comments) + + [Admin](#admin) + + [Notifications](#notifications) + + [Report Bugs](#bugs) + + [Backups](#backups) + [Support](#support) + [Contribute](#contribute) + [Dependencies](#dependencies) @@ -77,9 +87,8 @@ Steps: + Email: user@demo.com + Password: 12345 -### Routing, Controller, & View - -####Routing +## How It Works? +### Routing Whenever you make a request to the application, it wil be directed to index.php inside public root folder. So, if you make a request: ```http://localhost/miniPHP/User/update/412 ```. This will be splitted and translated into @@ -88,53 +97,26 @@ So, if you make a request: ```http://localhost/miniPHP/User/update/412 ```. This + Action Method: update + Arguemtns to action method: 412 -**NOTE** - In fact, htaccess splits everything comes after ```http://localhost/miniPHP ``` and adds it to the URL as querystring argument. So, this request will be converted to: ```http://localhost/miniPHP?url='User/update/412' ```. Then ```App``` Class, Inside ```splitUrl()```, will split the query string ```$_GET['url']``` intro controller, action method, and any passed arguments to action method. -In ```App``` Class, Inside ```__construct()```, will instantiate an object from controller class, and make a call to action method. +In ```App``` Class, Inside ```__construct()```, it will instantiate an object from controller class, and make a call to action method, passing any arguments if exist. + +### Controller -####Controller After the ```App``` Class intantiates controller object, The constructor of ```Controller```Class will trigger 3 consective events/methods: -1. ```initialize()```: Use it to load components, -2. ```beforeAction()```: Any logic before calling controller's action method +1. ```initialize()```: Use it to load components +2. ```beforeAction()```: Perform any logic actions before calling controller's action method 3. ```triggerComponents()```: Trigger startup() method of loaded components The constructor of ```Controller``` Class **shouldn't** be overridden, instead you can override the 3 methods above in extending classes, and constructor of ```Controller``` Class will call them one after another. After the constructor finishes it's job, Then, the requested action method will be called, and arguments will be passed(if any) -####Views -Inside the action method you can make a call to model to get some data, and/or render pages inside views directory - -```php - //Inside UserController - - public function index(){ - - //render full page with layout(header and footer) - echo $this->view->renderWithLayouts(VIEWS_PATH . "layout/", VIEWS_PATH . 'index.php'); - - //render page - echo $this->view->render(VIEWS_PATH . 'index.php'); - } - - public function updateProfileInfo(){ - - $fileData = $this->request->data("file"); - $image = $this->user->updateProfilePicture(Session::getUserId(), $fileData); - - //json_encode() for ajax calls - echo $this->view->JSONEncode(array("data" => ["src" => PUBLIC_ROOT . "img/profile_pictures/" . $image["basename"]])); - } - -``` - ### Components -Components are pretty much like backbone for controller. They provide reusable logic to be used as part of the controller. Things like Authentication, Authorization, Form Tampering, and Validate CSRF Tokens are implemented inside Components. +Components are pretty much like backbone for controller. They provide reusable logic to be used as part of the controller. Authentication, Authorization, Form Tampering, and Validate CSRF Tokens are implemented inside Components. It's better to pull these pieces of logic out of controller class, and keep all various tasks and validations inside these Components. @@ -175,9 +157,9 @@ Do you have the right to access or to perform X action?. The AuthComponent takes This method will be called by default at the end of controller constructor. What you need to do is to return ``` boolean ``` value. -So, for example: +So, for example, in order to check if current user is admin or not, you would do something like this: ```php - //Inside AdminController + // AdminController public function isAuthorized(){ @@ -192,38 +174,36 @@ So, for example: If you want to take it further and apply some permission rules, There is a powerful class called ``` Permission ``` responsible for defining permission rules. This class allows you to define "Who is allowed to perform specific action method on current controller". -Example on that: +So, for example, in order to allow admins to perform any action on notes, while normal users can only edit their notes: ```php - //Inside FilesController - + // NotesController + public function isAuthorized(){ $action = $this->request->param('action'); - $role = Session::getUserRole(); - $resource = "files"; + $role = Session::getUserRole(); + $resource = "notes"; - //only for admins - //they are allowed to all actions on $resource + // only for admins + // they are allowed to perform all actions on $resource Permission::allow('admin', $resource, ['*']); - //for normal users, they can delete only if the current user is the owner - //Permission class will then check if the current user is owner or not - Permission::allow('user', $resource, ['delete'], 'owner'); + // for normal users, they can edit only if the current user is the owner + Permission::allow('user', $resource, ['edit'], 'owner'); - $fileId = $this->request->data("file_id"); + $noteId = $this->request->data("note_id"); $config = [ "user_id" => Session::getUserId(), - "table" => "files", - "id" => $fileId + "table" => "notes", + "id" => $noteId ]; - //providing the current user's role, $resource, action method, and some configuration data - //Permission class will check based on rules defined above and return boolean value - return Permission::check($role, $resource, $action, $config); + // providing the current user's role, $resource, action method, and some configuration data + // Permission class will check based on rules defined above and return boolean value + return Permission::check($role, $resource, $action, $config); } - ``` -Now, you can check authorization based on user's role, and for each action method. +Now, you can check authorization based on user's role, resource, and for each action method. ### Security The SecurityComponent takes care of various security tasks and validation. @@ -232,60 +212,51 @@ The SecurityComponent takes care of various security tasks and validation. It's important to restrict the request methods. As an example, if you have an action method that accepts form values, So, ONLY POST request will be accepted. The same idea for Ajax, GET, ..etc. -You can do this inside ```initialize()``` method, or personally I prefer to keep it inside ```beforeAction() ``` method. These methods are inherited from ```Controller``` Class. +You can do this inside ```initialize()``` method, or keep it inside ```beforeAction() ``` method. These methods are inherited from ```Controller``` Class. ```php - //Inside FilesController + // NotesController public function beforeAction(){ parent::beforeAction(); - $action = $this->request->param('action'); $actions = ['create', 'delete']; $this->Security->requireAjax($actions); $this->Security->requirePost($actions); - } - - ``` Also if you require all requests to be through secured connection, you can configure whole controller, and specific actions to redirect all requests to HTTPS instead of HTTP. ```php - //Inside FilesController + // NotesController public function beforeAction(){ parent::beforeAction(); - $action = $this->request->param('action'); $actions = ['create', 'delete']; $this->Security->requireSecure($actions); - } - ``` #### Domain Validation Check & validate if request is coming from the same domain. Although they can be faked, It's good to keep them as part of our security layers. - #### Form Tampering -validate submitted form coming from POST request. In case of Ajax request, you can append data along with form values, these values will be validated too. +Validate submitted form coming from POST request. The pitfall of this method is you need to define the expected form fields, or data that will be sent with POST request. + +By default, the framework will make sure the CSRF token is passed with the form fields, if you don't want to validate the CSRF token, then assign ```validateCsrfToken``` to ```false``` as show in the example. + Unknown fields cannot be added to the form. + Fields cannot be removed from the form. -The pitfall of this method is you need to define the expected form fields, or data that will be sent with POST request. - - ```php - //Inside FilesController + // NotesController public function beforeAction(){ @@ -299,14 +270,15 @@ The pitfall of this method is you need to define the expected form fields, or da switch($action){ case "create": - $this->Security->config("form", [ 'fields' => ['file']]); + $this->Security->config("form", [ 'fields' => ['note_text']]); break; case "delete": - $this->Security->config("form", [ 'fields' => ['file_id']]); + // if you don't want to validate the CSRF Token, then assign 'validateCsrfToken' to false + // $this->Security->config("validateCsrfToken", false); + $this->Security->config("form", [ 'fields' => ['note_id']]); break; } } - ``` #### CSRF Tokens @@ -314,10 +286,9 @@ CSRF Tokens are important to validate the submitted forms, and to make sure they They are valid for a certain duration(>= 1 day), then it will be regenerated and stored in user's session. -Here, CSRF tokens are generated per session. You can either add a hidden form field with ``` name = "csrf_token" value = "= Session::generateCsrfToken(); ?>" ``` - -But, Since all form requests here are made through Ajax calls, ```Session::generateCsrfToken()``` will be assigned to JS variable and will be sent with every ajax request, Instead of adding hidden form value to every form. +CSRF tokens are generated per session. You can either add a hidden form field with ``` name = "csrf_token" value = "= Session::generateCsrfToken(); ?>" ``` +In case of Ajax calls, assign ```Session::generateCsrfToken()``` to a JavaScript varibale in the [footer.php](https://github.com/OmarElGabry/miniPHP/blob/master/app/views/layout/default/footer.php), which you can send with every ajax request. #### htacess @@ -325,9 +296,102 @@ But, Since all form requests here are made through Ajax calls, ```Session::gener + Block directory traversal/browsing + Deny access to app directory(Althought it's not needed if you setup the application correctly) +### Views + +Inside the action method you can make a call to model to get some data, and/or render pages inside views directory + +```php + // NotesController + + public function index(){ + + // render full page with layout(header and footer) + echo $this->view->renderWithLayouts(Config::get('VIEWS_PATH') . "layout/default/", Config::get('VIEWS_PATH') . 'notes/index.php'); + + // render page without layout + echo $this->view->render(Config::get('VIEWS_PATH') . 'notes/note.php'); + + // render using json_encode() for ajax calls + $html = $this->view->render(Config::get('VIEWS_PATH') . 'notes/note.php'); + echo $this->view->JSONEncode(array("data" => $html)); + } +``` + +### Models +> In MVC, the model represents the information (the data) and the business rules; the view contains elements of the user interface such as text, form inputs; and the controller manages the communication between the model and the view. +[Source](http://www.yiiframework.com/doc/guide/1.1/en/basics.mvc) + +All operations like create, delete, update, and validation are implemented in model classes. + +```php + // NotesController + + public function create(){ + + // get content of note submitted to a form + // then pass the content along with the current user to Note class + $content = $this->request->data("note_text"); + $note = $this->note->create(Session::getUserId(), $content); + + if(!$note){ + echo $this->view->renderErrors($this->note->errors(), false); + }else{ + Redirector::to(PUBLIC_ROOT . "Notes"); + } + } +``` + +**In Notes Model** + +```php + // Notes Model + + public function create($userId, $content){ + + // using validation class(see below) + $validation = new Validation(); + if(!$validation->validate(['Content' => [$content, "required|minLen(4)|maxLen(300)"]])) { + $this->errors = $validation->errors(); + return false; + } + + // using database class to insert new note + $database = Database::openConnection(); + $query = "INSERT INTO notes (user_id, content) VALUES (:user_id, :content)"; + $database->prepare($query); + $database->bindValue(':user_id', $userId); + $database->bindValue(':content', $content); + $database->execute(); + + if($database->countRows() !== 1){ + throw new Exception("Couldn't create note"); + } + + return true; + } +``` + +### JavaScript & Ajax +In order to send request and recieve a respond, you may depend on Ajax calls to do so. This framework is heavily depends on ajax requests to perform actions, but, you still can do the same thing for normal requests with just small tweaks. + +#### In _public/main.js_ + +**globalVars** object is assigned to key-value pairs in [footer.php](https://github.com/OmarElGabry/miniPHP/blob/master/app/views/layout/default/footer.php). These key-value pairs can be initialized in server-side code in Controller ```Controller::addVar()```, which will be assigned then to _globalVars_ object in footer.php. + +**ajax** namespace has two main functions for sending ajax request. One for normal ajax calls, and another for for uploading files. + +**helpers** namespace has variety of functions display errors, serialize, redirect, encodeHTML, and so on + +**app** namespace is used to initalize the whole javascript events for the current page + +**events** namespace is used to declare all of events that may occure, like when user clicks on a link to create, delete or update. + ### Login +Using the framework, you would probably do login, register, and logout. These actions are implemented in _app/models/Login_ & _app/controllers/LoginController_. In most situations, you won't need to modify anything related to login actions, just understand the behaviour of the framework. + +**NOTE** -**NOTE** If you don't have SSL, you would better want to encrypt data manually at Client Side, If So, read [this](http://stackoverflow.com/questions/3715920/about-password-hashing-system-on-client-side) and also [this](http://stackoverflow.com/questions/4121629/password-encryption-at-client-side?lq=1) +If you don't have SSL, you would better want to encrypt data manually at Client Side, If So, read [this](http://stackoverflow.com/questions/3715920/about-password-hashing-system-on-client-side) and also [this](http://stackoverflow.com/questions/4121629/password-encryption-at-client-side?lq=1) #### User Verification Whenever the user registers, An email will be sent with token concatenated with encrypted user id. This token will be expired after 24 hour. @@ -336,7 +400,7 @@ It's much better to expire these tokens, and re-use the registered email if they **Passwords** are hashed using the latest algorithms in PHP v5.5 ```php -$hashedPassword = password_hash($password, PASSWORD_DEFAULT, array('cost' => "10")); +$hashedPassword = password_hash($password, PASSWORD_DEFAULT, array('cost' => Config::get('HASH_COST_FACTOR'))); ``` #### Forgotten Password @@ -358,7 +422,7 @@ Solution: - Min Length is 8 characters #### Captcha -CAPTCHAs are particularly effective in preventing automated logins. I am using [Captcha](https://github.com/Gregwar/Captcha) an awesome PHP Captcha library. +CAPTCHAs are particularly effective in preventing automated logins. Using [Captcha](https://github.com/Gregwar/Captcha) an awesome PHP Captcha library. #### Block IP Address Blocking IP Addresses is the last solution to think about. IP Address will be blocked if the same IP failed to login multiple times(>=10) using different credentials(emails). @@ -372,19 +436,19 @@ PHP Data Objects (PDO) is used for preparing and executing database queries. Ins - Don't use _root_ user, Create a new one instead. - Always assign limited privileges to current database user - ```SELECT, INSERT, UPDATE, DELETE ``` are enough for users - - For backups, You need to use another database user with more privileges. These privileges needed for [mysqldump](https://dev.mysql.com/doc/refman/5.1/en/mysqldump.html) are mentioned in ```Admin``` Class. + - For backups, It's recommended to use another database user with more privileges. These privileges needed for [mysqldump](https://dev.mysql.com/doc/refman/5.1/en/mysqldump.html) are mentioned in ```Admin``` Class. + UTF-8 - For complete UTF-8 support, you need to use ```utf8mb4 ```on database level. - MySQL’s ```utf8``` charset only store UTF-8 encoded symbols that consist of one to three bytes. But, It can't for symbols with four bytes. - - Here, I am using ```utf8```. But, if you want to upgrade to ```utf8mb4 ``` follow these links: + - Here, charset is ```utf8```. But, if you want to upgrade to ```utf8mb4 ``` follow these links: - [Link 1](https://mathiasbynens.be/notes/mysql-utf8mb4) - [Link 2](https://dev.mysql.com/doc/refman/5.5/en/charset-unicode-upgrading.html) - Don't forget to change **charset** in _app/config/config.php_ to ```utf8mb4 ``` ### Encryption -``` Encryption ``` Class is responsible for encrypting and decryption of data. Encryption is applied to things like cookies, User Id, Post Id, ..etc. +``` Encryption ``` Class is responsible for encrypting and decryption of data. Encryption is applied to things like cookies, User ID, Post ID, ..etc. -Encrypted strings are authenticated and different every time you encrypt. +Encrypted strings are authenticated and they are different every time you encrypt. ### Validation Validation is a small library for validating user inputs. All validation rules are inside ``` Validation ``` Class. @@ -394,22 +458,23 @@ Validation is a small library for validating user inputs. All validation rules a $validation = new Validation(); -//there are default error messages for each rule -//but, you still can define your custom error message +// there are default error messages for each rule +// but, you still can define your custom error message $validation->addRuleMessage("emailUnique", "The email you entered is already exists"); if(!$validation->validate([ - "User Name" => [$name, "required|alphaNumWithSpaces|minLen(4)|maxLen(30)"], + "User Name" => [$name, "required|alphaNumWithSpaces|minLen(4)|maxLen(30)"], "Email" => [$email, "required|email|emailUnique|maxLen(50)"], 'Password' => [$password,"required|equals(".$confirmPassword.")|minLen(6)|password"], 'Password Confirmation' => [$confirmPassword, 'required']])) { - var_dump($validation->errors()); + var_dump($validation->errors()); } ``` ### Error -``` Error``` Class is responsible for handling all exceptions and errors. It will use [Logger](#logger) to log error. Error reporting is turned off by default, because every error will be logged and saved in _app/logs/log.txt_. +``` Error``` Class is responsible for handling all exceptions and errors. It will use [Logger](#logger) to log errors. +Error reporting is turned off by default, because every error will be logged and saved in _app/logs/log.txt_. If error encountered or exception was thrown, the application will show System Error(500). @@ -418,72 +483,438 @@ If error encountered or exception was thrown, the application will show System E + Turn Off log errors if not needed ### Logger -A place where you can log, write any failures, errors, exceptions, or any other malicious actions or attacks. +A place where you can log anything and save it to _app/log/log.txt_. You can write any failures, errors, exceptions, or any other malicious actions or attacks. ```php Logger::log("COOKIE", self::$userId . " is trying to login using invalid cookie", __FILE__, __LINE__); ``` ### Email -Emails are sent using [PHPMailer](https://github.com/PHPMailer/PHPMailer) via SMTP, another awesome library for sending emails. You shouldn't use ```mail()``` function of PHP. +Emails are sent using [PHPMailer](https://github.com/PHPMailer/PHPMailer) via SMTP, another library for sending emails. You shouldn't use ```mail()``` function of PHP. -**NOTE** You need to configure your SMTP account data in _app/config/config.php_. -**But**, If you don't have SMTP account, then you save emails in _app/logs/log.txt_, to do that check this line of [code](https://github.com/OmarElGabry/miniPHP/blob/master/app/core/Email.php#L78). +**NOTE** You need to configure your SMTP account data in _app/config/config.php_. **But**, If you don't have SMTP account, then you save emails in _app/logs/log.txt_, to do that check this line of [code](https://github.com/OmarElGabry/miniPHP/blob/master/app/core/Email.php#L78). -### News Feed & Posts & Comments +## How to Use? +### Intro +miniPHP framework mostly depends on ajax calls, but, you will do almost the same thing in case normal request. In this section, I'll go through how to build a quick application on top of miniPHP framework with & without ajax calls. + +### ToDo Application +Let's say you want to build a simple ToDo List, and users can udate their profile information, and change their profile picture. + +(1) If you followed the setup steps(see above), you shouldn't have any problem with creating initial user accounts, and do login/logout. + +(2) Create a table with id as INT, content VARCHAR, user_id as Foreign Key to ```users``` table + +```sql +CREATE TABLE `todo` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `user_id` int(11) NOT NULL, + `content` varchar(512) NOT NULL, + PRIMARY KEY (`id`), + FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci; +``` + +(3) Create TodoController + +Create a file called ```TodoController.php``` inside _app/controllers_ + +```php + +class TodoController extends Controller{ + + // override this method to perform any logic before calling action method as explained above + public function beforeAction(){ + + parent::beforeAction(); + + // define the actions in this Controller + $action = $this->request->param('action'); + + // restrict the request to action methods + // $this->Security->requireAjax(['create', 'delete']); + $this->Security->requirePost(['create', 'delete']); + + // define the expected form fields for every action if exist + switch($action){ + case "create": + // you can exclude form fields if you don't care if they were sent with form fields or not + $this->Security->config("form", [ 'fields' => ['content'], 'exclude' => ['submit']]); + break; + case "delete": + // if you don't want to validate the CSRF Token, then assign 'validateCsrfToken' to false + // $this->Security->config("validateCsrfToken", false); + $this->Security->config("form", [ 'fields' => ['todo_id'], 'exclude' => ['submit']]); + break; + } + } + + public function index(){ + + $html = $this->view->renderWithLayouts(Config::get('VIEWS_PATH') . "layout/todo/", Config::get('VIEWS_PATH') . 'todo/index.php'); + + // display todo list + echo $html; + } + + public function create(){ + + $content = $this->request->data("content"); + $todo = $this->todo->create(Session::getUserId(), $content); + + if(!$todo){ + + // in case of normal post request + Session::set('error', $this->todo->errors()); + Redirector::to(PUBLIC_ROOT . "Todo"); + + // in case of ajax + // echo $this->view->renderErrors($this->todo->errors()); + + }else{ + + // in case of normal post request + Session::set('success', "Todo has been created"); + Redirector::to(PUBLIC_ROOT . "Todo"); + + // in case of ajax + // echo $this->view->JSONEncode(array("success" => "Todo has been created")); + } + } + + public function delete(){ + + $todoId = Encryption::decryptIdWithDash($this->request->data("todo_id")); + $this->todo->delete($todoId); + + // in case of normal post request + Session::set('success', "Todo has been deleted"); + Redirector::to(PUBLIC_ROOT . "Todo"); + + // in case of ajax + // echo $this->view->JSONEncode(array("success" => "Todo has been deleted")); + } + + public function isAuthorized(){ + + $action = $this->request->param('action'); + $role = Session::getUserRole(); + $resource = "todo"; + + // only for admins + Permission::allow('admin', $resource, ['*']); + + // only for normal users + Permission::allow('user', $resource, ['delete'], 'owner'); + + $todoId = $this->request->data("todo_id"); + + if(!empty($todoId)){ + $todoId = Encryption::decryptIdWithDash($todoId); + } + + $config = [ + "user_id" => Session::getUserId(), + "table" => "todo", + "id" => $todoId]; + + return Permission::check($role, $resource, $action, $config); + } +} +``` + +(4) Create Note Model Class called ```Todo.php``` in _app/models_ + +```php +class Todo extends Model{ + + public function getAll(){ + + $database = Database::openConnection(); + $query = "SELECT todo.id AS id, users.id AS user_id, users.name AS user_name, todo.content "; + $query .= "FROM users, todo "; + $query .= "WHERE users.id = todo.user_id "; + + $database->prepare($query); + $database->execute(); + $todo = $database->fetchAllAssociative(); + + return $todo; + } + + public function create($userId, $content){ + + // using validation class + $validation = new Validation(); + if(!$validation->validate(['Content' => [$content, "required|minLen(4)|maxLen(300)"]])) { + $this->errors = $validation->errors(); + return false; + } + + // using database class to insert new todo + $database = Database::openConnection(); + $query = "INSERT INTO todo (user_id, content) VALUES (:user_id, :content)"; + $database->prepare($query); + $database->bindValue(':user_id', $userId); + $database->bindValue(':content', $content); + $database->execute(); + + if($database->countRows() !== 1){ + throw new Exception("Couldn't create todo"); + } + + return true; + } + + public function delete($id){ + + $database = Database::openConnection(); + $database->deleteById("todo", $id); -Think of News Feed as tweets in twitter, and in Posts like when you open an Issue in Github. + if($database->countRows() !== 1){ + throw new Exception ("Couldn't delete todo"); + } + } + } +``` -They are implemented to be merged with the core features mentioned above. Also apply some concepts like Pagination, How can you edit & delete in place(secured way), How can you manage permissions for who can create, edit, update and delete, and so forth. +(5) Inside _views/_ -You will see each newsfeed comes with and encrypted id like: ```feed-51b2cfa```. +(a) Create ```header.php``` & ```footer.php``` inside _views/layout/todo_ + +```php + + + +
+ + + + + + + += $this->autoLinks($this->encodeHTMLWithBR($todo["content"])); ?>
+ + + + + + +