This is an intermediate level post on how I created a CMS in PHP using custom framework. main image

Table of contents

Preface

What I learned through this project

  • Build Fully Object Oriented Website From Scratch
  • Practical Learning Design Patterns like :
    • Dependency Injection
    • Factory
    • MVC
    • Register
    • Singleton
    • Front Controller
  • How To Use Namespaces
  • How To Use Interfaces
  • Auto loading Classes “The Classic Way”
  • Using Clean URLs
  • Database Builder
  • Documentation
  • Using Ajax with PHP In
    • Logging in
    • Creating/updating forms

Test it yourself

Get the source code here

Setting up the environment

You’ll need

  • Apache web server
  • MySQL database
  • PHP

You can use xampp to set all these for you.

  • Good text editor or IDE (I’d strongly suggest Netbeans-PHP).

Setting up database:

Head to PHPmyadmin and create a new database, name it whatever you want .. Then Get into your database and head to import tab.

click on select to select the SQL file.

Open the project directory , then open Z_other_files sub-directory , and select blog.sql file to import.

Being done, You will need to edit config.php file which located in the main directory.

<?php
return [
    'server' => 'localhost',
    'dbname' => 'blog',
    'user'   => 'root',
    'pass'   => 'root'
];

Change those configurations to get it working.

This project is an application on this tutorial (in Arabic)

Modifications

Admin permissions

When I was about to finish that tutorial, I realised that the permissions system is weak and needs some improvements.

These are the top weaknesses

  • Any admin could delete the site owner account!
  • The site owner and admins could easily view/edit any account’s password and private info !!
  • Any admin could delete all User-groups including Admins , that would cause the registration and login system to fail, And even the super admin wouldn’t be able to fix that unless getting into database and starting a full rebuilding the missed data manually.

I started to make it more sensible, but because the lack of spare time, I made it a small improvement.

And here’s the Access controller for admin routes.

<?php

namespace appControllersAdmin;

use SystemController;

class AccessController extends Controller
{

    /**
     * Check user permissions
     *
     * @return void
     */
    public function index()
    {
        $loginModel   = $this->load->model('Login');
        $ignored      = ['/admin/login', '/admin/login/submit', '/404'];
        $currentRoute = $this->app->route->getCurrentRoute();
        if (($isNotLogged  = !$loginModel->isLogged()) && !in_array($currentRoute, $ignored)) {
            return $this->url->redirect('/admin/login');
        }
        if ($isNotLogged) {
            return FALSE;
        }
        $user = $loginModel->user();
        $ugm  = $this->load->model('UsersGroups');
        $ug   = $ugm->get($user->ugid);
        if (!in_array($currentRoute, $ug->page)) {
            return $this->url->redirect('/404');
        }
    }

}

Super admin permissions

  • Editing Users groups permissions (Cannot delete Admins nor Users groups).
  • Add a new users group and edit or delete them.
  • Change his own private data (without status or user-group).
  • Change other users’ status and user-group only (Can’t change their private data).
  • Deleting any other user.

Regular admin/moderator persmissions

  • Change his own private data (without status or user-group).
  • Change normal users’ status (Can’t change their private data) except admins.
  • Deleting other users except admins.
  • add/edit/delete posts, categories and ads.
  • delete comments.
  • Edit normal users status and delete them.
  • Change site settings (These settings have small UI effects, In the future I’ll tune them).

Workflow

There were some logical problems But I’ve fixed them like below:

  1. Sequences:
  • When Admin deletes a category, It should automatically deletes its related posts.
  • When Admin deletes a post, It should automatically deletes its related comments.
  • When disabling a category, It’s related posts shouldn’t be displayed in front end.
  • When disabling a user, his posts and comments shouldn’t be displayed in front end.
  • Any disabled user, category, post or advertisement will only be visible to admins in control panel.
  1. Dealing with uploaded images:
  • When uploading a new image for any purpose (post, user,ad ,.. etc) , The old image should be deleted first before uploading the new one (except default avatar images as they are not being deleted).
  1. Forms:
  • Converted from modal form to classic static form in Posts and ads sections, The reason is to implement a basic text editor, and date picker in easy way.
  1. Posts routes
  • Remade posts route from /post/title/id to this /post/id. Because any user could just change post’s ID in address bar, that would result in displaying the new post based on its ID, but the old title would remain the same.
  1. 404 Page:
  • Made it more human readable , with a link embedded in a text and picture that will lead you to the page you came from.

Authentication

Register

When a user register to the site .. they enter their username, email and password . Username and email must be unique, Password must be between 8-128 characters.

Behind the scenes: User input values are going to be validated, If it passes, He will be given a random 1 of 10 egg photos as avatar , like Twitter :) … Then they will be redirected to Login page.

Login

Works as expected, if login went successful, user will be redirected to home page.

