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