railpage/railpagecore

View on GitHub
lib/Locos/Locos.php

Summary

Maintainability
F
1 wk
Test Coverage
<?php

/** 
 * Loco database
 * @since Version 3.2
 * @version 3.10.0
 * @author Michael Greenhill
 * @package Railpage
 */

namespace Railpage\Locos;

define("LOCOS_FIND_CLASS_NOPHOTO", "class_nophoto");
define("LOCOS_FIND_CLASS_NOLOCOS", "class_nolocos");
define("LOCOS_FIND_LOCO_NOPHOTO", "loco_nophoto");
define("LOCOS_FIND_LOCO_NODATES", "loco_nodates"); 
define("LOCOS_FIND_FROM_NUMBERS", "loco_findfromnumbers");

define("RP_LOCO_RENUMBERED", 1); 
define("RP_LOCO_REBUILT", 2);

use Exception;
use DateTime;
use stdClass;
use Railpage\AppCore;
use Railpage\Module;
use Railpage\Debug;
use Zend_Db_Expr;

require_once(__DIR__ . DIRECTORY_SEPARATOR . "functions.php");

/**
 * Base locos class
 * @since Version 3.2
 * @version 3.8.7
 * @todo This will be used by the management class and the loco object class
 */

class Locos extends AppCore {
    
    /**
     * Status : Operational
     * @since Version 3.8.7
     * @const STATUS_OPERATIONAL
     */
    
    const STATUS_OPERATIONAL = 1;
    
    /**
     * Status : Scrapped
     * @since Version 3.8.7
     * @const STATUS_SCRAPPED
     */
    
    const STATUS_SCRAPPED = 2;
    
    /**
     * Status : Stored
     * @since Version 3.8.7
     * @const STATUS_STORED
     */
    
    const STATUS_STORED = 3;
    
    /**
     * Status : Preserved (static)
     * @since Version 3.8.7
     * @const STATUS_PRESERVED_STATIC
     */
    
    const STATUS_PRESERVED_STATIC = 4;
    
    /**
     * Status : Preserved (operational)
     * @since Version 3.8.7
     * @const STATUS_PRESERVED_OPERATIONAL
     */
    
    const STATUS_PRESERVED_OPERATIONAL = 5;
    
    /**
     * Status : Unknown
     * @since Version 3.8.7
     * @const STATUS_UNKNOWN
     */
    
    const STATUS_UNKNOWN = 6;
    
    /**
     * Status : Rebuilt
     * @since Version 3.8.7
     * @const STATUS_REBUILT
     */
    
    const STATUS_REBUILT = 7;
    
    /**
     * Status : Non-rail use
     * @since Version 3.8.7
     * @const STATUS_NONRAIL
     */
    
    const STATUS_NONRAIL = 8;
    
    /**
     * Status : Under restoration
     * @since Version 3.8.7
     * @const STATUS_RESTORATION
     */
    
    const STATUS_RESTORATION = 9;
    
    /**
     * Status : Under overhaul
     * @since Version 3.8.7
     * @const STATUS_OVERHAUL
     */
    
    const STATUS_OVERHAUL = 10;
    
    /**
     * Status : Re-numbered
     * @since Version 3.8.7
     * @const STATUS_RENUMBERED
     */
    
    const STATUS_RENUMBERED = 11;
    
    /**
     * Status : Sold overseas
     * @since Version 3.8.7
     * @const STATUS_OVERSEAS
     */
    
    const STATUS_OVERSEAS = 12;
    
    /**
     * Constructor
     * @since Version 3.8.7
     */
    
    public function __construct() {
        parent::__construct(); 
        
        $this->Module = new Module("locos");
        $this->namespace = $this->Module->namespace;
        
        $this->Template = new stdClass;
        $this->Template->index = "index";
    }
    
    /**
     * Find data for managers to fix
     * @since Version 3.2
     * @version 3.5
     * @param string $search_type
     * @param string $args
     * @return array
     * @deprecated Deprecated since 3.10.1 in favour of Railpage\Locos\Maintainers\Finder::find()
     */
    
