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:
- Create the database
- Create the
articles
database table - Make a configuration file
- Build the
Article
course - Write the front-end
alphabetize.php
script - Write the back-end
admin.php
script - Create the front-end templates
- Create the back-end templates
- Create the stylesheet and logo epitome
Ready? Catch a loving cup of tea, and permit's get coding!
Stride i: Create the database
The start thing we need to do is create a MySQL database to shop our content. You can do this as follows:
- 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.
- Create the database
At themysql>
prompt, type:create database cms;
And then printing Enter.
- Quit the
mysql
customer programme
At themysql>
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:
- Create the
manufactures
tabular arrayDROP Tabular array IF EXISTS articles
removes any existingmanufactures
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 newarticles
table. The stuff inside the parentheses defines the structure of the data inside the table, explained beneath… - 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 asmallint 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 theNOT Cypher
aspect, which ways the field tin't be empty (null) — this makes life easier for the states. Nosotros also add theauto_increment
attribute, which tells MySQL to assign a new, unique value to an article'southid
field when the article tape is created. So the kickoff commodity volition have anid
of 1, the second will have anid
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. - Add together the
publicationDate
field
The next line creates thepublicationDate
field, which stores the date that each article was published. This field has a data type ofappointment
, which means information technology can shop appointment values. - Add the
title
field
Side by side we create thetitle
field to hold each commodity's title. Information technology has a information type ofvarchar(255)
, which means it tin can store a string of upward to 255 characters. - Add the
summary
andcontent
fields
The last 2 fields,summary
andcontent
, hold a short summary of the commodity and the article's HTML content respectively. summary has atext
data blazon (which tin can hold up to 65,535 characters) andcontent
has amediumtext
information type (which tin concord up to xvi,777,215 characters). - 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 aPrimary KEY
. Each table can only have a singlePRIMARY 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
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:
- Display errors in the browser
Theini_set()
line causes mistake messages to be displayed in the browser. This is good for debugging, but it should be set tofalse
on a alive site since it tin exist a security risk. - Set the timezone
As our CMS will apply PHP'due southengagement()
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. - Set the database access details
Next we define a abiding,DB_DSN
, that tells PHP where to find our MySQL database. Brand sure thedbname
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 constantsDB_USERNAME
andDB_PASSWORD
. Ready these values to your MySQL username and password. - Set the paths
We set two path names in our config file:CLASS_PATH
, which is the path to the grade files, andTEMPLATE_PATH
, which is where our script should await for the HTML template files. Both these paths are relative to our top-levelcms
folder. - 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. - Set the admin username and password
TheADMIN_USERNAME
andADMIN_PASSWORD
constants contain the login details for the CMS admin user. Again, you'll want to modify these to your own values. - Include the
Article
class
Since theArticle
class file — which nosotros'll create next — is needed past all scripts in our application, we include information technology here. - Create an exception handler
Finally, nosotros ascertainhandleException()
, 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 definedhandleException()
, nosotros set information technology every bit the exception handler by calling PHP'sset_exception_handler()
function.
Step iv: Build the Article
class
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:
- 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. - 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 thearticles
table that matches the givenid
field. It also retrieves thepublicationDate
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 theSELECT
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 thebindValue()
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 usefetch()
to recollect the resulting tape equally an associative array of field names and corresponding field values, which we store in the$row
variable. - 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. - Return the new
Article
objectif ( $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 thefetch()
call,$row
, does in fact comprise information. If it does then it creates a newArticle
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 thearticles
tabular array, using placeholders to pass the belongings values to the database. Note the use of the MySQLFROM_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 thearticles
tabular array'sid
field as anauto_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
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:
- Include the config file
The first line of code includes theconfig.php
file we created before, then that all the configuration settings are bachelor to the script. We userequire()
rather thaninclude()
;require()
generates an fault if the file can't exist found. - Catch the
action
parameterWe 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 usingisset()
. If it doesn't, we fix the respective$action
variable to an empty cord (""
). - Decide which action to perform
Theswitch
block looks at theactiveness
parameter in the URL to determine which action to perform (display the archive, or view an commodity). If noaction
parameter is in the URL then the script displays the site homepage. -
annal()
This function displays a list of all the articles in the database. It does this past calling thegetList()
method of theArticle
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.) -
viewArticle()
This function displays a single article page. It retrieves the ID of the commodity to display from thearticleId
URL parameter, and so calls theArticle
form'sgetById()
method to retrieve the article object, which it stores in the$results
assortment for the template to use. (If noarticleId
was supplied, or the article couldn't be institute, then the function simply displays the homepage instead.) -
homepage()
Our last function,homepage()
, displays the site homepage containing a list of up toHOMEPAGE_NUM_ARTICLES
articles (5 by default). It'southward much like theannal()
office, except that it passesHOMEPAGE_NUM_ARTICLES
to thegetList()
method to limit the number of manufactures returned.
Pace vi: Write the back-end admin.php
script
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:
- 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.) - Grab the
action
parameter andusername
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 usingisset()
. If a value doesn't exist then we set the respective variable to an empty cord (""
). - 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 theusername
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. - Decide which activeness to perform
The
switch
block works much like the one inindex.php
: it calls the appropriate function based on the value of theactiveness
URL parameter. The default action is to brandish the list of manufactures in the CMS. -
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 valuesADMIN_USERNAME
andADMIN_PASSWORD
. If they match and then theusername
session key is fix to the admin username, effectively logging them in, and we then redirect the browser back to theadmin.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.
-
logout()
This office is called when the user elects to log out. It simply removes theusername
session key and redirects back toadmin.php
. -
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 callingstoreFormValues()
, inserts the article into the database by callinginsert()
, 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 theeditArticle.php
template to brandish the article edit form using this emptyArticle
object. -
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 usinggetById()
, stores the new values in theArticle
object, then saves the inverse object by callingupdate()
. (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. -
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 southdelete()
method to remove the article from the database. Information technology then redirects to the commodity listing page, displaying an "commodity deleted" status message. -
listArticles()
The last function inadmin.php
displays a list of all articles in the CMS for editing. The role uses theArticle
class'sgetList()
method to recollect all the articles, and so information technology uses thelistArticles.php
template to brandish the list. Along the way, it as well checks the URL query parameterserror
andstatus
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
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 © 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&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&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
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&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&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):
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!
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