Commit e3d28c74 authored by totten's avatar totten

Civi::paths() - Decouple settings from paths. Allow path vars.

There has been a policy underwhich paths and URLs are assumed to be relative
to different points (urls => webroot; paths => sites/*/files/civicrm).  For
some settings, this is fairly confusing.  The policy had been encoded in
`SettingsBag::getPath` and `SettingsBag::getUrl`.  It's particularly
confusing because (sometimes) the default value of a specific property isn't
really aligned with the policy.

This revision attempts to cleanup in two ways:

 1. It removes policy from `SettingsBag` and puts it in `Civi\Core\Paths`.
 2. It makes the policy less important by allowing variables in the paths.
   * `[civicrm.files]/upload` might evaluate to `/var/www/sites/default/files/civicrm/upload`
   * `[cms.root]/myuploads` might evaluate to `/var/www/myuploads`

The revision also updates MagicMerge to store various path and URL policies
in `getPropertyMap()` rather than adhoc callbacks.
parent 90777b73
......@@ -42,126 +42,9 @@
*/
class CRM_Core_Config_Defaults {
/**
* Set the default values.
* in an empty db, also called when setting component using GUI
*
* @param array $defaults
* Associated array of form elements.
* @param bool $formMode
* this variable is set true for GUI
* mode (eg: Global setting >> Components)
*
*/
public static function setValues(&$defaults, $formMode = FALSE) {
}
public static function getCustomCssUrl($k = NULL) {
return Civi::settings()->getUrl('customCSSURL', 'absolute');
}
public static function getCustomFileUploadDir($k = NULL) {
$settings = Civi::settings();
$value = $settings->getPath('customFileUploadDir');
if (empty($value)) {
$defaultFileStorage = CRM_Core_Config::singleton()->userSystem->getDefaultFileStorage();
$value = $settings->filterPath($defaultFileStorage['path'] . "custom/");
}
$value = CRM_Utils_File::addTrailingSlash($value);
CRM_Utils_File::createDir($value);
CRM_Utils_File::restrictAccess($value);
return $value;
}
public static function getCustomPhpPathDir($k = NULL) {
return Civi::settings()->getPath('customPHPPathDir');
}
public static function getCustomTemplateDir($k = NULL) {
return Civi::settings()->getPath('customTemplateDir');
}
public static function getExtensionsUrl($k = NULL) {
return Civi::settings()->getUrl('extensionsURL', 'absolute');
}
public static function getExtensionsDir($k = NULL) {
return Civi::settings()->getPath('extensionsDir');
}
public static function getImageUploadDir($k = NULL) {
$settings = Civi::settings();
$value = $settings->getPath('imageUploadDir');
if (empty($value)) {
$defaultFileStorage = CRM_Core_Config::singleton()->userSystem->getDefaultFileStorage();
$value = $settings->filterPath($defaultFileStorage['path'] . "persist/contribute/");
}
$value = CRM_Utils_File::addTrailingSlash($value);
CRM_Utils_File::createDir($value);
return $value;
}
public static function getImageUploadUrl($k = NULL) {
$settings = Civi::settings();
$imageUploadURL = $settings->getUrl('imageUploadURL', 'absolute');
if (empty($imageUploadURL)) {
$defaultFileStorage = CRM_Core_Config::singleton()->userSystem->getDefaultFileStorage();
$imageUploadURL = $settings->filterUrl($defaultFileStorage['url'] . 'persist/contribute/', 'absolute');
}
return $imageUploadURL;
}
public static function getUploadDir($k = NULL) {
$settings = Civi::settings();
$value = $settings->getPath('uploadDir');
if (empty($value)) {
$defaultFileStorage = CRM_Core_Config::singleton()->userSystem->getDefaultFileStorage();
$value = $settings->filterPath($defaultFileStorage['path'] . "upload/");
}
$value = CRM_Utils_File::addTrailingSlash($value);
CRM_Utils_File::createDir($value);
CRM_Utils_File::restrictAccess($value);
return $value;
}
public static function getUserFrameworkResourceUrl($k = NULL) {
$settings = Civi::settings();
$url = $settings->getUrl('userFrameworkResourceURL', 'absolute');
if (empty($url)) {
$config = CRM_Core_Config::singleton();
$civiSource = $config->userSystem->getCiviSourceStorage();
$url = $settings->filterUrl($civiSource['url'], 'absolute');
}
return $url;
}
public static function getResourceBase($k = NULL) {
$settings = Civi::settings();
$url = $settings->getUrl('userFrameworkResourceURL', 'relative');
if (empty($url)) {
$config = CRM_Core_Config::singleton();
$civiSource = $config->userSystem->getCiviSourceStorage();
$url = $settings->filterUrl($civiSource['url'], 'relative');
}
return $url;
}
public static function getDefaultCurrencySymbol($k = NULL) {
$config = CRM_Core_Config::singleton();
return $config->defaultCurrencySymbol(Civi::settings()->get('defaultCurrency'));
}
public static function setPath($key, $value) {
Civi::settings()->setPath($key, $value);
}
public static function setUrl($key, $value) {
Civi::settings()->setPath($key, $value);
}
public static function revert($key) {
Civi::settings()->revert($key);
}
}
This diff is collapsed.
......@@ -103,10 +103,6 @@ class CRM_Core_Config_Runtime {
*/
public $templateDir;
//public $customFileUploadDir, $customPHPPathDir, $customTemplateDir, $extensionsDir, $imageUploadDir, $resourceBase, $uploadDir;
//public $userFrameworkResourceURL, $customCSSURL, $extensionsURL, $imageUploadURL;
//public $geocodeMethod, $defaultCurrencySymbol;
/**
* @param bool $loadFromDB
*/
......@@ -138,27 +134,6 @@ class CRM_Core_Config_Runtime {
$this->setUserFramework(CIVICRM_UF);
$this->templateDir = array(dirname(dirname(dirname(__DIR__))) . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR);
//if ($loadFromDB) {
// // //$this->enableComponents = \Civi::settings()->get('enable_components');
//
// $this->customFileUploadDir = CRM_Core_Config_Defaults::getCustomFileUploadDir();
// $this->customPHPPathDir = CRM_Core_Config_Defaults::getCustomPhpPathDir();
// $this->customTemplateDir = CRM_Core_Config_Defaults::getCustomTemplateDir();
// $this->extensionsDir = CRM_Core_Config_Defaults::getExtensionsDir();
// $this->imageUploadDir = CRM_Core_Config_Defaults::getImageUploadDir();
// $this->uploadDir = CRM_Core_Config_Defaults::getUploadDir();
//
// $this->resourceBase = CRM_Core_Config_Defaults::getResourceBase();
// $this->useFrameworkRelativeBase = CRM_Core_Config_Defaults::getUserFrameworkRelativeBase();
//
// $this->userFrameworkResourceURL = CRM_Core_Config_Defaults::getUserFrameworkResourceUrl();
// $this->customCSSURL = CRM_Core_Config_Defaults::getCustomCssUrl();
// $this->extensionsURL = CRM_Core_Config_Defaults::getExtensionsUrl();
// $this->imageUploadURL = CRM_Core_Config_Defaults::getImageUploadUrl();
//
// $this->geocodeMethod = CRM_Utils_Geocode::getProviderClass();
// $this->defaultCurrencySymbol = CRM_Core_Config_Defaults::getDefaultCurrencySymbol();
//}
if (CRM_Utils_System::isSSL()) {
$this->userSystem->mapConfigToSSL();
......
......@@ -569,10 +569,12 @@ HTACCESS;
/**
* @param $directory
* @param string|NULL $basePath
* The base path when evaluating relative paths. Should include trailing slash.
*
* @return string
*/
public static function absoluteDirectory($directory) {
public static function absoluteDirectory($directory, $basePath = NULL) {
// check if directory is already absolute, if so return immediately
// Note: Windows PHP accepts any mix of "/" or "\", so "C:\htdocs" or "C:/htdocs" would be a valid absolute path
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' && preg_match(';^[a-zA-Z]:[/\\\\];', $directory)) {
......@@ -585,7 +587,7 @@ HTACCESS;
}
// make everything absolute from the baseFilePath
$basePath = self::baseFilePath();
$basePath = ($basePath === NULL) ? self::baseFilePath() : $basePath;
return $basePath . $directory;
}
......
......@@ -169,10 +169,7 @@ class CRM_Utils_Rule {
if (empty($url)) {
return TRUE;
}
if (!preg_match('/^([a-z]+):/', $url)) {
// allow relative URL's (CRM-15598)
$url = 'http://' . $_SERVER['HTTP_HOST'] . '/' . ltrim($url, '/');
}
$url = Civi::paths()->getUrl($url, 'absolute');
return (bool) filter_var($url, FILTER_VALIDATE_URL);
}
......@@ -733,7 +730,7 @@ class CRM_Utils_Rule {
* @return bool
*/
public static function settingPath($path) {
return is_dir(\CRM_Utils_File::absoluteDirectory($path));
return is_dir(Civi::paths()->getPath($path));
}
/**
......
......@@ -61,6 +61,19 @@ class Civi {
return Civi\Core\Container::singleton()->get('psr_log');
}
/**
* Obtain the core file/path mapper.
*
* @return \Civi\Core\Paths
*/
public static function paths() {
// Paths must be available before container can boot.
if (!isset(Civi::$statics[__CLASS__]['paths'])) {
Civi::$statics[__CLASS__]['paths'] = new \Civi\Core\Paths();
}
return Civi::$statics[__CLASS__]['paths'];
}
/**
* Fetch a service from the container.
*
......@@ -81,6 +94,13 @@ class Civi {
self::$statics = array();
}
/**
* @return CRM_Core_Resources
*/
public static function resources() {
return CRM_Core_Resources::singleton();
}
/**
* Obtain the domain settings.
*
......
<?php
namespace Civi\Core;
/**
* Class Paths
* @package Civi\Core
*
* This paths class translates path-expressions into local file paths and
* URLs. Path-expressions may take a few forms:
*
* - Paths and URLs may use a variable prefix. For example, '[civicrm.files]/upload'
* - Paths and URLS may be absolute.
* - Paths may be relative (base dir: [civicrm.files]).
* - URLs may be relative (base dir: [cms.root]).
*/
class Paths {
const DEFAULT_URL = 'cms.root';
const DEFAULT_PATH = 'civicrm.files';
/**
* @var array
* Array(string $name => array(url => $, path => $)).
*/
private $containers = array();
protected $containerFactory = array();
public function __construct() {
$this
->register('civicrm', function () {
return \CRM_Core_Config::singleton()->userSystem->getCiviSourceStorage();
})
->register('civicrm.root', function () {
return \CRM_Core_Config::singleton()->userSystem->getCiviSourceStorage();
})
->register('civicrm.files', function () {
return \CRM_Core_Config::singleton()->userSystem->getDefaultFileStorage();
})
->register('cms', function () {
return array(
'path' => \CRM_Core_Config::singleton()->userSystem->cmsRootPath(),
'url' => \CRM_Utils_System::baseCMSURL(),
);
})
->register('cms.root', function () {
return array(
'path' => \CRM_Core_Config::singleton()->userSystem->cmsRootPath(),
// Misleading: this *removes* the language part of the URL, producing a pristine base URL.
'url' => \CRM_Utils_System::languageNegotiationURL(\CRM_Utils_System::baseCMSURL(), FALSE, TRUE),
);
});
}
/**
* Register a new URL/file path mapping.
*
* @param string $name
* The name of the container.
* @param callable $factory
* Function which returns an array with keys:
* - path: string.
* - url: string.
* @return $this
*/
public function register($name, $factory) {
$this->containerFactory[$name] = $factory;
return $this;
}
protected function getContainerAttr($name, $attr) {
if (!isset($this->containers[$name])) {
$this->containers[$name] = call_user_func($this->containerFactory[$name]);
}
if (!isset($this->containers[$name][$attr])) {
throw new \RuntimeException("Cannot resolve path using \"$name.$attr\"");
}
return $this->containers[$name][$attr];
}
/**
* Determine the absolute path to a file, given that the file is most likely
* in a given particular container.
*
* @param string $value
* The file path (which is probably relative to $container).
* Use "." to reference to container root.
* Values may explicitly specify the a container, e.g. "[civicrm.files]/upload".
* @return mixed|string
*/
public function getPath($value) {
$defaultContainer = self::DEFAULT_PATH;
if ($value && $value{0} == '[' && preg_match(';^\[([a-zA-Z0-9\._]+)\]/(.*);', $value, $matches)) {
$defaultContainer = $matches[1];
$value = $matches[2];
}
if (empty($value)) {
return FALSE;
}
if ($value === '.') {
$value = '';
}
return \CRM_Utils_File::absoluteDirectory($value, $this->getContainerAttr($defaultContainer, 'path'));
}
/**
* Determine the absolute URL to a file, given that the file is most likely
* in a given particular container.
*
* @param string $value
* The file path (which is probably relative to $container).
* Values may explicitly specify the a container, e.g. "[civicrm.files]/upload".
* @param string $preferFormat
* The preferred format ('absolute', 'relative').
* The result data may not meet the preference -- if the setting
* refers to an external domain, then the result will be
* absolute (regardless of preference).
* @param bool|NULL $ssl
* NULL to autodetect. TRUE to force to SSL.
* @return mixed|string
*/
public function getUrl($value, $preferFormat = 'relative', $ssl = NULL) {
$defaultContainer = self::DEFAULT_URL;
if ($value && $value{0} == '[' && preg_match(';^\[([a-zA-Z0-9\._]+)\]/(.*);', $value, $matches)) {
$defaultContainer = $matches[1];
$value = $matches[2];
}
if (empty($value)) {
return FALSE;
}
if ($value === '.') {
$value = '';
}
if (substr($value, 0, 4) == 'http') {
return $value;
}
$value = $this->getContainerAttr($defaultContainer, 'url') . $value;
if ($preferFormat === 'relative') {
$parsed = parse_url($value);
if (isset($_SERVER['HTTP_HOST']) && isset($parsed['host']) && $_SERVER['HTTP_HOST'] == $parsed['host']) {
$value = $parsed['path'];
}
}
if ($ssl || ($ssl === NULL && \CRM_Utils_System::isSSL())) {
$value = str_replace('http://', 'https://', $value);
}
return $value;
}
}
......@@ -181,42 +181,6 @@ class SettingsBag {
return isset($all[$key]) ? $all[$key] : NULL;
}
/**
* Get the value of a setting, formatted as a path.
*
* @param string $key
* @return string|NULL
* Absolute path.
*/
public function getPath($key) {
if (!isset($this->filteredValues[$key])) {
$this->filteredValues[$key] = $this->filterPath($this->get($key));
}
return $this->filteredValues[$key];
}
/**
* Get the value of a setting, formatted as a URL.
*
* @param string $key
* @param bool $preferFormat
* The preferred format ('absolute', 'relative').
* The result data may not meet the preference -- if the setting
* refers to an external domain, then the result will be
* absolute (regardless of preference).
* @param bool|NULL $ssl
* NULL to autodetect. TRUE to force to SSL.
* @return string|NULL
* URL.
*/
public function getUrl($key, $preferFormat, $ssl = NULL) {
if (!isset($this->filteredValues[$key][$preferFormat][$ssl])) {
$value = $this->filterUrl($this->get($key), $preferFormat, $ssl);
$this->filteredValues[$key][$preferFormat][$ssl] = $value;
}
return $this->filteredValues[$key][$preferFormat][$ssl];
}
/**
* Determine the default value of a setting.
*
......@@ -296,30 +260,6 @@ class SettingsBag {
return $this;
}
/**
* @param string $key
* The simple name of the setting.
* @param string $value
* Absolute path.
* @return $this
*/
public function setPath($key, $value) {
$this->set($key, \CRM_Utils_File::relativeDirectory($value));
return $this;
}
/**
* @param string $key
* The simple name of the setting.
* @param string $value
* Absolute URL.
* @return $this
*/
public function setUrl($key, $value) {
$this->set($key, \CRM_Utils_System::relativeURL($value));
return $this;
}
/**
* @return \CRM_Utils_SQL_Select
*/
......@@ -428,60 +368,4 @@ class SettingsBag {
$dao->free();
}
/**
* Filter a URL, the same way that it would be if it were read from settings.
*
* This converts an expression like "persist/contribute" to an absolute path
* like "http://example.org/sites/default/files/civicrm/persist/contribute".
*
* @param string $value
* @param string $preferFormat
* The preferred format ('absolute', 'relative').
* The result data may not meet the preference -- if the setting
* refers to an external domain, then the result will be
* absolute (regardless of preference).
* @param bool|NULL $ssl
* NULL to autodetect. TRUE to force to SSL.
* @return mixed|string
*/
public function filterUrl($value, $preferFormat, $ssl = NULL) {
if ($value) {
$value = \CRM_Utils_System::absoluteURL($value, TRUE);
}
if ($preferFormat === 'relative' && $value) {
$parsed = parse_url($value);
if (isset($_SERVER['HTTP_HOST']) && isset($parsed['host']) && $_SERVER['HTTP_HOST'] == $parsed['host']) {
$value = $parsed['path'];
}
}
if ($value) {
if ($ssl || ($ssl === NULL && \CRM_Utils_System::isSSL())) {
$value = str_replace('http://', 'https://', $value);
}
}
return $value;
}
/**
* Filter a path, the same way that it would be it it were from the settings.
*
* This converts an expression like "persist/contribute" to an absolute path
* like "/var/www/drupal/sites/default/files/civicrm/persist/contribute".
*
* @param string $value
* The value like "persist/contribute".
* @return bool|string
* The value like "/var/www/drupal/sites/default/files/civicrm/persist/contribute",
* or FALSE if empty.
*/
public function filterPath($value) {
if ($value) {
return \CRM_Utils_File::absoluteDirectory($value);
}
else {
return FALSE;
}
}
}
......@@ -129,44 +129,6 @@ class SettingsManagerTest extends \CiviUnitTestCase {
$this->assertEquals('from contact', $contactSettings->get('monkeywrench'));
}
public function testPaths() {
$domain = \CRM_Core_DAO::createTestObject('CRM_Core_DAO_Domain');
$manager = $this->createManager()->useDefaults();
$settings = $manager->getBagByDomain($domain->id);
$this->assertEquals('foo', $settings->get('myrelpath'));
$this->assertRegExp(':/.+/foo$:', $settings->getPath('myrelpath'));
$settings->setPath('myrelpath', 'foo/sub');
$this->assertEquals('foo/sub', $settings->get('myrelpath'));
$this->assertRegExp(':/.+/foo/sub$:', $settings->getPath('myrelpath'));
$this->assertEquals('/tmp/bar', $settings->get('myabspath'));
$this->assertEquals('/tmp/bar', $settings->getPath('myabspath'));
$settings->setPath('myabspath', '/tmp/bar/whiz');
$this->assertEquals('/tmp/bar/whiz', $settings->get('myabspath'));
}
public function testUrl() {
$domain = \CRM_Core_DAO::createTestObject('CRM_Core_DAO_Domain');
$manager = $this->createManager()->useDefaults();
$settings = $manager->getBagByDomain($domain->id);
$this->assertEquals('sites/foo', $settings->get('myrelurl'));
$this->assertRegExp(';^http.*sites/foo$;', $settings->getUrl('myrelurl', 'absolute'));
$this->assertRegExp(';^https:.*sites/foo$;', $settings->getUrl('myrelurl', 'absolute', TRUE));
//$this->assertEquals('/sites/foo', $settings->getUrl('myrelurl', 'relative'));
$settings->setUrl('myrelurl', 'sites/foo/sub');
$this->assertEquals('sites/foo/sub', $settings->get('myrelurl'));
$this->assertRegExp(';^http.*sites/foo/sub$;', $settings->getUrl('myrelurl', 'absolute'));
//$this->assertEquals('/sites/foo/sub', $settings->getUrl('myrelurl', 'relative'));
$this->assertEquals('http://example.com/bar', $settings->get('myabsurl'));
$this->assertEquals('http://example.com/bar', $settings->getUrl('myabsurl', 'absolute'));
$settings->setUrl('myabsurl', 'http://example.com/whiz');
$this->assertEquals('http://example.com/whiz', $settings->get('myabsurl'));
$this->assertEquals('http://example.com/whiz', $settings->getUrl('myabsurl', 'absolute'));
}
/**
* @return SettingsManager
*/
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment