Category: Zend Framework


I just managed to skip all the rather horrible and complex looking Magento form config by getting Zend_Form working in the controllers instead. (I reserve the right to retract what I said just there about Magento forms, seeing as I haven’t actually tried it that way yet!)

Anyway, as you know, CSRF (cross site request forgery) tokens are pretty important to secure up your forms, and Zend_Form_Element_Hash does a fine job of taking care of this, however the problem I got was the old “session already started” message. What I did was create my own Form_Element_Hash. It’s a copy paste job of the original Zend class, only it uses the Magento session. Here it is:

<?php /**  * Created by PhpStorm.  * User: delboy1978uk  * Date: 03/06/15  * Time: 15:30  */ class Del_Form_Element_Hash extends Zend_Form_Element_Xhtml {     /**      * Use formHidden view helper by default      * @var string      */     public $helper = 'formHidden';     /**      * Actual hash used.      *      * @var mixed      */     protected $_hash;     /**      * Salt for CSRF token      * @var string      */     protected $_salt = '54lt4ndP3pp3r';     /**      * @var Zend_Session_Namespace      */     protected $_session;     /**      * TTL for CSRF token      * @var int      */     protected $_timeout = 300;     /**      * Constructor      *      * Creates session namespace for CSRF token, and adds validator for CSRF      * token.      *      * @param array|string|Zend_Config $spec      * @param null $options      * @throws Zend_Form_Exception      */     public function __construct($spec, $options = null)     {         parent::__construct($spec, $options);         $this->setAllowEmpty(false)
            ->setRequired(true)
            ->initCsrfValidator();
    }

    /**
     * Set session object
     *
     * @param  Mage_Core_Model_Session $session
     * @return Del_Form_Element_Hash
     */
    public function setSession($session)
    {
        $this->_session = $session;
        return $this;
    }

    /**
     * Get session object
     *
     * Instantiate session object if none currently exists
     *
     * @return Mage_Core_Model_Session
     */
    public function getSession()
    {
        if (null === $this->_session)
        {
            $this->_session = Mage::getSingleton('core/session');
        }
        return $this->_session;
    }

    /**
     * Initialize CSRF validator
     *
     * Creates Session namespace, and initializes CSRF token in session.
     * Additionally, adds validator for validating CSRF token.
     *
     * @return Del_Form_Element_Hash
     */
    public function initCsrfValidator()
    {
        $session = $this->getSession();
        $key = $session->getData('csrf');
        if (isset($key))
        {
            $rightHash = $session->getData('csrf');
        }
        else
        {
            $rightHash = null;
        }

        $this->addValidator('Identical', true, array($rightHash));
        return $this;
    }

    /**
     * Salt for CSRF token
     *
     * @param  string $salt
     * @return Del_Form_Element_Hash
     */
    public function setSalt($salt)
    {
        $this->_salt = (string) $salt;
        return $this;
    }

    /**
     * Retrieve salt for CSRF token
     *
     * @return string
     */
    public function getSalt()
    {
        return $this->_salt;
    }

    /**
     * Retrieve CSRF token
     *
     * If no CSRF token currently exists, generates one.
     *
     * @return string
     */
    public function getHash()
    {
        if (null === $this->_hash) {
            $this->_generateHash();
        }
        return $this->_hash;
    }

    /**
     * Get session namespace for CSRF token
     *
     * Generates a session namespace based on salt, element name, and class.
     *
     * @return string
     */
    public function getSessionName()
    {
        return __CLASS__ . '_' . $this->getSalt() . '_' . $this->getName();
    }

    /**
     * Set timeout for CSRF session token
     *
     * @param  int $ttl
     * @return Del_Form_Element_Hash
     */
    public function setTimeout($ttl)
    {
        $this->_timeout = (int) $ttl;
        return $this;
    }

    /**
     * Get CSRF session token timeout
     *
     * @return int
     */
    public function getTimeout()
    {
        return $this->_timeout;
    }

    /**
     * Override getLabel() to always be empty
     *
     * @return null
     */
    public function getLabel()
    {
        return null;
    }

    /**
     * Initialize CSRF token in session
     *
     * @return void
     */
    public function initCsrfToken()
    {
        $session = $this->getSession();
        $session->setData('csrf',$this->getHash());
    }

    /**
     * Render CSRF token in form
     *
     * @param  Zend_View_Interface $view
     * @return string
     */
    public function render(Zend_View_Interface $view = null)
    {
        $this->initCsrfToken();
        return parent::render($view);
    }

    /**
     * Generate CSRF token
     *
     * Generates CSRF token and stores both in {@link $_hash} and element
     * value.
     *
     * @return void
     */
    protected function _generateHash()
    {
        $this->_hash = md5(
            mt_rand(1,1000000)
            .  $this->getSalt()
            .  $this->getName()
            .  mt_rand(1,1000000)
        );
        $this->setValue($this->_hash);
    }

}

