diff --git a/source/packages/com_mokosuitebackup/src/Extension/MokoSuiteBackupComponent.php b/source/packages/com_mokosuitebackup/src/Extension/MokoSuiteBackupComponent.php
index e6fad77..6332938 100644
--- a/source/packages/com_mokosuitebackup/src/Extension/MokoSuiteBackupComponent.php
+++ b/source/packages/com_mokosuitebackup/src/Extension/MokoSuiteBackupComponent.php
@@ -12,6 +12,7 @@ namespace Joomla\Component\MokoSuiteBackup\Administrator\Extension;
defined('_JEXEC') or die;
+use Joomla\CMS\Document\HtmlDocument;
use Joomla\CMS\Extension\MVCComponent;
use Joomla\CMS\Factory;
@@ -28,7 +29,13 @@ class MokoSuiteBackupComponent extends MVCComponent
return;
}
- $wa = $app->getDocument()->getWebAssetManager();
+ $doc = $app->getDocument();
+
+ if (!($doc instanceof HtmlDocument)) {
+ return;
+ }
+
+ $wa = $doc->getWebAssetManager();
$wa->addInlineStyle(
'.main-nav a[href*="com_mokosuitebackup"][href*="view=dashboard"] .sidebar-item-title::before,'
. ' .main-nav a[href*="com_mokosuitebackup"][href*="view=backups"] .sidebar-item-title::before,'
@@ -38,7 +45,7 @@ class MokoSuiteBackupComponent extends MVCComponent
. ' .main-nav a[href*="com_mokosuitebackup"][href*="view=backups"] .sidebar-item-title::before { content: "\f1c0"; }'
. ' .main-nav a[href*="com_mokosuitebackup"][href*="view=profiles"] .sidebar-item-title::before { content: "\f013"; }'
);
- } catch (\Throwable $e) {
+ } catch (\RuntimeException $e) {
error_log('MokoSuiteBackup: boot() CSS injection failed: ' . $e->getMessage());
}
}
diff --git a/source/script.php b/source/script.php
index 689e702..f472862 100644
--- a/source/script.php
+++ b/source/script.php
@@ -40,6 +40,15 @@ class Pkg_MokoSuiteBackupInstallerScript
*/
public function preflight(string $type, InstallerAdapter $parent): bool
{
+ if (version_compare(JVERSION, $this->minimumJoomla, '<')) {
+ Factory::getApplication()->enqueueMessage(
+ Text::sprintf('PKG_MOKOJOOMBACKUP_JOOMLA_VERSION_ERROR', $this->minimumJoomla),
+ 'error'
+ );
+
+ return false;
+ }
+
if (version_compare(PHP_VERSION, $this->minimumPhp, '<')) {
Factory::getApplication()->enqueueMessage(
Text::sprintf('PKG_MOKOJOOMBACKUP_PHP_VERSION_ERROR', $this->minimumPhp),
@@ -57,14 +66,6 @@ class Pkg_MokoSuiteBackupInstallerScript
return true;
}
- /**
- * Called after install/update.
- *
- * @param string $type Action type
- * @param InstallerAdapter $parent Installer adapter
- *
- * @return void
- */
/**
* Called before install/update to preserve the download key.
*
@@ -100,113 +101,43 @@ class Pkg_MokoSuiteBackupInstallerScript
if (!empty($key)) {
$this->savedDownloadKey = $key;
}
- } catch (\Throwable $e) {
+ } catch (\Exception $e) {
error_log('MokoSuiteBackup: Could not save download key: ' . $e->getMessage());
}
}
+ /**
+ * Called after install/update/uninstall.
+ *
+ * @param string $type Action type (install, update, uninstall)
+ * @param InstallerAdapter $parent Installer adapter
+ *
+ * @return void
+ */
public function postflight(string $type, InstallerAdapter $parent): void
{
+ if ($type === 'uninstall') {
+ return;
+ }
+
// Restore download key if it was saved before update
if ($this->savedDownloadKey !== null) {
$this->restoreDownloadKey();
}
if ($type === 'install') {
- // Enable the system plugin automatically on fresh install
- $db = Factory::getDbo();
- $query = $db->getQuery(true)
- ->update($db->quoteName('#__extensions'))
- ->set($db->quoteName('enabled') . ' = 1')
- ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
- ->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
- ->where($db->quoteName('element') . ' = ' . $db->quote('mokosuitebackup'));
-
- $db->setQuery($query);
- $db->execute();
-
- // Enable the quickicon plugin automatically
- $query = $db->getQuery(true)
- ->update($db->quoteName('#__extensions'))
- ->set($db->quoteName('enabled') . ' = 1')
- ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
- ->where($db->quoteName('folder') . ' = ' . $db->quote('quickicon'))
- ->where($db->quoteName('element') . ' = ' . $db->quote('mokosuitebackup'));
-
- $db->setQuery($query);
- $db->execute();
-
- // Enable the task plugin automatically
- $query = $db->getQuery(true)
- ->update($db->quoteName('#__extensions'))
- ->set($db->quoteName('enabled') . ' = 1')
- ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
- ->where($db->quoteName('folder') . ' = ' . $db->quote('task'))
- ->where($db->quoteName('element') . ' = ' . $db->quote('mokosuitebackup'));
-
- $db->setQuery($query);
- $db->execute();
-
- // Enable the webservices plugin automatically
- $query = $db->getQuery(true)
- ->update($db->quoteName('#__extensions'))
- ->set($db->quoteName('enabled') . ' = 1')
- ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
- ->where($db->quoteName('folder') . ' = ' . $db->quote('webservices'))
- ->where($db->quoteName('element') . ' = ' . $db->quote('mokosuitebackup'));
-
- $db->setQuery($query);
- $db->execute();
-
- // Enable the console plugin automatically
- $query = $db->getQuery(true)
- ->update($db->quoteName('#__extensions'))
- ->set($db->quoteName('enabled') . ' = 1')
- ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
- ->where($db->quoteName('folder') . ' = ' . $db->quote('console'))
- ->where($db->quoteName('element') . ' = ' . $db->quote('mokosuitebackup'));
-
- $db->setQuery($query);
- $db->execute();
-
- // Enable the content plugin automatically
- $query = $db->getQuery(true)
- ->update($db->quoteName('#__extensions'))
- ->set($db->quoteName('enabled') . ' = 1')
- ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
- ->where($db->quoteName('folder') . ' = ' . $db->quote('content'))
- ->where($db->quoteName('element') . ' = ' . $db->quote('mokosuitebackup'));
-
- $db->setQuery($query);
- $db->execute();
-
- // Enable the actionlog plugin automatically
- $query = $db->getQuery(true)
- ->update($db->quoteName('#__extensions'))
- ->set($db->quoteName('enabled') . ' = 1')
- ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
- ->where($db->quoteName('folder') . ' = ' . $db->quote('actionlog'))
- ->where($db->quoteName('element') . ' = ' . $db->quote('mokosuitebackup'));
-
- $db->setQuery($query);
- $db->execute();
+ // Enable all bundled plugins on fresh install
+ $this->enableBundledPlugins();
// Create default backup directory in site root
- $backupDir = JPATH_ROOT . '/backups';
+ $this->createBackupDirectory();
- if (!is_dir($backupDir)) {
- @mkdir($backupDir, 0755, true);
- }
-
- // Create default scheduled task — every 30 days, profile 1
+ // Create default scheduled task for backup automation
$this->createDefaultScheduledTask();
}
- if ($type === 'uninstall') {
- return;
- }
-
- // Ensure submenu items exist (Joomla only creates them on fresh install)
+ // Ensure submenu items exist and are up to date
+ // (Joomla may not add new submenu entries or update params on upgrades)
$this->ensureSubmenuItems();
// Sync submenu icons in #__menu (Joomla doesn't update icons on upgrades)
@@ -215,8 +146,8 @@ class Pkg_MokoSuiteBackupInstallerScript
// Warn if no license key configured
$this->warnMissingLicenseKey();
- // Warn if any profile still uses the default backup directory
- $this->warnDefaultBackupDir();
+ // Migrate profiles with old default backup_dir values to [DEFAULT_DIR] placeholder
+ $this->migrateDefaultBackupDir();
// Remind user to review backup profile settings
if ($type === 'install') {
@@ -232,11 +163,70 @@ class Pkg_MokoSuiteBackupInstallerScript
}
}
- private function warnDefaultBackupDir(): void
+ private function enableBundledPlugins(): void
+ {
+ $folders = ['system', 'quickicon', 'task', 'webservices', 'console', 'content', 'actionlog'];
+ $db = Factory::getDbo();
+
+ foreach ($folders as $folder) {
+ try {
+ $query = $db->getQuery(true)
+ ->update($db->quoteName('#__extensions'))
+ ->set($db->quoteName('enabled') . ' = 1')
+ ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
+ ->where($db->quoteName('folder') . ' = ' . $db->quote($folder))
+ ->where($db->quoteName('element') . ' = ' . $db->quote('mokosuitebackup'));
+ $db->setQuery($query);
+ $db->execute();
+ } catch (\Exception $e) {
+ error_log('MokoSuiteBackup: Failed to enable ' . $folder . ' plugin: ' . $e->getMessage());
+ Factory::getApplication()->enqueueMessage(
+ 'MokoSuiteBackup: Could not enable the ' . $folder . ' plugin. '
+ . 'Please enable it manually in Extensions → Plugins.',
+ 'warning'
+ );
+ }
+ }
+ }
+
+ private function createBackupDirectory(): void
+ {
+ $backupDir = JPATH_ROOT . '/backups';
+
+ if (is_dir($backupDir)) {
+ return;
+ }
+
+ if (!mkdir($backupDir, 0755, true)) {
+ error_log('MokoSuiteBackup: Failed to create default backup directory: ' . $backupDir);
+ Factory::getApplication()->enqueueMessage(
+ 'MokoSuiteBackup could not create the default backup directory at '
+ . htmlspecialchars($backupDir) . '. '
+ . 'Please create it manually and ensure the web server has write permissions.',
+ 'warning'
+ );
+
+ return;
+ }
+
+ // Protect directory from direct web access
+ $htaccess = $backupDir . '/.htaccess';
+
+ if (!file_exists($htaccess)) {
+ file_put_contents($htaccess, "Order Deny,Allow\nDeny from all\n");
+ }
+
+ $indexHtml = $backupDir . '/index.html';
+
+ if (!file_exists($indexHtml)) {
+ file_put_contents($indexHtml, '