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 same
26: * folder, which could contain further class map settings or could overwrite
27: * settings of main class map file.
28: * - data/config/{environment}/contenido/includes/config.autoloader.local.php
29: *
30: * You can also add additional class map configuration by using function 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: const ERROR_FILE_NOT_FOUND = 'file_not_found';
44: const ERROR_CLASS_EXISTS = 'class_exists';
45:
46: /**
47: * CONTENIDO root path. Path to the folder which contains the CONTENIDO installation.
48: *
49: * @var string
50: */
51: private static $_conRootPath = null;
52:
53: /**
54: * Array of interface/class names with related files to include
55: *
56: * @var array
57: */
58: private static $_includeFiles = null;
59:
60: /**
61: * Flag containing initialized status
62: *
63: * @var bool
64: */
65: private static $_initialized = null;
66:
67: /**
68: * Array to store loaded classnames and the paths to the class files.
69: * $_loadedClasses['classname'] = '/path/to/the/class.php';
70: *
71: * @var array
72: */
73: private static $_loadedClasses = array();
74:
75: /**
76: * Array to store invalid classnames and the paths to the class files.
77: * $_errors[pos] = array('class' => classname, 'file' => file, 'error' => errorType);
78: *
79: * @var array
80: */
81: private static $_errors = array();
82:
83: /**
84: * Initialization of CONTENIDO autoloader, is to call at least once.
85: *
86: * Registers itself as a __autoload implementation, includes the class map file,
87: * and if exists, the user defined class map file, containing the includes.
88: *
89: * @param array $cfg The CONTENIDO cfg array
90: */
91: public static function initialize(array $cfg) {
92: if (self::$_initialized == true) {
93: return;
94: }
95:
96: self::$_initialized = true;
97: self::$_conRootPath = str_replace('\\', '/', realpath($cfg['path']['contenido'] . '/../')) . '/';
98:
99: spl_autoload_register(array(__CLASS__, 'autoload'));
100:
101: // load n' store autoloader class map file
102: $file = $cfg['path']['contenido_config'] . 'config.autoloader.php';
103: if ($arr = include_once($file)) {
104: self::addClassmapConfig($arr);
105: }
106:
107: // load n' store additional autoloader class map file, if exists
108: $file = $cfg['path']['contenido_config'] . 'config.autoloader.local.php';
109: if (is_file($file)) {
110: self::addClassmapConfigFile($file);
111: }
112: }
113:
114: /**
115: * Adding additional autoloader class map configuration.
116: * NOTE:
117: * Since this autoloader is implemented for CONTENIDO, it doesn't support to
118: * load classfiles being located outside of the CONTENIDO installation folder.
119: *
120: * @param array $config Assoziative class map array as follows:
121: * <pre>
122: * // Structure is: "Classname" => "Path to classfile from CONTENIDO installation folder"
123: * $config = array(
124: * 'myPluginsClass' => 'contenido/plugins/myplugin/classes/class.myPluginClass.php',
125: * 'myPluginsOtherClass' => 'contenido/plugins/myplugin/classes/class.myPluginsOtherClass.php',
126: * );
127: * </pre>
128: */
129: public static function addClassmapConfig(array $config) {
130: $newConfig = self::_normalizeConfig($config);
131: if (!is_array(self::$_includeFiles)) {
132: self::$_includeFiles = array();
133: }
134: self::$_includeFiles = array_merge(self::$_includeFiles, $newConfig);
135: }
136:
137: /**
138: * Adding additional autoloader class map configuration file.
139: * NOTE:
140: * Since this autoloader is implemented for CONTENIDO, it doesn't support to
141: * load classfiles being located outside of the CONTENIDO installation folder.
142: *
143: * @param string $configFile Full path to class map configuration file.
144: * The provided file must return a class map configuration array as follows:
145: * <pre>
146: * // Structure is: "Classname" => "Path to classfile from CONTENIDO installation folder"
147: * return array(
148: * 'myPluginsClass' => 'contenido/plugins/myplugin/classes/class.myPluginClass.php',
149: * 'myPluginsOtherClass' => 'contenido/plugins/myplugin/classes/class.myPluginsOtherClass.php',
150: * 'myCmsClass' => 'cms/includes/class.myCmsClass.php',
151: * );
152: * </pre>
153: */
154: public static function addClassmapConfigFile($configFile) {
155: if (is_file($configFile)) {
156: if ($arr = include_once($configFile)) {
157: self::addClassmapConfig($arr);
158: }
159: }
160: }
161:
162: /**
163: * The main __autoload() implementation.
164: * Tries to include the file of passed classname.
165: *
166: * @param string $className The classname
167: * @throws cBadMethodCallException If autoloader wasn't initialized before
168: */
169: public static function autoload($className) {
170: if (self::$_initialized !== true) {
171: throw new cBadMethodCallException("Autoloader has to be initialized by calling method initialize()");
172: }
173:
174: if (isset(self::$_loadedClasses[$className])) {
175: return;
176: }
177:
178: $file = '';
179:
180: if ($file = self::_getContenidoClassFile($className)) {
181: // load class file from class map
182: self::_loadFile($file);
183: }
184:
185: self::$_loadedClasses[$className] = str_replace(self::$_conRootPath, '', $file);
186: }
187:
188: /**
189: * Checks, if passed filename is a file, which will be included by the autoloader.
190: *
191: * @param string $file Filename or Filename with a part of the path, e. g.
192: * - class.foobar.php
193: * - classes/class.foobar.php
194: * - contenido/classes/class.foobar.php
195: * @return bool
196: */
197: public static function isAutoloadable($file) {
198: foreach (self::$_includeFiles as $className => $includeFile) {
199: if (strpos($includeFile, $file) !== false) {
200: return true;
201: }
202: }
203: return false;
204: }
205:
206: /**
207: * Returns the loaded classes (@see cAutoload::$_loadedClasses)
208: *
209: * @return array
210: */
211: public static function getLoadedClasses() {
212: return self::$_loadedClasses;
213: }
214:
215: /**
216: * Returns the errorlist containing invalid classes (@see cAutoload::$_errors)
217: *
218: * @return array
219: */
220: public static function getErrors() {
221: return self::$_errors;
222: }
223:
224: /**
225: * Returns the path to a CONTENIDO class file by processing the given classname
226: *
227: * @param string $className
228: * @return (string|null) Path and filename or null
229: */
230: private static function _getContenidoClassFile($className) {
231: $classNameLower = strtolower($className);
232: $file = isset(self::$_includeFiles[$classNameLower]) ? self::$_conRootPath . self::$_includeFiles[$classNameLower] : null;
233: return self::_validateClassAndFile($className, $file);
234: }
235:
236: /**
237: * Validates the given class and the file
238: *
239: * @param string $className
240: * @param string $filePathName
241: * @return (string|null) The file if validation was successfull, otherwhise null
242: */
243: private static function _validateClassAndFile($className, $filePathName) {
244: if (class_exists($className)) {
245: self::$_errors[] = array(
246: 'class' => $className,
247: 'file' => str_replace(self::$_conRootPath, '', $filePathName),
248: 'error' => self::ERROR_CLASS_EXISTS
249: );
250: return null;
251: } elseif (!is_file($filePathName)) {
252: self::$_errors[] = array(
253: 'class' => $className,
254: 'file' => str_replace(self::$_conRootPath, '', $filePathName),
255: 'error' => self::ERROR_FILE_NOT_FOUND
256: );
257: return null;
258: }
259:
260: return $filePathName;
261: }
262:
263: /**
264: * Normalizes the passed configuration array by returning a new copy of it
265: * which contains the keys in lowercase.
266: * This prevents errors by trying to load class 'foobar' if the real class name is 'FooBar'.
267: *
268: * @param array $config
269: * @return array
270: */
271: private static function _normalizeConfig(array $config) {
272: $newConfig = array();
273: foreach ($config as $name => $file) {
274: $newConfig[strtolower($name)] = $file;
275: }
276: return $newConfig;
277: }
278:
279: /**
280: * Loads the desired file by invoking require_once method
281: *
282: * @param string $filePathName
283: * @param bool $beQuiet Flag to prevent thrown warnings/errors by using
284: * the error control operator @
285: */
286: private static function _loadFile($filePathName, $beQuiet = false) {
287: if ($beQuiet) {
288: @require_once($filePathName);
289: } else {
290: require_once($filePathName);
291: }
292: }
293:
294: }
295: