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(
115: __CLASS__,
116: 'autoload'
117: ));
118:
119: // load n' store autoloader class map file
120: $file = $cfg['path']['contenido_config'] . 'config.autoloader.php';
121: $arr = include_once($file);
122: if ($arr) {
123: self::addClassmapConfig($arr);
124: }
125:
126: // load n' store additional autoloader class map file, if exists
127: $file = $cfg['path']['contenido_config'] . 'config.autoloader.local.php';
128: if (is_file($file)) {
129: self::addClassmapConfigFile($file);
130: }
131: }
132:
133: /**
134: * Adding additional autoloader class map configuration.
135: * NOTE:
136: * Since this autoloader is implemented for CONTENIDO, it doesn't support to
137: * load classfiles being located outside of the CONTENIDO installation
138: * folder.
139: *
140: * @param array $config
141: * 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: * 'contenido/plugins/myplugin/classes/class.myPluginsOtherClass.php',
150: * );
151: * </pre>
152: */
153: public static function addClassmapConfig(array $config) {
154: $newConfig = self::_normalizeConfig($config);
155: if (!is_array(self::$_includeFiles)) {
156: self::$_includeFiles = array();
157: }
158: self::$_includeFiles = array_merge(self::$_includeFiles, $newConfig);
159: }
160:
161: /**
162: * Adding additional autoloader class map configuration file.
163: * NOTE:
164: * Since this autoloader is implemented for CONTENIDO, it doesn't support to
165: * load classfiles being located outside of the CONTENIDO installation
166: * folder.
167: *
168: * @param string $configFile
169: * Full path to class map configuration file.
170: * The provided file must return a class map configuration array as
171: * follows:
172: * <pre>
173: * // Structure is: "Classname" => "Path to classfile from CONTENIDO
174: * installation folder"
175: * return array(
176: * 'myPluginsClass' =>
177: * 'contenido/plugins/myplugin/classes/class.myPluginClass.php',
178: * 'myPluginsOtherClass' =>
179: * 'contenido/plugins/myplugin/classes/class.myPluginsOtherClass.php',
180: * 'myCmsClass' => 'cms/includes/class.myCmsClass.php',
181: * );
182: * </pre>
183: */
184: public static function addClassmapConfigFile($configFile) {
185: if (is_file($configFile)) {
186: $arr = include_once($configFile);
187: if ($arr) {
188: self::addClassmapConfig($arr);
189: }
190: }
191: }
192:
193: /**
194: * The main __autoload() implementation.
195: * Tries to include the file of passed classname.
196: *
197: * @param string $className
198: * The classname
199: * @throws cBadMethodCallException
200: * If autoloader wasn't initialized before
201: */
202: public static function autoload($className) {
203: if (self::$_initialized !== true) {
204: throw new cBadMethodCallException("Autoloader has to be initialized by calling method initialize()");
205: }
206:
207: if (isset(self::$_loadedClasses[$className])) {
208: return;
209: }
210:
211: $file = self::_getContenidoClassFile($className);
212:
213: if ($file) {
214: // load class file from class map
215: self::_loadFile($file);
216: }
217:
218: self::$_loadedClasses[$className] = str_replace(self::$_conRootPath, '', $file);
219: }
220:
221: /**
222: * Checks, if passed filename is a file, which will be included by the
223: * autoloader.
224: *
225: * @param string $file
226: * Filename or Filename with a part of the path, e.g.
227: * - class.foobar.php
228: * - classes/class.foobar.php
229: * - contenido/classes/class.foobar.php
230: * @return bool
231: */
232: public static function isAutoloadable($file) {
233: foreach (self::$_includeFiles as $className => $includeFile) {
234: if (strpos($includeFile, $file) !== false) {
235: return true;
236: }
237: }
238: return false;
239: }
240:
241: /**
242: * Returns the loaded classes (@see cAutoload::$_loadedClasses)
243: *
244: * @return array
245: */
246: public static function getLoadedClasses() {
247: return self::$_loadedClasses;
248: }
249:
250: /**
251: * Returns the errorlist containing invalid classes (@see
252: * cAutoload::$_errors)
253: *
254: * @return array
255: */
256: public static function getErrors() {
257: return self::$_errors;
258: }
259:
260: /**
261: * Returns the path to a CONTENIDO class file by processing the given
262: * classname
263: *
264: * @param string $className
265: * @return string|null
266: * string if validation was successfull, otherwise NULL
267: */
268: private static function _getContenidoClassFile($className) {
269: $classNameLower = strtolower($className);
270: $file = isset(self::$_includeFiles[$classNameLower]) ? self::$_conRootPath . self::$_includeFiles[$classNameLower] : NULL;
271: return self::_validateClassAndFile($className, $file);
272: }
273:
274: /**
275: * Validates the given class and the file
276: *
277: * @param string $className
278: * @param string $filePathName
279: * @return string|null
280: * string 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 [optional]
324: * Flag to prevent thrown warnings/errors by using the error control
325: * operator @
326: */
327: private static function _loadFile($filePathName, $beQuiet = false) {
328: if ($beQuiet) {
329: @require_once($filePathName);
330: } else {
331: require_once($filePathName);
332: }
333: }
334: }
335: