1: <?php
2: /**
3: * This file contains the autoloader class.
4: *
5: * @package Core
6: * @subpackage Backend
7: * @author Murat Purc <murat@purc.de>
8: * @copyright four for business AG <www.4fb.de>
9: * @license http://www.contenido.org/license/LIZENZ.txt
10: * @link http://www.4fb.de
11: * @link http://www.contenido.org
12: */
13:
14: defined('CON_FRAMEWORK') || die('Illegal call: Missing framework initialization - request aborted.');
15:
16: /**
17: * Implements autoload feature for a CONTENIDO project.
18: *
19: * Autoloading for CONTENIDO is provided via a generated class map configuration
20: * file, which is available inside data/config/{environment}/ folder.
21: * - data/config/{environment}/config.autoloader.php
22: *
23: * Autoloading is extendable by adding a additional class map file inside the
24: * same
25: * folder, which could contain further class map settings or could overwrite
26: * settings of main class map file.
27: * - data/config/{environment}/contenido/includes/config.autoloader.local.php
28: *
29: * You can also add additional class map configuration by using function
30: * following
31: * functions:
32: * - cAutoload::addClassmapConfig(array $config)
33: * - cAutoload::addClassmapConfigFile($configFile)
34: *
35: * Read also docs/techref/backend/backend.autoloader.html to get involved in
36: * CONTENIDO autoloader mechanism.
37: *
38: * @package Core
39: * @subpackage Backend
40: */
41: class cAutoload {
42:
43: /**
44: * Identifier for error if class file could not be found.
45: *
46: * @var string
47: */
48: const ERROR_FILE_NOT_FOUND = 'file_not_found';
49:
50: /**
51: * Identifier for error if class already exists.
52: *
53: * @var string
54: */
55: const ERROR_CLASS_EXISTS = 'class_exists';
56:
57: /**
58: * CONTENIDO root path.
59: * Path to the folder which contains the CONTENIDO installation.
60: *
61: * @var string
62: */
63: private static $_conRootPath = NULL;
64:
65: /**
66: * Array of interface/class names with related files to include
67: *
68: * @var array
69: */
70: private static $_includeFiles = NULL;
71:
72: /**
73: * Flag containing initialized status
74: *
75: * @var bool
76: */
77: private static $_initialized = NULL;
78:
79: /**
80: * Array to store loaded classnames and the paths to the class files.
81: * $_loadedClasses['classname'] = '/path/to/the/class.php';
82: *
83: * @var array
84: */
85: private static $_loadedClasses = array();
86:
87: /**
88: * Array to store invalid classnames and the paths to the class files.
89: * $_errors[pos] = array('class' => classname, 'file' => file, 'error' =>
90: * errorType);
91: *
92: * @var array
93: */
94: private static $_errors = array();
95:
96: /**
97: * Initialization of CONTENIDO autoloader, is to call at least once.
98: *
99: * Registers itself as a __autoload implementation, includes the class map
100: * file,
101: * and if exists, the user defined class map file, containing the includes.
102: *
103: * @param array $cfg
104: * The CONTENIDO cfg array
105: */
106: public static function initialize(array $cfg) {
107: if (self::$_initialized == true) {
108: return;
109: }
110:
111: self::$_initialized = true;
112: self::$_conRootPath = str_replace('\\', '/', realpath($cfg['path']['contenido'] . '/../')) . '/';
113:
114: spl_autoload_register(array(__CLASS__, 'autoload'));
115:
116: // load n' store autoloader class map file
117: $file = $cfg['path']['contenido_config'] . 'config.autoloader.php';
118: $arr = include_once($file);
119: if ($arr) {
120: self::addClassmapConfig($arr);
121: }
122:
123: // load n' store additional autoloader class map file, if exists
124: $file = $cfg['path']['contenido_config'] . 'config.autoloader.local.php';
125: if (is_file($file)) {
126: self::addClassmapConfigFile($file);
127: }
128: }
129:
130: /**
131: * Adding additional autoloader class map configuration.
132: * NOTE:
133: * Since this autoloader is implemented for CONTENIDO, it doesn't support to
134: * load classfiles being located outside of the CONTENIDO installation
135: * folder.
136: *
137: * @param array $config
138: * Assoziative class map array as follows:
139: * <pre>
140: * // Structure is: "Classname" => "Path to classfile from CONTENIDO
141: * installation folder"
142: * $config = array(
143: * 'myPluginsClass' =>
144: * 'contenido/plugins/myplugin/classes/class.myPluginClass.php',
145: * 'myPluginsOtherClass' =>
146: * 'contenido/plugins/myplugin/classes/class.myPluginsOtherClass.php',
147: * );
148: * </pre>
149: */
150: public static function addClassmapConfig(array $config) {
151: $newConfig = self::_normalizeConfig($config);
152: if (!is_array(self::$_includeFiles)) {
153: self::$_includeFiles = array();
154: }
155: self::$_includeFiles = array_merge(self::$_includeFiles, $newConfig);
156: }
157:
158: /**
159: * Adding additional autoloader class map configuration file.
160: * NOTE:
161: * Since this autoloader is implemented for CONTENIDO, it doesn't support to
162: * load classfiles being located outside of the CONTENIDO installation
163: * folder.
164: *
165: * @param string $configFile
166: * Full path to class map configuration file.
167: * The provided file must return a class map configuration array as
168: * follows:
169: * <pre>
170: * // Structure is: "Classname" => "Path to classfile from CONTENIDO
171: * installation folder"
172: * return array(
173: * 'myPluginsClass' =>
174: * 'contenido/plugins/myplugin/classes/class.myPluginClass.php',
175: * 'myPluginsOtherClass' =>
176: * 'contenido/plugins/myplugin/classes/class.myPluginsOtherClass.php',
177: * 'myCmsClass' => 'cms/includes/class.myCmsClass.php',
178: * );
179: * </pre>
180: */
181: public static function addClassmapConfigFile($configFile) {
182: if (is_file($configFile)) {
183: $arr = include_once($configFile);
184: if ($arr) {
185: self::addClassmapConfig($arr);
186: }
187: }
188: }
189:
190: /**
191: * The main __autoload() implementation.
192: * Tries to include the file of passed classname.
193: *
194: * @param string $className
195: * The classname
196: *
197: * @throws cBadMethodCallException
198: * If autoloader wasn't initialized before
199: */
200: public static function autoload($className) {
201: if (self::$_initialized !== true) {
202: throw new cBadMethodCallException('Autoloader has to be initialized by calling method initialize()');
203: }
204:
205: if (isset(self::$_loadedClasses[$className])) {
206: return;
207: }
208:
209: $file = self::_getContenidoClassFile($className);
210:
211: if ($file) {
212: // load class file from class map
213: self::_loadFile($file);
214: }
215:
216: self::$_loadedClasses[$className] = str_replace(self::$_conRootPath, '', $file);
217: }
218:
219: /**
220: * Checks, if passed filename is a file, which will be included by the
221: * autoloader.
222: *
223: * @param string $file
224: * Filename or Filename with a part of the path, e.g.
225: * - class.foobar.php
226: * - classes/class.foobar.php
227: * - contenido/classes/class.foobar.php
228: * @return bool
229: */
230: public static function isAutoloadable($file) {
231: foreach (self::$_includeFiles as $className => $includeFile) {
232: if (cString::findFirstPos($includeFile, $file) !== false) {
233: return true;
234: }
235: }
236: return false;
237: }
238:
239: /**
240: * Returns the loaded classes (@see cAutoload::$_loadedClasses)
241: *
242: * @return array
243: */
244: public static function getLoadedClasses() {
245: return self::$_loadedClasses;
246: }
247:
248: /**
249: * Returns the errorlist containing invalid classes (@see
250: * cAutoload::$_errors)
251: *
252: * @return array
253: */
254: public static function getErrors() {
255: return self::$_errors;
256: }
257:
258: /**
259: * Returns the path to a CONTENIDO class file by processing the given
260: * classname
261: *
262: * @param string $className
263: * @return string|null
264: * string if validation was successful, otherwise NULL
265: */
266: private static function _getContenidoClassFile($className) {
267: $classNameLower = cString::toLowerCase($className);
268: $file = isset(self::$_includeFiles[$classNameLower]) ? self::$_conRootPath . self::$_includeFiles[$classNameLower] : NULL;
269: return self::_validateClassAndFile($className, $file);
270: }
271:
272: /**
273: * Validates the given classname and filename.
274: *
275: * @param string $classname
276: * @param string $filename
277: * @return string|null
278: * string if validation was successful, otherwise NULL
279: */
280: private static function _validateClassAndFile($classname, $filename) {
281: if (class_exists($classname)) {
282: self::$_errors[] = array(
283: 'class' => $classname,
284: 'file' => str_replace(self::$_conRootPath, '', $filename),
285: 'error' => self::ERROR_CLASS_EXISTS
286: );
287: return NULL;
288: } elseif (!is_file($filename)) {
289: self::$_errors[] = array(
290: 'class' => $classname,
291: 'file' => str_replace(self::$_conRootPath, '', $filename),
292: 'error' => self::ERROR_FILE_NOT_FOUND
293: );
294: return NULL;
295: } else {
296: return $filename;
297: }
298: }
299:
300: /**
301: * Normalizes the passed configuration array by returning a new copy of it
302: * which contains the keys in lowercase.
303: * This prevents errors by trying to load class 'foobar' if the real class
304: * name is 'FooBar'.
305: *
306: * @param array $config
307: * @return array
308: */
309: private static function _normalizeConfig(array $config) {
310: $newConfig = array();
311: foreach ($config as $name => $file) {
312: $newConfig[cString::toLowerCase($name)] = $file;
313: }
314: return $newConfig;
315: }
316:
317: /**
318: * Loads the desired file by invoking require_once method
319: *
320: * @param string $filePathName
321: * @param bool $beQuiet [optional]
322: * Flag to prevent thrown warnings/errors by using the error control
323: * operator @
324: */
325: private static function _loadFile($filePathName, $beQuiet = false) {
326: if ($beQuiet) {
327: @require_once($filePathName);
328: } else {
329: require_once($filePathName);
330: }
331: }
332: }
333: