Skip to content

Commit

Permalink
Remove duplicated extension_id records on install / discover_install
Browse files Browse the repository at this point in the history
If there's a timeout or PHP fatal error during a component's installation there are invalid records left behind in #__extensions and #__menus which prevent further installations. We now detect them and remove them to let the installation proceed.
  • Loading branch information
Nicholas K. Dionysopoulos committed Dec 23, 2014
1 parent 2841b1b commit b5f17a1
Showing 1 changed file with 86 additions and 4 deletions.
90 changes: 86 additions & 4 deletions libraries/cms/installer/adapter/component.php
Original file line number Diff line number Diff line change
Expand Up @@ -376,18 +376,51 @@ protected function parseOptionalTags()
/**
* Method to store the extension to the database
*
* @param bool $deleteExisting Should I try to delete existing records of the same component?
*
* @return void
*
* @since 3.1
* @throws RuntimeException
*/
protected function storeExtension()
protected function storeExtension($deleteExisting = false)
{
// Add an entry to the extension table with a whole heap of defaults
$this->extension->name = $this->name;
$this->extension->type = 'component';
$this->extension->element = $this->element;

// If we are told to delete existing extension entries then do so.
if ($deleteExisting)
{
$db = $this->parent->getDBO();

$query = $db->getQuery(true)
->select($db->qn('extension_id'))
->from($db->qn('#__extensions'))
->where($db->qn('name') . ' = ' . $db->q($this->extension->name))
->where($db->qn('type') . ' = ' . $db->q($this->extension->type))
->where($db->qn('element') . ' = ' . $db->q($this->extension->element));

$db->setQuery($query);

$extension_ids = $db->loadColumn();

if (!empty($extension_ids))
{
foreach ($extension_ids as $eid)
{
// Remove leftover admin menus for this extension ID
$this->_removeAdminMenus($eid);

// Remove the extension record itself
/** @var JTableExtension $extensionTable */
$extensionTable = JTable::getInstance('extension');
$extensionTable->delete($eid);
}
}
}

// There is no folder for components
$this->extension->folder = '';
$this->extension->enabled = 1;
Expand All @@ -397,7 +430,9 @@ protected function storeExtension()
$this->extension->params = $this->parent->getParams();
$this->extension->manifest_cache = $this->parent->generateManifestCache();

if (!$this->extension->store())
$couldStore = $this->extension->store();

if (!$couldStore && $deleteExisting)
{
// Install failed, roll back changes
throw new RuntimeException(
Expand All @@ -407,6 +442,12 @@ protected function storeExtension()
)
);
}

if (!$couldStore && !$deleteExisting)
{
// Maybe we have a failed installation (e.g. timeout). Let's retry after deleting old records.
$this->storeExtension(true);
}
}

/**
Expand Down Expand Up @@ -1527,15 +1568,56 @@ public function discover_install()
$this->parent->extension->enabled = 1;
$this->parent->extension->params = $this->parent->getParams();

$stored = false;

try
{
$this->parent->extension->store();
$stored = true;
}
catch (RuntimeException $e)
{
JLog::add(JText::_('JLIB_INSTALLER_ERROR_COMP_DISCOVER_STORE_DETAILS'), JLog::WARNING, 'jerror');
// Try to delete existing failed records before retrying
$db = $this->parent->getDBO();

return false;
$query = $db->getQuery(true)
->select($db->qn('extension_id'))
->from($db->qn('#__extensions'))
->where($db->qn('name') . ' = ' . $db->q($this->parent->extension->name))
->where($db->qn('type') . ' = ' . $db->q($this->parent->extension->type))
->where($db->qn('element') . ' = ' . $db->q($this->parent->extension->element));

$db->setQuery($query);

$extension_ids = $db->loadColumn();

if (!empty($extension_ids))
{
foreach ($extension_ids as $eid)
{
// Remove leftover admin menus for this extension ID
$this->_removeAdminMenus($eid);

// Remove the extension record itself
/** @var JTableExtension $extensionTable */
$extensionTable = JTable::getInstance('extension');
$extensionTable->delete($eid);
}
}
}

if (!$stored)
{
try
{
$this->parent->extension->store();
}
catch (RuntimeException $e)
{
JLog::add(JText::_('JLIB_INSTALLER_ERROR_COMP_DISCOVER_STORE_DETAILS'), JLog::WARNING, 'jerror');

return false;
}
}

// Now we need to run any SQL it has, languages, media or menu stuff
Expand Down

0 comments on commit b5f17a1

Please sign in to comment.