3 +--------------------------------------------------------------------+
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2020 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
30 * @copyright CiviCRM LLC (c) 2004-2020
34 * An extension container is a locally-accessible source tree which can be
35 * scanned for extensions.
37 class CRM_Extension_Container_Basic
implements CRM_Extension_Container_Interface
{
42 * Note: Treat as private. This is only public to facilitate debugging.
49 * Note: Treat as private. This is only public to facilitate debugging.
54 * @var CRM_Utils_Cache_Interface|NULL
56 * Note: Treat as private. This is only public to facilitate debugging.
61 * @var string the cache key used for any data stored by this container
63 * Note: Treat as private. This is only public to facilitate debugging.
68 * @var array($key => $relPath)
70 * Note: Treat as private. This is only public to facilitate debugging.
72 public $relPaths = FALSE;
75 * @var array($key => $relUrl)
77 * Derived from $relPaths. On Unix systems (where file-paths and
78 * URL-paths both use '/' separator), this isn't necessary. On Windows
79 * systems, this is derived from $relPaths.
81 * Note: Treat as private. This is only public to facilitate debugging.
83 public $relUrls = FALSE;
87 * Array(function(CRM_Extension_Info $info): bool)
88 * List of callables which determine whether an extension is visible.
89 * Each function returns TRUE if the extension should be visible.
91 protected $filters = [];
94 * @param string $baseDir
95 * Local path to the container.
96 * @param string $baseUrl
97 * Public URL of the container.
98 * @param CRM_Utils_Cache_Interface $cache
99 * Cache in which to store extension metadata.
100 * @param string $cacheKey
101 * Unique name for this container.
103 public function __construct($baseDir, $baseUrl, CRM_Utils_Cache_Interface
$cache = NULL, $cacheKey = NULL) {
104 $this->cache
= $cache;
105 $this->cacheKey
= $cacheKey;
106 $this->baseDir
= rtrim($baseDir, '/');
107 $this->baseUrl
= rtrim($baseUrl, '/');
115 public function checkRequirements() {
118 if (empty($this->baseDir
) ||
!is_dir($this->baseDir
)) {
120 'title' => ts('Invalid Base Directory'),
121 'message' => ts('An extension container has been defined with a blank directory.'),
124 if (empty($this->baseUrl
)) {
126 'title' => ts('Invalid Base URL'),
127 'message' => ts('An extension container has been defined with a blank URL.'),
139 public function getKeys() {
140 return array_keys($this->getRelPaths());
146 public function getPath($key) {
147 return $this->baseDir
. $this->getRelPath($key);
153 public function getResUrl($key) {
154 if (!$this->baseUrl
) {
155 CRM_Core_Session
::setStatus(
156 ts('Failed to determine URL for extension (%1). Please update <a href="%2">Resource URLs</a>.',
159 2 => CRM_Utils_System
::url('civicrm/admin/setting/url', 'reset=1'),
164 return $this->baseUrl
. $this->getRelUrl($key);
170 public function refresh() {
171 $this->relPaths
= NULL;
173 $this->cache
->delete($this->cacheKey
);
180 public function getBaseDir() {
181 return $this->baseDir
;
185 * Determine the relative path of an extension directory.
190 * @throws CRM_Extension_Exception_MissingException
193 protected function getRelPath($key) {
194 $keypaths = $this->getRelPaths();
195 if (!isset($keypaths[$key])) {
196 throw new CRM_Extension_Exception_MissingException("Failed to find extension: $key");
198 return $keypaths[$key];
202 * Scan $basedir for a list of extension-keys
207 protected function getRelPaths() {
208 if (!is_array($this->relPaths
)) {
210 $this->relPaths
= $this->cache
->get($this->cacheKey
);
212 if (!is_array($this->relPaths
)) {
213 $this->relPaths
= [];
214 $infoPaths = CRM_Utils_File
::findFiles($this->baseDir
, 'info.xml');
215 foreach ($infoPaths as $infoPath) {
216 $relPath = CRM_Utils_File
::relativize(dirname($infoPath), $this->baseDir
);
218 $info = CRM_Extension_Info
::loadFromFile($infoPath);
220 catch (CRM_Extension_Exception_ParseException
$e) {
221 CRM_Core_Session
::setStatus(ts('Parse error in extension: %1', [
222 1 => $e->getMessage(),
224 CRM_Core_Error
::debug_log_message("Parse error in extension: " . $e->getMessage());
228 foreach ($this->filters
as $filter) {
229 if (!$filter($info)) {
235 $this->relPaths
[$info->key
] = $relPath;
239 $this->cache
->set($this->cacheKey
, $this->relPaths
);
243 return $this->relPaths
;
247 * Determine the relative path of an extension directory.
252 * @throws CRM_Extension_Exception_MissingException
255 protected function getRelUrl($key) {
256 $relUrls = $this->getRelUrls();
257 if (!isset($relUrls[$key])) {
258 throw new CRM_Extension_Exception_MissingException("Failed to find extension: $key");
260 return $relUrls[$key];
264 * Scan $basedir for a list of extension-keys
269 protected function getRelUrls() {
270 if (DIRECTORY_SEPARATOR
== '/') {
271 return $this->getRelPaths();
273 if (!is_array($this->relUrls
)) {
274 $this->relUrls
= self
::convertPathsToUrls(DIRECTORY_SEPARATOR
, $this->getRelPaths());
276 return $this->relUrls
;
280 * Register a filter which determine whether a copy of an extension
281 * appears as available.
283 * @param callable $callable
284 * function(CRM_Extension_Info $info): bool
285 * Each function returns TRUE if the extension should be visible.
288 public function addFilter($callable) {
289 $this->filters
[] = $callable;
294 * Convert a list of relative paths to relative URLs.
296 * Note: Treat as private. This is only public to facilitate testing.
298 * @param string $dirSep
299 * Directory separator ("/" or "\").
300 * @param array $relPaths
301 * Array($key => $relPath).
303 * Array($key => $relUrl).
305 public static function convertPathsToUrls($dirSep, $relPaths) {
307 foreach ($relPaths as $key => $relPath) {
308 $relUrls[$key] = str_replace($dirSep, '/', $relPath);