Skip to content

Commit 3ecac63

Browse files
authored
[6.0] Fix language autoload in CMSPlugin constructor to work safely (#40355)
* Move language autoload out from CMSPlugin constructor * CMSPlugin::autoloadLanguage() * cs * re-arrange autoloadLanguage feature
1 parent 2fefcf9 commit 3ecac63

File tree

3 files changed

+75
-9
lines changed

3 files changed

+75
-9
lines changed

libraries/src/Plugin/CMSPlugin.php

Lines changed: 70 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,25 @@ abstract class CMSPlugin implements DispatcherAwareInterface, PluginInterface, L
7272
protected $_type = null;
7373

7474
/**
75-
* Affects constructor behavior. If true, language files will be loaded automatically.
75+
* If true, language files of the plugin will be loaded automatically.
76+
*
77+
* NOTE: Enabling this feature have a negative effect on performance,
78+
* therefore it is recommended to load a language manually, in the respective event.
7679
*
7780
* @var boolean
7881
* @since 3.1
7982
*/
8083
protected $autoloadLanguage = false;
8184

85+
/**
86+
* Internal flag for autoloadLanguage feature.
87+
*
88+
* @var boolean
89+
*
90+
* @since __DEPLOY_VERSION__
91+
*/
92+
private $autoloadLanguageDone = false;
93+
8294
/**
8395
* Should I try to detect and register legacy event listeners, i.e. methods which accept unwrapped arguments? While
8496
* this maintains a great degree of backwards compatibility to Joomla! 3.x-style plugins it is much slower. You are
@@ -154,11 +166,6 @@ public function __construct($config = [])
154166
$this->_type = $config['type'];
155167
}
156168

157-
// Load the language files if needed.
158-
if ($this->autoloadLanguage) {
159-
$this->loadLanguage();
160-
}
161-
162169
if (property_exists($this, 'app')) {
163170
@trigger_error('The application should be injected through setApplication() and requested through getApplication().', E_USER_DEPRECATED);
164171
$reflection = new \ReflectionClass($this);
@@ -178,6 +185,12 @@ public function __construct($config = [])
178185
$this->db = Factory::getDbo();
179186
}
180187
}
188+
189+
// Load the language files if needed.
190+
// It is required Application to be set, so we trying it here and in CMSPlugin::setApplication()
191+
if ($this->autoloadLanguage && $this->getApplication()) {
192+
$this->autoloadLanguage();
193+
}
181194
}
182195

183196
/**
@@ -193,11 +206,26 @@ public function __construct($config = [])
193206
public function loadLanguage($extension = '', $basePath = JPATH_ADMINISTRATOR)
194207
{
195208
if (empty($extension)) {
196-
$extension = 'Plg_' . $this->_type . '_' . $this->_name;
209+
$extension = 'plg_' . $this->_type . '_' . $this->_name;
197210
}
198211

199212
$extension = strtolower($extension);
200-
$lang = $this->getApplication() ? $this->getApplication()->getLanguage() : Factory::getLanguage();
213+
$app = $this->getApplication() ?: Factory::getApplication();
214+
$lang = $app->getLanguage();
215+
216+
if (!$lang) {
217+
// @TODO: Throw an exception in Joomla 7
218+
@trigger_error(
219+
\sprintf(
220+
'Trying to load language before Application is initialised is discouraged. This will throw an exception in 7.0. Plugin "%s/%s"',
221+
$this->_type,
222+
$this->_name
223+
),
224+
E_USER_DEPRECATED
225+
);
226+
227+
return false;
228+
}
201229

202230
// If language already loaded, don't load it again.
203231
if ($lang->getPaths($extension)) {
@@ -208,6 +236,35 @@ public function loadLanguage($extension = '', $basePath = JPATH_ADMINISTRATOR)
208236
|| $lang->load($extension, JPATH_PLUGINS . '/' . $this->_type . '/' . $this->_name);
209237
}
210238

239+
/**
240+
* Method to handle language autoload feature in safe way, only when the language is initialised.
241+
*
242+
* @return void
243+
*
244+
* @internal The method does not expect to be called outside the CMSPlugin class.
245+
*
246+
* @since __DEPLOY_VERSION__
247+
*/
248+
final protected function autoloadLanguage(): void
249+
{
250+
if ($this->autoloadLanguageDone) {
251+
return;
252+
}
253+
254+
$app = $this->getApplication();
255+
256+
// Check whether language already initialised in the Application, otherwise wait for it
257+
if (!$app->getLanguage()) {
258+
$app->getDispatcher()->addListener('onAfterInitialise', function () {
259+
$this->loadLanguage();
260+
});
261+
} else {
262+
$this->loadLanguage();
263+
}
264+
265+
$this->autoloadLanguageDone = true;
266+
}
267+
211268
/**
212269
* Registers legacy Listeners to the Dispatcher, emulating how plugins worked under Joomla! 3.x and below.
213270
*
@@ -426,6 +483,11 @@ public function setApplication(CMSApplicationInterface $application): void
426483
if ($application->getLanguage()) {
427484
$this->setLanguage($application->getLanguage());
428485
}
486+
487+
// Try to load the language files if it were not loaded in the constructor already.
488+
if ($this->autoloadLanguage) {
489+
$this->autoloadLanguage();
490+
}
429491
}
430492

431493
/**

tests/Unit/Plugin/EditorsXtd/PageBreak/Extension/PageBreakTest.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ public function testInvalidPermissions()
8989
$user->method('getAuthorisedCategories')->willReturn([]);
9090

9191
$app = $this->createStub(CMSWebApplicationInterface::class);
92+
$app->method('getLanguage')->willReturn($this->createStub(Language::class));
9293
$app->method('getIdentity')->willReturn($user);
9394

9495
$btnsReg = new ButtonsRegistry();
@@ -126,7 +127,9 @@ public function testInvalidApplication()
126127

127128
$dispatcher = new Dispatcher();
128129
$plugin = new PageBreak($dispatcher, ['name' => 'pagebreak', 'type' => 'editors-xtd', 'params' => []]);
129-
$plugin->setApplication($this->createStub(CMSApplicationInterface::class));
130+
$app = $this->createStub(CMSApplicationInterface::class);
131+
$app->method('getLanguage')->willReturn($this->createStub(Language::class));
132+
$plugin->setApplication($app);
130133
$plugin->onEditorButtonsSetup($event);
131134

132135
$button = $btnsReg->getAll()[0] ?? false;

tests/Unit/Plugin/Filesystem/Local/Extension/LocalPluginTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ public function testAdapters()
106106

107107
$app = $this->createStub(CMSApplicationInterface::class);
108108
$app->method('getIdentity')->willReturn(new User());
109+
$app->method('getLanguage')->willReturn($this->createStub(Language::class));
109110

110111
$plugin = new Local($dispatcher, ['params' => ['directories' => '[{"directory": "tests"}]']], JPATH_ROOT);
111112
$plugin->setApplication($app);

0 commit comments

Comments
 (0)