Our simple PHP MVC Framework is starting to take shape. We can now manage our dependencies, our sessions, cache data, log errors, handle users, and authenticate users. We now need to start building some structure for our App. In this article, we will begin implementing a page management system that will allow us to generate navigation menus and to expand our authentication system to handle user level roles and custom page permissions.

To achieve this, we will create a new Model, which contains a Page class, as well as a PageStore, which we will use to add/edit/remove pages in a similar way to our UserStore Model.

<?PHP	// models/page.class.php

class Page extends GDSEntity {
	
}	

?>
	
<?PHP	// models/pagestore.class.php

class PageStore extends CacheStore {
	
	private $Page;
	
	public function buildSchema(){
		$Schema = new GDSSchema('Page');
		$Schema->addString('Controller', true);
		$Schema->addString('Method', true);
		$Schema->addString('Title', true);
		$Schema->addString('MenuTitle',true);
		$Schema->addString('Description', true);
		$Schema->addBoolean('ShowOnMenu', true);
		$Schema->addBoolean('Active', true);
		$Schema->addString('Parent', true);
		return $Schema;
	}
	
	public function __construct($Gateway, $Cache){
		parent::__construct($this->buildSchema(), $Gateway, $Cache);
		$this->setEntityClass('Page');
	}
}
	
?>

The files shown above are very similar to the User and UserStore classes we have seen before. I will take a moment to have a look at each of the fields we are storing, and what we will use them for.

  1. Controller – the controller field will be used to store the name of the controller to call to handle requests for the page. For example, our add user form’s Controller was ‘Users’.
  2. Method – the method field will hold the Controller method responsible for setting up and rendering our page. In our add user example, the Controller’s method was ‘add’. By specifying the Controller and Method, we can generate a URL that will load the correct page.
  3. Title – this will hold the title of the page, to be used in the Browser’s header bar, and also throughout our application.
  4. MenuTitle – this will hold a short title for the page, which will be suitable to display in the navigation menu.
  5. Description – a description of the page and it’s purpose. We can use this to display information about the page throughout our App.
  6. ShowOnMenu – a true/false value to let us know if the page should be displayed on the menu. There may be times where you need to add a page so it can be used in the permissions system, but may not be an item you want to show on the menu.
  7. Active – a true/false value to let us know if this page is active and should be shown on menus.
  8. Parent – the ID of this page’s parent page. For example, You may have a Users page that shows the index() method from the Users Controller, which would have a navigation menu of sub pages like our ‘add’ page. In this case, our ‘add’ page would have the Users/index page as its Parent.

We can now begin to add some functionality to our Model. I will start by adding an add/update form.

