Merge pull request #13517 from alifrumin/contribsearchsoftcredit
[civicrm-core.git] / CRM / Extension / Container / Basic.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
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. |
13 | |
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. |
18 | |
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 +--------------------------------------------------------------------+
26 */
27
28 /**
29 * @package CRM
30 * @copyright CiviCRM LLC (c) 2004-2019
31 */
32
33 /**
34 * An extension container is a locally-accessible source tree which can be
35 * scanned for extensions.
36 */
37 class CRM_Extension_Container_Basic implements CRM_Extension_Container_Interface {
38
39 /**
40 * @var string
41 *
42 * Note: Treat as private. This is only public to facilitate debugging.
43 */
44 public $baseDir;
45
46 /**
47 * @var string
48 *
49 * Note: Treat as private. This is only public to facilitate debugging.
50 */
51 public $baseUrl;
52
53 /**
54 * @var CRM_Utils_Cache_Interface|NULL
55 *
56 * Note: Treat as private. This is only public to facilitate debugging.
57 */
58 public $cache;
59
60 /**
61 * @var string the cache key used for any data stored by this container
62 *
63 * Note: Treat as private. This is only public to facilitate debugging.
64 */
65 public $cacheKey;
66
67 /**
68 * @var array($key => $relPath)
69 *
70 * Note: Treat as private. This is only public to facilitate debugging.
71 */
72 public $relPaths = FALSE;
73
74 /**
75 * @var array($key => $relUrl)
76 *
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.
80 *
81 * Note: Treat as private. This is only public to facilitate debugging.
82 */
83 public $relUrls = FALSE;
84
85 /**
86 * @param string $baseDir
87 * Local path to the container.
88 * @param string $baseUrl
89 * Public URL of the container.
90 * @param CRM_Utils_Cache_Interface $cache
91 * Cache in which to store extension metadata.
92 * @param string $cacheKey
93 * Unique name for this container.
94 */
95 public function __construct($baseDir, $baseUrl, CRM_Utils_Cache_Interface $cache = NULL, $cacheKey = NULL) {
96 $this->cache = $cache;
97 $this->cacheKey = $cacheKey;
98 $this->baseDir = rtrim($baseDir, '/');
99 $this->baseUrl = rtrim($baseUrl, '/');
100 }
101
102 /**
103 * @inheritDoc
104 *
105 * @return array
106 */
107 public function checkRequirements() {
108 $errors = array();
109
110 if (empty($this->baseDir) || !is_dir($this->baseDir)) {
111 $errors[] = array(
112 'title' => ts('Invalid Base Directory'),
113 'message' => ts('An extension container has been defined with a blank directory.'),
114 );
115 }
116 if (empty($this->baseUrl)) {
117 $errors[] = array(
118 'title' => ts('Invalid Base URL'),
119 'message' => ts('An extension container has been defined with a blank URL.'),
120 );
121 }
122
123 return $errors;
124 }
125
126 /**
127 * @inheritDoc
128 *
129 * @return array_keys
130 */
131 public function getKeys() {
132 return array_keys($this->getRelPaths());
133 }
134
135 /**
136 * @inheritDoc
137 */
138 public function getPath($key) {
139 return $this->baseDir . $this->getRelPath($key);
140 }
141
142 /**
143 * @inheritDoc
144 */
145 public function getResUrl($key) {
146 if (!$this->baseUrl) {
147 CRM_Core_Session::setStatus(
148 ts('Failed to determine URL for extension (%1). Please update <a href="%2">Resource URLs</a>.',
149 array(
150 1 => $key,
151 2 => CRM_Utils_System::url('civicrm/admin/setting/url', 'reset=1'),
152 )
153 )
154 );
155 }
156 return $this->baseUrl . $this->getRelUrl($key);
157 }
158
159 /**
160 * @inheritDoc
161 */
162 public function refresh() {
163 $this->relPaths = NULL;
164 if ($this->cache) {
165 $this->cache->delete($this->cacheKey);
166 }
167 }
168
169 /**
170 * @return string
171 */
172 public function getBaseDir() {
173 return $this->baseDir;
174 }
175
176 /**
177 * Determine the relative path of an extension directory.
178 *
179 * @param string $key
180 * Extension name.
181 *
182 * @throws CRM_Extension_Exception_MissingException
183 * @return string
184 */
185 protected function getRelPath($key) {
186 $keypaths = $this->getRelPaths();
187 if (!isset($keypaths[$key])) {
188 throw new CRM_Extension_Exception_MissingException("Failed to find extension: $key");
189 }
190 return $keypaths[$key];
191 }
192
193 /**
194 * Scan $basedir for a list of extension-keys
195 *
196 * @return array
197 * ($key => $relPath)
198 */
199 protected function getRelPaths() {
200 if (!is_array($this->relPaths)) {
201 if ($this->cache) {
202 $this->relPaths = $this->cache->get($this->cacheKey);
203 }
204 if (!is_array($this->relPaths)) {
205 $this->relPaths = array();
206 $infoPaths = CRM_Utils_File::findFiles($this->baseDir, 'info.xml');
207 foreach ($infoPaths as $infoPath) {
208 $relPath = CRM_Utils_File::relativize(dirname($infoPath), $this->baseDir);
209 try {
210 $info = CRM_Extension_Info::loadFromFile($infoPath);
211 }
212 catch (CRM_Extension_Exception_ParseException $e) {
213 CRM_Core_Session::setStatus(ts('Parse error in extension: %1', array(
214 1 => $e->getMessage(),
215 )), '', 'error');
216 CRM_Core_Error::debug_log_message("Parse error in extension: " . $e->getMessage());
217 continue;
218 }
219 $this->relPaths[$info->key] = $relPath;
220 }
221 if ($this->cache) {
222 $this->cache->set($this->cacheKey, $this->relPaths);
223 }
224 }
225 }
226 return $this->relPaths;
227 }
228
229 /**
230 * Determine the relative path of an extension directory.
231 *
232 * @param string $key
233 * Extension name.
234 *
235 * @throws CRM_Extension_Exception_MissingException
236 * @return string
237 */
238 protected function getRelUrl($key) {
239 $relUrls = $this->getRelUrls();
240 if (!isset($relUrls[$key])) {
241 throw new CRM_Extension_Exception_MissingException("Failed to find extension: $key");
242 }
243 return $relUrls[$key];
244 }
245
246 /**
247 * Scan $basedir for a list of extension-keys
248 *
249 * @return array
250 * ($key => $relUrl)
251 */
252 protected function getRelUrls() {
253 if (DIRECTORY_SEPARATOR == '/') {
254 return $this->getRelPaths();
255 }
256 if (!is_array($this->relUrls)) {
257 $this->relUrls = self::convertPathsToUrls(DIRECTORY_SEPARATOR, $this->getRelPaths());
258 }
259 return $this->relUrls;
260 }
261
262 /**
263 * Convert a list of relative paths to relative URLs.
264 *
265 * Note: Treat as private. This is only public to facilitate testing.
266 *
267 * @param string $dirSep
268 * Directory separator ("/" or "\").
269 * @param array $relPaths
270 * Array($key => $relPath).
271 * @return array
272 * Array($key => $relUrl).
273 */
274 public static function convertPathsToUrls($dirSep, $relPaths) {
275 $relUrls = array();
276 foreach ($relPaths as $key => $relPath) {
277 $relUrls[$key] = str_replace($dirSep, '/', $relPath);
278 }
279 return $relUrls;
280 }
281
282 }