    public function find() {
        
        throw new Exception(__METHOD__ . " has been replaced with \\Railpage\\Locos\\Maintainers\\Finder::find()"); 
        
    }
    
    /**
     * List classes
     * @since Version 3.2
     * @version 3.5
     * @return array
     * @param array $types
     */
    
    public function listClasses($types = null) {
        $params = array(); 
        $return = array(); 
        
        $suffix = $types === false ? "all" : (is_array($types) ? implode(",", $types) : $types); 
        $mckey = sprintf("railpage:loco.class.bytype=%s", $suffix);
        
        /**
         * Memcached lookup
         */
        
        /*
        if ($return = $this->Memcached->fetch($mckey)) {
            
            $update = false;
            
            foreach ($return['class'] as $id => $row) {
                if (!isset($row['class_url'])) {
                    $return['class'][$id]['class_url'] = $this->makeClassURL($row['slug']);
                    $update = true;
                }
            }
            
            if ($update) {
                $this->Memcached->save($mckey, $return, strtotime("+1 week"));
            }
            
            return $return;
        }
        */
        
        /**
         * Database lookup
         */
        
        $motive_power_types = ""; 
        
        if (is_array($types)) {
            $motive_power_types = " WHERE c.loco_type_id IN (".implode(",", $types).")";
        } elseif (is_int($types)) {
            $motive_power_types = " WHERE c.loco_type_id IN (?)";
            $params[] = $types;
        }
        
        $query = "SELECT c.parent AS parent_class_id, c.source_id AS source, c.id AS class_id, c.flickr_tag, c.slug, c.flickr_image_id, c.introduced AS class_introduced, c.name AS class_name, c.desc AS class_desc, c.manufacturer_id AS class_manufacturer_id, m.manufacturer_name AS class_manufacturer, w.arrangement AS wheel_arrangement, w.id AS wheel_arrangement_id, t.title AS loco_type, c.loco_type_id AS loco_type_id, t.slug AS loco_type_slug
                    FROM loco_class AS c
                    LEFT JOIN loco_type AS t ON c.loco_type_id = t.id
                    LEFT JOIN wheel_arrangements AS w ON c.wheel_arrangement_id = w.id
                    LEFT JOIN loco_manufacturer AS m ON m.manufacturer_id = c.manufacturer_id
                    " . $motive_power_types . "
                    ORDER BY c.name";
        
        $return['stat'] = "ok";
        $return['count'] = 0;
        
        foreach ($this->db->fetchAll($query, $params) as $row) {
            $row['class_url'] = $this->makeClassUrl($row['slug']);
            $return['class'][$row['class_id']] = $row;
        }
        
        $return['count'] = count($return['class']);
        
        $this->Memcached->save($mckey, $return, strtotime("+1 week"));
        
        return $return;
    }
    
    /**
     * Loco classes by builder
     * @since Version 3.2
     * @version 3.2
     * @return array
     */
    
    public function classByManufacturer() {
        $classes = $this->listClasses();
        
        $return = array(
            "stat" => "err"
        );
        
        if ($classes['stat'] === "ok") {
            $return['stat'] = "ok";
            
            foreach ($classes['class'] as $id => $data) {
                $return['manufacturer'][$data['class_manufacturer_id']][$id] = $data;
            }
            
            ksort($return['manufacturer']);
        }
        
        return $return;
    }
    
    /**
     * Loco classes by builder
     * @since Version 3.2
     * @version 3.2
     * @return array
     */
    
    public function classByWheelset() {
        $classes = $this->listClasses();
        
        $return = array(
            "stat" => "err"
        );
        
        if ($classes['stat'] === "ok") {
            $return['stat'] = "ok";
            
            foreach ($classes['class'] as $id => $data) {
                $return['wheels'][$data['wheel_arrangement_id']][$id] = $data;
            }
            
            ksort($return['wheels']);
        }
        
        return $return;
    }
    
    /**
     * Loco classes by traction type
     * @since Version 3.2
     * @version 3.2
     * @return array
     */
    
    public function classByType() {
        $classes = $this->listClasses();
        
        $return = array(
            "stat" => "err"
        );
        
        if ($classes['stat'] === "ok") {
            $return['stat'] = "ok";
            
            foreach ($classes['class'] as $id => $data) {
                $return['type'][$data['loco_type_id']][$id] = $data;
            }
            
            ksort($return['type']);
        }
        
        return $return;
    }
    
    /**
     * Locos by operator
     * @since Version 3.2
     * @version 3.2
     * @param int $operatorId
     * @return array
     */
    
    public function locosByOperator($operatorId = null) {
        
        if (!filter_var($operatorId, FILTER_VALIDATE_INT)) {
            return false;
        }
        
        $query = "SELECT l.loco_id, l.loco_num, l.loco_gauge, l.loco_status_id AS status_id, s.name AS status_name, l.class_id AS class_id, c.slug AS class_slug, c.name AS class_name, c.flickr_tag AS class_flickr_tag, l.owner_id, o.operator_name AS owner_name, c.wheel_arrangement_id, w.arrangement AS wheel_arrangement, c.loco_type_id, t.title AS loco_type
                    FROM loco_unit AS l 
                    LEFT JOIN loco_org_link AS lo ON lo.loco_id = l.loco_id
                    LEFT JOIN loco_status AS s ON s.id = l.loco_status_id
                    LEFT JOIN loco_class AS c ON l.class_id = c.id
                    LEFT JOIN operators AS o ON o.operator_id = lo.operator_id
                    LEFT JOIN wheel_arrangements AS w ON c.wheel_arrangement_id = w.id
                    LEFT JOIN loco_type AS t ON c.loco_type_id = t.id
                    WHERE lo.operator_id = ?
                    AND lo.link_type = 2
                    ORDER BY c.name, l.loco_num";
        
        $return = array("stat" => "ok", "count" => 0, "locos" => array()); 
        
        foreach ($this->db->fetchAll($query, $operatorId) as $row) {
            if (!empty($row['class_flickr_tag'])) {
                $row['flickr_tag'] = $row['class_flickr_tag']."-".$row['loco_num'];
            }
            
            $row['loco_url'] = $this->makeLocoURL($row['class_slug'], $row['loco_num']);
            
            $return['locos'][$row['loco_id']] = $row;
            $return['count']++; 
        }
        
        return $return;
        
    }
    
    /**
     * Locos by owner
     * @since Version 3.2
     * @version 3.2
     * @param int $ownerId
     * @return array
     */
    
    public function locosByOwner($ownerId = null) {
        
        if (!filter_var($ownerId, FILTER_VALIDATE_INT)) {
            return false;
        }
        
        $query = "SELECT l.loco_id, l.loco_num, l.loco_gauge, l.loco_status_id AS status_id, s.name AS status_name, l.class_id AS class_id, c.slug AS class_slug, c.name AS class_name, c.flickr_tag AS class_flickr_tag, l.operator_id, o.operator_name AS operator_name, c.wheel_arrangement_id, w.arrangement AS wheel_arrangement, c.loco_type_id, t.title AS loco_type
                    FROM loco_unit AS l 
                    LEFT JOIN loco_org_link AS lo ON lo.loco_id = l.loco_id
                    LEFT JOIN loco_status AS s ON s.id = l.loco_status_id
                    LEFT JOIN loco_class AS c ON l.class_id = c.id
                    LEFT JOIN operators AS o ON o.operator_id = lo.operator_id
                    LEFT JOIN wheel_arrangements AS w ON c.wheel_arrangement_id = w.id
                    LEFT JOIN loco_type AS t ON c.loco_type_id = t.id
                    WHERE lo.operator_id = ?
                    AND lo.link_type = 1
                    ORDER BY c.name, l.loco_num";
        
        $return = array("stat" => "ok", "count" => 0, "locos" => array()); 
        
        foreach ($this->db->fetchAll($query, $ownerId) as $row) {
            if (!empty($row['class_flickr_tag'])) {
                $row['flickr_tag'] = $row['class_flickr_tag']."-".$row['loco_num'];
            
            }
            
            $row['loco_url'] = $this->makeLocoURL($row['class_slug'], $row['loco_num']);
            
            $return['locos'][$row['loco_id']] = $row;
            $return['count']++; 
        }
        
        return $return;
        
    }
    