As we all know, Magento is built using some of the Zend Framework library. However, that doesn’t mean you can go straight into a controller and start programming like you would ZF. However, preparing your Zend_Form in order to work in Magento is pretty straigtforward.

Magento’s controllers do not have a view! So your form will fail to render! To fix this, we override the __toString() method:

/**
 *  Overridden because Magento doesnt have the zend view
 */
public function __toString()
{
    return  $this->render(new Zend_View());
}

Now your form will work in your magento controller.  If you are new to Magento like I am, you’ll also need to know how to instantiate the form, send the form to the view, and how to display it once we are in the view! In the controller:

public function indexAction()
{
    $this->loadLayout();
 
 // new My_Form_Class(); also works here, but this is how Magento does it, from the xml config.
 $this->form = Mage::getModel('madskull_feedback/form'); 

 if($this->getRequest()->isPost())
 {
   $data = $this->getRequest()->getPost();
   if($this->form->isValid($data))
   {
     // get sanitised data
     $data = $this->form->getValues();
     $this->sendFeedbackEmail($data);
     $this->_redirect('*/*/thanks');
   }
   else
   {
     $this->form->populate($data);
   }
  }

  $this->getLayout()->getBlock('madskull_feedback.form')->setData('form', $this->form);
  $this->renderLayout();
}

On the .phtml file, it’s a piece of cake:

echo  $this->getData('form');

There you go! Full Zend_Form goodness! Have fun!

This is a short little post, primarily just to remind me. We need to convert to html entities or symbols like $ and £ won’t print. There are lots of ways of getting the price from the product object, but getFinalPrice seems to be the simplest.

$raw_price = $this->getProduct()->getFinalPrice(); // string "£3,295.00"
$price =  htmlspecialchars(Mage::helper('core')->currency($raw_price,true,false));

In my new work we run a Magento eCommerce store. As such, I’ve set up a fresh Vagrant Box through http://www.puphpet.com (try it!) and installed Magento into one of the vHosts. Stupidly, I forgot my password almost immediately, which highlighted a problem! Mail wasn’t sending out.

Of course, Zend_Mail defaults to using Zend_Mail_Transport_Sendmail and not Zend_Mail_Transport_Smtp, so I looked around for how to set this in Magento. Everyone on IRC told me to use 3rd party software! I checked it out, and the thing is totally insecure! https://github.com/aschroder/Magento-SMTP-Pro-Email-Extension/issues
So I decided against that.

The real answer lies in the way that Magento overrides existing classes. I told the guys in the #magento IRC channel that I refused to use the above insecure library, and would rather just hack the core ZF class.

It turns out that you can create a local directory in /app/code. So for a completely customised Zend_Mail without touching third party vendor files (ie. the ZF lib), you create /app/code/local/Zend/Mail.php, and tweak away to your liking!

line 1175 is the culprit . Change

