Weve Encountered a Problem Please Try Again in a Few Minutesã¯â€¦å¡

xiv May 2019: This article and the code were updated for PHP7 compatibility.

Edifice a content management system can seem like a daunting task to the novice PHP developer. Nonetheless, it needn't be that hard. In this tutorial I'll prove y'all how to build a basic, but fully functional, CMS from scratch in just a few hours. Yes, it can exist washed!

Along the manner, you'll larn how to create MySQL databases and tables; how to work with PHP objects, constants, includes, sessions, and other features; how to separate business logic from presentation; how to make your PHP code more secure, and much more!

Before y'all begin, cheque out the finished product past clicking the View Demo link above. (For security reasons this demo is read-only, and so yous can't add, modify or delete articles.) You can also click the Download Code link above to download the complete PHP code for the CMS, so you can run it on your own server.

The feature list

Our offset job is to work out exactly what we want our CMS to do. The CMS will have the post-obit features:

Front stop:

  • The homepage, list the 5 most recent articles
  • The commodity listing folio, list all articles
  • The "view article" page, letting visitors meet a single article

Back stop:

  • Admin login/logout
  • Listing all articles
  • Add a new article
  • Edit an existing article
  • Delete an existing commodity

Each article will accept an associated headline, summary, and publication date.

Planning it out

Here are the steps we'll need to follow to create our CMS:

  1. Create the database
  2. Create the articles database table
  3. Make a configuration file
  4. Build the Article course
  5. Write the front-end alphabetize.php script
  6. Write the back-end admin.php script
  7. Create the front-end templates
  8. Create the back-end templates
  9. Create the stylesheet and logo epitome

Ready? Catch a loving cup of tea, and permit's get coding!

Stride i: Create the database

Safe

The start thing we need to do is create a MySQL database to shop our content. You can do this as follows:

  1. Run the mysql customer program
    Open a last window and enter the following:

    mysql -u username -p

    And then enter your MySQL password when prompted.

  2. Create the database
    At the mysql> prompt, type:

    create database cms;

    And then printing Enter.

  3. Quit the mysql customer programme
    At the mysql> prompt, type:

    go out

    So printing Enter.

That'southward it! You've now created a new, empty database, into which you tin can put your database tables and content.

Stride 2: Create the articles database table

Our elementary CMS has just one database table: articles. This, as you'd imagine, holds all of the articles in the system.

Let'south create the schema for the table. A table's schema describes the types of data that the table can hold, every bit well every bit other information about the table.

