/
var
/
www
/
html
/
freshsugar25
/
include
/
utils
/
Upload File
HOME
<?php /* * Your installation or use of this SugarCRM file is subject to the applicable * terms available at * http://support.sugarcrm.com/Resources/Master_Subscription_Agreements/. * If you do not agree to all of the applicable terms or do not have the * authority to bind the entity as an authorized representative, then do not * install or use this SugarCRM file. * * Copyright (C) SugarCRM Inc. All rights reserved. */ require_once 'include/utils/file_utils.php'; require_once 'include/utils/sugar_file_utils.php'; /** * File and class loader * @api */ class SugarAutoLoader { public const CLASS_CACHE_FILE = 'class_map.php'; /** * Root namespace */ public const NS_ROOT = 'Sugarcrm\\Sugarcrm\\'; /** * Direct class mapping * @var array name => path */ public static $classMap = []; /** * Flag class map as dirty to flush update to cache * @var boolean */ public static $classMapDirty = false; /** * Classes not to be loaded * @var array name => boolean */ public static $noAutoLoad = [ // this one is generated by ViewFactory for classic view, but if never actually exists 'UsersViewClassic' => true, // WTF on duplicate class names ... 'Popup_Picker' => true, 'SubPanelViewTeams' => true, 'ViewHistory' => true, 'ViewIndex' => true, 'SugarTab' => true, 'EditView' => true, // has also EditView2 'SearchForm' => true, // has also SearchForm2 and both are used it seems :S ]; /** * Class prefixes * Classes are loaded by prefix: * SugarAclFoo.php => data/acl/SugarACLFoo.php * @var array prefix => directory */ public static $prefixMap = [ 'SugarACL' => 'data/acl/', 'SugarWidget' => 'include/generic/SugarWidgets/', 'Zend_' => 'vendor/', 'SugarJob' => 'include/SugarQueue/jobs/', 'MetaDataContext' => 'modules/ModuleBuilder/parsers/MetaDataContext/', 'MetaDataManager' => 'include/MetaDataManager/', ]; /** * Namespace directory mapping * - Prefix should include trailing \ * - Directory should NOT contain trailing / * * Order is important on overlapping prefixes, first match wins ! * 'Sugarcrm\\lib\\' => 'include' * 'Sugarcrm\\' => '' * ... * * To add namespaces dynamically it's advised to use `self::addNamespace` * as this method will ensure a correct order from more to less * specific namespace prefixes. Also every prefix supports multiple * paths which are iterated in order to find a match. * * A better way to introduce new namespaces is by loading your libraries * through composer.json. The namespace definition as per the autoload * section will be automatically honored by this autoloader and avoids * the need of programmatically adding new namespaces using addNameSpace. * * @var array nsPrefix => array(directories) */ public static $namespaceMap = []; /** * @var array nsPrefix => array(directories) * @see self::$namespaceMap */ public static $namespaceMapPsr4 = []; /** * Class loading directories * Classes in these dirs are loaded by class name: * Foo -> $dir/Foo.php * @var array paths */ public static $dirMap = [ 'clients/base/api/', 'data/visibility/', 'data/Relationships/', 'data/duplicatecheck/', 'include/api/', 'include/CalendarEvents/', 'include/SugarSearchEngine/', 'include/', 'modules/Mailer/', 'modules/Calendar/', ]; /** * Directories to exclude form mapping * @var array */ public static $exclude = [ 'cache', 'custom/history', '.idea', 'custom/blowfish', 'custom/Extension', 'custom/backup', 'custom/modulebuilder', 'tests', 'examples', 'docs', 'upload', 'portal', 'vendor/HTMLPurifier', 'vendor/ytree', 'vendor/bin', ]; /** * Extensions to include in mapping * @var array */ public static $exts = [ 'php', 'tpl', 'html', 'js', 'override', 'gif', 'png', 'jpg', 'tif', 'bmp', 'ico', 'css', 'xml', 'hbs', 'less', ]; /** * Copy of extension map * @var array */ public static $extensions = []; /** * @var boolean Developer mode flag */ public static $devMode = false; /** * Enables writing to class_map.php */ public static bool $enableClassMapUpdates = true; protected static $baseDirs; protected static $ds = DIRECTORY_SEPARATOR; /** * Initialize the loader * * @param boolean $useConfig Used for UnitTest Runs since we don't have the stack installed */ public static function init($useConfig = true) { if ($useConfig === true) { require_once 'include/SugarObjects/SugarConfig.php'; $config = SugarConfig::getInstance(); /* * When development mode is enabled, we bypass the usage * of the filemap and build the classmap dynamically on * every page load. We drop both cache file to make sure * when devMode is disabled again that the system is * properly initialized again withour the need for * running a QuickRepairRebuild. */ self::$devMode = $config->get('developerMode', false); if (self::$devMode) { @unlink(sugar_cached(self::CLASS_CACHE_FILE)); } // Extensions included from config $exts = $config->get('autoloader.exts'); if (is_array($exts)) { self::$exts += $exts; } // Excludes from config $exclude = $config->get('autoloader.exclude'); if (is_array($exclude)) { self::$exclude += $exclude; } } else { // since we are ignoring the config, we have to use DevMode self::$devMode = true; } self::loadClassMap(); self::registerAutoload(); self::loadExts(); } /** * Register SugarAutoLoader * @param boolean $prepend Prepend on top of spl_autoload stack */ public static function registerAutoload($prepend = false) { spl_autoload_register(['SugarAutoLoader', 'autoload'], true, $prepend); } /** * Load a class * @param string $class Class name * @return boolean Success? */ public static function autoload($class) { global $beanFiles; if (!empty(self::$noAutoLoad[$class])) { return false; } // try known classes if (isset(self::$classMap[$class])) { if (self::$classMap[$class]) { // No need for a file_exists, if it is in the map we have found it before require_once self::$classMap[$class]; return true; } return false; } // try namespaces if (false !== strpos($class, '\\')) { if ($file = self::getFilenameForFQCN($class)) { if (self::load($file)) { self::$classMap[$class] = $file; self::$classMapDirty = true; return true; } } self::$classMap[$class] = false; self::$classMapDirty = true; return false; } // Try known modules if (!empty($beanFiles[$class]) && file_exists($beanFiles[$class])) { require_once $beanFiles[$class]; return true; } // Split on _, capitalize elements and make a path // foo_bar -> Foo/Bar. $class_file = join('/', array_map('ucfirst', explode('_', $class))); // Try known prefixes foreach (self::$prefixMap as $prefix => $dir) { if (strncasecmp($prefix, $class, strlen($prefix)) === 0) { if ($file = self::requireWithCustom("{$dir}$class_file.php")) { self::$classMap[$class] = $file; self::$classMapDirty = true; return true; } else { break; } } } // Special cases // Special case because lookup goes to $_REQUEST['module'] if ($file = self::getFilenameForViewClass($class)) { self::$classMap[$class] = $file; self::$classMapDirty = true; return true; } // Special case because widget name can be lowercased if ($file = self::getFilenameForSugarWidget($class)) { self::$classMap[$class] = $file; self::$classMapDirty = true; return true; } // Special case because it checks by ending in Layout if ($file = self::getFilenameForLayoutClass($class)) { self::$classMap[$class] = $file; self::$classMapDirty = true; return true; } if ($file = self::getFilenameForExpressionClass($class)) { self::$classMap[$class] = $file; self::$classMapDirty = true; return true; } // Try known dirs foreach (self::$dirMap as $dir) { // include/Class.php if ($file = self::requireWithCustom("{$dir}$class_file.php")) { self::$classMap[$class] = $file; self::$classMapDirty = true; return true; } // include/Class/Class.php // Note here we don't use $class_file since using path twice would not make sense: // Foo/Bar/Foo/Bar.php vs. Foo_Bar/Foo_Bar.php if ($file = self::requireWithCustom("{$dir}$class/$class.php")) { self::$classMap[$class] = $file; self::$classMapDirty = true; return true; } // try include/Foo_Bar.php as a last resort if ($file = self::requireWithCustom("{$dir}$class.php")) { self::$classMap[$class] = $file; self::$classMapDirty = true; return true; } } self::$classMap[$class] = false; self::$classMapDirty = true; return false; } /** * Return filename for given Fully Qualified Class Name * * @param string $class FQCN without leading backslash * @return string|false */ public static function getFilenameForFQCN($class) { // PSR-4 has precendence if ($file = self::getFileNamePsr4($class)) { return $file; } return self::getFileNamePsr0($class); } /** * PSR-0 support http://www.php-fig.org/psr/psr-0/ * * @param string $class Fully Qualified Class Name * @return string|false */ public static function getFileNamePsr0($class) { foreach (self::$namespaceMap as $namespace => $paths) { foreach ($paths as $path) { if (strpos($class, (string) $namespace) === 0) { $path = empty($path) ? '' : $path . '/'; if (false !== $pos = strrpos($class, '\\')) { $path .= str_replace('\\', '/', substr($class, 0, $pos)) . '/'; $path .= str_replace('_', '/', substr($class, $pos + 1)) . '.php'; } else { $path .= str_replace('_', '/', $class) . '.php'; } if (file_exists($path)) { return $path; } } } } return false; } /** * PSR-4 support http://www.php-fig.org/psr/psr-4/ * * @param string $class Fully Qualified Class Name * @return mixed(string|boolean) */ public static function getFileNamePsr4($class) { foreach (self::$namespaceMapPsr4 as $prefix => $paths) { foreach ($paths as $path) { if (strpos($class, (string) $prefix) === 0) { $path = empty($path) ? '' : $path . '/'; $path .= str_replace('\\', '/', str_replace($prefix, '', $class)) . '.php'; if (file_exists($path)) { return $path; } } } } return false; } /** * Load layout class from include/MetaDataManager/layouts * @param string $class * @return string|false */ protected static function getFilenameForLayoutClass($class) { if (substr($class, -6) == 'Layout') { return self::requireWithCustom("include/MetaDataManager/layouts/$class.php"); } return false; } /** * Add directory for loading classes * Directory should include trailing / * @param string $dir */ public static function addDirectory($dir) { self::$dirMap[] = $dir; } /** * Add namespace prefix directory mapping * * @param string $prefix * @param string $dir * @param string $type `psr0` (default) or `psr4` */ public static function addNamespace($prefix, $dir, $type = 'psr0') { // select which map to use $map = ($type === 'psr4') ? 'namespaceMapPsr4' : 'namespaceMap'; // enforce trailing \ $prefix = rtrim($prefix, '\\') . '\\'; // remove trailing / and normalize path $dir = rtrim(self::normalizeFilePath($dir), '/'); // use a list of paths per prefix self::${$map}[$prefix][] = $dir; // The order of self::$namespace is important because the first match // will win. When registering new namespace dynamically we need to make // sure this array is ordered from more to less specific. uksort(self::${$map}, function ($val1, $val2) { $level1 = substr_count($val1, '\\'); $level2 = substr_count($val2, '\\'); if ($level1 > $level2) { return -1; } elseif ($level1 < $level2) { return 1; } else { // if levels are the same, sort alphabetically for predictable result return strcasecmp($val1, $val2); } }); } /** * Add directory for loading classes by prefix * Directory should include trailing / * @param string $prefix * @param string $dir */ public static function addPrefixDirectory($prefix, $dir) { self::$prefixMap[$prefix] = $dir; } protected static function getFilenameForViewClass($class) { $module = false; if (!empty($_REQUEST['module']) && substr($class, 0, strlen($_REQUEST['module'])) === $_REQUEST['module']) { //This is a module view $module = $_REQUEST['module']; $class = substr($class, strlen($module)); } $view = strtolower(substr($class, 4)); $viewClass = substr($class, 0, 4); if ($viewClass == 'View' && $view !== '') { if ($module) { return self::requireWithCustom("modules/$module/views/view.$view.php"); } else { return self::requireWithCustom("include/MVC/View/views/view.$view.php"); } } return false; } /** * getFilenameForSugarWidget * * This method attempts to autoload classes starting with name "SugarWidget". It first checks for the file * in custom/include/generic/SugarWidgets directory and if not found defaults to include/generic/SugarWidgets. * This method is used so that we can easily customize and extend these SugarWidget classes. * * Can not be served by prefixMap because of the lowercasing in class names. * * @static * @param $class String name of the class to load * @return String file of the SugarWidget class; false if none found */ protected static function getFilenameForSugarWidget($class) { //Only bother to check if the class name starts with SugarWidget if (strpos($class, 'SugarWidgetField') !== false) { //We need to lowercase the portion after SugarWidgetField $name = substr($class, 16); if (empty($name)) { return false; } $class = 'SugarWidgetField' . strtolower($name); return self::requireWithCustom("include/generic/SugarWidgets/{$class}.php"); } return false; } /** * Load file if exists * @param string $file * @return boolean True if file was loaded */ public static function load($file) { if (file_exists($file)) { require_once $file; return true; } return false; } /** * Load file either from custom, if exists, or from core * @param string $file filename * @param bool $both Do we want both? * @return was any file loaded? */ public static function requireWithCustom($file, $both = false) { if (!check_file_name($file)) { return false; } if (file_exists("custom/$file")) { if ($both) { // when loading both, core file goes first so custom can override it // however we check for custom first and if $both not set load only it if (file_exists($file)) { require_once $file; } } require_once "custom/$file"; return "custom/$file"; } else { if (file_exists($file)) { require_once $file; return $file; } } return false; } /** * Get list of existing files and their customizations. * @param ... $files * @return array Existing files and customizations. Customizations go after files. */ public static function existing() { $files = func_get_args(); $out = []; foreach ($files as $file) { if (empty($file)) { continue; } if (is_array($file)) { $out += call_user_func_array(['SugarAutoLoader', 'existing'], $file); continue; } if (file_exists($file)) { $out[] = $file; } } return $out; } /** * Get list of existing files and their customizations. * @param ... $files * @return array Existing files and customizations. Customizations go after files. */ public static function existingCustom() { $files = func_get_args(); $out = []; foreach ($files as $file) { if (empty($file)) { continue; } if (is_array($file)) { $out += call_user_func_array(['SugarAutoLoader', 'existingCustom'], $file); continue; } if (file_exists($file)) { $out[] = $file; } if (substr($file, 0, 7) != 'custom/' && file_exists("custom/$file")) { $out[] = "custom/$file"; } } return $out; } /** * Get customized file or core file. * Returns only the last existing variant, custom if exists * @param ... $files * @return string|null Last existing file out of given arguments */ public static function existingCustomOne() { $files = func_get_args(); $out = call_user_func_array(['SugarAutoLoader', 'existingCustom'], $files); if (empty($out)) { return null; } else { return array_pop($out); } } /** * Lookup filename in a list of paths. Paths are checked with and without custom/ * @param array $paths * @param string $file * @return string|bool Filename found or false */ public static function lookupFile($paths, $file) { foreach ($paths as $path) { $fullname = "$path/$file"; if (file_exists("custom/$fullname")) { return "custom/$fullname"; } if (file_exists($fullname)) { return $fullname; } } return false; } /** * getFilenameForExpressionClass * * Used to autoload classes that end in "Expression". It will check in all directories found in * custom/include/Expressions/Expression and include/Expressions/Expression . * This method is allows for easy loading of arbitrary expression classes by the SugarLogic Expression parser. * * @static * @param $class String name of the class to load * @return String file of the Expression class; false if none found */ protected static function getFilenameForExpressionClass($class) { if (substr($class, -10) == 'Expression') { if ($file = self::requireWithCustom("include/Expressions/Expression/{$class}.php")) { return $file; } $types = ['Boolean', 'Date', 'Enum', 'Generic', 'Numeric', 'Relationship', 'String', 'Time']; foreach ($types as $type) { if ($file = self::requireWithCustom("include/Expressions/Expression/{$type}/{$class}.php")) { return $file; } } } return false; } /** * Load all classes in self::$classMap * * @deprecated */ public static function loadAll() { $beanList = null; foreach (self::$classMap as $class => $file) { require_once $file; } if (isset($GLOBALS['beanFiles'])) { $files = $GLOBALS['beanFiles']; } else { include 'include/modules.php'; $files = $beanList; } foreach ($files as $class => $file) { require_once $file; } } /** * Get viewdefs file name using the following logic: * 1. Check custom/module/metadata/$varname.php * 2. If not there, check metafiles.php * 3. If still not found, use module/metadata/$varname.php * This is used for Studio-enabled definitions. Only one file is loaded * because Studio should be able to delete fields. * @param string $module * @param string $varname Name of the vardef file (listviewdef, etc.) - no .php * @return string|null Suitable metadata file or null */ public static function loadWithMetafiles($module, $varname) { $sanitizedModule = basename($module); $sanitizedVarname = basename($varname); $vardef = self::existingCustomOne('modules/' . $sanitizedModule . '/metadata/' . $sanitizedVarname . '.php'); if (!empty($vardef) && substr($vardef, 0, 7) == 'custom/') { // custom goes first, because this is how Studio overrides defaults return $vardef; } // otherwise check metadata global $metafiles; if (!isset($metafiles[$sanitizedModule])) { $meta = self::existingCustomOne('modules/' . $sanitizedModule . '/metadata/metafiles.php'); if ($meta) { require $meta; } } if (!empty($metafiles[$sanitizedModule][$sanitizedVarname])) { $defs = self::existing($metafiles[$sanitizedModule][$sanitizedVarname], $vardef); } else { $defs = self::existing($vardef); } if (!$defs) { return null; } else { return $defs[0]; } } /** * Load search fields * Search fields are loaded differently since they are not Studio metadata file, * so they are combined instead of being overloaded. * NOTE: unlike generic loadWithMetafiles, this one returns defs, not filenames * Also note that even though $module is given, the defs are not in $searchFields but in $searchFields[$module] * for BC reasons. * @param string $module * @return array searchFields def */ public static function loadSearchFields($module) { // load metadata first global $metafiles; if (!isset($metafiles[$module])) { $meta = self::existingCustomOne('modules/' . $module . '/metadata/metafiles.php'); if ($meta) { require $meta; } } // Then get all files that are revevant if (!empty($metafiles[$module]['searchfields'])) { $defs = $metafiles[$module]['searchfields']; } else { $defs = "modules/$module/metadata/SearchFields.php"; } foreach (self::existingCustom($defs) as $file) { require $file; } if (empty($searchFields)) { return []; } return $searchFields; } /** * Load popupdefs metadata file * Allows to override 'popupdefs' with $metadata variable * NOTE: unlike generic loadWithMetafiles, this one returns defs, not filenames * @param string $module * @param string $metadata metadata name override * @return array popup defs data or NULL */ public static function loadPopupMeta($module, $metadata = null) { $popupMeta = null; $defs = null; if ($metadata == 'undefined' || strpos($metadata, '..') !== false) { $metadata = null; } if (!empty($metadata)) { $defs = SugarAutoLoader::loadWithMetafiles($module, $metadata); } if (!$defs) { $defs = SugarAutoLoader::loadWithMetafiles($module, 'popupdefs'); } if ($defs) { require $defs; return $popupMeta; } return []; } /** * Get metadata file for an extension * see extensions.php for the list * @param string $extname Extension name * @param string $module Module to apply to * @return boolean|string File to load, false if none */ public static function loadExtension($extname, $module = 'application') { if (empty(self::$extensions[$extname])) { return false; } $ext = self::$extensions[$extname]; if (empty($ext['file']) || empty($ext['extdir'])) { // custom rebuilds, can't handle return false; } if (isset($ext['module'])) { $module = $ext['module']; } if ($module == 'application') { $file = "custom/application/Ext/{$ext['extdir']}/{$ext['file']}"; } else { $file = "custom/modules/{$module}/Ext/{$ext['extdir']}/{$ext['file']}"; } if (file_exists($file)) { return $file; } return false; } /** * Check if file exists in the cache * @param string $filename * @return boolean * @deprecated use file_exists() instead */ public static function fileExists($filename) { self::logDeprecation(__METHOD__ . ' is deprecated'); return file_exists($filename); } /** * Get all files in directory from cache * @param string $dir * @param bool $get_dirs Get directories and not files * @param string $extension Get only files with given extension * @param boolean $recursive Scan directory recursively * @return array List of files */ public static function getDirFiles($dir, $get_dirs = false, $extension = null, $recursive = false) { $data = self::scanSubDir($dir); // remove leading . if present $extension = ltrim((string)$extension, '.'); $dir = rtrim($dir, '/'); $parts = explode('/', $dir); foreach ($parts as $part) { if (empty($part)) { continue; // allow sequences of /s } if (!isset($data[$part])) { return []; } $data = $data[$part]; } if (!is_array($data)) { return []; } return self::flatten($dir, $data, $get_dirs, $extension, $recursive); } /** * Flattens the result of self::getDirFiles() * * @param string $dir Base directory * @param array $data Tree data * @param boolean $get_dirs * @param string $extension Filter files by extension * @param boolean $recursive Use data from subdirectories * * @return array Flattened data */ protected static function flatten($dir, array $data, $get_dirs, $extension, $recursive) { $result = []; foreach ($data as $file => $nodes) { // check extension if given if (!empty($extension) && pathinfo($file, PATHINFO_EXTENSION) != $extension) { continue; } $path = $dir . '/' . $file; // get dirs or files depending on $get_dirs if (is_array($nodes) == $get_dirs) { $result[] = $path; } if ($recursive && is_array($nodes)) { $result = array_merge( $result, self::flatten($path, $nodes, $get_dirs, $extension, $recursive) ); } } return $result; } /** * Get list of files in this dir and custom duplicate of it * @param string $dir * @param bool $get_dirs Get directories and not files * @return array */ public static function getFilesCustom($dir, $get_dirs = false, $extension = null) { return array_merge(self::getDirFiles($dir, $get_dirs, $extension), self::getDirFiles("custom/$dir", $get_dirs, $extension)); } /** * Build file cache */ public static function buildCache() { // Rebuild the class cache so that it can find any new classes in the file map self::buildClassCache(); } /** * Load cached file map * @deprecated */ public static function loadFileMap() { self::logDeprecation(__METHOD__ . ' is deprecated'); } /** * Build class map cache */ public static function buildClassCache() { $class_map = []; foreach (self::existingCustom('include/utils/class_map.php') as $file) { require $file; } // Don't save to disk in development mode as the map will be ignored // and its content will not be incremental. if (!self::$devMode && self::$enableClassMapUpdates) { write_array_to_file('class_map', $class_map, sugar_cached(self::CLASS_CACHE_FILE)); } self::$classMap = $class_map; self::$classMapDirty = false; } /** * Load cached class map */ public static function loadClassMap() { $class_map = null; // in development mode we start with a clean slate if (!self::$devMode) { @include sugar_cached(self::CLASS_CACHE_FILE); } if ($class_map === null) { // oops, something happened to cache // try to rebuild self::buildClassCache(); } else { self::$classMap = $class_map; self::$classMapDirty = false; } } /** * Load extensions map */ protected static function loadExts() { $extensions = null; include 'ModuleInstall/extensions.php'; self::$extensions = $extensions; } /** * Add filename to list of existing files * @param string $filename * @param bool $save should we save it to file? * @param bool $dir should it be empty directory? * @deprecated */ public static function addToMap($filename, $save = true, $dir = false) { self::logDeprecation(__METHOD__ . ' is deprecated'); } /** * Delete file from the map * Mainly for use in tests * @param string $filename * @param bool $save should we save it to file? * @deprecated */ public static function delFromMap($filename, $save = true) { self::logDeprecation(__METHOD__ . ' is deprecated'); } /** * Scan directory and build the list of files it contains * @param string $path * @return array Files data */ public static function scanDir($path) { $path = rtrim($path, '/'); if (in_array($path, self::$exclude) || !file_exists('./' . $path)) { return []; } $iter = new DirectoryIterator('./' . $path); $data = []; foreach ($iter as $item) { if ($item->isDot()) { continue; } $filename = $item->getFilename(); if ($item->isDir()) { $filepath = ($path === '') ? $filename : $path . '/' . $filename; $data[$filename] = self::scanDir($filepath); } else { if (!in_array(pathinfo($filename, PATHINFO_EXTENSION), self::$exts)) { continue; } $data[$filename] = 1; } } return $data; } /** * This method is equivalent to `self::scanDir`, but allows to scan * a specific sub directory (leaf) only while still returning the * same structure as returned by `self::scanDir` from the base dir. * * This is an internal function and should only be called when * using developer mode. * * @param string $path * @return array Files data */ protected static function scanSubDir($path) { $data = self::scanDir($path); // append base path structure $subDirs = array_reverse(array_filter(explode('/', $path))); $data = array_reduce($subDirs, function ($c, $i) use ($data) { return [$i => (empty($c) ? $data : $c)]; }); return $data; } /** * Get custom class name if that exists or original one if not * @param string $className Class name, legacy or FQCN * @return string Classname */ public static function customClass($className) { if (strpos($className, '\\') !== false) { $customClass = self::getCustomClassFQCN($className); } else { $customClass = 'Custom' . $className; } if ($customClass && class_exists($customClass)) { return $customClass; } return $className; } /** * Get custom fully qualified class name, return false if not able * to transform the given class name into its custom counterpart. * Will also return false if given class name is already in custom * format. * @param string $className Fully qualified class name * @return string|false */ public static function getCustomClassFQCN($className) { $customBase = self::NS_ROOT . 'custom\\'; if (strpos($className, self::NS_ROOT) === 0 && strpos($className, $customBase) === false) { return str_replace(self::NS_ROOT, $customBase, $className); } return false; } /** * Unlink and delete from map * To use mainly for tests * @param string $filename * @param bool $save Save map to file? * @return bool Success? * @deprecated use unlink() instead */ public static function unlink($filename, $save = false) { self::logDeprecation(__METHOD__ . ' is deprecated'); unlink($filename); } /** * Create empty file and add to map * To use mainly for tests * @param string $filename * @param bool $save Save map to file? * @return bool Success? * @deprecated use sugar_touch() instead */ public static function touch($filename, $save = false) { self::logDeprecation(__METHOD__ . ' is deprecated'); return sugar_touch($filename); } /** * Put data to file and add to map * To use mainly for tests * @param string $filename * @param bool $save Save map to file? * @return bool Success? * @deprecated use file_put_contents() instead */ public static function put($filename, $data, $save = false) { self::logDeprecation(__METHOD__ . ' is deprecated'); return file_put_contents($filename, $data) !== false; } /** * Ensure the directory exists * @param string $dir * @return boolean */ public static function ensureDir($dir) { if (file_exists($dir)) { return true; } return sugar_mkdir($dir, null, true); } /** * Save the file map to disk * @deprecated */ public static function saveMap() { self::logDeprecation(__METHOD__ . ' is deprecated'); } /** * Save the file map to disk */ public static function saveClassMap() { if (!self::$devMode && self::$classMapDirty && !empty(self::$classMap) && self::$enableClassMapUpdates) { write_array_to_file('class_map', self::$classMap, sugar_cached(self::CLASS_CACHE_FILE)); self::$classMapDirty = false; } } /** * Cleans up a filepath, normalizing path separators and removing extras * * @param string $filename The name of the file to work on * @return string */ public static function normalizeFilePath($filename) { if (!isset(self::$baseDirs)) { self::$baseDirs = [SUGAR_BASE_DIR]; if (defined('SHADOW_INSTANCE_DIR')) { self::$baseDirs[] = SHADOW_INSTANCE_DIR; } } // Normalize directory separators if (self::$ds != '/') { $filename = str_replace(self::$ds, '/', $filename); } // Remove base dir - Composer always has absolute paths. foreach (self::$baseDirs as $baseDir) { $filename = str_replace($baseDir . '/', '', $filename, $count); if ($count > 0) { break; } } // Remove repeated separators $filename = preg_replace('#(/)(\1+)#', '/', $filename); return $filename; } protected static function logDeprecation(string $message): void { if (isset($GLOBALS['log']) && $GLOBALS['log'] instanceof \LoggerManager) { $GLOBALS['log']->deprecated($message); } else { trigger_error($message, E_USER_DEPRECATED); } } }