$transport = new Zend_Mail_Transport_Sendmail();   // the ZF default
to
$transport = new Zend_Mail_Transport_Smtp();

Lastly, if you need to set a custom port (on my dev box, I wanted all my mail going through port 1025 so that Mailcatcher would stop development mails going out), then run the following SQL query on your DB:

INSERT INTO `core_config_data` (`config_id`, `scope`, `scope_id`, `path`, `value`) VALUES (NULL, 'default', 0, 'system/smtp/port', '1025');

If you set a  db prefix then tweak the above table name to suit. That should be you ready to rock! Have fun!

Getting Started with Pimcore

I’ve only just started using Pimcore, but of all the CMS’es out there, this looks the best. Anyway, in the admin panel, you can create new pages etc. As the developer, we can create the templates and views, but restrict to just the editable areas for standard cms users. These editable areas are called areablocks.

For instance, in my homepage.phtml, I have the following:

<?= $this->areablock('some-id-or-other'); ?>

Once there is an areablock on a view, when you are in the admin panel, you can select an area block from the collapsible menu and literally just drag it into place, and start editing.

Customised Area files go in the website/views/areas folder. I’m making a Carousel, so in website/views/areas, I created a gallery-carousel folder, and inside that I created a view.phtml file (or .php, depending on your pimcore prefs), and a area.xml:

<?xml version="1.0"?>
<zend-config xmlns:zf="http://framework.zend.com/xml/zend-config-xml/1.0/">
    <id>tabbed-slider-text</id>
    <name>Slider (Tabs/Text)</name>
    <description></description>
    <author>pimcore.org</author>
    <version>1.0</version>
</zend-config>
<section class="area-tabbed-slider-text">

    <?php if($this->editmode) { ?>
        <div class="alert alert-info">
            How many tabs you want to show?

            <?php
                // prepare the store
                $selectStore = [];
                for($i=2; $i<6; $i++) {
                    $selectStore[] = [$i, $i];
                }
            ?>
            <?= $this->select("slides",[
                "store" => $selectStore,
                "reload" => true,
                "width" => 60
            ]); ?>
        </div>
    <?php } ?>

    <?php
        $id = "tabbed-slider-" . uniqid();
        $slides = 2; // default value
        if(!$this->select("slides")->isEmpty())
        {
            $slides = (int) $this->select("slides")->getData();
        }
    ?>
    <div id="<?= $id ?>" class="tabbed-slider carousel slide">
        <div class="carousel-inner">
            <?php for($i=0; $i<$slides; $i++) { ?>
                <div class="item <?= ($i==0 ? "active" : "") ?> item-<?= $i ?> <?= $id . "-" . $i ?>">
                    <?php if(!$this->image("image_" . $i)->isEmpty() || $this->editmode) { ?>
                        <?= $this->image("image_" . $i, [
                            "dropClass" => $id . "-" . $i,
                            "thumbnail" => ""
                        ]); ?>
                    <?php } ?>
                    <div class="carousel-caption">
                        <h1><?= $this->input("headline_" . $i) ?></h1>
                        <p><?= $this->textarea("description_" . $i, ["nl2br" => true]) ?></p>
                    </div>
                </div>
            <?php } ?>
        </div>
        <!-- End Carousel Inner -->
        <ul class="nav nav-pills nav-justified">
            <?php for($i=0; $i<$slides; $i++) { ?>
                <li data-target="#<?= $id ?>" data-slide-to="<?= $i ?>" class="<?= ($i==0 ? "active" : "") ?> item-<?= $i ?>">
                    <a href="#">
                        <?= $this->input("pill-title_" . $i) ?>
                        <small><?= $this->input("pill-small_" . $i) ?></small>
                    </a></li>
            <?php } ?>
        </ul>
    </div>

</section>

The customised area blocks must be enabled before they will appear in Pimcore. Click on Extras -> Extensions in the Admin section, and you will see each folder you have added. Click the icon to enable/disable the area. Now if you refresh your page with the $this->areablock(…) code, you should see your customised area available to use!

