<?php
/*
 * Copyright (C) 2017 Karmabunny Pty Ltd.
 *
 * This file is a part of SproutCMS.
 *
 * SproutCMS is free software: you can redistribute it and/or modify it under the terms
 * of the GNU General Public License as published by the Free Software Foundation, either
 * version 2 of the License, or (at your option) any later version.
 *
 * For more information, visit <http://getsproutcms.com>.
 */
 
namespace Sprout\Helpers;
 
use Exception;
use InvalidArgumentException;
 
use Kohana;
 
use karmabunny\pdb\Exceptions\QueryException;
 
 
/**
* Provides functions for getting information about subsites
**/
class Subsites
{
    private static $subsites = null;
    private static $configs = null;
 
 
    /**
    * Loads all the subsites into memory, ready for further processing
    **/
    static private function loadSubsites()
    {
        if (self::$subsites != null) return;
 
        $q = "SELECT id, cond_directory, cond_domain, content_id, name, code
            FROM ~subsites
            ORDER BY record_order";
        try {
            $result = Pdb::query($q, [], 'pdo');
        } catch (QueryException $ex) {
            // Assume DB has no tables; pretend there's a subsite
            self::$subsites = [];
            self::$subsites[1] = [
                'id' => 1,
                'cond_directory' => '',
                'cond_domains' => [],
                'content_id' => '',
                'name' => 'Site with no DB',
                'code' => 'default',
            ];
            return;
        }
 
        foreach ($result as $sub) {
            $subsites[$sub['id']] = $sub;
        }
        $result->closeCursor();
 
        self::$subsites = $subsites;
    }
 
 
    /**
    * Determines if multiple subsites can be accessed by the currently logged in administrator
    *
    * @return bool True if multiple subsites are available, false if they are not
    **/
    public static function hasMultiple()
    {
        self::loadSubsites();
        AdminAuth::checkLogin();
 
        $count = 0;
        foreach (self::$subsites as $sub) {
            if (AdminPerms::canAccessSubsite($sub['id'])) $count++;
        }
 
        return ($count > 1);
    }
 
 
    /**
    * Outputs a select UL for choosing a subsite from within the admin area
    *
    * @param int $selected_id The currently selected subsite, if any
    **/
    public static function listSelector($selected_id = null)
    {
        self::loadSubsites();
        AdminAuth::checkLogin();
 
        echo '<form action="admin/set_active_subsite" method="post">';
        echo Csrf::token();
        echo "<div class=\"field-element field-element--white field-element--select\">";
            echo "<div class=\"field-label -vis-hidden\">";
                echo "<label for=\"subsite-selector\">Current site</label>";
            echo "</div>";
            echo "<div class=\"field-input\">";
                echo "<select id=\"subsite-selector\" name=\"subsite\">";
 
                    foreach (self::$subsites as $site) {
                        if (AdminPerms::canAccessSubsite($site['id'])) {
                            $site_name_html = Enc::html($site['name']);
 
                            // It's a shared-content subsite, they can't be edited
                            if ($site['content_id'] != 0) {
                                $shared_name_html = 'another subsite';
                                foreach (self::$subsites as $ss) {
                                    if ($ss['id'] == $site['content_id']) { $shared_name_html = Enc::html($ss['name']); break; }
                                }
 
                                echo "<option value=\"\">{$site_name_html} (content from {$shared_name_html})</option>\n";
                                continue;
                            }
 
                            // Regular subsite with on-state
                            if ($site['id'] == $selected_id) {
                                echo "<option value=\"\" selected>Editing {$site_name_html}</option>\n";
                            } else {
                                echo "<option value=\"{$site['id']}\">{$site_name_html}</option>\n";
                            }
                        }
                    }
 
                echo "</select>";
            echo "</div>";
        echo "</div>";
        echo '</form>';
    }
 
 
    /**
    * Returns the id of the first subsite in the list (alphabetical) that the user is able to access.
    * Returns null if there are no subsites the user can access.
    **/
    public static function getFirstAccessable()
    {
        self::loadSubsites();
 
        foreach (self::$subsites as $sub) {
            if (AdminPerms::canAccessSubsite($sub['id'])) return $sub['id'];
        }
 
        return null;
    }
 
 
    /**
    * Returns the name of the specified subsite
    **/
    public static function getName($id)
    {
        self::loadSubsites();
 
        if (!isset(self::$subsites[$id])) {             throw new InvalidArgumentException("Subsite #{$id} not found");
        }
 
        return self::$subsites[$id]['name'];
    }
 
