1<?php 2/** 3 * Zend Framework 4 * 5 * LICENSE 6 * 7 * This source file is subject to the new BSD license that is bundled 8 * with this package in the file LICENSE.txt. 9 * It is also available through the world-wide-web at this URL: 10 * http://framework.zend.com/license/new-bsd 11 * If you did not receive a copy of the license and are unable to 12 * obtain it through the world-wide-web, please send an email 13 * to license@zend.com so we can send you a copy immediately. 14 * 15 * @category Zend 16 * @package Zend_Loader 17 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 18 * @license http://framework.zend.com/license/new-bsd New BSD License 19 * @version $Id$ 20 */ 21 22/** 23 * Static methods for loading classes and files. 24 * 25 * @category Zend 26 * @package Zend_Loader 27 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 28 * @license http://framework.zend.com/license/new-bsd New BSD License 29 */ 30class Zend_Loader 31{ 32 /** 33 * Loads a class from a PHP file. The filename must be formatted 34 * as "$class.php". 35 * 36 * If $dirs is a string or an array, it will search the directories 37 * in the order supplied, and attempt to load the first matching file. 38 * 39 * If $dirs is null, it will split the class name at underscores to 40 * generate a path hierarchy (e.g., "Zend_Example_Class" will map 41 * to "Zend/Example/Class.php"). 42 * 43 * If the file was not found in the $dirs, or if no $dirs were specified, 44 * it will attempt to load it from PHP's include_path. 45 * 46 * @param string $class - The full class name of a Zend component. 47 * @param string|array $dirs - OPTIONAL Either a path or an array of paths 48 * to search. 49 * @return void 50 * @throws Zend_Exception 51 */ 52 public static function loadClass($class, $dirs = null) 53 { 54 if (class_exists($class, false) || interface_exists($class, false)) { 55 return; 56 } 57 58 if ((null !== $dirs) && !is_string($dirs) && !is_array($dirs)) { 59 throw new Zend_Exception('Directory argument must be a string or an array'); 60 } 61 62 $file = self::standardiseFile($class); 63 64 if (!empty($dirs)) { 65 // use the autodiscovered path 66 $dirPath = dirname($file); 67 if (is_string($dirs)) { 68 $dirs = explode(PATH_SEPARATOR, $dirs); 69 } 70 foreach ($dirs as $key => $dir) { 71 if ($dir == '.') { 72 $dirs[$key] = $dirPath; 73 } else { 74 $dir = rtrim($dir, '\\/'); 75 $dirs[$key] = $dir . DIRECTORY_SEPARATOR . $dirPath; 76 } 77 } 78 $file = basename($file); 79 self::loadFile($file, $dirs, true); 80 } else { 81 self::loadFile($file, null, true); 82 } 83 84 if (!class_exists($class, false) && !interface_exists($class, false)) { 85 throw new Zend_Exception("File \"$file\" does not exist or class \"$class\" was not found in the file"); 86 } 87 } 88 89 /** 90 * Loads a PHP file. This is a wrapper for PHP's include() function. 91 * 92 * $filename must be the complete filename, including any 93 * extension such as ".php". Note that a security check is performed that 94 * does not permit extended characters in the filename. This method is 95 * intended for loading Zend Framework files. 96 * 97 * If $dirs is a string or an array, it will search the directories 98 * in the order supplied, and attempt to load the first matching file. 99 * 100 * If the file was not found in the $dirs, or if no $dirs were specified, 101 * it will attempt to load it from PHP's include_path. 102 * 103 * If $once is TRUE, it will use include_once() instead of include(). 104 * 105 * @param string $filename 106 * @param string|array $dirs - OPTIONAL either a path or array of paths 107 * to search. 108 * @param boolean $once 109 * @return boolean 110 * @throws Zend_Exception 111 */ 112 public static function loadFile($filename, $dirs = null, $once = false) 113 { 114 self::_securityCheck($filename); 115 116 /** 117 * Search in provided directories, as well as include_path 118 */ 119 $incPath = false; 120 if (!empty($dirs) && (is_array($dirs) || is_string($dirs))) { 121 if (is_array($dirs)) { 122 $dirs = implode(PATH_SEPARATOR, $dirs); 123 } 124 $incPath = get_include_path(); 125 set_include_path($dirs . PATH_SEPARATOR . $incPath); 126 } 127 128 /** 129 * Try finding for the plain filename in the include_path. 130 */ 131 if ($once) { 132 include_once $filename; 133 } else { 134 include $filename; 135 } 136 137 /** 138 * If searching in directories, reset include_path 139 */ 140 if ($incPath) { 141 set_include_path($incPath); 142 } 143 144 return true; 145 } 146 147 /** 148 * Returns TRUE if the $filename is readable, or FALSE otherwise. 149 * This function uses the PHP include_path, where PHP's is_readable() 150 * does not. 151 * 152 * Note from ZF-2900: 153 * If you use custom error handler, please check whether return value 154 * from error_reporting() is zero or not. 155 * At mark of fopen() can not suppress warning if the handler is used. 156 * 157 * @param string $filename 158 * @return boolean 159 */ 160 public static function isReadable($filename) 161 { 162 if (is_readable($filename)) { 163 // Return early if the filename is readable without needing the 164 // include_path 165 return true; 166 } 167 168 if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' 169 && preg_match('/^[a-z]:/i', $filename) 170 ) { 171 // If on windows, and path provided is clearly an absolute path, 172 // return false immediately 173 return false; 174 } 175 176 foreach (self::explodeIncludePath() as $path) { 177 if ($path == '.') { 178 if (is_readable($filename)) { 179 return true; 180 } 181 continue; 182 } 183 $file = $path . '/' . $filename; 184 if (is_readable($file)) { 185 return true; 186 } 187 } 188 return false; 189 } 190 191 /** 192 * Explode an include path into an array 193 * 194 * If no path provided, uses current include_path. Works around issues that 195 * occur when the path includes stream schemas. 196 * 197 * @param string|null $path 198 * @return array 199 */ 200 public static function explodeIncludePath($path = null) 201 { 202 if (null === $path) { 203 $path = get_include_path(); 204 } 205 206 if (PATH_SEPARATOR == ':') { 207 // On *nix systems, include_paths which include paths with a stream 208 // schema cannot be safely explode'd, so we have to be a bit more 209 // intelligent in the approach. 210 $paths = preg_split('#:(?!//)#', $path); 211 } else { 212 $paths = explode(PATH_SEPARATOR, $path); 213 } 214 return $paths; 215 } 216 217 /** 218 * spl_autoload() suitable implementation for supporting class autoloading. 219 * 220 * Attach to spl_autoload() using the following: 221 * <code> 222 * spl_autoload_register(array('Zend_Loader', 'autoload')); 223 * </code> 224 * 225 * @deprecated Since 1.8.0 226 * @param string $class 227 * @return string|false Class name on success; false on failure 228 */ 229 public static function autoload($class) 230 { 231 trigger_error(__CLASS__ . '::' . __METHOD__ . ' is deprecated as of 1.8.0 and will be removed with 2.0.0; use Zend_Loader_Autoloader instead', E_USER_NOTICE); 232 try { 233 @self::loadClass($class); 234 return $class; 235 } catch (Exception $e) { 236 return false; 237 } 238 } 239 240 /** 241 * Register {@link autoload()} with spl_autoload() 242 * 243 * @deprecated Since 1.8.0 244 * @param string $class (optional) 245 * @param boolean $enabled (optional) 246 * @return void 247 * @throws Zend_Exception if spl_autoload() is not found 248 * or if the specified class does not have an autoload() method. 249 */ 250 public static function registerAutoload($class = 'Zend_Loader', $enabled = true) 251 { 252 trigger_error(__CLASS__ . '::' . __METHOD__ . ' is deprecated as of 1.8.0 and will be removed with 2.0.0; use Zend_Loader_Autoloader instead', E_USER_NOTICE); 253 $autoloader = Zend_Loader_Autoloader::getInstance(); 254 $autoloader->setFallbackAutoloader(true); 255 256 if ('Zend_Loader' != $class) { 257 self::loadClass($class); 258 $methods = get_class_methods($class); 259 if (!in_array('autoload', (array) $methods)) { 260 throw new Zend_Exception("The class \"$class\" does not have an autoload() method"); 261 } 262 263 $callback = array($class, 'autoload'); 264 265 if ($enabled) { 266 $autoloader->pushAutoloader($callback); 267 } else { 268 $autoloader->removeAutoloader($callback); 269 } 270 } 271 } 272 273 /** 274 * Ensure that filename does not contain exploits 275 * 276 * @param string $filename 277 * @return void 278 * @throws Zend_Exception 279 */ 280 protected static function _securityCheck($filename) 281 { 282 /** 283 * Security check 284 */ 285 if (preg_match('/[^a-z0-9\\/\\\\_.:-]/i', $filename)) { 286 throw new Zend_Exception('Security check: Illegal character in filename'); 287 } 288 } 289 290 /** 291 * Attempt to include() the file. 292 * 293 * include() is not prefixed with the @ operator because if 294 * the file is loaded and contains a parse error, execution 295 * will halt silently and this is difficult to debug. 296 * 297 * Always set display_errors = Off on production servers! 298 * 299 * @param string $filespec 300 * @param boolean $once 301 * @return boolean 302 * @deprecated Since 1.5.0; use loadFile() instead 303 */ 304 protected static function _includeFile($filespec, $once = false) 305 { 306 if ($once) { 307 return include_once $filespec; 308 } else { 309 return include $filespec ; 310 } 311 } 312 313 /** 314 * Standardise the filename. 315 * 316 * Convert the supplied filename into the namespace-aware standard, 317 * based on the Framework Interop Group reference implementation: 318 * http://groups.google.com/group/php-standards/web/psr-0-final-proposal 319 * 320 * The filename must be formatted as "$file.php". 321 * 322 * @param string $file - The file name to be loaded. 323 * @return string 324 */ 325 public static function standardiseFile($file) 326 { 327 $fileName = ltrim($file, '\\'); 328 $file = ''; 329 $namespace = ''; 330 if ($lastNsPos = strripos($fileName, '\\')) { 331 $namespace = substr($fileName, 0, $lastNsPos); 332 $fileName = substr($fileName, $lastNsPos + 1); 333 $file = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR; 334 } 335 $file .= str_replace('_', DIRECTORY_SEPARATOR, $fileName) . '.php'; 336 return $file; 337 } 338} 339