Last thing! Pimcore has all manner of caching going on! Go into Settings > Cache in order to clear it and see your changes!

Pimcore looks to be a great CMS! I haven’t played around with it much but if you are a fan of Zend Framework 1 then you’ll be right at home using it!

Right now, I’m designing the front end for a small promotional site that requires a CMS admin panel etc, and I’m rendering a customised Navigation view, in order to get the Bootstrap classes that I want.

Pimcore allows you to run multiple sites from the one system. So first we put a check in our layout.phtml :

$navStartNode = $this->document->getProperty("navigationRoot");
    if(!$navStartNode instanceof Document_Page) 
    {
        if(Site::isSiteRequest()) 
        {
            $site = Site::getCurrentSite();
            $navStartNode = $site->getRootDocument();
        } 
        else 
        {
            $navStartNode = Document::getById(1);
        }
    }

The root site always has Document ID 1. In the Pimcore admin panel, you right click and add a new page, then right click and you can tell it to use that page as an actual site, so you can add as many as you like!

In the layout.phtml (or view partial like header.phtml), I have the following:

<div class="navbar-collapse collapse">
<?php
        $navigation = $this->pimcoreNavigation()->getNavigation($this->document, $navStartNode);;
        echo $this->navigation()->menu()->renderPartial($navigation, 'elements/theme-nav.phtml', array(
                "maxDepth" => 1,
                "ulClass" => "nav navbar-nav"
          ));
 ?>
</div>

Pimcore checks in the views folder for the partial (elements/theme-nav.phtml in my example). In my project the views folder is located in /website/views/scripts, so create an elements folder, and create theme-nav.phtml:

<ul class="nav navbar-nav"  data-0="margin-top:20px;" data-300="margin-top:5px;">
    <?php
    foreach ($this->container as $page) 
    {
        echo '<li>'.$this->navigation()->menu()->htmlify($page).'</li>';
    }
    ?>
</ul>

And that’s it! Your Navigation is now rendered in the way we want!

Getting this error? Set your form to  enctype=”multipart/form-data”. Job done.

Sometimes, you’ll want the same form in more than one place in your application, however occasionally, that means your forms look terrible! However you can specify a different view script to use (I put mine in a forms subdirectory):

$this->addDecorators(array(
    array('ViewScript', array('viewScript' => 'view_folder/different-looking-form.phtml'))
));

And the bit that confdused me for a while, accessing the form in that view is strangle called $this->element:

<?= $this->element; ?>

Of course, as you may know, you don’t have to render the form like that, which is kind of the point of this:

<?php
$form = $this->element;
$supply = $form->getSupply();
$fueltype = $form->getFueltype();

?>

<form id = "<?=$form->getAttrib('id') ?>"
    name = "<?=$form->getName() ?>"
    method = "<?=$form->getMethod(); ?>"
    enctype = "<?=$form->getEnctype(); ?>"
    >
    <dl class="zend_form">

        <dt><label>Supply / Meter</label></dt>
        <dd><?=$supply->getMeterNumber() ?></dd>

        <dt><label>Fuel Type</label></dt>
        <dd><?=$fueltype->getFueltype() ?></dd>

        <dt><label>Description</label></dt>
        <dd><?=$supply->getDescription() ?></dd>

    <?php
    foreach ($form->getElements() as $element)
    {
        echo $element;
    }
    ?>
    </dl>
</form>

Sorted! No need to encase your form in more divs and mess around with CSS! 🙂

Expanding upon my abstract DB class that auto creates Zend_Paginators on demand, I thought it was about time I did something regarding automatically sorting the records returned.

First thing I did was go in and edit my db base class (need a reminder? https://delboy1978uk.wordpress.com/2014/06/30/auto-paginator-with-zend_db_table/), adding a sort_field property, a setSortField() method, and a little if statement in our getResult() method :

    /** @var string $sort_field */
    protected $sort_field = null;
    /**
     *  For instance 'date_added DESC'
     *  @param int $no
     *  @return $this
     */
    public function setSortField($sort_and_direction)
    {
        $this->sort_field = $sort_and_direction;
        return $this;
    }
    /**
     * @param Zend_Db_Select|string $select
     * @return \Zend_Db_Table_Rowset_Abstract|Paginator
     */
    public function getResult($select)
    {
        if(is_string($select))
        {
            $select = $this->select()->where($select);
        }
        if($this->sort_field)
        {
            $select->order($this->sort_field);
        }
        if($this->paginator_flag == true)
        {
            return $this->getPaginator($select);
        }
        return $this->fetchAll($select);
    }

Now we have that, we can sort any queries like so:

        // Get the page number
        $page = ($this->getRequest()->getParam('page')) ?: 1;
                
        // Set pagination
        $db->setPaginatorFlag(true);
        $db->setNumPerPage(30);
        $db->setPage($page);
        
        // Get sort field
        $sort = $this->getRequest()->getParam('sort');
        $dir = $this->getRequest()->getParam('dir');
        
        // Set the sort
        $db->setSortField($sort.' '.$dir);

Again, in your DB class method, make sure it calls $this->getResult(); and not fetchAll().

Anyway, thats only one half of the equation, what we need is sort links! I create a view helper for this, which basically calls the url view helper, but with additional search capabilities. In the Bootstrap (as you’ll know), we define our routes as such:

$router->addRoute(
    'lead-assessor-view-supply',
    new Zend_Controller_Router_Route(
        '/lead-assessor/view-supply/:orgid/:id/:page/:sort/:dir',
         array(
            'controller' => 'lead-assessor',
            'action' => 'view-supply', 
            'page' => 1, 
            'sort' => 'date_from', 
            'dir' => 'DESC'
         )
      )
);

As you can see, I have added sort and dir in addition to page.  To get the links working, send the page, sort, and dir variables to the view, and in the view, instead of calling $this->url(blahblah); we will call our own view helper, sortLinks(); Here’s the class:

<?php 

class ESOS_View_Helper_SortLinks extends Zend_View_Helper_Abstract
{
    /** 
     *  Pass in a route name, and an array of any params which are the same for all the sort links
     *  @param string $route_name
     *  @param array $params
     *  @param string $field_name
     *  @param string $sort_field
     *  @param string $sort_dir
     *  @param string $field_label
     *  @param string $classes
     *  @return string
     */
    public function sortLinks(array $params,$route_name,$field_name,$field_label, $classes = null)
    {
        // Set the current sort field
        $current_sort_field = isset($params['sort']) ? $params['sort'] : 'ASC' ;
        
        // Set the Icon
        $icon = $this->view->sortIcon($field_name,$current_sort_field,$params['dir']);
        
        // Set params
        $params['sort'] = $field_name;
        $params['dir'] = $this->view->sortDir($field_name, $current_sort_field, $params['dir']);
        
        // Build the HTML
        $link = '<a href="';
        $link .= $this->view->url($params,$route_name);
        $link .= '" class="';
        $link .= $classes;
        $link .= '">';
        $link .= $icon;
        $link .= $field_label;
        $link .= '</a>';
        
        // Return the HTML
        return $link;
    }
}

Almost there! You’ll see it is calling another two view helpers, sortIcon and sortDir:

<?php

class ESOS_View_Helper_SortDir extends Zend_View_Helper_Abstract
{
    public function sortDir($field_name, $current_sort_field, $dir)
    {
        if($field_name == $current_sort_field)
        {
            switch($dir)
            {
                case 'ASC':
                    $dir = 'DESC';
                    break;
                case 'DESC':
                default:
                    $dir = 'ASC';
                    break;
            }
        }
        return $dir;
    }
}
<?php

class ESOS_View_Helper_SortIcon extends Zend_View_Helper_Abstract
{
    public function sortIcon($field_name, $current_sort_field, $dir)
    {
        if($field_name == $current_sort_field)
        {
            switch($dir)
            {
                case 'ASC':
                    $icon = '<i class="fa fa-caret-up"></i>&nbsp;';
                    break;
                case 'DESC':
                default:
                    $icon = '<i class="fa fa-caret-up"></i>&nbsp;';
                    break;
            }
        }
        else 
        {
            $icon = null;
        }
        return $icon;
    }
}

These just figure out which direction of sort the link will generate. The Icons are from Font Awesome, but feel free to use anything you like!

Tying it all together, in your view, define an array of any parameters that you will pass into each sort link (sort, and dir should always be in there, page if using the paginator):

    // Common params for the sortLinks
    $params = array(
        'orgid' => $this->org->getId(),
        'page' => $this->page,
        'sort' => $this->sort,
        'dir' => $this->dir,
    );

At the top of your table where you want your clickable sort links, call:

<?= $this->sortLinks($params,'portfolio-admin-users','name',$this->t('Organisation')); ?>

Sorting By Price

Sorting By Status

This will save a LOT of farting around in the future! Have fun! 🙂

Make your pagination EASY! Whether using Zend Framework’s MVC, or just the library classes!

By now you might have read by now that half the folk that were using Zend_Paginator have been using it wrong.  They would get their rowset from the table, and then create a paginator and pass in the rowset. Sure, it works, but you have just fetched ALL the rows first, and Paginator displays only the records in question. That is a huge waste of resources!

What SHOULD be happening is that you pass a select() object to the paginator, to which it will figure out the offsets and limits.

Anyway, pretty much all my db classes were returning either rowsets or rows depending on the method. What i needed was for it to return the select. So I had an idea. Auto pagination!

Rather than your db classes extending Zend_Db_Table, we will extend our own abstract class which will  itself extend Zend_Db_Table.

Our class will have a couple of properties;  a flag for pagination on/off, the page number, and the num per page. It will also have a few  methods, as you can see below.

<?php

namespace AA\Repository;

use Zend_Db_Table_Abstract as Table;
use Zend_Db_Table_Select as Select;
use Zend_Paginator as Paginator;
use Zend_View_Helper_PaginationControl as PaginationControl;

abstract class AbstractRepository extends Table
{
 /** @var bool */
 private $paginator_flag = false;
 /** @var int */
 private $page = 1;
 /** @var int */
 private $num_per_page = 30;

 /**
 * @param bool $bool
 * @return $this
 */
 public function setPaginatorFlag($bool)
 {
 $this->paginator_flag = $bool;
 return $this;
 }

 /**
 * @param int $no
 * @return $this
 */
 public function setPage($no)
 {
 $this->page = $no;
 return $this;
 }

 /**
 * @param int $no
 * @return $this
 */
 public function setNumPerPage($no)
 {
 $this->num_per_page = $no;
 return $this;
 }

 /**
 * @param Select $select
 * @return Paginator
 */
 private function getPaginator(Select $select)
 {
 PaginationControl::setDefaultViewPartial('NavControls.phtml');
 $paginator = Paginator::factory($select);
 $paginator->setCurrentPageNumber($this->page);
 $paginator->setItemCountPerPage($this->num_per_page);
 return $paginator;
 }

 /**
 * @param Select $select
 * @return \Zend_Db_Table_Rowset_Abstract|Paginator
 */
 protected function getResult(Select $select)
 {
 if($this->paginator_flag == true)
 {
 return $this->getPaginator($select);
 }
 return $this->fetchAll($select);
 }
}

(If you aren’t using namespaces in your code, you can tweak it to the old style.)

The setPaginatorFlag($bool) turns paging on or off. setPage($no) and
setNumPerPage($no) are self explanitory. getPaginator(Select $select) returns a paginator from the select object.  The interesting method is getResult(Select $select). It checks wether we asked for a paginator. If so it calls getPaginator, if not it does a plain fetchAll.

Have a look at a simple DB Class which now uses the auto paginator:

namespace AA\Repository;


class Competitors extends AbstractRepository
{
 protected $_name = 'branch_competitors';
 protected $_rowClass = 'AA\Entity\Branch\Competitors';


 public function getCompetitors($bid)
 {
 return $this->fetchRow('bid = '.$bid);
 }

 public function getAllCompetitors()
 {
 $select = $this->select()
 ->order('bid DESC');
 return $this->getResult($select);
 }
}

The interesting parts here are the extends AbstractRepository, and return $this->getResult($select). We now use getResult so that it can decide whether to give you a rowset or a paginator.

In your views, you can now just echo it out to give you your paginator! (Of course, you will have your pagination view partial set! see manual for details)

However if you are using Zend Framework libraries without using the MVC component, we can still use Zend Paginator without concerning ourselves with Zend_View etc. I created a view helper class with a static render method, accepting the paginator and the current url:

namespace AA\View\Helper;

use Zend_Paginator;


class Paginator 
{
 public static function render(Zend_Paginator $paginator,$url)
 {
 $url = parse_url($url);
 parse_str($url['query'],$result);
 $class = null; //for now
 if ($paginator->getPages()->pageCount)
 {
 $html = '<div class="pagination"><ul>';
 if (isset($paginator->getPages()->previous))
 {
 $result['page'] = $paginator->getPages()->first;
 $query = http_build_query($result);
 $html .= '<li>
 <a class="'.$class.'" id="first" href="'.$url['path'].'?'.$query.'">
 first
 </a>
 </li>';
 }
 else
 {
 $html .= '<li class="disabled"><a href="#">first</a></li>';
 }
 if (isset($paginator->getPages()->previous))
 {
 $result['page'] = $paginator->getPages()->previous;
 $query = http_build_query($result);
 $html .= '<li>
 <a class="'.$class.'" id="last" href="'.$url['path'].'?'.$query.'">
 previous
 </a>
 </li>';
 }
 else
 {
 $html .= '<li class="disabled"><a href="#">previous</a></li>';
 }
 if (isset($paginator->getPages()->next))
 {
 $result['page'] = $paginator->getPages()->next;
 $query = http_build_query($result);
 $html .= '<li>
 <a class="'.$class.'" id="next" href="'.$url['path'].'?'.$query.'">
 next
 </a>
 </li>';
 }
 else
 {
 $html .= '<li class="disabled"><a href="#">next</a></li>';
 }
 if (isset($paginator->getPages()->next))
 {
 $result['page'] = $paginator->getPages()->last;
 $query = http_build_query($result);
 $html .= '<li>
 <a class="'.$class.'" id="last" href="'.$url['path'].'?'.$query.'">
 last
 </a>
 </li>';
 }
 else
 {
 $html .= '<li class="disabled"><a href="#">last</a></li>';
 }
 $html .= '</ul></div>';
 }
 else
 {
 $html = null;
 }
 return $html;
 }
}

So on a plain ol’ PHP page (that’s able to autoload the classes, if not, require them or look into composer), here’s all you would need (my current url in this example is “/?id1=-competitors”):

$page = ($_GET['page']) ? filter_input(INPUT_GET, 'page', FILTER_SANITIZE_NUMBER_INT) : 1;
//set pagination on for this query
$db->setPaginatorFlag(true)
   ->setPage($page)
   ->setNumPerPage(30);
//get paginator/rowset
$competitors = $db->getAllCompetitors();
//show paginator
echo \AA\View\Helper\Paginator::render($competitors,'/?id1=competitors');
foreach($competitors as $competitor)
{
    //row stuff here
}

Awesome! Now you need never again have to muck around with paginators, whether using Zend Frameworks MVC or not! Have fun!