From 7a8226bc19d020125dd5a7e5acabf0615731f570 Mon Sep 17 00:00:00 2001 From: Ivan Bochkarev Date: Mon, 10 Aug 2026 08:18:53 +0600 Subject: [PATCH 1/6] Stop using getService for lexicon, registry, and error Register modError during initialize next to registry (lexicon already comes from _initCulture). Replace core/manager getService call sites for those three services with the DI container or synced properties. Part of the series planned on #15986 (tier 1). --- .../src/Revolution/Filters/modOutputFilter.php | 6 ------ core/src/Revolution/Mail/modPHPMailer.php | 4 ++-- .../Revolution/Processors/Resource/Reload.php | 6 ++---- .../Processors/Security/User/Create.php | 2 +- .../Processors/Security/User/Update.php | 2 +- .../Revolution/Processors/System/Console.php | 1 - .../System/Registry/Register/Read.php | 1 - .../System/Registry/Register/Send.php | 1 - .../Processors/System/RemoveLocks.php | 2 +- core/src/Revolution/Rest/modRest.php | 1 - core/src/Revolution/Rest/modRestService.php | 1 - core/src/Revolution/modLexiconTag.php | 3 --- core/src/Revolution/modMenu.php | 4 ---- core/src/Revolution/modRequest.php | 2 +- core/src/Revolution/modResource.php | 7 +++---- core/src/Revolution/modUser.php | 4 ++-- core/src/Revolution/modX.php | 18 +++++++++--------- .../default/resource/resource.class.php | 3 --- .../default/security/login.class.php | 8 ++++---- 19 files changed, 26 insertions(+), 50 deletions(-) diff --git a/core/src/Revolution/Filters/modOutputFilter.php b/core/src/Revolution/Filters/modOutputFilter.php index 7f5969178d9..ae14a55a041 100644 --- a/core/src/Revolution/Filters/modOutputFilter.php +++ b/core/src/Revolution/Filters/modOutputFilter.php @@ -469,9 +469,6 @@ public function filter(&$element) break; case 'fuzzydate': /* displays a "fuzzy" date reference */ - if (empty($this->modx->lexicon)) { - $this->modx->getService('lexicon', 'modLexicon'); - } $this->modx->lexicon->load('filters'); if (empty($m_val)) { $m_val = '%b %e'; @@ -495,9 +492,6 @@ public function filter(&$element) if (empty($output)) { break; } - if (empty($this->modx->lexicon)) { - $this->modx->getService('lexicon', 'modLexicon'); - } $this->modx->lexicon->load('filters'); $agoTS = []; diff --git a/core/src/Revolution/Mail/modPHPMailer.php b/core/src/Revolution/Mail/modPHPMailer.php index c86cbf6ebbf..c973e069103 100644 --- a/core/src/Revolution/Mail/modPHPMailer.php +++ b/core/src/Revolution/Mail/modPHPMailer.php @@ -241,7 +241,7 @@ public function send(array $attributes = []) } $sent = $this->mailer->send(); } catch (Exception $e) { - $this->error = $this->modx->getService('error.modError'); + $this->error = $this->modx->services->get('error'); $this->error->addError($e->getMessage()); } @@ -302,7 +302,7 @@ public function attach($file, $name = '', $encoding = 'base64', $type = 'applica try { $this->mailer->addAttachment($file, $name, $encoding, $type); } catch (Exception $e) { - $this->error = $this->modx->getService('error.modError'); + $this->error = $this->modx->services->get('error'); $this->error->addError($e->getMessage()); } } diff --git a/core/src/Revolution/Processors/Resource/Reload.php b/core/src/Revolution/Processors/Resource/Reload.php index d0ec9ef5df8..a238a6288b5 100644 --- a/core/src/Revolution/Processors/Resource/Reload.php +++ b/core/src/Revolution/Processors/Resource/Reload.php @@ -37,10 +37,8 @@ public function initialize() { $return = true; $modx =& $this->modx; - if (!isset($modx->registry)) { - if (!$modx->getService('registry', 'registry.modRegistry')) { - $return = 'Could not instantiate registry service.'; - } + if (!$modx->services->has('registry') || !$modx->registry) { + $return = 'Could not instantiate registry service.'; } $modx->registry->addRegister('resource_reload', 'registry.modDbRegister', ['directory' => 'resource_reload']); $this->reg = $modx->registry->resource_reload; diff --git a/core/src/Revolution/Processors/Security/User/Create.php b/core/src/Revolution/Processors/Security/User/Create.php index 07edf682e26..ef419f02e66 100644 --- a/core/src/Revolution/Processors/Security/User/Create.php +++ b/core/src/Revolution/Processors/Security/User/Create.php @@ -242,7 +242,7 @@ public function sendNotificationEmail() { $activationHash = bin2hex(random_bytes(32)); /** @var modRegistry $registry */ - $registry = $this->modx->getService('registry', 'registry.modRegistry'); + $registry = $this->modx->services->get('registry'); /** @var modRegister $register */ $register = $registry->getRegister('user', 'registry.modDbRegister'); $register->connect(); diff --git a/core/src/Revolution/Processors/Security/User/Update.php b/core/src/Revolution/Processors/Security/User/Update.php index 757271b2da3..10b31d7e7ed 100644 --- a/core/src/Revolution/Processors/Security/User/Update.php +++ b/core/src/Revolution/Processors/Security/User/Update.php @@ -319,7 +319,7 @@ public function sendNotificationEmail() { $activationHash = bin2hex(random_bytes(32)); /** @var modRegistry $registry */ - $registry = $this->modx->getService('registry', 'registry.modRegistry'); + $registry = $this->modx->services->get('registry'); /** @var modRegister $register */ $register = $registry->getRegister('user', 'registry.modDbRegister'); $register->connect(); diff --git a/core/src/Revolution/Processors/System/Console.php b/core/src/Revolution/Processors/System/Console.php index 43ec760c6cc..eaad95b732c 100644 --- a/core/src/Revolution/Processors/System/Console.php +++ b/core/src/Revolution/Processors/System/Console.php @@ -66,7 +66,6 @@ public function process() 'remove_read' => true, ]; - $this->modx->getService('registry', modRegistry::class); $this->modx->registry->addRegister($register, $registerClass, ['directory' => $register]); if (!$this->modx->registry->$register->connect()) { return $this->failure($this->modx->lexicon('error')); diff --git a/core/src/Revolution/Processors/System/Registry/Register/Read.php b/core/src/Revolution/Processors/System/Registry/Register/Read.php index cde66c47824..48453bae6ca 100644 --- a/core/src/Revolution/Processors/System/Registry/Register/Read.php +++ b/core/src/Revolution/Processors/System/Registry/Register/Read.php @@ -71,7 +71,6 @@ public function initialize() public function connectRegister($register) { $register_class = trim($this->getProperty('register_class', modFileRegister::class)); - $this->modx->getService('registry', modRegistry::class); $this->modx->registry->addRegister($register, $register_class, ['directory' => $register]); $this->register = $this->modx->registry->$register; diff --git a/core/src/Revolution/Processors/System/Registry/Register/Send.php b/core/src/Revolution/Processors/System/Registry/Register/Send.php index 4af2dd405f1..da992e28e9d 100644 --- a/core/src/Revolution/Processors/System/Registry/Register/Send.php +++ b/core/src/Revolution/Processors/System/Registry/Register/Send.php @@ -63,7 +63,6 @@ public function initialize() public function connectRegister($register) { $register_class = trim($this->getProperty('register_class', modFileRegister::class)); - $this->modx->getService('registry', modRegistry::class); $this->modx->registry->addRegister($register, $register_class, ['directory' => $register]); $this->register = $this->modx->registry->$register; diff --git a/core/src/Revolution/Processors/System/RemoveLocks.php b/core/src/Revolution/Processors/System/RemoveLocks.php index 8661d7afe42..e2197de2498 100644 --- a/core/src/Revolution/Processors/System/RemoveLocks.php +++ b/core/src/Revolution/Processors/System/RemoveLocks.php @@ -34,7 +34,7 @@ public function checkPermissions() public function process() { /** @var modRegistry $registry */ - $registry = $this->modx->getService('registry', modRegistry::class); + $registry = $this->modx->services->get('registry'); if ($registry) { $registry->addRegister('locks', modDbRegister::class, ['directory' => 'locks']); $registry->locks->connect(); diff --git a/core/src/Revolution/Rest/modRest.php b/core/src/Revolution/Rest/modRest.php index 890cf8cdcc1..da511fdcf20 100644 --- a/core/src/Revolution/Rest/modRest.php +++ b/core/src/Revolution/Rest/modRest.php @@ -60,7 +60,6 @@ public function __construct(modX &$modx, array $config = []) 'userAgent' => 'MODX RestClient/1.0.0', 'username' => null, ], $config); - $this->modx->getService('lexicon', 'modLexicon'); if ($this->modx->lexicon) { $this->modx->lexicon->load('rest'); } diff --git a/core/src/Revolution/Rest/modRestService.php b/core/src/Revolution/Rest/modRestService.php index 784a8d37c16..41aeaf7fdd0 100644 --- a/core/src/Revolution/Rest/modRestService.php +++ b/core/src/Revolution/Rest/modRestService.php @@ -69,7 +69,6 @@ public function __construct(modX &$modx, array $config = []) 'xmlRootNode' => 'response', 'sanitize' => false, ], $config); - $this->modx->getService('lexicon', 'modLexicon'); if ($this->modx->lexicon) { $this->modx->lexicon->load('rest'); } diff --git a/core/src/Revolution/modLexiconTag.php b/core/src/Revolution/modLexiconTag.php index 3ce814752d1..2c17e45d927 100644 --- a/core/src/Revolution/modLexiconTag.php +++ b/core/src/Revolution/modLexiconTag.php @@ -75,9 +75,6 @@ public function getContent(array $options = []) if (isset($options['content'])) { $this->_content = $options['content']; } else { - if (!is_object($this->modx->lexicon)) { - $this->modx->getService('lexicon', 'modLexicon'); - } $topic = !empty($this->_properties['topic']) ? $this->_properties['topic'] : 'default'; $namespace = !empty($this->_properties['namespace']) ? $this->_properties['namespace'] : 'core'; $language = !empty($this->_properties['language']) ? $this->_properties['language'] : $this->modx->getOption('cultureKey', diff --git a/core/src/Revolution/modMenu.php b/core/src/Revolution/modMenu.php index bb4b50b7345..cafebcdb70d 100644 --- a/core/src/Revolution/modMenu.php +++ b/core/src/Revolution/modMenu.php @@ -120,10 +120,6 @@ protected function getLanguageMenu() */ public function getSubMenus($start = '') { - if (!$this->xpdo->lexicon) { - $this->xpdo->getService('lexicon', modLexicon::class); - } - $this->xpdo->lexicon->load('menu', 'en:menu', 'topmenu', 'en:topmenu'); $c = $this->xpdo->newQuery(modMenu::class); diff --git a/core/src/Revolution/modRequest.php b/core/src/Revolution/modRequest.php index 1bb206ffa4b..b45e76fcafd 100644 --- a/core/src/Revolution/modRequest.php +++ b/core/src/Revolution/modRequest.php @@ -471,7 +471,7 @@ public function loadErrorHandler($class = modError::class) public function registerLogging(array $options = []) { if (isset($options['register']) && isset($options['topic'])) { - if ($this->modx->getService('registry', modRegistry::class)) { + if ($this->modx->registry) { $register_class = isset($options['register_class']) ? $options['register_class'] : modFileRegister::class; $register = $this->modx->registry->getRegister($options['register'], $register_class); if ($register) { diff --git a/core/src/Revolution/modResource.php b/core/src/Revolution/modResource.php index 4833f04a3c2..073d6bd8115 100644 --- a/core/src/Revolution/modResource.php +++ b/core/src/Revolution/modResource.php @@ -3,7 +3,6 @@ namespace MODX\Revolution; use MODX\Revolution\Registry\modDbRegister; -use MODX\Revolution\Registry\modRegistry; use MODX\Revolution\modX; use PDO; use ReflectionClass; @@ -208,7 +207,7 @@ public static function filterPathSegment(&$xpdo, $segment, array $options = []) $segment = html_entity_decode($segment, ENT_QUOTES, $charset); /* prepare '&' replacement */ - if ($xpdo instanceof modX && $xpdo->getService('lexicon', modLexicon::class) && $xpdo->lexicon('and')) { + if ($xpdo instanceof modX && $xpdo->lexicon && $xpdo->lexicon('and')) { $ampersand = ' ' . $xpdo->lexicon('and') . ' '; } else { $ampersand = ' and '; @@ -797,7 +796,7 @@ public function getLock() { $lock = 0; if ($this->xpdo instanceof modX) { - if ($this->xpdo->getService('registry', modRegistry::class)) { + if ($this->xpdo->registry) { $this->xpdo->registry->addRegister('locks', modDbRegister::class, ['directory' => 'locks']); $this->xpdo->registry->locks->connect(); $this->xpdo->registry->locks->subscribe('/resource/' . md5($this->get('id'))); @@ -829,7 +828,7 @@ public function removeLock($user = 0) } $lockedBy = $this->getLock(); if (empty($lockedBy) || $lockedBy == $user) { - if ($this->xpdo->getService('registry', modRegistry::class)) { + if ($this->xpdo->registry) { $this->xpdo->registry->addRegister('locks', modDbRegister::class, ['directory' => 'locks']); $this->xpdo->registry->locks->connect(); $this->xpdo->registry->locks->subscribe('/resource/' . md5($this->get('id'))); diff --git a/core/src/Revolution/modUser.php b/core/src/Revolution/modUser.php index f927b30b3f7..d64c84b84b0 100644 --- a/core/src/Revolution/modUser.php +++ b/core/src/Revolution/modUser.php @@ -283,7 +283,7 @@ public function activatePassword($key) { $activated = -1; if ($this->get('cachepwd')) { - if ($this->xpdo->getService('registry', modRegistry::class) + if ($this->xpdo->registry && $this->xpdo->registry->getRegister('user', modDbRegister::class)) { if ($this->xpdo->registry->user->connect()) { $activated = false; @@ -866,7 +866,7 @@ public function removeLocks(array $options = []) { $removed = false; if ($this->xpdo instanceof modX) { - if ($this->xpdo->getService('registry', modRegistry::class)) { + if ($this->xpdo->registry) { $this->xpdo->registry->addRegister('locks', modDbRegister::class, ['directory' => 'locks']); $this->xpdo->registry->locks->connect(); diff --git a/core/src/Revolution/modX.php b/core/src/Revolution/modX.php index c2307d0f393..ca0822291a7 100644 --- a/core/src/Revolution/modX.php +++ b/core/src/Revolution/modX.php @@ -588,9 +588,15 @@ public function initialize($contextKey= 'web', $options = null) { $this->_initHttpClient(); $this->_initCulture($options); + // Core DI services (tier 1 of #15986): prefer $modx->services->get() over getService(). $this->services->add('registry', new modRegistry($this)); $this->registry = $this->services->get('registry'); + if (!$this->services->has('error')) { + $this->services->add('error', new modError($this)); + } + $this->error = $this->services->get('error'); + $this->services->add(modManagerDateFormatter::class, fn() => new modManagerDateFormatter($this)); if (!$this->getOption(xPDO::OPT_SETUP)) { @@ -1764,17 +1770,11 @@ public function invokeEvent($eventName, array $params= []) { public function runProcessor($action = '', $scriptProperties = [], $options = []) { $result = null; - // Make sure the required services are loaded before initialising a processor - if (!$this->lexicon) { - if (!$this->services->has('lexicon')) { - $this->services->add('lexicon', new modLexicon($this)); - } + // lexicon/error are registered during initialize(); sync properties if a custom bootstrap cleared them + if (!$this->lexicon && $this->services->has('lexicon')) { $this->lexicon = $this->services->get('lexicon'); } - if (!$this->error) { - if (!$this->services->has('error')) { - $this->services->add('error', new modError($this)); - } + if (!$this->error && $this->services->has('error')) { $this->error = $this->services->get('error'); } diff --git a/manager/controllers/default/resource/resource.class.php b/manager/controllers/default/resource/resource.class.php index b9452a00bb4..1c16b00d302 100644 --- a/manager/controllers/default/resource/resource.class.php +++ b/manager/controllers/default/resource/resource.class.php @@ -502,9 +502,6 @@ protected function getReloadData() // get reload data if reload token found in registry if (array_key_exists('reload', $scriptProperties) && !empty($scriptProperties['reload'])) { - if (!isset($modx->registry)) { - $modx->getService('registry', modRegistry::class); - } /** @var modRegistry $modx->registry */ if (isset($modx->registry)) { $modx->registry->addRegister('resource_reload', 'registry.modDbRegister', ['directory' => 'resource_reload']); diff --git a/manager/controllers/default/security/login.class.php b/manager/controllers/default/security/login.class.php index 1b0f6cbb1af..c2695201fb7 100644 --- a/manager/controllers/default/security/login.class.php +++ b/manager/controllers/default/security/login.class.php @@ -263,7 +263,7 @@ public function handleForgotLoginHash() if (!empty($_GET['modhash'])) { $hash = $this->modx->sanitizeString($_GET['modhash']); /** @var modDbRegister $registry */ - $registry = $this->modx->getService('registry', modRegistry::class) + $registry = $this->modx->services->get('registry') ->getRegister('user', modDbRegister::class); $registry->connect(); $registry->subscribe('/pwd/change/' . $hash); @@ -291,7 +291,7 @@ public function handleMagicLoginLink() if (!empty($_GET['magiclink'])) { $hash = $this->modx->sanitizeString($_GET['magiclink']); /** @var modDbRegister $registry */ - $registry = $this->modx->getService('registry', 'registry.modRegistry') + $registry = $this->modx->services->get('registry') ->getRegister('user', 'registry.modDbRegister'); $registry->connect(); $registry->subscribe('/pwd/magiclink/' . $hash); @@ -411,7 +411,7 @@ public function handleLogin() $hash = $this->modx->sanitizeString($this->scriptProperties['modhash']); if (!empty($hash)) { /** @var modDbRegister $registry */ - $registry = $this->modx->getService('registry', modRegistry::class) + $registry = $this->modx->services->get('registry') ->getRegister('user', modDbRegister::class); $registry->connect(); $registry->subscribe('/pwd/change/' . $hash); @@ -533,7 +533,7 @@ private function setActivationHash($user, $ttl = 86400, $topic = '/pwd/change/') $hash = md5(uniqid(md5($user->get('email') . '/' . $user->get('id')), true)); /** @var modRegistry $registry */ - $registry = $this->modx->getService('registry', modRegistry::class); + $registry = $this->modx->services->get('registry'); /** @var modDbRegister $register */ $register = $registry->getRegister('user', modDbRegister::class); $register->connect(); From ced708d30ddc0155a4a1e741ab5f9fe7c0df0ee7 Mon Sep 17 00:00:00 2001 From: Ivan Bochkarev Date: Mon, 10 Aug 2026 08:23:39 +0600 Subject: [PATCH 2/6] Stop using getService for smarty, mail, and hashing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Register hashing eagerly and mail/smarty as lazy shared DI services during initialize, then replace remaining core/manager call sites so tiers 1–2 of #15986 ship in one PR. --- .../Configs/GetInputPropertyConfigs.php | 2 +- .../Renders/GetInputProperties.php | 2 +- .../Element/TemplateVar/Renders/GetInputs.php | 2 +- .../Processors/Security/User/Create.php | 16 +++++++----- .../Processors/Security/User/Update.php | 5 +++- core/src/Revolution/modManagerRequest.php | 6 ++--- core/src/Revolution/modTemplateVar.php | 10 ++++---- core/src/Revolution/modUser.php | 8 +++--- core/src/Revolution/modX.php | 25 ++++++++++++++++++- .../default/dashboard/widget.buttons.php | 3 +-- .../default/dashboard/widget.configcheck.php | 3 +-- .../default/dashboard/widget.grid-online.php | 3 +-- .../default/dashboard/widget.grid-rer.php | 3 +-- .../default/dashboard/widget.updates.php | 3 +-- 14 files changed, 57 insertions(+), 34 deletions(-) diff --git a/core/src/Revolution/Processors/Element/TemplateVar/Configs/GetInputPropertyConfigs.php b/core/src/Revolution/Processors/Element/TemplateVar/Configs/GetInputPropertyConfigs.php index 0272710ee87..6f4145afdc4 100644 --- a/core/src/Revolution/Processors/Element/TemplateVar/Configs/GetInputPropertyConfigs.php +++ b/core/src/Revolution/Processors/Element/TemplateVar/Configs/GetInputPropertyConfigs.php @@ -138,7 +138,7 @@ private function setExampleData() public function initialize() { /* simulate controller to allow controller methods in TV Input Properties controllers */ - $this->modx->getService('smarty', 'MODX\Revolution\Smarty\modSmarty', ''); + $this->modx->smarty = $this->modx->services->get('smarty'); $context = $this->getProperty('context'); if (empty($context)) { diff --git a/core/src/Revolution/Processors/Element/TemplateVar/Renders/GetInputProperties.php b/core/src/Revolution/Processors/Element/TemplateVar/Renders/GetInputProperties.php index 98fa65a8521..388f0319ba9 100644 --- a/core/src/Revolution/Processors/Element/TemplateVar/Renders/GetInputProperties.php +++ b/core/src/Revolution/Processors/Element/TemplateVar/Renders/GetInputProperties.php @@ -45,7 +45,7 @@ public function getLanguageTopics() public function initialize() { /* simulate controller to allow controller methods in TV Input Properties controllers */ - $this->modx->getService('smarty', 'MODX\Revolution\Smarty\modSmarty', ''); + $this->modx->smarty = $this->modx->services->get('smarty'); $context = $this->getProperty('context'); if (empty($context)) { diff --git a/core/src/Revolution/Processors/Element/TemplateVar/Renders/GetInputs.php b/core/src/Revolution/Processors/Element/TemplateVar/Renders/GetInputs.php index 1ea960f40ed..ed8d2f62acb 100644 --- a/core/src/Revolution/Processors/Element/TemplateVar/Renders/GetInputs.php +++ b/core/src/Revolution/Processors/Element/TemplateVar/Renders/GetInputs.php @@ -39,7 +39,7 @@ public function getLanguageTopics() public function initialize() { /* simulate controller to allow controller methods in TV Input Properties controllers */ - $this->modx->getService('smarty', 'MODX\Revolution\Smarty\modSmarty', ''); + $this->modx->smarty = $this->modx->services->get('smarty'); return true; } diff --git a/core/src/Revolution/Processors/Security/User/Create.php b/core/src/Revolution/Processors/Security/User/Create.php index ef419f02e66..39a65938306 100644 --- a/core/src/Revolution/Processors/Security/User/Create.php +++ b/core/src/Revolution/Processors/Security/User/Create.php @@ -12,7 +12,6 @@ use Exception; -use MODX\Revolution\Hashing\modHashing; use MODX\Revolution\Processors\Model\CreateProcessor; use MODX\Revolution\Processors\Processor; use MODX\Revolution\modUser; @@ -22,7 +21,6 @@ use MODX\Revolution\modX; use MODX\Revolution\Registry\modRegister; use MODX\Revolution\Registry\modRegistry; -use MODX\Revolution\Smarty\modSmarty; /** * Create a user @@ -224,9 +222,12 @@ public function sendNotificationEmail() { // Then restore previous placeholders to prevent any breakage $this->modx->placeholders = $ph; - $this->modx->getService('smarty', modSmarty::class, '', [ - 'template_dir' => $this->modx->getOption('manager_path') . 'templates/' . $this->modx->getOption('manager_theme', null, 'default') . '/', - ]); + if (!$this->modx->smarty) { + $this->modx->smarty = $this->modx->services->get('smarty'); + $this->modx->smarty->setTemplatePath( + $this->modx->getOption('manager_path') . 'templates/' . $this->modx->getOption('manager_theme', null, 'default') . '/' + ); + } $this->modx->smarty->assign('_config', $this->modx->config); $this->modx->smarty->assign('content', $message); $message = $this->modx->smarty->fetch('email/default.tpl'); @@ -263,7 +264,10 @@ public function sendNotificationEmail() { // Then restore previous placeholders to prevent any breakage $this->modx->placeholders = $ph; - $this->modx->getService('smarty', 'smarty.modSmarty', '', ['template_dir' => $this->modx->getOption('manager_path') . 'templates/default/']); + if (!$this->modx->smarty) { + $this->modx->smarty = $this->modx->services->get('smarty'); + $this->modx->smarty->setTemplatePath($this->modx->getOption('manager_path') . 'templates/default/'); + } $this->modx->smarty->assign('_config', $this->modx->config); $this->modx->smarty->assign('content', $message, true); diff --git a/core/src/Revolution/Processors/Security/User/Update.php b/core/src/Revolution/Processors/Security/User/Update.php index 10b31d7e7ed..b6d9cfca82d 100644 --- a/core/src/Revolution/Processors/Security/User/Update.php +++ b/core/src/Revolution/Processors/Security/User/Update.php @@ -342,7 +342,10 @@ public function sendNotificationEmail() { // Then restore previous placeholders to prevent any breakage $this->modx->placeholders = $ph; - $this->modx->getService('smarty', 'smarty.modSmarty', '', ['template_dir' => $this->modx->getOption('manager_path') . 'templates/default/']); + if (!$this->modx->smarty) { + $this->modx->smarty = $this->modx->services->get('smarty'); + $this->modx->smarty->setTemplatePath($this->modx->getOption('manager_path') . 'templates/default/'); + } $this->modx->smarty->assign('_config', $this->modx->config); $this->modx->smarty->assign('content', $message, true); diff --git a/core/src/Revolution/modManagerRequest.php b/core/src/Revolution/modManagerRequest.php index cb1ac5e0ab2..80e6eb1d302 100644 --- a/core/src/Revolution/modManagerRequest.php +++ b/core/src/Revolution/modManagerRequest.php @@ -12,7 +12,6 @@ use MODX\Revolution\Error\modError; use MODX\Revolution\Registry\modFileRegister; -use MODX\Revolution\Smarty\modSmarty; use xPDO\Cache\xPDOCacheManager; use xPDO\xPDO; @@ -77,9 +76,8 @@ public function initialize() if (!file_exists($templatePath)) { /* fallback to default */ $templatePath = $this->modx->getOption('manager_path') . 'templates/default/'; } - $this->modx->getService('smarty', modSmarty::class, '', [ - 'template_dir' => $templatePath, - ]); + $this->modx->smarty = $this->modx->services->get('smarty'); + $this->modx->smarty->setTemplatePath($templatePath); /* load context-specific cache dir */ $this->modx->smarty->setCachePath($this->modx->context->get('key') . '/smarty/' . $theme . '/'); diff --git a/core/src/Revolution/modTemplateVar.php b/core/src/Revolution/modTemplateVar.php index 6bb26e49aee..1b65acab18f 100644 --- a/core/src/Revolution/modTemplateVar.php +++ b/core/src/Revolution/modTemplateVar.php @@ -3,7 +3,6 @@ namespace MODX\Revolution; use DirectoryIterator; -use MODX\Revolution\Smarty\modSmarty; use MODX\Revolution\Sources\modFileMediaSource; use MODX\Revolution\Sources\modFTPMediaSource; use MODX\Revolution\Sources\modMediaSource; @@ -384,10 +383,11 @@ public function renderInput($resource = null, $options = []) $style = is_array($options) && isset($options['style']) ? strval($options['style']) : ''; $value = is_array($options) && isset($options['value']) ? strval($options['value']) : ''; } - if (!isset($this->xpdo->smarty)) { - $this->xpdo->getService('smarty', modSmarty::class, '', [ - 'template_dir' => $this->xpdo->getOption('manager_path') . 'templates/' . $this->xpdo->getOption('manager_theme', null, 'default') . '/' - ]); + if (!$this->xpdo->smarty) { + $this->xpdo->smarty = $this->xpdo->services->get('smarty'); + $this->xpdo->smarty->setTemplatePath( + $this->xpdo->getOption('manager_path') . 'templates/' . $this->xpdo->getOption('manager_theme', null, 'default') . '/' + ); } $this->xpdo->smarty->assign('style', $style); if (!isset($value) || empty($value)) { diff --git a/core/src/Revolution/modUser.php b/core/src/Revolution/modUser.php index d64c84b84b0..421981e51ad 100644 --- a/core/src/Revolution/modUser.php +++ b/core/src/Revolution/modUser.php @@ -3,7 +3,6 @@ namespace MODX\Revolution; use MODX\Revolution\Hashing\modHash; -use MODX\Revolution\Hashing\modHashing; use MODX\Revolution\Mail\modMail; use MODX\Revolution\Mail\modPHPMailer; use MODX\Revolution\Registry\modDbRegister; @@ -59,7 +58,7 @@ public function set($k, $v = null, $vType = '') return false; } } - if (in_array($k, ['password', 'cachepwd']) && $this->xpdo->getService('hashing', modHashing::class)) { + if (in_array($k, ['password', 'cachepwd']) && $this->xpdo->hashing) { if (!$this->get('salt')) { $this->set('salt', md5(uniqid(rand(), true))); } @@ -257,7 +256,7 @@ public function endSession() public function passwordMatches($password, array $options = []) { $match = false; - if ($this->xpdo->getService('hashing', modHashing::class)) { + if ($this->xpdo->hashing) { $options = array_merge(['salt' => $this->get('salt')], $options); /** @var modHash $hasher */ @@ -938,7 +937,8 @@ public function sendEmail($message, array $options = []) /** @var modUserProfile $profile */ $profile = $this->getOne('Profile'); /** @var modPHPMailer $mail */ - $mail = $this->xpdo->getService('mail', modPHPMailer::class); + $mail = $this->xpdo->services->get('mail'); + $this->xpdo->mail = $mail; if (!$profile || !$mail) { return false; diff --git a/core/src/Revolution/modX.php b/core/src/Revolution/modX.php index ca0822291a7..f01e2b7e98b 100644 --- a/core/src/Revolution/modX.php +++ b/core/src/Revolution/modX.php @@ -14,10 +14,12 @@ use GuzzleHttp\Client; use GuzzleHttp\Psr7\HttpFactory; use MODX\Revolution\Formatter\modManagerDateFormatter; +use MODX\Revolution\Hashing\modHashing; use MODX\Revolution\Services\Container; use MODX\Revolution\Error\modError; use MODX\Revolution\Error\modErrorHandler; use MODX\Revolution\Mail\modMail; +use MODX\Revolution\Mail\modPHPMailer; use MODX\Revolution\Processors\Processor; use MODX\Revolution\Processors\ProcessorResponse; use MODX\Revolution\Registry\modRegister; @@ -219,6 +221,10 @@ class modX extends xPDO { * @var modRegistry $registry */ public $registry; + /** + * @var modHashing $hashing + */ + public $hashing; /** * @var modMail $mail */ @@ -588,7 +594,7 @@ public function initialize($contextKey= 'web', $options = null) { $this->_initHttpClient(); $this->_initCulture($options); - // Core DI services (tier 1 of #15986): prefer $modx->services->get() over getService(). + // Core DI services (#15986 tiers 1–2): prefer $modx->services->get() over getService(). $this->services->add('registry', new modRegistry($this)); $this->registry = $this->services->get('registry'); @@ -597,6 +603,23 @@ public function initialize($contextKey= 'web', $options = null) { } $this->error = $this->services->get('error'); + if (!$this->services->has('hashing')) { + $this->services->add('hashing', new modHashing($this)); + } + $this->hashing = $this->services->get('hashing'); + + // Lazy shared: mail/smarty are heavier and unused on many front-end requests. + if (!$this->services->has('mail')) { + $this->services->add('mail', function () { + return new modPHPMailer($this); + }); + } + if (!$this->services->has('smarty')) { + $this->services->add('smarty', function () { + return new modSmarty($this); + }); + } + $this->services->add(modManagerDateFormatter::class, fn() => new modManagerDateFormatter($this)); if (!$this->getOption(xPDO::OPT_SETUP)) { diff --git a/manager/controllers/default/dashboard/widget.buttons.php b/manager/controllers/default/dashboard/widget.buttons.php index 363c6928424..528465c1f5e 100644 --- a/manager/controllers/default/dashboard/widget.buttons.php +++ b/manager/controllers/default/dashboard/widget.buttons.php @@ -2,7 +2,6 @@ use MODX\Revolution\modChunk; use MODX\Revolution\modDashboardWidgetInterface; -use MODX\Revolution\Smarty\modSmarty; /** * @package modx @@ -16,7 +15,7 @@ class modDashboardWidgetButtons extends modDashboardWidgetInterface */ public function render() { - $this->modx->getService('smarty', modSmarty::class); + $this->modx->smarty = $this->modx->services->get('smarty'); foreach ($this->widget->toArray() as $key => $value) { $this->modx->smarty->assign($key, $value); } diff --git a/manager/controllers/default/dashboard/widget.configcheck.php b/manager/controllers/default/dashboard/widget.configcheck.php index 92943f38657..41770b430c9 100644 --- a/manager/controllers/default/dashboard/widget.configcheck.php +++ b/manager/controllers/default/dashboard/widget.configcheck.php @@ -11,7 +11,6 @@ use MODX\Revolution\modDashboardWidgetInterface; use MODX\Revolution\Processors\ProcessorResponse; use MODX\Revolution\Processors\System\ConfigCheck; -use MODX\Revolution\Smarty\modSmarty; /** * Renders the config check box @@ -32,7 +31,7 @@ public function render() /** @var ProcessorResponse $response */ $response = $this->modx->runProcessor(ConfigCheck::class); - $this->modx->getService('smarty', modSmarty::class); + $this->modx->smarty = $this->modx->services->get('smarty'); $this->modx->smarty->assign('warnings', $response->getObject()); return $this->controller->fetchTemplate('dashboard/configcheck.tpl'); diff --git a/manager/controllers/default/dashboard/widget.grid-online.php b/manager/controllers/default/dashboard/widget.grid-online.php index 60f2a4ee3be..c85c33eda3e 100644 --- a/manager/controllers/default/dashboard/widget.grid-online.php +++ b/manager/controllers/default/dashboard/widget.grid-online.php @@ -11,7 +11,6 @@ use MODX\Revolution\modDashboardWidgetInterface; use MODX\Revolution\Processors\ProcessorResponse; use MODX\Revolution\Processors\Security\User\GetOnline; -use MODX\Revolution\Smarty\modSmarty; /** * @package modx @@ -36,7 +35,7 @@ public function render() $data = json_decode($data, true); } } - $this->modx->getService('smarty', modSmarty::class); + $this->modx->smarty = $this->modx->services->get('smarty'); $this->modx->smarty->assign('data', $data); $this->modx->smarty->assign('can_view_logs', $this->modx->hasPermission('logs')); diff --git a/manager/controllers/default/dashboard/widget.grid-rer.php b/manager/controllers/default/dashboard/widget.grid-rer.php index 28a1a2744cc..c691a5fd395 100644 --- a/manager/controllers/default/dashboard/widget.grid-rer.php +++ b/manager/controllers/default/dashboard/widget.grid-rer.php @@ -11,7 +11,6 @@ use MODX\Revolution\modDashboardWidgetInterface; use MODX\Revolution\Processors\ProcessorResponse; use MODX\Revolution\Processors\Security\User\GetRecentlyEditedResources; -use MODX\Revolution\Smarty\modSmarty; /** * Renders a grid of recently edited resources by the active user @@ -39,7 +38,7 @@ public function render() $data = json_decode($data, true); } } - $this->modx->getService('smarty', modSmarty::class); + $this->modx->smarty = $this->modx->services->get('smarty'); $this->modx->smarty->assign('data', $data); $this->modx->smarty->assign('can_view_logs', $this->modx->hasPermission('logs')); diff --git a/manager/controllers/default/dashboard/widget.updates.php b/manager/controllers/default/dashboard/widget.updates.php index 9c099b77953..430196a0866 100644 --- a/manager/controllers/default/dashboard/widget.updates.php +++ b/manager/controllers/default/dashboard/widget.updates.php @@ -3,7 +3,6 @@ use MODX\Revolution\modX; use MODX\Revolution\modDashboardWidgetInterface; use MODX\Revolution\Processors\SoftwareUpdate\GetList as SoftwareUpdateGetList; -use MODX\Revolution\Smarty\modSmarty; use xPDO\xPDO; /** @@ -55,7 +54,7 @@ public function render() $this->modx->cacheManager->set($updateCacheKey, $data, $this->updatesCacheExpire, $updateCacheOptions); } - $this->modx->getService('smarty', modSmarty::class); + $this->modx->smarty = $this->modx->services->get('smarty'); foreach ($data as $key => $value) { $this->modx->smarty->assign($key, $value); } From fbc9a194ab5982c8f9f5d1ea9449898ecf51e067 Mon Sep 17 00:00:00 2001 From: Ivan Bochkarev Date: Mon, 10 Aug 2026 08:24:53 +0600 Subject: [PATCH 3/6] style(modUser): fix multi-line if formatting for PHPCS --- core/src/Revolution/modUser.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/Revolution/modUser.php b/core/src/Revolution/modUser.php index 421981e51ad..83241e84e04 100644 --- a/core/src/Revolution/modUser.php +++ b/core/src/Revolution/modUser.php @@ -282,8 +282,10 @@ public function activatePassword($key) { $activated = -1; if ($this->get('cachepwd')) { - if ($this->xpdo->registry - && $this->xpdo->registry->getRegister('user', modDbRegister::class)) { + if ( + $this->xpdo->registry + && $this->xpdo->registry->getRegister('user', modDbRegister::class) + ) { if ($this->xpdo->registry->user->connect()) { $activated = false; $this->xpdo->registry->user->subscribe('/pwd/reset/' . md5($this->get('username'))); From f02fe7e05872e8c27573e238ee776973ce44e111 Mon Sep 17 00:00:00 2001 From: Ivan Bochkarev Date: Mon, 10 Aug 2026 08:31:29 +0600 Subject: [PATCH 4/6] refactor: centralize smarty/mail accessors after getService removal Extract _registerCoreServices, add getSmarty/getMail/getManagerTemplatePath, drop duplicated smarty bootstraps and dead widget ensures, and simplify Reload registry handling. --- .../Configs/GetInputPropertyConfigs.php | 2 +- .../Renders/GetInputProperties.php | 2 +- .../Element/TemplateVar/Renders/GetInputs.php | 2 +- .../Revolution/Processors/Resource/Reload.php | 3 - .../Processors/Security/User/Create.php | 14 +-- .../Processors/Security/User/Update.php | 7 +- .../Processors/System/RemoveLocks.php | 2 +- core/src/Revolution/modManagerRequest.php | 7 +- core/src/Revolution/modTemplateVar.php | 7 +- core/src/Revolution/modUser.php | 3 +- core/src/Revolution/modX.php | 112 +++++++++++++----- .../default/dashboard/widget.buttons.php | 1 - .../default/dashboard/widget.configcheck.php | 1 - .../default/dashboard/widget.grid-online.php | 1 - .../default/dashboard/widget.grid-rer.php | 1 - .../default/dashboard/widget.updates.php | 1 - .../default/security/login.class.php | 8 +- 17 files changed, 102 insertions(+), 72 deletions(-) diff --git a/core/src/Revolution/Processors/Element/TemplateVar/Configs/GetInputPropertyConfigs.php b/core/src/Revolution/Processors/Element/TemplateVar/Configs/GetInputPropertyConfigs.php index 6f4145afdc4..eb2cdc18db9 100644 --- a/core/src/Revolution/Processors/Element/TemplateVar/Configs/GetInputPropertyConfigs.php +++ b/core/src/Revolution/Processors/Element/TemplateVar/Configs/GetInputPropertyConfigs.php @@ -138,7 +138,7 @@ private function setExampleData() public function initialize() { /* simulate controller to allow controller methods in TV Input Properties controllers */ - $this->modx->smarty = $this->modx->services->get('smarty'); + $this->modx->getSmarty($this->modx->getManagerTemplatePath()); $context = $this->getProperty('context'); if (empty($context)) { diff --git a/core/src/Revolution/Processors/Element/TemplateVar/Renders/GetInputProperties.php b/core/src/Revolution/Processors/Element/TemplateVar/Renders/GetInputProperties.php index 388f0319ba9..2b0b5fef7f2 100644 --- a/core/src/Revolution/Processors/Element/TemplateVar/Renders/GetInputProperties.php +++ b/core/src/Revolution/Processors/Element/TemplateVar/Renders/GetInputProperties.php @@ -45,7 +45,7 @@ public function getLanguageTopics() public function initialize() { /* simulate controller to allow controller methods in TV Input Properties controllers */ - $this->modx->smarty = $this->modx->services->get('smarty'); + $this->modx->getSmarty($this->modx->getManagerTemplatePath()); $context = $this->getProperty('context'); if (empty($context)) { diff --git a/core/src/Revolution/Processors/Element/TemplateVar/Renders/GetInputs.php b/core/src/Revolution/Processors/Element/TemplateVar/Renders/GetInputs.php index ed8d2f62acb..1682a52df75 100644 --- a/core/src/Revolution/Processors/Element/TemplateVar/Renders/GetInputs.php +++ b/core/src/Revolution/Processors/Element/TemplateVar/Renders/GetInputs.php @@ -39,7 +39,7 @@ public function getLanguageTopics() public function initialize() { /* simulate controller to allow controller methods in TV Input Properties controllers */ - $this->modx->smarty = $this->modx->services->get('smarty'); + $this->modx->getSmarty($this->modx->getManagerTemplatePath()); return true; } diff --git a/core/src/Revolution/Processors/Resource/Reload.php b/core/src/Revolution/Processors/Resource/Reload.php index a238a6288b5..9820dbe44a8 100644 --- a/core/src/Revolution/Processors/Resource/Reload.php +++ b/core/src/Revolution/Processors/Resource/Reload.php @@ -37,9 +37,6 @@ public function initialize() { $return = true; $modx =& $this->modx; - if (!$modx->services->has('registry') || !$modx->registry) { - $return = 'Could not instantiate registry service.'; - } $modx->registry->addRegister('resource_reload', 'registry.modDbRegister', ['directory' => 'resource_reload']); $this->reg = $modx->registry->resource_reload; if (!$this->reg->connect()) { diff --git a/core/src/Revolution/Processors/Security/User/Create.php b/core/src/Revolution/Processors/Security/User/Create.php index 39a65938306..a948de0ed1b 100644 --- a/core/src/Revolution/Processors/Security/User/Create.php +++ b/core/src/Revolution/Processors/Security/User/Create.php @@ -222,12 +222,7 @@ public function sendNotificationEmail() { // Then restore previous placeholders to prevent any breakage $this->modx->placeholders = $ph; - if (!$this->modx->smarty) { - $this->modx->smarty = $this->modx->services->get('smarty'); - $this->modx->smarty->setTemplatePath( - $this->modx->getOption('manager_path') . 'templates/' . $this->modx->getOption('manager_theme', null, 'default') . '/' - ); - } + $this->modx->getSmarty($this->modx->getManagerTemplatePath()); $this->modx->smarty->assign('_config', $this->modx->config); $this->modx->smarty->assign('content', $message); $message = $this->modx->smarty->fetch('email/default.tpl'); @@ -243,7 +238,7 @@ public function sendNotificationEmail() { $activationHash = bin2hex(random_bytes(32)); /** @var modRegistry $registry */ - $registry = $this->modx->services->get('registry'); + $registry = $this->modx->registry; /** @var modRegister $register */ $register = $registry->getRegister('user', 'registry.modDbRegister'); $register->connect(); @@ -264,10 +259,7 @@ public function sendNotificationEmail() { // Then restore previous placeholders to prevent any breakage $this->modx->placeholders = $ph; - if (!$this->modx->smarty) { - $this->modx->smarty = $this->modx->services->get('smarty'); - $this->modx->smarty->setTemplatePath($this->modx->getOption('manager_path') . 'templates/default/'); - } + $this->modx->getSmarty($this->modx->getManagerTemplatePath()); $this->modx->smarty->assign('_config', $this->modx->config); $this->modx->smarty->assign('content', $message, true); diff --git a/core/src/Revolution/Processors/Security/User/Update.php b/core/src/Revolution/Processors/Security/User/Update.php index b6d9cfca82d..7cdcd4cfd16 100644 --- a/core/src/Revolution/Processors/Security/User/Update.php +++ b/core/src/Revolution/Processors/Security/User/Update.php @@ -319,7 +319,7 @@ public function sendNotificationEmail() { $activationHash = bin2hex(random_bytes(32)); /** @var modRegistry $registry */ - $registry = $this->modx->services->get('registry'); + $registry = $this->modx->registry; /** @var modRegister $register */ $register = $registry->getRegister('user', 'registry.modDbRegister'); $register->connect(); @@ -342,10 +342,7 @@ public function sendNotificationEmail() { // Then restore previous placeholders to prevent any breakage $this->modx->placeholders = $ph; - if (!$this->modx->smarty) { - $this->modx->smarty = $this->modx->services->get('smarty'); - $this->modx->smarty->setTemplatePath($this->modx->getOption('manager_path') . 'templates/default/'); - } + $this->modx->getSmarty($this->modx->getManagerTemplatePath()); $this->modx->smarty->assign('_config', $this->modx->config); $this->modx->smarty->assign('content', $message, true); diff --git a/core/src/Revolution/Processors/System/RemoveLocks.php b/core/src/Revolution/Processors/System/RemoveLocks.php index e2197de2498..59ed1061ebd 100644 --- a/core/src/Revolution/Processors/System/RemoveLocks.php +++ b/core/src/Revolution/Processors/System/RemoveLocks.php @@ -34,7 +34,7 @@ public function checkPermissions() public function process() { /** @var modRegistry $registry */ - $registry = $this->modx->services->get('registry'); + $registry = $this->modx->registry; if ($registry) { $registry->addRegister('locks', modDbRegister::class, ['directory' => 'locks']); $registry->locks->connect(); diff --git a/core/src/Revolution/modManagerRequest.php b/core/src/Revolution/modManagerRequest.php index 80e6eb1d302..74f10acc171 100644 --- a/core/src/Revolution/modManagerRequest.php +++ b/core/src/Revolution/modManagerRequest.php @@ -72,12 +72,7 @@ public function initialize() /* load smarty template engine */ $theme = $this->modx->getOption('manager_theme', null, 'default'); - $templatePath = $this->modx->getOption('manager_path') . 'templates/' . $theme . '/'; - if (!file_exists($templatePath)) { /* fallback to default */ - $templatePath = $this->modx->getOption('manager_path') . 'templates/default/'; - } - $this->modx->smarty = $this->modx->services->get('smarty'); - $this->modx->smarty->setTemplatePath($templatePath); + $this->modx->getSmarty($this->modx->getManagerTemplatePath(), true); /* load context-specific cache dir */ $this->modx->smarty->setCachePath($this->modx->context->get('key') . '/smarty/' . $theme . '/'); diff --git a/core/src/Revolution/modTemplateVar.php b/core/src/Revolution/modTemplateVar.php index 1b65acab18f..4732787aa0f 100644 --- a/core/src/Revolution/modTemplateVar.php +++ b/core/src/Revolution/modTemplateVar.php @@ -383,11 +383,8 @@ public function renderInput($resource = null, $options = []) $style = is_array($options) && isset($options['style']) ? strval($options['style']) : ''; $value = is_array($options) && isset($options['value']) ? strval($options['value']) : ''; } - if (!$this->xpdo->smarty) { - $this->xpdo->smarty = $this->xpdo->services->get('smarty'); - $this->xpdo->smarty->setTemplatePath( - $this->xpdo->getOption('manager_path') . 'templates/' . $this->xpdo->getOption('manager_theme', null, 'default') . '/' - ); + if ($this->xpdo instanceof modX) { + $this->xpdo->getSmarty($this->xpdo->getManagerTemplatePath()); } $this->xpdo->smarty->assign('style', $style); if (!isset($value) || empty($value)) { diff --git a/core/src/Revolution/modUser.php b/core/src/Revolution/modUser.php index 83241e84e04..07e9661207e 100644 --- a/core/src/Revolution/modUser.php +++ b/core/src/Revolution/modUser.php @@ -939,8 +939,7 @@ public function sendEmail($message, array $options = []) /** @var modUserProfile $profile */ $profile = $this->getOne('Profile'); /** @var modPHPMailer $mail */ - $mail = $this->xpdo->services->get('mail'); - $this->xpdo->mail = $mail; + $mail = $this->xpdo->getMail(); if (!$profile || !$mail) { return false; diff --git a/core/src/Revolution/modX.php b/core/src/Revolution/modX.php index f01e2b7e98b..a8cd3f06c7c 100644 --- a/core/src/Revolution/modX.php +++ b/core/src/Revolution/modX.php @@ -593,33 +593,7 @@ public function initialize($contextKey= 'web', $options = null) { $this->_initErrorHandler($options); $this->_initHttpClient(); $this->_initCulture($options); - - // Core DI services (#15986 tiers 1–2): prefer $modx->services->get() over getService(). - $this->services->add('registry', new modRegistry($this)); - $this->registry = $this->services->get('registry'); - - if (!$this->services->has('error')) { - $this->services->add('error', new modError($this)); - } - $this->error = $this->services->get('error'); - - if (!$this->services->has('hashing')) { - $this->services->add('hashing', new modHashing($this)); - } - $this->hashing = $this->services->get('hashing'); - - // Lazy shared: mail/smarty are heavier and unused on many front-end requests. - if (!$this->services->has('mail')) { - $this->services->add('mail', function () { - return new modPHPMailer($this); - }); - } - if (!$this->services->has('smarty')) { - $this->services->add('smarty', function () { - return new modSmarty($this); - }); - } - + $this->_registerCoreServices(); $this->services->add(modManagerDateFormatter::class, fn() => new modManagerDateFormatter($this)); if (!$this->getOption(xPDO::OPT_SETUP)) { @@ -819,6 +793,90 @@ public function getParser() { return $this->parser; } + /** + * Register built-in DI services that replace deprecated getService() usage for core keys. + * + * Prefer $modx->services->get() or the accessors below over getService(). + * hashing/registry/error are eager; mail/smarty are lazy shared factories. + */ + protected function _registerCoreServices(): void + { + if (!$this->services->has('registry')) { + $this->services->add('registry', new modRegistry($this)); + } + $this->registry = $this->services->get('registry'); + + if (!$this->services->has('error')) { + $this->services->add('error', new modError($this)); + } + $this->error = $this->services->get('error'); + + if (!$this->services->has('hashing')) { + $this->services->add('hashing', new modHashing($this)); + } + $this->hashing = $this->services->get('hashing'); + + if (!$this->services->has('mail')) { + $this->services->add('mail', function () { + return new modPHPMailer($this); + }); + } + if (!$this->services->has('smarty')) { + $this->services->add('smarty', function () { + return new modSmarty($this); + }); + } + } + + /** + * Resolve the manager Smarty template directory for the active theme. + */ + public function getManagerTemplatePath(): string + { + $theme = $this->getOption('manager_theme', null, 'default'); + $templatePath = $this->getOption('manager_path') . 'templates/' . $theme . '/'; + if (!file_exists($templatePath)) { + $templatePath = $this->getOption('manager_path') . 'templates/default/'; + } + + return $templatePath; + } + + /** + * Get the shared Smarty service, syncing $modx->smarty. + * + * Path policy matches former getService() first-wins: setTemplatePath runs only on the + * first bind unless $forceTemplatePath is true (manager request always forces the theme path). + * + * @param string|null $templatePath Optional template directory to apply on first bind (or when forced) + * @param bool $forceTemplatePath When true, always apply $templatePath even if smarty was already bound + */ + public function getSmarty(?string $templatePath = null, bool $forceTemplatePath = false): modSmarty + { + if (!$this->smarty) { + $this->smarty = $this->services->get('smarty'); + if ($templatePath) { + $this->smarty->setTemplatePath($templatePath); + } + } elseif ($forceTemplatePath && $templatePath) { + $this->smarty->setTemplatePath($templatePath); + } + + return $this->smarty; + } + + /** + * Get the shared mail service, syncing $modx->mail. + */ + public function getMail(): modMail + { + if (!$this->mail) { + $this->mail = $this->services->get('mail'); + } + + return $this->mail; + } + /** * Gets all of the parent resource ids for a given resource. * diff --git a/manager/controllers/default/dashboard/widget.buttons.php b/manager/controllers/default/dashboard/widget.buttons.php index 528465c1f5e..c318de25916 100644 --- a/manager/controllers/default/dashboard/widget.buttons.php +++ b/manager/controllers/default/dashboard/widget.buttons.php @@ -15,7 +15,6 @@ class modDashboardWidgetButtons extends modDashboardWidgetInterface */ public function render() { - $this->modx->smarty = $this->modx->services->get('smarty'); foreach ($this->widget->toArray() as $key => $value) { $this->modx->smarty->assign($key, $value); } diff --git a/manager/controllers/default/dashboard/widget.configcheck.php b/manager/controllers/default/dashboard/widget.configcheck.php index 41770b430c9..c9e61297565 100644 --- a/manager/controllers/default/dashboard/widget.configcheck.php +++ b/manager/controllers/default/dashboard/widget.configcheck.php @@ -31,7 +31,6 @@ public function render() /** @var ProcessorResponse $response */ $response = $this->modx->runProcessor(ConfigCheck::class); - $this->modx->smarty = $this->modx->services->get('smarty'); $this->modx->smarty->assign('warnings', $response->getObject()); return $this->controller->fetchTemplate('dashboard/configcheck.tpl'); diff --git a/manager/controllers/default/dashboard/widget.grid-online.php b/manager/controllers/default/dashboard/widget.grid-online.php index c85c33eda3e..a4506b938a8 100644 --- a/manager/controllers/default/dashboard/widget.grid-online.php +++ b/manager/controllers/default/dashboard/widget.grid-online.php @@ -35,7 +35,6 @@ public function render() $data = json_decode($data, true); } } - $this->modx->smarty = $this->modx->services->get('smarty'); $this->modx->smarty->assign('data', $data); $this->modx->smarty->assign('can_view_logs', $this->modx->hasPermission('logs')); diff --git a/manager/controllers/default/dashboard/widget.grid-rer.php b/manager/controllers/default/dashboard/widget.grid-rer.php index c691a5fd395..d46cb7631e7 100644 --- a/manager/controllers/default/dashboard/widget.grid-rer.php +++ b/manager/controllers/default/dashboard/widget.grid-rer.php @@ -38,7 +38,6 @@ public function render() $data = json_decode($data, true); } } - $this->modx->smarty = $this->modx->services->get('smarty'); $this->modx->smarty->assign('data', $data); $this->modx->smarty->assign('can_view_logs', $this->modx->hasPermission('logs')); diff --git a/manager/controllers/default/dashboard/widget.updates.php b/manager/controllers/default/dashboard/widget.updates.php index 430196a0866..547bc49b3ea 100644 --- a/manager/controllers/default/dashboard/widget.updates.php +++ b/manager/controllers/default/dashboard/widget.updates.php @@ -54,7 +54,6 @@ public function render() $this->modx->cacheManager->set($updateCacheKey, $data, $this->updatesCacheExpire, $updateCacheOptions); } - $this->modx->smarty = $this->modx->services->get('smarty'); foreach ($data as $key => $value) { $this->modx->smarty->assign($key, $value); } diff --git a/manager/controllers/default/security/login.class.php b/manager/controllers/default/security/login.class.php index c2695201fb7..6b5055d6d4c 100644 --- a/manager/controllers/default/security/login.class.php +++ b/manager/controllers/default/security/login.class.php @@ -263,7 +263,7 @@ public function handleForgotLoginHash() if (!empty($_GET['modhash'])) { $hash = $this->modx->sanitizeString($_GET['modhash']); /** @var modDbRegister $registry */ - $registry = $this->modx->services->get('registry') + $registry = $this->modx->registry ->getRegister('user', modDbRegister::class); $registry->connect(); $registry->subscribe('/pwd/change/' . $hash); @@ -291,7 +291,7 @@ public function handleMagicLoginLink() if (!empty($_GET['magiclink'])) { $hash = $this->modx->sanitizeString($_GET['magiclink']); /** @var modDbRegister $registry */ - $registry = $this->modx->services->get('registry') + $registry = $this->modx->registry ->getRegister('user', 'registry.modDbRegister'); $registry->connect(); $registry->subscribe('/pwd/magiclink/' . $hash); @@ -411,7 +411,7 @@ public function handleLogin() $hash = $this->modx->sanitizeString($this->scriptProperties['modhash']); if (!empty($hash)) { /** @var modDbRegister $registry */ - $registry = $this->modx->services->get('registry') + $registry = $this->modx->registry ->getRegister('user', modDbRegister::class); $registry->connect(); $registry->subscribe('/pwd/change/' . $hash); @@ -533,7 +533,7 @@ private function setActivationHash($user, $ttl = 86400, $topic = '/pwd/change/') $hash = md5(uniqid(md5($user->get('email') . '/' . $user->get('id')), true)); /** @var modRegistry $registry */ - $registry = $this->modx->services->get('registry'); + $registry = $this->modx->registry; /** @var modDbRegister $register */ $register = $registry->getRegister('user', modDbRegister::class); $register->connect(); From ce305612a09a6ca3be4cc118031dce284d24f8af Mon Sep 17 00:00:00 2001 From: Ivan Bochkarev Date: Mon, 10 Aug 2026 08:33:31 +0600 Subject: [PATCH 5/6] style(modX): rename registerCoreServices for PHPCS --- core/src/Revolution/modX.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/Revolution/modX.php b/core/src/Revolution/modX.php index a8cd3f06c7c..b344e8c5ff4 100644 --- a/core/src/Revolution/modX.php +++ b/core/src/Revolution/modX.php @@ -593,7 +593,7 @@ public function initialize($contextKey= 'web', $options = null) { $this->_initErrorHandler($options); $this->_initHttpClient(); $this->_initCulture($options); - $this->_registerCoreServices(); + $this->registerCoreServices(); $this->services->add(modManagerDateFormatter::class, fn() => new modManagerDateFormatter($this)); if (!$this->getOption(xPDO::OPT_SETUP)) { @@ -799,7 +799,7 @@ public function getParser() { * Prefer $modx->services->get() or the accessors below over getService(). * hashing/registry/error are eager; mail/smarty are lazy shared factories. */ - protected function _registerCoreServices(): void + protected function registerCoreServices(): void { if (!$this->services->has('registry')) { $this->services->add('registry', new modRegistry($this)); From 68b23af7465610231aa05767ee4dc6ad50bde8b2 Mon Sep 17 00:00:00 2001 From: Ivan Bochkarev Date: Mon, 10 Aug 2026 08:37:20 +0600 Subject: [PATCH 6/6] fix: restore smarty/hashing bootstrap for PHPUnit Do not pre-register mail/smarty (keeps getService param+property sync), ensure widgets call getSmarty(), sync properties in modX::getService, and resolve hashing on plain xPDO without touching a missing property. --- _build/test/MODxControllerTestCase.php | 5 +-- .../Tests/Controllers/LoadControllerTest.php | 5 +-- core/src/Revolution/modUser.php | 42 +++++++++++++++---- core/src/Revolution/modX.php | 40 +++++++++++++----- .../default/dashboard/widget.buttons.php | 1 + .../default/dashboard/widget.configcheck.php | 1 + .../default/dashboard/widget.grid-online.php | 1 + .../default/dashboard/widget.grid-rer.php | 1 + .../default/dashboard/widget.updates.php | 1 + 9 files changed, 71 insertions(+), 26 deletions(-) diff --git a/_build/test/MODxControllerTestCase.php b/_build/test/MODxControllerTestCase.php index f95c47a4d95..d266746ff34 100644 --- a/_build/test/MODxControllerTestCase.php +++ b/_build/test/MODxControllerTestCase.php @@ -11,7 +11,6 @@ */ namespace MODX\Revolution; -use MODX\Revolution\Smarty\modSmarty; /** * Abstract class extending MODxTestCase for controller-specific testing @@ -43,9 +42,7 @@ public function setUpFixtures() { /* load smarty template engine */ $templatePath = $this->modx->getOption('manager_path') . 'templates/default/'; - $this->modx->getService('smarty', modSmarty::class, '', [ - 'template_dir' => $templatePath, - ]); + $this->modx->getSmarty($templatePath); $this->modx->smarty->setCachePath('mgr/smarty/default/'); $this->modx->smarty->assign('_config',$this->modx->config); $this->modx->smarty->assignByRef('modx',$this->modx); diff --git a/_build/test/Tests/Controllers/LoadControllerTest.php b/_build/test/Tests/Controllers/LoadControllerTest.php index 7ebfd59adf7..5c0d19f5c11 100644 --- a/_build/test/Tests/Controllers/LoadControllerTest.php +++ b/_build/test/Tests/Controllers/LoadControllerTest.php @@ -7,7 +7,6 @@ use MODX\Revolution\modX; use MODX\Revolution\MODxTestCase; use MODX\Revolution\Processors\Resource\Create; -use MODX\Revolution\Smarty\modSmarty; /** * Tests related to the modManagerResponse and modManagerController classes for loading controllers @@ -27,9 +26,7 @@ public function setUpFixtures() /* load smarty template engine */ $templatePath = $this->modx->getOption('manager_path') . 'templates/default/'; - $this->modx->getService('smarty', modSmarty::class, '', [ - 'template_dir' => $templatePath, - ]); + $this->modx->getSmarty($templatePath); $this->modx->smarty->setCachePath('mgr/smarty/default/'); $this->modx->smarty->assign('_config', $this->modx->config); $this->modx->smarty->assignByRef('modx', $this->modx); diff --git a/core/src/Revolution/modUser.php b/core/src/Revolution/modUser.php index 07e9661207e..27d6bb03f41 100644 --- a/core/src/Revolution/modUser.php +++ b/core/src/Revolution/modUser.php @@ -3,6 +3,7 @@ namespace MODX\Revolution; use MODX\Revolution\Hashing\modHash; +use MODX\Revolution\Hashing\modHashing; use MODX\Revolution\Mail\modMail; use MODX\Revolution\Mail\modPHPMailer; use MODX\Revolution\Registry\modDbRegister; @@ -58,12 +59,15 @@ public function set($k, $v = null, $vType = '') return false; } } - if (in_array($k, ['password', 'cachepwd']) && $this->xpdo->hashing) { - if (!$this->get('salt')) { - $this->set('salt', md5(uniqid(rand(), true))); + if (in_array($k, ['password', 'cachepwd'])) { + $hashing = $this->getHashingService(); + if ($hashing) { + if (!$this->get('salt')) { + $this->set('salt', md5(uniqid(rand(), true))); + } + $vOptions = ['salt' => $this->get('salt')]; + $v = $hashing->getHash('', $this->get('hash_class'))->hash($v, $vOptions); } - $vOptions = ['salt' => $this->get('salt')]; - $v = $this->xpdo->hashing->getHash('', $this->get('hash_class'))->hash($v, $vOptions); } return parent::set($k, $v, $vType); @@ -256,17 +260,41 @@ public function endSession() public function passwordMatches($password, array $options = []) { $match = false; - if ($this->xpdo->hashing) { + $hashing = $this->getHashingService(); + if ($hashing) { $options = array_merge(['salt' => $this->get('salt')], $options); /** @var modHash $hasher */ - $hasher = $this->xpdo->hashing->getHash('', $this->get('hash_class')); + $hasher = $hashing->getHash('', $this->get('hash_class')); $match = $hasher->verify($password, $this->get('password'), $options); } return $match; } + /** + * Resolve the hashing service from modX property or the DI container (plain xPDO / setup). + */ + private function getHashingService(): ?modHashing + { + if ($this->xpdo instanceof modX && $this->xpdo->hashing instanceof modHashing) { + return $this->xpdo->hashing; + } + if (!$this->xpdo->services->has('hashing')) { + $this->xpdo->services->add('hashing', new modHashing($this->xpdo)); + } + $hashing = $this->xpdo->services->get('hashing'); + if ($hashing instanceof modHashing) { + if ($this->xpdo instanceof modX && empty($this->xpdo->hashing)) { + $this->xpdo->hashing = $hashing; + } + + return $hashing; + } + + return null; + } + /** * Activate a reset user password if the proper activation key is provided. * diff --git a/core/src/Revolution/modX.php b/core/src/Revolution/modX.php index b344e8c5ff4..c4aae53763b 100644 --- a/core/src/Revolution/modX.php +++ b/core/src/Revolution/modX.php @@ -797,7 +797,7 @@ public function getParser() { * Register built-in DI services that replace deprecated getService() usage for core keys. * * Prefer $modx->services->get() or the accessors below over getService(). - * hashing/registry/error are eager; mail/smarty are lazy shared factories. + * hashing/registry/error are eager; mail/smarty register on first accessor/getService use. */ protected function registerCoreServices(): void { @@ -816,16 +816,8 @@ protected function registerCoreServices(): void } $this->hashing = $this->services->get('hashing'); - if (!$this->services->has('mail')) { - $this->services->add('mail', function () { - return new modPHPMailer($this); - }); - } - if (!$this->services->has('smarty')) { - $this->services->add('smarty', function () { - return new modSmarty($this); - }); - } + // mail/smarty stay on-demand via getMail()/getSmarty() so getService() can still + // construct them with params and sync $modx->mail / $modx->smarty (tests, extras). } /** @@ -853,6 +845,11 @@ public function getManagerTemplatePath(): string */ public function getSmarty(?string $templatePath = null, bool $forceTemplatePath = false): modSmarty { + if (!$this->services->has('smarty')) { + $this->services->add('smarty', function () { + return new modSmarty($this); + }); + } if (!$this->smarty) { $this->smarty = $this->services->get('smarty'); if ($templatePath) { @@ -870,6 +867,11 @@ public function getSmarty(?string $templatePath = null, bool $forceTemplatePath */ public function getMail(): modMail { + if (!$this->services->has('mail')) { + $this->services->add('mail', function () { + return new modPHPMailer($this); + }); + } if (!$this->mail) { $this->mail = $this->services->get('mail'); } @@ -877,6 +879,22 @@ public function getMail(): modMail return $this->mail; } + /** + * Ensure $modx->$name is synced when a service was already registered in the container + * (parent getService only assigns the property on first construction). + * + * @deprecated Use $modx->services or accessors (getSmarty/getMail/…) + */ + public function getService($name, $class = '', $path = '', $params = []) + { + $service = parent::getService($name, $class, $path, $params); + if (is_object($service) && empty($this->$name)) { + $this->$name = $service; + } + + return $service; + } + /** * Gets all of the parent resource ids for a given resource. * diff --git a/manager/controllers/default/dashboard/widget.buttons.php b/manager/controllers/default/dashboard/widget.buttons.php index c318de25916..d23b1f4e606 100644 --- a/manager/controllers/default/dashboard/widget.buttons.php +++ b/manager/controllers/default/dashboard/widget.buttons.php @@ -15,6 +15,7 @@ class modDashboardWidgetButtons extends modDashboardWidgetInterface */ public function render() { + $this->modx->getSmarty(); foreach ($this->widget->toArray() as $key => $value) { $this->modx->smarty->assign($key, $value); } diff --git a/manager/controllers/default/dashboard/widget.configcheck.php b/manager/controllers/default/dashboard/widget.configcheck.php index c9e61297565..080de6f2afd 100644 --- a/manager/controllers/default/dashboard/widget.configcheck.php +++ b/manager/controllers/default/dashboard/widget.configcheck.php @@ -31,6 +31,7 @@ public function render() /** @var ProcessorResponse $response */ $response = $this->modx->runProcessor(ConfigCheck::class); + $this->modx->getSmarty(); $this->modx->smarty->assign('warnings', $response->getObject()); return $this->controller->fetchTemplate('dashboard/configcheck.tpl'); diff --git a/manager/controllers/default/dashboard/widget.grid-online.php b/manager/controllers/default/dashboard/widget.grid-online.php index a4506b938a8..122583a7b55 100644 --- a/manager/controllers/default/dashboard/widget.grid-online.php +++ b/manager/controllers/default/dashboard/widget.grid-online.php @@ -35,6 +35,7 @@ public function render() $data = json_decode($data, true); } } + $this->modx->getSmarty(); $this->modx->smarty->assign('data', $data); $this->modx->smarty->assign('can_view_logs', $this->modx->hasPermission('logs')); diff --git a/manager/controllers/default/dashboard/widget.grid-rer.php b/manager/controllers/default/dashboard/widget.grid-rer.php index d46cb7631e7..ef84f88ca2e 100644 --- a/manager/controllers/default/dashboard/widget.grid-rer.php +++ b/manager/controllers/default/dashboard/widget.grid-rer.php @@ -38,6 +38,7 @@ public function render() $data = json_decode($data, true); } } + $this->modx->getSmarty(); $this->modx->smarty->assign('data', $data); $this->modx->smarty->assign('can_view_logs', $this->modx->hasPermission('logs')); diff --git a/manager/controllers/default/dashboard/widget.updates.php b/manager/controllers/default/dashboard/widget.updates.php index 547bc49b3ea..7fca9fbc930 100644 --- a/manager/controllers/default/dashboard/widget.updates.php +++ b/manager/controllers/default/dashboard/widget.updates.php @@ -54,6 +54,7 @@ public function render() $this->modx->cacheManager->set($updateCacheKey, $data, $this->updatesCacheExpire, $updateCacheOptions); } + $this->modx->getSmarty(); foreach ($data as $key => $value) { $this->modx->smarty->assign($key, $value); }