SproutCMS

This is the code documentation for the SproutCMS project

source of /sprout/Helpers/Subsites.php

  1. <?php
  2. /*
  3.  * Copyright (C) 2017 Karmabunny Pty Ltd.
  4.  *
  5.  * This file is a part of SproutCMS.
  6.  *
  7.  * SproutCMS is free software: you can redistribute it and/or modify it under the terms
  8.  * of the GNU General Public License as published by the Free Software Foundation, either
  9.  * version 2 of the License, or (at your option) any later version.
  10.  *
  11.  * For more information, visit <http://getsproutcms.com>.
  12.  */
  13.  
  14. namespace Sprout\Helpers;
  15.  
  16. use Exception;
  17. use InvalidArgumentException;
  18.  
  19. use Kohana;
  20.  
  21. use karmabunny\pdb\Exceptions\QueryException;
  22.  
  23.  
  24. /**
  25. * Provides functions for getting information about subsites
  26. **/
  27. class Subsites
  28. {
  29. private static $subsites = null;
  30. private static $configs = null;
  31.  
  32.  
  33. /**
  34.   * Loads all the subsites into memory, ready for further processing
  35.   **/
  36. static private function loadSubsites()
  37. {
  38. if (self::$subsites != null) return;
  39.  
  40. $q = "SELECT id, cond_directory, cond_domain, content_id, name, code
  41. FROM ~subsites
  42. ORDER BY record_order";
  43. try {
  44. $result = Pdb::query($q, [], 'pdo');
  45. } catch (QueryException $ex) {
  46. // Assume DB has no tables; pretend there's a subsite
  47. self::$subsites = [];
  48. self::$subsites[1] = [
  49. 'id' => 1,
  50. 'cond_directory' => '',
  51. 'cond_domains' => [],
  52. 'content_id' => '',
  53. 'name' => 'Site with no DB',
  54. 'code' => 'default',
  55. ];
  56. return;
  57. }
  58.  
  59. $subsites = array();
  60. foreach ($result as $sub) {
  61. $sub['cond_domains'] = array_filter(explode("\n", $sub['cond_domain']));
  62. $subsites[$sub['id']] = $sub;
  63. }
  64. $result->closeCursor();
  65.  
  66. self::$subsites = $subsites;
  67. }
  68.  
  69.  
  70. /**
  71.   * Determines if multiple subsites can be accessed by the currently logged in administrator
  72.   *
  73.   * @return bool True if multiple subsites are available, false if they are not
  74.   **/
  75. public static function hasMultiple()
  76. {
  77. self::loadSubsites();
  78. AdminAuth::checkLogin();
  79.  
  80. $count = 0;
  81. foreach (self::$subsites as $sub) {
  82. if (AdminPerms::canAccessSubsite($sub['id'])) $count++;
  83. }
  84.  
  85. return ($count > 1);
  86. }
  87.  
  88.  
  89. /**
  90.   * Outputs a select UL for choosing a subsite from within the admin area
  91.   *
  92.   * @param int $selected_id The currently selected subsite, if any
  93.   **/
  94. public static function listSelector($selected_id = null)
  95. {
  96. self::loadSubsites();
  97. AdminAuth::checkLogin();
  98.  
  99. echo '<form action="admin/set_active_subsite" method="post">';
  100. echo Csrf::token();
  101. echo "<div class=\"field-element field-element--white field-element--select\">";
  102. echo "<div class=\"field-label -vis-hidden\">";
  103. echo "<label for=\"subsite-selector\">Current site</label>";
  104. echo "</div>";
  105. echo "<div class=\"field-input\">";
  106. echo "<select id=\"subsite-selector\" name=\"subsite\">";
  107.  
  108. foreach (self::$subsites as $site) {
  109. if (AdminPerms::canAccessSubsite($site['id'])) {
  110. $site_name_html = Enc::html($site['name']);
  111.  
  112. // It's a shared-content subsite, they can't be edited
  113. if ($site['content_id'] != 0) {
  114. $shared_name_html = 'another subsite';
  115. foreach (self::$subsites as $ss) {
  116. if ($ss['id'] == $site['content_id']) { $shared_name_html = Enc::html($ss['name']); break; }
  117. }
  118.  
  119. echo "<option value=\"\">{$site_name_html} (content from {$shared_name_html})</option>\n";
  120. continue;
  121. }
  122.  
  123. // Regular subsite with on-state
  124. if ($site['id'] == $selected_id) {
  125. echo "<option value=\"\" selected>Editing {$site_name_html}</option>\n";
  126. } else {
  127. echo "<option value=\"{$site['id']}\">{$site_name_html}</option>\n";
  128. }
  129. }
  130. }
  131.  
  132. echo "</select>";
  133. echo "</div>";
  134. echo "</div>";
  135. echo '</form>';
  136. }
  137.  
  138.  
  139. /**
  140.   * Returns the id of the first subsite in the list (alphabetical) that the user is able to access.
  141.   * Returns null if there are no subsites the user can access.
  142.   **/
  143. public static function getFirstAccessable()
  144. {
  145. self::loadSubsites();
  146.  
  147. foreach (self::$subsites as $sub) {
  148. if (AdminPerms::canAccessSubsite($sub['id'])) return $sub['id'];
  149. }
  150.  
  151. return null;
  152. }
  153.  
  154.  
  155. /**
  156.   * Returns the name of the specified subsite
  157.   **/
  158. public static function getName($id)
  159. {
  160. self::loadSubsites();
  161.  
  162. if (!isset(self::$subsites[$id])) {
  163. throw new InvalidArgumentException("Subsite #{$id} not found");
  164. }
  165.  
  166. return self::$subsites[$id]['name'];
  167. }
  168.  
  169. /**
  170.   * Returns the name of the specified subsite
  171.   **/
  172. public static function getCode($id)
  173. {
  174. self::loadSubsites();
  175.  
  176. if (!isset(self::$subsites[$id])) {
  177. throw new InvalidArgumentException("Subsite #{$id} not found");
  178. }
  179.  
  180. return self::$subsites[$id]['code'];
  181. }
  182.  
  183.  
  184. /**
  185.   * For a given list of domains, determine the best match based on the current HTTP_HOST
  186.   *
  187.   * If there is an exact match, it is used
  188.   * If there is an ends-with match, it is used
  189.   * Failing all that, the first domain in the list is used
  190.   **/
  191. static private function determineBestDomain($domains)
  192. {
  193. if (empty($_SERVER['HTTP_HOST'])) return $domains[0];
  194.  
  195. foreach ($domains as $d) {
  196. if ($d === $_SERVER['HTTP_HOST']) return $d;
  197. }
  198.  
  199. foreach ($domains as $d) {
  200. if (strrpos($d, $_SERVER['HTTP_HOST']) === strlen($d) - strlen($_SERVER['HTTP_HOST'])) return $d;
  201. }
  202.  
  203. return $domains[0];
  204. }
  205.  
  206.  
  207. /**
  208.   * Returns the abs root (including protocol and server name) of the specified subsite
  209.   * Falls back to the current root if no abs root can be found in the database
  210.   * @param int $id The subsite ID, e.g. SubsiteSelector::$subsite_id
  211.   * @param string $protocol The protocol to use for the link. Specifying null (the default)
  212.   * will cause the protocol to be automatically determined based on the request.
  213.   * @return string An absolute URL including protocol
  214.   **/
  215. public static function getAbsRoot($id, $protocol = null)
  216. {
  217. self::loadSubsites();
  218.  
  219. if (!isset(self::$subsites[$id])) {
  220. throw new InvalidArgumentException('Subsite not found');
  221. }
  222.  
  223. if (count(self::$subsites[$id]['cond_domains'])) {
  224. $domain = self::determineBestDomain(self::$subsites[$id]['cond_domains']);
  225. } else {
  226. $domain = $_SERVER['HTTP_HOST'];
  227. }
  228.  
  229. if (!$protocol) {
  230. if (PHP_SAPI != 'cli') {
  231. $protocol = Request::protocol();
  232. } else {
  233. $protocol = 'http';
  234. }
  235. }
  236.  
  237. if (!empty(self::$subsites[$id]['cond_directory'])) {
  238. $path = rtrim(self::$subsites[$id]['cond_directory'], '/') . '/';
  239. } else {
  240. $path = '';
  241. }
  242.  
  243. return $protocol . '://' . $domain . Kohana::config('config.site_domain') . $path;
  244. }
  245.  
  246.  
  247. /**
  248.   * Returns the abs root (including protocol and server name) of the current admin subsite
  249.   **/
  250. public static function getAbsRootAdmin()
  251. {
  252. AdminAuth::checkLogin();
  253. return self::getAbsRoot(@$_SESSION['admin']['active_subsite']);
  254. }
  255.  
  256.  
  257. /**
  258.   * Get a config value for a given subsite
  259.   *
  260.   * Use this instead of Kohana::config, if you need to target a specific subsite.
  261.   * It always looks in the "sprout" config file, doesn't support other files.
  262.   *
  263.   * @param string $key Configuration key
  264.   * @param int $subsite_id Subsite to get config value for
  265.   * @return mixed Configuration value
  266.   **/
  267. public static function getConfig($key, $subsite_id)
  268. {
  269. $code = self::getCode($subsite_id);
  270.  
  271. if (!$code) {
  272. throw new Exception('Subsite #'. $subsite_id . ' does not have a valid code specified');
  273. }
  274.  
  275. $configuration = self::loadConfig($code);
  276. return @$configuration[$key];
  277. }
  278.  
  279.  
  280. /**
  281.   * Admin version of the getConfig function, uses the currently active subsite
  282.   **/
  283. public static function getConfigAdmin($key)
  284. {
  285. AdminAuth::checkLogin();
  286. return self::getConfig($key, @$_SESSION['admin']['active_subsite']);
  287. }
  288.  
  289.  
  290. /**
  291.   * Load the configuration for a subsite code.
  292.   * It will only be loaded once per request - it gets cached in a static var
  293.   *
  294.   * @param string $subsite_code The subsite code to load and return config for
  295.   * @param array Configuration for that subsite
  296.   **/
  297. public static function loadConfig($subsite_code)
  298. {
  299. if (isset(self::$configs[$subsite_code])) {
  300. return self::$configs[$subsite_code];
  301. }
  302.  
  303. if (! file_exists(DOCROOT . 'skin/' . $subsite_code . '/')) {
  304. throw new Exception('Invalid subsite "' . $subsite_code . '"; skin directory not found.');
  305. }
  306.  
  307. $files = array(
  308. DOCROOT . 'skin/' . $subsite_code . '/config/sprout.php',
  309. );
  310.  
  311. $configuration = array();
  312. foreach ($files as $file) {
  313. if (file_exists($file)) {
  314. include $file;
  315. }
  316.  
  317. if (isset($config) AND is_array($config)) {
  318. $configuration = array_merge($configuration, $config);
  319. }
  320. }
  321.  
  322. self::$configs[$subsite_code] = $configuration;
  323.  
  324. return $configuration;
  325. }
  326.  
  327.  
  328. /**
  329.   * Gets the list of subsite codes which are available, by reading the appropriate directory
  330.   * @return array
  331.   */
  332. public static function getCodes()
  333. {
  334. $codes = [];
  335. $skin_dir = DOCROOT . 'skin' . DIRECTORY_SEPARATOR;
  336. $files = scandir($skin_dir);
  337. foreach ($files as $file) {
  338. if ($file[0] == '.') continue;
  339. if ($file == 'unavailable') continue;
  340. if (!is_dir($skin_dir . $file)) continue;
  341. $codes[$file] = $file;
  342. }
  343. return $codes;
  344. }
  345.  
  346. }
  347.  
  348.