Skip to content

Building a Wiki Part 1

Scott edited this page Nov 22, 2013 · 3 revisions

In this tutorial, we will create a very basic wiki which will allow anyone to modify the wiki pages. The idea of having a Wiki created in QCubed has been a subject of conversation many times. But in fact, a wiki is very easy to do in QCubed, so why not build one?

For this tutorial, we will assume that you have a working copy of QCubed installed. If not, get the latest copy.

Creating the database model

With QCubed it all starts from the database model. So to start, the database model is the first thing we will create. For our wiki, the database design is relatively simple, at least, to start with. In this tutorial, we will limit the database design to a single table. This table should hold the contents of the wiki pages. Each page is identified through a wiki word. This word is unique per page in the database. With each wikiword, we also have the contents of our page. So our basic table will hold 2 fields: title (the wiki word) and contents (the contents of the page). We also add an id to the table, as well as a unique key on the title.

CREATE TABLE  `wiki`.`page` (  
	`id` int(10) unsigned NOT NULL auto_increment,  
	`title` varchar(45) NOT NULL default '',  
	`contents` text NOT NULL,
	PRIMARY KEY  (`id`),  
	UNIQUE KEY `title` USING BTREE (`title`)
) 
ENGINE=MyISAM AUTO_INCREMENT=9 DEFAULT CHARSET=latin1;

Code generation

After we have created the table in our database, we can let QCubed perform code generation. you can do this now by going to the QCubed start page and use the link to "/assets/_core/php/_devtools/codegen.php". After Code generation, QCubed will have created the data objects for our page table, as well as some default CRUD functionality for the table. The html pages 'drafts' folder, where you will find the html pages for listing, creating, modifying and deleting 'page' records. For our wiki, we don't need any of the default generated pages, as we will create our own page to display the wiki page.

Creating our wiki page

Our wiki will consist of a single PHP script, which will handle all functionality of displaying and editing the wiki page. Let's create this basic page called "wiki.php" and its template: "wiki.tpl.php"

wiki.php:

<?php
// Load QCubed
require('includes/prepend.inc.php');

class WikiForm extends QForm {
    protected function Form_Create() {
    }

}

WikiForm::Run('WikiForm');
?>

wiki.tpl.php

<!DOCTYPE html>
<html>
<head>
<meta charset=<?php _p(QApplication::$EncodingType); ?>" />
<title>QCubed Wiki Example</title>
<style type="text/css">@import url("<?php _p(__VIRTUAL_DIRECTORY__ . __CSS_ASSETS__); ?>/styles.css");</style>
</head>
<body>
<?php $this->RenderBegin(); ?>
<?php $this->RenderEnd(); ?>
</body>
</html>

For now, when you load this page, it will display a blank page, as we did not add anything to the page yet.

Adding controls

For our page to do something, we will need to add several controls. The first thing we want our page to do is display the contents of the page we are at. If this page is not present, a default message should be displayed. Let's begin with adding this functionality.

Adding the database object.

QCubed has created many functions to retrieve the contents from our table. We will now add a member variable to our QWikiForm class to hold the data from the table.

...
class WikiForm extends QForm {
    protected $objWikiPage;

