14 min read

 

CMS Design Using PHP and jQuery

CMS Design Using PHP and jQuery Build and improve your in-house PHP CMS by enhancing it with jQuery

  • Create a completely functional and a professional looking CMS
  • Add a modular architecture to your CMS and create template-driven web designs
  • Use jQuery plugins to enhance the “feel” of your CMS
  • A step-by-step explanatory tutorial to get your hands dirty in building your own CMS
        Read more about this book      

(For more resources on this subject, see here.)

After completing this article, the CMS could be considered “complete”, in that almost every other requested feature can be supplied by writing a plugin for it.

However, it should be noted that a CMS never is actually complete, because each new website may bring a new request that is not yet catered for.

Having said that, using plugins lets you at least complete a “core” engine and concentrate on providing hooks that allow further development to be done, outside that core.

What are plugins

A plugin is a module of code that can be dropped into a directory and enabled, to give a CMS extra capabilities.

Plugins need to be able to change the output and do other tasks, so it is necessary to add various “hooks” throughout the code where the plugins can apply their code.

A very important reason for adding a plugin architecture to a CMS is that it lets you stabilize the core code. The core is basically the code that will be available in every instance of the CMS, as opposed to plugin code, which may or may not be present in any particular instance of the CMS.

With a core piece of code that is deemed “complete”, it becomes easier to manage bugs. Because you are not always adding to the core code, you are not actively adding to the potential number of bugs.

In a CMS which does not have a stable core, any change to the central code can affect just about anything else.

You really need to get your CMS to a stage where you are no longer developing the central engine. Instead, you are working mostly on external plugins and maybe occasional bug fixes to the core, as they are found.

In my case, for example, I worked for years on building up a CMS before getting around to building in plugins. Every change that was requested was built into the core code. Usually, only the fully-tested code at that time would be the new code, so very often we would miss a problem that the new code would have caused somewhere else in the CMS. Often, this problem would not show up for weeks, so it would not be obvious what the problem was related to!

When all the development of a CMS is shifted to plugins, it becomes less likely that the core is at fault when a problem occurs. Because plugins, by their nature, tend to be isolated pieces of code, if a bug does appear, it is very likely the bug is within the plugin’s code and not anywhere else.

Also, because plugins allow a person to develop without touching the core engine, it is possible for the external teams or individuals to create their own plugins that they can use with the engine, without needing to understand all the parts of the core engine. One more advantage is that if the plugin architecture is solid, it is possible for development to continue on the core completely separately from the plugins, knowing that plugins from one version of the CMS will most likely work with a core from another version.

Events in the CMS

One example of a hook is event triggers.

In JavaScript (and therefore jQuery), there is the concept of events, where you can set a block of code to run when a certain trigger happens.

For example, when you move your mouse over an element, there are a number of potential trigger points—onmouseover, onmouseenter, onmousemove (and possibly others, depending on the context).

Obviously, PHP does not have those events, as it’s a server-side language. But it is possible to conceive of triggers for your CMS that you could potentially hook onto.

For example, let’s say you’ve just finished figuring out the page content. At this point, you may want to trigger a page-content-created event. This could (and will, in this article) be used by a Page Comments plugin to tack on the comments thread, and any required forms, to the end of that page content.

Another example: Let’s say you want to create a custom log for your own purposes. You would then be interested in a start trigger that can be used to initialize certain values, such as a timer. After the output has been sent, a finish trigger that can be used to tally up a number of figures (compilation time, memory used, size of rendered output, and so on) and record them in a file or database before the script finishes.

Page types

In some cases, you will want the page content to be totally converted. Instead of showing a page body as normal, you may want to show an image gallery or a store checkout.

In this case, you would need to create a “page type” block of code, which the frontend will use instead of the usual page data render() call.

In the admin area, this might also require using a customized form instead of the usual rich text editor.

Admin sections

The admin area may need to have new sections added by a plugin. In the Events section, we described a logging plugin. A perfect complement to that is a graphing log viewer, which would be shown as a completely new admin section and have its own entry in the admin menu.

Page admin form additions

You may also want to add extra forms to all the Page forms in the admin, regardless of what page type it is. For example, if you create a security plugin and want to protect various pages depending on who is viewing it, you will need to be able to choose which users or groups have access and what to display if the current user does not have full access. This requires an additional form in the Page admin.

It is very difficult to describe all the possible plugin uses, and the number of triggers that may be required.

The easiest way to proceed is to just adjust the engine as required. If it turns out you forgot to add an event trigger at some point, it should be a small matter to just add it in at that point without affecting the core code beyond that addition.

Example plugin configuration

Create a directory called /ww.plugins.