    /**
     * List wheel arrangements
     * @since Version 3.2
     * @version 3.10.0
     * @return array
     * @param boolean $force Ignore Memcached and force refresh this list
     * @todo Remove this helper function, redirect all frontend code to \Railpage\Locos\Lister::getWheelArrangements()
     */
    
    public function listWheelArrangements($force = null) {
        
        return Lister::getWheelArrangements($force); 
        
    }
    
    /**
     * List manufacturers
     * @since Version 3.2
     * @version 3.10.0
     * @return array
     * @param boolean $force Ignore Memcached and force refresh this list
     * @todo Remove this helper function, redirect all frontend code to \Railpage\Locos\Lister::getManufacturers()
     */
    
    public function listManufacturers($force = null) {
        
        return Lister::getManufacturers($force); 
        
    }
    
    /**
     * List loco types
     * @since Version 3.2
     * @version 3.10.0
     * @return array
     * @todo Remove this helper function, redirect all frontend code to \Railpage\Locos\Lister::getTypes()
     */
    
    public function listTypes() {
        
        return Lister::getTypes(); 
        
    }
    
    /**
     * List loco status types
     * @since Version 3.2
     * @version 3.10.0
     * @return array
     * @todo Remove this helper function, redirect all frontend code to \Railpage\Locos\Lister::getStatus()
     */
    
    public function listStatus() {
        
        return Lister::getStatus(); 
        
    }
    
    /**
     * List years and the classes in each year
     * @since Version 3.2
     * @version 3.10.0
     * @return array
     * @todo Remove this helper function, redirect all frontend code to \Railpage\Locos\Lister::getYears()
     */
    
    public function listyears() {
        
        return Lister::getYears(); 
        
    }
    
    /**
     * List operators
     * @since Version 3.2
     * @version 3.10.0
     * @return array
     * @todo Remove this helper function, redirect all frontend code to \Railpage\Locos\Lister::getOperators()
     */
    
    public function listOperators() {
        
        return Lister::getOperators(); 
        
    }
            
    /** 
     * List all locos
     * @since Version 3.2
     * @version 3.10.0
     * @return array
     * @todo Remove this helper function, redirect all frontend code to \Railpage\Locos\Lister::getOperators()
     */
    
    public function listAllLocos() {
        
        return Lister::getAllLocos(); 
        
    }
    
    /**
     * List all liveries
     * @since Version 3.2
     * @return array
     * @todo Remove this helper function, redirect all frontend code to \Railpage\Locos\Lister::getLiveries()
     */
    
    public function listLiveries() {
        
        return Lister::getLiveries(); 
        
    }
    
    /**
     * Get loco gauges
     * @since Version 3.4
     * @return array
     * @todo Remove this helper function, redirect all frontend code to \Railpage\Locos\Lister::getGauges()
     */
    
    public function listGauges() {
       
        return Lister::getGauges(); 
        
    }
    
    /**
     * List all organisation types
     * @since Version 3.4
     * @return array
     * @todo Remove this helper function, redirect all frontend code to \Railpage\Locos\Lister::getOrgLinkTypes()
     */
    
    public function listOrgLinkTypes() {
        
        return Lister::getOrgLinkTypes(); 
        
    }
    
    /**
     * List production models
     * @since Version 3.4
     * @return array
     * @todo Remove this helper function, redirect all frontend code to \Railpage\Locos\Lister::getModels()
     */
    
    public function listModels() {
        
        return Lister::getModels(); 
        
    }
    
    /**
     * List locomotive groupings
     * @since Version 3.5
     * @return array
     * @todo Remove this helper function, redirect all frontend code to \Railpage\Locos\Lister::getGroupings()
     */
    
    public function listGroupings() {
        
        return Lister::getGroupings(); 
        
    }
    
    /**
     * Edit note
     * @since Version 3.2
     * @param int $noteId
     * @param string $noteText
     * @return boolean
     */
    
