September 7, 2023
by Matt Raines

Looking back to 2012

« 2010 | 2011 | 2012 | 2013 | 2014 »

The Prater Raines office decorated for the 2012 Olympic Torch procession

In our tenth year working together we took on a large portfolio of clients from Assanka Ltd, as they went on to become FT Labs, and I’m very grateful they considered us reliable and trustworthy partners. Customers taking us up on our offer of development, hosting and fixed price support included the Chartered Institute of Public Relations, legal firm Stone Rowe Brewer‘s secure and searchable document storage application which remains cutting edge, Hampshire Foot and Ankle clinic, James Cooke Coaching, Merrony Wall, Naked Communications, Racepoint, Staines Prep School, Thameside Collaborative Lawyers & Mediators, Windtronics, and Twickenham Town Centre.

CIPR website in 2012The CIPR was the biggest of the new clients and took us up to our sixth server excluding off-site backup. The main site was a Drupal build with a separate bespoke Continuous Professional Development portal, WordPress based social site, events site, register of PR firms, and a proxy providing online access to subscription periodicals. We met regularly at their beautiful (then) offices on Russell Square, often enough with Tim that I tried every beer at the nearby Marquis Cornwallis, and often enough without Tim that one or two a time on the way home I eventually visited every room at the nearby British Museum.

Other new clients in 2012 included a new website for commercial property company Targetfollow who joined Radical Middle Way and Midas Training on a Symfony based CMS platform.

Prater Raines website in 2012This was also the year our tremendous customer support manager Gary joined the team and clients were immediately pleased with the step change in our responsiveness to queries and our training and documentation. These days he does a lot of excellent coding, design, and sysadmin work as well and he seems to just keep on getting distinctions in modules on his Open University computing course.

The Sage, Gateshead remains my all time favourite venue for a Lib Dem Conference. Although I might be a bit biased since I got married there. Not at Lib Dem Conference. That would have been a story. I am, however, incredibly pleased that the hotel we stayed at (“It’s cheap! It’s round the corner!”) has now permanently closed down.

The London Olympics was another thing that happened. The Olympic Torch passing by the office seemed a good excuse for a get-together and getting a bit of bunting out.

The always informative PHP UK Conference and Symfony Live London rounded out the year.

I spent far too much time on Project Euler this year, eventually completing about 100 of the maths puzzles using PHP. If you need a bit of a brain workout, it remains a really clever set of challenges and I’d fully recommend it.

Tools of the trade

Location, location, location

The year in tech

Sample code

<?php

$results_per_page = 20;
$highlight_fragment_size = 100;
$highlight_number_of_fragments = 2;
$highlight_excerpt_size = 210;
$facet_size = 1000;

if (!$user) CiprUtils::redirectToSignIn();

