railpage/railpagecore

View on GitHub
lib/Locations/Locations.php

Summary

Maintainability
F
5 days
Test Coverage
<?php
    /**
     * Locations module 
     * @since Version 3.0
     * @package Railpage
     * @author Michael Greenhill
     */
    
    namespace Railpage\Locations;
    
    use Exception;
    use Railpage\AppCore;
    use Railpage\Module;
    use Railpage\Place;
    use Railpage\Users\User;
    use Railpage\Users\Factory as UsersFactory;
    use Railpage\Debug;
    use Railpage\ISO\ISO_3166;
    
    /**
     * Base Locations class
     * @since Version 3.0
     */
    
    class Locations extends AppCore {
        
        /**
         * Database table
         * @since Version 3.0
         * @var string $table
         */
        
        public $table = "location";
        
        /**
         * Radius around location to find photos within
         * @since Version 3.0
         * @var float $photoRadius
         */
        
        public $photoRadius = 1; 
        
        /**
         * Constructor
         * @since Version 3.8.7
         */
        
        public function __construct() {
            parent::__construct(); 
            
            $this->Module = new Module("locations");
            $this->namespace = $this->Module->namespace;
        }
        
        /**
         * Get a list of countries in the locations database
         * @since Version 3.0
         * @return array
         */
         
        public function getCountries() {
            
            $timer = Debug::GetTimer();
            
            $mckey = "railpage:locations.countries";
            
            if ($return = $this->Memcached->fetch($mckey)) {
                // Do nothing
            } else {
                $return = array(); 
                
                foreach ($this->db->fetchAll("SELECT DISTINCT country FROM location ORDER BY country") as $row) {
                    $return[] = $row['country']; 
                } 
                    
                $this->Memcached->save($mckey, $return, strtotime("+1 day"));
            }
            
            Debug::LogEvent(__METHOD__, $timer);
            
            return $return;
        }
        
        /**
         * Get the regions in the locations database.
         * If $country is not specified, it will return all regions for all countries
         * @since Version 3.0
         * @param string $country An optional two letter country code we want to search for
         * @return array
         */
         
        public function getRegions($country = false) {
            
            $timer = Debug::GetTimer();
            
            $return = false;
            $mckey  = ($country) ? "railpage:locations.regions.country=" . $country : "railpage:locations.regions";
            
            if ($return = $this->Memcached->fetch($mckey)) {
                return $return;
            }
            
            $return = array(); 
            
            if ($country) {
                foreach ($this->db->fetchAll("SELECT DISTINCT region FROM location WHERE country = ? AND active = 1 ORDER BY region ASC", $country) as $row) {
                            
                    $woe = Place::getWOEData($country);
                    if (isset($woe['places']['place'][0])) {
                        $return[$country]['woe'] = $woe['places']['place'][0];
                    }
                    
                    $datarow = array(
                        "region" => $row['region'],
                        "url" => $this->makeRegionPermalink($country, $row['region']),
                        "count" => $this->db->fetchOne("SELECT COUNT(id) FROM location WHERE country = ? AND region = ?", array($country, $row['region'])),
                    );
                    
                    $woe = Place::getWOEData($row['region'] . "," . $country); 
                    if (isset($woe['places']['place'][0])) {
                        $datarow['woe'] = $woe['places']['place'][0];
                    }
                    
                    $return[$country]['children'][] = $datarow;
                }
                
                $this->Memcached->save($mckey, $return, strtotime("+1 day"));
                
                Debug::LogEvent(__METHOD__ . "(" . $country . ")", $timer);
                
                return $return;
            }
                
            $query = "SELECT DISTINCT l.region, l.country, g.country_name, g.region_name 
                FROM location AS l 
                LEFT JOIN geoplace AS g ON l.geoplace = g.id 
                WHERE l.active = 1 
                GROUP BY l.country 
                ORDER BY l.region DESC";
            
            foreach ($this->db->fetchAll($query) as $row) {
                if (empty($row['country'])) {
                    continue;
                }
                
                $return[$row['country']]['woe'] = array(
                    "country" => $row['country_name']
                );
                
                if (empty($return[$row['country']]['woe']['country'])) {
                    $woe = Place::getWOEData(strtoupper($row['region']));
                    $return[$row['country']]['woe'] = array(
                        "country" => $woe['places']['place'][0]['country']
                    );
                }
                
                $return[$row['country']]['children'][] = $row['region']; 
            }
            
            // Cache it
            $this->Memcached->save($mckey, $return, strtotime("+1 day"));
            
            Debug::LogEvent(__METHOD__ . "(" . $country . ")", $timer);
            
            return $return;
        }
        
        /**
         * Get locations within region
         * @since Version 3.0
         * @param string $region
         * @param string $country
         * @return array
         */
         
        public function getLocations($region = false, $country = false) {
            
            if (!$region || !$country) {
                return false;
            }
            
            $timer = Debug::GetTimer();
            
            $mckey = "railpage:locations";
            if ($country) $mckey .= ".country=" . $country;
            if ($region) $mckey .= ".region=" . $region; 
            
            $query = "SELECT * FROM location WHERE country = ? AND region = ? AND active = 1 ORDER BY locality, neighbourhood";
            
            $return = $this->db->fetchAll($query, array($country, $region));
            
            Debug::LogEvent(__METHOD__, $timer);
            
            return $return; 
            
        }
        
        /**
         * Get all pending locations
         * @since Version 3.0
         * @return array
         */
         
        public function getPending() {
            
            $query = "SELECT l.*, u.username 
                    FROM location AS l 
                    INNER JOIN nuke_users AS u ON u.user_id = l.user_id 
                    WHERE l.active = 0 
                    ORDER BY l.date_added";
            return $this->db->fetchAll($query);
            
        }
        
        /**
         * Get a specific location
         * @since Version 3.0
         * @param int $id
         * @param boolean $pending
         * @return array
         */
         
        public function getSite($id = false, $pending = false) {
            if (!$this->db) {
                return false;
            }
            
            $timer = Debug::GetTimer(); 
            
            $query  = "SELECT location.*, count(locations_like.location_id) AS likes FROM location LEFT JOIN locations_like ON location.id = locations_like.location_id";
            $params = array();
            $args   = array();
            
            if ($id) {
                $args[]     = "location.id = ?";
                $params[]   = $id;
            }
            
            if (!$pending) {
                $args[] = "location.active = 1";
            }
            
            if (count($args)) {
                $query .= " WHERE ".implode(" AND ", $args); 
            }
            
            $query .= " GROUP BY 1";
            
            $return = array(); 
            
            foreach ($this->db->fetchAll($query, $params) as $row) {
                $row['url'] = sprintf("%s/%s", $this->makeRegionPermalink($row['country'], $row['region']), $row['slug']);
                $return[$row['id']] = $row; 
            }
            
            if (count($return) && $id) {
                // Set the photo radius based on the zoom level
                $zoom = $return[$id]['zoom']; 
                
                // Rough guide, my mathematics sucks - 11 = 1
                $photo_radius = 11; 
                
                $zoom = 100 - ($photo_radius / $zoom * 100); 
                $zoom = round(1 - $zoom / 100, 2); 
                $this->photoRadius = $zoom; 
            }
            
            Debug::LogEvent(__METHOD__, $timer);
            
            return $return;
        }
        
        /**
         * Find a location from a given latitude and longitude
         * 
         * This function uses mysqli::multi_query() as it was buggering up all subsequent SQL queries on the page
         * @since Version 3.0.1
         * @version 3.0.1
         * @param string $lat
         * @param string $lon
         * @param int $distance
         * @param int $num
         * @return array
         */
         
        public function getSiteFromCoords($lat = false, $lon = false, $distance = 1, $num = 5) {
            if (!$lat || !$lon || !$this->db) {
                return false;
            }
            
            $timer = Debug::GetTimer(); 
            
            $mckey = "rp-locations-geolookup-lat:" . $lat . "-lon:" . $lon . "-dist:" . $distance . "-num:" . $num;
            
            if (!$return = $this->Memcached->fetch($mckey)) {
                $query = "SELECT location.*, 
                    3956 * 2 * ASIN(
                        SQRT(
                            POWER(
                            SIN(
                                (" . $lat . " - location.lat) * pi() / 180 / 2
                            ), 2
                            ) + COS(
                                " . $lat . " * pi() / 180
                            ) * COS(
                                location.lat * pi() / 180
                            ) * POWER(
                                SIN(
                                    (" . $lon . " - location.long) * pi() / 180 / 2
                                ), 2
                            )
                        )
                    ) AS distance 
                    FROM location 
                    WHERE 
                        location.long BETWEEN (
                            " . $lon . " - " . $distance . " / abs(cos(radians(" . $lat . ")) * 69)
                        ) AND ( 
                            " . $lon . " + " . $distance . " / abs(cos(radians(" . $lat . ")) * 69)
                        )
                        AND location.lat BETWEEN (
                            " . $lat . " - (" . $distance . " / 69) 
                        ) AND ( 
                            " . $lat . " + (" . $distance . " / 69)
                        )
                    HAVING distance < ? 
                    ORDER BY distance
                    LIMIT ?";
                
                $params = array(
                    $distance,
                    $num
                );
                
                $return = $this->db->fetchAll($query, $params); 
                
                $this->Memcached->save($mckey, $return, 0);
            }
            
            Debug::LogEvent(__METHOD__, $timer);
            
            return $return;
        }
        
        /**
         * Find a nearby location
         * @since Version 3.0
         * @param float $lat
         * @param float $lon
         * @param int $radius
         * @return array
         * @throws \Exception if $lat has not been provided
         * @throws \Exception if $lon has not been provided
         */
         
        public function nearby($lat = false, $lon = false, $radius = false) {
            
            if (!$lat) {
                throw new Exception("Cannot fetch locations near co-ordinates: no latitude value given");
            }
            
            if (!$lon) {
                throw new Exception("Cannot fetch locations near co-ordinates: no longitude value given");
            }
            
            if (!$radius) {
                $radius = $this->photoRadius; 
            }
            
            $timer = Debug::GetTimer(); 
            
            $result = false;
                
            $min_lat    = $lat - $radius;
            $max_lat    = $lat + $radius;
            $min_long   = $lon - $radius;
            $max_long   = $lon + $radius;
            
            $query = "SELECT * FROM location WHERE `lat` > ? AND `lat` < ? AND `long` > ? AND `long` < ?"; 
            $params = array(
                $min_lat, 
                $max_lat,
                $min_long,
                $max_long
            );
            
            $return = array(); 
            
            foreach ($this->db->fetchAll($query, $params) as $row) {
                $row['url'] = sprintf("%s/%s", $this->makeRegionPermalink($row['country'], $row['region']), $row['slug']);
                $return[$row['id']] = $row; 
            }
            
            Debug::LogEvent(__METHOD__, $timer);
            
            return $return;
        }       
        /**
         * Get newest locations
         * @param int $limit
         */
        
        public function newest($limit = 5) {
            
            $timer = Debug::GetTimer(); 
            
            $query = "SELECT * FROM location WHERE active = 1 ORDER BY date_added DESC LIMIT ?";
            
            $return = $this->db->fetchAll($query, $limit); 
                
            Debug::LogEvent(__METHOD__, $timer);
            
            return $return;
        }
        
        /**
         * Get location ID from slug
         * @since Version 3.7.5
         * @param string $slug
         * @return int
         * @throws \Exception if $slug has not been provided
         */
        
        public function getIdFromSlug($slug = false) {
            if (!$slug || empty($slug)) {
                throw new Exception("\$slug cannot be empty or unset - required to find location ID");
                return false;
            }
            
            $query = "SELECT id FROM location WHERE slug = ?";
            
            return $this->db->fetchOne($query, $slug); 
        }
        
        /**
         * Make a permalink for this location
         * @since Version 3.7.5
         * @return string
         * @param int $id Optional location ID - inherited by 
         *    Railpage\Locations\Location so will attempt to use $this->id if none provided
         */
        
        public function makePermalink($id = false) {
            $mckey = $id ? "railpage:locations.permalink.id=" . $id : "railpage:locations.permalink.id=" . $this->id;
            
            $timer = Debug::GetTimer(); 
            
            if (!$string = $this->Memcached->fetch($mckey)) {
                if ((!isset($this->country) || !isset($this->region) || !isset($this->slug)) && $id) {
                    // Fetch it from the database
                    
                    $query = "SELECT country, region, slug FROM location WHERE id = ?";
                    $data = $this->db->fetchRow($query, $id); 
                
                    if (empty($data['slug'])) {
                        $Location = new Location($id);
                        $data['slug'] = $Location->slug; 
                    }
                } else {
                    $data['country'] = $this->country;
                    $data['region'] = $this->region; 
                    $data['slug'] = $this->slug; 
                }
                
                $params = [ 
                    $this->Module->url, 
                    str_replace(" ", "-", $data['country']), 
                    str_replace(" ", "-", $data['region']), 
                    $data['slug']
                ];
                
                $string = strtolower(vsprintf("%s/%s/%s/%s", $params));
                
                $this->Memcached->save($mckey, $string, strtotime("+1 year"));
            }
            
            Debug::LogEvent(__METHOD__, $timer);
            
            return $string;
        }
        
        /**
         * Region permalink
         * @since Version 3.7.5
         * @return string
         * @param string $country
         * @param string $region
         */
        
        public function makeRegionPermalink($country = false, $region = false) {
            if (!$region && isset($this->region) && !empty($this->region)) {
                $region = $this->region;
            }
            
            if (!$country && isset($this->country) && !empty($this->country)) {
                $country = $this->country;
            }
            
            if (!$region || !$country) {
                return false;
            }
            
            return strtolower(sprintf("%s/%s/%s", $this->Module->url, self::create_slug($country), $this->makeRegionSlug($region)));
        }
        
        /**
         * Make a region slug
         * @since Version 3.8.7
         * @param string $region
         * @return string
         */
        
        public function makeRegionSlug($region = false) {
            if (!$region && isset($this->region) && !empty($this->region)) {
                $region = $this->region;
            }
            
            if (!$region) {
                return false;
            }
            
            return self::create_slug($region);
        }
        
        /**
         * Get date types
         * @since Version 3.8.7
         * @return array
         */
        
        public function getDateTypes() {
            $query = "SELECT * FROM location_datetypes ORDER BY name";
            
            return $this->db->fetchAll($query);
        }
        
        /**
         * Get a random location from the database
         */
        
        public function getRandomLocation() {
            $query = "SELECT id, `desc` FROM location WHERE active = 1";
            $locations = array();
            
            foreach ($this->db->fetchAll($query) as $row) {
                if (!empty($row['desc']) && strlen($row['desc']) > 5) {
                    $locations[] = $row['id'];
                }
            }
            
            return new Location(array_rand($locations));
        }
        
        /**
         * Get all open corrections
         * @since Version 3.9.1
         * @return array
         */
        
        public function getOpenCorrections() {
            
            $query = "SELECT l.name AS location_name, u.username, c.* 
                    FROM location_corrections AS c 
                    LEFT JOIN location AS l ON c.location_id = l.id 
                    LEFT JOIN nuke_users AS u ON u.user_id = c.user_id 
                    WHERE c.status = ? 
                    ORDER BY c.date_added DESC";
            
            $return = array(); 
            
            foreach ($this->db->fetchAll($query, Correction::STATUS_NEW) as $row) {
                $return[] = array(
                    "location" => (new Location($row['location_id']))->getArray(),
                    "author" => UsersFactory::CreateUser($row['user_id'])->getArray(),
                    "correction" => array(
                        "comments" => $row['comments'],
                        "date" => array(
                            "added" => $row['date_added'],
                            "closed" => $row['date_closed']
                        )
                    )
                );
            }
            
            return $return;
            
        }
    }