    public function editNote($noteId = null, $noteText = null) {
        
        if (!filter_var($noteId, FILTER_VALIDATE_INT)) {
            throw new InvalidArgumentException("No valid note ID was provided"); 
        }
        
        if (is_null($noteText)) {
            throw new InvalidArgumentException("No note text was provided"); 
        }
        
        $data = array(
            "note_text" => $noteText
        ); 
        
        $where = array(
            "note_id = ?" => $noteId
        );
        
        $this->db->update("loco_notes", $data, $where); 
        
        return true;
        
    }
    
    /**
     * Delete note
     * @since Version 3.2
     * @param int $noteId
     * @return boolean
     */
    
    public function deleteNote($noteId = null) {
        
        if (!filter_var($noteId, FILTER_VALIDATE_INT)) {
            throw new InvalidArgumentException("No valid note ID was provided"); 
        } 
        
        $where = array(
            "note_id = ?" => $noteId
        );
        
        $this->db->delete("loco_notes", $where); 
        
        return true;
        
    }
    
    /**
     * Get the type of dates
     * @since Version 3.2
     * @return array
     */
    
    public function dateTypes() {
        
        $query = "SELECT * FROM loco_date_type ORDER BY loco_date_text ASC";
        $return = array();
        
        foreach ($this->db->fetchAll($query) as $row) {
            $return[$row['loco_date_id']] = $row['loco_date_text']; 
        }
        
        return $return;
        
    }
    
    /**
     * Load a single date entry
     * @since Version 3.2
     * @param int $dateId
     * @return mixed
     */
    
    public function loadDate($dateId = null) {
        
        throw new Exception("Deprecated function: use \Railpage\Locos\Date"); 
        
        /*
        if (!filter_var($dateId, FILTER_VALIDATE_INT)) {
            throw new Exception("Cannot load date - no date ID provided");
        }
        
        $query = "SELECT * FROM loco_unit_date WHERE date_id = ?"; 
        
        return $this->db->fetchRow($query, $dateId);
        */
    }
    
    /**
     * Delete a date
     * @since Version 3.2
     * @param int $dateId
     * @return boolean
     */
    
    public function deleteDate($dateId = null) {
        
        throw new Exception("Deprecated function: use \Railpage\Locos\Date"); 
        
        /*
        if (!filter_var($dateId, FILTER_VALIDATE_INT)) {
            throw new Exception("Cannot delete date - no date ID provided"); 
        }
        
        $where = array(
            "date_id = ?" => $dateId
        );
        
        $this->db->delete("loco_unit_date", $where);
        return true;
        */
    }
    
    /**
     * Get a random photo 
     * @since Version 3.2
     * @return string
     */
    
    public function randomPhoto() {
        $query = "SELECT photo_id FROM loco_unit WHERE photo_id > 0";
        
        $return = array(); 
        
        foreach ($this->db->fetchAll($query) as $row) {
            $return[] = $row['photo_id']; 
        }
        
        return $return;
    }
    
    /**
     * Find locos, classes from a tag or array of tags
     * @since Version 3.2
     * @param string|array $tags
     * @return array
     */
    
    public function findFromTag($tags) {
        return (new Maintainers\Finder)->find(Maintainers\Finder::FIND_FROM_TAGS, $tags); 
    }
    
    /**
     * Get the ID from the livery name
     * @since Version 3.2
     * @author Michael Greenhill
     * @param string $livery
     */
    
    public function liveryID($livery = null) {
        
        if ($livery == null) {
            throw new InvalidArgumentException("No livery name was provided"); 
        }
        
        $query = "SELECT livery_id FROM loco_livery WHERE livery = ?";
        
        $result = $this->db->fetchOne($query, $livery); 
        
        if (count($result) === 0) {
            $data = array(
                "livery" => $livery
            );
            
            $this->db->insert("loco_livery", $data); 
            return $this->db->lastInsertId(); 
        }
        
        return $result['livery_id'];
        
    }
    
