From e3d28c747e2da24292705080ccc84f29e2b44f12 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Tue, 15 Sep 2015 15:58:17 -0700 Subject: [PATCH] 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. --- CRM/Core/Config/Defaults.php | 117 --------- CRM/Core/Config/MagicMerge.php | 243 +++++++++--------- CRM/Core/Config/Runtime.php | 25 -- CRM/Utils/File.php | 6 +- CRM/Utils/Rule.php | 7 +- Civi.php | 20 ++ Civi/Core/Paths.php | 155 +++++++++++ Civi/Core/SettingsBag.php | 116 --------- .../phpunit/Civi/Core/SettingsManagerTest.php | 38 --- 9 files changed, 308 insertions(+), 419 deletions(-) create mode 100644 Civi/Core/Paths.php diff --git a/CRM/Core/Config/Defaults.php b/CRM/Core/Config/Defaults.php index a89db3a5d9..af90e57943 100644 --- a/CRM/Core/Config/Defaults.php +++ b/CRM/Core/Config/Defaults.php @@ -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); - } - } diff --git a/CRM/Core/Config/MagicMerge.php b/CRM/Core/Config/MagicMerge.php index 927ac69c5d..377c035f21 100644 --- a/CRM/Core/Config/MagicMerge.php +++ b/CRM/Core/Config/MagicMerge.php @@ -68,117 +68,105 @@ class CRM_Core_Config_MagicMerge { * @return array */ public static function getPropertyMap() { + // Each mapping: $propertyName => Array(0 => $type, 1 => $foreignName|NULL, ...). + // If $foreignName is omitted/null, then it's assumed to match the $propertyName. + // Other parameters may be specified, depending on the type. return array( - 'backtrace' => array('setting', 'backtrace'), - 'countryLimit' => array('setting', 'countryLimit'), - 'dashboardCacheTimeout' => array('setting', 'dashboardCacheTimeout'), - 'dateInputFormat' => array('setting', 'dateInputFormat'), - 'dateformatDatetime' => array('setting', 'dateformatDatetime'), - 'dateformatFull' => array('setting', 'dateformatFull'), - 'dateformatPartial' => array('setting', 'dateformatPartial'), - 'dateformatTime' => array('setting', 'dateformatTime'), - 'dateformatYear' => array('setting', 'dateformatYear'), + 'backtrace' => array('setting'), + 'countryLimit' => array('setting'), + 'dashboardCacheTimeout' => array('setting'), + 'dateInputFormat' => array('setting'), + 'dateformatDatetime' => array('setting'), + 'dateformatFull' => array('setting'), + 'dateformatPartial' => array('setting'), + 'dateformatTime' => array('setting'), + 'dateformatYear' => array('setting'), 'debug' => array('setting', 'debug_enabled'), // renamed. - 'defaultContactCountry' => array('setting', 'defaultContactCountry'), - 'defaultContactStateProvince' => array('setting', 'defaultContactStateProvince'), - 'defaultCurrency' => array('setting', 'defaultCurrency'), - 'defaultSearchProfileID' => array('setting', 'defaultSearchProfileID'), - 'doNotAttachPDFReceipt' => array('setting', 'doNotAttachPDFReceipt'), - 'empoweredBy' => array('setting', 'empoweredBy'), + 'defaultContactCountry' => array('setting'), + 'defaultContactStateProvince' => array('setting'), + 'defaultCurrency' => array('setting'), + 'defaultSearchProfileID' => array('setting'), + 'doNotAttachPDFReceipt' => array('setting'), + 'empoweredBy' => array('setting'), 'enableComponents' => array('setting', 'enable_components'), // renamed. - 'enableSSL' => array('setting', 'enableSSL'), - 'fatalErrorHandler' => array('setting', 'fatalErrorHandler'), - 'fieldSeparator' => array('setting', 'fieldSeparator'), - 'fiscalYearStart' => array('setting', 'fiscalYearStart'), - 'geoAPIKey' => array('setting', 'geoAPIKey'), - 'geoProvider' => array('setting', 'geoProvider'), - 'includeAlphabeticalPager' => array('setting', 'includeAlphabeticalPager'), - 'includeEmailInName' => array('setting', 'includeEmailInName'), - 'includeNickNameInName' => array('setting', 'includeNickNameInName'), - 'includeOrderByClause' => array('setting', 'includeOrderByClause'), - 'includeWildCardInName' => array('setting', 'includeWildCardInName'), - 'inheritLocale' => array('setting', 'inheritLocale'), - 'languageLimit' => array('setting', 'languageLimit'), - 'lcMessages' => array('setting', 'lcMessages'), - 'legacyEncoding' => array('setting', 'legacyEncoding'), - 'logging' => array('setting', 'logging'), - 'mailThrottleTime' => array('setting', 'mailThrottleTime'), - 'mailerBatchLimit' => array('setting', 'mailerBatchLimit'), - 'mailerJobSize' => array('setting', 'mailerJobSize'), - 'mailerJobsMax' => array('setting', 'mailerJobsMax'), - 'mapAPIKey' => array('setting', 'mapAPIKey'), - 'mapProvider' => array('setting', 'mapProvider'), - 'maxFileSize' => array('setting', 'maxFileSize'), + 'enableSSL' => array('setting'), + 'fatalErrorHandler' => array('setting'), + 'fieldSeparator' => array('setting'), + 'fiscalYearStart' => array('setting'), + 'geoAPIKey' => array('setting'), + 'geoProvider' => array('setting'), + 'includeAlphabeticalPager' => array('setting'), + 'includeEmailInName' => array('setting'), + 'includeNickNameInName' => array('setting'), + 'includeOrderByClause' => array('setting'), + 'includeWildCardInName' => array('setting'), + 'inheritLocale' => array('setting'), + 'languageLimit' => array('setting'), + 'lcMessages' => array('setting'), + 'legacyEncoding' => array('setting'), + 'logging' => array('setting'), + 'mailThrottleTime' => array('setting'), + 'mailerBatchLimit' => array('setting'), + 'mailerJobSize' => array('setting'), + 'mailerJobsMax' => array('setting'), + 'mapAPIKey' => array('setting'), + 'mapProvider' => array('setting'), + 'maxFileSize' => array('setting'), 'maxAttachments' => array('setting', 'max_attachments'), // renamed. - 'monetaryDecimalPoint' => array('setting', 'monetaryDecimalPoint'), - 'monetaryThousandSeparator' => array('setting', 'monetaryThousandSeparator'), - 'moneyformat' => array('setting', 'moneyformat'), - 'moneyvalueformat' => array('setting', 'moneyvalueformat'), - 'provinceLimit' => array('setting', 'provinceLimit'), - 'recaptchaOptions' => array('setting', 'recaptchaOptions'), - 'recaptchaPublicKey' => array('setting', 'recaptchaPublicKey'), - 'recaptchaPrivateKey' => array('setting', 'recaptchaPrivateKey'), - 'secondDegRelPermissions' => array('setting', 'secondDegRelPermissions'), - 'smartGroupCacheTimeout' => array('setting', 'smartGroupCacheTimeout'), - 'timeInputFormat' => array('setting', 'timeInputFormat'), - 'userFrameworkLogging' => array('setting', 'userFrameworkLogging'), - 'userFrameworkUsersTableName' => array('setting', 'userFrameworkUsersTableName'), - 'verpSeparator' => array('setting', 'verpSeparator'), - 'wkhtmltopdfPath' => array('setting', 'wkhtmltopdfPath'), - 'wpBasePage' => array('setting', 'wpBasePage'), - 'wpLoadPhp' => array('setting', 'wpLoadPhp'), - - 'doNotResetCache' => array('local', 'doNotResetCache'), - 'inCiviCRM' => array('local', 'inCiviCRM'), - 'userFrameworkFrontend' => array('local', 'userFrameworkFrontend'), - 'initialized' => array('local', 'initialized'), - - 'dsn' => array('runtime', 'dsn'), - 'userFramework' => array('runtime', 'userFramework'), - 'userFrameworkBaseURL' => array('runtime', 'userFrameworkBaseURL'), - 'userFrameworkClass' => array('runtime', 'userFrameworkClass'), - 'userFrameworkDSN' => array('runtime', 'userFrameworkDSN'), + 'monetaryDecimalPoint' => array('setting'), + 'monetaryThousandSeparator' => array('setting'), + 'moneyformat' => array('setting'), + 'moneyvalueformat' => array('setting'), + 'provinceLimit' => array('setting'), + 'recaptchaOptions' => array('setting'), + 'recaptchaPublicKey' => array('setting'), + 'recaptchaPrivateKey' => array('setting'), + 'secondDegRelPermissions' => array('setting'), + 'smartGroupCacheTimeout' => array('setting'), + 'timeInputFormat' => array('setting'), + 'userFrameworkLogging' => array('setting'), + 'userFrameworkUsersTableName' => array('setting'), + 'verpSeparator' => array('setting'), + 'wkhtmltopdfPath' => array('setting'), + 'wpBasePage' => array('setting'), + 'wpLoadPhp' => array('setting'), + + 'doNotResetCache' => array('local'), + 'inCiviCRM' => array('local'), + 'userFrameworkFrontend' => array('local'), + 'initialized' => array('local'), + + 'dsn' => array('runtime'), + 'userFramework' => array('runtime'), + 'userFrameworkBaseURL' => array('runtime'), + 'userFrameworkClass' => array('runtime'), + 'userFrameworkDSN' => array('runtime'), 'useFrameworkRelativeBase' => array('runtime', 'useFrameworkRelativeBase'), - 'userFrameworkURLVar' => array('runtime', 'userFrameworkURLVar'), - 'userPermissionClass' => array('runtime', 'userPermissionClass'), - 'userPermissionTemp' => array('runtime', 'userPermissionTemp'), - 'userSystem' => array('runtime', 'userSystem'), - 'userHookClass' => array('runtime', 'userHookClass'), - 'cleanURL' => array('runtime', 'cleanURL'), - 'configAndLogDir' => array('runtime', 'configAndLogDir'), - 'templateCompileDir' => array('runtime', 'templateCompileDir'), - 'templateDir' => array('runtime', 'templateDir'), - - 'customFileUploadDir' => array('callback', 'CRM_Core_Config_Defaults', 'getCustomFileUploadDir', 'setPath', 'revert'), - 'customPHPPathDir' => array('callback', 'CRM_Core_Config_Defaults', 'getCustomPhpPathDir', 'setPath', 'revert'), - 'customTemplateDir' => array('callback', 'CRM_Core_Config_Defaults', 'getCustomTemplateDir', 'setPath', 'revert'), - 'extensionsDir' => array('callback', 'CRM_Core_Config_Defaults', 'getExtensionsDir', 'setPath', 'revert'), - 'imageUploadDir' => array('callback', 'CRM_Core_Config_Defaults', 'getImageUploadDir', 'setPath', 'revert'), - 'uploadDir' => array('callback', 'CRM_Core_Config_Defaults', 'getUploadDir', 'setPath'), - - 'customCSSURL' => array('callback', 'CRM_Core_Config_Defaults', 'getCustomCssUrl', 'setUrl', 'revert'), - 'extensionsURL' => array('callback', 'CRM_Core_Config_Defaults', 'getExtensionsUrl', 'setUrl', 'revert'), - 'imageUploadURL' => array('callback', 'CRM_Core_Config_Defaults', 'getImageUploadUrl', 'setUrl', 'revert'), - 'resourceBase' => array('callback', 'CRM_Core_Config_Defaults', 'getResourceBase', 'setUrl', 'revert'), - 'userFrameworkResourceURL' => array('callback', 'CRM_Core_Config_Defaults', 'getUserFrameworkResourceUrl', 'setUrl', 'revert'), + 'userFrameworkURLVar' => array('runtime'), + 'userPermissionClass' => array('runtime'), + 'userPermissionTemp' => array('runtime'), + 'userSystem' => array('runtime'), + 'userHookClass' => array('runtime'), + 'cleanURL' => array('runtime'), + 'configAndLogDir' => array('runtime'), + 'templateCompileDir' => array('runtime'), + 'templateDir' => array('runtime'), + + 'customFileUploadDir' => array('setting-path', NULL, '[civicrm.files]/custom/', array('mkdir', 'restrict')), + 'customPHPPathDir' => array('setting-path'), + 'customTemplateDir' => array('setting-path'), + 'extensionsDir' => array('setting-path'), + 'imageUploadDir' => array('setting-path', NULL, '[civicrm.files]/persist/contribute/', array('mkdir')), + 'uploadDir' => array('setting-path', NULL, '[civicrm.files]/upload/', array('mkdir', 'restrict')), + + 'customCSSURL' => array('setting-url-abs'), + 'extensionsURL' => array('setting-url-abs'), + 'imageUploadURL' => array('setting-url-abs', NULL, '[civicrm.files]/persist/contribute/'), + 'resourceBase' => array('setting-url-rel', 'userFrameworkResourceURL', '[civicrm]/.'), + 'userFrameworkResourceURL' => array('setting-url-abs', NULL, '[civicrm]/.'), 'geocodeMethod' => array('callback', 'CRM_Utils_Geocode', 'getProviderClass'), 'defaultCurrencySymbol' => array('callback', 'CRM_Core_Config_Defaults', 'getDefaultCurrencySymbol'), - //'customFileUploadDir' => array('runtime', 'customFileUploadDir'), - //'customPHPPathDir' => array('runtime', 'customPHPPathDir'), - //'customTemplateDir' => array('runtime', 'customTemplateDir'), - //'extensionsDir' => array('runtime', 'extensionsDir'), - //'imageUploadDir' => array('runtime', 'imageUploadDir'), - //'uploadDir' => array('runtime', 'uploadDir'), - // - //'customCSSURL' => array('runtime', 'customCSSURL'), - //'extensionsURL' => array('runtime', 'extensionsURL'), - //'imageUploadURL' => array('runtime', 'imageUploadURL'), - //'resourceBase' => array('runtime', 'resourceBase'), - //'userFrameworkResourceURL' => array('runtime', 'userFrameworkResourceURL'), - // - //'geocodeMethod' => array('runtime', 'geocodeMethod'), - //'defaultCurrencySymbol' => array('runtime', 'defaultCurrencySymbol'), ); } @@ -186,17 +174,47 @@ class CRM_Core_Config_MagicMerge { if (!isset($this->map[$k])) { throw new \CRM_Core_Exception("Cannot read unrecognized property CRM_Core_Config::\${$k}."); } - list ($type, $name) = $this->map[$k]; + + $type = $this->map[$k][0]; + $name = isset($this->map[$k][1]) ? $this->map[$k][1] : $k; switch ($type) { case 'setting': return $this->getSettings()->get($name); case 'setting-path': - return $this->getSettings()->getPath($name); + // Array(0 => $type, 1 => $setting, 2 => $default, 3 => $actions). + $value = $this->getSettings()->get($name); + if (empty($value) && isset($this->map[$k][2])) { + $value = $this->map[$k][2]; + } + $value = Civi::paths()->getPath($value); + if ($value) { + $value = CRM_Utils_File::addTrailingSlash($value); + if (isset($this->map[$k][3]) && in_array('mkdir', $this->map[$k][3])) { + CRM_Utils_File::createDir($value); + } + if (isset($this->map[$k][3]) && in_array('restrict', $this->map[$k][3])) { + CRM_Utils_File::restrictAccess($value); + } + } + return $value; + + case 'setting-url-abs': + // Array(0 => $type, 1 => $setting, 2 => $default). + $value = $this->getSettings()->get($name); + if (empty($value) && isset($this->map[$k][2])) { + $value = $this->map[$k][2]; + } + return Civi::paths()->getUrl($value, 'absolute'); - case 'setting-url': - return $this->getSettings()->getUrl($name, 'absolute'); + case 'setting-url-rel': + // Array(0 => $type, 1 => $setting, 2 => $default). + $value = $this->getSettings()->get($name); + if (empty($value) && isset($this->map[$k][2])) { + $value = $this->map[$k][2]; + } + return Civi::paths()->getUrl($value, 'relative'); case 'runtime': return $this->getRuntime()->{$name}; @@ -231,14 +249,6 @@ class CRM_Core_Config_MagicMerge { $this->getSettings()->set($name, $v); return; - case 'setting-path': - $this->getSettings()->setPath($name, $v); - return; - - case 'setting-url': - $this->getSettings()->setUrl($name, $v); - return; - case 'runtime': $this->getRuntime()->{$name} = $v; return; @@ -274,7 +284,8 @@ class CRM_Core_Config_MagicMerge { switch ($type) { case 'setting': case 'setting-path': - case 'setting-url': + case 'setting-url-abs': + case 'setting-url-rel': $this->getSettings()->revert($k); return; diff --git a/CRM/Core/Config/Runtime.php b/CRM/Core/Config/Runtime.php index bd68943a2c..7382811a1c 100644 --- a/CRM/Core/Config/Runtime.php +++ b/CRM/Core/Config/Runtime.php @@ -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(); diff --git a/CRM/Utils/File.php b/CRM/Utils/File.php index 7da2868bbc..73d6c46b3b 100644 --- a/CRM/Utils/File.php +++ b/CRM/Utils/File.php @@ -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; } diff --git a/CRM/Utils/Rule.php b/CRM/Utils/Rule.php index 50a687e0ca..c4c0be33b6 100644 --- a/CRM/Utils/Rule.php +++ b/CRM/Utils/Rule.php @@ -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)); } /** diff --git a/Civi.php b/Civi.php index c88efd0268..01e1fbbc4d 100644 --- a/Civi.php +++ b/Civi.php @@ -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. * diff --git a/Civi/Core/Paths.php b/Civi/Core/Paths.php new file mode 100644 index 0000000000..a9d4d6e165 --- /dev/null +++ b/Civi/Core/Paths.php @@ -0,0 +1,155 @@ + 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; + } + +} diff --git a/Civi/Core/SettingsBag.php b/Civi/Core/SettingsBag.php index e0709e3b0b..ab54ebdebc 100644 --- a/Civi/Core/SettingsBag.php +++ b/Civi/Core/SettingsBag.php @@ -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; - } - } - } diff --git a/tests/phpunit/Civi/Core/SettingsManagerTest.php b/tests/phpunit/Civi/Core/SettingsManagerTest.php index c117b8c4e8..b92f597197 100644 --- a/tests/phpunit/Civi/Core/SettingsManagerTest.php +++ b/tests/phpunit/Civi/Core/SettingsManagerTest.php @@ -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 */ -- 2.25.1