<?PHP	// model/pagestore.class.php -- additions only --
	
	public function getUpdateForm($New = false){
		$Form = new AppForm('UpdatePage');
		
		$UController = $UMethod = $UTitle = $UMenuTitle = $UDescription = $UParent = '';
		$UShowOnMenu = $UActive = true;
		
		if(!$New){
			if(isset($_POST['PageKey'])){
				$this->Page = $this->fetchById($_POST['PageKey']);
				$UController = $this->Page->Controller;
				$UMethod = $this->Page->Method;
				$UTitle = $this->Page->Title;
				$UMenuTitle = $this->Page->MenuTitle;
				$UDescription = $this->Page->Description;
				$UParent = $this->Page->Parent;
				$UShowOnMenu = $this->Page->ShowOnMenu;
				$UActive = $this->Page->Active;
			} else{
				throw new PageException('Trying to generate update form, but $_POST[PageKey] not set');
			}
		}
		
		$Form->add('label','labelController','Controller','Controller: ');
		$Controller = $Form->add('text','Controller', $UController);
		$Controller->set_rule(array(
			'required' => array('error','A Controller is Required'),
		));
		
		$Form->add('label','labelMethod','Method','Method: ');
		$Method = $Form->add('text','Method',$UMethod);
		$Controller->set_rule(array(
			'required' => array('error','A Controller is Required'),
		));
		
		$Form->add('label','labelParent','Parent','Parent page: ');
		$Parent = $Form->add('select','Parent',$UParent);
		$Parent->set_rule(array(
			'required' => array('error','Parent is required'),
		));
		
		$Parent->add_options(array('root' => 'This Page is parent.'));
		//add page directory/sorted array here
		$Directory = $this->getPageDirectory(true, true);
		$Options = $this->getPageOptions($Directory);
		$Parent->add_options($Options);
		
		
		$Form->add('label','labelTitle','Title','Title: ');
		$Title = $Form->add('text','Title',$UTitle);
		$Title->set_rule(array(
			'required' => array('error','A title is required'),
		));
		
		$Form->add('label','labelMenuTitle','MenuTitle','MenuTitle: ');
		$MenuTitle = $Form->add('text','MenuTitle',$UMenuTitle);
		$MenuTitle->set_rule(array(
			'required' => array('error','A Menu Title is required'),
		));
		
		$Form->add('label','labelDescription','Description','Description');
		$Description = $Form->add('textarea','Description',$UDescription);
		
		$Form->add('label','labelShowOnMenu','ShowOnMenu','Show on menu: ');
		$ShowAttributes = '';
		if($UShowOnMenu or $New){
			$ShowAttributes = array('checked' => 'checked');
		}
		$ShowOnMenu = $Form->add('checkbox','ShowOnMenu',true,$ShowAttributes);
		
		$Form->add('label','labelActive','Active','Active: ');
		$ActiveAttributes = '';
		if($UActive or $New){
			$ActiveAttributes = array('checked' => 'checked');
		}
		$Active = $Form->add('checkbox','Active',true,$ActiveAttributes);
		
		if(isset($this->Page)){
			$Form->add('hidden','PageKey',$this->Page->getKeyId());
		}
		
		$Form->add('submit','btnSubmit','Save Page');
		
		$this->UpdateForm = $Form;
		return $Form;
	}
	
	public function getPageDirectory($Active = true, $ShowOnMenu = true){
		$Pages = $this->fetchAll('SELECT * FROM Page WHERE Active = @Active AND ShowOnMenu = @ShowOnMenu', ['Active' => $Active, 'ShowOnMenu' => $ShowOnMenu]);

		//find all root pages
		$Roots = $this->findRoots($Pages);
		
		$Directory = [];
		
		foreach($Roots AS $Root){
			$Directory[] = array($Root, 1);
			$Children = $this->findChildren($Pages, $Root);
			foreach($Children AS $Child){
				$Directory[] = array($Child, 2);
				$GChildren = $this->findChildren($Pages, $Child);
				foreach($GChildren AS $GChild){
					$Directory[] = array($GChild, 3);
				}
			}
		}
		
		return $Directory;
	}
	
	public function getPageOptions($Directory){
		$Options = [];
		foreach($Directory AS $Listing){
			$Spacing = '';
			if($Listing[1] == 2){
				$Spacing = '    ';
			}
			if($Listing[1] == 3){
				$Spacing = '        ';
			}
			$Options[$Listing[0]->getKeyId()] = $Spacing.$Listing[0]->MenuTitle;
		}
		return $Options;
	}
	
	public function findRoots($Pages){
		$Roots = [];
		foreach($Pages AS $Page){
			if($Page->Parent == 'root'){
				$Roots[] = $Page;
			}
		}
		return $Roots;
	}
	
	public function findChildren($Pages, $Parent){
		$Children = [];
		foreach($Pages AS $Page){
			if($Page->Parent == $Parent->getKeyId()){
				$Children[] = $Page;
			}
		}
		return $Children;
	}
?>

Generating the add/update form is very similar to the way we generated the User add/update form. In this case however, we also need to create a drop down menu with a list of all pages that could act as parent pages. To do this, we have created 4 helper functions. The first, is getPageDirectory, which looks up all pages in the datastore, and uses the findRoots() and findChildren() methods to sort the array of pages into their structural order. The getPageOptions() method then takes the sorted array, and returns an array of PageKey and MenuTitle’s to be used to populate our drop down menu.

We can now add a Pages Controller and an AddPageView to render our new form.

<?PHP	// controllers/pages.class.php

class Pages extends Controller {
	
	public function __construct(){
		$this->Model(App::newPageStore());
	}
	
	public function add(){
		$this->View(App::newAddPageView());
		$this->View->Model($this->Model);
		$this->View->setForm($this->Model->getUpdateForm(true));
		$this->View->render();
	}

?>
	
<?PHP	// views/addpageview.class.php
	
class AddPageView extends View {
	
	private $Form;
	private $Content;
	
	public function __construct(){
		$this->setContent();
	}
	
	public function setForm($Form){
		$this->Form = $Form;
	}
	
	public function render(){
		echo $this->Content;
		if($this->Form->validate()){
			$this->Form = $this->Model->processUpdateForm();
		}
		
		$this->Form->render('*horizontal');					
	}
	
	public function setContent(){
		$Content = '<h1>Add a New Page</h1>';
		$this->Content = $Content;
	}
	
}
	
?>

The Controller and View are both very similar to the Users Controller and AddUserView we have seen previously. After adding our new Controller, Store and View to our Container class, we can run composer update, and we should be able to see an Add Page form in the browser when we visit [our app url]/Pages/add

We now need to be able to process the data submitted by the form. We can do this by creating a processUpdateForm() method in our PageStore.

<?PHP	// models/pagestore.class.php -- additions only --
	