    /**
     * Link loco A to loco B using relationship C
     * @since Version 3.2
     * @param \Railpage\Locos\Locomotive $locoObjectA
     * @param \Railpage\Locos\Locomotive $locoObjectB
     * @param int $link_type_id
     * @return boolean
     * @throws \Exception if $LocoA is not an instance of \Railpage\Locos\Locomotive
     * @throws \Exception if $LocoB is not an instance of \Railpage\Locos\Locomotive
     * @throws \Exception if $linkTypeId is missing
     */
    
    public function link(Locomotive $locoObjectA, Locomotive $locoObjectB, $linkTypeId = null) {
        if (!$locoObjectA instanceof Locomotive) {
            throw new Exception("Cannot establish link between locomotives - Paramter 1 (Loco A) missing");
        }
        if (!$locoObjectB instanceof Locomotive) {
            throw new Exception("Cannot establish link between locomotives - Paramter 2 (Loco B) missing");
        }
        
        if (!filter_var($linkTypeId, FILTER_VALIDATE_INT)) {
            throw new Exception("Cannot establish link between locomotives - Parameter 3 (link type) missing");
        }
        
        $data = array(
            "loco_id_a" => $locoObjectA->id,
            "loco_id_b" => $locoObjectB->id,
            "link_type_id" => $linkTypeId
        );
        
        $this->db->insert("loco_link", $data);
        return true;
    }
    
    /**
     * Delete loco 
     * @since Version 3.2
     * @param int $id
     * @return boolean
     */
    
    public function deleteLink($id = null) {
        if (!filter_var($id, FILTER_VALIDATE_INT)) {
            throw new Exception("Cannot delete loco  - no ID given"); 
        } 

        $where = array(
            "link_id = ?" => $id
        );
        
        $this->db->delete("loco_link", $where); 
        
        return true;
    }
    
    /**
     * Get loco 
     * @since Version 3.5
     * @param int $id
     * @return array
     */
    
    public function getLink($id = null) {
        if (!filter_var($id, FILTER_VALIDATE_INT)) {
            throw new Exception("Cannot fetch loco  - no ID given"); 
        }
        
        $query = "SELECT * FROM loco_link WHERE link_id = ?";
        
        return $this->db->fetchRow($query, $id); 
    }
    
    /**
     * Get suggested corrections
     * @since Version 3.2
     * @return array
     * @param boolean $active
     */
    
    public function corrections($active = null) {
        $active_sql = " WHERE c.status != 0 ";
        
        if ($active != null) {
            $active_sql = " WHERE c.status = 0 ";
        }
        
        $query = "SELECT c.*, l.loco_num, lc.name AS class_name, lc.id AS class_id, u.username, u.user_avatar
                    FROM loco_unit_corrections AS c
                    LEFT JOIN loco_unit AS l ON c.loco_id = l.loco_id
                    LEFT JOIN loco_class AS lc ON lc.id = l.class_id
                    LEFT JOIN nuke_users AS u ON c.user_id = u.user_id
                    ".$active_sql."
                    ORDER BY c.date DESC";
        
        $return = array(); 
        
        foreach ($this->db->fetchAll($query) as $row) {
            $return[$row['loco_id']][] = $row; 
        }
        
        return $return;
    }
    
    /**
     * Get latest owner of a locomotive
     * @since Version 3.4
     * @param int $locoId
     * @return array
     */
    
    public function getLastOwner($locoId = null) {
        if (!filter_var($locoId, FILTER_VALIDATE_INT)) {
            throw new Exception("Could not get latest owner - no loco ID given"); 
        }
        
        $query = "SELECT l.*, o.operator_name FROM loco_org_link AS l LEFT JOIN operators AS o ON l.operator_id = o.operator_id WHERE l.loco_id = ? AND l.link_type = 1 ORDER BY l.link_weight DESC LIMIT 1";
        
        return $this->db->fetchRow($query, $locoId); 
    }
    
    /**
     * Get latest operator of a locomotive
     * @since Version 3.4
     * @param int $locoId
     * @return array
     */
    
