Preventing Actions
Posted by Mark Fenoglio under PHP for Big Nerd Ranch
January 5, 2010

In the previous blog entry, I showed how to prevent actions on an object-by-object basis. Suppose, however, that you wanted to entirely eliminate an action?

Partial Listing: /bnr/rsrc/editors/PageEditor.php
class PageEditor extends MasterEditor
{
    ...
    
    function updateListActions($actions)
    {
        $actions->removeObjectForKey(DO_DELETE);
    }

    ...
}

Because updateListActions($actions) is called before each action is individually updated for each object, this is far more efficient than creating a shouldAllowDelete() method that simply returns false.

Custom Workflow

Of course, if you were developing a series of application modules that never allowed deletion, you might prefer to remove the overhead of establishing the IWDeleteMode in the first place.

In those circumstances, an alternate approach might be to create a custom workflow class, perhaps called SafeEditWorkflow.

Listing: /bnr/extensions/workflow/SafeEditWorkflow.php
<?php

class SafeEditWorkflow extends IWLeadWorkflow
{
    function initModeDiagram()
    {
        $this->mode[MODE_LIST] = new IWListMode($this);
        $this->mode[MODE_EDIT] = new IWEditMode($this);
        $this->mode[MODE_SAVE] = new IWSaveMode($this);
    }

    function listActions()
    {
        return array(
            DO_EDIT => new IWEditAction($this->modeURL(MODE_EDIT))
        );
    }
}

?>

Now, I can have PageEditor use this workflow instead.

Partial Listing: /bnr/rsrc/editors/PageEditor.php
<?php

class PageEditor extends MasterEditor
{
    function requiredWorkflow() { return 'SafeEditWorkflow'; }

    ...
}

?>

Because I now use SafeEditWorkflow as the handler for PageEditor, I can remove the shouldAllowDelete() method previously written.

SafeEditWorkflow for PageEditor
SafeEditWorkflow for PageEditor
Restricting Actions
Posted by Mark Fenoglio under PHP for Big Nerd Ranch
January 1, 2010

In the last two blog entries, I illustrated the key methods in displaying lists of objects in the list mode that is part of an IWLeadWorkflow. One thing that might have been particularly concerning is that there is a delete action enabled for each object.

Each IWAction object can define a callback method for determining whether that action should be permitted for an individual object. For actions defined in the various workflows of the Istarel Workshop frameworks, the callback methods use the naming convention shouldAllowActionname.

Suppose I decided that only the original author could edit a page, and that only a site administrator could delete a page. In that case, I would write the appropriate action permission methods in PageEditor.

Partial Listing: /bnr/rsrc/editors/PageEditor.php
<?php

class PageEditor extends MasterEditor
{

//  @section List

    function shouldAllowEdit($page)
    {
        $asd = ApplicationSecurityDelegate::applicationSecurityDelegate();
        return ($asd->userIdentity() == $page->authorId());
    }
    
    function shouldAllowDelete($page)
    {
        $asd = ApplicationSecurityDelegate::applicationSecurityDelegate();
        return ($asd->userHasRole(ROLE_SA));
    }
}

?>

Since I am a site administrator, I should expect to still see the "Delete" actions for each of the objects. On the other hand, I know I was not the author of all the pages, so only some of the pages will have an "Edit" action.

Restrict Edit Rights on Pages
Restrict Edit Rights on Pages
PreviewAction for PageModule
Posted by Mark Fenoglio under PHP for Big Nerd Ranch
December 29, 2009

In the last blog entry, I showed how easy it is to prepare a list view for an application module. By default, there are two actions available for each object: Edit and Delete. You might imagine that "Preview" might prove to be a useful action for our PageEditor module.

This can be accomplished by creating a method called listActions() that defines the list action that should be part of each object row.

Partial Listing: /bnr/rsrc/editors/PageEditor.php
<?php

class PageEditor extends MasterEditor
{
        
//  @section List

