diff --git a/src/Control/HTTPApplication.php b/src/Control/HTTPApplication.php index d9c1373ae..d6be9abf6 100644 --- a/src/Control/HTTPApplication.php +++ b/src/Control/HTTPApplication.php @@ -6,12 +6,15 @@ use SilverStripe\Control\Middleware\HTTPMiddlewareAware; use SilverStripe\Core\Application; use SilverStripe\Core\Environment; use SilverStripe\Core\Kernel; +use SilverStripe\Core\Manifest\Module; use SilverStripe\Core\Startup\FlushDiscoverer; use SilverStripe\Core\Startup\CompositeFlushDiscoverer; use SilverStripe\Core\Startup\CallbackFlushDiscoverer; use SilverStripe\Core\Startup\RequestFlushDiscoverer; use SilverStripe\Core\Startup\ScheduledFlushDiscoverer; use SilverStripe\Core\Startup\DeployFlushDiscoverer; +use SilverStripe\Dev\Deprecation; +use SilverStripe\GraphQL\TypeCreator; class HTTPApplication implements Application { @@ -130,6 +133,10 @@ class HTTPApplication implements Application return $this->callMiddleware($request, function ($request) use ($callback, $flush) { // Pre-request boot $this->getKernel()->boot($flush); + + // This is the earliest point we can do this and guarantee it's hit exactly once per request. + $this->warnAboutDeprecatedSetups(); + return call_user_func($callback, $request); }); } catch (HTTPResponse_Exception $ex) { @@ -138,4 +145,48 @@ class HTTPApplication implements Application $this->getKernel()->shutdown(); } } + + /** + * Trigger deprecation notices for legacy configuration which is deprecated but + * doesn't have deprecation notices directly on the relevant API + * + * Don't remove this method even if it's just a no-op - we'll reuse this mechanism + * in the future as needed. + */ + private function warnAboutDeprecatedSetups() + { + // TypeCreator is a class unique to GraphQL v3 - we use it in other areas to detect + // which version is being used. + if (class_exists(TypeCreator::class)) { + Deprecation::notice( + '4.13.0', + 'silverstripe/graphql 3.x is deprecated. Upgrade to 4.x instead.' + . ' See https://docs.silverstripe.org/en/4/upgrading/upgrading_to_graphql_4/', + Deprecation::SCOPE_GLOBAL + ); + } + + // The alternate_public_dir config property is deprecated, but because it's + // always fetched it'll throw a deprecation warning whether you've set it or not. + // There are also multiple mechanisms which can result in this bad configuration. + if (PUBLIC_DIR !== 'public' || Director::publicDir() !== PUBLIC_DIR) { + Deprecation::notice( + '4.13.0', + 'Use of a public webroot other than "public" is deprecated.' + . ' See https://docs.silverstripe.org/en/4/changelogs/4.1.0#public-folder/', + Deprecation::SCOPE_GLOBAL + ); + } + + // This change of defaults has no other deprecation notice being emitted currently. + $project = new Module(BASE_PATH, BASE_PATH); + if ($project->getResourcesDir() === '') { + Deprecation::notice( + '4.13.0', + 'The RESOURCES_DIR constant will change to "_resources" by default.' + . ' See https://docs.silverstripe.org/en/5/changelogs/5.0.0/#api-general', + Deprecation::SCOPE_GLOBAL + ); + } + } } diff --git a/src/Core/ClassInfo.php b/src/Core/ClassInfo.php index f127470d3..2e196710a 100644 --- a/src/Core/ClassInfo.php +++ b/src/Core/ClassInfo.php @@ -289,14 +289,14 @@ class ClassInfo */ public static function classes_for_file($filePath) { - $absFilePath = Director::getAbsFile($filePath); + $absFilePath = Convert::slashes(Director::getAbsFile($filePath)); $classManifest = ClassLoader::inst()->getManifest(); $classes = $classManifest->getClasses(); $classNames = $classManifest->getClassNames(); $matchedClasses = []; foreach ($classes as $lowerClass => $compareFilePath) { - if (strcasecmp($absFilePath ?? '', $compareFilePath ?? '') === 0) { + if (strcasecmp($absFilePath, Convert::slashes($compareFilePath ?? '')) === 0) { $matchedClasses[$lowerClass] = $classNames[$lowerClass]; } } @@ -312,14 +312,14 @@ class ClassInfo */ public static function classes_for_folder($folderPath) { - $absFolderPath = Director::getAbsFile($folderPath); + $absFolderPath = Convert::slashes(Director::getAbsFile($folderPath)); $classManifest = ClassLoader::inst()->getManifest(); $classes = $classManifest->getClasses(); $classNames = $classManifest->getClassNames(); $matchedClasses = []; foreach ($classes as $lowerClass => $compareFilePath) { - if (stripos($compareFilePath ?? '', $absFolderPath ?? '') === 0) { + if (stripos(Convert::slashes($compareFilePath ?? ''), $absFolderPath) === 0) { $matchedClasses[$lowerClass] = $classNames[$lowerClass]; } } diff --git a/src/Core/CustomMethods.php b/src/Core/CustomMethods.php index 465cb5b07..8841a8c25 100644 --- a/src/Core/CustomMethods.php +++ b/src/Core/CustomMethods.php @@ -227,6 +227,7 @@ trait CustomMethods * * @param object $object * @return array + * @deprecated 4.13.0 Will be replaced by findMethodsFrom() in CMS 5 */ protected function findMethodsFrom($object) { diff --git a/src/Dev/Deprecation.php b/src/Dev/Deprecation.php index 4f1922b1f..e9b1e7c74 100644 --- a/src/Dev/Deprecation.php +++ b/src/Dev/Deprecation.php @@ -10,19 +10,13 @@ use SilverStripe\Core\Injector\InjectorLoader; use SilverStripe\Core\Manifest\Module; /** - * Handles raising an notice when accessing a deprecated method + * Handles raising an notice when accessing a deprecated method, class, configuration, or behaviour. * - * A pattern used in SilverStripe when deprecating a method is to add something like - * user_error('This method is deprecated', E_USER_NOTICE); - * to the method - * - * However sometimes we want to mark that a method will be deprecated in some future version and shouldn't be used in + * Sometimes we want to mark that a method will be deprecated in some future version and shouldn't be used in * new code, but not forbid in the current version - for instance when that method is still heavily used in framework * or cms. * - * This class abstracts the above pattern and adds a way to do that. - * - * Each call to notice passes a version that the notice will be valid from. + * See https://docs.silverstripe.org/en/contributing/release_process/#deprecation */ class Deprecation { @@ -114,6 +108,12 @@ class Deprecation $level = 1; } $newLevel = $level; + // handle closures inside withNoReplacement() + if (self::$insideWithNoReplacement + && substr($backtrace[$newLevel]['function'], -strlen('{closure}')) === '{closure}' + ) { + $newLevel = $newLevel + 2; + } // handle call_user_func if ($level === 4 && strpos($backtrace[2]['function'] ?? '', 'call_user_func') !== false) { $newLevel = 5; @@ -190,10 +190,10 @@ class Deprecation // Do not add to self::$userErrorMessageBuffer, as the backtrace is too expensive return; } - + // Getting a backtrace is slow, so we only do it if we need it $backtrace = null; - + // Get the calling scope if ($scope == Deprecation::SCOPE_METHOD) { $backtrace = debug_backtrace(0); @@ -204,7 +204,7 @@ class Deprecation } else { $caller = false; } - + if (substr($string, -1) != '.') { $string .= "."; } diff --git a/src/ORM/RelatedData/StandardRelatedDataService.php b/src/ORM/RelatedData/StandardRelatedDataService.php index 04501fc23..c6be4632d 100644 --- a/src/ORM/RelatedData/StandardRelatedDataService.php +++ b/src/ORM/RelatedData/StandardRelatedDataService.php @@ -394,14 +394,7 @@ class StandardRelatedDataService implements RelatedDataService */ private function prepareClassNameLiteral(string $value): string { - $c = chr(92); - $escaped = str_replace($c ?? '', "{$c}{$c}", $value ?? ''); - // postgres - if (stripos(get_class(DB::get_conn()), 'postgres') !== false) { - return "E'{$escaped}'"; - } - // mysql - return "'{$escaped}'"; + return DB::get_conn()->quoteString($value); } /** diff --git a/src/i18n/Data/Sources.php b/src/i18n/Data/Sources.php index 0afc11c46..f6bebdd7a 100644 --- a/src/i18n/Data/Sources.php +++ b/src/i18n/Data/Sources.php @@ -24,6 +24,7 @@ class Sources implements Resettable * * @config * @var array + * @deprecated 4.0.0 Use SilverStripe\Core\Manifest\ModuleManifest.module_priority instead */ private static $module_priority = []; @@ -35,7 +36,6 @@ class Sources implements Resettable public function getSortedModules() { $sortedModules = []; - foreach (ModuleLoader::inst()->getManifest()->getModules() as $module) { $sortedModules[$module->getName()] = $module->getPath(); }; diff --git a/src/i18n/TextCollection/i18nTextCollector.php b/src/i18n/TextCollection/i18nTextCollector.php index db5447983..dc8fc811d 100644 --- a/src/i18n/TextCollection/i18nTextCollector.php +++ b/src/i18n/TextCollection/i18nTextCollector.php @@ -457,7 +457,7 @@ class i18nTextCollector $this->getWriter()->write( $entities, $this->defaultLocale, - $this->baseSavePath . '/' . $module->getRelativePath() + $this->baseSavePath . DIRECTORY_SEPARATOR . $module->getRelativePath() ); return $this; } @@ -511,25 +511,25 @@ class i18nTextCollector $modulePath = $module->getPath(); // Search all .ss files in themes - if (stripos($module->getRelativePath() ?? '', 'themes/') === 0) { + if (stripos($module->getRelativePath() ?? '', 'themes' . DIRECTORY_SEPARATOR) === 0) { return $this->getFilesRecursive($modulePath, null, 'ss'); } // If non-standard module structure, search all root files - if (!is_dir("{$modulePath}/code") && !is_dir("{$modulePath}/src")) { + if (!is_dir($modulePath . DIRECTORY_SEPARATOR . 'code') && !is_dir($modulePath . DIRECTORY_SEPARATOR . 'src')) { return $this->getFilesRecursive($modulePath); } // Get code files - if (is_dir("{$modulePath}/src")) { - $files = $this->getFilesRecursive("{$modulePath}/src", null, 'php'); + if (is_dir($modulePath . DIRECTORY_SEPARATOR . 'src')) { + $files = $this->getFilesRecursive($modulePath . DIRECTORY_SEPARATOR . 'src', null, 'php'); } else { - $files = $this->getFilesRecursive("{$modulePath}/code", null, 'php'); + $files = $this->getFilesRecursive($modulePath . DIRECTORY_SEPARATOR . 'code', null, 'php'); } // Search for templates in this module - if (is_dir("{$modulePath}/templates")) { - $templateFiles = $this->getFilesRecursive("{$modulePath}/templates", null, 'ss'); + if (is_dir($modulePath . DIRECTORY_SEPARATOR . 'templates')) { + $templateFiles = $this->getFilesRecursive($modulePath . DIRECTORY_SEPARATOR . 'templates', null, 'ss'); } else { $templateFiles = $this->getFilesRecursive($modulePath, null, 'ss'); } @@ -957,7 +957,7 @@ class i18nTextCollector $fileList = []; } // Skip ignored folders - if (is_file("{$folder}/_manifest_exclude") || preg_match($folderExclude ?? '', $folder ?? '')) { + if (is_file($folder . DIRECTORY_SEPARATOR . '_manifest_exclude') || preg_match($folderExclude ?? '', $folder ?? '')) { return $fileList; } diff --git a/tests/php/Dev/DeprecationTest.php b/tests/php/Dev/DeprecationTest.php index fac1ad726..8b0cf8184 100644 --- a/tests/php/Dev/DeprecationTest.php +++ b/tests/php/Dev/DeprecationTest.php @@ -151,6 +151,22 @@ class DeprecationTest extends SapphireTest Deprecation::outputNotices(); } + public function testNoticeWithNoReplacementTrue() + { + $message = implode(' ', [ + 'SilverStripe\Dev\Tests\DeprecationTest->testNoticeWithNoReplacementTrue is deprecated.', + 'My message.', + 'Called from PHPUnit\Framework\TestCase->runTest.' + ]); + $this->expectDeprecation(); + $this->expectDeprecationMessage($message); + Deprecation::enable(true); + Deprecation::withNoReplacement(function () { + Deprecation::notice('123', 'My message.'); + }); + Deprecation::outputNotices(); + } + public function testClassWithNoReplacement() { $message = implode(' ', [