    public function getLastOperator($locoId = null) {
        if (!filter_var($locoId, FILTER_VALIDATE_INT)) {
            throw new Exception("Could not get latest operator - no loco ID given"); 
        }
        
        $query = "SELECT l.*, o.operator_name FROM loco_org_link AS l LEFT JOIN operators AS o ON l.operator_id = o.operator_id WHERE l.loco_id = ? AND l.link_type = 2 ORDER BY l.link_weight DESC LIMIT 1";
        
        return $this->db->fetchRow($query, $locoId); 
    }
    
    /**
     * Get classes by production model
     * @since Version 3.4
     * @param string $model
     * @return array
     */
    
    public function getClassesByModel($model = null) {
        if (is_null($model)) {
            throw new Exception("Could not fetch list of classes - no production model given"); 
        }
        
        $query = "SELECT id, name, slug FROM loco_class WHERE Model = ? ORDER BY name";
        
        $return = array(); 
        
        foreach ($this->db->fetchAll($query, $model) as $row) {
            $row['class_url'] = $this->makeClassUrl($row['slug']);
            $return[$row['id']] = $row; 
        }
        
        return $return;
    }
    
    /**
     * Add a new locomotive group
     * @since Version 3.5
     * @param string $name
     * @return int
     */
    
    public function addGrouping($name = null) {
        if (is_null($name)) {
            throw new Exception("Cannot add group - group name cannot be empty"); 
        }
        
        $data = array(
            "group_name" => $name
        );
        
        $this->db->insert("loco_groups", $data); 
        return $this->db->lastInsertId();
    }
    
    /**
     * Get group membership for this loco
     * @since Version 3.5
     * @param int $locoUnitId
     * @param boolean $includeInactive
     * @return array
     */
    
    public function getGroupsMembership($locoUnitId = null, $includeInactive = null) {
        if (!filter_var($locoUnitId, FILTER_VALIDATE_INT)) {
            throw new Exception("Could not fetch group membership - no loco ID given"); 
        }

        $query = "SELECT lg.* FROM loco_groups AS lg LEFT JOIN loco_groups_members AS lgm ON lgm.group_id = lg.group_id WHERE lgm.loco_unit_id = ?"; 
        
        if ($includeInactive == null) {
            $query .= " AND lg.active = 1"; 
        }
        
        $return = array(); 
        
        foreach ($this->db->fetchAll($query, $locoUnitId) as $row) {
            $return[$row['group_id']] = $row; 
        }
        
        return $return;
    }
    
    /**
     * Get members of a group
     * @since Version 3.5
     * @param int $groupId
     */
     
    public function getGroupMembers($groupId) {
        if (!filter_var($groupId, FILTER_VALIDATE_INT)) {
            throw new Exception("Cannot fetch group members - no group ID given"); 
        }
        
        $query = "SELECT loco_unit_id FROM loco_groups_members WHERE group_id = ?";
        
        $return = array(); 
        
        foreach ($this->db->fetchAll($query, $groupId) as $row) {
            $return[] = $row['loco_unit_id']; 
        }
        
        return $return;
    }
    
    /** 
     * Log an event 
     * @since Version 3.5
     * @param int $userId
     * @param string $title
     * @param array $args
     */
    
    public function logEvent($userId = null, $title = null, $args = null) {
        
        if (!filter_var($userId, FILTER_VALIDATE_INT)) {
            throw new Exception("Cannot log event, no User ID given"); 
        }
        
        if ($title == null) {
            throw new Exception("Cannot log event, no title given"); 
        }
        
        if (is_array($args)) {
            $args = json_encode($args); 
        }
        
        $dataArray = [
            "user_id" => $userId,
            "timestamp" => new \Zend_Db_Expr("NOW()"),
            "title" => $title, 
            "args" => $args
        ];
        
        $this->db->insert("log_locos", $dataArray); 
        return true;
        
    }
    
    /**
     * Make a class URL
     * @since Version 3.8.7
     * @param string $slug
     * @return string
     */
    
    public function makeClassURL($slug) {
        return sprintf("%s/%s", $this->Module->url, $slug);
    }
    
