A WikkaCake Example


This page is deprecated.
This example assumes CakePHP 1.1. An updated version for WikkaCake 1.3 is in the works.

See also: WikkaCake


Overview

If you have yet to read the WikkaCake page, I'd suggest starting there first. This is actually an extension of that page, and provides an example of a completely self-contained Wikka action that has been written using the CakePHP framework. This is not a Cake tutorial. Instead, I will simply provide the code as-is and provide comments that are Wikka-specific.

If one has already set up and tested an embedded WikkaCake environment, then this application should run unmodified (in fact, it is the exact code that's currently running on my own machine).

All directory names are referenced from the top-level "caketest" directory.

This application is a simple database management app for saving, modifying, and deleting servers. It was developed for managing the public nameserver DB on the OpenNIC wiki. A publicly accessible version will be available shortly once testing is completed.

config/database.php
    var $default = array('driver' => 'mysql',
                                'connect' => 'mysql_pconnect',
                                'host' => 'localhost',
                                'login' => 'root',
                                'password' => 'root',
                                'database' => 'wikka_cake',
                                'prefix' => '');

Comments: This is the setup I use on my personal machine (no, it's not externally accessible, so I'm not giving away any state secrets here). Obviously, you will need to modify accordingly. If you haven't done so, simply copy config/database.php.default to config/database.php and modify that. Eventually, this will go away once I work on seamlessly accessing the Wikka DB authentication params.

public_access_servers.sql
CREATE TABLE IF NOT EXISTS public_access_servers (
    id              INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
    ip_addr         VARCHAR(15) NOT NULL,
    internic_hn     VARCHAR(50),
    opennic_hn      VARCHAR(50),
    owner           VARCHAR(50) NOT NULL,
    city            VARCHAR(50),
    state           CHAR(2),
    country         CHAR(2) NOT NULL,
    email           VARCHAR(50),
    last_verified   DATE,
    comments        LONGTEXT);

Comments: This is the DB schema upon which the following code is based. If you modify this, there's a good chance you'll need to modify one or more files below. Note that Cake expect, by default, a primary key called "id".

models/server.php
<?php
    class Server extends AppModel
    {
        var $name = 'Server';
        var $useTable = "public_access_servers";

        var $validate = array(
            'ip_addr' => VALID_NOT_EMPTY,
            'owner' => VALID_NOT_EMPTY,
            'country' => VALID_NOT_EMPTY
        );
    }
?>

Comments: $useTable reflects that fact that I've named my DB table in a non-conforming way.

controllers/servers_controller.php
<?php
    class ServersController extends AppController
    {
        var $name = 'Servers';
        var $layout = 'default';
        //var $wikka = $this->params['wakka'];
        //var $scaffold;

        /*
        function ServersController()
        {
            parent::__construct();
            // This doesn't work, as the params array isn't yet
            // available at time of class object construction!
            //$this->wikka = parent::$this->params['wakka'];
        }
        */


        function index()
        {
            $this->set('servers', $this->Server->findAll());
        }
        function add()
        {  
            if(false===$this->params['wakka']->IsAdmin())
            {  
                $this->redirect($this->base);
            }
            if(true===isset($this->data['Servers']['action']) &&
               false!==strpos($this->data['Servers']['action'], 'Cancel'))
            {  
                $this->redirect($this->base);
                exit;
            }
            uses('sanitize');
            $sanitize = new Sanitize();
            if(empty($this->data))
            {
                $this->render();
            }
            if(!empty($this->data))
            {
                $sanitize->cleanArray($this->data);
                if($this->Server->save($this->data))
                {
                    $this->flash('Your entry has been saved.', '', 1);
                }
                else
                {
                    $this->set('errorMessage', 'Please correct errors below.');
                    $this->render();
                }
            }
        }

        function delete()
        {
            if(false===$this->params['wakka']->IsAdmin())
            {
                $this->redirect($this->base);
            }

            if(isset($this->params['url']['id']))
            {
                $this->Server->del($this->params['url']['id']);
            }
            $this->redirect($this->base);
            exit;
        }

        function modify()
        {
            if(false===$this->params['wakka']->IsAdmin())
            {
                $this->redirect($this->base);
            }

            $id = null;
            if(isset($this->params['url']['id']))
            {
                $id = $this->params['url']['id'];
            }
            if(empty($this->data))
            {
                $this->Server->id = $id;
                $this->data = $this->Server->read();
            }
            else
            {
                uses('sanitize');
                $sanitize = new Sanitize();
                if(empty($this->data))
                {
                    $this->render();
                }
                if(!empty($this->data))
                {
                    $sanitize->cleanArray($this->data);
                    if($this->Server->save($this->data))
                    {
                        $this->flash('Your entry has been saved.', '', 1);
                    }
                    else
                    {
                        $this->set('errorMessage', 'Please correct errors below.');
                        $this->render();
                    }
                }
            }
        }
    }
?>

Comments: Look back at the changes you made in your webroot/index.php code. Remember this?

array('wakka'=>(object)$this)


That is how the Wikka instance is passed into the Cake framework so it's accessible in the controller (where the business logic resides). The commented-out construction above indicates that due to the way the Cake core libraries are bootstrapped, the Wikka instance isn't available until *after* the constructor has been called. Nor can it be set as a var (since only static assignments can be made to var-declared variables). So you'll need to call your Wikka lib functions within each method from which you need access.

I could have chosen to use the data filters provided by Wikka, but for simplicity sake I used the Cake built-in "sanitize" class. Also, note that the $this->flash(...) call doesn't redirect properly due to some interaction between Wikka and Cake. The link displays just fine, but one has to actually click the link for the redirect to occur.

views/layouts/default.thtml
<?php echo $content_for_layout; ?>

Comments: We don't want Cake to output any additional HTML <head> sections, as this is already handled by Wikka.

views/servers/index.thtml
<h1>Public Access Servers</h1>
<br/>
<table border="1">
<tr>
</tr>
<?php foreach ($servers as $server): ?>
<tr>
<?php if(true
$this->params['wakka']->IsAdmin()): ?>
<td><a class="keys" href="<?php echo $html->base.'?action=delete&id='.$server['Server']['id'] ?>">X</a> 
<a class="keys" href="<?php echo $html->base.'?action=modify&id='.$server['Server']['id'] ?>">M</a></td>
<?php endif; ?>
<td><?php echo $server['Server']['ip_addr']; ?></td>
<td><?php echo $server['Server']['internic_hn']; ?></td>
<td><?php echo $server['Server']['opennic_hn']; ?></td>
<td><?php echo $server['Server']['owner']; ?></td>
<td><?php echo $server['Server']['city']; ?></td>
<td><?php echo $server['Server']['state']; ?></td>
<td><?php echo $server['Server']['country']; ?></td>
<td><?php echo preg_replace('/@/', ' <at> ', $server['Server']['email']); ?></td>
<td><?php echo $server['Server']['last_verified']; ?></td>
<td><?php echo $server['Server']['comments']; ?></td>
</tr>
<?php endforeach; ?>
</table>
<?php ?>

Comments: Note that the Wikka instance is accessible in the views for whatever you might need it for. Also, because of the conflicting way in which Wikka and Cake handle the parsing of URLs, links must be generated using the format shown above. Attempting to format Cake links as per the Cake manual is sure to lead to abject failure.

views/servers/add.thtml
<h1>Add Server</h1>
<form method="post" action="<?php echo $html->base; ?>?action=add">
<p>
IP Addr:
<?php echo $html->input('Server/ip_addr', array('size'=>'15')) ?>
<?php echo $html->tagErrorMsg('Server/ip_addr', 'IP address is required.') ?>
</p><p>
Owner:
<?php echo $html->input('Server/owner', array('size'=>'50')) ?>
<?php echo $html->tagErrorMsg('Server/owner', 'Owner is required.') ?>
</p><p>
City:
<?php echo $html->input('Server/city', array('size'=>'50')) ?>
</p><p>
State/Province:
<?php echo $html->input('Server/state', array('size'=>'2')) ?>
</p><p>
Country:
<?php echo $html->input('Server/country', array('size'=>'2')) ?>
<?php echo $html->tagErrorMsg('Server/country', 'Country is required.') ?>
</p><p>
Email (will be obfuscated):
<?php echo $html->input('Server/email', array('size'=>'50')) ?>
</p><p>
Comments:
<?php echo $html->textarea('Server/comments', array('rows'=>'2')) ?>
</p><p>
<?php echo $html->submit('Save') ?>
<?php echo $html->submit('Cancel', array('name'=>'data[Servers][action]')) ?>
</p></form>

Comments: Handling the Cancel buttons was somewhat tricky.

views/servers/modify.thtml
<h1>Modify Server</h1>
<form method="post" action="<?php echo $html->base; ?>?action=modify">
<?php echo $html->hidden('Server/id'); ?>
<p>
IP Addr:
<?php echo $html->input('Server/ip_addr', array('size'=>'15')) ?>
<?php echo $html->tagErrorMsg('Server/ip_addr', 'IP address is required.') ?>
</p><p>
Owner:
<?php echo $html->input('Server/owner', array('size'=>'50')) ?>
<?php echo $html->tagErrorMsg('Server/owner', 'Owner is required.') ?>
</p><p>
City:
<?php echo $html->input('Server/city', array('size'=>'50')) ?>
</p><p>
State/Province:
<?php echo $html->input('Server/state', array('size'=>'2')) ?>
</p><p>
Country:
<?php echo $html->input('Server/country', array('size'=>'2')) ?>
<?php echo $html->tagErrorMsg('Server/country', 'Country is required.') ?>
</p><p>
Email (will be obfuscated):
<?php echo $html->input('Server/email', array('size'=>'50')) ?>
</p><p>
Comments:
<?php echo $html->textarea('Server/comments', array('rows'=>'2')) ?>
</p><p>
<?php echo $html->submit('Save') ?>
<?php echo $html->submit('Cancel', array('name'=>'data[Servers][action]')) ?>
</p></form>

Comments: Straightforward, using the same trick as in add.thtml to differentiate between Save and Cancel button clicks.

Summary

So, there you have it! A functioning DB application using Wikka as the presentation framework and CakePHP as a modular MVC framework. Once one removes the config/database.php file, this could be zipped/tarred up and distributed as a plugin action.

Valid XHTML :: Valid CSS: :: Powered by WikkaWiki