Each plugin you create will be placed in a directory—one directory per plugin.

For our first example, we’re going to build a Page Comments plugin, which will allow visitors to your site to leave comments on your pages.

On the admin side, we will need to provide methods to maintain the submitted comments per page and for the whole site.

Anyway, create a directory to hold the plugin called /ww.plugins/page-comments. The CMS will expect the plugin configuration for each plugin to be in a file named plugin.php. So the configuration for the Page Comments plugin /ww.plugins/page-comments/plugin.php is as follows:

<?php $plugin=array( ‘name’ => ‘Page Comments’, ‘description’ => ‘Allow your visitors to comment on pages.’, ‘version’ => ‘0’, ‘admin’ => array( ‘menu’ => array( ‘Communication>Page Comments’ => ‘comments’ ) , ‘page_tab’ => array( ‘name’ => ‘Comments’, ‘function’ => ‘page_comments_admin_page_tab’ ) ), ‘triggers’ => array( ‘page-content-created’ => ‘page_comments_show’ ) );


The plugin.php files at least contain an array named $plugin, which describes the plugin.

For now, let’s look at what the current example says. All of these options, except the first two, are optional.

First, we define a name, “Page Comments”. This is only ever used in the admin area, when you are choosing your plugins. The same is true of the description field.

The version field is used by the CMS to tell whether a plugin is up-to-date or if some automatic maintenance is needed. This will be explained in more detail later in this article.

Next, we have the admin array, which holds details of the admin-only functions. The menu array is used to edit the admin menu, in case you need to add an admin section for the plugin. In this case, we will add an admin section for Page Comments, which will let you set site-wide settings and view comments site-wide.

If a new tab is to be added to the page admin section, this tab is described in the page_tab array. name is what appears in the tab header, and function is the name of a PHP function that will be called to generate the tab content.

Finally, the triggers array holds details of the various triggers that the plugin should react to. Each trigger calls a function.

Obviously, this is not a complete list, and it is not possible to ever have a complete list, as each new circumstance you are requested to write for may bring up a need for a trigger or plugin config setting that you had not thought of.

However, there are less and less additions, as the plugin architecture becomes more complete.

From the plugin configuration, you can see that there are some functions named, which we have not defined.

You should define those functions in the same file:

function page_comments_admin_page_tab($PAGEDATA){ require_once SCRIPTBASE.’ww.plugins/page-comments/’ .’admin/page-tab.php’; return $html; } function page_comments_show($PAGEDATA){ if(isset($PARENTDATA->vars->comments_disabled) && $PARENTDATA->vars->comments_disabled==’yes’) return; require_once SCRIPTBASE.’ww.plugins/page-comments/’ .’frontend/show.php’; }


The functions are prefixed with an identifier to make sure that they don’t clash with the functions from other plugins. In this case, because the plugin is named Page Comments, the prefix is page_comments_.

The functions here are essentially stubs. Plugins will be loaded every time any request is made to the server. Because of this, and the obvious fact that not all the functions would be needed in every request, it makes sense to keep as little code in it as possible in the plugin.php files.

In most cases, triggers will be called with just the $PAGEDATA object as a parameter. Obviously, in cases in the admin area where you’re not editing any particular page this would not make sense, but for most plugins, to keep the function calls consistent, the only parameter is $PAGEDATA.

Enabling plugins

We have defined a plugin. We could make it such that when you place a plugin in the /ww.plugins directory, it is automatically enabled. However, if you are creating a CMS that you intend to reuse for a lot of other clients, it is a lot easier to simply copy the entire CMS source and reconfigure, than to copy the CMS source and then clear out the existing plugins and repopulate carefully with new ones that you would download from a repository that you keep somewhere else.

So, what we do is we give the admin a maintenance page where they choose the plugins they want to load. The CMS then only loads those and does not even look at the other directories.

Edit the /ww.admin/header.php file and add a new link (highlighted) to the plugin admin section:

  • <a href=”/ww.admin/themes.php”>Themes</a>

 

 

 

 

 

We will be changing the admin menu later in this article to make it customizable more easily, but for now, add in that link manually.

Now create the /ww.admin/plugins.php file:

<?php require ‘header.php’; echo ‘

Plugin Management

‘; echo ‘

‘; echo ‘Users‘; echo ‘Themes‘; echo ‘Plugins‘; echo ‘

‘; echo ‘

‘; echo ‘

Plugin Management

‘; require ‘plugins/list.php’; echo ‘

‘; require ‘footer.php’;


You’ll have noticed that this is similar to the /ww.admin/themes.php and /ww.admin/users.php files. They’re all related to site-wide settings, so I’ve placed links to them all in the left-menu. Edit those files and add in the new Plugins link to their menus.