// Get current cycle so that existing submissions can be found
$cycle = $user->getCurrentCycle();
if ($cycle && $cycle->getPointsTotal() >= $cycle->minpoints) {
  CiprUtils::alert("
    Congratulations! Your CPD year is complete. You don't need to take any
    further action.<br /><br />
    You may, however, continue to add new activities to this year's record if
    you wish.", "info");
}

// Determine offset from page
$pageno = (empty($_GET['p']) or !is_numeric($_GET['p']) or $_GET['p'] < 1) ? 1 : $_GET['p'];
$offset = ($pageno - 1) * $results_per_page;

$query = new Elastica_Query();

$bool = new Elastica_Query_Bool();
$author = new Elastica_Query_Terms();
$author->setTerms("author_user_id", array(0, $user->id));
$bool->addMust($author);

if (isset($_GET["q"]) && strlen($_GET["q"])) {
  $page->set("q", $_GET['q']);
  $string = new Elastica_Query_QueryString();
  $string->setDefaultOperator("OR");
  $string->setQuery($_GET["q"]);
  $bool->addMust($string);
} else {
  $page->set("q", "");
}

if ((isset($_GET["from"]) && $_GET["from"]) || (isset($_GET["to"]) && $_GET["to"])) {
  $settings = array();
  if (isset($_GET["from"]) && $_GET["from"]) {
    $page->set("from", $_GET["from"]);
    $settings["from"] = date("Y-m-d", strtotime($_GET["from"]));
  }
  if (isset($_GET["to"]) && $_GET["to"]) {
    $page->set("to", $_GET["to"]);
    $settings["to"] = date("Y-m-d", strtotime($_GET["to"]));
  }
  $range = new Elastica_Query_Range();
  $range->addField("start_date", $settings);
  $bool->addMust($range);
}

$query->setQuery($bool);
$query->setHighlight(array(
  "pre_tags"    => array("<strong>"),
  "post_tags"   => array("</strong>"),
  "encoder"     => "html",
  "fields"      => array(
  "name"        => array("number_of_fragments" => 0),
  "description" => array("fragment_size" => $highlight_fragment_size, "number_of_fragments" => 2))));
$query->setFrom($offset);
$query->setLimit($results_per_page);

$facet_filters = array();
if (isset($_GET["f"]) && is_array($_GET["f"])) {
  $filter = new Elastica_Filter_And();
  foreach ($_GET["f"] as $term => $values) {
    $facet_filters[$term] = new Elastica_Filter_Terms();
    $facet_filters[$term]->setTerms($term, $values);
    $filter->addFilter($facet_filters[$term]);
  }
  $query->setFilter($filter);
}

$terms = array(
  "free" => array(
    "title" => "Cost",
    "all" => "Free and paid activities",
    "callback" => function($s){return $s == "T" ? "Free activities only" : "Paid activities only";},
    "order" => "reverse_term"),
  "subject" => array(
    "title" => "Subject area",
    "all" => "All subject areas"),
  "type" => array(
    "title" => "Activity type",
    "all" => "All types"),
  "provider" => array(
    "title" => "Course provider",
    "all" => "All providers"),
  "points" => array(
    "title" => "Points available",
    "all" => "Any number of points",
    "format" => "%d points"));

foreach ($terms as $term => $definition) {
  $facet = new Elastica_Facet_Terms($term);
  $facet->setField($term);
  $facet->setSize($facet_size);
  $facet->setOrder(isset($definition["order"]) ? $definition["order"] : "term");
  if ($facet_filters) {
    $filter = new Elastica_Filter_And();
    $has_filter = false;
    foreach ($facet_filters as $filtered_term => $facet_filter) {
      if ($term != $filtered_term) {
        $filter->addFilter($facet_filter);
        $has_filter = true;
      }
    }
    if ($has_filter) {
      $facet->setFilter($filter);
    }
  }
  $query->addFacet($facet);
}

$index = elastic_get_index();
$resultset = $index->search($query);
$totalresults = $resultset->getTotalHits();
$hits = $resultset->getResults();

$facets = $resultset->getFacets();
foreach ($terms as $term => $definition) {
  $definition["selected"] = isset($_GET["f"][$term]) ? $_GET["f"][$term] : array();
  $facets[$term]["definition"] = $definition;
}

$activities = array();
foreach ($hits as $hit) {
  $highlights = $hit->getHighlights();
  $separator = "<strong>...</strong>";
  if (isset($highlights["description"])) {
    $original_excerpt = implode(" $separator ", $highlights["description"]);
    $excerpt = $page->modifier_truncate(
      $original_excerpt,
      $highlight_fragment_size * $highlight_number_of_fragments
        + (22 * (count($highlights["description"]) - 1))
        + 17 * count($highlights["description"]),
      " <strong>...</strong>", true);
    if ($original_excerpt == $excerpt) {
      $last_bit = strip_tags($highlights["description"][count($highlights["description"]) - 1]);
      if (strpos($hit->description, $last_bit) !== strlen($hit->description) - strlen($last_bit)) {
        $excerpt .= " $separator";
      }
    }
    $first_bit = strip_tags($highlights["description"][0]);
    if (strpos($hit->description, $first_bit) !== 0) {
      $excerpt = "$separator $excerpt";
    }

  } else {
    $excerpt = $page->modifier_truncate(
      $hit->description, $highlight_excerpt_size, "end", " <strong>...</strong>");
  }
  $activities[] = array(
    "id" => $hit->id,
    "name" => $hit->name,
    "highlighted_name" => isset($highlights["name"]) ? $highlights["name"][0] : $hit->name,
    "highlighted_description" => $excerpt,
    "points" => $hit->points,
    "provider" => $hit->provider_display,
    "activity_type" => $hit->type_display,
    "free" => $hit->free,
    "keywords" => $hit->keyword ? (is_array($hit->keyword) ? $hit->keyword : array($hit->keyword)) : false,
    "start_date" => $hit->start_date ? date("j M Y", strtotime($hit->start_date)) : false,
    "already_submitted" => ($cycle && $cycle->hasSubmissionForActivityId($hit->id)),
  );
}

$page->set('totalresults', $totalresults);
$page->set('firstresult', $offset + 1);
$page->set('lastresult', min($offset + $results_per_page, $totalresults));
if ($totalresults > $results_per_page) {
  $url = $_SERVER["REQUEST_URI"];
  $url = preg_replace("/([?&])p=[^&]*(&|$)/e", "'$2'?'$1':'';", $url);
  $url .= (preg_match("/\?/", $url) ? "&" : "?") . "p=";
  $page->set('pagination', CiprUtils::renderPagination($offset, $results_per_page, $totalresults, $url));
}
$page->set("activities", $activities);

$page->set('pagetitle', 'Activities');
$page->set('selectedtab', 'activities');

$page->set('content', $page->render('activity_search'));

$page->set("facets", $facets);
$page->set("sidebar", $page->render("activity_search_sidebar"));

if (isset($_SERVER["HTTP_X_REQUESTED_WITH"]) && $_SERVER["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest") {
  echo $page->render("activity_search_ajax");
} else {
  echo $page->render('page');
}