diff --git a/administrator/components/com_installer/models/manage.php b/administrator/components/com_installer/models/manage.php
index 8df46b2446af4..086d56213d512 100644
--- a/administrator/components/com_installer/models/manage.php
+++ b/administrator/components/com_installer/models/manage.php
@@ -231,7 +231,7 @@ public function remove($eid = array())
$rowtype = $row->type;
}
- if ($row->type && $row->type != 'language')
+ if ($row->type)
{
$result = $installer->uninstall($row->type, $id);
@@ -251,14 +251,6 @@ public function remove($eid = array())
continue;
}
- if ($row->type == 'language')
- {
- // One should always uninstall a language package, not a single language
- $msgs[] = JText::_('COM_INSTALLER_UNINSTALL_LANGUAGE');
-
- continue;
- }
-
// There was an error in uninstalling the package
$msgs[] = JText::sprintf('COM_INSTALLER_UNINSTALL_ERROR', $rowtype);
}
diff --git a/administrator/language/en-GB/en-GB.com_installer.ini b/administrator/language/en-GB/en-GB.com_installer.ini
index 28f3a80a31345..a0b0e9a4c9cf8 100644
--- a/administrator/language/en-GB/en-GB.com_installer.ini
+++ b/administrator/language/en-GB/en-GB.com_installer.ini
@@ -243,6 +243,7 @@ COM_INSTALLER_TYPE_TYPE_TEMPLATE="template"
COM_INSTALLER_UNABLE_TO_FIND_INSTALL_PACKAGE="Unable to find install package"
COM_INSTALLER_UNABLE_TO_INSTALL_JOOMLA_PACKAGE="The Joomla package cannot be installed through the Extension Manager. Please use the Joomla! Update component to update Joomla."
COM_INSTALLER_UNINSTALL_ERROR="Error uninstalling %s."
+; This string is deprecated and will be removed with 4.0.
COM_INSTALLER_UNINSTALL_LANGUAGE="A language should always have been installed as a package.
To uninstall a language, filter type by package and uninstall the package."
COM_INSTALLER_UNINSTALL_SUCCESS="Uninstalling the %s was successful."
COM_INSTALLER_UPDATE_FILTER_SEARCH_DESC="Search in extension name. Prefix with ID:, UID: or EID: to search for a update ID, update site ID or extension ID."
diff --git a/administrator/language/en-GB/en-GB.lib_joomla.ini b/administrator/language/en-GB/en-GB.lib_joomla.ini
index 10d44b61317fb..168df39066093 100644
--- a/administrator/language/en-GB/en-GB.lib_joomla.ini
+++ b/administrator/language/en-GB/en-GB.lib_joomla.ini
@@ -588,6 +588,7 @@ JLIB_INSTALLER_PURGED_UPDATES="Cleared updates"
JLIB_INSTALLER_FAILED_TO_PURGE_UPDATES="Failed to clear updates."
JLIB_INSTALLER_DEFAULT_STYLE="%s - Default"
JLIB_INSTALLER_DISCOVER="Discover"
+JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE="The %s extension is part of a package which does not allow individual extensions to be uninstalled."
JLIB_INSTALLER_ERROR_COMP_DISCOVER_STORE_DETAILS="Component Discover install: Failed to store component details."
JLIB_INSTALLER_ERROR_COMP_FAILED_TO_CREATE_DIRECTORY="Component %1$s: Failed to create folder: %2$s."
JLIB_INSTALLER_ERROR_COMP_INSTALL_ADMIN_ELEMENT="Component Install: The XML file did not contain an administration element."
diff --git a/administrator/manifests/packages/pkg_en-GB.xml b/administrator/manifests/packages/pkg_en-GB.xml
index f54deffd38c69..d1526b6900d77 100644
--- a/administrator/manifests/packages/pkg_en-GB.xml
+++ b/administrator/manifests/packages/pkg_en-GB.xml
@@ -13,6 +13,7 @@
Joomla! Project
www.joomla.org
+ true
site_en-GB
admin_en-GB
diff --git a/language/en-GB/en-GB.lib_joomla.ini b/language/en-GB/en-GB.lib_joomla.ini
index 4983d6423ca8d..f0cb47306c17c 100644
--- a/language/en-GB/en-GB.lib_joomla.ini
+++ b/language/en-GB/en-GB.lib_joomla.ini
@@ -588,6 +588,7 @@ JLIB_INSTALLER_PURGED_UPDATES="Cleared updates"
JLIB_INSTALLER_FAILED_TO_PURGE_UPDATES="Failed to clear updates."
JLIB_INSTALLER_DEFAULT_STYLE="%s - Default"
JLIB_INSTALLER_DISCOVER="Discover"
+JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE="The %s extension is part of a package which does not allow individual extensions to be uninstalled."
JLIB_INSTALLER_ERROR_COMP_DISCOVER_STORE_DETAILS="Component Discover install: Failed to store component details."
JLIB_INSTALLER_ERROR_COMP_FAILED_TO_CREATE_DIRECTORY="Component %1$s: Failed to create folder: %2$s."
JLIB_INSTALLER_ERROR_COMP_INSTALL_ADMIN_ELEMENT="Component Install: The XML file did not contain an administration element."
diff --git a/libraries/cms/installer/adapter.php b/libraries/cms/installer/adapter.php
index e6f423126a99f..1ca29ebe6f30b 100644
--- a/libraries/cms/installer/adapter.php
+++ b/libraries/cms/installer/adapter.php
@@ -132,6 +132,41 @@ public function __construct(JInstaller $parent, JDatabaseDriver $db, array $opti
}
}
+ /**
+ * Check if a package extension allows its child extensions to be uninstalled individually
+ *
+ * @param integer $packageId The extension ID of the package to check
+ *
+ * @return boolean
+ *
+ * @since __DEPLOY_VERSION__
+ * @note This method defaults to true to emulate the behavior of 3.6 and earlier which did not support this lookup
+ */
+ protected function canUninstallPackageChild($packageId)
+ {
+ $package = JTable::getInstance('extension');
+
+ // If we can't load this package ID, we have a corrupt database
+ if (!$package->load((int) $packageId))
+ {
+ return true;
+ }
+
+ $manifestFile = JPATH_MANIFESTS . '/packages/' . $package->element . '.xml';
+
+ $xml = $this->parent->isManifest($manifestFile);
+
+ // If the manifest doesn't exist, we've got some major issues
+ if (!$xml)
+ {
+ return true;
+ }
+
+ $manifest = new JInstallerManifestPackage($manifestFile);
+
+ return $manifest->blockChildUninstall === false;
+ }
+
/**
* Method to check if the extension is already present in the database
*
diff --git a/libraries/cms/installer/adapter/component.php b/libraries/cms/installer/adapter/component.php
index 83e42a6d135bb..267f4bf48e567 100644
--- a/libraries/cms/installer/adapter/component.php
+++ b/libraries/cms/installer/adapter/component.php
@@ -688,6 +688,17 @@ public function uninstall($id)
return false;
}
+ /*
+ * Does this extension have a parent package?
+ * If so, check if the package disallows individual extensions being uninstalled if the package is not being uninstalled
+ */
+ if ($this->extension->package_id && !$this->parent->isPackageUninstall() && !$this->canUninstallPackageChild($this->extension->package_id))
+ {
+ JLog::add(JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE', $this->extension->name), JLog::WARNING, 'jerror');
+
+ return false;
+ }
+
// Get the admin and site paths for the component
$this->parent->setPath('extension_administrator', JPath::clean(JPATH_ADMINISTRATOR . '/components/' . $this->extension->element));
$this->parent->setPath('extension_site', JPath::clean(JPATH_SITE . '/components/' . $this->extension->element));
diff --git a/libraries/cms/installer/adapter/file.php b/libraries/cms/installer/adapter/file.php
index 545e122fe2bdd..9765e3ef0e410 100644
--- a/libraries/cms/installer/adapter/file.php
+++ b/libraries/cms/installer/adapter/file.php
@@ -304,6 +304,17 @@ public function uninstall($id)
return false;
}
+ /*
+ * Does this extension have a parent package?
+ * If so, check if the package disallows individual extensions being uninstalled if the package is not being uninstalled
+ */
+ if ($row->package_id && !$this->parent->isPackageUninstall() && !$this->canUninstallPackageChild($row->package_id))
+ {
+ JLog::add(JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE', $row->name), JLog::WARNING, 'jerror');
+
+ return false;
+ }
+
$retval = true;
$manifestFile = JPATH_MANIFESTS . '/files/' . $row->element . '.xml';
diff --git a/libraries/cms/installer/adapter/language.php b/libraries/cms/installer/adapter/language.php
index 64585ab18ea92..19b4db3643e85 100644
--- a/libraries/cms/installer/adapter/language.php
+++ b/libraries/cms/installer/adapter/language.php
@@ -623,6 +623,17 @@ public function uninstall($eid)
return false;
}
+ /*
+ * Does this extension have a parent package?
+ * If so, check if the package disallows individual extensions being uninstalled if the package is not being uninstalled
+ */
+ if ($extension->package_id && !$this->parent->isPackageUninstall() && !$this->canUninstallPackageChild($extension->package_id))
+ {
+ JLog::add(JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE', $extension->name), JLog::WARNING, 'jerror');
+
+ return false;
+ }
+
// Construct the path from the client, the language and the extension element name
$path = $client->path . '/language/' . $element;
diff --git a/libraries/cms/installer/adapter/library.php b/libraries/cms/installer/adapter/library.php
index 1f2037a563a8a..b6e4abcb98e48 100644
--- a/libraries/cms/installer/adapter/library.php
+++ b/libraries/cms/installer/adapter/library.php
@@ -378,6 +378,17 @@ public function uninstall($id)
return false;
}
+ /*
+ * Does this extension have a parent package?
+ * If so, check if the package disallows individual extensions being uninstalled if the package is not being uninstalled
+ */
+ if ($row->package_id && !$this->parent->isPackageUninstall() && !$this->canUninstallPackageChild($row->package_id))
+ {
+ JLog::add(JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE', $row->name), JLog::WARNING, 'jerror');
+
+ return false;
+ }
+
$manifestFile = JPATH_MANIFESTS . '/libraries/' . $row->element . '.xml';
// Because libraries may not have their own folders we cannot use the standard method of finding an installation manifest
diff --git a/libraries/cms/installer/adapter/module.php b/libraries/cms/installer/adapter/module.php
index 3b428fdfe7d55..b2454dcb47747 100644
--- a/libraries/cms/installer/adapter/module.php
+++ b/libraries/cms/installer/adapter/module.php
@@ -533,6 +533,17 @@ public function uninstall($id)
return false;
}
+ /*
+ * Does this extension have a parent package?
+ * If so, check if the package disallows individual extensions being uninstalled if the package is not being uninstalled
+ */
+ if ($this->extension->package_id && !$this->parent->isPackageUninstall() && !$this->canUninstallPackageChild($this->extension->package_id))
+ {
+ JLog::add(JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE', $this->extension->name), JLog::WARNING, 'jerror');
+
+ return false;
+ }
+
// Get the extension root path
$element = $this->extension->element;
$client = JApplicationHelper::getClientInfo($this->extension->client_id);
diff --git a/libraries/cms/installer/adapter/package.php b/libraries/cms/installer/adapter/package.php
index 85948c7083baf..69230e801124c 100644
--- a/libraries/cms/installer/adapter/package.php
+++ b/libraries/cms/installer/adapter/package.php
@@ -543,6 +543,17 @@ public function uninstall($id)
return false;
}
+ /*
+ * Does this extension have a parent package?
+ * If so, check if the package disallows individual extensions being uninstalled if the package is not being uninstalled
+ */
+ if ($row->package_id && !$this->parent->isPackageUninstall() && !$this->canUninstallPackageChild($row->package_id))
+ {
+ JLog::add(JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE', $row->name), JLog::WARNING, 'jerror');
+
+ return false;
+ }
+
$manifestFile = JPATH_MANIFESTS . '/packages/' . $row->get('element') . '.xml';
$manifest = new JInstallerManifestPackage($manifestFile);
@@ -620,6 +631,8 @@ public function uninstall($id)
foreach ($manifest->filelist as $extension)
{
$tmpInstaller = new JInstaller;
+ $tmpInstaller->setPackageUninstall(true);
+
$id = $this->_getExtensionId($extension->type, $extension->id, $extension->client, $extension->group);
$client = JApplicationHelper::getClientInfo($extension->client, true);
diff --git a/libraries/cms/installer/adapter/plugin.php b/libraries/cms/installer/adapter/plugin.php
index 1bca17deef825..cc5a8dea79d47 100644
--- a/libraries/cms/installer/adapter/plugin.php
+++ b/libraries/cms/installer/adapter/plugin.php
@@ -482,6 +482,17 @@ public function uninstall($id)
return false;
}
+ /*
+ * Does this extension have a parent package?
+ * If so, check if the package disallows individual extensions being uninstalled if the package is not being uninstalled
+ */
+ if ($row->package_id && !$this->parent->isPackageUninstall() && !$this->canUninstallPackageChild($row->package_id))
+ {
+ JLog::add(JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE', $row->name), JLog::WARNING, 'jerror');
+
+ return false;
+ }
+
// Get the plugin folder so we can properly build the plugin path
if (trim($row->folder) == '')
{
diff --git a/libraries/cms/installer/adapter/template.php b/libraries/cms/installer/adapter/template.php
index b24ae7431e092..6cacff3b36d1e 100644
--- a/libraries/cms/installer/adapter/template.php
+++ b/libraries/cms/installer/adapter/template.php
@@ -425,6 +425,17 @@ public function uninstall($id)
return false;
}
+ /*
+ * Does this extension have a parent package?
+ * If so, check if the package disallows individual extensions being uninstalled if the package is not being uninstalled
+ */
+ if ($row->package_id && !$this->parent->isPackageUninstall() && !$this->canUninstallPackageChild($row->package_id))
+ {
+ JLog::add(JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE', $row->name), JLog::WARNING, 'jerror');
+
+ return false;
+ }
+
$name = $row->element;
$clientId = $row->client_id;
diff --git a/libraries/cms/installer/installer.php b/libraries/cms/installer/installer.php
index 3093f25bcc238..e1e4f341cb9d1 100644
--- a/libraries/cms/installer/installer.php
+++ b/libraries/cms/installer/installer.php
@@ -102,6 +102,14 @@ class JInstaller extends JAdapter
*/
protected $redirect_url = null;
+ /**
+ * Flag if the uninstall process was triggered by uninstalling a package
+ *
+ * @var boolean
+ * @since __DEPLOY_VERSION__
+ */
+ protected $packageUninstall = false;
+
/**
* JInstaller instance container.
*
@@ -225,6 +233,32 @@ public function setRedirectUrl($newurl)
$this->redirect_url = $newurl;
}
+ /**
+ * Get whether this installer is uninstalling extensions which are part of a package
+ *
+ * @return boolean
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function isPackageUninstall()
+ {
+ return $this->packageUninstall;
+ }
+
+ /**
+ * Set whether this installer is uninstalling extensions which are part of a package
+ *
+ * @param boolean $uninstall True if a package triggered the uninstall, false otherwise
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function setPackageUninstall($uninstall)
+ {
+ $this->packageUninstall = $uninstall;
+ }
+
/**
* Get the upgrade switch
*
diff --git a/libraries/cms/installer/manifest/package.php b/libraries/cms/installer/manifest/package.php
index cee1a5eff82ae..c15e310b8e990 100644
--- a/libraries/cms/installer/manifest/package.php
+++ b/libraries/cms/installer/manifest/package.php
@@ -40,6 +40,14 @@ class JInstallerManifestPackage extends JInstallerManifest
*/
public $scriptfile = '';
+ /**
+ * Flag if the package blocks individual child extensions from being uninstalled
+ *
+ * @var boolean
+ * @since __DEPLOY_VERSION__
+ */
+ public $blockChildUninstall = false;
+
/**
* Apply manifest data from a SimpleXMLElement to the object.
*
@@ -63,6 +71,16 @@ protected function loadManifestFromData(SimpleXMLElement $xml)
$this->scriptfile = (string) $xml->scriptfile;
$this->version = (string) $xml->version;
+ if (isset($xml->blockChildUninstall))
+ {
+ $value = (string) $xml->blockChildUninstall;
+
+ if ($value === '1' || $value === 'true')
+ {
+ $this->blockChildUninstall = true;
+ }
+ }
+
if (isset($xml->files->file) && count($xml->files->file))
{
foreach ($xml->files->file as $file)