diff --git a/plugins/task/sitestatus/services/provider.php b/plugins/task/sitestatus/services/provider.php new file mode 100644 index 0000000000000..a5e6afce3465f --- /dev/null +++ b/plugins/task/sitestatus/services/provider.php @@ -0,0 +1,50 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +defined('_JEXEC') or die; + +use Joomla\CMS\Extension\PluginInterface; +use Joomla\CMS\Factory; +use Joomla\CMS\Plugin\PluginHelper; +use Joomla\DI\Container; +use Joomla\DI\ServiceProviderInterface; +use Joomla\Event\DispatcherInterface; +use Joomla\Plugin\Task\SiteStatus\Extension\SiteStatus; +use Joomla\Utilities\ArrayHelper; + +return new class implements ServiceProviderInterface +{ + /** + * Registers the service provider with a DI container. + * + * @param Container $container The DI container. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function register(Container $container) + { + $container->set( + PluginInterface::class, + function (Container $container) + { + $plugin = new SiteStatus( + $container->get(DispatcherInterface::class), + (array) PluginHelper::getPlugin('task', 'sitestatus'), + ArrayHelper::fromObject(new JConfig), + JPATH_CONFIGURATION . '/configuration.php' + ); + $plugin->setApplication(Factory::getApplication()); + + return $plugin; + } + ); + } +}; diff --git a/plugins/task/sitestatus/sitestatus.xml b/plugins/task/sitestatus/sitestatus.xml index ba54f3a508c85..ccae445cbb324 100644 --- a/plugins/task/sitestatus/sitestatus.xml +++ b/plugins/task/sitestatus/sitestatus.xml @@ -9,10 +9,10 @@ www.joomla.org 4.1 PLG_TASK_SITE_STATUS_XML_DESCRIPTION + Joomla\Plugin\Task\SiteStatus - sitestatus.php - language - forms + services + src language/en-GB/plg_task_sitestatus.ini diff --git a/plugins/task/sitestatus/sitestatus.php b/plugins/task/sitestatus/src/Extension/SiteStatus.php similarity index 62% rename from plugins/task/sitestatus/sitestatus.php rename to plugins/task/sitestatus/src/Extension/SiteStatus.php index ecb7cbb9817a6..a8bc52c22af93 100644 --- a/plugins/task/sitestatus/sitestatus.php +++ b/plugins/task/sitestatus/src/Extension/SiteStatus.php @@ -7,20 +7,21 @@ * @license GNU General Public License version 2 or later; see LICENSE.txt */ +namespace Joomla\Plugin\Task\SiteStatus\Extension; + // Restrict direct access defined('_JEXEC') or die; -use Joomla\CMS\Application\CMSApplication; -use Joomla\CMS\Filesystem\File; -use Joomla\CMS\Filesystem\Path; -use Joomla\CMS\Language\Text; +use Exception; use Joomla\CMS\Plugin\CMSPlugin; use Joomla\Component\Scheduler\Administrator\Event\ExecuteTaskEvent; use Joomla\Component\Scheduler\Administrator\Task\Status; use Joomla\Component\Scheduler\Administrator\Traits\TaskPluginTrait; +use Joomla\Event\DispatcherInterface; use Joomla\Event\SubscriberInterface; +use Joomla\Filesystem\File; +use Joomla\Filesystem\Path; use Joomla\Registry\Registry; -use Joomla\Utilities\ArrayHelper; /** * Task plugin with routines to change the offline status of the site. These routines can be used to control planned @@ -28,7 +29,7 @@ * * @since 4.1.0 */ -class PlgTaskSitestatus extends CMSPlugin implements SubscriberInterface +final class SiteStatus extends CMSPlugin implements SubscriberInterface { use TaskPluginTrait; @@ -54,14 +55,6 @@ class PlgTaskSitestatus extends CMSPlugin implements SubscriberInterface ]; - /** - * The application object. - * - * @var CMSApplication - * @since 4.1.0 - */ - protected $app; - /** * Autoload the language file. * @@ -85,6 +78,40 @@ public static function getSubscribedEvents(): array ]; } + /** + * The old config + * + * @var array + * @since __DEPLOY_VERSION__ + */ + private $oldConfig; + + /** + * The config file + * + * @var string + * @since __DEPLOY_VERSION__ + */ + private $configFile; + + /** + * Constructor. + * + * @param DispatcherInterface $dispatcher The dispatcher + * @param array $config An optional associative array of configuration settings + * @param array $oldConfig The old config + * @param string $configFile The config + * + * @since __DEPLOY_VERSION__ + */ + public function __construct(DispatcherInterface $dispatcher, array $config, array $oldConfig, string $configFile) + { + parent::__construct($dispatcher, $config); + + $this->oldConfig = $oldConfig; + $this->configFile = $configFile; + } + /** * @param ExecuteTaskEvent $event The onExecuteTask event * @@ -102,7 +129,7 @@ public function alterSiteStatus(ExecuteTaskEvent $event): void $this->startRoutine($event); - $config = ArrayHelper::fromObject(new JConfig); + $config = $this->oldConfig; $toggle = self::TASKS_MAP[$event->getRoutineId()]['toggle']; $oldStatus = $config['offline'] ? 'offline' : 'online'; @@ -113,13 +140,12 @@ public function alterSiteStatus(ExecuteTaskEvent $event): void } else { - $offline = self::TASKS_MAP[$event->getRoutineId()]['offline']; - $config['offline'] = $offline; + $config['offline'] = self::TASKS_MAP[$event->getRoutineId()]['offline']; } $newStatus = $config['offline'] ? 'offline' : 'online'; $exit = $this->writeConfigFile(new Registry($config)); - $this->logTask(Text::sprintf('PLG_TASK_SITE_STATUS_TASK_LOG_SITE_STATUS', $oldStatus, $newStatus)); + $this->logTask($this->translate('PLG_TASK_SITE_STATUS_TASK_LOG_SITE_STATUS', $oldStatus, $newStatus)); $this->endRoutine($event, $exit); } @@ -137,20 +163,23 @@ public function alterSiteStatus(ExecuteTaskEvent $event): void private function writeConfigFile(Registry $config): int { // Set the configuration file path. - $file = JPATH_CONFIGURATION . '/configuration.php'; + $file = $this->configFile; // Attempt to make the file writeable. - if (Path::isOwner($file) && !Path::setPermissions($file)) + if (file_exists($file) && Path::isOwner($file) && !Path::setPermissions($file)) { - $this->logTask(Text::_('PLG_TASK_SITE_STATUS_ERROR_CONFIGURATION_PHP_NOTWRITABLE'), 'notice'); + $this->logTask($this->translate('PLG_TASK_SITE_STATUS_ERROR_CONFIGURATION_PHP_NOTWRITABLE'), 'notice'); } - // Attempt to write the configuration file as a PHP class named JConfig. - $configuration = $config->toString('PHP', array('class' => 'JConfig', 'closingtag' => false)); - - if (!File::write($file, $configuration)) + try + { + // Attempt to write the configuration file as a PHP class named JConfig. + $configuration = $config->toString('PHP', array('class' => 'JConfig', 'closingtag' => false)); + File::write($file, $configuration); + } + catch (Exception $e) { - $this->logTask(Text::_('PLG_TASK_SITE_STATUS_ERROR_WRITE_FAILED'), 'error'); + $this->logTask($this->translate('PLG_TASK_SITE_STATUS_ERROR_WRITE_FAILED'), 'error'); return Status::KNOCKOUT; } @@ -164,7 +193,7 @@ private function writeConfigFile(Registry $config): int // Attempt to make the file un-writeable. if (Path::isOwner($file) && !Path::setPermissions($file, '0444')) { - $this->logTask(Text::_('PLG_TASK_SITE_STATUS_ERROR_CONFIGURATION_PHP_NOTUNWRITABLE'), 'notice'); + $this->logTask($this->translate('PLG_TASK_SITE_STATUS_ERROR_CONFIGURATION_PHP_NOTUNWRITABLE'), 'notice'); } return Status::OK; diff --git a/tests/Unit/Plugin/Task/SiteStatus/Extension/SiteStatusPluginTest.php b/tests/Unit/Plugin/Task/SiteStatus/Extension/SiteStatusPluginTest.php new file mode 100644 index 0000000000000..66d420824dd7a --- /dev/null +++ b/tests/Unit/Plugin/Task/SiteStatus/Extension/SiteStatusPluginTest.php @@ -0,0 +1,243 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Tests\Unit\Plugin\Task\SiteStatus\Extension; + +use Joomla\CMS\Application\CMSApplicationInterface; +use Joomla\CMS\Language\Language; +use Joomla\Component\Scheduler\Administrator\Event\ExecuteTaskEvent; +use Joomla\Component\Scheduler\Administrator\Task\Status; +use Joomla\Component\Scheduler\Administrator\Task\Task; +use Joomla\Event\Dispatcher; +use Joomla\Filesystem\Folder; +use Joomla\Plugin\Task\SiteStatus\Extension\SiteStatus; +use Joomla\Tests\Unit\UnitTestCase; + +/** + * Test class for SiteStatus plugin + * + * @package Joomla.UnitTest + * @subpackage SiteStatus + * + * @testdox The SiteStatus plugin + * + * @since __DEPLOY_VERSION__ + */ +class SiteStatusPluginTest extends UnitTestCase +{ + /** + * Setup + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function setUp(): void + { + if (!is_dir(__DIR__ . '/tmp')) + { + mkdir(__DIR__ . '/tmp'); + } + + touch(__DIR__ . '/tmp/config.php'); + } + + /** + * Cleanup + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function tearDown(): void + { + if (is_dir(__DIR__ . '/tmp')) + { + Folder::delete(__DIR__ . '/tmp'); + } + } + + /** + * @testdox can set the config from online to offline + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testSetOnlineWhenOffline() + { + $app = $this->createStub(CMSApplicationInterface::class); + $app->method('getLanguage')->willReturn($this->createStub(Language::class)); + + $plugin = new SiteStatus(new Dispatcher, [], ['offline' => true], __DIR__ . '/tmp/config.php'); + $plugin->setApplication($app); + + $task = $this->createStub(Task::class); + $task->method('get')->willReturnMap([['id', null, 1], ['type', null, 'plg_task_toggle_offline_set_online']]); + + $event = new ExecuteTaskEvent('test', ['subject' => $task]); + $plugin->alterSiteStatus($event); + + $this->assertEquals(Status::OK, $event->getResultSnapshot()['status']); + $this->assertStringContainsString('$offline = false;', file_get_contents(__DIR__ . '/tmp/config.php')); + } + + /** + * @testdox can keep the config online + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testSetOnlineWhenOnline() + { + $app = $this->createStub(CMSApplicationInterface::class); + $app->method('getLanguage')->willReturn($this->createStub(Language::class)); + + $plugin = new SiteStatus(new Dispatcher, [], ['offline' => false], __DIR__ . '/tmp/config.php'); + $plugin->setApplication($app); + + $task = $this->createStub(Task::class); + $task->method('get')->willReturnMap([['id', null, 1], ['type', null, 'plg_task_toggle_offline_set_online']]); + + $event = new ExecuteTaskEvent('test', ['subject' => $task]); + $plugin->alterSiteStatus($event); + + $this->assertEquals(Status::OK, $event->getResultSnapshot()['status']); + $this->assertStringContainsString('$offline = false;', file_get_contents(__DIR__ . '/tmp/config.php')); + } + + /** + * @testdox can set the config from offline to online + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testSetOfflineWhenOnline() + { + $app = $this->createStub(CMSApplicationInterface::class); + $app->method('getLanguage')->willReturn($this->createStub(Language::class)); + + $plugin = new SiteStatus(new Dispatcher, [], ['offline' => false], __DIR__ . '/tmp/config.php'); + $plugin->setApplication($app); + + $task = $this->createStub(Task::class); + $task->method('get')->willReturnMap([['id', null, 1], ['type', null, 'plg_task_toggle_offline_set_offline']]); + + $event = new ExecuteTaskEvent('test', ['subject' => $task]); + $plugin->alterSiteStatus($event); + + $this->assertEquals(Status::OK, $event->getResultSnapshot()['status']); + $this->assertStringContainsString('$offline = true;', file_get_contents(__DIR__ . '/tmp/config.php')); + } + + /** + * @testdox can keep the config offline + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testSetOfflineWhenOffline() + { + $app = $this->createStub(CMSApplicationInterface::class); + $app->method('getLanguage')->willReturn($this->createStub(Language::class)); + + $plugin = new SiteStatus(new Dispatcher, [], ['offline' => true], __DIR__ . '/tmp/config.php'); + $plugin->setApplication($app); + + $task = $this->createStub(Task::class); + $task->method('get')->willReturnMap([['id', null, 1], ['type', null, 'plg_task_toggle_offline_set_offline']]); + + $event = new ExecuteTaskEvent('test', ['subject' => $task]); + $plugin->alterSiteStatus($event); + + $this->assertEquals(Status::OK, $event->getResultSnapshot()['status']); + $this->assertStringContainsString('$offline = true;', file_get_contents(__DIR__ . '/tmp/config.php')); + } + + /** + * @testdox can toggle the config from online to offline + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testToggleOffline() + { + $app = $this->createStub(CMSApplicationInterface::class); + $app->method('getLanguage')->willReturn($this->createStub(Language::class)); + + $plugin = new SiteStatus(new Dispatcher, [], ['offline' => false], __DIR__ . '/tmp/config.php'); + $plugin->setApplication($app); + + $task = $this->createStub(Task::class); + $task->method('get')->willReturnMap([['id', null, 1], ['type', null, 'plg_task_toggle_offline']]); + + $event = new ExecuteTaskEvent('test', ['subject' => $task]); + $plugin->alterSiteStatus($event); + + $this->assertEquals(Status::OK, $event->getResultSnapshot()['status']); + $this->assertStringContainsString('$offline = true;', file_get_contents(__DIR__ . '/tmp/config.php')); + } + + /** + * @testdox can toggle the config from offline to online + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testToggleOnline() + { + $app = $this->createStub(CMSApplicationInterface::class); + $app->method('getLanguage')->willReturn($this->createStub(Language::class)); + + $plugin = new SiteStatus(new Dispatcher, [], ['offline' => true], __DIR__ . '/tmp/config.php'); + $plugin->setApplication($app); + + $task = $this->createStub(Task::class); + $task->method('get')->willReturnMap([['id', null, 1], ['type', null, 'plg_task_toggle_offline']]); + + $event = new ExecuteTaskEvent('test', ['subject' => $task]); + $plugin->alterSiteStatus($event); + + $this->assertEquals(Status::OK, $event->getResultSnapshot()['status']); + $this->assertStringContainsString('$offline = false;', file_get_contents(__DIR__ . '/tmp/config.php')); + } + + /** + * @testdox can't set the config file' + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testInvalidConfigFile() + { + $language = $this->createStub(Language::class); + $language->method('_')->willReturn('test'); + + $app = $this->createStub(CMSApplicationInterface::class); + $app->method('getLanguage')->willReturn($language); + + $plugin = new SiteStatus(new Dispatcher, [], ['offline' => true], '/invalid/config.php'); + $plugin->setApplication($app); + + $task = $this->createStub(Task::class); + $task->method('get')->willReturnMap([['id', null, 1], ['type', null, 'plg_task_toggle_offline']]); + + $event = new ExecuteTaskEvent('test', ['subject' => $task]); + $plugin->alterSiteStatus($event); + + $this->assertEquals(Status::KNOCKOUT, $event->getResultSnapshot()['status']); + $this->assertFileNotExists('/invalid/config.php'); + } +}