September 12, 2023
by Matt Raines

Looking back to 2014

« 2012 | 2013 | 2014

Windows updating at the very time we try to leave the conference stand

I missed spring conference even though it was in my favourite city, York. But I was a fair bit preoccupied waiting for child #2 to be born. The autumn event was back in Glasgow, so I didn’t have to wait too long for my next my cool northern city hit.

Major developments on our Lib Dem platform included statistics on email send and open rates, better email templating, and automated campaign groups based on area or membership status. Gary did a lot of design work on the sites, with a particular emphasis on making them more mobile-friendly and producing new skins which closely mirrored the rebranded federal party site, and enough other designs to triple the number of choices available to customers.

Oh, we had to move one of the sites behind a content delivery network after they put up a rather controversial petition and triggered a Distributed Denial of Service attack. It was though one of our most successful petitions, outside of Gurkha Justice, garnering 80,000 signatures, so there’s that, I guess.

The CIPR ConversationWe continued to work in-depth with the CIPR, visiting their central London offices often, completing their social WordPress rebuild of the CIPR Conversation and working on rebranding and updates on their main website and integrations between their CRM system Integra and their online network of sites.

Prater Raines website in 2014On FIM’s fund management platform we made significant improvements to the system’s performance, automated processes, internal reports and PDF documents sent to customers, including a trip to the client’s offices to work on the PDF rebranding together, under perhaps the sunniest skies the Isle of Man has ever seen.

I learned about DNSSEC and the upcoming new global top level domains at ICANN50. I’m not sure it was the most valuable conference we’ve ever been to but it was the one and only time it’s ever been in the United Kingdom, and there was at least a great party afterwards. Meanwhile we applied for and gained Accredited Channel Partner status, Nominet’s highest standard for registrars, providing us more functionality when working with our customers on domain name registrations and updates, in exchange for somewhat more formal rules on customer service and data validation.

Tools of the trade

Location, location, location

The year in tech

Sample code

<?php

require_once dirname(__FILE__).'/../lib/mailoutGeneratorConfiguration.class.php';
require_once dirname(__FILE__).'/../lib/mailoutGeneratorHelper.class.php';

/**
 * mailout actions.
 *
 * @package    foci2
 * @subpackage mailout
 * @author     Matt Raines <matt@praterraines.co.uk>
 */
class mailoutActions extends autoMailoutActions
{
  public function executeDelete(sfWebRequest $request)
  {
    $mailout = $this->getRoute()->getObject();
    if ($mailout->getSentCount() > 0) {
      $this->getUser()->setFlash(
        "error",
        "This email campaign cannot be deleted because at least one email has already been sent.");
      $this->redirect("@mailout");
    } else {
      parent::executeDelete($request);
    }
  }
    
  public function executePreview(sfWebRequest $request)
  {
    $this->forward404Unless($mailout = $this->getRoute()->getObject());
    sfConfig::set(
      "app_email_message_class",
      sfConfig::get("app_email_message_class"). "PreviewStub");
    return $this->renderText($mailout->getPreview()->getBody());
  }
  
  public function executeChartHour(sfWebRequest $request)
  {
    $this->forward404Unless($mailout = $this->getRoute()->getObject());

    $width = sfConfig::get("app_mailout_chart_bar_width");
    $height = sfConfig::get("app_mailout_chart_bar_height");

    $chart = new sfPChartBar($width, $height);
    $chart->setColorPalette(0, 0xf0, 0x80, 0x80);
    $chart->setColorPalette(1, 0x87, 0xce, 0xfa);
    $chart->setFontProperties(
      sfConfig::get("app_mailout_chart_font_family"),
      sfConfig::get("app_mailout_chart_font_size"));

    $chart->setData(
      Doctrine_Core::getTable("MailoutEmail")->countByHourOfDayForMailout($mailout));
    $method = "drawChart" . ucfirst(sfConfig::get("app_mailout_chart_bar_type"));
    $chart->$method();
    $start = strtotime($mailout->getSentRange()["start"]);
    $end = strtotime($mailout->getSentRange()["end"]);
    $label = "Email sent "
      . (date("Y-m-d H:i", $start) == date("Y-m-d H:i", $end)
         ? "at " . date("h:ia", $start)
         : date("h:ia", $start) . " to " . date("h:ia", $end));
    $chart->addMarker(date("H", $start) + date("i", $start) / 60, $label);
    if ($end - $start > 60 * 5) {
      $chart->addMarker(date("H", $end) + date("i", $end) / 60);
    }
    $chart->Render();

    $path = $chart->getPath();
    return $this->renderFile($path, "image/png");
  }
  
  public function executeChartWeekday(sfWebRequest $request)
  {
    $this->forward404Unless($mailout = $this->getRoute()->getObject());

    $width = sfConfig::get("app_mailout_chart_bar_width");
    $height = sfConfig::get("app_mailout_chart_bar_height");

    $chart = new sfPChartBar($width, $height);
    $chart->setColorPalette(0, 0xf0, 0x80, 0x80);
    $chart->setColorPalette(1, 0x87, 0xce, 0xfa);
    $chart->setFontProperties(
      sfConfig::get("app_mailout_chart_font_family"),
      sfConfig::get("app_mailout_chart_font_size"));

    $chart->setData(
      Doctrine_Core::getTable("MailoutEmail")->countByDayOfWeekForMailout($mailout));
    $method = "drawChart" . ucfirst(sfConfig::get("app_mailout_chart_bar_type"));
    $chart->$method();
    $chart->Render();

    $path = $chart->getPath();
    return $this->renderFile($path, "image/png");
  }
  