Before we create the page for listing the enabled plugins, we must first set up the array of enabled plugins in /ww.incs/basics.php, by adding this to the end of the file:

// { plugins $PLUGINS=array(); if (isset($DBVARS[‘plugins’])&&$DBVARS[‘plugins’]) { $DBVARS[‘plugins’]=explode(‘,’,$DBVARS[‘plugins’]); foreach($DBVARS[‘plugins’] as $pname){ if (strpos(‘/’,$pname)!==false) continue; require SCRIPTBASE . ‘ww.plugins/’.$pname.’/plugin.php’; $PLUGINS[$pname]=$plugin; } } else $DBVARS[‘plugins’]=array(); // }


As you can see, we are again referencing the $DBVARS array in the /.private/config.php.

Because we already have a function for editing that, all we need to do to change the list of enabled or disabled plugins, and create and maintain the $DBVARS[‘plugins’] array, making sure to resave the config file after each change.

What the code block does is that it reads in the plugin.php file for each enabled plugin, and saves the $plugin array from each file into a global $PLUGINS array.

The $DBVARS[‘plugins’] variable is an array, but we’ll store it as a comma-delimited string in the config file. Edit config_rewrite() in the same file and add this highlighted line:

$tmparr=$DBVARS; $tmparr[‘plugins’]=join(‘,’,$DBVARS[‘plugins’]); $tmparr2=array();


We’ll enhance the plugin loader in a short while. In the meantime, let’s finish the admin plugin maintenance page.

Create the directory /ww.admin/plugins, and in it, add /ww.admin/plugins/list.php:

<?php echo ‘

‘; echo ‘
‘; // { list enabled plugins first foreach($PLUGINS as $name=>$plugin){ echo ”, ”, ”, ”; } // } // { then list disabled plugins $dir=new DirectoryIterator(SCRIPTBASE . ‘ww.plugins’); foreach($dir as $plugin){ if($plugin->isDot())continue; $name=$plugin->getFilename(); if(isset($PLUGINS[$name]))continue; require_once(SCRIPTBASE.’ww.plugins/’.$name.’/plugin.php’); echo ”, ”, ”, ”, ”; } // } echo ‘

Plugin Name Description
‘,htmlspecialchars(@$plugin[‘name’]),’ ‘,htmlspecialchars(@$plugin[‘description’]),’ disable
‘,htmlspecialchars($plugin[‘name’]),’ ‘,htmlspecialchars($plugin[‘description’]),’ enable

‘;


When viewed in a browser, it displays like this:

The script displays a list of already-enabled plugins (we have none so far), and then reads the /ww.plugins directory for any other plugins and adds them along with an “enable” link.

Now we need to write some code to do the actual selection/enabling of the plugins.

While it would be great to write some jQuery to do it in an Ajaxy way (so you click on the enable link and the plugin is enabled in the background, without reloading the page), there are too many things that might cause problems. For instance, if the plugin caused new items to appear in the menu, we’d have to handle that. If the plugin changed the theme, or did anything else that caused a layout change, we’d have to handle that as well.

So instead, we’ll do it the old-fashioned PHP way—you click on enable or disable, which does the job on the server, and then reloads the plugin page so you can see the change.

Create the /ww.admin/plugins/enable.php file:

<?php require ‘../admin_libs.php’; if(!in_array($_REQUEST[‘n’],$DBVARS[‘plugins’])){ $DBVARS[‘plugins’][]=$_REQUEST[‘n’]; config_rewrite(); } header(‘Location: /ww.admin/plugins.php’);


It simply adds the requested plugin to the $DBVARS[‘plugins’] array, then rewrites the config and redirects the browser back to the plugins page.

When clicked, the page apparently just reloads, and the plugin’s link changes to disable.

The opposite script is just as simple. Write this code block in the file /ww.admin/plugins/disable.php:

<?php require ‘../admin_libs.php’; if(in_array($_REQUEST[‘n’],$DBVARS[‘plugins’])){ unset($DBVARS[‘plugins’][ array_search($_REQUEST[‘n’],$DBVARS[‘plugins’]) ]); config_rewrite(); } header(‘Location: /ww.admin/plugins.php’);


In this case, all we needed to do was to remove the plugin name from $DBVARS[‘plugins’] by unsetting its position in the array.

Plugins are now very simply set up. Here’s a screenshot of that page with a number of plugins enabled and disabled. I copied some plugins from a more mature copy of the CMS that I have. We will be looking at a few of them, and building one or two others:

The enabled plugins are moved to the top of the list to make them more visible and the rest are shown below them.

LEAVE A REPLY

Please enter your comment!
Please enter your name here