Here’s the function responsible for the login process:


    /**
     * Submit Login form
     *
     * @return mixed
     */
    public function submit()
    {
        if ($this->isValid()) {
            $email      = $this->request->post('email');
            $pass       = $this->request->post('password');
            $loginModel = $this->load->model('Login');
            if (!$loginModel->isValidLogin($email, $pass)) {
                $json['errors'] = 'Invalid email or password';
                return $this->json($json);
            }
            $logged_in_user = $loginModel->user();
            if ($logged_in_user->status === 'disabled') {
                $json['errors'] = 'Please confirm your email by openeing the link we've sent to';
                return $this->json($json);
            }
            // save login data in cookie and session
            $this->cookie->set('login', $logged_in_user->code);
            $this->session->set('login', $logged_in_user->code);
            $json               = [];
            $json['success']    = 'Welcome Back ' . $logged_in_user->name;
            $json['redirectTo'] = $this->url->link('/');
            return $this->json($json);
        } else {
            $json           = [];
            $json['errors'] = $this->validator->flatMsg();
            return $this->json($json);
        }
    }

Added features

Tags

You can find post’s tags in the bottom , when you click a tag, you will be redirected to a page like /tag/foo contains all posts related to that tag.

The tag controller is just plain and simple :)

<?php

namespace appControllersBlog;

use SystemController;

class TagController extends Controller
{

    /**
     * Display Tag Page
     *
     * @param string name
     * @param int $id
     * @return mixed
     */
    public function index($tag)
    {
        $postsModel = $this->load->model('Posts');
        $posts      = $postsModel->getPostsByTag($tag);
        if (!$posts) {
            return $this->url->redirect('/404');
        }
        $this->blogLayout->title($tag);
        if ($this->pagination->page() != 1) {
            // then just redirect him to the first page of the category
            // regardless there is posts or not in that category
            return $this->url->redirect("/tag/$tag");
        }
        $data['tag']    = $tag;
        $data['posts']  = $posts;
        $postController = $this->load->controller('Blog/Post');

        // the idea here as follows:
        // first we will pass the $post variable to $post_box variable
        // in the view file
        // why ? because $post_box is an anonymous function
        // when calling it, it will call the "box" method
        // from the post controller
        // so it will pass to it the "$post" variable to be used in the
        // post-box view
        $data['post_box'] = function ($post) use ($postController) {
            return $postController->box($post);
        };

        $data['url']        = $this->url->link('/tag/' . seo($tag) . '/' . '?page=');
        $data['pagination'] = $this->pagination->paginate();
        $view               = $this->view->render('blog/tag', $data);
        return $this->blogLayout->render($view);
    }

}

Basic search that is redirected to /search route to view results.

Comments

In the dashboard, You will find Comments section below posts..

That contains recent comments added by users with only one option to delete them..

Logically, Admin’s cannot edit other users’ comments ..

I should implement editing own comments, but that will be done in the future. (Narrator, He didn’t :P)

Post views counter

That’s a basic counter that depends on a method that update the view column of the given post..

That method will be called when a visitor opens a post.

This is a simple function that does the job well!

    /**
     * View counter
     *
     * @param ing $id
     * @return void
     */
    public function addView($id)
    {
        $views = $this->db
                        ->select('views')
                        ->where('id=?', $id)
                        ->fetch($this->table)->views + 1;
        $this->db
                ->data('views', $views)
                ->where('id=?', $id)
                ->update($this->table);
    }

Author posts:

Every user has a custom URL to view his posts .. Navigating to /author/name will redirect you to that author’s contributed posts.

Other features:

  • User’s profile .. User can change their own name, email, photo and password.
  • Added User Bio .. A few words about the user, They can change it in profile page.
  • Added About and contact-us pages … They are just static pages.

UI/UX

Dashboard

Common:

Any disabled element will be displayed in Red colour.

Header:

  • User bio in the sliding down menu.
  • Home page link embedded in breadcrumb placed in top right.

Categories:

  • Each category name contains a link refers to that category page contains posts under that category.
  • I’ve added a new column contains total number of posts in each category.

Users groups:

I’ve added a new column contains total number of users in each group.

Users

  • Each username contains a link refers to that user page contains his posts.
  • If the user is disabled, his name will be displayed without link.
  • I’ve added a new column contains total number of posts of that user.
  • I’ve added a new column contains total number of comments of that user.

Posts

  • Each post title contains a link refers to that post page.
  • Each category name contains a link refers to that category page.
  • I’ve added a new column contains the author name of that post, with a link refers to that user’s posts.
  • I’ve added a new column contains total number of comments on that post.
  • Each number of comments has a link refers to comments section of that post.

Comments

  • Each author name contains a link refers to that author page.
  • Each post title contains a link refers to that post page.
  • Each comment has a link refers to comments section of the parent post.

Ads

  • Any future starting date will be displayed in Blue colour.
  • Any past ending date will be displayed in Red colour.

Conclusion

This was a very good exercise. I’ve learned a lot about MVC, PHP design patterns, object oriented programming, database building, routing, and much more.

Related topics