    function listActions()
    {
        $preview = new IWAction(new IWURL('PageView'));
        $preview->setLabel('Preview')
                ->setPropertyName('_do_preview')
                ->openInNewWindow();
        return array($preview);
    }
}

?>

Each action becomes a dynamic property of the instance, and I use setPropertyName() to define what the action should be called in the instance: By convention, the name is in the form _do_actionname.

With just that little bit of code, I now have a link for each object in the list that will open a new browser window and display the page exactly as it will appear on the website once it is made public (more on that later).

ListMode for PageEditor
Posted by Mark Fenoglio under PHP for Big Nerd Ranch
December 25, 2009

Implementing an application module in support of an IWLeadWorkflow instance forces you to consider many required and optional methods. I am embarking on a series of blog entries that attempts to encompass the entire design process for an application module supported by IWLeadWorkflow.

When an end-user reaches an IWLeadWorkflow-supported page for the first time, she will see a list view of objects. This presentation is made possible by the action of an IWListMode object that gets handed control by the IWLeadWorkflow.

For my PageEditor module, that means I must provide information about the list columns and the retrieval of objects used to populate that list.

Partial Listing: /bnr/rsrc/editors/PageEditor.php
<?php

class PageEditor extends MasterEditor
{
    
//  @section Model Object

    function modelObjectClass() { return 'Page'; }
    
//  @section List

    function listColumns()
    {
        return array(
            'legacy_page' => new IWColumn('HTML Path', 140),
            'title'       => new IWColumn('Title', 220),
            'name'        => new IWColumn('Name', 140),
            'section'     => new IWColumn('Section', 100)
        );
    }
    
    function orderByProperty() { return 'legacy_page'; }
    
    ...
}

?>

With just that minimal set of methods implemented, I can now see the list of pages built for the public-facing Big Nerd Ranch web site.

Initial List View for PageEditor
Initial List View for PageEditor
Adding Google Analytics
Posted by Mark Fenoglio under PHP for developerBlog
December 15, 2009

Now that the developerBlog and www.istarelworkshop.com have been relaunched, I want to be able to learn more about who visits the web site. The Istarel Workshop Frameworks makes it simple to add support for Google Analytics to your web site.

Add A Constant

Although strictly not necessary, creating a constant for your Google ID makes later code easier, particularly if you have to add that code in more than one place.

Partial Listing: /dblog/rsrc/conf/DeveloperBlogConstants.php
<?php

// Google Apps
define('GOOGLE_ACCOUNT_NUMBER', 'UA-1234567-1');

?>

Update the HTML Template

If you recall, any page rendered in the Istarel Workshop frameworks relies on an underlying HTML template file which uses tags to represent components. I will use {google} to represent the GoogleAnalytics component, which must be the last content on an HTML page.

Listing: /dblog/rsrc/page/html/public.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
 "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
{head}
<body onload="javascript:bring_focus_to_search();">
<div id="header">
    {title}
    {section}
    {descriptor}
    <div id="navigation">
    {menu}
    </div>
</div>

<div id="main_content">
{view}
</div>

<div id="sidebar">
    {calendar}
    {sidemenu}
    {search}
    {copyright}
</div>

{banner}

{google}
</body>
</html>

Modify the Page Controller

For basic GoogleAnalytics support, I can modify the primary page controller to create the right component to replace the tag in the HTML template

Partial Listing: /dblog/rsrc/page/PublicPage.php
<?php

class PublicPage extends IWPage
{
    ...

    function expectedComponents()
    {
        return array('head', 'title', 'menu', 'view', 'calendar', 'sidemenu',
                     'application_directory', 'year', 'banner', 'google');
    }
    
    function replaceGoogle()
    {
        return GoogleAnalytics::googleCode();
    }
}

?>

For more complex projects, particularly eCommerce solutions, the Google Analytics code must instead be managed in Application modules since there is more data required for Google Analytics to track the transaction process.

Also, had I not created a constant called GOOGLE_ACCOUNT_NUMBER, that account number would have to be passed as an argument to GoogleAnalytics::googleCode().