Create a text file called tables.sql somewhere on your difficult drive. Add the following code to the file:

                      Drop Tabular array IF EXISTS manufactures; CREATE TABLE articles (   id              smallint unsigned NOT Aught auto_increment,   publicationDate date Not NULL,                              # When the article was published   title           varchar(255) NOT NULL,                      # Full title of the article   summary         text Not NULL,                              # A short summary of the article   content         mediumtext Non NULL,                        # The HTML content of the article    Master Central     (id) );                  

The above code defines the schema for the articles table. It's written in SQL, the language used to create and dispense databases in MySQL (and most other database systems).

Let's break the above code downwardly a picayune:

  1. Create the manufactures tabular array

    DROP Tabular array IF EXISTS articles removes any existing manufactures table (and information — be conscientious!) if it already exists. We do this because nosotros tin can't ascertain a table with the same name as an existing table.

    CREATE Tabular array articles ( ) creates the new articles table. The stuff inside the parentheses defines the structure of the data inside the table, explained beneath…

  2. Give each article a unique ID

    Nosotros're at present ready to ascertain our table structure. A tabular array consists of a number of fields (besides called columns). Each field holds a specific type of information virtually each article.

    Start, nosotros create an id field. This has a smallint unsigned (unsigned small integer) information type, which means it can hold whole numbers from 0 to 65,535. This lets our CMS hold up to 65,535 articles. We also specify the NOT Cypher aspect, which ways the field tin't be empty (null) — this makes life easier for the states. Nosotros also add the auto_increment attribute, which tells MySQL to assign a new, unique value to an article'south id field when the article tape is created. So the kickoff commodity volition have an id of 1, the second will have an id of 2, and and then on. We'll utilise this unique value as a handle to refer to the commodity that we want to brandish or edit in the CMS.

  3. Add together the publicationDate field
    The next line creates the publicationDate field, which stores the date that each article was published. This field has a data type of appointment, which means information technology can shop appointment values.
  4. Add the title field
    Side by side we create the title field to hold each commodity's title. Information technology has a information type of varchar(255), which means it tin can store a string of upward to 255 characters.
  5. Add the summary and content fields
    The last 2 fields, summary and content, hold a short summary of the commodity and the article's HTML content respectively. summary has a text data blazon (which tin can hold up to 65,535 characters) and content has a mediumtext information type (which tin concord up to xvi,777,215 characters).
  6. Add the main cardinal

    The last line inside the CREATE Tabular array statement defines a key for the table. A key is likewise called an index, and in unproblematic terms it makes it quicker to find data in the table, at the expense of some extra storage space.

    We make the id field a Primary KEY. Each table can only have a single PRIMARY Key; this is the key that uniquely identifies each record in the table. In addition, past adding this key, MySQL can call back an article based on its ID very chop-chop.

Now that we've created our tabular array schema, nosotros demand to load it into MySQL to create the table itself. The easiest mode to do this is to open up a terminal window and alter to the folder containing your tables.sql file, then run this control:

mysql -u username -p cms < tables.sql

…where username is your MySQL username. cms is the name of the database that you created in Footstep 1.

Enter your password when prompted. MySQL and then loads and runs the code in your tables.sql file, creating the articles table inside the cms database.

Stride 3: Brand a configuration file

Levers

Now that you've created your database, you're set to start writing your PHP code. Permit'south showtime by creating a configuration file to store diverse useful settings for our CMS. This file volition be used by all the script files in our CMS.

First, create a cms folder somewhere in the local website on your figurer, to hold all the files relating to the CMS. If you're running XAMPP then the local website will exist in an htdocs binder inside your XAMPP folder. Or, if y'all prefer, you can create a brand new website simply for your CMS, and put all the files in that new website'southward certificate root folder.

Within the cms folder, create a file chosen config.php with the post-obit code:

<?php ini_set( "display_errors", truthful ); date_default_timezone_set( "Australia/Sydney" );  // http://www.php.net/manual/en/timezones.php define( "DB_DSN", "mysql:host=localhost;dbname=cms" ); ascertain( "DB_USERNAME", "username" ); define( "DB_PASSWORD", "countersign" ); ascertain( "CLASS_PATH", "classes" ); ascertain( "TEMPLATE_PATH", "templates" ); define( "HOMEPAGE_NUM_ARTICLES", v ); define( "ADMIN_USERNAME", "admin" ); define( "ADMIN_PASSWORD", "mypass" ); require( CLASS_PATH . "/Article.php" );  office handleException( $exception ) {   echo "Distressing, a problem occurred. Please try afterward.";   error_log( $exception->getMessage() ); }  set_exception_handler( 'handleException' ); ?>        

Let's interruption this file down:

  1. Display errors in the browser
    The ini_set() line causes mistake messages to be displayed in the browser. This is good for debugging, but it should be set to false on a alive site since it tin exist a security risk.
  2. Set the timezone
    As our CMS will apply PHP'due south engagement() function, we need to tell PHP our server's timezone (otherwise PHP generates a warning message). Mine is gear up to "Australia/Sydney" — change this value to your local timezone.
  3. Set the database access details
    Next we define a abiding, DB_DSN, that tells PHP where to find our MySQL database. Brand sure the dbname parameter matches the proper name of your CMS database (cms in this example). We also store the MySQL username and password that are used to access the CMS database in the constants DB_USERNAME and DB_PASSWORD. Ready these values to your MySQL username and password.
  4. Set the paths
    We set two path names in our config file: CLASS_PATH, which is the path to the grade files, and TEMPLATE_PATH, which is where our script should await for the HTML template files. Both these paths are relative to our top-level cms folder.
  5. Ready the number of articles to display on the homepage
    HOMEPAGE_NUM_ARTICLES controls the maximum number of article headlines to display on the site homepage. We've ready this to 5 initially, but if you want more or less articles, simply change this value.
  6. Set the admin username and password
    The ADMIN_USERNAME and ADMIN_PASSWORD constants contain the login details for the CMS admin user. Again, you'll want to modify these to your own values.
  7. Include the Article class
    Since the Article class file — which nosotros'll create next — is needed past all scripts in our application, we include information technology here.
  8. Create an exception handler
    Finally, nosotros ascertain handleException(), a elementary function to handle any PHP exceptions that might exist raised every bit our code runs. The role displays a generic error message, and logs the actual exception message to the spider web server'southward fault log. In particular, this function improves security by handling whatever PDO exceptions that might otherwise brandish the database username and password in the page. Once nosotros've defined handleException(), nosotros set information technology every bit the exception handler by calling PHP's set_exception_handler() function.

Step iv: Build the Article class

Cogs

Yous're now set up to build the Commodity PHP class. This is the but class in our CMS, and it handles the nitty-gritty of storing articles in the database, likewise as retrieving articles from the database. Once we've built this form, information technology will be really easy for our other CMS scripts to create, update, recall and delete articles.

Inside your cms folder, create a classes binder. Within that classes folder, create a new file chosen Article.php, and put the following code into it:

<?php  /**  * Form to handle manufactures  */  class Article {    // Properties    /**   * @var int The article ID from the database   */   public $id = null;    /**   * @var int When the article was published   */   public $publicationDate = null;    /**   * @var cord Full championship of the commodity   */   public $title = null;    /**   * @var string A short summary of the article   */   public $summary = zilch;    /**   * @var string The HTML content of the article   */   public $content = null;     /**   * Sets the object's properties using the values in the supplied assortment   *   * @param assoc The holding values   */    public function __construct( $information=array() ) {     if ( isset( $data['id'] ) ) $this->id = (int) $data['id'];     if ( isset( $data['publicationDate'] ) ) $this->publicationDate = (int) $data['publicationDate'];     if ( isset( $data['title'] ) ) $this->title = preg_replace ( "/[^\.\,\-\_\'\"\@\?\!\:\$ a-zA-Z0-9()]/", "", $data['championship'] );     if ( isset( $data['summary'] ) ) $this->summary = preg_replace ( "/[^\.\,\-\_\'\"\@\?\!\:\$ a-zA-Z0-nine()]/", "", $information['summary'] );     if ( isset( $data['content'] ) ) $this->content = $data['content'];   }     /**   * Sets the object'south properties using the edit form mail values in the supplied array   *   * @param assoc The form mail service values   */    public function storeFormValues ( $params ) {      // Store all the parameters     $this->__construct( $params );      // Parse and store the publication engagement     if ( isset($params['publicationDate']) ) {       $publicationDate = explode ( '-', $params['publicationDate'] );        if ( count($publicationDate) == iii ) {         list ( $y, $m, $d ) = $publicationDate;         $this->publicationDate = mktime ( 0, 0, 0, $thousand, $d, $y );       }     }   }     /**   * Returns an Article object matching the given article ID   *   * @param int The article ID   * @render Commodity|false The article object, or false if the tape was non found or there was a problem   */    public static function getById( $id ) {     $conn = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD );     $sql = "SELECT *, UNIX_TIMESTAMP(publicationDate) Equally publicationDate FROM articles WHERE id = :id";     $st = $conn->ready( $sql );     $st->bindValue( ":id", $id, PDO::PARAM_INT );     $st->execute();     $row = $st->fetch();     $conn = cipher;     if ( $row ) return new Commodity( $row );   }     /**   * Returns all (or a range of) Article objects in the DB   *   * @param int Optional The number of rows to render (default=all)   * @return Array|false A ii-element array : results => array, a list of Article objects; totalRows => Total number of articles   */    public static function getList( $numRows=meg ) {     $conn = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD );     $sql = "SELECT SQL_CALC_FOUND_ROWS *, UNIX_TIMESTAMP(publicationDate) AS publicationDate FROM articles             ORDER BY publicationDate DESC LIMIT :numRows";      $st = $conn->prepare( $sql );     $st->bindValue( ":numRows", $numRows, PDO::PARAM_INT );     $st->execute();     $list = assortment();      while ( $row = $st->fetch() ) {       $commodity = new Article( $row );       $list[] = $article;     }      // Now get the total number of articles that matched the criteria     $sql = "SELECT FOUND_ROWS() Every bit totalRows";     $totalRows = $conn->query( $sql )->fetch();     $conn = cypher;     return ( array ( "results" => $listing, "totalRows" => $totalRows[0] ) );   }     /**   * Inserts the current Commodity object into the database, and sets its ID property.   */    public office insert() {      // Does the Article object already have an ID?     if ( !is_null( $this->id ) ) trigger_error ( "Article::insert(): Attempt to insert an Article object that already has its ID holding set (to $this->id).", E_USER_ERROR );      // Insert the Commodity     $conn = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD );     $sql = "INSERT INTO articles ( publicationDate, title, summary, content ) VALUES ( FROM_UNIXTIME(:publicationDate), :title, :summary, :content )";     $st = $conn->prepare ( $sql );     $st->bindValue( ":publicationDate", $this->publicationDate, PDO::PARAM_INT );     $st->bindValue( ":championship", $this->title, PDO::PARAM_STR );     $st->bindValue( ":summary", $this->summary, PDO::PARAM_STR );     $st->bindValue( ":content", $this->content, PDO::PARAM_STR );     $st->execute();     $this->id = $conn->lastInsertId();     $conn = null;   }     /**   * Updates the electric current Article object in the database.   */    public function update() {      // Does the Article object accept an ID?     if ( is_null( $this->id ) ) trigger_error ( "Article::update(): Endeavor to update an Article object that does not have its ID belongings gear up.", E_USER_ERROR );         // Update the Article     $conn = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD );     $sql = "UPDATE manufactures Ready publicationDate=FROM_UNIXTIME(:publicationDate), title=:title, summary=:summary, content=:content WHERE id = :id";     $st = $conn->set ( $sql );     $st->bindValue( ":publicationDate", $this->publicationDate, PDO::PARAM_INT );     $st->bindValue( ":title", $this->title, PDO::PARAM_STR );     $st->bindValue( ":summary", $this->summary, PDO::PARAM_STR );     $st->bindValue( ":content", $this->content, PDO::PARAM_STR );     $st->bindValue( ":id", $this->id, PDO::PARAM_INT );     $st->execute();     $conn = cipher;   }     /**   * Deletes the current Article object from the database.   */    public part delete() {      // Does the Article object have an ID?     if ( is_null( $this->id ) ) trigger_error ( "Article::delete(): Attempt to delete an Commodity object that does not take its ID holding set.", E_USER_ERROR );      // Delete the Article     $conn = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD );     $st = $conn->prepare ( "DELETE FROM articles WHERE id = :id LIMIT one" );     $st->bindValue( ":id", $this->id, PDO::PARAM_INT );     $st->execute();     $conn = naught;   }  }  ?>        

This file is quite long, simply it'due south fairly elementary stuff when you break information technology downwards. Let's take a look at each department of the code:

1. The form definition and backdrop

First, we brainstorm to define our Article class with the code:

class Article {        

Everything after these lines of code — up until the closing brace at the terminate of the file — contains the code that makes up the Article form.

After starting our grade definition, we declare the properties of the class: $id, $publicationDate, and and then on. Each Article object that we create will store its article data in these properties. You can see that the property names mirror the field names in our manufactures database table.

ii. The constructor

Next nosotros create the grade methods. These are functions that are tied to the course, besides as to objects created from the course. Our main code tin telephone call these methods in order to manipulate the data in the Commodity objects.

The first method, __construct(), is the constructor. This is a special method that is called automatically by the PHP engine whenever a new Article object is created. Our constructor takes an optional $data array containing the data to put into the new object's properties. We then populate those properties inside the body of the constructor. This gives us a handy style to create and populate an object in ane go.

Yous'll observe that the method filters the information before it stores them in the properties. The id and publicationDate properties are cast to integers using (int), since these values should always be integers. The title and summary are filtered using a regular expression to just allow a sure range of characters. It's skilful security practice to filter data on input like this, only assuasive acceptable values and characters through.

Nosotros don't filter the content property, still. Why? Well, the ambassador volition probably desire to apply a broad range of characters, as well as HTML markup, in the article content. If we restricted the range of allowed characters in the content then we would limit the usefulness of the CMS for the ambassador.

Commonly this could exist a security loophole, since a user could insert malicious JavaScript and other nasty stuff in the content. Nevertheless, since we presumably trust our site administrator — who is the only person allowed to create the content — this is an acceptable tradeoff in this case. If yous were dealing with user-generated content, such as comments or forum posts, and then you would want to exist more than conscientious, and simply allow "condom" HTML to be used. A really not bad tool for this is HTML Purifier, which thoroughly analyses HTML input and removes all potentially malicious code.

iii. storeFormValues()

Our side by side method, storeFormValues(), is similar to the constructor in that it stores a supplied array of data in the object'due south properties. The primary difference is that storeFormValues() tin handle information in the format that is submitted via our New Commodity and Edit Commodity forms (which we'll create subsequently). In particular, it tin handle publication dates in the format YYYY-MM-DD, converting the engagement into the UNIX timestamp format suitable for storing in the object.

The purpose of this method is simply to make it piece of cake for our admin scripts to store the data submitted by the forms. All they have to do is call storeFormValues(), passing in the array of form data.

4. getById()

Now we come up to the methods that actually access the MySQL database. The beginning of these, getById(), accepts an article ID statement ($id), then retrieves the article record with that ID from the manufactures table, and stores information technology in a new Article object.

Usually, when yous call a method, you first create or retrieve an object, then call the method on that object. Still, since this method returns a new Commodity object, it would exist helpful if the method could be called direct past our calling lawmaking, and non via an existing object. Otherwise, we would accept to create a new dummy object each time we wanted to telephone call the method and recall an commodity.

To enable our method to be chosen without needing an object, nosotros add the static keyword to the method definition. This allows the method to exist chosen directly without specifying an object:

          public static function getById( $id ) {        

The method itself uses PDO to connect to the database, retrieve the commodity record using a SELECT SQL statement, and store the commodity information in a new Commodity object, which is and then returned to the calling code.

Let's suspension this method down:

  1. Connect to the database
                  $conn = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD );            

    This makes a connection to the MySQL database using the login details from the config.php file, and stores the resulting connection handle in $conn. This handle is used past the remaining code in the method to talk to the database.

  2. Retrieve the article tape
                  $sql = "SELECT *, UNIX_TIMESTAMP(publicationDate) AS publicationDate FROM articles WHERE id = :id";     $st = $conn->set( $sql );     $st->bindValue( ":id", $id, PDO::PARAM_INT );     $st->execute();     $row = $st->fetch();            

    Our SELECT argument retrieves all fields (*) from the record in the articles table that matches the given id field. It also retrieves the publicationDate field in UNIX timestamp format instead of the default MySQL date format, so we can store information technology hands in our object.

    Rather than placing our $id parameter directly inside the SELECT string, which can be a security risk, nosotros instead use :id. This is known as a placeholder. In a minute, we'll call a PDO method to bind our $id value to this placeholder.

    Once nosotros've stored our SELECT statement in a cord, we set the statement past calling $conn->prepare(), storing the resulting statement handle in a $st variable.

    Nosotros now bind the value of our $id variable — that is, the ID of the article nosotros desire to retrieve — to our :id placeholder by calling the bindValue() method. Nosotros pass in the placeholder name; the value to bind to it; and the value's information type (integer in this case) so that PDO knows how to correctly escape the value.

    Lastly, we call execute() to run the query, then nosotros use fetch() to recollect the resulting tape equally an associative array of field names and corresponding field values, which we store in the $row variable.

  3. Close the connectedness
                  $conn = null;            

    Since we no longer need our connection, we close it by assigning null to the $conn variable. It'south a skilful thought to close database connections every bit shortly as possible to free up memory on the server.

  4. Return the new Article object
                  if ( $row ) return new Article( $row );   }            

    The last thing our method needs to do is create a new Article object that stores the tape returned from the database, and return this object to the calling code. First it checks that the returned value from the fetch() call, $row, does in fact comprise information. If it does then it creates a new Article object, passing in $row as it does and so. Remember that this calls our constructor that we created earlier, which populates the object with the data contained in the $row assortment. We and then render this new object, and our work here is washed.

five. getList()

Our side by side method, getList(), is similar in many means to getById(). The main divergence, equally yous might imagine, is that it can retrieve many articles at one time, rather than only 1 article. Information technology'due south used whenever nosotros need to brandish a list of articles to the user or ambassador.

getList() accepts an optional argument:

$numRows
The maximum number of articles to retrieve. Nosotros default this value to 1,000,000 (i.east. finer all manufactures). This parameter allows us to display, say, just the first 5 articles on the site homepage.

Much of this method's code is similar to getById(). Let'southward look at a few lines of involvement:

          $sql = "SELECT SQL_CALC_FOUND_ROWS *, UNIX_TIMESTAMP(publicationDate) AS publicationDate FROM manufactures
Club BY publicationDate DESC LIMIT :numRows";

Our query is a bit more complex than terminal fourth dimension. Showtime, notice that there'due south no WHERE clause this time; this is considering we want to remember all articles, rather than an article that matches a specific ID.

We've too added aLIMIT clause, passing in the $numRows parameter (as a placeholder), so that we can optionally limit the number of records returned.

Finally, the special MySQL value SQL_CALC_FOUND_ROWS tells MySQL to return the bodily number of records returned; this information is useful for displaying to the user, as well equally for other things like pagination of results.

          $list = array();      while ( $row = $st->fetch() ) {       $commodity = new Article( $row );       $list[] = $commodity;     }        

Since we're returning multiple rows, we create an assortment, $list, to agree the respective Article objects. Nosotros then utilize a while loop to retrieve the next row via fetch(), create a new Article object, store the row values in the object, and add the object to the $list array. When at that place are no more than rows, fetch() returns fake and the loop exits.

          // Now get the total number of manufactures that matched the criteria     $sql = "SELECT FOUND_ROWS() AS totalRows";     $totalRows = $conn->query( $sql )->fetch();     $conn = zilch;     return ( array ( "results" => $list, "totalRows" => $totalRows[0] ) );        

Finally, we run another query that uses the MySQL FOUND_ROWS() office to go the number of returned rows calculated past our previous SQL_CALC_FOUND_ROWS command. This time we use the PDO query() method, which lets us speedily run a query if in that location are no placeholders to bind. We phone call fetch() on the resulting statement handle to retrieve the consequence row, then return both the listing of Commodity objects ($list) and the total row count as an associative array.

half dozen. insert()

The remaining methods in our Article class deal with calculation, changing and deleting article records in the database.

insert() adds a new article record to the manufactures tabular array, using the values stored in the electric current Article object:

  • First, the method makes sure that the object doesn't already have its $id property set. If it does take an ID then the article presumably already exists in the database, and so we shouldn't try to insert information technology again.
  • And then the method runs an SQL INSERT query to insert the record into the articles tabular array, using placeholders to pass the belongings values to the database. Note the use of the MySQL FROM_UNIXTIME() function to convert the publication date from UNIX timestamp format dorsum into MySQL format.
  • After running the query, the method retrieves the new commodity tape's ID using the PDO lastInsertId() function, and stores it in the object'due south $id property for futurity reference. Remember that we gear up the articles tabular array's id field as an auto_increment field, so that MySQL generates a new unique ID for each new commodity record.

7. update()

This method is like to insert(), except that information technology updates an existing commodity record in the database instead of creating a new record.

Commencement it checks that the object has an ID, since you tin can't update a record without knowing its ID. And then information technology uses the SQL UPDATE statement to update the record's fields. Notice that we pass the object's ID to the UPDATE argument so that it knows which record to update.

five. delete()

The delete() method is pretty self-explanatory. It uses the SQL DELETE statement to remove the article stored in the object from the articles table, using the object'southward $id belongings to identify the record in the table. For condom reasons, we add together LIMIT 1 to the query to make sure that only 1 article record can be deleted at a time.

Pace 5: Write the front-end index.php script

Welcome

We've now created our Article class, which does the heavy lifting for our CMS. Now that's out of the way, the rest of the lawmaking is pretty elementary!

First, permit'south create index.php, the script that controls the brandish of the forepart-finish pages of the site. Relieve this file in the cms folder you created earlier, at the start of Stride iv:

<?php  crave( "config.php" ); $action = isset( $_GET['action'] ) ? $_GET['action'] : "";  switch ( $action ) {   case 'archive':     archive();     intermission;   example 'viewArticle':     viewArticle();     break;   default:     homepage(); }   part archive() {   $results = array();   $data = Commodity::getList();   $results['articles'] = $data['results'];   $results['totalRows'] = $data['totalRows'];   $results['pageTitle'] = "Commodity Archive | Widget News";   crave( TEMPLATE_PATH . "/archive.php" ); }  function viewArticle() {   if ( !isset($_GET["articleId"]) || !$_GET["articleId"] ) {     homepage();     return;   }    $results = array();   $results['article'] = Article::getById( (int)$_GET["articleId"] );   $results['pageTitle'] = $results['commodity']->title . " | Widget News";   crave( TEMPLATE_PATH . "/viewArticle.php" ); }  function homepage() {   $results = array();   $data = Article::getList( HOMEPAGE_NUM_ARTICLES );   $results['articles'] = $data['results'];   $results['totalRows'] = $data['totalRows'];   $results['pageTitle'] = "Widget News";   require( TEMPLATE_PATH . "/homepage.php" ); }  ?>        

Permit's break this script downwards:

  1. Include the config file
    The first line of code includes the config.php file we created before, then that all the configuration settings are bachelor to the script. We use require() rather than include(); require() generates an fault if the file can't exist found.
  2. Catch the action parameter

    We store the $_GET['action'] parameter in a variable called $action, so that nosotros can use the value later in the script. Before doing this, we check that the $_GET['action'] value exists by using isset(). If it doesn't, we fix the respective $action variable to an empty cord ("").

  3. Decide which action to perform
    The switch block looks at the activeness parameter in the URL to determine which action to perform (display the archive, or view an commodity). If no action parameter is in the URL then the script displays the site homepage.
  4. annal()
    This function displays a list of all the articles in the database. It does this past calling the getList() method of the Article course that we created earlier. The function then stores the results, along with the page title, in a $results associative array so the template can display them in the folio. Finally, information technology includes the template file to display the page. (We'll create the templates in a moment.)
  5. viewArticle()
    This function displays a single article page. It retrieves the ID of the commodity to display from the articleId URL parameter, and so calls the Article form's getById() method to retrieve the article object, which it stores in the $results assortment for the template to use. (If no articleId was supplied, or the article couldn't be institute, then the function simply displays the homepage instead.)
  6. homepage()
    Our last function, homepage(), displays the site homepage containing a list of up to HOMEPAGE_NUM_ARTICLES articles (5 by default). It'southward much like the annal() office, except that it passes HOMEPAGE_NUM_ARTICLES to the getList() method to limit the number of manufactures returned.

Pace vi: Write the back-end admin.php script

Lock

Our admin script is a fleck more circuitous than index.php, since it deals with all the admin functions for the CMS. The basic structure, though, is similar to alphabetize.php.

Save this file, admin.php, in the same folder equally your index.php script:

<?php  require( "config.php" ); session_start(); $activity = isset( $_GET['action'] ) ? $_GET['action'] : ""; $username = isset( $_SESSION['username'] ) ? $_SESSION['username'] : "";  if ( $action != "login" && $action != "logout" && !$username ) {   login();   go out; }  switch ( $action ) {   case 'login':     login();     break;   case 'logout':     logout();     suspension;   case 'newArticle':     newArticle();     intermission;   case 'editArticle':     editArticle();     pause;   case 'deleteArticle':     deleteArticle();     break;   default:     listArticles(); }   function login() {    $results = array();   $results['pageTitle'] = "Admin Login | Widget News";    if ( isset( $_POST['login'] ) ) {      // User has posted the login grade: try to log the user in      if ( $_POST['username'] == ADMIN_USERNAME && $_POST['password'] == ADMIN_PASSWORD ) {        // Login successful: Create a session and redirect to the admin homepage       $_SESSION['username'] = ADMIN_USERNAME;       header( "Location: admin.php" );      } else {        // Login failed: display an fault bulletin to the user       $results['errorMessage'] = "Incorrect username or countersign. Please attempt again.";       require( TEMPLATE_PATH . "/admin/loginForm.php" );     }    } else {      // User has not posted the login course yet: display the class     require( TEMPLATE_PATH . "/admin/loginForm.php" );   }  }   role logout() {   unset( $_SESSION['username'] );   header( "Location: admin.php" ); }   function newArticle() {    $results = assortment();   $results['pageTitle'] = "New Article";   $results['formAction'] = "newArticle";    if ( isset( $_POST['saveChanges'] ) ) {      // User has posted the article edit grade: salve the new article     $article = new Article;     $article->storeFormValues( $_POST );     $commodity->insert();     header( "Location: admin.php?status=changesSaved" );    } elseif ( isset( $_POST['cancel'] ) ) {      // User has cancelled their edits: render to the commodity list     header( "Location: admin.php" );   } else {      // User has non posted the article edit form yet: brandish the form     $results['article'] = new Commodity;     require( TEMPLATE_PATH . "/admin/editArticle.php" );   }  }   function editArticle() {    $results = array();   $results['pageTitle'] = "Edit Article";   $results['formAction'] = "editArticle";    if ( isset( $_POST['saveChanges'] ) ) {      // User has posted the commodity edit form: save the article changes      if ( !$article = Commodity::getById( (int)$_POST['articleId'] ) ) {       header( "Location: admin.php?error=articleNotFound" );       return;     }      $article->storeFormValues( $_POST );     $commodity->update();     header( "Location: admin.php?status=changesSaved" );    } elseif ( isset( $_POST['abolish'] ) ) {      // User has cancelled their edits: return to the commodity list     header( "Location: admin.php" );   } else {      // User has not posted the article edit class nevertheless: display the form     $results['article'] = Article::getById( (int)$_GET['articleId'] );     require( TEMPLATE_PATH . "/admin/editArticle.php" );   }  }   function deleteArticle() {    if ( !$article = Commodity::getById( (int)$_GET['articleId'] ) ) {     header( "Location: admin.php?error=articleNotFound" );     return;   }    $article->delete();   header( "Location: admin.php?status=articleDeleted" ); }   function listArticles() {   $results = array();   $information = Commodity::getList();   $results['manufactures'] = $data['results'];   $results['totalRows'] = $information['totalRows'];   $results['pageTitle'] = "All Articles";    if ( isset( $_GET['error'] ) ) {     if ( $_GET['error'] == "articleNotFound" ) $results['errorMessage'] = "Error: Article not constitute.";   }    if ( isset( $_GET['status'] ) ) {     if ( $_GET['status'] == "changesSaved" ) $results['statusMessage'] = "Your changes take been saved.";     if ( $_GET['status'] == "articleDeleted" ) $results['statusMessage'] = "Commodity deleted.";   }    require( TEMPLATE_PATH . "/admin/listArticles.php" ); }  ?>        

Let's look at some interesting sections of this script:

  1. Start a user session

    Towards the summit of the script we phone call session_start(). This PHP function starts a new session for the user, which we can apply to track whether the user is logged in or not. (If a session for this user already exists, PHP automatically picks it up and uses it.)

  2. Grab the action parameter and username session variable
    Adjacent we shop the $_GET['action'] parameter in a variable called $action, and the $_SESSION['username'] session variable in $username, so that we can use these values subsequently in the script. Before doing this, nosotros check that these values be by using isset(). If a value doesn't exist then we set the respective variable to an empty cord ("").
  3. Check the user is logged in
    The user shouldn't be immune to do anything unless they're logged in as an administrator. And so the next thing nosotros practise is inspect $username to see if the session contained a value for the username key, which we apply to signify that the user is logged in. If $username'southward value is empty — and the user isn't already trying to log in or out — then we display the login page and exit immediately.
  4. Decide which activeness to perform

    The switch block works much like the one in index.php: it calls the appropriate function based on the value of the activeness URL parameter. The default action is to brandish the list of manufactures in the CMS.

  5. login()
    This is called when the user needs to log in, or is in the procedure of logging in.

    If the user has submitted the login form — which we bank check by looking for the login form parameter — so the function checks the entered username and password against the config values ADMIN_USERNAME and ADMIN_PASSWORD. If they match and then the username session key is fix to the admin username, effectively logging them in, and we then redirect the browser back to the admin.php script, which then displays the list of articles. If the username and countersign don't match then the login form is redisplayed with an error message.

    If the user hasn't submitted the login form withal then the function only displays the course.

  6. logout()
    This office is called when the user elects to log out. It simply removes the username session key and redirects back to admin.php.
  7. newArticle()

    This part lets the user create a new article. If the user has simply posted the "new article" form so the function creates a new Article object, stores the course data in the object by calling storeFormValues(), inserts the article into the database by calling insert(), and redirects dorsum to the article listing, displaying a "Changes Saved" status message.

    If the user has non posted the "new commodity" class however then the function creates a new empty Article object with no values, and then uses the editArticle.php template to brandish the article edit form using this empty Article object.

  8. editArticle()

    This part is like to newArticle(), except that it lets the user edit an existing article. When the user saves their changes then the office retrieves the existing article using getById(), stores the new values in the Article object, then saves the inverse object by calling update(). (If the article isn't found in the database then the function displays an error.)

    When displaying the article edit form, the function again uses the getById() method to load the current article field values into the course for editing.

  9. deleteArticle()
    If the user has called to delete an commodity then this function first retrieves the article to be deleted (displaying an mistake if the article couldn't exist found in the database), then calls the article'due south delete() method to remove the article from the database. Information technology then redirects to the commodity listing page, displaying an "commodity deleted" status message.
  10. listArticles()
    The last function in admin.php displays a list of all articles in the CMS for editing. The role uses the Article class's getList() method to recollect all the articles, and so information technology uses the listArticles.php template to brandish the list. Along the way, it as well checks the URL query parameters error and status to come across if whatever error or status message needs to be displayed in the page. If so, and so it creates the necessary message and passes it to the template for display.

Footstep vii: Create the front-end templates

View Article screenshot

We've at present created all the PHP code for our CMS'south functionality. The next footstep is to create the HTML templates for both the front-end and admin pages.

First, the front-end templates.

ane. The include files

Create a binder called templates inside your cms binder. At present create a folder called include inside the templates folder. In this folder nosotros're going to put the header and footer markup that is common to every page of the site, to save having to put information technology inside every template file.

Create a new file called header.php within your include folder, with the post-obit lawmaking:

<!DOCTYPE html> <html lang="en">   <head>     <title><?php echo htmlspecialchars( $results['pageTitle'] )?></title>     <link rel="stylesheet" type="text/css" href="fashion.css" />   </caput>   <body>     <div id="container">        <a href="."><img id="logo" src="images/logo.jpg" alt="Widget News" /></a>        

As you can run into, this code simply displays the markup to start the HTML page. Information technology uses the $results['pageTitle'] variable passed from the chief script (alphabetize.php or admin.php) to set up the title chemical element, and as well links to a stylesheet, style.css (we'll create this in a moment).

Side by side, create a file called footer.php in the same folder:

          <div id="footer">         Widget News &copy; 2011. All rights reserved. <a href="admin.php">Site Admin</a>       </div>      </div>   </trunk> </html>        

This markup finishes off each HTML folio in the system.

2. homepage.php

Now become back up to the templates folder, and create a homepage.php template file in there, with the following code:

<?php include "templates/include/header.php" ?>        <ul id="headlines">  <?php foreach ( $results['articles'] equally $article ) { ?>          <li>           <h2>             <span class="pubDate"><?php echo date('j F', $article->publicationDate)?></bridge><a href=".?action=viewArticle&amp;articleId=<?php echo $article->id?>"><?php echo htmlspecialchars( $article->title )?></a>           </h2>           <p form="summary"><?php repeat htmlspecialchars( $article->summary )?></p>         </li>  <?php } ?>        </ul>        <p><a href="./?activity=archive">Article Annal</a></p>  <?php include "templates/include/footer.php" ?>        

This template displays the article headlines on the homepage as an unordered list. It loops through the array of Commodity objects stored in $results['manufactures'] and displays each article's publication date, championship, and summary. The title is linked dorsum to '.' (index.php), passing action=viewArticle, as well every bit the commodity'southward ID, in the URL. This allows the visitor to read an article past clicking its title.

The template too includes a link to the article archive ("./?action=archive").

three. annal.php

At present create an annal.php template file in your templates binder:

<?php include "templates/include/header.php" ?>        <h1>Article Archive</h1>        <ul id="headlines" class="archive">  <?php foreach ( $results['articles'] every bit $commodity ) { ?>          <li>           <h2>             <span class="pubDate"><?php echo date('j F Y', $article->publicationDate)?></span><a href=".?activity=viewArticle&amp;articleId=<?php echo $article->id?>"><?php echo htmlspecialchars( $commodity->title )?></a>           </h2>           <p class="summary"><?php repeat htmlspecialchars( $article->summary )?></p>         </li>  <?php } ?>        </ul>        <p><?php echo $results['totalRows']?> article<?php repeat ( $results['totalRows'] != ane ) ? 's' : '' ?> in total.</p>        <p><a href="./">Return to Homepage</a></p>  <?php include "templates/include/footer.php" ?>        

This template displays the annal of all articles in the CMS. As you tin can see, information technology'due south almost identical to homepage.php. It adds an annal CSS class to the unordered list so we can style the list items a flake differently to the homepage, and it also adds the year to the article publication dates (since the archive might go dorsum a few years).

The folio as well includes a total count of the manufactures in the database, retrieved via $results['totalRows']. Finally, instead of the archive link at the bottom of the folio, it includes a "Return to Homepage" link.

four. viewArticle.php

The concluding forepart-end template displays an article to the user. Create a file called viewArticle.php in your templates folder, and add the post-obit markup:

<?php include "templates/include/header.php" ?>        <h1 manner="width: 75%;"><?php echo htmlspecialchars( $results['article']->title )?></h1>       <div style="width: 75%; font-way: italic;"><?php echo htmlspecialchars( $results['article']->summary )?></div>       <div style="width: 75%;"><?php echo $results['article']->content?></div>       <p grade="pubDate">Published on <?php echo appointment('j F Y', $results['article']->publicationDate)?></p>        <p><a href="./">Return to Homepage</a></p>  <?php include "templates/include/footer.php" ?>        

This template is very straightforward. It displays the selected commodity's championship, summary and content, besides every bit its publication appointment and a link to return to the homepage.

Step eight: Create the back-finish templates

Edit Article screenshot

Now that we've created the templates for the front end of the site, it's time to create the three admin templates.

1. loginForm.php

Kickoff, create some other folder chosen admin inside your templates folder. Within the admin folder, create the kickoff of the 3 templates, loginForm.php:

<?php include "templates/include/header.php" ?>        <class action="admin.php?activity=login" method="mail" way="width: 50%;">         <input type="hidden" name="login" value="true" />  <?php if ( isset( $results['errorMessage'] ) ) { ?>         <div grade="errorMessage"><?php echo $results['errorMessage'] ?></div> <?php } ?>          <ul>            <li>             <characterization for="username">Username</label>             <input type="text" name="username" id="username" placeholder="Your admin username" required autofocus maxlength="20" />           </li>            <li>             <characterization for="password">Password</label>             <input blazon="password" name="password" id="password" placeholder="Your admin password" required maxlength="twenty" />           </li>          </ul>          <div class="buttons">           <input type="submit" proper noun="login" value="Login" />         </div>        </form>  <?php include "templates/include/footer.php" ?>        

This page contains the admin login form, which posts back to admin.php?action=login. It includes a hidden field, login, that our login() part from Step half-dozen uses to check if the form has been posted. The course also contains an surface area for displaying any error letters (such as an incorrect username or password), likewise as username and password fields and a "Login" button.

2. listArticles.php

At present create the second admin template in your admin folder. This 1's called listArticles.php:

<?php include "templates/include/header.php" ?>        <div id="adminHeader">         <h2>Widget News Admin</h2>         <p>You are logged in every bit <b><?php echo htmlspecialchars( $_SESSION['username']) ?></b>. <a href="admin.php?action=logout"?>Log out</a></p>       </div>        <h1>All Articles</h1>  <?php if ( isset( $results['errorMessage'] ) ) { ?>         <div class="errorMessage"><?php echo $results['errorMessage'] ?></div> <?php } ?>   <?php if ( isset( $results['statusMessage'] ) ) { ?>         <div grade="statusMessage"><?php repeat $results['statusMessage'] ?></div> <?php } ?>        <table>         <tr>           <th>Publication Date</thursday>           <thursday>Commodity</th>         </tr>  <?php foreach ( $results['articles'] as $commodity ) { ?>          <tr onclick="location='admin.php?action=editArticle&amp;articleId=<?php echo $commodity->id?>'">           <td><?php echo date('j M Y', $article->publicationDate)?></td>           <td>             <?php echo $commodity->title?>           </td>         </tr>  <?php } ?>        </table>        <p><?php echo $results['totalRows']?> article<?php repeat ( $results['totalRows'] != 1 ) ? 's' : '' ?> in total.</p>        <p><a href="admin.php?activity=newArticle">Add a New Article</a></p>  <?php include "templates/include/footer.php" ?>        

This template displays the listing of manufactures for the ambassador to edit. After displaying any error or status messages, it loops through the array of Article objects stored in $results['manufactures'], displaying each article's publication date and title in a table row. It too adds a JavaScript onclick effect to each article's table row, so that the administrator tin click an article to edit information technology.

The template besides includes the total article count, as well equally a link to allow the administrator add a new article.

3. editArticle.php

Now save the final template, editArticle.php, in your admin folder:

<?php include "templates/include/header.php" ?>        <div id="adminHeader">         <h2>Widget News Admin</h2>         <p>You are logged in equally <b><?php repeat htmlspecialchars( $_SESSION['username']) ?></b>. <a href="admin.php?activity=logout"?>Log out</a></p>       </div>        <h1><?php echo $results['pageTitle']?></h1>        <grade action="admin.php?action=<?php repeat $results['formAction']?>" method="postal service">         <input blazon="subconscious" name="articleId" value="<?php repeat $results['article']->id ?>"/>  <?php if ( isset( $results['errorMessage'] ) ) { ?>         <div course="errorMessage"><?php echo $results['errorMessage'] ?></div> <?php } ?>          <ul>            <li>             <characterization for="title">Article Title</label>             <input type="text" name="title" id="title" placeholder="Name of the article" required autofocus maxlength="255" value="<?php repeat htmlspecialchars( $results['article']->title )?>" />           </li>            <li>             <label for="summary">Commodity Summary</label>             <textarea name="summary" id="summary" placeholder="Brief description of the article" required maxlength="m" way="height: 5em;"><?php repeat htmlspecialchars( $results['article']->summary )?></textarea>           </li>            <li>             <characterization for="content">Article Content</label>             <textarea name="content" id="content" placeholder="The HTML content of the article" required maxlength="100000" style="height: 30em;"><?php echo htmlspecialchars( $results['article']->content )?></textarea>           </li>            <li>             <label for="publicationDate">Publication Date</characterization>             <input type="date" name="publicationDate" id="publicationDate" placeholder="YYYY-MM-DD" required maxlength="ten" value="<?php echo $results['commodity']->publicationDate ? date( "Y-m-d", $results['article']->publicationDate ) : "" ?>" />           </li>           </ul>          <div class="buttons">           <input type="submit" name="saveChanges" value="Salvage Changes" />           <input type="submit" formnovalidate proper name="cancel" value="Cancel" />         </div>        </form>  <?php if ( $results['article']->id ) { ?>       <p><a href="admin.php?action=deleteArticle&amp;articleId=<?php echo $results['article']->id ?>" onclick="return confirm('Delete This Commodity?')">Delete This Article</a></p> <?php } ?>  <?php include "templates/include/footer.php" ?>        

This edit form is used both for creating new manufactures, and for editing existing manufactures. It posts to either admin.php?action=newArticle or admin.php?action=editArticle, depending on the value passed in the $results['formAction'] variable. It too contains a hidden field, articleId, to runway the ID of the commodity being edited (if whatever).

The grade also includes an expanse for error messages, as well as fields for the commodity title, summary, content, and publication date. Finally, there are 2 buttons for saving and cancelling changes, and a link to permit the admin to delete the currently-edited article.

Step 9: Create the stylesheet and logo image

Our CMS application is basically washed now, but in order to make it look a bit nicer for both our visitors and the site ambassador, nosotros'll create a CSS file to control the wait of the site. Save this file as style.css in your cms folder:

/* Way the body and outer container */  body {   margin: 0;   color: #333;   groundwork-colour: #00a0b0;   font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;   line-pinnacle: one.5em; }  #container {   width: 960px;   background: #fff;   margin: 20px automobile;   padding: 20px;   -moz-border-radius: 5px;   -webkit-border-radius: 5px;   border-radius: 5px; }   /* The logo and footer */  #logo {   brandish: block;   width: 300px;   padding: 0 660px 20px 0;   edge: none;   edge-bottom: 1px solid #00a0b0;   margin-lesser: 40px; }  #footer {   edge-top: 1px solid #00a0b0;   margin-top: 40px;   padding: 20px 0 0 0;   font-size: .8em; }   /* Headings */  h1 {   colour: #eb6841;   margin-lesser: 30px;   line-tiptop: 1.2em; }  h2, h2 a {   color: #edc951; }  h2 a {   text-ornamentation: none; }   /* Article headlines */  #headlines {   list-style: none;   padding-left: 0;   width: 75%; }  #headlines li {   margin-lesser: 2em; }  .pubDate {   font-size: .8em;   color: #eb6841;   text-transform: uppercase; }  #headlines .pubDate {   display: inline-block;   width: 100px;   font-size: .5em;   vertical-align: middle; }  #headlines.archive .pubDate {   width: 130px; }  .summary {   padding-left: 100px; }  #headlines.archive .summary {   padding-left: 130px; }   /* "You are logged in..." header on admin pages */  #adminHeader {   width: 940px;   padding: 0 10px;   edge-bottom: 1px solid #00a0b0;   margin: -30px 0 40px 0;   font-size: 0.8em; }   /* Mode the class with a coloured groundwork, along with curved corners and a drop shadow */  course {   margin: 20px auto;   padding: 40px 20px;   overflow: car;   groundwork: #fff4cf;   border: 1px solid #666;   -moz-edge-radius: 5px;   -webkit-border-radius: 5px;     border-radius: 5px;   -moz-box-shadow: 0 0 .5em rgba(0, 0, 0, .eight);   -webkit-box-shadow: 0 0 .5em rgba(0, 0, 0, .eight);   box-shadow: 0 0 .5em rgba(0, 0, 0, .viii); }   /* Give form elements consistent margin, padding and line height */  form ul {   list-mode: none;   margin: 0;   padding: 0; }  class ul li {   margin: .9em 0 0 0;   padding: 0; }  class * {   line-pinnacle: 1em; }   /* The field labels */  label {   brandish: block;   float: left;   clear: left;   text-align: correct;   width: 15%;   padding: .4em 0 0 0;   margin: .15em .5em 0 0; }   /* The fields */  input, select, textarea {   display: block;   margin: 0;   padding: .4em;   width: 80%; }  input, textarea, .date {   border: 2px solid #666;   -moz-edge-radius: 5px;   -webkit-border-radius: 5px;       border-radius: 5px;   background: #fff; }  input {   font-size: .9em; }  select {   padding: 0;   margin-bottom: 2.5em;   position: relative;   top: .7em; }  textarea {   font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;   font-size: .9em;   height: 5em;   line-meridian: 1.5em; }  textarea#content {   font-family unit: "Courier New", courier, stock-still; }     /* Identify a border effectually focused fields */  form *:focus {   border: 2px solid #7c412b;   outline: none; }   /* Brandish correctly filled-in fields with a green background */  input:valid, textarea:valid {   groundwork: #efe; }   /* Submit buttons */  .buttons {   text-marshal: center;   margin: 40px 0 0 0; }  input[blazon="submit"] {   display: inline;   margin: 0 20px;   width: 12em;   padding: 10px;   border: 2px solid #7c412b;   -moz-border-radius: 5px;   -webkit-border-radius: 5px;     border-radius: 5px;   -moz-box-shadow: 0 0 .5em rgba(0, 0, 0, .8);   -webkit-box-shadow: 0 0 .5em rgba(0, 0, 0, .8);   box-shadow: 0 0 .5em rgba(0, 0, 0, .8);   colour: #fff;   background: #ef7d50;   font-weight: assuming;   -webkit-appearance: none; }  input[type="submit"]:hover, input[type="submit"]:agile {   cursor: pointer;   background: #fff;   color: #ef7d50; }  input[blazon="submit"]:active {   groundwork: #eee;   -moz-box-shadow: 0 0 .5em rgba(0, 0, 0, .viii) inset;   -webkit-box-shadow: 0 0 .5em rgba(0, 0, 0, .8) inset;   box-shadow: 0 0 .5em rgba(0, 0, 0, .viii) inset; }   /* Tables */  table {   width: 100%;   border-collapse: collapse; }  tr, th, td {   padding: 10px;   margin: 0;   text-align: left; }  table, th {   border: 1px solid #00a0b0; }  th {   border-left: none;   border-correct: none;   background: #ef7d50;   colour: #fff;   cursor: default; }  tr:nth-child(odd) {   groundwork: #fff4cf; }  tr:nth-child(even) {   background: #fff; }  tr:hover {   background: #ddd;   cursor: pointer; }   /* Status and mistake boxes */  .statusMessage, .errorMessage {   font-size: .8em;   padding: .5em;   margin: 2em 0;   -moz-border-radius: 5px;   -webkit-border-radius: 5px;   border-radius: 5px;    -moz-box-shadow: 0 0 .5em rgba(0, 0, 0, .eight);   -webkit-box-shadow: 0 0 .5em rgba(0, 0, 0, .8);   -box-shadow: 0 0 .5em rgba(0, 0, 0, .8); }  .statusMessage {   background-colour: #2b2;   edge: 1px solid #080;   colour: #fff; }  .errorMessage {   background-colour: #f22;   border: 1px solid #800;   colour: #fff; }        

I won't become into the details of the CSS, since this tutorial is most PHP and MySQL! Suffice to say, it styles things like the folio layout, colours, fonts, forms, tables so on.

Last, but not least, our site needs a logo. Here's one I prepared earlier — salvage it in an images binder within your cms folder, calling it logo.jpg (or roll your own logo):

logo

All done!

We've finished our CMS! To try it out, open a browser and point it to the base URL of your CMS (for instance, http://localhost/cms/). Click the em>Site Admin link in the footer, log in, and add some articles. Then attempt browsing them on the front cease (click the logo to render to the homepage).

Don't forget you tin can try out the demo on my server too!

In this tutorial y'all've built a bones content direction organisation from the ground up, using PHP and MySQL. You've learnt about MySQL, tables, field types, PDO, object-oriented programming, templating, security, sessions, and lots more.

While this CMS is pretty basic, it has hopefully given you lot a starting point for building your own CMS-driven websites. Some features you lot might want to add include:

  • Pagination on the article archive (front) and commodity listing (back stop) so that the system can hands handle hundreds of articles
  • A WYSIWYG editor for easier content editing
  • An epitome upload facility (I've written a follow-upward tutorial on adding an image upload feature to the CMS)
  • A preview facility, so the admin can come across how an article volition look before publishing information technology
  • Article categories and tags (I've written a follow-upwards tutorial on adding categories)
  • Integration with Apache's mod_rewrite to create more human-friendly permalink URLs for the articles (notice out how to do this)
  • A user comments organisation

I hope you've enjoyed this tutorial and found it useful. Happy coding!

starktherens.blogspot.com

Source: https://www.elated.com/cms-in-an-afternoon-php-mysql/

0 Response to "Weve Encountered a Problem Please Try Again in a Few Minutesã¯â€¦å¡"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel