Merge pull request #4983 from colemanw/CRM-15842
[civicrm-core.git] / CRM / Extension / Mapper.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
39de6fd5 4 | CiviCRM version 4.6 |
6a488035 5 +--------------------------------------------------------------------+
06b69b18 6 | Copyright CiviCRM LLC (c) 2004-2014 |
6a488035
TO
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 +--------------------------------------------------------------------+
d25dd0ee 26 */
6a488035
TO
27
28/**
29 * This class proivdes various helper functions for locating extensions
30 * data. It's designed for compatibility with pre-existing functions from
31 * CRM_Core_Extensions.
32 *
33 * Most of these helper functions originate with the first major iteration
34 * of extensions -- a time when every extension had one eponymous PHP class,
35 * when there was no PHP class-loader, and when there was special-case logic
36 * sprinkled around to handle loading of "extension classes".
37 *
38 * With module-extensions (Civi 4.2+), there are no eponymous classes --
39 * instead, module-extensions follow the same class-naming and class-loading
40 * practices as core (and don't require special-case logic for class
41 * loading). Consequently, the helpers in here aren't much used with
42 * module-extensions.
43 *
44 * @package CRM
06b69b18 45 * @copyright CiviCRM LLC (c) 2004-2014
6a488035
TO
46 * $Id$
47 *
48 */
49class CRM_Extension_Mapper {
50
51 /**
52 * An URL for public extensions repository
53 */
7da04cde 54 //const DEFAULT_EXTENSIONS_REPOSITORY = 'http://civicrm.org/extdir/ver={ver}|cms={uf}';
6a488035
TO
55
56 /**
57 * Extension info file name
58 */
59 const EXT_TEMPLATES_DIRNAME = 'templates';
60
61 /**
62 * @var CRM_Extension_Container_Interface
63 */
64 protected $container;
65
66 /**
67 * @var array (key => CRM_Extension_Info)
68 */
69 protected $infos = array();
70
71 /**
72 * @var array
73 */
74 protected $moduleExtensions = NULL;
75
76 /**
77 * @var CRM_Utils_Cache_Interface
78 */
79 protected $cache;
80
81 protected $cacheKey;
82
83 protected $civicrmPath;
84
85 protected $civicrmUrl;
86
e0ef6999
EM
87 /**
88 * @param CRM_Extension_Container_Interface $container
89 * @param CRM_Utils_Cache_Interface $cache
90 * @param null $cacheKey
91 * @param null $civicrmPath
92 * @param null $civicrmUrl
93 */
6a488035
TO
94 public function __construct(CRM_Extension_Container_Interface $container, CRM_Utils_Cache_Interface $cache = NULL, $cacheKey = NULL, $civicrmPath = NULL, $civicrmUrl = NULL) {
95 $this->container = $container;
96 $this->cache = $cache;
97 $this->cacheKey = $cacheKey;
98 if ($civicrmUrl) {
99 $this->civicrmUrl = rtrim($civicrmUrl, '/');
0db6c3e1
TO
100 }
101 else {
6a488035
TO
102 $config = CRM_Core_Config::singleton();
103 $this->civicrmUrl = rtrim($config->resourceBase, '/');
104 }
105 if ($civicrmPath) {
b3a4b879 106 $this->civicrmPath = rtrim($civicrmPath, '/');
0db6c3e1
TO
107 }
108 else {
6a488035 109 global $civicrm_root;
b3a4b879 110 $this->civicrmPath = rtrim($civicrm_root, '/');
6a488035
TO
111 }
112 }
113
114 /**
115 * Given the class, provides extension's key.
116 *
6a488035 117 *
f41911fd
TO
118 * @param string $clazz
119 * Extension class name.
6a488035 120 *
a6c01b45
CW
121 * @return string
122 * name of extension key
6a488035
TO
123 */
124 public function classToKey($clazz) {
125 return str_replace('_', '.', $clazz);
126 }
127
128 /**
129 * Given the class, provides extension path.
130 *
6a488035 131 *
6c8f6e67
EM
132 * @param $clazz
133 *
a6c01b45
CW
134 * @return string
135 * full path the extension .php file
6a488035
TO
136 */
137 public function classToPath($clazz) {
138 $elements = explode('_', $clazz);
139 $key = implode('.', $elements);
140 return $this->keyToPath($key);
141 }
142
143 /**
144 * Given the string, returns true or false if it's an extension key.
145 *
6a488035 146 *
f41911fd
TO
147 * @param string $key
148 * A string which might be an extension key.
6a488035 149 *
e7483cbe 150 * @return bool
a6c01b45 151 * true if given string is an extension name
6a488035
TO
152 */
153 public function isExtensionKey($key) {
154 // check if the string is an extension name or the class
155 return (strpos($key, '.') !== FALSE) ? TRUE : FALSE;
156 }
157
158 /**
159 * Given the string, returns true or false if it's an extension class name.
160 *
6a488035 161 *
f41911fd
TO
162 * @param string $clazz
163 * A string which might be an extension class name.
6a488035 164 *
e7483cbe 165 * @return bool
a6c01b45 166 * true if given string is an extension class name
6a488035
TO
167 */
168 public function isExtensionClass($clazz) {
169
170 if (substr($clazz, 0, 4) != 'CRM_') {
171 return (bool) preg_match('/^[a-z0-9]+(_[a-z0-9]+)+$/', $clazz);
172 }
173 return FALSE;
174 }
175
176 /**
f41911fd
TO
177 * @param string $key
178 * Extension fully-qualified-name.
77b97be7
EM
179 * @param bool $fresh
180 *
181 * @throws CRM_Extension_Exception
182 * @throws Exception
c490a46a 183 * @return CRM_Extension_Info
6a488035
TO
184 */
185 public function keyToInfo($key, $fresh = FALSE) {
186 if ($fresh || !array_key_exists($key, $this->infos)) {
187 try {
188 $this->infos[$key] = CRM_Extension_Info::loadFromFile($this->container->getPath($key) . DIRECTORY_SEPARATOR . CRM_Extension_Info::FILENAME);
0db6c3e1
TO
189 }
190 catch (CRM_Extension_Exception $e) {
6a488035
TO
191 // file has more detailed info, but we'll fallback to DB if it's missing -- DB has enough info to uninstall
192 $this->infos[$key] = CRM_Extension_System::singleton()->getManager()->createInfoFromDB($key);
193 if (!$this->infos[$key]) {
194 throw $e;
195 }
196 }
197 }
198 return $this->infos[$key];
199 }
200
201 /**
202 * Given the key, provides extension's class name.
203 *
6a488035 204 *
f41911fd
TO
205 * @param string $key
206 * Extension key.
6a488035 207 *
a6c01b45
CW
208 * @return string
209 * name of extension's main class
6a488035
TO
210 */
211 public function keyToClass($key) {
212 return str_replace('.', '_', $key);
213 }
214
215 /**
216 * Given the key, provides the path to file containing
217 * extension's main class.
218 *
6a488035 219 *
f41911fd
TO
220 * @param string $key
221 * Extension key.
6a488035 222 *
a6c01b45
CW
223 * @return string
224 * path to file containing extension's main class
6a488035
TO
225 */
226 public function keyToPath($key) {
227 $info = $this->keyToInfo($key);
228 return $this->container->getPath($key) . DIRECTORY_SEPARATOR . $info->file . '.php';
229 }
230
231 /**
232 * Given the key, provides the path to file containing
233 * extension's main class.
234 *
f41911fd
TO
235 * @param string $key
236 * Extension key.
a6c01b45
CW
237 * @return string
238 * local path of the extension source tree
6a488035
TO
239 */
240 public function keyToBasePath($key) {
241 if ($key == 'civicrm') {
242 return $this->civicrmPath;
243 }
244 return $this->container->getPath($key);
245 }
246
247 /**
248 * Given the key, provides the path to file containing
249 * extension's main class.
250 *
6a488035 251 *
f41911fd
TO
252 * @param string $key
253 * Extension key.
6a488035 254 *
a6c01b45
CW
255 * @return string
256 * url for resources in this extension
6a488035
TO
257 */
258 public function keyToUrl($key) {
259 if ($key == 'civicrm') {
dee7a2b1
PJ
260 // CRM-12130 Workaround: If the domain's config_backend is NULL at the start of the request,
261 // then the Mapper is wrongly constructed with an empty value for $this->civicrmUrl.
3d4a4ccf
PJ
262 if (empty($this->civicrmUrl)) {
263 $config = CRM_Core_Config::singleton();
264 return rtrim($config->resourceBase, '/');
265 }
6a488035
TO
266 return $this->civicrmUrl;
267 }
268
269 return $this->container->getResUrl($key);
270 }
271
272 /**
273 * Fetch the list of active extensions of type 'module'
274 *
5a4f6742
CW
275 * @param bool $fresh
276 * whether to forcibly reload extensions list from canonical store.
a6c01b45
CW
277 * @return array
278 * array(array('prefix' => $, 'file' => $))
6a488035
TO
279 */
280 public function getActiveModuleFiles($fresh = FALSE) {
281 $config = CRM_Core_Config::singleton();
282 if ($config->isUpgradeMode() || !defined('CIVICRM_DSN')) {
283 return array(); // hmm, ok
284 }
285
286 $moduleExtensions = NULL;
287 if ($this->cache && !$fresh) {
288 $moduleExtensions = $this->cache->get($this->cacheKey . '/moduleFiles');
289 }
290
291 if (!is_array($moduleExtensions)) {
292 // Check canonical module list
293 $moduleExtensions = array();
294 $sql = '
295 SELECT full_name, file
296 FROM civicrm_extension
297 WHERE is_active = 1
298 AND type = "module"
299 ';
300 $dao = CRM_Core_DAO::executeQuery($sql);
301 while ($dao->fetch()) {
302 try {
303 $moduleExtensions[] = array(
304 'prefix' => $dao->file,
305 'filePath' => $this->keyToPath($dao->full_name),
306 );
0db6c3e1
TO
307 }
308 catch (CRM_Extension_Exception $e) {
6a488035
TO
309 // Putting a stub here provides more consistency
310 // in how getActiveModuleFiles when racing between
311 // dirty file-removals and cache-clears.
312 CRM_Core_Session::setStatus($e->getMessage(), '', 'error');
313 $moduleExtensions[] = array(
314 'prefix' => $dao->file,
315 'filePath' => NULL,
316 );
317 }
318 }
319
320 if ($this->cache) {
321 $this->cache->set($this->cacheKey . '/moduleFiles', $moduleExtensions);
322 }
323 }
324 return $moduleExtensions;
325 }
326
e7ff7042
TO
327 /**
328 * Get a list of base URLs for all active modules
329 *
a6c01b45
CW
330 * @return array
331 * (string $extKey => string $baseUrl)
e7ff7042
TO
332 */
333 public function getActiveModuleUrls() {
334 // TODO optimization/caching
335 $urls = array();
336 $urls['civicrm'] = $this->keyToUrl('civicrm');
337 foreach ($this->getModules() as $module) {
338 /** @var $module CRM_Core_Module */
339 if ($module->is_active) {
340 $urls[$module->name] = $this->keyToUrl($module->name);
341 }
342 }
343 return $urls;
344 }
345
e0ef6999 346 /**
100fef9d 347 * @param string $name
e0ef6999
EM
348 *
349 * @return bool
350 */
6a488035
TO
351 public function isActiveModule($name) {
352 $activeModules = $this->getActiveModuleFiles();
353 foreach ($activeModules as $activeModule) {
354 if ($activeModule['prefix'] == $name) {
355 return TRUE;
356 }
357 }
358 return FALSE;
359 }
360
361 /**
362 * Get a list of all installed modules, including enabled and disabled ones
363 *
a6c01b45
CW
364 * @return array
365 * CRM_Core_Module
6a488035
TO
366 */
367 public function getModules() {
368 $result = array();
369 $dao = new CRM_Core_DAO_Extension();
370 $dao->type = 'module';
371 $dao->find();
372 while ($dao->fetch()) {
373 $result[] = new CRM_Core_Module($dao->full_name, $dao->is_active);
374 }
375 return $result;
376 }
377
378 /**
379 * Given the class, provides the template path.
380 *
6a488035 381 *
f41911fd
TO
382 * @param string $clazz
383 * Extension class name.
6a488035 384 *
a6c01b45
CW
385 * @return string
386 * path to extension's templates directory
6a488035
TO
387 */
388 public function getTemplatePath($clazz) {
389 $path = $this->container->getPath($this->classToKey($clazz));
390 return $path . DIRECTORY_SEPARATOR . self::EXT_TEMPLATES_DIRNAME;
391 /*
392 $path = $this->classToPath($clazz);
393 $pathElm = explode(DIRECTORY_SEPARATOR, $path);
394 array_pop($pathElm);
395 return implode(DIRECTORY_SEPARATOR, $pathElm) . DIRECTORY_SEPARATOR . self::EXT_TEMPLATES_DIRNAME;
e70a7fc0 396 */
6a488035
TO
397 }
398
399 /**
400 * Given te class, provides the template name.
401 * @todo consider multiple templates, support for one template for now
402 *
6a488035 403 *
f41911fd
TO
404 * @param string $clazz
405 * Extension class name.
6a488035 406 *
a6c01b45
CW
407 * @return string
408 * extension's template name
6a488035
TO
409 */
410 public function getTemplateName($clazz) {
411 $info = $this->keyToInfo($this->classToKey($clazz));
412 return (string) $info->file . '.tpl';
413 }
414
415 public function refresh() {
416 $this->infos = array();
417 $this->moduleExtensions = NULL;
418 if ($this->cache) {
419 $this->cache->delete($this->cacheKey . '/moduleFiles');
420 }
421 }
96025800 422
6a488035 423}