  public function executeChartTimeline(sfWebRequest $request)
  {
    $this->forward404Unless($mailout = $this->getRoute()->getObject());

    $width = sfConfig::get("app_mailout_chart_graph_width");
    $height = sfConfig::get("app_mailout_chart_graph_height");

    $chart = new sfPChartTimeline($width, $height);
    $chart->setColorPalette(0, 0xf0, 0x80, 0x80);
    $chart->setColorPalette(1, 0x87, 0xce, 0xfa);
    $chart->setFontProperties(
      sfConfig::get("app_mailout_chart_font_family"),
      sfConfig::get("app_mailout_chart_font_size"));
    $chart->setData(
      Doctrine_Core::getTable("MailoutEmail")->countByDateForMailout($mailout));
    $chart->drawChartLine();
    $chart->Render();

    $path = $chart->getPath();
    return $this->renderFile($path, "image/png");
  }
  
  /**
   * An image representing a percentage.
   *
   * @param sfWebRequest $request
   */
  public function executeBar(sfWebRequest $request)
  {
    $percentage = round($request->getParameter("percentage", "0"));
    $colour = $request->getParameter("colour", "");
    if (!preg_match("/^[0-9a-f]{6}$/", $colour)) {
      $colour = "f08080";
    }
    $dir = sfConfig::get("sf_cache_dir") . "/mailout";

    $path = "$dir/$colour-$percentage.png";
    if (!file_exists($path)) {
      $fs = new sfFilesystem();
      $fs->mkdirs($dir);

      $image = new Imagick();
      $image->newImage(100, 16, "white");
      $image->setImageFormat("png");

      $draw = new ImagickDraw();
      $draw->setFillColor("#$colour");
      $draw->rectangle(0, 0, $percentage - 1, 15);
      $image->drawImage($draw);

      $draw = new ImagickDraw();
      $draw->setFont("Helvetica");
      $draw->setFontSize(12);
      $draw->setTextAlignment(Imagick::ALIGN_CENTER);
      $draw->annotation(50, 12, "$percentage%");
      $image->drawImage($draw);

      $image->borderImage("grey", 1, 1);

      $image->writeImage($path);
    }
    
    return $this->renderFile($path, "image/png");
  }
  
  public function executeListChooseRecipients(sfWebRequest $request)
  {
    $this->error = false;
    $this->form = new ChooseRegisteredUsersToEmailForm(array("confirmed" => true));
    if ($request->isMethod("post")) {
      $this->form->bind($request->getParameter("interaction_choice"));
      if ($this->form->isValid()) {
        if ($this->form->store()) {
          $this->redirect("mailout/new");
        } else {
          $this->error = "No users match your query.";
        }
      }
    }
  }
  
  public function executeReport(sfWebRequest $request)
  {
    $this->mailout = $this->getRoute()->getObject();
    
    $this->totals = $this->mailout->getTotals();
    $this->totals_by_email_domain = $this->mailout->getTotalsByEmailDomain();
    $this->opened_by_platform = $this->mailout->getOpenedByPlatform();
    
    $chart = new sfPChartPie(10, 10);
    $this->palette = $chart->getPalette();
    
    $this->links = $this->mailout->getLinks();
    
    $this->most_open_recipients = $this->mailout->getMostOpenRecipients(10);
    $this->unsubscribes = $this->mailout->getUnsubscribes();
  }
  
  protected function processForm(sfWebRequest $request, sfForm $form)
  {
    $form->bind(
      $request->getParameter($form->getName()),
      $request->getFiles($form->getName()));
    if ($form->isValid()) {
      if ($request->hasParameter("_preview")) {
        $form->updateObject();
        $mailout = $form->getObject();
        $this->getMailer()->sendNextImmediately()->send($mailout->getPreview());
        $this->getUser()->setFlash(
          "notice",
          "A preview of this email has been sent to " . $mailout->getSenderAddress()
          . ". If you're happy with it, the \"Send email\" button sends the message.");
      } else {
        try {
          $mailout = $form->save();
        } catch (Doctrine_Validator_Exception $e) {
          $errorStack = $form->getObject()->getErrorStack();

          $message = get_class($form->getObject()) . ' has ' . count($errorStack)
            . " field" . (count($errorStack) > 1 ?  's' : null) . " with validation errors: ";
          foreach ($errorStack as $field => $errors) {
              $message .= "$field (" . implode(", ", $errors) . "), ";
          }
          $message = trim($message, ', ');

          $this->getUser()->setFlash('error', $message);
          return sfView::SUCCESS;
        }
        $recipients = $this->form->getRecipients();
        if (count($recipients) == 1) {
          $this->getUser()->setFlash(
            "notice",
            "Your email to " . (each($recipients)["value"]["r_name"]) . " was "
            . ($mailout->getSendAfter() ? "queued for sending" : "sent") . " successfully.");
        } else {
          $this->getUser()->setFlash(
            "notice",
            "Your email to " . count($recipients) . " users was "
            . ($mailout->getSendAfter() ? "queued for sending" : "sent") . " successfully.");
        }
        $this->redirect("@mailout");
      }
    } else {
      $this->getUser()->setFlash("error", "Your email has not been sent due to some errors.", false);
    }
  }
}