    protected function Form_Create() {
...

The first time a QCubed PHP script is loaded, it will call the Form_Create() function within the QForm class, so we put our initialization of the object in here. The initialization of the database object will retrieve the contents of the page based on the URL. We can do this using the following code:

...
    protected function Form_Create() {
        $this->objWikiPage = Page::LoadByTitle(QApplication::QueryString('page'));
}
...

Let's break down this single line:

  • Page is an object which is created by QCubed during code generation.
  • LoadByTitle() is a static function created by QCubed during code generation. The function takes one parameter (the page title) and will return a Page object, which will contain the contents of the database record.
  • QApplication::QueryString() is an array in which QCubed has stored all $_GET[] and $_POST[] parameters that were used to access the PHP script.

After this line of code is executed, the $objWikiPage member variable will contain a page object. If the page object cannot be found (which will be the case in our situation, since we have no contents in our Wiki yet), the page object will be "null".

Adding the title and contents label

Our wiki page should display the title and the contents on our page. To do this, we will create two labels on the page (one for the title and one for the contents) by adding two member variables to the class, and initializing them in the Form_Create() just like before:

...
    protected $objWikiPage;
    protected $lblTitle;
    protected $lblContents;

    protected function Form_Create() {
        $this->objWikiPage = Page::LoadByTitle(QApplication::QueryString('page'));

        $this->lblTitle = new QLabel($this);
        $this->lblTitle->Text = QApplication::QueryString('page');
	  
        $this->lblContents = new QLabel($this);
		  
        if ($this->objWikiPage){
            $this->lblContents->Text = $this->objWikiPage->Contents;
        } else {
            $this->lblContents->Text = "This page does not exist yet.";		  
        }
...

In our form create, we also assigned some logic to display the contents of the page based on the existence of the database record in the table. If the record for the page does not exist, we display a message that says so.

To display these labels, we also need to render them in the template file:

...
    <?php $this->RenderBegin(); ?>
    <?php $this->lblTitle->Render() ?>
    <?php $this->lblContents->Render() ?>
    <?php $this->RenderEnd(); ?>
...

If we would run this page now, we would see one line which displays the text "This page does not exist yet".

Retrieve initial page

The first time the wiki is loaded, no parameters will be passed to the url, as it will look something like: " http://localhost/wiki/wiki.php" We need to change the behavior of the application to load a default page (WikiStart) when no parameters have been passed through the URL:

...
    protected function Form_Create() {
    
        if (QApplication::QueryString('page') === '') {
            $strPage = 'WikiStart';
        }else{
            $strPage = QApplication::QueryString('page');
        }
        $this->objWikiPage = Page::LoadByTitle($strPage);
    
        $this->lblTitle = new QLabel($this);
        $this->lblTitle->Text = $strPage;
    
        $this->lblContents = new QLabel($this);
    
        if ($this->objWikiPage) {
            $this->lblContents->Text = $this->objWikiPage->Contents;
        } else {
            $this->lblContents->Text = "This page does not exist yet.";
        }
    }
...

When we now reload the page, we will see the page title "WikiStart" as well!

Editing the page

Add controls

Our next step would be to edit/create the page we are currently displaying. For this, we will need: a text input field where we can edit the page, buttons to create/edit the page, and to save/cancel the edit.

Again, we will create member variables within the WikiForm class, and initialize them in the Form_Create() function. Afterwards, we will also render them in the template file. Note that all these new controls have the property "Visible" set to false (except for the $btnEditPage). This will cause these controls NOT to be rendered. We only want to display them when we click the $btnEditPage button.

...
    protected function Form_Create() {

        $this->txtEditContents = new QTextBox($this);
        if ($this->objWikiPage){
            $this->txtEditContents->Text = $this->objWikiPage->Contents;
        } else {
            $this->txtEditContents->Text = "This page does not exist yet.";
        }
        $this->txtEditContents->Visible = false;

        $this->btnEditPage = new QButton($this);
        $this->btnEditPage->Text = "Edit";

        $this->btnSavePage = new QButton($this);
        $this->btnSavePage->Text = "Save";
        $this->btnSavePage->Visible = false;

        $this->btnCancelEdit = new QButton($this);
        $this->btnCancelEdit->Text = "Cancel";
        $this->btnCancelEdit->Visible = false;
...
...
<?php $this->RenderBegin(); ?>
<?php $this->lblTitle->Render(); ?><br />
<?php $this->lblContents->Render(); ?><br />
<?php $this->txtEditContents->Render(); ?><br />
<br />
<? $this->btnEditPage->Render() ?> <? $this->btnSavePage->Render() ?> <? $this->btnCancelEdit->Render() ?>
<?php $this->RenderEnd(); ?>
...

Assign actions

Now that we have the buttons, we can assign actions to them. When the edit button is clicked, the Edit button and $lblContent should be hidden, and the $txtEditContent, Save and Cancel should be displayed. When the cancel button is clicked, the reverse should happen. When the Save button is clicked, the contents of the textbox should be saved to the database and copied over to the label to display it. We then call the cancel button routine, which will take care of resetting the buttons and labels to the correct state.

...
        $this->txtEditContents = new QTextBox($this);
        if ($this->objWikiPage){
            $this->txtEditContents->Text = $this->objWikiPage->Contents;
        } else {
            $this->txtEditContents->Text = "This page does not exist yet.";
        }
        $this->txtEditContents->Visible = false;
    
        $this->btnEditPage = new QButton($this);
        $this->btnEditPage->Text = "Edit";
        $this->btnEditPage->AddAction(new QClickEvent(), new QServerAction('btnEditPage_Click'));
    
        $this->btnSavePage = new QButton($this);
        $this->btnSavePage->Text = "Save";
        $this->btnSavePage->Visible = false;
        $this->btnSavePage->AddAction(new QClickEvent(), new QServerAction('btnSavePage_Click'));
    
        $this->btnCancelEdit = new QButton($this);
        $this->btnCancelEdit->Text = "Cancel";
        $this->btnCancelEdit->Visible = false;
        $this->btnCancelEdit->AddAction(new QClickEvent(), new QServerAction('btnCancelEdit_Click'));
    
    }

    protected function btnEditPage_Click($strFormId, $strControlId, $strParameter) {
        $this->txtEditContents->Visible = true;
        $this->lblContents->Visible = false;
        $this->btnEditPage->Visible = false;
        $this->btnCancelEdit->Visible = true;
        $this->btnSavePage->Visible = true;
    }

    protected function btnSavePage_Click($strFormId, $strControlId, $strParameter) {
        if (!$this->objWikiPage){
            $this->objWikiPage = new Page();
        }
        $this->objWikiPage->Title = $this->lblTitle->Text;
        $this->objWikiPage->Contents = $this->txtEditContents->Text;
        $this->objWikiPage->Save();
        $this->lblContents->Text = $this->objWikiPage->Contents;
        $this->btnCancelEdit_Click($strFormId, $strControlId, $strParameter);
    }

    protected function btnCancelEdit_Click($strFormId, $strControlId, $strParameter) {
        $this->txtEditContents->Visible = false;
        $this->lblContents->Visible = true;
        $this->btnEditPage->Visible = true;
        $this->btnCancelEdit->Visible = false;
        $this->btnSavePage->Visible = false;
    }
...

WikiWords

All this will allow us to start editing the main "WikiStart" page. But a Wiki consists of more pages then just the front page. Links within a wiki are created by using WikiWords. Whenever a WikiWord is detected, it should be replaced with a link to the corresponding wiki page. To do this, we will use a regular expression to scan for WikiWords, and replace it with a link.

protected function FindWikiWords($contents) {
    $page = $_SERVER["SCRIPT_NAME"];
    $contents = ereg_replace("( ){1}(([A-Z][a-z0-9]+){2,})", " <a href=\"" . $page . "?page=\\2\">\\2</a>", $contents);
    return $contents;
}

Every time we set the Text of the $lblContents, we should call this function:

if ($this->objWikiPage) {
    $this->lblContents->Text = $this->FindWikiWords($this->objWikiPage->Contents);
} else {
    $this->lblContents->Text = "This page does not exist yet.";
}
protected function btnSavePage_Click($strFormId, $strControlId, $strParameter) {
    if (!$this->objWikiPage) {
        $this->objWikiPage = new Page();
    }
    $this->objWikiPage->Title = $this->lblTitle->Text;
    $this->objWikiPage->Contents = $this->txtEditContents->Text;
    $this->objWikiPage->Save();
    $this->lblContents->Text = $this->FindWikiWords($this->objWikiPage->Contents);
    $this->btnCancelEdit_Click($strFormId, $strControlId, $strParameter);
}

HtmlEntities

The above function will work fine, but instead of displaying a link, it will display the HTML code within the Contents label. This is because by default, QCubed will perform an htmlentities() function on the text. If you do not want this, we need to disable it. This can be done by setting the property HtmlEntities to false for the $lblContents during initialization:

$this->lblContents = new QLabel($this);
$this->lblContents->HtmlEntities = false;

if ($this->objWikiPage) {
    $this->lblContents->Text = $this->FindWikiWords($this->objWikiPage->Contents);
} else {
    $this->lblContents->Text = "This page does not exist yet.";
}

End of part one

We now have a fully operational wiki!

See the full code.

In the next part:

  • Improve wiki formatting
  • Add basic navigation
  • Improve look and feel by applying CSS styles
  • Improving our code
  • Adding Ajax

Go to Part 2