Go live.
authorIain Patterson <me@iain.cx>
Tue, 9 Apr 2013 15:44:41 +0000 (11:44 -0400)
committerIain Patterson <me@iain.cx>
Wed, 10 Apr 2013 10:10:48 +0000 (06:10 -0400)
27 files changed:
.gitignore [new file with mode: 0644]
Makefile [new file with mode: 0644]
lib/admin.php [new file with mode: 0644]
lib/area.php [new file with mode: 0644]
lib/city.php [new file with mode: 0644]
lib/constants.php [new file with mode: 0644]
lib/contact.php [new file with mode: 0644]
lib/delivery.php [new file with mode: 0644]
lib/donation.php [new file with mode: 0644]
lib/footer.php [new file with mode: 0644]
lib/forms.php [new file with mode: 0644]
lib/functions.php [new file with mode: 0644]
lib/header.php [new file with mode: 0644]
lib/hub.php [new file with mode: 0644]
lib/menu.php [new file with mode: 0644]
lib/order.php [new file with mode: 0644]
propel/build.properties [new file with mode: 0644]
propel/build/classes/ReadifoodObject.php [new file with mode: 0644]
propel/build/classes/readifood/Contact.php [new file with mode: 0644]
propel/build/classes/readifood/Donation.php [new file with mode: 0644]
propel/build/classes/readifood/Hub.php [new file with mode: 0644]
propel/build/classes/readifood/Order.php [new file with mode: 0644]
propel/schema.xml [new file with mode: 0644]
www/.htaccess [new file with mode: 0644]
www/index.php [new file with mode: 0644]
www/logo.png [new file with mode: 0644]
www/style.css [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..d20bcbf
--- /dev/null
@@ -0,0 +1,3 @@
+propel/build/
+propel/schema-transformed.xml
+propel/runtime-conf.xml
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..cfa96a4
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,2 @@
+all:
+       cd propel && propel-gen
diff --git a/lib/admin.php b/lib/admin.php
new file mode 100644 (file)
index 0000000..39f6db4
--- /dev/null
@@ -0,0 +1,10 @@
+<?php
+
+  function check_admin($level, $complaint = null) {
+    global $admin_level;
+    if ($admin_level >= $level) return true;
+    if (isset($complaint)) echo "<p>Insufficient privileges to $complaint.</p>\n";
+    return false;
+  }
+
+?>
diff --git a/lib/area.php b/lib/area.php
new file mode 100644 (file)
index 0000000..5785ed2
--- /dev/null
@@ -0,0 +1,261 @@
+<?php
+
+  if ($_POST['area_name']) {
+    $id = add_area($_POST['area_name'], $_POST['city_id']);
+    if ($id !== false) {
+      echo "<p>Added area.</p>\n";
+      $parameters = array($_POST['area_name'], $id);
+    }
+  }
+  else if ($_POST['city_id']) {
+    /* XXX: city_id is actually a string $city_name/$city_id */
+    header(sprintf("Location: http%s://%s/%s/in/%s", ($_SERVER['HTTPS']) ? "s" : "", $_SERVER['HTTP_HOST'], $module, $_POST['city_id']));
+    exit;
+  }
+  else if ($_POST['update_area']) {
+    update_area_delivery_days($parameters[0]);
+  }
+
+  function show_areas($offset, $per_page, $city_name = null, $city_id = null) {
+    if (isset($city_name) || isset($city_id)) {
+      if (isset($city_id)) $city = get_city_by_id($city_id);
+      else if ($city_name) $city = get_city_by_name($city_name);
+      if ($city) {
+        echo "<p>Areas in " . $city->getLink(get_city_displayname($city)) . ":";
+        $q = new AreaQuery;
+        $areas = $q->filterByCityId($city_id)->find();
+
+        if (count($areas)) {
+          foreach ($areas as $area) {
+            echo "<br>\nArea: " . $area->getStrongLink();
+            printf(" <a class=\"small\" href=\"/contact/in/area/%s/%d\">Contacts</a>", urlencode($area->getName()), $area->getId());
+            printf(" <a class=\"small\" href=\"/donation/in/area/%s/%d\">Donations</a>", urlencode($area->getName()), $area->getId());
+            printf(" <a class=\"small\" href=\"/order/in/area/%s/%d\">Orders</a>", urlencode($area->getName()), $area->getId());
+            if (check_admin(1)) {
+              echo " " . $area->getDeleteLink();
+            }
+          }
+        }
+        else echo " none";
+        echo "</p>\n";
+      }
+      else echo "<p>No such city!</p>\n";
+    }
+  }
+
+  function show_area_cities_form($city_id = null) {
+    $q = new CityQuery;
+    $cities = $q->find();
+    if (! count($cities)) {
+      echo "<p>No <a href=\"/city\">cities</a>!</p>\n";
+      return;
+    }
+
+    echo "<form method=\"POST\" action=\"" . $_SCRIPT['REQUEST_URI'] . "\">\n";
+    echo "<p>Show areas in\n";
+    echo "<select name=\"city_id\">\n";
+    foreach ($cities as $city) {
+      option("city_id", sprintf("%s/%s", $city->getName(), $city->getId()), get_city_displayname($city));
+    }
+    echo "</select>\n";
+    echo "<input type=\"submit\" value=\"Show\">\n";
+    echo "</form>\n";
+  }
+
+  function show_new_area_form($city_id = null) {
+    if (! check_admin(1)) return;
+
+    $q = new CityQuery;
+    $cities = $q->find();
+    if (! count($cities)) {
+      echo "<p>No <a href=\"/city\">cities</a>!</p>\n";
+      return;
+    }
+
+    echo "<form method=\"POST\" action=\"" . $_SERVER['REQUEST_URI'] . "\">\n";
+    echo "<p>Add a new area <input name=\"area_name\">\n";
+    echo "in <select name=\"city_id\">\n";
+
+    foreach ($cities as $city) {
+      option("city_id", $city->getId(), get_city_displayname($city));
+    }
+    echo "</select>\n";
+    echo "<input type=\"submit\" value=\"Add\">\n";
+    echo "</form>\n";
+  }
+
+  function add_area($name, $city_id) {
+    if (! check_admin(1, "add an area")) return;
+
+    $name = urldecode($name);
+    $area = get_area_by_name($name, false);
+    if ($area) {
+      echo "<p>$name already exists!</p>\n";
+      show_area($name);
+      return false;
+    }
+
+    $city = get_city_by_id($city_id);
+    if (! $city) {
+      echo "<p>Not a valid city!</p>\n";
+      return false;
+    }
+
+    $area = new Area;
+    $area->setName($name);
+    $area->setCityId($city_id);
+
+    try {
+      $area->save();
+    }
+    catch (Exception $e) {
+      echo "<p>Error adding $name!</p>\n";
+      /* XXX: Why? */
+      return false;
+    }
+
+    return $area->getId();
+  }
+
+  function delete_city($name) {
+    if (! check_admin(1, "delete a city")) return;
+
+    $city = get_city_by_name($name);
+    if (! $city) return false;
+
+    try {
+      $city->delete();
+    }
+    catch (Exception $e) {
+      echo "<p>Error deleting $name!</p>\n";
+      /* XXX: Why? Check for addresses in use... */
+      return false;
+    }
+
+    return true;
+  }
+
+  function show_area_delivery_days_form($days) {
+    global $week;
+    echo "Delivery days:";
+    if (check_admin(1)) {
+      for ($i = 0; $i < count($week); $i++) {
+        echo " <input type=\"checkbox\" name=\"day_$i\"";
+        if ($days & (1 << $i)) echo " checked";
+        echo ">$week[$i]\n";
+      }
+    }
+    else {
+      if (! $days) echo " none";
+      else {
+        for ($i = 0; $i < count($week); $i++) {
+          if ($days & (1 << $i)) echo " $week[$i]";
+        }
+      }
+    }
+  }
+
+  function update_area_delivery_days($name) {
+    global $week;
+
+    if (! check_admin(1, "edit an area")) return false;
+
+    $days = 0;
+    for ($i = 0; $i < count($week); $i++) {
+      if ($_POST['day_' . $i] == "on") $days |= (1 << $i);
+    }
+
+    $area = get_area_by_name($name);
+    if (! $area) return false;
+
+    $area->setDays($days);
+
+    try {
+      $area->save();
+    }
+    catch (Exception $e) {
+      echo "<p>Error updating area!</p>\n";
+      return false;
+    }
+
+    echo "<p>Updated area.</p>\n";
+    return true;
+  }
+
+  function delete_area($name, $id = null, &$city_id = null) {
+    if (! check_admin(1, "delete a area")) return;
+
+    if (isset($id)) $area = get_area_by_id($id);
+    else $area = get_area_by_name($name);
+    if (! $area) return false;
+
+    /* Remember city ID for dropdown. */
+    $city_id = $area->getCityId();
+
+    try {
+      $area->delete();
+      echo "<p>Deleted area.</p>\n";
+    }
+    catch (Exception $e) {
+      echo "<p>Error deleting $name!</p>\n";
+      /* XXX: Why? Check for addresses in use... */
+      return false;
+    }
+
+    return true;
+  }
+
+  function show_area($name, &$city_id = null) {
+    $area = get_area_by_name($name);
+    if (! $area) return;
+
+    echo "<form method=\"POST\" action=\"" . $_SERVER['REQUEST_URI'] . "\">\n";
+    echo "<p>Area: <span class=\"strong\">" . $area->getName() . "</span>";
+    printf(" <a class=\"small\" href=\"/contact/in/area/%s/%d\">Contacts</a>", urlencode($area->getName()), $area->getId());
+    printf(" <a class=\"small\" href=\"/donation/in/area/%s/%d\">Donations</a>", urlencode($area->getName()), $area->getId());
+    printf(" <a class=\"small\" href=\"/order/in/area/%s/%d\">Orders</a>", urlencode($area->getName()), $area->getId());
+    if (check_admin(1)) {
+      echo " " . $area->getDeleteLink();
+    }
+    $city = get_city_by_id($area->getCityId());
+    if ($city) {
+      /* Remember city ID for dropdown. */
+      $city_id = $city->getId();
+      echo " in " . $city->getLink(get_city_displayname($city));
+    }
+    echo ": ";
+    echo "\n<br>";
+    show_area_delivery_days_form($area->getDays());
+    if (check_admin(1)) {
+      echo "<input type=\"submit\" name=\"update_area\" value=\"Update\">\n";
+    }
+    echo "</p>\n";
+    echo "</form>\n";
+  }
+
+  /* /area/in/Cambridge/1 */
+  if (count($parameters)) {
+    if ($parameters[0] == "in") {
+      $city_id = $parameters[2];
+      show_areas(0, 10, $parameters[1], $city_id);
+      show_new_area_form($city_id);
+    }
+  }
+  list($name, $id, $args) = parse_parameters($parameters);
+  //echo "<p>$name($id) " . print_r($args, true) . "</p>\n";
+  if (count($args)) {
+    switch ($args[0]) {
+      case "delete":
+        delete_area($name, $id, $city_id);
+      break;
+    }
+  }
+  else if (isset($name)) show_area($name, $city_id);
+  else {
+    show_area_cities_form($city_id);
+    show_new_area_form($city_id);
+  }
+
+  if (count($parameters))
+    show_area_cities_form($city_id);
+?>
diff --git a/lib/city.php b/lib/city.php
new file mode 100644 (file)
index 0000000..29f2c75
--- /dev/null
@@ -0,0 +1,145 @@
+<?php
+
+  /* XXX: Show links to other objects.  Don't show cities by default. */
+
+  if ($_POST['city_name']) {
+    $id = add_city($_POST['city_name'], $_POST['city_postcode_area']);
+    if ($id !== false) {
+      echo "<p>Added city.</p>\n";
+      $parameters = array($_POST['city_name'], $id);
+    }
+  }
+
+  function show_cities($offset, $per_page, $name = null) {
+    echo "<p>Cities:";
+    $q = new CityQuery;
+    if (isset($name)) $q->filterByName($name);
+    $p = $q->paginate($offset, $per_page);
+    if (count($p)) {
+      foreach ($p as $city) {
+        echo "<br>\nCity: " . $city->getStrongLink(get_city_displayname($city));
+        printf(" <a class=\"small\" href=\"/area/in/%s/%d\">Areas</a>", urlencode($city->getName()), $city->getID());
+        printf(" <a class=\"small\" href=\"/contact/in/city/%s/%d\">Contacts</a>", urlencode($city->getName()), $city->getId());
+        printf(" <a class=\"small\" href=\"/donation/in/city/%s/%d\">Donations</a>", urlencode($city->getName()), $city->getId());
+        printf(" <a class=\"small\" href=\"/order/in/city/%s/%d\">Orders</a>", urlencode($city->getName()), $city->getId());
+        if (check_admin(1)) {
+          echo " " . $city->getDeleteLink();
+        }
+      }
+    }
+    else echo " none";
+    echo "</p>\n";
+  }
+
+  function show_new_city_form() {
+    if (! check_admin(1)) return;
+
+    echo "<form method=\"POST\" action=\"" . $_SERVER['REQUEST_URI'] . "\">\n";
+    echo "<p>Add a new city <input name=\"city_name\">\n";
+    echo "with postcode prefix <input name=\"city_postcode_area\" size=4 maxlength=4>\n";
+    echo "<input type=\"submit\" value=\"Add\"></p>\n";
+  }
+
+  function add_city($name, $postcode) {
+    if (! check_admin(1, "add a city")) return;
+
+    if (preg_match('/^([A-Za-z]+)/', $postcode, $m)) {
+      $prefix = strtoupper($m[1]);
+    }
+    else {
+      echo "<p>Invalid postcode prefix!</p>\n";
+      return false;
+    }
+
+    $city = get_city_by_name($name, $prefix, false);
+    if ($city) {
+      echo "<p>$name already exists!</p>\n";
+      show_city($name, $city->getId());
+      return false;
+    }
+
+    $city = new City;
+    $city->setName($name);
+    $city->setPostcodeArea($prefix);
+
+    try {
+      $city->save();
+    }
+    catch (Exception $e) {
+      echo "<p>Error adding $name!</p>\n";
+      /* XXX: Why? */
+      return false;
+    }
+
+    return $city->getId();
+  }
+
+  function delete_city($name, $id = null) {
+    if (! check_admin(1, "delete a city")) return;
+
+    if (isset($id)) $city = get_city_by_id($id);
+    else $city = get_city_by_name($name);
+    if (! $city) return false;
+
+    try {
+      $city->delete();
+      echo "<p>Deleted city.</p>\n";
+    }
+    catch (Exception $e) {
+      echo "<p>Error deleting $name!</p>\n";
+      /* XXX: Why? Check for addresses in use... */
+      return false;
+    }
+
+    return true;
+  }
+
+  function show_city($name, $id = null) {
+    if (isset($id)) $city = get_city_by_id($id);
+    else $city = get_city_by_name($name);
+    if (! $city) return;
+
+    echo "<p>City: <span class=\"strong\">" . get_city_displayname($city) . "</span>";
+    printf(" <a class=\"small\" href=\"/area/in/city/%s/%d\">Areas</a>", urlencode($city->getName), $city->getID());
+    printf(" <a class=\"small\" href=\"/contact/in/city/%s/%d\">Contacts</a>", urlencode($city->getName()), $city->getId());
+    printf(" <a class=\"small\" href=\"/donation/in/city/%s/%d\">Donations</a>", urlencode($city->getName()), $city->getId());
+    printf(" <a class=\"small\" href=\"/order/in/city/%s/%d\">Orders</a>", urlencode($city->getName()), $city->getId());
+    if (check_admin(1)) {
+      echo " " . $city->getDeleteLink();
+    }
+
+    $q = new AreaQuery;
+    $areas = $q->filterByCityId($city->getId())->find();
+    if (count($areas)) {
+      foreach ($areas as $area) {
+        echo "<br>\nArea: " . $area->getLink();
+      }
+    }
+
+    echo "</p>\n";
+  }
+
+  list($name, $id, $args) = parse_parameters($parameters);
+  //echo "<p>$name($id) " . print_r($args, true) . "</p>\n";
+  if (count($args)) {
+    switch ($args[0]) {
+      case "delete":
+        delete_city($name, $id);
+      break;
+
+      default:
+        show_cities(0, 10, $name);
+        show_new_city_form();
+      break;
+    }
+  }
+  else if (isset($name)) show_city($name, $id);
+  else {
+    show_cities(0, 10);
+    show_new_city_form();
+  }
+
+  # XXX: Format URL in branch case...
+  if (count($parameters))
+  echo "<p>Show all <a href=\"/city\">cities</a></p>\n";
+?>
diff --git a/lib/constants.php b/lib/constants.php
new file mode 100644 (file)
index 0000000..06a7c9e
--- /dev/null
@@ -0,0 +1,25 @@
+<?php
+
+/* Area days of week. */
+  $week = array("Sun", "Mon", "Tues", "Wed", "Thu", "Fri", "Sat");
+
+  /* Contact roles. */
+  $roles = array("Staff", "Driver", "Requester", "Beneficiary", "Donor");
+  $ROLE_STAFF = 1 << 0;
+  $ROLE_DRIVER = 1 << 1;
+  $ROLE_REQUESTER = 1 << 2;
+  $ROLE_BENEFICIARY = 1 << 3;
+  $ROLE_DONOR = 1 << 4;
+
+  /* Order states. */
+  $states = array("placed", "picked", "despatched", "undelivered", "delivered", "cancelled");
+  $all_states = (1 << count($states)) - 1;
+  $STATE_PLACED = 1 << 0;
+  $STATE_PICKED = 1 << 1;
+  $STATE_DESPATCHED = 1 << 2;
+  $STATE_UNDELIVERED = 1 << 3;
+  $STATE_DELIVERED = 1 << 4;
+  $STATE_CANCELLED = 1 << 5;
+  $STATE_ANY = $all_states;
+
+?>
diff --git a/lib/contact.php b/lib/contact.php
new file mode 100644 (file)
index 0000000..170d342
--- /dev/null
@@ -0,0 +1,510 @@
+<?php
+
+  if (isset($_POST['show_add_contact'])) {
+    $city_id = $_POST['city_id'];
+    show_new_contact_form($city_id);
+  }
+  else if (isset($_POST['add_contact'])) {
+    $id = add_contact($displayname);
+    if ($id !== false) {
+      echo "<p>Added contact.</p>\n";
+      $parameters = array($displayname, $id);
+    }
+  }
+  else if (isset($_POST['update_contact'])) {
+    list($name, $id, $args) = parse_parameters($parameters);
+    $q = new ContactQuery;
+    $contact = $q->findOneById($id);
+    if ($contact) {
+      $area = get_contact_area($contact);
+      if ($area) $area_id = $area->getId();
+      if (update_contact($contact, $area_id) !== false) {
+        echo "<p>Updated contact.</p>\n";
+        $parameters = array($contact->getDisplayname(), $contact->getId());
+      }
+    }
+    else {
+      echo "<p>No such contact!</p>\n";
+    }
+  }
+  else if ($_POST['search_contact']) {
+    header(sprintf("Location: http%s://%s/%s/search/%s", ($_SERVER['HTTPS']) ? "s" : "", $_SERVER['HTTP_HOST'], $module, urlencode($_POST['search_contact'])));
+    exit;
+  }
+  else if ($_POST['area_id']) {
+    $q = new AreaQuery;
+    $area = $q->findOneById($_POST['area_id']);
+    header(sprintf("Location: http%s://%s/%s/in/area/%s/%d", ($_SERVER['HTTPS']) ? "s" : "", $_SERVER['HTTP_HOST'], $module, urlencode($area->getName()), $_POST['area_id']));
+    exit;
+  }
+  else if ($_POST['city_id']) {
+    $q = new CityQuery;
+    $city = $q->findOneById($_POST['city_id']);
+    header(sprintf("Location: http%s://%s/%s/in/city/%s/%d", ($_SERVER['HTTPS']) ? "s" : "", $_SERVER['HTTP_HOST'], $module, urlencode($city->getName()), $_POST['city_id']));
+    exit;
+  }
+
+  function show_contact_summary(&$contact) {
+    echo "<br>\nContact " . $contact->getLink();
+    $role = $contact->getRole();
+    $role_string = get_contact_role_string($contact);
+    if ($role_string) echo " $role_string";
+    if ($role & $GLOBALS['ROLE_DONOR']) printf(" <a class=\"small\" href=\"/donation/from/contact/%s/%d\">Donations</a>", urlencode($contact->getDisplayname()), $contact->getId());
+    if ($role & $GLOBALS['ROLE_REQUESTER']) printf(" <a class=\"small\" href=\"/order/from/requester/%s/%d\">Requested</a>", urlencode($contact->getDisplayname()), $contact->getId());
+    if ($role & $GLOBALS['ROLE_BENEFICIARY']) printf(" <a class=\"small\" href=\"/order/to/beneficiary/%s/%d\">Orders</a>", urlencode($contact->getDisplayname()), $contact->getId());
+    if (check_admin(1)) {
+      echo " " . $contact->getDeleteLink();
+    }
+    $area = get_contact_area($contact);
+    echo " in " . $area->getLink();
+  }
+
+  function show_contacts($offset, $per_page, $address_ids) {
+    $q = new ContactQuery;
+    $contacts = $q->filterByAddressId($address_ids)->orderByForename()->orderBySurname()->find();
+    if (count($contacts)) {
+      foreach ($contacts as $contact) show_contact_summary($contact);
+    }
+    else echo " none";
+  }
+
+  function search_contacts($offset, $per_page, $search) {
+    $q = new ContactQuery;
+    $contacts = $q->filterByDisplayname("%$search%")->find();
+    echo "<p>Contacts matching '" . htmlspecialchars($search) . "':";
+    if (count($contacts)) {
+      foreach ($contacts as $contact) show_contact_summary($contact);
+    }
+    else echo "none";
+    echo "</p>\n";
+  }
+
+  function show_city_contacts($offset, $per_page, $city_name, $city_id = null) {
+    if (isset($city_id)) $city = get_city_by_id($city_id);
+    else if ($city_name) $city = get_city_by_name($city_name);
+    if ($city) {
+      $q = new AreaQuery;
+      $areas = $q->filterByCityId($city->getId())->find();
+      $area_ids = array();
+      foreach ($areas as $area) $area_ids[] = $area->getId();
+
+      $q = new AddressQuery;
+      $addresses = $q->filterByAreaId($area_ids)->find();
+      $address_ids = array();
+      foreach ($addresses as $address) $address_ids[] = $address->getId();
+
+      echo "<p>Contacts in city " . $city->getLink(get_city_displayname($city)) . ":";
+      return show_contacts($offset, $per_page, $address_ids);
+    }
+    else echo "<p>No such city!</p>\n";
+  }
+
+  function show_area_contacts($offset, $per_page, $area_name, $area_id = null) {
+    if (isset($area_id)) $area = get_area_by_id($area_id);
+    else if ($area_name) $area = get_area_by_name($area_name);
+    if ($area) {
+      $q = new AddressQuery;
+      $addresses = $q->filterByAreaId($area->getId())->find();
+      $address_ids = array();
+      foreach ($addresses as $address) $address_ids[] = $address->getId();
+
+      echo "<p>Contacts in area " . $area->getLink() . ":";
+      return show_contacts($offset, $per_page, $address_ids);
+    }
+    else echo "<p>No such area!</p>\n";
+  }
+
+  function show_contact_areas_form($city_id = null) {
+    $areas = get_city_areas($city_id);
+    if (! count($areas)) {
+      echo "<p>No <a href=\"/area\">areas</a>!</p>\n";
+      return;
+    }
+
+    echo "<form method=\"POST\" action=\"" . $_SERVER['REQUEST_URI'] . "\">\n";
+    echo "<p>Show contacts in area\n";
+    echo "<select name=\"area_id\">\n";
+    foreach ($areas as $area) {
+      option("area_id", $area->getId(), get_area_displayname($area));
+    }
+    echo "</select>\n";
+    echo "<input type=\"submit\" value=\"Show\">\n";
+    echo "</form>\n";
+  }
+
+  function show_contact_cities_form($city_id = null) {
+    $q = new CityQuery;
+    $cities = $q->orderByName()->find();
+
+    if (! count($cities)) {
+      echo "<p>No <a href=\"/city\">cities</a>!</p>\n";
+      return;
+    }
+
+    echo "<form method=\"POST\" action=\"" . $_SERVER['REQUEST_URI'] . "\">\n";
+    echo "<p>Show contacts in city\n";
+    echo "<select name=\"city_id\">\n";
+    foreach ($cities as $city) {
+      option("city_id", $city->getId(), get_city_displayname($city), $city_id);
+    }
+    echo "</select>\n";
+    echo "<input type=\"submit\" value=\"Show\">\n";
+    echo "</form>\n";
+  }
+
+  function show_contact_search_form() {
+    echo "<form method=\"POST\" action=\"" . $_SERVER['REQUEST_URI'] . "\">\n";
+    echo "<p>Search for contacts:";
+    input("search_contact");
+    echo "<input type=\"submit\" value=\"Search\">\n";
+    echo "</form>\n";
+  }
+
+  function show_contact_forms($city_id) {
+    show_contact_areas_form($city_id);
+    show_contact_cities_form($city_id);
+    show_contact_search_form();
+  }
+
+  function show_contact_role_form($role) {
+    global $roles;
+
+    for ($i = 0; $i < count($roles); $i++) {
+      echo " <input type=\"checkbox\" name=\"role_$i\"";
+      if ($role & (1 << $i)) echo " checked";
+      echo ">$roles[$i]\n";
+    }
+  }
+
+  function show_contact_form($contact = null) {
+    global $roles;
+
+    if (! $contact) $contact = new Contact;
+
+    /* Role. */
+    echo "<tr>\n";
+    echo "  <td>Role</td>\n";
+    echo "  <td>"; show_contact_role_form($contact->getRole()); echo "</td>\n";
+    echo "</tr>\n";
+
+    /* Forename. */
+    echo "<tr>\n";
+    echo "  <td>Forename</td>\n";
+    echo "  <td>"; input("forename", $contact->getForename()); echo "</td>\n";
+    echo "</tr>\n";
+
+    /* Middle names. */
+    echo "<tr>\n";
+    echo "  <td>Middle name(s)</td>\n";
+    echo "  <td>"; input("middle", $contact->getMiddle()); echo "</td>\n";
+    echo "</tr>\n";
+
+    /* Surname. */
+    echo "<tr>\n";
+    echo "  <td>Surname</td>\n";
+    echo "  <td>"; input("surname", $contact->getSurname()); echo "</td>\n";
+    echo "</tr>\n";
+
+    /* Display name. */
+    echo "<tr>\n";
+    echo "  <td>Display name (if not concatenation of above)</td>\n";
+    echo "  <td>"; input("displayname", $contact->getDisplayname()); echo "</td>\n";
+    echo "</tr>\n";
+
+    /* Address. */
+    $address = get_contact_address($contact);
+    if (! $address) $address = new Address;
+    echo "<tr>\n";
+    echo "  <td>Address</td>\n";
+    echo "  <td>"; textarea("address", $address->getLine()); echo "</td>\n";
+    echo "</tr>\n";
+
+    /* Postcode. */
+    echo "<tr>\n";
+    echo "  <td>Postcode</td>\n";
+    echo "  <td>"; input("postcode", $address->getPostcode()); echo "</td>\n";
+    echo "</tr>\n";
+
+    /* Telephone. */
+    echo "<tr>\n";
+    echo "  <td>Telephone</td>\n";
+    echo "  <td>"; input("telephone1", $contact->getTelephone1()); echo "</td>\n";
+    echo "</tr>\n";
+    echo "<tr>\n";
+    echo "  <td>Alternative telephone</td>\n";
+    echo "  <td>"; input("telephone2", $contact->getTelephone2()); echo "</td>\n";
+    echo "</tr>\n";
+
+    /* Email. */
+    echo "<tr>\n";
+    echo "  <td>Email</td>\n";
+    echo "  <td>"; input("email", $contact->getEmail()); echo "</td>\n";
+    echo "</tr>\n";
+
+    /* Area. */
+    $area = get_contact_area($contact);
+    if ($area) $area_id = $area->getId();
+    echo "<tr>\n";
+    echo "  <td>Area</td>\n";
+    echo "  <td><select name=\"area_id\">\n";
+    $areas = get_city_areas();
+    foreach ($areas as $area) {
+      option("area_id", $area->getId(), get_area_displayname($area), $area_id);
+    }
+    echo "  </select></td>\n";
+    echo "</tr>\n";
+  }
+
+  function show_new_contact_form($city_id = null) {
+    if (! check_admin(1)) return;
+
+    $areas = get_city_areas($city_id);
+    if (! count($areas)) {
+      echo "<p>No <a href=\"/area\">areas</a>!</p>\n";
+      return;
+    }
+
+    echo "<form method=\"POST\" action=\"" . $_SERVER['REQUEST_URI'] . "\">\n";
+    echo "<p>Add a new contact:</p>\n";
+
+    echo "<table>\n";
+    show_contact_form($contact);
+
+    echo "<tr>\n";
+    echo "  <td colspan=2>"; submit("add_contact", "Add"); echo "</td></tr>\n";
+    echo "</tr>\n";
+    echo "</table>\n";
+    echo "</form>\n";
+  }
+
+  function show_add_new_contact_form() {
+    if (! check_admin(1)) return;
+
+    $q = new CityQuery;
+    $cities = $q->find();
+    if (! count($cities)) {
+      echo "<p>No <a href=\"/city\">cities</a>!</p>\n";
+      return;
+    }
+
+    echo "<form method=\"POST\" action=\"" . $_SERVER['REQUEST_URI'] . "\">\n";
+    echo "<p>Add a new contact in <select name=\"city_id\">\n";
+    foreach ($cities as $city) {
+      option("city_id", $city->getId(), get_city_displayname($city));
+    }
+    echo "</select>";
+    submit("show_add_contact", "Proceed");
+    echo "</p>\n";
+    echo "</form>\n";
+  }
+
+  function update_contact(&$contact, $area_id, $new = false) {
+    global $roles;
+
+    $role = 0;
+    for ($i = 0; $i < count($roles); $i++) {
+      if ($_POST['role_' . $i] == "on") $role |= (1 << $i);
+    }
+
+    /* Staff can place orders. */
+    if ($role & (1 << 0)) $role |= (1 << 2);
+
+    $forename = $_POST['forename'];
+    $middle = $_POST['middle'];
+    $surname = $_POST['surname'];
+    $displayname = $_POST['displayname'];
+
+    if (! $forename && ! $surname) {
+      echo "<p>Must have either a forename or surname!</p>\n";
+      return false;
+    }
+    if ($middle && ! ($forename && $surname)) {
+      echo "<p>Must have both a forename or surname for middle name(s) to make sense!</p>\n";
+      return false;
+    }
+
+    if (! $displayname) {
+      $displayname = $forename;
+      if ($middle) $displayname .= " $middle";
+      if ($forename) $displayname .= " ";
+      $displayname .= $surname;
+      echo "<p>Display name will be $displayname.</p>\n";
+    }
+
+    /* Get address. */
+    $line = $_POST['address'];
+    $postcode = $_POST['postcode'];
+    $q = new AddressQuery;
+    /* XXX: Finding by area properly? */
+    $address = $q->filterByAreaId($area_id)->filterByLine($line)->filterByPostcode($postcode)->findOneOrCreate();
+    if ($address->isNew()) {
+      /* Changing address. */
+      //if (! $new)
+      /*
+        XXX: Check for other contacts at the old address.
+        Make this a new address if there are others, but
+        provide a link to update other contacts.
+      */
+      try {
+        $address->save();
+      }
+      catch (Exception $e) {
+        echo "<p>Error adding $line.</p>\n";
+        return false;
+      }
+    }
+
+    $telephone1 = $_POST['telephone1'];
+    $telephone2 = $_POST['telephone2'];
+    $email = $_POST['email'];
+
+    $contact->setRole($role);
+    $contact->setForename($forename);
+    $contact->setMiddle($middle);
+    $contact->setSurname($surname);
+    $contact->setDisplayname($displayname);
+    $contact->setTelephone1($telephone1);
+    $contact->setTelephone2($telephone2);
+    $contact->setEmail($email);
+    $contact->setAddressId($address->getId());
+
+    try {
+      $contact->save();
+    }
+    catch (Exception $e) {
+      if ($new) echo "<p>Error adding $displayname.</p>\n";
+      else echo "<p>Error updating $displayname.</p>\n";
+      return false;
+    }
+
+    return true;
+  }
+
+  function add_contact(&$name) {
+    if (! check_admin(1, "add a contact")) return;
+
+    $area_id = $_POST['area_id'];
+    if (! is_numeric($area_id)) {
+      echo "<p>Invalid area!</p>\n";
+      return false;
+    }
+
+    $area = get_area_by_id($area_id);
+    if (! $area) {
+      echo "<p>No such area!</p>\n";
+      return false;
+    }
+
+    $contact = new Contact;
+    if (! update_contact($contact, $area_id, true)) return false;
+    return $contact->getId();
+  }
+
+  function delete_contact($name, $id = null, &$city_id = null) {
+    if (! check_admin(1, "delete a contact")) return;
+
+    if (isset($id)) $contact = get_contact_by_id($id);
+    else $contact = get_contact_by_name($name);
+    if (! $contact) return false;
+
+    ///* Remember city ID for dropdown. */
+    //$city_id = $area->getCityId();
+
+    try {
+      $contact->delete();
+      echo "<p>Deleted contact.</p>\n";
+    }
+    catch (Exception $e) {
+      echo "<p>Error deleting $name!</p>\n";
+      return false;
+    }
+
+    return true;
+  }
+
+  function show_contact($name, &$id = null) {
+    if (isset($id)) $contact = get_contact_by_id($id);
+    else $contact = get_contact_by_name($name);
+    if (! $contact) return;
+
+    echo "<form method=\"POST\" action=\"" . $_SERVER['REQUEST_URI'] . "\">\n";
+    echo "<p>Contact: <span class=\"strong\">" . $contact->getDisplayname() . "</span>";
+    $role = $contact->getRole();
+    $role_string = get_contact_role_string($contact);
+    if ($role_string) echo " $role_string";
+    if ($role & $GLOBALS['ROLE_DONOR']) printf(" <a class=\"small\" href=\"/donation/from/contact/%s/%d\">Donations</a>", urlencode($contact->getDisplayname()), $contact->getId());
+    if ($role & $GLOBALS['ROLE_REQUESTER']) printf(" <a class=\"small\" href=\"/order/from/requester/%s/%d\">Requested</a>", urlencode($contact->getDisplayname()), $contact->getId());
+    if ($role & $GLOBALS['ROLE_BENEFICIARY']) printf(" <a class=\"small\" href=\"/order/to/beneficiary/%s/%d\">Orders</a>", urlencode($contact->getDisplayname()), $contact->getId());
+    if (check_admin(1)) {
+      echo " " . $contact->getDeleteLink();
+    }
+    $city = get_contact_city($contact);
+    if ($city) echo " in " . $city->getLink(get_city_displayname($city));
+    echo ": ";
+    echo "\n</p>";
+
+    echo "<table>\n";
+    show_contact_form($contact);
+
+    if (check_admin(1)) {
+      echo "<tr>\n";
+      echo "  <td colspan=2>";
+      submit("update_contact", "Update");
+      echo "</td>\n";
+      echo "</tr>\n";
+    }
+
+    echo "</table>\n";
+    echo "</form>\n";
+  }
+
+  /* /contact/in/area/Cambridge/1 */
+  if (count($parameters)) {
+    if ($parameters[0] == "in") {
+      switch ($parameters[1]) {
+        case "area":
+          $area_id = $parameters[3];
+          $_POST['area_id'] = $area_id;
+          $q = new AreaQuery;
+          $area = $q->findOneById($area_id);
+          $city = get_area_city($area);
+          if ($city) $city_id = $city->getId();
+          show_area_contacts(0, 10, $parameters[2], $area_id);
+        break;
+
+        case "city":
+          $city_id = $parameters[3];
+          $_POST['city_id'] = $city_id;
+          $q = new CityQuery;
+          $city = $q->findOneById($city_id);
+          show_city_contacts(0, 10, $parameters[2], $city_id);
+        break;
+      }
+
+      show_add_new_contact_form($city_id);
+    }
+    else if ($parameters[0] == "search") {
+      search_contacts(0, 10, $parameters[1]);
+    }
+  }
+  list($name, $id, $args) = parse_parameters($parameters);
+  //echo "<p>$name($id) " . print_r($args, true) . "</p>\n";
+  if (count($args)) {
+    switch ($args[0]) {
+      case "delete":
+        delete_contact($name, $id);
+      break;
+    }
+  }
+  else if (isset($name)) show_contact($name, $id);
+  else {
+    /* XXX: Shown after adding. */
+    show_contact_forms($city_id);
+    show_add_new_contact_form($city_id);
+  }
+
+  if (count($parameters)) {
+    show_contact_forms($city_id);
+  }
+
+?>
diff --git a/lib/delivery.php b/lib/delivery.php
new file mode 100644 (file)
index 0000000..03495df
--- /dev/null
@@ -0,0 +1,122 @@
+<?php
+
+  /* Find orders scheduled for delivery today. */
+  function get_orders_for_today() {
+    $order_ids = array();
+
+    $q = new OrderQuery();
+    $orders = $q->filterByDate(time('Y-m-d'))->find();
+    if (count($orders)) {
+      foreach ($orders as $order) $order_ids[] = $order->getId();
+    }
+    else echo "<p>No deliveries today.</p>\n";
+
+    return $order_ids;
+  }
+
+  /* Find drivers with deliveries today. */
+  function get_drivers_by_order_id($order_ids) {
+    $driver_ids = array();
+
+    if (count($order_ids)) {
+      $dbh = Propel::getConnection();
+      $sth = $dbh->prepare("select * from OrderState o where updated=(select max(updated) from OrderState where order_id=o.order_id) and order_id in (" . implode(",", $order_ids) . ") and driver_id is not null");
+      $sth->execute();
+      $order_states = OrderStatePeer::populateObjects($sth);
+      if (count($order_states)) {
+        foreach ($order_states as $order_state) $driver_ids[] = $order_state->getDriverId();
+      }
+      else echo "<p>No drivers assigned for deliveries.</p>\n";
+    }
+
+    return $driver_ids;
+  }
+
+  /* Find schedule for a driver today. */
+  function get_driver_schedule_by_order_id($driver_id, $all_order_ids) {
+    $order_ids = array();
+
+    if (! count($all_order_ids)) {
+      echo "<p>No orders for today.</p>\n";
+      return null;
+    }
+
+    $dbh = Propel::getConnection();
+    $sth = $dbh->prepare("select * from OrderState o where updated=(select max(updated) from OrderState where order_id=o.order_id) and order_id in (" . implode(",", $all_order_ids) . ") and driver_id=$driver_id");
+    $sth->execute();
+    $order_states = OrderStatePeer::populateObjects($sth);
+    if (count($order_states)) {
+      foreach ($order_states as $order_state) $order_ids[] = $order_state->getOrderId();
+    }
+    else echo "<p>No deliveries for this driver.</p>\n";
+
+    return $order_ids;
+  }
+
+  function show_driver_forms($driver_ids) {
+    global $module;
+
+    if (! count($driver_ids)) return;
+
+    $q = new ContactQuery();
+    $contacts = $q->filterById($driver_ids)->find();
+    if (! count($contacts)) {
+      echo "<p>Can't find drivers!</p>\n";
+      return;
+    }
+
+    echo "<p>Drivers with deliveries scheduled:";
+    foreach ($contacts as $contact) {
+      printf("<br>\n<a href=\"/$module/driver/%s/%d\">%s</a>", urlencode($contact->getDisplayname()), $contact->getId(), htmlspecialchars($contact->getDisplayname()));
+    }
+  }
+
+  function show_driver_schedule($driver_name = null, $driver_id = null) {
+    if (isset($driver_id)) $contact = get_contact_by_id($driver_id);
+    else if (isset($driver_name)) $contact = get_contact_by_name($driver_name);
+    if (! $contact) {
+      echo "<p>No such driver!</p>\n";
+      return;
+    }
+
+    echo "<h3>Delivery schedule for <strong>" . htmlspecialchars($contact->getDisplayname()) . "</strong></h3>\n";
+    $order_ids = get_driver_schedule_by_order_id($contact->getId(), get_orders_for_today());
+    $q = new OrderQuery;
+    $orders = $q->filterById($order_ids)->find();
+    foreach ($orders as $order) {
+      $contact = get_contact_by_id($order->getBeneficiaryId());
+      if (! $contact) continue;
+
+      $area = get_contact_area($contact);
+      echo "<p>Order of <em>" . $order->getQuantity() . "kg</em> for <strong>" . htmlspecialchars($contact->getDisplayname()) . "</strong> in " . htmlspecialchars(get_area_displayname($area)) . ".</p>\n";
+      $hub = get_hub_by_id($order->getHubId(), false);
+      if ($hub) {
+        echo "<p>Deliver to hub <strong> " . htmlspecialchars($hub->getName()) . "</strong>";
+        $address = get_hub_address($hub);
+      }
+      else {
+        echo "<p>Deliver direct to beneficiary";
+        $address = get_contact_address($contact);
+      }
+      $area = get_address_area($address);
+
+      echo " in " . htmlspecialchars($area->getName()) . " at:<br>";
+      $city = get_area_city($area);
+      echo "\n<br>" . htmlspecialchars($address->getLine());
+      echo "\n<br>" . htmlspecialchars($city->getName());
+      echo "\n<br>" . htmlspecialchars($address->getPostcode());
+      echo "</p>\n";
+
+      echo "<hr>\n\n";
+    }
+  }
+
+  list($ignored, $id, $args) = parse_parameters($parameters);
+  if (count($args)) show_driver_schedule($args[0], $args[1]);
+  else {
+    $order_ids = get_orders_for_today();
+    if ($order_ids) $driver_ids = get_drivers_by_order_id($order_ids);
+    if ($driver_ids) show_driver_forms($driver_ids);
+  }
+
+?>
diff --git a/lib/donation.php b/lib/donation.php
new file mode 100644 (file)
index 0000000..8a62a84
--- /dev/null
@@ -0,0 +1,411 @@
+<?php
+
+  if (isset($_POST['show_add_donation'])) {
+    $area_id = $_POST['area_id'];
+    show_new_donation_form($area_id);
+  }
+  else if (isset($_POST['add_donation'])) {
+    $id = add_donation();
+    if ($id !== false) {
+      echo "<p>Donation recorded.</p>\n";
+      $parameters = array("id", $id);
+    }
+  }
+  else if (isset($_POST['update_donation'])) {
+    list($ignored, $id, $args) = parse_parameters($parameters);
+    $q = new DonationQuery;
+    $donation = $q->findOneById($id);
+    if ($donation) {
+      if (update_donation($donation) !== false) {
+        echo "<p>Updated donation.</p>\n";
+        $parameters = array("id", $donation->getId());
+      }
+    }
+    else {
+      echo "<p>No such contact!</p>\n";
+    }
+  }
+  else if ($_POST['area_id']) {
+    $q = new AreaQuery;
+    $area = $q->findOneById($_POST['area_id']);
+    /* XXX: Function to build URL because we need to set a class in links. */
+    header(sprintf("Location: http%s://%s/%s/in/area/%s/%d", ($_SERVER['HTTPS']) ? "s" : "", $_SERVER['HTTP_HOST'], $module, urlencode($area->getName()), $_POST['area_id']));
+    exit;
+  }
+  else if ($_POST['city_id']) {
+    $q = new CityQuery;
+    $city = $q->findOneById($_POST['city_id']);
+    header(sprintf("Location: http%s://%s/%s/in/city/%s/%d", ($_SERVER['HTTPS']) ? "s" : "", $_SERVER['HTTP_HOST'], $module, urlencode($city->getName()), $_POST['city_id']));
+    exit;
+  }
+
+  function show_donations($offset, $per_page, $contact_ids = null, $hub_ids = null) {
+    $q = new DonationQuery;
+    if (isset($contact_ids)) $q->filterByContactId($contact_ids);
+    if (isset($hub_ids)) $q->filterByHubId($hub_ids);
+    $donations = $q->find();
+    if (count($donations)) {
+      foreach ($donations as $donation) {
+        echo "<br>\nDonation " . $donation->getStrongLink($donation->getId()) . ": " . get_donation_displayname($donation);
+
+        /* XXX: Should pull from query. */
+        $q = new ContactQuery;
+        $contact = $q->findOneById($donation->getContactId());
+        if ($contact) echo " from " . $contact->getLink();
+
+        $q = new HubQuery;
+        $hub = $q->findOneById($donation->getHubId());
+        if ($hub) {
+          echo " to " . $hub->getLink();
+          $area = get_hub_area($hub);
+          if ($area) {
+            echo " in " . $area->getLink();
+            $city = get_area_city($area);
+            if ($city) echo ", " . $city->getLink(get_city_displayname($city));
+          }
+        }
+        if (check_admin(1)) {
+          echo " " . $donation->getDeleteLink();
+        }
+      }
+    }
+    else echo " none";
+  }
+
+  function show_city_donations($offset, $per_page, $city_name, $city_id = null) {
+    if (isset($city_id)) $city = get_city_by_id($city_id);
+    else if ($city_name) $city = get_city_by_name($city_name);
+    if ($city) {
+      $hubs = get_city_hubs($city->getId());
+      $hub_ids = array();
+      foreach ($hubs as $hub) $hub_ids[] = $hub->getId();
+
+      echo "<p>Donations in city " . $city->getLink(get_city_displayname($city)) . ":";
+      return show_donations($offset, $per_page, null, $hub_ids);
+    }
+    else echo "<p>No such city!</p>\n";
+  }
+
+  function show_contact_donations($offset, $per_page, $contact_name, $contact_id = null) {
+    if (isset($contact_id)) $contact = get_contact_by_id($contact_id);
+    else if ($contact_name) $contact = get_contact_by_name($contact_name);
+    if ($contact) {
+      echo "<p>Donations from contact " . $contact->getLink() . ":";
+      return show_donations($offset, $per_page, $contact->getId());
+    }
+    else echo "<p>No such contact!</p>\n";
+  }
+
+  function show_hub_donations($offset, $per_page, $hub_name, $hub_id = null) {
+    if (isset($hub_id)) $hub = get_hub_by_id($hub_id);
+    else if ($hub_name) $hub = get_hub_by_name($hub_name);
+    if ($hub) {
+      echo "<p>Donations to hub " . $hub->getLink() . ":";
+      return show_donations($offset, $per_page, null, $hub->getId());
+    }
+    else echo "<p>No such hub!</p>\n";
+  }
+
+  function show_area_donations($offset, $per_page, $area_name, $area_id = null) {
+    if (isset($area_id)) $area = get_area_by_id($area_id);
+    else if ($area_name) $area = get_area_by_name($area_name);
+    if ($area) {
+      $hubs = get_area_hubs($area->getId());
+      $hub_ids = array();
+      foreach ($hubs as $hub) $hub_ids[] = $hub->getId();
+
+      echo "<p>Donations in area " . $area->getLink() . ":";
+      return show_donations($offset, $per_page, null, $hub_ids);
+    }
+    else echo "<p>No such area!</p>\n";
+  }
+
+  function show_donation_areas_form($city_id = null) {
+    $areas = get_city_areas($city_id);
+    if (! count($areas)) {
+      echo "<p>No <a href=\"/area\">areas</a>!</p>\n";
+      return;
+    }
+
+    echo "<form method=\"POST\" action=\"" . $_SERVER['REQUEST_URI'] . "\">\n";
+    echo "<p>Show donations in area\n";
+    echo "<select name=\"area_id\">\n";
+    foreach ($areas as $area) {
+      option("area_id", $area->getId(), get_area_displayname($area));
+    }
+    echo "</select>\n";
+    echo "<input type=\"submit\" value=\"Show\">\n";
+    echo "</form>\n";
+  }
+
+  function show_donation_cities_form($city_id = null) {
+    $q = new CityQuery;
+    $cities = $q->orderByName()->find();
+
+    if (! count($cities)) {
+      echo "<p>No <a href=\"/city\">cities</a>!</p>\n";
+      return;
+    }
+
+    echo "<form method=\"POST\" action=\"" . $_SERVER['REQUEST_URI'] . "\">\n";
+    echo "<p>Show donations in city\n";
+    echo "<select name=\"city_id\">\n";
+    foreach ($cities as $city) {
+      option("city_id", $city->getId(), get_city_displayname($city), $city_id);
+    }
+    echo "</select>\n";
+    echo "<input type=\"submit\" value=\"Show\">\n";
+    echo "</form>\n";
+  }
+
+  function show_donation_forms($city_id) {
+    show_donation_areas_form($city_id);
+    show_donation_cities_form($city_id);
+  }
+
+  function show_donation_form($donation = null, $area_id = null) {
+    if (! $donation) $donation = new Donation;
+
+    /* Date. */
+    echo "<tr>\n";
+    echo "  <td>Date</td>\n";
+    echo "  <td>"; show_date_form("date", $donation->getDate()); echo "</td>\n";
+    echo "</tr>\n";
+
+    /* Contact. */
+    echo "<tr>\n";
+    echo "  <td>Donor</td>\n";
+    echo "  <td><select name=\"contact_id\">\n";
+    $contacts = get_area_donors();
+    foreach ($contacts as $contact) {
+      option("contact_id", $contact->getId(), $contact->getDisplayname(), $donation->getContactId());
+    }
+    echo "</select></td>\n";
+    echo "</tr>\n";
+
+    /* Hub. */
+    echo "<tr>\n";
+    echo "  <td>Hub</td>\n";
+    echo "  <td><select name=\"hub_id\">\n";
+    $hubs = get_area_hubs();
+    foreach ($hubs as $hub) {
+      option("hub_id", $hub->getId(), $hub->getDisplayname(), $donation->getHubId());
+    }
+    echo "</select></td>\n";
+    echo "</tr>\n";
+
+    /* Quantity. */
+    echo "<tr>\n";
+    echo "  <td>Quantity (kg)</td>\n";
+    echo "  <td>"; input("quantity", $donation->getQuantity()); echo "</td>\n";
+    echo "</tr>\n";
+  }
+
+  function show_new_donation_form($area_id = null) {
+    if (! check_admin(1)) return;
+
+    $area = get_area_by_id($area_id);
+    if (! count($area)) {
+      echo "<p>No such <a href=\"/area\">area</a>!</p>\n";
+      return;
+    }
+
+    echo "<form method=\"POST\" action=\"" . $_SERVER['REQUEST_URI'] . "\">\n";
+    echo "<p>Record a donation:</p>\n";
+
+    echo "<table>\n";
+    show_donation_form(null, $area_id);
+
+    echo "<tr>\n";
+    echo "  <td colspan=2>"; submit("add_donation", "Record"); echo "</td></tr>\n";
+    echo "</tr>\n";
+    echo "</table>\n";
+    echo "</form>\n";
+  }
+
+  function show_add_new_donation_form() {
+    if (! check_admin(1)) return;
+
+    /* We intentionally hide areas with no hubs. */
+    $areas = get_city_areas_with_hubs();
+    if (! count($areas)) {
+      echo "<p>Can't record any donations until at least one <a href=\"/area\">area</a> has a <a href=\"/hub\">hub</a>!</p>\n";
+      return;
+    }
+
+    echo "<form method=\"POST\" action=\"" . $_SERVER['REQUEST_URI'] . "\">\n";
+    echo "<p>Record a donation in <select name=\"area_id\">\n";
+    foreach ($areas as $area) {
+      option("area_id", $area->getId(), get_area_displayname($area));
+    }
+    echo "</select>";
+    submit("show_add_donation", "Proceed");
+    echo "</p>\n";
+    echo "</form>\n";
+  }
+
+  function update_donation(&$donation, $new = false) {
+    #$date = ymd_to_iso8601("date");
+    $date = $_POST['date'];
+    $contact_id = $_POST['contact_id'];
+    $hub_id = $_POST['hub_id'];
+    $quantity = $_POST['quantity'];
+
+    if (! $date) $date = time();
+    /* XXX: check date */
+
+    $contact = get_contact_by_id($contact_id);
+    if (! $contact) {
+      echo "<p>Invalid contact!</p>\n";
+      return false;
+    }
+
+    $hub = get_hub_by_id($hub_id);
+    if (! $hub) {
+      echo "<p>Invalid hub!</p>\n";
+      return false;
+    }
+
+    if (! is_numeric($quantity)) {
+      echo "<p>Invalid quantity!</p>\n";
+      return false;
+    }
+
+    $donation->setDate($date);
+    $donation->setContactId($contact_id);
+    $donation->setHubId($hub_id);
+    $donation->setQuantity($quantity);
+
+    try {
+      $donation->save();
+    }
+    catch (Exception $e) {
+      if ($new) echo "<p>Error recording donation.</p>\n";
+      else echo "<p>Error updating donation.</p>\n";
+      return false;
+    }
+
+    return true;
+  }
+
+  function add_donation() {
+    if (! check_admin(1, "record a donation")) return;
+
+    $donation = new Donation;
+    if (! update_donation($donation, true)) return false;
+    return $donation->getId();
+  }
+
+  function delete_donation($id = null) {
+    if (! check_admin(1, "delete a donation")) return;
+
+    if (isset($id)) $donation = get_donation_by_id($id);
+    if (! $donation) return false;
+
+    try {
+      $donation->delete();
+      echo "<p>Deleted donation.</p>\n";
+    }
+    catch (Exception $e) {
+      echo "<p>Error deleting donation $id!</p>\n";
+      return false;
+    }
+
+    return true;
+  }
+
+  function show_donation(&$id = null) {
+    if (isset($id)) $donation = get_donation_by_id($id);
+    if (! $donation) return;
+
+    echo "<form method=\"POST\" action=\"" . $_SERVER['REQUEST_URI'] . "\">\n";
+    echo "<p>Donation <span class=\"strong\">" . $donation->getId() . "</span>";
+    if (check_admin(1)) {
+      echo " " . $donation->getDeleteLink();
+    }
+    echo ": ";
+    echo "\n</p>";
+
+    echo "<table>\n";
+    show_donation_form($donation);
+
+    if (check_admin(1)) {
+      echo "<tr>\n";
+      echo "  <td colspan=2>";
+      submit("update_donation", "Update");
+      echo "</td>\n";
+      echo "</tr>\n";
+    }
+
+    echo "</table>\n";
+    echo "</form>\n";
+  }
+
+  if (count($parameters)) {
+    if ($parameters[0] == "in") {
+      /* /donation/in/area/Romsey+Town/1 */
+      switch ($parameters[1]) {
+        case "area":
+          $area_id = $parameters[3];
+          $_POST['area_id'] = $area_id;
+          $q = new AreaQuery;
+          $area = $q->findOneById($area_id);
+          $city = get_area_city($area);
+          if ($city) $city_id = $city->getId();
+          show_area_donations(0, 10, $parameters[2], $area_id);
+        break;
+
+        case "city":
+          $city_id = $parameters[3];
+          $_POST['city_id'] = $city_id;
+          $q = new CityQuery;
+          $city = $q->findOneById($city_id);
+          show_city_donations(0, 10, $parameters[2], $city_id);
+        break;
+      }
+    }
+    else if ($parameters[0] == "from") {
+      /* /donation/from/contact/Iain+Patterson/4 */
+      switch ($parameters[1]) {
+        case "contact":
+          $contact_id = $parameters[3];
+          $q = new ContactQuery;
+          $contact = $q->findOneById($contact_id);
+          show_contact_donations(0, 10, $parameters[2], $contact_id);
+        break;
+      }
+    }
+    else if ($parameters[0] == "to") {
+      /* /donation/to/hub/Cambridge+Community+Church/1 */
+      switch ($parameters[1]) {
+        case "hub":
+          $hub_id = $parameters[3];
+          $q = new HubQuery;
+          $hub = $q->findOneById($hub_id);
+          show_hub_donations(0, 10, $parameters[2], $hub_id);
+        break;
+      }
+    }
+  }
+  list($ignored, $id, $args) = parse_parameters($parameters);
+  //echo "<p>$name($id) " . print_r($args, true) . "</p>\n";
+  if (count($args)) {
+    switch ($args[0]) {
+      case "delete":
+        delete_donation($id);
+      break;
+    }
+  }
+  else if (isset($id)) show_donation($id);
+  else {
+    /* XXX: Shown after adding. */
+    show_donation_forms($city_id);
+    show_add_new_donation_form($city_id);
+  }
+
+  if (count($parameters)) {
+    show_donation_forms($city_id);
+  }
+
+
+?>
diff --git a/lib/footer.php b/lib/footer.php
new file mode 100644 (file)
index 0000000..9943ff0
--- /dev/null
@@ -0,0 +1,3 @@
+</div>
+</body>
+</html>
diff --git a/lib/forms.php b/lib/forms.php
new file mode 100644 (file)
index 0000000..af51c1c
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+
+  function input($name, $value = null, $type = null) {
+    echo "<input name=\"$name\"";
+    if (isset($type)) echo " type=\"$type\"";
+    if (isset($value)) echo " value=\"$value\"";
+    else echo " value=\"" . $_POST[$name] . "\"";
+    echo ">";
+  }
+
+  function hidden($name, $value = null) {
+    return input($name, $value, "hidden");
+  }
+
+  function submit($name, $value = null) {
+    return input($name, $value, "submit");
+  }
+
+  function textarea($name, $value = null) {
+    echo "<textarea name=\"$name\">";
+    if (isset($value)) echo $value;
+    else echo $_POST[$name];
+    echo "</textarea>";
+  }
+
+  function option($select, $value, $text, $selected = null) {
+    echo "  <option value=\"$value\"";
+    if (! isset($selected)) $selected = $_POST[$select];
+    if ($value == $selected) echo " selected";
+    echo ">$text\n";
+  }
+
+?>
diff --git a/lib/functions.php b/lib/functions.php
new file mode 100644 (file)
index 0000000..b019e93
--- /dev/null
@@ -0,0 +1,378 @@
+<?php
+
+  function parse_parameters($parameters) {
+    $name = null;
+    $id = null;
+    $args = array();
+
+    if (count($parameters) > 0) {
+      $name = array_shift($parameters);
+
+      /* Recall that we shifted. */
+      if (count($parameters) > 0) {
+        if (is_numeric($parameters[0])) {
+          $id = array_shift($parameters);
+        }
+      }
+
+      $args = $parameters;
+    }
+
+    return array($name, $id, $args);
+  }
+
+  function get_city_by_name($name, $postcode_area = null, $verbose = true) {
+    $q = new CityQuery;
+
+    $m = $q->filterByName(urldecode($name));
+    if (isset($postcode_area)) {
+      $m->filterByPostcodeArea($postcode_area);
+    }
+    $cities = $m->find();
+
+    switch ($m->count()) {
+      case 0:
+        if ($verbose) echo "<p>No such city!</p>\n";
+        return null;
+
+      case 1:
+        return $cities[0];
+
+      default:
+        if ($verbose) echo "<p>Can't identify city uniquely.</p>!\n";
+        return null;
+    }
+  }
+
+  function get_city_by_id($id, $verbose = true) {
+    $q = new CityQuery;
+    $city = $q->findOneById($id);
+
+    if (! $q->count()) {
+      if ($verbose) echo "<p>No such city!</p>\n";
+      return null;
+    }
+
+    return $city;
+  }
+
+  function get_area_by_name($name, $verbose = true) {
+    $q = new AreaQuery;
+    $areas = $q->filterByName(urldecode($name))->find();
+
+    switch ($q->count()) {
+      case 0:
+        if ($verbose) echo "<p>No such area!</p>\n";
+        return null;
+
+      case 1:
+        return $areas[0];
+
+      default:
+        if ($verbose) echo "<p>Can't identify area uniquely.</p>!\n";
+        return null;
+    }
+  }
+
+  function get_area_by_id($id, $verbose = true) {
+    $q = new AreaQuery;
+    $area = $q->findOneById($id);
+
+    if (! $q->count()) {
+      if ($verbose) echo "<p>No such area!</p>\n";
+      return null;
+    }
+
+    return $area;
+  }
+
+  function get_area_city($area) {
+    $q = new CityQuery;
+    return $q->findOneById($area->getCityId());
+  }
+
+  function get_contact_by_name($name, $verbose = true) {
+    $q = new ContactQuery;
+    $contact = $q->filterByDisplayname(urldecode($name))->find();
+
+    switch ($q->count()) {
+      case 0:
+        if ($verbose) echo "<p>No such contact!</p>\n";
+        return null;
+
+      case 1:
+        return $contacts[0];
+
+      default:
+        if ($verbose) echo "<p>Can't identify contact uniquely.</p>!\n";
+        return null;
+    }
+  }
+
+  function get_contact_by_id($id, $verbose = true) {
+    $q = new ContactQuery;
+    $contact = $q->findOneById($id);
+
+    if (! $q->count()) {
+      if ($verbose) echo "<p>No such contact!</p>\n";
+      return null;
+    }
+
+    return $contact;
+  }
+
+  function get_hub_by_name($name, $verbose = true) {
+    $q = new HubQuery;
+    $hubs = $q->filterByDisplayname(urldecode($name))->find();
+
+    switch ($q->count()) {
+      case 0:
+        if ($verbose) echo "<p>No such hub!</p>\n";
+        return null;
+
+      case 1:
+        return $hubs[0];
+
+      default:
+        if ($verbose) echo "<p>Can't identify hub uniquely.</p>!\n";
+        return null;
+    }
+  }
+
+  function get_hub_by_id($id, $verbose = true) {
+    $q = new HubQuery;
+    $hub = $q->findOneById($id);
+
+    if (! $q->count()) {
+      if ($verbose) echo "<p>No such hub!</p>\n";
+      return null;
+    }
+
+    return $hub;
+  }
+
+  function get_donation_by_id($id, $verbose = true) {
+    $q = new DonationQuery;
+    $donation = $q->findOneById($id);
+
+    if (! $q->count()) {
+      if ($verbose) echo "<p>No such donation!</p>\n";
+      return null;
+    }
+
+    return $donation;
+  }
+
+  function get_order_by_id($id, $verbose = true) {
+    $q = new OrderQuery;
+    $order = $q->findOneById($id);
+
+    if (! $q->count()) {
+      if ($verbose) echo "<p>No such order!</p>\n";
+      return null;
+    }
+
+    return $order;
+  }
+
+  function get_user_by_contact_id($id, $verbose = true) {
+    $q = new UserQuery;
+    $user = $q->findOneByContactId($id);
+
+    if (! $q->count()) {
+      if ($verbose) echo "<p>No such user!</p>\n";
+      return null;
+    }
+
+    return $user;
+  }
+
+  function get_city_displayname($city) {
+    return $city->getName() . ", " . $city->getPostcodeArea();
+  }
+
+  function get_area_displayname($area) {
+    return $area->getName() . " in " . get_city_displayname(CityQuery::create()->findOneById($area->getCityId()));
+  }
+
+  function get_donation_displayname($donation) {
+    return $donation->getQuantity() . "kg on " . $donation->getDate();
+  }
+
+  function get_order_displayname($order) {
+    return $order->getQuantity() . "kg on " . $order->getDate();
+  }
+
+  function get_address_area($address) {
+    $q = new AreaQuery;
+    return $q->findOneById($address->getAreaId());
+  }
+
+  function get_contact_address($contact) {
+    $q = new AddressQuery;
+    return $q->findOneById($contact->getAddressId());
+  }
+
+  function get_contact_area($contact) {
+    $address = get_contact_address($contact);
+    if (! $address) return null;
+
+    return get_address_area($address);
+  }
+
+  function get_contact_city($contact) {
+    $area = get_contact_area($contact);
+    if (! $area) return null;
+
+    return get_area_city($area);
+  }
+
+  /* Hub and Contact are similar enough that this can work. */
+  function get_hub_address($hub) {
+    return get_contact_address($hub);
+  }
+
+  /* Hub and Contact are similar enough that this can work. */
+  function get_hub_area($hub) {
+    return get_contact_area($hub);
+  }
+
+  /* Hub and Contact are similar enough that this can work. */
+  function get_hub_city($hub) {
+    return get_contact_city($hub);
+  }
+
+  function get_area_contacts($area_id = null, $role = null) {
+    $q = new ContactQuery;
+    if (isset($area_id)) $q->useAddressQuery()->filterByAreaId($area_id)->endUse();
+    if (isset($role)) $q->where("role & $role");
+    return $q->orderByDisplayname()->find();
+  }
+
+  function get_area_requesters($area_id = null) {
+    return get_area_contacts($area_id, $GLOBALS['ROLE_REQUESTER']);
+  }
+
+  function get_area_beneficiaries($area_id = null) {
+    return get_area_contacts($area_id, $GLOBALS['ROLE_BENEFICIARY']);
+  }
+
+  function get_area_donors($area_id = null) {
+    return get_area_contacts($area_id, $GLOBALS['ROLE_DONOR']);
+  }
+
+  function get_city_contacts($city_id = null, $role = null) {
+    /* XXX */
+    $area_ids = array();
+    $areas = get_city_areas($city_id);
+    foreach ($areas as $area) $area_ids[] = $area->getId();
+    return get_area_contacts($area_ids, $role);
+  }
+
+  function get_city_requesters($city_id = null) {
+    return get_city_contacts($city_id, $GLOBALS['ROLE_REQUESTER']);
+  }
+
+  function get_city_beneficiaries($city_id = null) {
+    return get_city_contacts($city_id, $GLOBALS['ROLE_BENEFICIARY']);
+  }
+
+  function get_city_donors($city_id = null) {
+    return get_city_contacts($city_id, $GLOBALS['ROLE_DONOR']);
+  }
+
+  function get_city_drivers($city_id = null) {
+    return get_city_contacts($city_id, $GLOBALS['ROLE_DRIVER']);
+  }
+
+  function get_contact_role_string($contact) {
+    global $roles;
+
+    $role = $contact->getRole();
+
+    $selected = array();
+
+    for ($i =0; $i < count($roles); $i++) {
+      if ($role & (1 << $i)) $selected[] = $roles[$i];
+    }
+
+    return implode(", ", $selected);
+  }
+
+  function get_area_hubs($area_id = null) {
+    $q = new HubQuery;
+    if (isset($area_id)) $q->useAddressQuery()->filterByAreaId($area_id)->endUse();
+    return $q->orderByDisplayname()->find();
+  }
+
+  function get_city_areas($city_id = null) {
+    $q = new AreaQuery;
+    $q->join("City")->orderBy("City.Name");
+    if (isset($city_id)) $q->filterByCityId($city_id);
+    return $q->orderByName()->find();
+  }
+
+  function get_city_areas_with_contacts($city_id = null, $role = null) {
+    $q = new AreaQuery;
+    $q->join("City")->orderBy("City.Name");
+    if (isset($city_id)) $q->filterByCityId($city_id);
+    /* XXX */
+    if (isset($role)) $q->useAddressQuery()->join("Contact")->useContactQuery()->where("role & $role")->endUse()->endUse();
+    else $q->useAddressQuery()->join("Contact")->endUse();
+    return $q->orderByName()->distinct()->find();
+  }
+
+  function get_city_areas_with_hubs($city_id = null) {
+    $q = new AreaQuery;
+    $q->join("City")->orderBy("City.Name");
+    if (isset($city_id)) $q->filterByCityId($city_id);
+    $q->useAddressQuery()->join("Hub")->endUse();
+    return $q->orderByName()->distinct()->find();
+  }
+
+  function get_city_hubs($city_id = null) {
+    $q = new HubQuery;
+    if (isset($city_id)) $q->useAddressQuery()->useAreaQuery()->filterByCityId($city_id)->endUse()->endUse();
+    return $q->orderByDisplayname()->find();
+  }
+
+  function iso8601_to_ymd($iso8601) {
+    return split("-", $iso8601);
+  }
+
+  function ymd_to_iso8601($name) {
+    $y = $_POST[$name . "_y"];
+    if (! $y) return null;
+    $m = $_POST[$name . "_m"];
+    if (! $m) $m = 1;
+    $d = $_POST[$name . "_d"];
+    if (! $d) $d = 1;
+    return sprintf("%04d-%02d-%02d", $y, $m, $d);
+  }
+
+  function show_date_form($name, $date = null) {
+    echo "<select name=\"$name\">\n";
+    $now = time();
+    if (isset($date)) {
+      list($y, $m, $d) = explode('-', $date);
+      $then = mktime(0, 0, 0, $m, $d, $y);
+      option($name, $date, date('l j F Y', $then), $date);
+    }
+    for ($i = 0; $i < 60; $i++) {
+      $then = $now + 86400 * $i;
+      option($name, date('Y-m-d', $then), date('l j F Y', $then), $date);
+    }
+    echo "</select>\n";
+    return;
+    if (! isset($date)) $date = date('Y-m-d');
+    list($y, $m, $d) = iso8601_to_ymd($date);
+
+    echo "Year: <input name=\"$name" . "_y\" value=\"$y\" size=4 maxlen=4> ";
+    echo "Month: <input name=\"$name" . "_m\" value=\"$m\" size=2 maxlen=2> ";
+    echo "Day: <input name=\"$name" . "_d\" value=\"$d\" size=2 maxlen=2> ";
+  }
+
+  include_once("$lib_root/admin.php");
+  include_once("$lib_root/forms.php");
+
+?>
diff --git a/lib/header.php b/lib/header.php
new file mode 100644 (file)
index 0000000..8f9624a
--- /dev/null
@@ -0,0 +1,24 @@
+<head>
+<link rel="stylesheet" type="text/css" href="/style.css">
+<title><?php echo "$charity"; if ($module) echo " - $module"; ?></title>
+</head>
+<body>
+
+<p id="header">
+<strong> <?php if ($username) echo "<em>$username</em>@"; echo "$charity"; ?></strong>
+<a href="/city">Cities</a>
+/
+<a href="/area">Areas</a>
+/
+<a href="/contact">Contacts</a>
+/
+<a href="/order">Orders</a>
+/
+<a href="/delivery">Deliveries</a>
+/
+<a href="/hub">Hubs</a>
+/
+<a href="/donation">Donations</a>
+</p>
+
+<div id="main">
diff --git a/lib/hub.php b/lib/hub.php
new file mode 100644 (file)
index 0000000..48b45ae
--- /dev/null
@@ -0,0 +1,403 @@
+<?php
+
+  if (isset($_POST['show_add_hub'])) {
+    $city_id = $_POST['city_id'];
+    show_new_hub_form($city_id);
+  }
+  else if (isset($_POST['add_hub'])) {
+    $id = add_hub($displayname);
+    if ($id !== false) {
+      echo "<p>Added hub.</p>\n";
+      $parameters = array($displayname, $id);
+    }
+  }
+  else if (isset($_POST['update_hub'])) {
+    list($name, $id, $args) = parse_parameters($parameters);
+    $q = new HubQuery;
+    $hub = $q->findOneById($id);
+    if ($hub) {
+      $area = get_hub_area($hub);
+      if ($area) $area_id = $area->getId();
+      if (update_hub($hub, $area_id) !== false) {
+        echo "<p>Updated hub.</p>\n";
+        $parameters = array($hub->getDisplayname(), $hub->getId());
+      }
+    }
+    else {
+      echo "<p>No such hub!</p>\n";
+    }
+  }
+  else if ($_POST['area_id']) {
+    $q = new AreaQuery;
+    $area = $q->findOneById($_POST['area_id']);
+    header(sprintf("Location: http%s://%s/%s/in/area/%s/%d", ($_SERVER['HTTPS']) ? "s" : "", $_SERVER['HTTP_HOST'], $module, urlencode($area->getName()), $_POST['area_id']));
+    exit;
+  }
+  else if ($_POST['city_id']) {
+    $q = new CityQuery;
+    $city = $q->findOneById($_POST['city_id']);
+    header(sprintf("Location: http%s://%s/%s/in/city/%s/%d", ($_SERVER['HTTPS']) ? "s" : "", $_SERVER['HTTP_HOST'], $module, urlencode($city->getName()), $_POST['city_id']));
+    exit;
+  }
+
+  function show_hubs($offset, $per_page, $address_ids) {
+    $q = new HubQuery;
+    $hubs = $q->filterByAddressId($address_ids)->find();
+    if (count($hubs)) {
+      foreach ($hubs as $hub) {
+        echo "<br>\nhub " . $hub->getLink();
+        if (check_admin(1)) {
+          echo " " . $hub->getDeleteLink();
+        }
+        $area = get_hub_area($hub);
+        echo " in " . $area->getLink();
+      }
+    }
+    else echo " none";
+  }
+
+  function show_city_hubs($offset, $per_page, $city_name, $city_id = null) {
+    if (isset($city_id)) $city = get_city_by_id($city_id);
+    else if ($city_name) $city = get_city_by_name($city_name);
+    if ($city) {
+      $q = new AreaQuery;
+      $areas = $q->filterByCityId($city->getId())->find();
+      $area_ids = array();
+      foreach ($areas as $area) $area_ids[] = $area->getId();
+
+      $q = new AddressQuery;
+      $addresses = $q->filterByAreaId($area_ids)->find();
+      $address_ids = array();
+      foreach ($addresses as $address) $address_ids[] = $address->getId();
+
+      echo "<p>hubs in city " . $city->getLink(get_city_displayname($city)) . ":";
+      return show_hubs($offset, $per_page, $address_ids);
+    }
+    else echo "<p>No such city!</p>\n";
+  }
+
+  function show_area_hubs($offset, $per_page, $area_name, $area_id = null) {
+    if (isset($area_id)) $area = get_area_by_id($area_id);
+    else if ($area_name) $area = get_area_by_name($area_name);
+    if ($area) {
+      $q = new AddressQuery;
+      $addresses = $q->filterByAreaId($area->getId())->find();
+      $address_ids = array();
+      foreach ($addresses as $address) $address_ids[] = $address->getId();
+
+      echo "<p>Hubs in area " . $area->getLink() . ":";
+      return show_hubs($offset, $per_page, $address_ids);
+    }
+    else echo "<p>No such area!</p>\n";
+  }
+
+  function show_hub_areas_form($city_id = null) {
+    $areas = get_city_areas($city_id);
+    if (! count($areas)) {
+      echo "<p>No <a href=\"/area\">areas</a>!</p>\n";
+      return;
+    }
+
+    echo "<form method=\"POST\" action=\"" . $_SERVER['REQUEST_URI'] . "\">\n";
+    echo "<p>Show hubs in area\n";
+    echo "<select name=\"area_id\">\n";
+    foreach ($areas as $area) {
+      option("area_id", $area->getId(), get_area_displayname($area));
+    }
+    echo "</select>\n";
+    echo "<input type=\"submit\" value=\"Show\">\n";
+    echo "</form>\n";
+  }
+
+  function show_hub_cities_form($city_id = null) {
+    $q = new CityQuery;
+    $cities = $q->orderByName()->find();
+
+    if (! count($cities)) {
+      echo "<p>No <a href=\"/city\">cities</a>!</p>\n";
+      return;
+    }
+
+    echo "<form method=\"POST\" action=\"" . $_SERVER['REQUEST_URI'] . "\">\n";
+    echo "<p>Show hubs in city\n";
+    echo "<select name=\"city_id\">\n";
+    foreach ($cities as $city) {
+      option("city_id", $city->getId(), get_city_displayname($city), $city_id);
+    }
+    echo "</select>\n";
+    echo "<input type=\"submit\" value=\"Show\">\n";
+    echo "</form>\n";
+  }
+
+  function show_hub_forms($city_id) {
+    show_hub_areas_form($city_id);
+    show_hub_cities_form($city_id);
+  }
+
+  function show_hub_form($hub = null, $area_id = null) {
+    if (! $hub) $hub = new Hub;
+
+    /* Display name. */
+    echo "<tr>\n";
+    echo "  <td>Hub name</td>\n";
+    echo "  <td>"; input("displayname", $hub->getDisplayname()); echo "</td>\n";
+    echo "</tr>\n";
+
+    /* Address. */
+    $address = get_hub_address($hub);
+    if (! $address) $address = new Address;
+    echo "<tr>\n";
+    echo "  <td>Address</td>\n";
+    echo "  <td>"; textarea("address", $address->getLine()); echo "</td>\n";
+    echo "</tr>\n";
+
+    /* Postcode. */
+    echo "<tr>\n";
+    echo "  <td>Postcode</td>\n";
+    echo "  <td>"; input("postcode", $address->getPostcode()); echo "</td>\n";
+    echo "</tr>\n";
+
+    /* Telephone. */
+    echo "<tr>\n";
+    echo "  <td>Telephone</td>\n";
+    echo "  <td>"; input("telephone1", $hub->getTelephone1()); echo "</td>\n";
+    echo "</tr>\n";
+    echo "<tr>\n";
+    echo "  <td>Alternative telephone</td>\n";
+    echo "  <td>"; input("telephone2", $hub->getTelephone2()); echo "</td>\n";
+    echo "</tr>\n";
+
+    /* Email. */
+    echo "<tr>\n";
+    echo "  <td>Email</td>\n";
+    echo "  <td>"; input("email", $hub->getEmail()); echo "</td>\n";
+    echo "</tr>\n";
+
+    /* Area. */
+    $areas = get_city_areas();
+    if (! isset($area_id)) $area_id = get_hub_area($hub);
+    echo "<tr>\n";
+    echo "  <td>Area</td>\n";
+    echo "  <td><select name=\"area_id\">\n";
+    foreach ($areas as $area) {
+      option("area_id", $area->getId(), get_area_displayname($area), $area_id);
+    }
+    echo "  </select></td>\n";
+    echo "</tr>\n";
+  }
+
+  function show_new_hub_form($city_id = null) {
+    if (! check_admin(1)) return;
+
+    $areas = get_city_areas($city_id);
+    if (! count($areas)) {
+      echo "<p>No <a href=\"/area\">areas</a>!</p>\n";
+      return;
+    }
+
+    echo "<form method=\"POST\" action=\"" . $_SERVER['REQUEST_URI'] . "\">\n";
+    echo "<p>Add a new hub:</p>\n";
+
+    echo "<table>\n";
+    show_hub_form($hub);
+
+    echo "<tr>\n";
+    echo "  <td colspan=2>"; submit("add_hub", "Add"); echo "</td></tr>\n";
+    echo "</tr>\n";
+    echo "</table>\n";
+    echo "</form>\n";
+  }
+
+  function show_add_new_hub_form() {
+    if (! check_admin(1)) return;
+
+    $q = new CityQuery;
+    $cities = $q->find();
+    if (! count($cities)) {
+      echo "<p>No <a href=\"/city\">cities</a>!</p>\n";
+      return;
+    }
+
+    echo "<form method=\"POST\" action=\"" . $_SERVER['REQUEST_URI'] . "\">\n";
+    echo "<p>Add a new hub in <select name=\"city_id\">\n";
+    foreach ($cities as $city) {
+      option("city_id", $city->getId(), get_city_displayname($city));
+    }
+    echo "</select>";
+    submit("show_add_hub", "Proceed");
+    echo "</p>\n";
+    echo "</form>\n";
+  }
+
+  function update_hub(&$hub, $area_id, $new = false) {
+    $displayname = $_POST['displayname'];
+
+    if (! $displayname) {
+      echo "<p>Must have a name!</p>\n";
+      return false;
+    }
+
+    /* Get address. */
+    $line = $_POST['address'];
+    $postcode = $_POST['postcode'];
+    $q = new AddressQuery;
+    /* XXX: Finding by area properly? */
+    $address = $q->filterByAreaId($area_id)->filterByLine($line)->filterByPostcode($postcode)->findOneOrCreate();
+    if ($address->isNew()) {
+      /* Changing address. */
+      //if (! $new)
+      /*
+        XXX: Check for other hubs at the old address.
+        Make this a new address if there are others, but
+        provide a link to update other hubs.
+      */
+      try {
+        $address->save();
+      }
+      catch (Exception $e) {
+        echo "<p>Error adding $line.</p>\n";
+        return false;
+      }
+    }
+
+    $telephone1 = $_POST['telephone1'];
+    $telephone2 = $_POST['telephone2'];
+    $email = $_POST['email'];
+
+    $hub->setDisplayname($displayname);
+    $hub->setTelephone1($telephone1);
+    $hub->setTelephone2($telephone2);
+    $hub->setEmail($email);
+    $hub->setAddressId($address->getId());
+
+    try {
+      $hub->save();
+    }
+    catch (Exception $e) {
+      if ($new) echo "<p>Error adding $displayname.</p>\n";
+      else echo "<p>Error updating $displayname.</p>\n";
+      return false;
+    }
+
+    return true;
+  }
+
+  function add_hub(&$name) {
+    if (! check_admin(1, "add a hub")) return;
+
+    $area_id = $_POST['area_id'];
+    if (! is_numeric($area_id)) {
+      echo "<p>Invalid area!</p>\n";
+      return false;
+    }
+
+    $area = get_area_by_id($area_id);
+    if (! $area) {
+      echo "<p>No such area!</p>\n";
+      return false;
+    }
+
+    $hub = new Hub;
+    if (! update_hub($hub, $area_id, true)) return false;
+    return $hub->getId();
+  }
+
+  function delete_hub($name, $id = null, &$city_id = null) {
+    if (! check_admin(1, "delete a hub")) return;
+
+    if (isset($id)) $hub = get_hub_by_id($id);
+    else $hub = get_hub_by_name($name);
+    if (! $hub) return false;
+
+    ///* Remember city ID for dropdown. */
+    //$city_id = $area->getCityId();
+
+    try {
+      $hub->delete();
+      echo "<p>Deleted hub.</p>\n";
+    }
+    catch (Exception $e) {
+      echo "<p>Error deleting $name!</p>\n";
+      return false;
+    }
+
+    return true;
+  }
+
+  function show_hub($name, &$id = null) {
+    if (isset($id)) $hub = get_hub_by_id($id);
+    else $hub = get_hub_by_name($name);
+    if (! $hub) return;
+
+    echo "<form method=\"POST\" action=\"" . $_SERVER['REQUEST_URI'] . "\">\n";
+    echo "<p>Hub: <span class=\"strong\">" . $hub->getDisplayname() . "</span>";
+    if (check_admin(1)) {
+      echo " " . $hub->getDeleteLink();
+    }
+    $city = get_hub_city($hub);
+    if ($city) echo " in " . $city->getLink(get_city_displayname($city));
+    echo ": ";
+    echo "\n</p>";
+
+    echo "<table>\n";
+    show_hub_form($hub);
+
+    if (check_admin(1)) {
+      echo "<tr>\n";
+      echo "  <td colspan=2>";
+      submit("update_hub", "Update");
+      echo "</td>\n";
+      echo "</tr>\n";
+    }
+
+    echo "</table>\n";
+    echo "</form>\n";
+  }
+
+  /* /hub/in/area/Cambridge/1 */
+  if (count($parameters)) {
+    if ($parameters[0] == "in") {
+      switch ($parameters[1]) {
+        case "area":
+          $area_id = $parameters[3];
+          $_POST['area_id'] = $area_id;
+          $q = new AreaQuery;
+          $area = $q->findOneById($area_id);
+          $city = get_area_city($area);
+          if ($city) $city_id = $city->getId();
+          show_area_hubs(0, 10, $parameters[2], $area_id);
+        break;
+
+        case "city":
+          $city_id = $parameters[3];
+          $_POST['city_id'] = $city_id;
+          $q = new CityQuery;
+          $city = $q->findOneById($city_id);
+          show_city_hubs(0, 10, $parameters[2], $city_id);
+        break;
+      }
+
+      show_new_hub_form($city_id);
+    }
+  }
+  list($name, $id, $args) = parse_parameters($parameters);
+  //echo "<p>$name($id) " . print_r($args, true) . "</p>\n";
+  if (count($args)) {
+    switch ($args[0]) {
+      case "delete":
+        delete_hub($name, $id);
+      break;
+    }
+  }
+  else if (isset($name)) show_hub($name, $id);
+  else {
+    /* XXX: Shown after adding. */
+    show_hub_forms($city_id);
+    show_add_new_hub_form($city_id);
+  }
+
+  if (count($parameters)) {
+    show_hub_forms($city_id);
+  }
+
+?>
diff --git a/lib/menu.php b/lib/menu.php
new file mode 100644 (file)
index 0000000..d120a9f
--- /dev/null
@@ -0,0 +1,39 @@
+<head>
+<link rel="stylesheet" type="text/css" href="/style.css">
+</head>
+<body>
+<?php
+
+if (false) {
+  /* Propel 1.3 syntax. */
+  #$area = AreaPeer::retrieveByPK(1);
+
+  $q = new AreaQuery;
+  $area = $q->findPK(1);
+  $qq = new CityQuery;
+  $city = $qq->findPK($area->getCityId());
+
+  printf("%s in %s\n", $area->getName(), $city->getName());
+}
+
+  function button($text, $link) {
+    $safe = htmlspecialchars($text);
+    echo "<span class=\"button\"><a href=\"$link\">$safe</a></span>\n";
+  }
+
+  echo "<p>\n";
+  button("Orders", "/order");
+  button("Donations", "/donation");
+  echo "</p>\n";
+
+  echo "<p>\n";
+  button("Contacts", "/contact");
+  button("Hubs", "/hub");
+  button("Areas", "/area");
+  echo "</p>\n";
+
+  echo "<p>\n";
+  button("Cities", "/city");
+  echo "</p>\n";
+?>
+</body>
diff --git a/lib/order.php b/lib/order.php
new file mode 100644 (file)
index 0000000..8b1bbcd
--- /dev/null
@@ -0,0 +1,600 @@
+<?php
+
+  if (isset($_POST['show_add_order'])) {
+    $area_id = $_POST['area_id'];
+    show_new_order_form($area_id);
+  }
+  else if (isset($_POST['add_order'])) {
+    $id = add_order();
+    if ($id !== false) {
+      echo "<p>Order placed.</p>\n";
+      $parameters = array("id", $id);
+    }
+  }
+  else if (isset($_POST['update_order'])) {
+    list($ignored, $id, $args) = parse_parameters($parameters);
+    $q = new OrderQuery;
+    $order = $q->findOneById($id);
+    if ($order) {
+      if (update_order($order) !== false) {
+        echo "<p>Updated order.</p>\n";
+        $parameters = array("id", $order->getId());
+      }
+    }
+    else {
+      echo "<p>No such contact!</p>\n";
+    }
+  }
+  else if ($_POST['show_in_area']) {
+    $q = new AreaQuery;
+    $area = $q->findOneById($_POST['area_id']);
+    header(sprintf("Location: http%s://%s/%s/in/area/%s/%d%s", ($_SERVER['HTTPS']) ? "s" : "", $_SERVER['HTTP_HOST'], $module, urlencode($area->getName()), $_POST['area_id'], get_order_state_uri(get_order_state_mask())));
+    exit;
+  }
+  else if ($_POST['show_in_city']) {
+    $q = new CityQuery;
+    $city = $q->findOneById($_POST['city_id']);
+    header(sprintf("Location: http%s://%s/%s/in/city/%s/%d%s", ($_SERVER['HTTPS']) ? "s" : "", $_SERVER['HTTP_HOST'], $module, urlencode($city->getName()), $_POST['city_id'], get_order_state_uri(get_order_state_mask())));
+    exit;
+  }
+
+  function show_orders($offset, $per_page, $requester_ids = null, $beneficiary_ids = null, $state_mask = null) {
+    /* XXX: Use Propel methods. */
+    if (isset($state_mask)) {
+      $order_ids = array();
+      $dbh = Propel::getConnection();
+      $sth = $dbh->prepare("select * from OrderState o where updated=(select max(updated) from OrderState where order_id=o.order_id) and state & $state_mask");
+      $sth->execute();
+      $order_states = OrderStatePeer::populateObjects($sth);
+      foreach ($order_states as $order_state) $order_ids[] = $order_state->getOrderId();
+    }
+    $q = new OrderQuery;
+    if (isset($requester_ids)) $q->filterByRequesterId($requester_ids);
+    if (isset($beneficiary_ids)) $q->filterByBeneficiaryId($beneficiary_ids);
+    # XXX: Doesn't work.
+    #if (isset($state_mask)) $q->useOrderStateQuery()->addSelectQuery($latest_state, 'latestState')->where("order_id=latestState.order_id")->where("state & $state_mask")->endUse();
+    if (isset($state_mask)) $q->filterById($order_ids);
+    $orders = $q->orderByDate()->find();
+    if (count($orders)) {
+      foreach ($orders as $order) {
+        echo "<br>\nOrder " . $order->getStrongLink($order->getId()) . ": " . get_order_displayname($order);
+        if (check_admin(1)) {
+          echo " " . $order->getDeleteLink();
+        }
+
+        /* XXX: Should pull from query. */
+        $q = new ContactQuery;
+        $contact = $q->findOneById($order->getBeneficiaryId());
+        if ($contact) {
+          echo " for " . $contact->getLink();
+          $area = get_contact_area($contact);
+          if ($area) echo " in " . $area->getLink();
+        }
+
+        if ($order->getHubId()) {
+          $q = new HubQuery;
+          $hub = $q->findOneById($order->getHubId());
+          if ($hub) echo " to hub " . $hub->getLink();
+          $area = get_hub_area($hub);
+          if ($area) echo " in " . $area->getLink();
+        }
+      }
+    }
+    else echo " none";
+  }
+
+  function show_city_orders($offset, $per_page, $city_name, $city_id = null, $state_mask = null) {
+    if (isset($city_id)) $city = get_city_by_id($city_id);
+    else if ($city_name) $city = get_city_by_name($city_name);
+    if ($city) {
+      $contacts = get_city_contacts($city->getId(), $GLOBALS['ROLE_BENEFICIARY']);
+      $beneficiary_ids = array();
+      foreach ($contacts as $contact) $beneficiary_ids[] = $contact->getId();
+
+      echo "<p>Orders in city " . $city->getLink(get_city_displayname($city)) . ":";
+      return show_orders($offset, $per_page, null, $beneficiary_ids, $state_mask);
+    }
+    else echo "<p>No such city!</p>\n";
+  }
+
+  function show_requester_orders($offset, $per_page, $contact_name, $contact_id = null, $state_mask = null) {
+    if (isset($contact_id)) $contact = get_contact_by_id($contact_id);
+    else if ($contact_name) $contact = get_contact_by_name($contact_name);
+    if ($contact) {
+      echo "<p>Orders from requester " . $contact->getLink() . ":";
+      return show_orders($offset, $per_page, $contact->getId(), null, $state_mask);
+    }
+    else echo "<p>No such contact!</p>\n";
+  }
+
+  function show_beneficiary_orders($offset, $per_page, $contact_name, $contact_id = null, $state_mask = null) {
+    if (isset($contact_id)) $contact = get_contact_by_id($contact_id);
+    else if ($contact_name) $contact = get_contact_by_name($contact_name);
+    if ($contact) {
+      echo "<p>Orders to beneficiary " . $contact->getLink() . ":";
+      return show_orders($offset, $per_page, null, $contact->getId(), $state_mask);
+    }
+    else echo "<p>No such contact!</p>\n";
+  }
+
+  function show_area_orders($offset, $per_page, $area_name, $area_id = null, $state_mask = null) {
+    if (isset($area_id)) $area = get_area_by_id($area_id);
+    else if ($area_name) $area = get_area_by_name($area_name);
+    if ($area) {
+      $contacts = get_area_contacts($area->getId(), $GLOBALS['ROLE_BENEFICIARY']);
+      $contact_ids = array();
+      foreach ($contacts as $contact) $contact_ids[] = $contact->getId();
+
+      echo "<p>Orders in area " . $area->getLink() . ":";
+      return show_orders($offset, $per_page, null, $contact_ids, $state_mask);
+    }
+    else echo "<p>No such area!</p>\n";
+  }
+
+  function show_order_state_form($state_mask = null) {
+    global $states, $all_states;
+
+    if (is_null($state_mask)) $state_mask = $all_states;
+
+    echo "<p>Restrict to order states:\n";
+    for ($i = 0; $i < count($states); $i++) {
+      echo " <input type=\"checkbox\" name=\"state_$i\"";
+      if ($state_mask & (1 << $i)) echo " checked";
+      echo ">$states[$i]\n";
+    }
+    echo "</p>\n";
+  }
+
+  function get_order_state_mask($string = null) {
+    global $states, $all_states;
+
+    $mask = 0;
+
+    if (isset($string)) {
+      $selected = explode("+", $string);
+      for ($i = 0; $i < count($states); $i++) {
+        if (in_array($states[$i], $selected)) $mask |= (1 << $i);
+      }
+    }
+    else {
+      for ($i = 0; $i < count($states); $i++) {
+        if ($_POST['state_' . $i] == "on") $mask |= (1 << $i);
+      }
+    }
+
+    if (! $mask) $mask = $all_states;
+    return $mask;
+  }
+
+  function get_order_state_string($mask) {
+    global $states;
+
+    $selected = array();
+
+    for ($i = 0; $i < count($states); $i++) {
+      if ($mask & (1 << $i)) $selected[] = $states[$i];
+    }
+
+    return implode("+", $selected);
+  }
+
+  function get_order_state_uri($mask) {
+    global $all_states;
+
+    if (is_null($mask)) return "";
+    if ($mask == $all_states) return "";
+
+    return "/state/" . get_order_state_string($mask);
+  }
+
+  function show_order_areas_form($city_id = null) {
+    $areas = get_city_areas($city_id);
+    if (! count($areas)) {
+      echo "<p>No <a href=\"/area\">areas</a>!</p>\n";
+      return;
+    }
+
+    echo "<p>Show orders in area\n";
+    echo "<select name=\"area_id\">\n";
+    foreach ($areas as $area) {
+      option("area_id", $area->getId(), get_area_displayname($area));
+    }
+    echo "</select>\n";
+    echo "<input type=\"submit\" name=\"show_in_area\" value=\"Show\">\n";
+  }
+
+  function show_order_cities_form($city_id = null) {
+    $q = new CityQuery;
+    $cities = $q->orderByName()->find();
+
+    if (! count($cities)) {
+      echo "<p>No <a href=\"/city\">cities</a>!</p>\n";
+      return;
+    }
+
+    echo "<p>Show orders in city\n";
+    echo "<select name=\"city_id\">\n";
+    foreach ($cities as $city) {
+      option("city_id", $city->getId(), get_city_displayname($city), $city_id);
+    }
+    echo "</select>\n";
+    echo "<input type=\"submit\" name=\"show_in_city\" value=\"Show\">\n";
+  }
+
+  function show_order_forms($city_id, $state_mask) {
+    echo "<form method=\"POST\" action=\"" . $_SERVER['REQUEST_URI'] . "\">\n";
+    show_order_state_form($state_mask);
+    show_order_areas_form($city_id);
+    show_order_cities_form($city_id);
+    echo "</form>\n";
+  }
+
+  function show_order_form($order = null, $area_id = null) {
+    global $states;
+
+    if ($order) {
+      $q = new OrderStateQuery;
+      $order_state = $q->filterByOrderId($order->getId())->orderByUpdated('desc')->limit(1)->findOne();
+      if ($order_state) $state = $order_state->getState();
+    }
+    else $order = new Order;
+
+
+    /* Date. */
+    echo "<tr>\n";
+    echo "  <td>Delivery</td>\n";
+    /* XXX: Find suitable dates from area. */
+    echo "  <td>"; show_date_form("date", $order->getDate()); echo "</td>\n";
+    echo "</tr>\n";
+
+    /* Requester. */
+    echo "<tr>\n";
+    echo "  <td>Requester</td>\n";
+    echo "  <td><select name=\"requester_id\">\n";
+    option("requester_id", null, "");
+    $contacts = get_area_requesters();
+    foreach ($contacts as $contact) {
+      option("requester_id", $contact->getId(), $contact->getDisplayname(), $order->getRequesterId());
+    }
+    echo "</select></td>\n";
+    echo "</tr>\n";
+
+    /* Beneficiary. */
+    echo "<tr>\n";
+    echo "  <td>Beneficiary</td>\n";
+    echo "  <td><select name=\"beneficiary_id\">\n";
+    option("beneficiary_id", null, "");
+    $contacts = get_area_beneficiaries($area_id);
+    foreach ($contacts as $contact) {
+      option("beneficiary_id", $contact->getId(), $contact->getDisplayname(), $order->getBeneficiaryId());
+    }
+    echo "</select></td>\n";
+    echo "</tr>\n";
+
+    /* Hub. */
+    echo "<tr>\n";
+    echo "  <td>Hub</td>\n";
+    echo "  <td><select name=\"hub_id\">\n";
+    option("hub_id", null, "");
+    $hubs = get_area_hubs();
+    foreach ($hubs as $hub) {
+      option("hub_id", $hub->getId(), $hub->getDisplayname(), $order->getHubId());
+    }
+    echo "</select></td>\n";
+    echo "</tr>\n";
+
+    /* Quantity. */
+    echo "<tr>\n";
+    echo "  <td>Quantity (kg)</td>\n";
+    echo "  <td>"; input("quantity", $order->getQuantity()); echo "</td>\n";
+    echo "</tr>\n";
+
+    /* Driver. */
+    echo "<tr>\n";
+    echo "  <td>Driver</td>\n";
+    $contacts = get_city_drivers();
+    if (count($contacts)) {
+      echo "  <td><select name=\"driver_id\">\n";
+      option("driver_id", null, "");
+      foreach ($contacts as $contact) {
+        option("driver_id", $contact->getId(), $contact->getDisplayname(), $driver_id);
+      }
+      echo "</select></td>\n";
+    }
+    else echo "  <td>No drivers!</td>\n";
+    echo "</tr>\n";
+
+    /* State. */
+    if ($order->getId()) {
+      echo "<tr>\n";
+      echo "  <td>State</td>\n";
+      echo "  <td><select name=\"state\">\n";
+      for ($i = 0; $i < count($states); $i++) {
+        option("state", $i << 1, ucfirst($states[$i]), $state);
+      }
+      echo "</select></td>\n";
+      echo "</tr>\n";
+    }
+  }
+
+  function show_new_order_form($area_id = null) {
+    if (! check_admin(1)) return;
+
+    $area = get_area_by_id($area_id);
+    if (! count($area)) {
+      echo "<p>No such <a href=\"/area\">area</a>!</p>\n";
+      return;
+    }
+
+    echo "<form method=\"POST\" action=\"" . $_SERVER['REQUEST_URI'] . "\">\n";
+    echo "<p>Place an order:</p>\n";
+
+    echo "<table>\n";
+    show_order_form(null, $area_id);
+
+    echo "<tr>\n";
+    echo "  <td colspan=2>"; submit("add_order", "Order"); echo "</td></tr>\n";
+    echo "</tr>\n";
+    echo "</table>\n";
+    echo "</form>\n";
+  }
+
+  function show_add_new_order_form() {
+    if (! check_admin(1)) return;
+
+    /* We intentionally hide areas with no contacts. */
+    $areas = get_city_areas_with_contacts(null, $GLOBALS['ROLE_BENEFICIARY']);
+    if (! count($areas)) {
+      echo "<p>Can't place any orders until at least one <a href=\"/area\">area</a> has a <a href=\"/contact\">contact</a>!</p>\n";
+      return;
+    }
+
+    echo "<form method=\"POST\" action=\"" . $_SERVER['REQUEST_URI'] . "\">\n";
+    echo "<p>Place an order in <select name=\"area_id\">\n";
+    foreach ($areas as $area) {
+      option("area_id", $area->getId(), get_area_displayname($area));
+    }
+    echo "</select>";
+    submit("show_add_order", "Proceed");
+    echo "</p>\n";
+    echo "</form>\n";
+  }
+
+  function update_order(&$order, $new = false) {
+    global $user_id;
+
+    #$date = ymd_to_iso8601("date");
+    $date = $_POST['date'];
+    $requester_id = $_POST['requester_id'];
+    $beneficiary_id = $_POST['beneficiary_id'];
+    $hub_id = $_POST['hub_id'];
+    $quantity = $_POST['quantity'];
+    $driver_id = $_POST['driver_id'];
+    $state = $_POST['state'];
+    if (! $state) $state = $GLOBALS['STATE_PLACED'];
+
+    if (! $date) $date = time();
+    /* XXX: check date */
+
+    $requester = get_contact_by_id($requester_id);
+    if (! $requester) {
+      echo "<p>Invalid requester!</p>\n";
+      return false;
+    }
+
+    $beneficiary = get_contact_by_id($beneficiary_id);
+    if (! $beneficiary) {
+      echo "<p>Invalid beneficiary!</p>\n";
+      return false;
+    }
+
+    if ($hub_id) {
+      $hub = get_hub_by_id($hub_id);
+      if (! $hub) {
+        echo "<p>Invalid hub!</p>\n";
+        return false;
+      }
+    }
+
+    if (! is_numeric($quantity)) {
+      echo "<p>Invalid quantity!</p>\n";
+      return false;
+    }
+
+    $order->setDate($date);
+    $order->setRequesterId($requester_id);
+    $order->setBeneficiaryId($beneficiary_id);
+    $order->setHubId($hub_id);
+    $order->setQuantity($quantity);
+
+    try {
+      $order->save();
+
+      $order_state = new OrderState;
+      $order_state->setUpdated(time());
+      $order_state->setOrderId($order->getId());
+      $order_state->setUserId($user_id);
+      $order_state->setDriverId($driver_id);
+      $order_state->setState($state);
+
+      $order_state->save();
+    }
+    catch (Exception $e) {
+      if ($new) echo "<p>Error placing order.</p>\n";
+      else echo "<p>Error updating order.</p>\n";
+      return false;
+    }
+
+    return true;
+  }
+
+  function add_order() {
+    if (! check_admin(1, "place an order")) return;
+
+    $order = new Order;
+    if (! update_order($order, true)) return false;
+    return $order->getId();
+  }
+
+  function delete_order($id = null) {
+    if (! check_admin(1, "delete an order")) return;
+
+    if (isset($id)) $order = get_order_by_id($id);
+    if (! $order) return false;
+
+    try {
+      $q = new OrderStateQuery;
+      $order_states = $q->filterByOrderId($id)->find();
+      foreach ($order_states as $order_state) $order_state->delete();
+      $order->delete();
+      echo "<p>Deleted order.</p>\n";
+    }
+    catch (Exception $e) {
+      echo "<p>Error deleting order $id!</p>\n";
+      return false;
+    }
+
+    return true;
+  }
+
+  function show_order_history($id) {
+    global $states;
+
+    $q = new OrderStateQuery();
+    $order_states = $q->filterByOrderId($id)->orderById()->find();
+
+    if (! count($order_states)) return;
+
+    echo "<h3>Order history</h3>\n";
+    foreach ($order_states as $order_state) {
+      $date = $order_state->getUpdated();
+
+      $user = get_contact_by_id($order_state->getUserId());
+      if ($user) $username = $user->getDisplayname();
+      else $username = "unknown user";
+
+      $driver_id = $order_state->getDriverId();
+      if ($driver_id) $driver = get_contact_by_id($driver_id);
+
+      /* XXX */
+      $state = $order_state->getState();
+      for ($i = 0; $i < count($states); $i++) {
+        if ((1 << $i) == $state) {
+          $state = $states[$i];
+          break;
+        }
+      }
+      #$state = $states[$order_state->getState()];
+      echo "<p><strong>$username</strong> changed order to state <strong>$state</strong>";
+      if ($driver) echo " for driver " . $driver->getDisplayname();
+      echo " on $date.</p>\n";
+    }
+  }
+
+  function show_order(&$id = null) {
+    if (isset($id)) $order = get_order_by_id($id);
+    if (! $order) return;
+
+    echo "<form method=\"POST\" action=\"" . $_SERVER['REQUEST_URI'] . "\">\n";
+    echo "<p>Order: <span class=\"strong\">" . $order->getId() . "</span>";
+    if (check_admin(1)) {
+      echo " " . $order->getDeleteLink();
+    }
+    echo ": ";
+    echo "\n</p>";
+
+    echo "<table>\n";
+    show_order_form($order);
+
+    if (check_admin(1)) {
+      echo "<tr>\n";
+      echo "  <td colspan=2>";
+      submit("update_order", "Update");
+      echo "</td>\n";
+      echo "</tr>\n";
+    }
+
+    echo "</table>\n";
+    echo "</form>\n";
+
+    show_order_history($order->getId());
+  }
+
+  $state_mask = null;
+  if (count($parameters)) {
+    for ($i = 1; $i < count($parameters); $i++) {
+      if ($parameters[$i] == "state") {
+        /* /order/state/placed+picked */
+        $state_mask = get_order_state_mask($parameters[$i + 1]);
+      }
+    }
+
+    if ($parameters[0] == "in") {
+      /* /order/in/area/Romsey+Town/1 */
+      switch ($parameters[1]) {
+      case "area":
+        case "area":
+          $area_id = $parameters[3];
+          $_POST['area_id'] = $area_id;
+          $q = new AreaQuery;
+          $area = $q->findOneById($area_id);
+          $city = get_area_city($area);
+          if ($city) $city_id = $city->getId();
+          show_area_orders(0, 10, $parameters[2], $area_id, $state_mask);
+        break;
+
+        case "city":
+          $city_id = $parameters[3];
+          $_POST['city_id'] = $city_id;
+          $q = new CityQuery;
+          $city = $q->findOneById($city_id);
+          show_city_orders(0, 10, $parameters[2], $city_id, $state_mask);
+        break;
+      }
+    }
+    else if ($parameters[0] == "from") {
+      /* /order/from/requester/Iain+Patterson/4 */
+      switch ($parameters[1]) {
+        case "requester":
+          $contact_id = $parameters[3];
+          $q = new ContactQuery;
+          $contact = $q->findOneById($contact_id);
+          show_requester_orders(0, 10, $parameters[2], $contact_id, $state_mask);
+        break;
+      }
+    }
+    else if ($parameters[0] == "to") {
+      /* /order/to/beneficiary/Cambridge+Community+Church/1 */
+      switch ($parameters[1]) {
+        case "beneficiary":
+          $contact_id = $parameters[3];
+          $q = new ContactQuery;
+          $hub = $q->findOneById($contact_id);
+          show_beneficiary_orders(0, 10, $parameters[2], $contact_id, $state_mask);
+        break;
+      }
+    }
+  }
+  list($ignored, $id, $args) = parse_parameters($parameters);
+  //echo "<p>$name($id) " . print_r($args, true) . "</p>\n";
+  if (count($args)) {
+    switch ($args[0]) {
+      case "delete":
+        delete_order($id);
+      break;
+    }
+  }
+  else if (isset($id)) show_order($id);
+  else if ($state_mask) show_orders(0, 10, null, null, $state_mask);
+  else {
+    /* XXX: Shown after adding. */
+    show_order_forms($city_id, $state_mask);
+    show_add_new_order_form($city_id);
+  }
+
+  if (count($parameters)) {
+    show_order_forms($city_id, $state_mask);
+  }
+
+
+?>
diff --git a/propel/build.properties b/propel/build.properties
new file mode 100644 (file)
index 0000000..22a1609
--- /dev/null
@@ -0,0 +1,3 @@
+propel.database = mysql
+propel.project = readifood
+propel.defaultDateFormat = %F
diff --git a/propel/build/classes/ReadifoodObject.php b/propel/build/classes/ReadifoodObject.php
new file mode 100644 (file)
index 0000000..e7f6f2c
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+
+  class ReadifoodObject extends BaseObject {
+    function getURL() {
+      $class = get_class($this->getPeer());
+      return sprintf("/%s/%s/%d", urlencode(strtolower($class::OM_CLASS)), urlencode($this->getName()), $this->getId());
+    }
+
+    function getLink($blurb = null, $url = null, $classes = null) {
+      if (is_null($classes)) $classes = array();
+      else if (! is_array($classes)) $classes = array($classes);
+      return sprintf("<a %shref=\"%s\">%s</a>", (count($classes)) ? " class=\"" . join(" ", $classes) . "\"" : "", (isset($url)) ? $url : $this->getURL(), (isset($blurb)) ? htmlspecialchars($blurb) : htmlspecialchars($this->getName()));
+    }
+
+    function getStrongLink($blurb = null, $url = null) {
+      return $this->getLink($blurb, $url, "strong");
+    }
+
+    function getActionLink($action, $blurb, $classes = null) {
+      if (is_null($classes)) $classes = array();
+      else if (! is_array($classes)) $classes = array($classes);
+      return $this->getLink($blurb, sprintf("%s/%s", $this->getURL(), urlencode($action)), array_unique(array_merge($classes, array("small"))));
+    }
+
+    function getDeleteLink() {
+      return $this->getActionLink("delete", "Delete", "delete");
+    }
+  }
+
+?>
diff --git a/propel/build/classes/readifood/Contact.php b/propel/build/classes/readifood/Contact.php
new file mode 100644 (file)
index 0000000..404c486
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+
+
+
+/**
+ * Skeleton subclass for representing a row from the 'Contact' table.
+ *
+ *
+ *
+ * You should add additional methods to this class to meet the
+ * application requirements.  This class will only be generated as
+ * long as it does not already exist in the output directory.
+ *
+ * @package    propel.generator.readifood
+ */
+class Contact extends BaseContact
+{
+  /* For getURL(). */
+  function getName() {
+    return $this->getDisplayname();
+  }
+}
diff --git a/propel/build/classes/readifood/Donation.php b/propel/build/classes/readifood/Donation.php
new file mode 100644 (file)
index 0000000..249293f
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+
+
+
+/**
+ * Skeleton subclass for representing a row from the 'Donation' table.
+ *
+ *
+ *
+ * You should add additional methods to this class to meet the
+ * application requirements.  This class will only be generated as
+ * long as it does not already exist in the output directory.
+ *
+ * @package    propel.generator.readifood
+ */
+class Donation extends BaseDonation
+{
+  /* For /donation/id/1. */
+  function getName() {
+    return "id";
+  }
+}
diff --git a/propel/build/classes/readifood/Hub.php b/propel/build/classes/readifood/Hub.php
new file mode 100644 (file)
index 0000000..8c92167
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+
+
+
+/**
+ * Skeleton subclass for representing a row from the 'Hub' table.
+ *
+ *
+ *
+ * You should add additional methods to this class to meet the
+ * application requirements.  This class will only be generated as
+ * long as it does not already exist in the output directory.
+ *
+ * @package    propel.generator.readifood
+ */
+class Hub extends BaseHub
+{
+  function getName() {
+    return $this->getDisplayname();
+  }
+}
diff --git a/propel/build/classes/readifood/Order.php b/propel/build/classes/readifood/Order.php
new file mode 100644 (file)
index 0000000..bfb4167
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+
+
+
+/**
+ * Skeleton subclass for representing a row from the 'Order' table.
+ *
+ *
+ *
+ * You should add additional methods to this class to meet the
+ * application requirements.  This class will only be generated as
+ * long as it does not already exist in the output directory.
+ *
+ * @package    propel.generator.readifood
+ */
+class Order extends BaseOrder
+{
+  /* For /order/id/1. */
+  function getName() {
+    return "id";
+  }
+}
diff --git a/propel/schema.xml b/propel/schema.xml
new file mode 100644 (file)
index 0000000..78516d8
--- /dev/null
@@ -0,0 +1,165 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<database name="readifood" defaultIdMethod="native">
+  <!-- City -->
+  <table name="City" phpName="City" baseClass="ReadifoodObject">
+    <vendor type="mysql">
+      <parameter name="Engine" value="InnoDB"/>
+      <parameter name="Charset" value="utf8"/>
+    </vendor>
+    <column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true"/>
+    <column name="name" type="varchar" size="64" required="true"/>
+    <column name="postcode_area" type="varchar" size="4" required="true"/>
+    <unique name="postcode">
+      <unique-column name="name"/>
+      <unique-column name="postcode_area"/>
+    </unique>
+  </table>
+
+  <!-- Area or neighbourhood -->
+  <table name="Area" phpName="Area" baseClass="ReadifoodObject">
+    <vendor type="mysql">
+      <parameter name="Engine" value="InnoDB"/>
+      <parameter name="Charset" value="utf8"/>
+    </vendor>
+    <column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true"/>
+    <column name="city_id" type="integer" required="true"/>
+    <column name="name" type="varchar" size="64" required="true"/>
+    <column name="days" type="integer" required="true"/>
+    <foreign-key foreignTable="City" phpName="City" refPhpName="Area">
+      <reference local="city_id" foreign="id"/>
+    </foreign-key>
+  </table>
+
+  <!-- Address -->
+  <table name="Address" phpName="Address" baseClass="ReadifoodObject">
+    <vendor type="mysql">
+      <parameter name="Engine" value="InnoDB"/>
+      <parameter name="Charset" value="utf8"/>
+    </vendor>
+    <column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true"/>
+    <column name="area_id" type="integer" required="true"/>
+    <column name="line" type="varchar" size="128" required="true"/>
+    <column name="postcode" type="varchar" size="16" required="true"/>
+    <foreign-key foreignTable="Area" phpName="Area" refPhpName="Address">
+      <reference local="area_id" foreign="id"/>
+    </foreign-key>
+  </table>
+
+  <!-- Contact -->
+  <table name="Contact" phpName="Contact" baseClass="ReadifoodObject">
+    <vendor type="mysql">
+      <parameter name="Engine" value="InnoDB"/>
+      <parameter name="Charset" value="utf8"/>
+    </vendor>
+    <column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true"/>
+    <column name="address_id" type="integer" required="true"/>
+    <column name="forename" type="varchar" size="64" required="true"/>
+    <column name="middle" type="varchar" size="128" required="true"/>
+    <column name="surname" type="varchar" size="64" required="true"/>
+    <column name="displayname" type="varchar" size="256" required="true"/>
+    <column name="role" type="integer" required="true"/>
+    <column name="telephone1" type="varchar" size="32" required="true"/>
+    <column name="telephone2" type="varchar" size="32" required="true"/>
+    <column name="email" type="varchar" size="64" required="true"/>
+    <foreign-key foreignTable="Address" phpName="Address" refPhpName="Contact">
+      <reference local="address_id" foreign="id"/>
+    </foreign-key>
+  </table>
+
+  <!-- User -->
+  <table name="User" phpName="User" baseClass="ReadifoodObject">
+    <vendor type="mysql">
+      <parameter name="Engine" value="InnoDB"/>
+      <parameter name="Charset" value="utf8"/>
+    </vendor>
+    <column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true"/>
+    <column name="contact_id" type="integer" required="true"/>
+    <column name="username" type="varchar" size="64" required="true"/>
+    <column name="admin" type="integer" required="true"/>
+    <foreign-key foreignTable="Contact" phpName="Contact" refPhpName="User">
+      <reference local="contact_id" foreign="id"/>
+    </foreign-key>
+  </table>
+
+  <!-- Hub -->
+  <table name="Hub" phpName="Hub" baseClass="ReadifoodObject">
+    <vendor type="mysql">
+      <parameter name="Engine" value="InnoDB"/>
+      <parameter name="Charset" value="utf8"/>
+    </vendor>
+    <column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true"/>
+    <column name="address_id" type="integer" required="true"/>
+    <column name="displayname" type="varchar" size="256" required="true"/>
+    <column name="telephone1" type="varchar" size="32" required="true"/>
+    <column name="telephone2" type="varchar" size="32" required="true"/>
+    <column name="email" type="varchar" size="64" required="true"/>
+    <foreign-key foreignTable="Address" phpName="Address" refPhpName="Hub">
+      <reference local="address_id" foreign="id"/>
+    </foreign-key>
+  </table>
+
+  <!-- Donation -->
+  <table name="Donation" phpName="Donation" baseClass="ReadifoodObject">
+    <vendor type="mysql">
+      <parameter name="Engine" value="InnoDB"/>
+      <parameter name="Charset" value="utf8"/>
+    </vendor>
+    <column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true"/>
+    <column name="date" type="date" required="true"/>
+    <column name="contact_id" type="integer" required="true"/>
+    <column name="hub_id" type="integer" required="true"/>
+    <column name="quantity" type="integer" required="true"/>
+    <foreign-key foreignTable="Contact" phpName="Contact" refPhpName="Donation">
+      <reference local="contact_id" foreign="id"/>
+    </foreign-key>
+    <foreign-key foreignTable="Hub" phpName="Hub" refPhpName="Donation">
+      <reference local="hub_id" foreign="id"/>
+    </foreign-key>
+  </table>
+
+  <!-- Order -->
+  <table name="FoodOrder" phpName="Order" baseClass="ReadifoodObject">
+    <vendor type="mysql">
+      <parameter name="Engine" value="InnoDB"/>
+      <parameter name="Charset" value="utf8"/>
+    </vendor>
+    <column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true"/>
+    <column name="date" type="date" required="true"/>
+    <column name="requester_id" type="integer" required="true"/>
+    <column name="beneficiary_id" type="integer" required="true"/>
+    <column name="hub_id" type="integer" required="false"/>
+    <column name="quantity" type="integer" required="true"/>
+    <foreign-key foreignTable="Contact" phpName="Requester" refPhpName="Requester">
+      <reference local="requester_id" foreign="id"/>
+    </foreign-key>
+    <foreign-key foreignTable="Contact" phpName="Beneficiary" refPhpName="Beneficiary">
+      <reference local="beneficiary_id" foreign="id"/>
+    </foreign-key>
+    <foreign-key foreignTable="Hub" phpName="Hub" refPhpName="Hub">
+      <reference local="hub_id" foreign="id"/>
+    </foreign-key>
+  </table>
+
+  <!-- Order state -->
+  <table name="OrderState" phpName="OrderState" baseClass="ReadifoodObject">
+    <vendor type="mysql">
+      <parameter name="Engine" value="InnoDB"/>
+      <parameter name="Charset" value="utf8"/>
+    </vendor>
+    <column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true"/>
+    <column name="updated" type="timestamp" required="true"/>
+    <column name="order_id" type="integer" required="true"/>
+    <column name="user_id" type="integer" required="true"/>
+    <column name="driver_id" type="integer" required="false"/>
+    <column name="state" type="integer" required="true"/>
+    <foreign-key foreignTable="FoodOrder" phpName="Order" refPhpName="OrderState">
+      <reference local="order_id" foreign="id"/>
+    </foreign-key>
+    <foreign-key foreignTable="User" phpName="User" refPhpName="OrderState">
+      <reference local="user_id" foreign="id"/>
+    </foreign-key>
+    <foreign-key foreignTable="Contact" phpName="Driver" refPhpName="OrderState">
+      <reference local="driver_id" foreign="id"/>
+    </foreign-key>
+  </table>
+</database>
diff --git a/www/.htaccess b/www/.htaccess
new file mode 100644 (file)
index 0000000..cab738c
--- /dev/null
@@ -0,0 +1,4 @@
+RewriteEngine on
+RewriteCond %{REQUEST_FILENAME} !/style\.css$
+RewriteCond %{REQUEST_FILENAME} !/logo\.png$
+RewriteRule ^(.+) index.php [L]
diff --git a/www/index.php b/www/index.php
new file mode 100644 (file)
index 0000000..c75a157
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+
+  /* XXX */
+  date_default_timezone_set("UTC");
+
+  $charity = "Readifood";
+  $propel_project = "readifood";
+  $root = join(DIRECTORY_SEPARATOR, array($_SERVER['DOCUMENT_ROOT'], ".."));
+  $propel_root = join(DIRECTORY_SEPARATOR, array($root, "propel"));
+  $lib_root = join(DIRECTORY_SEPARATOR, array($root, "lib"));
+  require_once "propel/Propel.php";
+
+  Propel::init("$propel_root/build/conf/$propel_project-conf.php");
+  set_include_path(join(PATH_SEPARATOR, array("$propel_root/build/classes", get_include_path())));
+
+  $request = preg_replace('!^/branches/[^/]+/!', "", $_SERVER['REQUEST_URI']);
+  $request = preg_replace('!^/+!', "", $request);
+  $request = preg_replace('!/+$!', "", $request);
+
+  # Extract module and parameters from URI.
+  $module = preg_replace('!/.*!', "", $request);
+  $parameters = split("/", preg_replace('!^[^/]*/!', "", $request));
+  if (count($parameters) == 1 && $parameters[0] == $module) $parameters = null;
+
+  # Sanitise module.
+  if (! preg_match('/^[A-Za-z0-9-_]+$/', $module)) $module = null;
+  else if (! file_exists("$lib_root/$module.php")) $module = null;
+  else $module = strtolower($module);
+  #echo "request: $request; module: $module; params: " . print_r($parameters, true);
+
+  $username = $_SERVER['REMOTE_USER'];
+  include_once("$lib_root/constants.php");
+  include_once("$lib_root/functions.php");
+  include_once("$lib_root/header.php");
+  $q = new UserQuery;
+  $user = $q->findOneByUsername($username);
+  if (! $q->count()) {
+    echo "<h1>Not logged in!</h1>\n";
+    if ($_SERVER['REMOTE_USER']) echo "<p>User <em>" . $_SERVER['REMOTE_USER'] . "</em> needs an entry in the user table.</p>\n";
+  }
+  else {
+    $user_id = $user->getContactId();
+    $admin_level = $user->getAdmin();
+    if ($module) include_once("$lib_root/$module.php");
+    #else include_once("$lib_root/menu.php");
+   }
+  include_once("$lib_root/footer.php");
+?>
diff --git a/www/logo.png b/www/logo.png
new file mode 100644 (file)
index 0000000..5f1e07b
Binary files /dev/null and b/www/logo.png differ
diff --git a/www/style.css b/www/style.css
new file mode 100644 (file)
index 0000000..95728d1
--- /dev/null
@@ -0,0 +1,61 @@
+body {
+  font-family: Helvetica, Arial;
+  margin: 0px;
+}
+
+#header {
+  top: 0px;
+  left: 0px;
+  width: 100%;
+  background-image: url(logo.png);
+  background-color: #007d00;
+  background-repeat: no-repeat;
+  padding: 0.5em;
+  padding-left: 56px;
+  margin: 0px;
+  font-size: 16pt;
+  box-shadow: 0px 4px 8px 2px black;
+}
+
+#header > a {
+  color: white;
+}
+
+#main {
+  margin: 0.5em;
+}
+
+.button {
+  background-color: #2040cc;
+  border-radius: 32px;
+  padding-top: 16px;
+  padding-bottom: 16px;
+  padding-left: 32px;
+  padding-right: 32px;
+  margin: 32px;
+  box-shadow: 4px 4px 8px 2px black;
+}
+
+.button > a {
+  color: white;
+}
+
+a {
+  text-decoration: none;
+}
+
+a:hover {
+  text-decoration: underline;
+}
+
+a.delete {
+  color: darkred;
+}
+
+.strong {
+  font-weight: bold;
+}
+
+.small {
+  font-size: 75%;
+}