		public function processUpdateForm(){
		if(!isset($_POST['PageKey'])){
			$this->Page = new Page();
		} else{
			$this->Page = $this->fetchById($_POST['PageKey']);
			if(!$this->Page){
				throw new PageException('Trying to process page update form, but the PageKey provided returned no valid page');
			}
		}
		
		$this->Page->Controller = $_POST['Controller'];
		$this->Page->Method = $_POST['Method'];
		$this->Page->Title = $_POST['Title'];
		$this->Page->MenuTitle = $_POST['MenuTitle'];
		$this->Page->Description = $_POST['Description'];
		$this->Page->Parent = $_POST['Parent'];
		$this->Page->ShowOnMenu = (bool) $_POST['ShowOnMenu'];
		$this->Page->Active = (bool) $_POST['Active'];
		
		$this->upsert($this->Page);
		usleep(500000);
		$_POST['PageKey'] = $this->findNewKeyId($this->Page->Controller, $this->Page->Method);
				
		$this->getUpdateForm(false);
		
		$this->UpdateForm->add_error('error', 'The page '.$this->Page->MenuTitle.' has been updated.');
		return $this->UpdateForm;
			
	}
?>

Now we are able to save the results of our add/update form. In order to implement an EditPage and DisablePage form, we need to develop a PageSelect form so we can choose the page to edit/disable. I have also added the getPageDisableForm() and processPageDisableForm() methods below. Our EditPage form will simply use our getPageUpdateForm, and processPageUpdateForm() we have already written.

<?PHP	// models/pagestore.class.php
	
	public function getPageSelectForm($Active = true, $ShowOnMenu = true){
		$Form = new AppForm('SelectPage');
		
		$Form->add('label', 'labelPageKey','PageKey','Page: ');
		$PageKey = $Form->add('select','PageKey');
		
		//Get options
		$Options = $this->getPageOptions($this->getPageDirectory($Active, $ShowOnMenu));
		
		$PageKey->add_options($Options);
		
		$Form->add('submit','btnSubmit','Select Page');
		
		return $Form;
	}
	
	public function getPageDisableForm(){
		if(!isset($_POST['PageKey'])){
			throw new UserException('Trying to generate disable page, but $_POST[PageKey] has not been set');
		}
		$Page = $this->fetchById($_POST['PageKey']);
		$Form = new AppForm('DeletePage');
		$Form->add('hidden','PageKey',$_POST['PageKey']);
		$Form->add('label','labelSubmit','btnSubmit','Do you want to remove the page: '.$Page->MenuTitle);
		$Form->add('submit', 'btnSubmit','Disable Page');
		
		return $Form;
	}
	
	public function processPageDisableForm(){
		if(!isset($_POST['PageKey'])){
			throw new UserException('Trying to disable a page, but $_POST[PageKey] has not been set');
		}
		$Page = $this->fetchById($_POST['PageKey']);
		$Page->Active = false;
		$this->upsert($Page);
	}
	
?>

All we need now, is to add an EditPageView, and a DisablePageView, and add the edit() and disable() methods to our Pages Controller.

<?PHP	// controllers/pages.class.php -- additions only --
	
	public function edit(){
		$this->View(App::newEditPageView());
		$this->View->Model($this->Model);
		$this->View->render();
	}
	
	public function disable(){
		$this->View(App::newDisablePageView());
		$this->View->Model($this->Model);
		$this->View->render();
	}
?>

<?PHP	// views/disablepageview.class.php

class DisablePageView extends View{
	
	private $Content;
	private $SelectForm;
	private $DisableForm;
	
	public function __construct(){
		$this->setContent();
	}
	
	public function render(){
		if(!isset($_POST['PageKey'])){
			echo $this->Content;
			$this->setSelectForm();
			$this->SelectForm->render('*horizontal');
		} else{
			
			$this->setDisableForm();
			
			if($this->DisableForm->validate()){
				$this->Model->processPageDisableForm();
				echo '<p>Page Disabled';
			} else{
				$this->DisableForm->render('*horizontal');				
			}
		}
	}
	
	public function setContent(){
		$Content = '<h1>Disable a Page</h1>';
		$Content .= '<p>Select a page to disable';
		$this->Content = $Content;
	}
	
	public function setSelectForm(){
		$this->SelectForm = $this->Model->getPageSelectForm();
	}
	
	public function setDisableForm(){
		$this->DisableForm = $this->Model->getPageDisableForm();
	}
	
}
	
?>
	
<?PHP	// views/editpageview.class.php
	
class EditPageView extends View {
	
	private $Content;
	private $SelectForm;
	private $EditForm;
	
	public function __construct(){
		$this->setContent();
	}
	
	public function render(){
		if(!isset($_POST['PageKey'])){
			echo $this->Content;
			$this->setSelectForm();
			$this->SelectForm->render('*horizontal');
		} else{
			
			$this->setEditForm();
			
			if($this->EditForm->validate()){
				$this->Model->processUpdateForm();
				$this->setEditForm();
				$this->EditForm->add_error('error','Page Updated');
			}
						
			echo $this->Content;
			$this->EditForm->render('*horizontal');
		}
	}
	
	public function setContent(){
		$Content = '<h1>Edit a Page</h1>';
		$this->Content = $Content;
	}
	
	public function setSelectForm(){
		$this->SelectForm = $this->Model->getPageSelectForm();
	}
	
	public function setEditForm(){
		$this->EditForm = $this->Model->getUpdateForm(false);
	}
}
	
?>

We now have the ability to add, edit and disable pages in our datastore. We will use this functionality later in upcoming articles to improve our security and user level access features, and to generate navigation menus. In the next article, we will look at setting up user level permissions and custom user permissions.