Merge pull request #15982 from civicrm/5.20
[civicrm-core.git] / CRM / Core / ClassLoader.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
6a488035 5 | |
bc77d7c0
TO
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
6a488035 9 +--------------------------------------------------------------------+
d25dd0ee 10 */
6a488035
TO
11
12/**
13 *
14 *
15 * @package CRM
ca5cec67 16 * @copyright CiviCRM LLC https://civicrm.org/licensing
6a488035
TO
17 * $Id$
18 *
19 */
20class CRM_Core_ClassLoader {
21
22 /**
23 * We only need one instance of this object. So we use the singleton
24 * pattern and cache the instance in this variable
25 * @var object
6a488035
TO
26 */
27 private static $_singleton = NULL;
28
649d6dd0
TO
29 /**
30 * The classes in CiviTest have ucky, non-standard naming.
31 *
32 * @var array
33 * Array(string $className => string $filePath).
34 */
35 private $civiTestClasses;
36
a0ee3941
EM
37 /**
38 * @param bool $force
39 *
40 * @return object
41 */
00be9182 42 public static function &singleton($force = FALSE) {
6a488035
TO
43 if ($force || self::$_singleton === NULL) {
44 self::$_singleton = new CRM_Core_ClassLoader();
45 }
46 return self::$_singleton;
47 }
48
49 /**
e97c66ff 50 * Has this been registered already.
51 *
52 * @var bool
6a488035
TO
53 */
54 protected $_registered;
55
a0ee3941 56 /**
e97c66ff 57 * Class constructor.
a0ee3941 58 */
6a488035
TO
59 protected function __construct() {
60 $this->_registered = FALSE;
be2fb01f 61 $this->civiTestClasses = [
649d6dd0
TO
62 'CiviCaseTestCase',
63 'CiviDBAssert',
64 'CiviMailUtils',
65 'CiviReportTestCase',
66 'CiviSeleniumTestCase',
649d6dd0
TO
67 'CiviTestSuite',
68 'CiviUnitTestCase',
9e80c052 69 'CiviEndToEndTestCase',
649d6dd0
TO
70 'Contact',
71 'ContributionPage',
72 'Custom',
73 'Event',
74 'Membership',
75 'Participant',
76 'PaypalPro',
be2fb01f 77 ];
6a488035
TO
78 }
79
45b293a2
DS
80 /**
81 * Requires the autoload.php generated by composer
82 *
83 * @return void
84 */
85 protected function requireComposerAutoload() {
d1f60655
DS
86 // We are trying to locate 'vendor/autoload.php'. When installing CiviCRM
87 // manually from the built tarball, that will be two directories up in the
88 // civicrm-core directory. However, if civicrm-core was installed via
89 // composer as a library, that'll be 5 directories up where composer was
90 // run (ex. the Drupal root on a Drupal 8 site).
9f1588f9
DS
91 $civicrm_base_path = dirname(dirname(__DIR__));
92 $top_path = dirname(dirname(dirname(dirname(dirname(__DIR__)))));
45b293a2
DS
93
94 if (file_exists($civicrm_base_path . '/vendor/autoload.php')) {
95 require_once $civicrm_base_path . '/vendor/autoload.php';
96 }
97 elseif (file_exists($top_path . '/vendor/autoload.php')) {
98 require_once $top_path . '/vendor/autoload.php';
99 }
100 }
101
6a488035
TO
102 /**
103 * Registers this instance as an autoloader.
104 *
6a0b768e
TO
105 * @param bool $prepend
106 * Whether to prepend the autoloader or not.
6a488035
TO
107 *
108 * @api
109 */
00be9182 110 public function register($prepend = FALSE) {
6a488035
TO
111 if ($this->_registered) {
112 return;
113 }
fa184193
TO
114 $civicrm_base_path = dirname(dirname(__DIR__));
115
45b293a2 116 $this->requireComposerAutoload();
6a488035
TO
117
118 // we do this to prevent a autoloader errors with joomla / 3rd party packages
119 // use absolute path since we dont know the content of include_path as yet
120 // CRM-11304
fa184193
TO
121 // TODO Remove this autoloader. For civicrm-core and civicrm-packages, the composer autoloader works fine.
122 // Extensions rely on include_path-based autoloading
be2fb01f 123 spl_autoload_register([$this, 'loadClass'], TRUE, $prepend);
c6af44d7 124 $this->initHtmlPurifier($prepend);
6a488035
TO
125
126 $this->_registered = TRUE;
be2fb01f
CW
127 $packages_path = implode(DIRECTORY_SEPARATOR, [$civicrm_base_path, 'packages']);
128 $include_paths = [
fa184193
TO
129 '.',
130 $civicrm_base_path,
21dfd5f5 131 $packages_path,
be2fb01f 132 ];
fa184193
TO
133 $include_paths = implode(PATH_SEPARATOR, $include_paths);
134 set_include_path($include_paths . PATH_SEPARATOR . get_include_path());
7991d8ed 135 // @todo Why do we need to load this again?
45b293a2 136 $this->requireComposerAutoload();
6a488035
TO
137 }
138
7a9ab499
EM
139 /**
140 * Initialize HTML purifier class.
141 *
142 * @param string $prepend
143 */
00be9182 144 public function initHtmlPurifier($prepend) {
c6af44d7
C
145 if (class_exists('HTMLPurifier_Bootstrap')) {
146 // HTMLPurifier is already initialized, e.g. by the Drupal module.
147 return;
148 }
149
150 $htmlPurifierPath = $this->getHtmlPurifierPath();
151
152 if (FALSE === $htmlPurifierPath) {
153 // No HTMLPurifier available, e.g. during installation.
154 return;
155 }
156 require_once $htmlPurifierPath;
be2fb01f 157 spl_autoload_register(['HTMLPurifier_Bootstrap', 'autoload'], TRUE, $prepend);
c6af44d7
C
158 }
159
160 /**
161 * @return string|false
162 * Path to the file where the class HTMLPurifier_Bootstrap is defined, or
163 * FALSE, if such a file does not exist.
164 */
165 private function getHtmlPurifierPath() {
166 if (function_exists('libraries_get_path')
167 && ($path = libraries_get_path('htmlpurifier'))
168 && file_exists($file = $path . '/library/HTMLPurifier/Bootstrap.php')
169 ) {
170 // We are in Drupal 7, and the HTMLPurifier module is installed.
171 // Use Drupal's HTMLPurifier path, to avoid conflicts.
172 // @todo Verify that we are really in Drupal 7, and not in some other
173 // environment that happens to provide a 'libraries_get_path()' function.
174 return $file;
175 }
176
177 // we do this to prevent a autoloader errors with joomla / 3rd party packages
178 // Use absolute path, since we don't know the content of include_path yet.
179 // CRM-11304
abc8354c 180 $file = dirname(__FILE__) . '/../../packages/IDS/vendors/htmlpurifier/HTMLPurifier/Bootstrap.php';
c6af44d7
C
181 if (file_exists($file)) {
182 return $file;
183 }
184
185 return FALSE;
186 }
187
a0ee3941
EM
188 /**
189 * @param $class
190 */
00be9182 191 public function loadClass($class) {
eb1afb93
ON
192 if ($class === 'CiviCRM_API3_Exception') {
193 //call internal error class api/Exception first
194 // allow api/Exception class call external error class
195 // CiviCRM_API3_Exception
196 require_once 'api/Exception.php';
197 }
6a488035
TO
198 if (
199 // Only load classes that clearly belong to CiviCRM.
8581f9ae 200 // Note: api/v3 does not use classes, but api_v3's test-suite does
dbaa9d7d 201 (0 === strncmp($class, 'CRM_', 4) || 0 === strncmp($class, 'CRMTraits', 9) || 0 === strncmp($class, 'api_v3_', 7) || 0 === strncmp($class, 'WebTest_', 8) || 0 === strncmp($class, 'E2E_', 4)) &&
6a488035
TO
202 // Do not load PHP 5.3 namespaced classes.
203 // (in a future version, maybe)
204 FALSE === strpos($class, '\\')
205 ) {
206 $file = strtr($class, '_', '/') . '.php';
207 // There is some question about the best way to do this.
208 // "require_once" is nice because it's simple and throws
a03a3680
TO
209 // intelligible errors.
210 if (FALSE != stream_resolve_include_path($file)) {
211 require_once $file;
212 }
6a488035 213 }
649d6dd0
TO
214 elseif (in_array($class, $this->civiTestClasses)) {
215 $file = "tests/phpunit/CiviTest/{$class}.php";
216 if (FALSE != stream_resolve_include_path($file)) {
217 require_once $file;
218 }
219 }
220 elseif ($class === 'CiviSeleniumSettings') {
221 if (!empty($GLOBALS['_CV'])) {
222 require_once 'tests/phpunit/CiviTest/CiviSeleniumSettings.auto.php';
223 }
224 elseif (CRM_Utils_File::isIncludable('tests/phpunit/CiviTest/CiviSeleniumSettings.php')) {
225 require_once 'tests/phpunit/CiviTest/CiviSeleniumSettings.php';
226 }
227 }
6a488035 228 }
a03a3680 229
6a488035 230}