Professional Documents
Culture Documents
1/26
2/26
Directory Structure
Digitalus CMS modules are found in the directory /application/modules. The way modules are handled by the CMS requires to have at least two controllers and view scripts, i.e. index and public. More controllers and view scripts can be created as we will see. To start with our module create a directory called guestbook. As modules follow the MVC pattern create the following subdirectories as proposed by the Zend Framework:
/application/modules/guestbook: controllers data forms models views helpers scripts index public
The data directory will later store several different data, e.g. translation files. Now already, the module guestbook appears on the admin tab Modules.
3/26
/application/modules/guestbook/views/scripts/index/index.phtml: /application/modules/guestbook/views/scripts/index/index.options.phtml:
Okay, not bad. But what about the titles? There are three empty titles right now. Change Your already existing files as follows: /application/modules/guestbook/controllers/IndexController.php:
<?php class Mod_Guestbook_IndexController extends Zend_Controller_Action { public function init() { $this->view->breadcrumbs = array( $this->view->getTranslation('Modules') => $this->view->getBaseUrl() . '/admin/module', $this->view->getTranslation('Guestbook') => $this->view->getBaseUrl() . '/mod_guestbook' ); $this->view->toolbarLinks['Add to my bookmarks'] = $this->view->getBaseUrl() . '/admin/index/bookmark/url/mod_guestbook'; } public function indexAction() { } }
/application/modules/guestbook/views/scripts/index/index.phtml:
4/26
/application/modules/guestbook/views/scripts/index/index.options.phtml:
Looks better, but do we do here? In the init() method of the IndexController we make use of two view helpers from the Digitalus library, i.e. Breadcrumbs and ToolbarLinks. We feed both with translatable texts and links that start with $this->view->getBaseUrl(). This is again a view helper that returns the base url of the website. This is required if Digitalus is not installed in the public root but in a sub-directory. The view scripts index.phtml and index.options.phtml make use of placeholders that are define within the Digitalus CMS, i.e. formHeadline and optionsHeadline. As the name says, they are the headlines.
5/26
/application/modules/guestbook/views/scripts/public/index.phtml: /application/modules/guestbook/views/scripts/public/index2.phtml:
If You select index to be the frontend action for Your page, the page will look something like this:
As our IndexAction is empty and the view script index.phtml simply echoes frontend1 the content of the page is simply the echoed string (apart from headers and titles). If Your module provides any options that should be selected by the administrator, You can (and You will) create a view script with the same name, but form before the file extension, e.g. index.phtml and index.form.phtml. Mostly, the latter will hold a form element to select Your options. We'll come to this point later. So far, this is not too complicated. Let's get into more detail. (Let's drop the index2Action as it was only for demonstrating purposes)
6/26
/application/modules/guestbook/controllers/PublicController.php:
<?php require_once './application/modules/guestbook/forms/Entry.php'; class Mod_Guestbook_PublicController extends Zend_Controller_Action { public $moduleData; public $properties; public function init() { $module = new Digitalus_Module(); $this->moduleData = $module->getData(); $this->properties = Digitalus_Module_Property::load('mod_guestbook'); } public function indexAction() { $entryForm = new Entry_Form(); $entryForm->setAction($_SERVER['REQUEST_URI']); $submit = $entryForm->getElement('submit'); $submit->setLabel($this->view->getTranslation('Create Guestbook Entry')); $this->view->form = $entryForm; } }
7/26
<?php
/application/modules/guestbook/views/scripts/public/index.phtml:
echo $this->form;
Again, it's not kind of magic. We create a form that extends Digitalus_Form. We add some form elements to the form - that's it. In our IndexAction of the IndexController we create an instance of the new form, modify it a bit (setAction(), etc.) and add it to our view. The view script simply echoes the form. That's how it looks by now:
8/26
Do some action
Okay, but what happens when we push the button? We have set the following action in the PublicController:
$entryForm->setAction($_SERVER['REQUEST_URI']);
So, the action of the form is the url we come from. The PublicController must react differently depending on whether the form is submitted or not. What do we expect the form to do? It should validate the input and if valid send the data to the server and store it into a database. That means, first of all we need a model for the guestbook entry that extends Zend_Db_Table_Abstract somehow. The model The Digitalus CMS offers some basic classes that already extend this abstract class, but it also provides some models that could be extended for our specific use. The Model_Page model is a good base to build own classes on as it provides several useful methods. It is already linked to a database (pages). More complex modules might require very special databases, but that's not shown in this tutorial. The new model should assist us in creating new entries, deleting entries and fetching entries from the database. Let's create the model with these basic methods: /application/modules/guestbook/models/Entry.php:
<?php class Guestbook_Entry extends Model_Page { protected $_namespace = 'guestbook_entry'; public function getEntries($guestbookId) { } public function getEntry($entryId) { } public function createEntry($guestbookId, $title = null) { return $this->createPage($title, $guestbookId); } public function deleteEntry($entryId) { $this->deletePageById($entryId); } }
First of all, we simply want to create an entry and be able to delete it. We'll come to the other methods later. The createEntry method makes use of the createPage method of the Model_Page model as well as the deleteEntry method uses the deletePageById.
Digitalus CMS Tutorial: Creating Modules The controller Now we need to modify the PublicController. /application/modules/guestbook/controllers/PublicController.php:
<?php require_once './application/modules/guestbook/forms/Entry.php'; require_once './application/modules/guestbook/models/Entry.php'; class Mod_Guestbook_PublicController extends Zend_Controller_Action { public $moduleData; public $properties; public function init() { $module = new Digitalus_Module(); $this->moduleData = $module->getData(); $this->properties = Digitalus_Module_Property::load('mod_guestbook'); } public function indexAction() { $entryForm = new Entry_Form(); $entryForm->setAction($_SERVER['REQUEST_URI']); $submit = $entryForm->getElement('submit'); $submit->setLabel($this->view->getTranslation('Create Guestbook Entry')); if ($this->_request->isPost() && $entryForm->isValid($_POST)) { $values = $entryForm->getValues();
9/26
$mdlEntry = new Guestbook_Entry(); $entry = $mdlEntry->createEntry($values['guestbook_id'], substr($values['content'], 0, 250)); $this->_forward('index'); } $this->view->form = $entryForm; } }
The indexAction now checks whether the form is submitted and valid. If this is true, a new entry is created by using the createEntry method of the Guestbook_Entry model. You might wonder what the substr function in the indexAction is all about. Well, the Model_Page model requires a name attribute. What makes sense for pages doesn't necessarily make sense for guestbook entries. So, I decided to store the first 250 letters in the name attribute (we can use it later to generate a preview of the entry).
Go to the frontend page MyGuestbookPage You created earlier Submit an empty form; You will see some error messages occur. Submit the form with some content. Check Your database (e.g. by using phpMyAdmin). You will see that a new entry is created in the database pages.
10/26
But what guestbook are the entries related to? And what about the content? When and how is it stored to the database?
11/26
The guestbook
We want to create a guestbook in the administration backend. Then we want to assign a specific guestbook to a page. For the creation of guestbook we need an HTML form. It's almost the same as the Entry_Form. /application/modules/guestbook/forms/Entry.php:
<?php class Guestbook_Form extends Digitalus_Form { public function __construct($options = null) { parent::__construct($options); $view = $this->getView(); $id = $this->createElement('hidden', 'id'); $name = $this->createElement('text', 'name'); $name->setAttrib('size', 40) ->setRequired('true') ->setLabel($view->getTranslation('Guestbook Name') . ':'); $submit = $this->createElement('submit', 'submit'); $this->setMethod('post') ->addElement($id) ->addElement($name) ->addElement($submit); } }
What we need to do then, is to modify the IndexController as it's the starting point for the backend and the related view scripts. /application/modules/guestbook/controllers/IndexController.php:
<?php require_once APPLICATION_PATH . '/modules/guestbook/forms/Guestbook.php'; class Mod_Guestbook_IndexController extends Zend_Controller_Action { public function init() { $this->view->breadcrumbs = array( $this->view->getTranslation('Modules') => $this->view->getBaseUrl() . '/admin/module', $this->view->getTranslation('Guestbook') => $this->view->getBaseUrl() . '/mod_guestbook' ); $this->view->toolbarLinks['Add to my bookmarks'] = $this->view->getBaseUrl() . '/admin/index/bookmark/url/mod_guestbook'; } public function indexAction() { $guestbookForm = new Guestbook_Form(); $guestbookForm->setAction($this->view->getBaseUrl() . '/mod_guestbook/guestbook/create'); $submit = $guestbookForm->getElement('submit'); $submit->setLabel($this->view->getTranslation('Create Guestbook')); $this->view->form = $guestbookForm; } }
/application/modules/guestbook/views/scripts/index/index.phtml:
<?php $this->placeholder('formHeadline')->set($this->getTranslation('Guestbook Module'));?> <p><?php echo $this->getTranslation('The guestbook module enables you to publish multiple guestbooks on your site.');?></p> <fieldset> <legend><?php echo $this->getTranslation('Create a new guestbook');?></legend> <?php echo $this->form; ?> </fieldset>
12/26
The action for the HTML form leads to the createAction of the GuestbookController. Both don't exist so far. Also, a guestbook model would be a good idea. Let's start with the model. /application/modules/guestbook/models/Guestbook.php:
<?php class Guestbook_Guestbook extends Model_Page { protected $_namespace = 'guestbook'; public function createGuestbook($name) { return $this->createPage($name); } public function updateGuestbook($id, $name) { $data = array( 'page_id' => $id, 'name' => $name ); return $this->edit($data); } public function deleteGuestbook($id) { $this->deletePageById($id); } public function getGuestbooks() { $select = $this->select(); $select->where('namespace = ?', $this->_namespace); $select->order('name'); $result = $this->fetchAll($select); if ($result->count() > 0) { return $result; } return null; } }
This looks very similar to the Model_Entry model. New is the getGuestbooks method, which retrieves all guestbooks from the database. We will use it later.
13/26
class Mod_Guestbook_GuestbookController extends Zend_Controller_Action { public function init() { $this->view->breadcrumbs = array( $this->view->getTranslation('Modules') => $this->view->getBaseUrl() . '/admin/module', $this->view->getTranslation('Guestbook') => $this->view->getBaseUrl() . '/mod_guestbook' ); $this->view->toolbarLinks['Add to my bookmarks'] = $this->view->getBaseUrl() . '/admin/index/bookmark/url/mod_guestbook'; } public function createAction() { $form = new Guestbook_Form(); if ($form->isValid($_POST)) { $values = $form->getValues(); $mdlGuestbook = new Guestbook_Guestbook(); $guestbook = $mdlGuestbook->createGuestbook($values['name']); $this->_request->setParam('id', $guestbook->id); $this->_request->setParam('isInsert', true); $this->_forward('edit'); } else { $this->_forward('index', 'index'); } } public function editAction() { $form = new Guestbook_Form(); $mdlGuestbook = new Guestbook_Guestbook(); $mdlEntry = new Guestbook_Entry(); if ($this->_request->isPost() && $form->isValid($_POST) && $this->_request->getParam('isInsert') != true) { $values = $form->getValues(); $guestbook = $mdlGuestbook->updateGuestbook($values['id'], $values['name']); $guestbook = $guestbook->page; } else { $id = $this->_request->getParam('id'); $guestbook = $mdlGuestbook->find($id)->current(); $form->populate($guestbook->toArray()); } $this->view->form = $form; $this->view->guestbook = $guestbook; $this->view->entries = $mdlEntry->getEntries($guestbook->id); $this->view->breadcrumbs[$guestbook->name] = $this->view->getBaseUrl() . '/mod_guestbook/guestbook/edit/id/' . $guestbook->id; $this->view->toolbarLinks['Delete'] = $this->view->getBaseUrl() . '/mod_guestbook/guestbook/delete/id/' . $guestbook->id; } public function deleteAction() { $id = $this->_request->getParam('id'); $mdlGuestbook = new Guestbook_Guestbook(); $mdlGuestbook->deletePageById($id); $this->_forward('index', 'index'); } }
Okay, now we're doing it a little quicker. The init() method is well known by now.
14/26
The createAction checks whether the submitted form is valid. If so, a new guestbook is created via the Model_Guestbook and we redirect to the editAction If not, we redirect to the indexAction of the IndexController. We don't need a view script for the createAction because we do not display anything but redirect promptly to the editAction. The interesting things are done within the editAction. Again, we check whether the HTML form is submitted and valid. If not, we search the database for the current guestbook and populate it's content into the HTML form, so we can edit it. If it is submitted, we retrieve the contents of the HTML form and update the guestbook with it. Last but not least, the view script(s): /application/modules/guestbook/views/scripts/guestbook/edit.phtml:
<?php $this->placeholder('formHeadline')->set($this->getTranslation('Update Guestbook'));?> <fieldset> <legend><?php echo $this->getTranslation('Update Guestbook');?></legend><?php echo $this->form;?> </fieldset>
We're ready to create and update a guestbook. Using specific guestbooks for pages Now, we want to assign our freshly created guestbook to a specific page. What exactly do we want to do? We have created a page called MyGuestbookPage. And we have created a guestbook called MyGuestbook. When we are editing the page MyGuestbookPage, we want to select the module guestbook for the page and we want to select the guestbook MyGuestbook. First of all, we create a view helper for this task. In /application/modules/guestbook/views/helpers create the file SelectGuestbook.php with following content. /application/modules/guestbook/views/helpers/SelectGuestbook.php:
<?php class Zend_View_Helper_SelectGuestbook extends Zend_View_Helper_Abstract { public function selectGuestbook ($name, $value) { $mdlGuestbook = new Guestbook_Guestbook(); $guestbooks = $mdlGuestbook->getGuestbooks(); if ($guestbooks == null) { return $this->view->getTranslation('There are no guestbooks to view!'); } else { $options[] = $this->view->getTranslation('Select One'); foreach($guestbooks as $guestbook) { $options[$guestbook->id] = $guestbook->name; } return $this->view->formSelect($name, $value, null, $options); } } }
The view helper is automatically registered by the Digitalus CMS. It creates a dropdown menu with a list of all existing guestbooks. As mentioned above, we need the following view script to call an HTML form element.
15/26
/application/modules/guestbook/views/scripts/index/index.options.phtml:
<?php $this->placeholder('optionsHeadline')->set($this->getTranslation('My Guestbooks')); if ($this->guestbooks != null) { echo '<ul class="padding-10">'; foreach ($this->guestbooks as $guestbook) { echo '<li>' . $this->link($guestbook->name, '/mod_guestbook/guestbook/edit/id/' . $guestbook->id, 'book.png') . '</li>'; } echo '</ul>'; } else { echo '<p class="padding-10">' . $this->getTranslation('You do not have any guestbooks currently') . '</p>'; }
But how does the page know the guestbook id? Well, that is magic! The id of the selected guestbook is stored in the database and can be retrieved with the Digitalus_Module's getData() method. Remember the init() method in our PublicController? That's exactly what we did there. Saving the entry's content The content of our entry, i.e. author name, email, and text are saved into the database as content nodes. They get the parent_id of the created entry and can be retrieved with the knowledge of this id. Modify the PublicController as follows. /application/modules/guestbook/controllers/PublicController.php:
<?php require_once APPLICATION_PATH . '/modules/guestbook/forms/Entry.php'; require_once APPLICATION_PATH . '/modules/guestbook/models/Guestbook.php'; require_once APPLICATION_PATH . '/modules/guestbook/models/Entry.php'; class Mod_Guestbook_PublicController extends Zend_Controller_Action {
16/26
if ($this->_request->isPost() && $entryForm->isValid($_POST)) { $values = $entryForm->getValues(); $entry = $mdlEntry->createEntry($values['guestbook_id'], substr($values['content'], 0, 250)); $arrayContent = array( 'page_id' => $values['guestbook_id'], 'id' => $entry->id, 'author' => $values['author'], 'email' => $values['email'], 'content' => $values['content'], ); $entry = $mdlEntry->edit($arrayContent); } $this->view->form = $entryForm; } }
Now that we know the guestbook id, we can use it to store the guestbook entry's content at the right place. We create an array with the values to be stored and use the edit() method to store it into the database. Also, we pass the entries for the current guestbook and the guestbook itself to the view. We will need it later.
17/26
We added a variable to the view that contains all existing guestbooks. This variable will be used within the index.options.phtml to display a list with them. /application/modules/guestbook/views/scripts/index/index.options.phtml:
<?php $this->placeholder('optionsHeadline')->set($this->getTranslation('My Guestbooks')); if ($this->guestbooks != null) { echo '<ul class="padding-10">'; foreach ($this->guestbooks as $guestbook) { echo '<li>' . $this->link($guestbook->name, '/mod_guestbook/guestbook/edit/id/' . $guestbook->id, 'book.png') . '</li>'; } echo '</ul>'; } else { echo '<p class="padding-10">' . $this->getTranslation('You do not have any guestbooks currently') . '</p>'; }
Now let' display the entries when modifying a specific guestbook. We have to modify the edit.options.phtml in our guestbook directory for that task. /application/modules/guestbook/views/scripts/guestbook/edit.options.phtml:
<?php $this->placeholder('optionsHeadline')->set($this->getTranslation('Guestbook Entries')); if ($this->entries != null) { echo '<ul class="padding-10">'; foreach ($this->entries as $entry) { echo '<li>' . $this->link(date('d.m.Y', $entry->createDate) . ' - ' . $entry->author, '/mod_guestbook/entry/edit/id/' . $entry->id, 'page_white_text.png') . '</li>'; } echo '</ul>'; } else {
18/26
We take the view variable entries and loop through it if it exists. We have defined the variable entries in our GuestbookController. For each entry a link is created. The reference of the link is something like /mod_guestbook/entry/edit/id/$entryId.
19/26
Most of the stuff is the same or almost the same as for the GuestbookController. We have an init() method, an editAction and a deleteAction. Init() method and deleteAction should be clear. What's happening in the EditAction? Well, we display the Entry_Form if it is not submitted or if the form is not valid. Otherwise, the entry is updated using an updateEntry() method of the Guestbook_Entry model that doesn't exist by now. Let's create it. /application/modules/guestbook/models/Entry.php:
<?php class Guestbook_Entry extends Model_Page { protected $_namespace = 'guestbook_entry'; ... public function updateEntry($entryId, $author, $email, $content) { $data = array( 'page_id' => $entryId, 'author' => $author, 'email' => $email, 'content' => $content, ); $this->edit($data); } }
The method simply uses the edit() method of the parent class Model_Page ; it updates the author, email and content of the entry. The entry can be deleted by the Delete link at the top right of the page (that's the default place for all Digitalus deletion actions).
20/26
21/26
Well, we still display the form at the top of the page. If a guestbook exists, we display a header and if entries exist we loop through them using a partialLoop. We simply pass the entries to our partial and let it to the rest. That's how our partial looks like. /application/modules/guestbook/views/scripts/partials/entry.phtml:
<?php ?> <h5 class="entry_title"><?php echo $this->escape(this->author) . ' ' . $this->getTranslation('wrote on'). ' ' . $this->renderDate($this->createDate);?></h5> <blockquote> <p><?php echo $this->content . PHP_EOL;?> </p> </blockquote>
We take the passed values and create a title for each entry followed by the content itself. To give the website visitor a visual note that his entry was stored to the database, we make some small changes to the PublicController In the indexAction. /application/modules/guestbook/controllers/PublicController.php:
<?php require_once APPLICATION_PATH . '/modules/guestbook/forms/Entry.php'; require_once APPLICATION_PATH . '/modules/guestbook/models/Guestbook.php'; require_once APPLICATION_PATH . '/modules/guestbook/models/Entry.php'; class Mod_Guestbook_PublicController extends Zend_Controller_Action { public $moduleData; public $properties; public function init() { $module = new Digitalus_Module(); $this->moduleData = $module->getData(); $this->properties = Digitalus_Module_Property::load('mod_guestbook'); } public function indexAction() { $entryForm = new Entry_Form();
22/26
if ($this->_request->isPost() && $entryForm->isValid($_POST)) { $values = $entryForm->getValues(); $entry = $mdlEntry->createEntry($values['guestbook_id'], substr($values['content'], 0, 100)); $arrayContent = array( 'page_id' => $values['guestbook_id'], 'id' => $entry->id, 'author' => $values['author'], 'email' => $values['email'], 'content' => $values['content'], ); $entry = $mdlEntry->edit($arrayContent); $this->view->form = $this->view->partial('partials/message.phtml', $arrayContent); } else { $this->view->form = $entryForm; } } }
If the form is submitted successfully, not the form is displayed, but again a partial that looks as follows: /application/modules/guestbook/views/scripts/partials/message.phtml:
$this->getTranslation('thank You for <?php ?> <div class="message"> <p><?php echo '<em>' . $this->escape($this->author) . '</em>, ' . Your entry in the guestbook!');?></p> </div>
Very simple; we take the passed value author to create a short message. Partials have the advantage that You can split Your view scripts into smaller parts that can be used over and over again. The drawback is the worse performance.
23/26
24/26
Only alphanumeric characters allowed? Well, that doesn't really make sense, but it's only for demonstrating purposes.
25/26
Translations
Finally, let's add a translation file. Create the file /application/module/guestbook/data/language/german.csv. If You're choosing German as Your administrator language, the contents of this file will appear. /application/modules/guestbook/data/language/german.csv:
# admin section -- Module guestbook -# GERMAN # admin section -- Entry -"Guestbook";"Gstebuch" "guestbook";"Gstebuch" "Update Guestbook Entry";"Gstebuch Eintrag aktualisieren "Guestbook Entry";"Gstebuch Eintrag" "Back to";"Zurck zu" "Update Entry";"Eintrag aktualisieren" # admin section -- Guestbook -"Guestbook Entries";"Gstebuch Eintrge" "This guestbook does not have any entries";"Dieses Gstebuch enthlt keine Eintrge" "Update Guestbook";"Gstebuch aktualisieren" "Guestbook Name";"Name des Gstebuchs" # admin section -- Index -"The guestbook module enables you to publish multiple guestbooks on your site.";"Das Gstebuch ermglicht es Ihnen, mehrere Gstebcher auf Ihren Seiten zu verffentlichen." "Create Guestbook";"Gstebuch erstellen" "My Guestbooks";"Meine Gstebcher" "You do not have any guestbooks currently";"Es gibt momentan keine Gstebcher" "Guestbook Module";"Gstebuch Modul" "The guestbook module enables you to publish multiple guestbooks on your site." "Create a new guestbook";"Erstellen eines neuen Gstebuchs" # admin section -- Public -"Create Guestbook Entry";"Gstebuch Eintrag erstellen" "Select a guestbook";"Gstebuch auswhlen" "Current entries in guestbook ";"Aktuelle Eintrge im Gstebuch " "Number of entries: ";"Anzahl der Eintrge: "
26/26
Properties
Each module has a properties.xml file. This file can define any information that might be required for the module: /application/modules/guestbook/properties.xml:
<?xml version="1.0" encoding="UTF-8"?> <properties> <icon>book_open.png</icon> <label>Guestbook</label> </properties>
The icon is used in the backend for the module. The label is shown on the module tab in the backend. You load the properties using the Digitalus_Module_Property class:
$props = Digitalus_Module_Property::load('mod_guestbook');
ACL
You can tie your module into the core CMS access control system by adding an acl.xml file. There are 3 levels of control in this model: 1. Module level: you don't have to do anything for this 2. Controller level: you list the controllers 3. Action level: you list the controller actions as well /application/modules/guestbook/acl.xml:
<?xml version="1.0" encoding="UTF-8"?> <controllers> <controller name='index'> <action>index</action> </controller> <controller name='entry'> <action>delete</action> <action>edit</action> </controller> <controller name='guestbook'> <action>create</action> <action>delete</action> <action>edit</action> </controller> </controllers>
Summary
The Digitalus CMS provides a smooth way to enhance the base system with custom user modules. In this tutorial You learned how to build Your own simple guestbook module. Until now, we have a basic application. It is not perfect but a good starting point. Further enhancements could be: more advanced design custom form decorators notify admin for each new entry (per email) deploy publishing system allow HTML tags for content (perhaps using HTML Purifier)