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 The CONTENIDO cfg array
106: */
107: public static function initialize(array $cfg) {
108: if (self::$_initialized == true) {
109: return;
110: }
111:
112: self::$_initialized = true;
113: self::$_conRootPath = str_replace('\\', '/', realpath($cfg['path']['contenido'] . '/../')) . '/';
114:
115: spl_autoload_register(array(
116: __CLASS__,
117: 'autoload'
118: ));
119:
120: // load n' store autoloader class map file
121: $file = $cfg['path']['contenido_config'] . 'config.autoloader.php';
122: $arr = include_once($file);
123: if ($arr) {
124: self::addClassmapConfig($arr);
125: }
126:
127: // load n' store additional autoloader class map file, if exists
128: $file = $cfg['path']['contenido_config'] . 'config.autoloader.local.php';
129: if (is_file($file)) {
130: self::addClassmapConfigFile($file);
131: }
132: }
133:
134: /**
135: * Adding additional autoloader class map configuration.
136: * NOTE:
137: * Since this autoloader is implemented for CONTENIDO, it doesn't support to
138: * load classfiles being located outside of the CONTENIDO installation
139: * folder.
140: *
141: * @param array $config Assoziative class map array as follows:
142: * <pre>
143: * // Structure is: "Classname" => "Path to classfile from CONTENIDO
144: * installation folder"
145: * $config = array(
146: * 'myPluginsClass' =>
147: * 'contenido/plugins/myplugin/classes/class.myPluginClass.php',
148: * 'myPluginsOtherClass' =>
149: *
150: *
151: *
152: * 'contenido/plugins/myplugin/classes/class.myPluginsOtherClass.php',
153: * );
154: * </pre>
155: */
156: public static function addClassmapConfig(array $config) {
157: $newConfig = self::_normalizeConfig($config);
158: if (!is_array(self::$_includeFiles)) {
159: self::$_includeFiles = array();
160: }
161: self::$_includeFiles = array_merge(self::$_includeFiles, $newConfig);
162: }
163:
164: /**
165: * Adding additional autoloader class map configuration file.
166: * NOTE:
167: * Since this autoloader is implemented for CONTENIDO, it doesn't support to
168: * load classfiles being located outside of the CONTENIDO installation
169: * folder.
170: *
171: * @param string $configFile 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: *
182: *
183: *
184: * 'contenido/plugins/myplugin/classes/class.myPluginsOtherClass.php',
185: * 'myCmsClass' => 'cms/includes/class.myCmsClass.php',
186: * );
187: * </pre>
188: */
189: public static function addClassmapConfigFile($configFile) {
190: if (is_file($configFile)) {
191: $arr = include_once($configFile);
192: if ($arr) {
193: self::addClassmapConfig($arr);
194: }
195: }
196: }
197:
198: /**
199: * The main __autoload() implementation.
200: * Tries to include the file of passed classname.
201: *
202: * @param string $className The classname
203: * @throws cBadMethodCallException If autoloader wasn't initialized before
204: */
205: public static function autoload($className) {
206: if (self::$_initialized !== true) {
207: throw new cBadMethodCallException("Autoloader has to be initialized by calling method initialize()");
208: }
209:
210: if (isset(self::$_loadedClasses[$className])) {
211: return;
212: }
213:
214: $file = self::_getContenidoClassFile($className);
215:
216: if ($file) {
217: // load class file from class map
218: self::_loadFile($file);
219: }
220:
221: self::$_loadedClasses[$className] = str_replace(self::$_conRootPath, '', $file);
222: }
223:
224: /**
225: * Checks, if passed filename is a file, which will be included by the
226: * autoloader.
227: *
228: * @param string $file 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 Ambigous <(string, NULL), NULL, string> Path and filename or NULL
268: */
269: private static function _getContenidoClassFile($className) {
270: $classNameLower = strtolower($className);
271: $file = isset(self::$_includeFiles[$classNameLower]) ? self::$_conRootPath . self::$_includeFiles[$classNameLower] : NULL;
272: return self::_validateClassAndFile($className, $file);
273: }
274:
275: /**
276: * Validates the given class and the file
277: *
278: * @param string $className
279: * @param string $filePathName
280: * @return NULL unknown if validation was successfull, otherwise NULL
281: */
282: private static function _validateClassAndFile($className, $filePathName) {
283: if (class_exists($className)) {
284: self::$_errors[] = array(
285: 'class' => $className,
286: 'file' => str_replace(self::$_conRootPath, '', $filePathName),
287: 'error' => self::ERROR_CLASS_EXISTS
288: );
289: return NULL;
290: } elseif (!is_file($filePathName)) {
291: self::$_errors[] = array(
292: 'class' => $className,
293: 'file' => str_replace(self::$_conRootPath, '', $filePathName),
294: 'error' => self::ERROR_FILE_NOT_FOUND
295: );
296: return NULL;
297: }
298:
299: return $filePathName;
300: }
301:
302: /**
303: * Normalizes the passed configuration array by returning a new copy of it
304: * which contains the keys in lowercase.
305: * This prevents errors by trying to load class 'foobar' if the real class
306: * name is 'FooBar'.
307: *
308: * @param array $config
309: * @return array
310: */
311: private static function _normalizeConfig(array $config) {
312: $newConfig = array();
313: foreach ($config as $name => $file) {
314: $newConfig[strtolower($name)] = $file;
315: }
316: return $newConfig;
317: }
318:
319: /**
320: * Loads the desired file by invoking require_once method
321: *
322: * @param string $filePathName
323: * @param bool $beQuiet Flag to prevent thrown warnings/errors by using
324: * the error control operator @
325: */
326: private static function _loadFile($filePathName, $beQuiet = false) {
327: if ($beQuiet) {
328: @require_once($filePathName);
329: } else {
330: require_once($filePathName);
331: }
332: }
333: }
334: