Creating a real-time CMS using jQuery, AJAX, PHP and JSON


#1

I have decided to share how to make a cms where it doesn’t reload the html page when the save button is click. I’m not completely done with fully implementing it on my own website with all the bells & whistles, but I thought this would be a good place to start this tutorial. Since extra code will eventually jumble up this code. So let’s get going. I’ll be starting with the file that cause me the most problems in coding, but I think that will help out people the most starting with this script first.

It’s a jQuery file that I called cms-script.js (I know how original. :P)

[code]$(document).ready(function() {

var d = new Date(),
$id = $(’#cmsId’),
$title = $(’#cmsTitle’),
$content = $(’#cmsContent’),
saveBtn = $("#save");

$.ajax({ // Start of ajax:
  url: 'data.php?='+d.getTime(),
  timeout: 5000, // Timeout * delay *
  cache: false, // Make sure it doesn't go to cache:
  dataType: "json", // Format type:
  success: function(info) { // Grab the data from php and then display it:

    // Variables * Self-Explanatory *
    var id      = info.id,
    title   = info.title,
    content = info.content;
    
    /* Display Editable Info in Form */
    $id.val(id);     
    $title.val(title);
    $content.append(content);
    
    //console.log('ID', $id, 'Title', $title, 'Content', $content);

  },
  error: function(request, status, error) {
    alert(status + ", " + error);
  }
}); // End of ajax call:

saveBtn.click(saveFCN); // Save to database:

function saveFCN(evt) {
evt.preventDefault(); // prevent button from firing:

	/* Encode a set of form elements as a string for submission */
	/* For more information goto: http://api.jquery.com/serialize/ */
	var data = $('#sendCMS :input').serialize(); 
	
	/* Save Function by grabbing & sending to location save_data.php */
	$.post($('#sendCMS').attr('action'), data , function(info) { 
		
	$('#debug_message').html(info); // Display the result back when saved:
	
}); // End of Save:

} // End of Save Function:

}); // End of Doc Ready:
[/code]

and before I explain what is going on, I will post the complementing HTML code:
[php]

<form id="sendCMS" action="save_data.php" method="post">
  <input type="hidden" name="id" id="cmsId">
	<label for="cmsTitle" class="cmsLabel">Title</label>
  <input type="text" data-id="1" class="cmsTitle" name="title" id="cmsTitle"><br><br>
  <label for="cmsContent" class="cmsLabel">Content</label><br>
  <textarea class="cmsContent" id="cmsContent" name="content"></textarea>
  
  <button id="save">SAVE</button><br><br>
</form>
<div id="debug_message"></div>
[/php]

Ok, the first portion pulls the title and content from data.php that was requested from the user who has permission to edit the file.

data.php file
[php]<?php

require(‘lib/includes/utilities.inc.php’);

if (isset($_SESSION[‘id’])) { // Check to see if $_SESSION isn’t empty:

$page_id = $_SESSION[‘id’]; // Get Page Id from $_SESSION:

// Validate the page ID:
if (!isset($page_id) || !filter_var($page_id, FILTER_VALIDATE_INT, array(‘min_range’ => 1))) {
throw new Exception(‘An invalid page ID was provided to this page.’);
}

$e = array(‘id’ => 20, ‘title’ => “Blank”, ‘content’ => “Blank”);

$setPage = new ReadPage();

if (isset($page_id)) {

try {
  $query = 'SELECT  id, title, content FROM pages WHERE id=:id';
  $stmt = $pdo->prepare($query);
  $result = $stmt->execute(array(':id' => htmlspecialchars($page_id)));

// If the query ran OK, fetch the record into an object:
  if ($result) {

    $stmt->setFetchMode(PDO::FETCH_CLASS, 'Page');
    $page = $stmt->fetch();

    $e['id'] = $page->getId();
    $e['title'] = $page->getTitle();
    $e['content'] = html_entity_decode($page->getContent());
    
  } else {
    throw new Exception('An invalid page ID was provided to this page');
  }
} catch (Exception $e) { // Catch generic exceptions
 
}

}

print json_encode($e);

} else {
$e = NULL;
print json_encode($e);

}
[/php]

Then when that person is satisfied with the changes he or she has made they hit the save button which the script calls save_data.php via the script (check out the script again and you will see what I mean).

here’s the save_data.php file
[php]<?php
header(‘Content-type: application/json’);
require(‘lib/includes/utilities.inc.php’);
// Redirect if the user doesn’t have permission:
if (!$user) {
header(“Location:index.php”);
exit;
} elseif (!$user->canCreatePage()) {
header(“Location:index.php”);
exit;
}
$id = htmlspecialchars($_POST[‘id’]);
$title = htmlspecialchars(strip_tags($_POST[‘title’]));
$content = htmlspecialchars($_POST[‘content’]);
$data = array(‘title’ => $title, ‘content’ => $content);
// Clean up any foul language that an user might had enter:
$dirtyWord = new DirtyWord($data);
// Store the clean up data in their respective variables:
$title = $dirtyWord->checkTitle;
$content = trim($dirtyWord->checkContent);
// Update the edited text:
$query = ‘UPDATE pages SET title=:title, content=:content, dateUpdated=NOW() WHERE id=:id’;

// Prepare the Statement:
$stmt = $pdo->prepare($query);

// execute the statement:
$result = $stmt->execute(array(‘title’ => $title, ‘:content’ => $content, ‘:id’ => $id));

if ($result) {
echo ‘Data Successfully Inserted’;
} else {
echo ‘Insertion Failed!’;
}

[/php]

I will be updating this tutorial as I tighten up the code and find bugs, but this code does work. I am very confident that it is secure as well, for it keeps the jQuery and PHP separate for the most part and the PHP is escaped and uses PDO prepared statements. As with anything on the web one always has to be on top of things and if I find a better way of doing this script that makes it even more secure I will post it.

Until then Happy New Year!

John

P.S. I will be adding “How to add a new page” doing it this way (method), but I want to rework the code a little. I hoping to get it where it operates like you see Facebook or to an extent Twitter works. :wink:


#2

It’s brought to my attention that in my code that I am escaping the input. DON’T ESCAPE THE INPUT for one should ESCAPE THE OUTPUT. Me bad… :’( I will fix that in a later post, I got into that bad habit via earlier watching earlier tutorials (around the time magic quotes where around) and I sometimes get scatterbrain when writing scripts, for I concentrate in getting it to work that I fall into bad habits. This just goes to show that there are a lot of bad tutorials out there.

Thanks JimL for pointing this out.


#3

OK, here are the fixes…again sorry for me bad. ;D
data.php - output data:
[php]<?php

require(‘lib/includes/utilities.inc.php’);

if (isset($_SESSION[‘id’])) { // Check to see if $_SESSION isn’t empty:

$page_id = $_SESSION[‘id’]; // Get Page Id from $_SESSION:

// Validate the page ID:
if (!isset($page_id) || !filter_var($page_id, FILTER_VALIDATE_INT, array(‘min_range’ => 1))) {
throw new Exception(‘An invalid page ID was provided to this page.’);
}

$e = array(‘id’ => 20, ‘title’ => “Blank”, ‘content’ => “Blank”);

$setPage = new ReadPage();

if (isset($page_id)) {

try {
  $query = 'SELECT  id, title, content FROM pages WHERE id=:id';
  $stmt = $pdo->prepare($query);
  $result = $stmt->execute(array(':id' => htmlspecialchars($page_id)));

// If the query ran OK, fetch the record into an object:
  if ($result) {

    $stmt->setFetchMode(PDO::FETCH_CLASS, 'Page');
    $page = $stmt->fetch();

    $e['id'] = $page->getId(); // This gets checked to when retrieving the page
                               // by that I mean it's checked to see if it's a number.
    $e['title'] = html_escape($page->getTitle());
    $e['content'] = html_escape($page->getContent());

  } else {
    $e = NULL;
    print json_encode($e);
    throw new Exception('An invalid page ID was provided to this page');
  }
} catch (Exception $e) { // Catch generic exceptions
 
}

}

print json_encode($e);

} else {
$e = NULL;
print json_encode($e);

}

[/php]

save_data.php - input data:
[php]<?php
header(‘Content-type: application/json’);
require(‘lib/includes/utilities.inc.php’);
// Redirect if the user doesn’t have permission:
if (!$user) {
header(“Location:index.php”);
exit;
} elseif (!$user->canCreatePage()) {
header(“Location:index.php”);
exit;
}
$id = $_POST[‘id’];
$title = $_POST[‘title’];
$content = $_POST[‘content’];
$data = array(‘title’ => $title, ‘content’ => $content);
// Clean up any foul language that an user might had enter:
$dirtyWord = new DirtyWord($data);
// Store the clean up data in their respective variables:
$title = $dirtyWord->checkTitle;
$content = trim($dirtyWord->checkContent);
// Update the edited text:
$query = ‘UPDATE pages SET title=:title, content=:content, dateUpdated=NOW() WHERE id=:id’;

// Prepare the Statement:
$stmt = $pdo->prepare($query);

// execute the statement:
$result = $stmt->execute(array(‘title’ => $title, ‘:content’ => $content, ‘:id’ => $id));

if ($result) {
echo ‘Data Successfully Inserted’;
} else {
echo ‘Insertion Failed!’;
}

[/php]

I also noticing a small bug that I’m checking into, it has to do with the delay in ajax I think…I hate logic bugs. :wink:

oh I forgot I’m using a special escape function to ensure that it’s escaped properly:
[php]function html_escape($raw_input) {
// important! don’t forget to specify ENT_QUOTES and the correct encoding
return htmlspecialchars($raw_input, ENT_QUOTES | ENT_HTML401, ‘UTF-8’);
}[/php]

but you can just use htmlspecialchars just as well.
LOL - I’m definitely htmlspecialchars happy, well at least it’s in the output section. ;D


#4

Thank you for the tutorial Strider. I have used a similar idea, minus the Ajax in my applications.


#5

Seems like you have an error in this line
[php] $result = $stmt->execute(array(‘title’ => $title, ‘:content’ => $content, ‘:id’ => $id));[/php]

Also, have you included the file with the DirtyWord class? Couldn’t find it.