    /**
     * Make a loco URL
     * @since Version 3.8.7
     * @param string $classSlug
     * @param string $locoNumber
     * @return string
     */
    
    public function makeLocoURL($classSlug, $locoNumber) {
        
        $locoNumber = str_replace(" ", "_", $locoNumber);
        return sprintf("%s/%s/%s", $this->Module->url, $classSlug, $locoNumber);
        
    }
    
    /**
     * Get a random class
     * @since Version 3.8.7
     * @return \Railpage\Locos\LocoClass;
     */
    
    public function getRandomClass() {
        $query = "SELECT `id`, `desc`, flickr_image_id FROM loco_class WHERE `desc` != '' AND flickr_image_id > 0";
        
        $ids = array();
        
        foreach ($this->db->fetchAll($query) as $row) {
            if (strlen(trim($row['desc'])) > 5 && $row['flickr_image_id'] > 0) {
                $ids[] = $row['id'];
            }
        }
        
        shuffle($ids);
        
        foreach ($ids as $id) {
            $LocoClass = new LocoClass($id);
            
            if (!empty($LocoClass->desc) && $LocoClass->getCoverImage()) {
                return $LocoClass;
            }
        }
    }
    
    /**
     * Year introduced URL
     * @since Version 3.8.7
     * @param int $year
     * @return string
     */
    
    public function makeYearURL($year = null) {
        return sprintf("/locos/year/%d", $year);
    }
    
    /**
     * Get statistics from the database
     * @since Version 3.8.7
     * @param string $statistic
     * @return mixed
     */
    
    public function getStatistic($statistic, $param1 = null, $param2 = null) {
        
        if (!$statistic) {
            throw new Exception("Cannot get module statistics - no statistic requested");
        }
        
        switch ($statistic) {
            
            case "railpage.locos.class.count" :
                
                $query = "SELECT count(id) AS count FROM loco_class";
                return $this->db->fetchOne($query);
                
                break;
            
            case "railpage.locos.loco.count" : 
                
                $query = "SELECT count(loco_id) AS count FROM loco_unit";
                return $this->db->fetchOne($query);
                
                break;
            
            case "railpage.locos.status.count" : 
                
                $query = "SELECT count(loco_id) AS count FROM loco_unit WHERE loco_status_id = ? OR loco_status_id = ?";
                return $this->db->fetchOne($query, array($param1, $param2));
                
                break;
        }
    }
    
    /**
     * Get locomotive dates from a given date range
     * @since Version 3.9
     * @param \DateTime $DateFrom
     * @param \DateTime $DateTo
     * @return \Railpage\Locos\Date
     * @yield \Railpage\Locos\Date
     */
    
    public function yieldDatesWithinRange(DateTime $dateFrom, DateTime $dateTo) {
        $query = "SELECT date_id FROM loco_unit_date WHERE timestamp >= ? AND timestamp <= ? ORDER BY timestamp";
        
        foreach ($this->db->fetchAll($query, array($dateFrom->format("Y-m-d"), $dateTo->format("Y-m-d"))) as $row) {
            yield new Date($row['date_id']);
        }
    }
    
    /**
     * Converts a height value given in cm to feet and inches
     *
     * @param int $cm
     * @return array
     */
    
    public static function convert_to_inches($cm) {
        $inches = round($cm * 0.393701);
        $result = [
            'ft' => intval($inches / 12),
            'in' => $inches % 12,
        ];
    
        return $result;
    }
    
    /**
     * Converts a height value given in feet/inches to cm
     *
     * @param int $feet
     * @param int $inches
     * @return int
     */
    
    public static function convert_to_cm($feet, $inches = 0) {
        $inches = ($feet * 12) + $inches;
        return (int) round($inches / 0.393701);
    }

    /**
     * Add a locomotive status
     * @since Version 3.9.1
     * @var string $name
     * @return \Railpage\Locos\Locos;
     */
    
    public function addStatus($name = null) {
        if ($name == null) {
            throw new Exception("No name was given for this status");
        }
        
        $data = array(
            "name" => $name
        );
        
        $this->db->insert("loco_status", $data); 
        
        return $this;
    }
}