    /**
    * Returns the name of the specified subsite
    **/
    public static function getCode($id)
    {
        self::loadSubsites();
 
        if (!isset(self::$subsites[$id])) {             throw new InvalidArgumentException("Subsite #{$id} not found");
        }
 
        return self::$subsites[$id]['code'];
    }
 
 
    /**
    * For a given list of domains, determine the best match based on the current HTTP_HOST
    *
    * If there is an exact match, it is used
    * If there is an ends-with match, it is used
    * Failing all that, the first domain in the list is used
    **/
    static private function determineBestDomain($domains)
    {
        if (empty($_SERVER['HTTP_HOST'])) return $domains[0];  
        foreach ($domains as $d) {
            if ($d === $_SERVER['HTTP_HOST']) return $d;
        }
 
        foreach ($domains as $d) {
            if (strrpos($d, $_SERVER['HTTP_HOST']) === strlen($d) - strlen($_SERVER['HTTP_HOST'])) return $d;         }
 
        return $domains[0];
    }
 
 
    /**
    * Returns the abs root (including protocol and server name) of the specified subsite
    * Falls back to the current root if no abs root can be found in the database
    * @param int $id The subsite ID, e.g. SubsiteSelector::$subsite_id
    * @param string $protocol The protocol to use for the link. Specifying null (the default)
    *        will cause the protocol to be automatically determined based on the request.
    * @return string An absolute URL including protocol
    **/
    public static function getAbsRoot($id, $protocol = null)
    {
        self::loadSubsites();
 
        if (!isset(self::$subsites[$id])) {             throw new InvalidArgumentException('Subsite not found');
        }
 
        if (count(self::$subsites[$id]['cond_domains'])) {             $domain = self::determineBestDomain(self::$subsites[$id]['cond_domains']);
        } else {
            $domain = $_SERVER['HTTP_HOST'];
        }
 
        if (!$protocol) {
            if (PHP_SAPI != 'cli') {
                $protocol = Request::protocol();
            } else {
                $protocol = 'http';
            }
        }
 
        if (!empty(self::$subsites[$id]['cond_directory'])) {             $path = rtrim(self::$subsites[$id]['cond_directory'], '/') . '/';         } else {
            $path = '';
        }
 
        return $protocol . '://' . $domain . Kohana::config('config.site_domain') . $path;
    }
 
 
    /**
    * Returns the abs root (including protocol and server name) of the current admin subsite
    **/
    public static function getAbsRootAdmin()
    {
        AdminAuth::checkLogin();
        return self::getAbsRoot(@$_SESSION['admin']['active_subsite']);
    }
 
 
    /**
    * Get a config value for a given subsite
    *
    * Use this instead of Kohana::config, if you need to target a specific subsite.
    * It always looks in the "sprout" config file, doesn't support other files.
    *
    * @param string $key Configuration key
    * @param int $subsite_id Subsite to get config value for
    * @return mixed Configuration value
    **/
    public static function getConfig($key, $subsite_id)
    {
        $code = self::getCode($subsite_id);
 
        if (!$code) {
            throw new Exception('Subsite #'. $subsite_id . ' does not have a valid code specified');
        }
 
        $configuration = self::loadConfig($code);
        return @$configuration[$key];
    }
 
 
    /**
    * Admin version of the getConfig function, uses the currently active subsite
    **/
    public static function getConfigAdmin($key)
    {
        AdminAuth::checkLogin();
        return self::getConfig($key, @$_SESSION['admin']['active_subsite']);
    }
 
 
    /**
    * Load the configuration for a subsite code.
    * It will only be loaded once per request - it gets cached in a static var
    *
    * @param string $subsite_code The subsite code to load and return config for
    * @param array Configuration for that subsite
    **/
    public static function loadConfig($subsite_code)
    {
        if (isset(self::$configs[$subsite_code])) {             return self::$configs[$subsite_code];
        }
 
        if (! file_exists(DOCROOT 
. 'skin/' . $subsite_code . '/')) {             throw new Exception('Invalid subsite "' . $subsite_code . '"; skin directory not found.');
        }
 
            DOCROOT . 'skin/' . $subsite_code . '/config/sprout.php',
        );
 
        $configuration = array();         foreach ($files as $file) {
                include $file;
            }
 
            }
        }
 
        self::$configs[$subsite_code] = $configuration;
 
        return $configuration;
    }
 
 
    /**
     * Gets the list of subsite codes which are available, by reading the appropriate directory
     * @return array
     */
    public static function getCodes()
    {
        $codes = [];
        $skin_dir = DOCROOT . 'skin' . DIRECTORY_SEPARATOR;
        foreach ($files as $file) {
            if ($file[0] == '.') continue;
            if ($file == 'unavailable') continue;
            if (!is_dir($skin_dir . $file)) continue;             $codes[$file] = $file;
        }
        return $codes;
    }
 
}