/** * Install or upgrade the code for an extension -- and perform any * necessary database changes (eg replacing extension metadata). * * This only works if the extension is stored in the default container. * * @param string $tmpCodeDir * Path to a local directory containing a copy of the new (inert) code. * @throws CRM_Extension_Exception */ public function replace($tmpCodeDir) { if (!$this->defaultContainer) { throw new CRM_Extension_Exception("Default extension container is not configured"); } $newInfo = CRM_Extension_Info::loadFromFile($tmpCodeDir . DIRECTORY_SEPARATOR . CRM_Extension_Info::FILENAME); $oldStatus = $this->getStatus($newInfo->key); // find $tgtPath, $oldInfo, $typeManager switch ($oldStatus) { case self::STATUS_UNINSTALLED: case self::STATUS_INSTALLED: case self::STATUS_DISABLED: // There is an old copy of the extension. Try to install in the same place -- but it must go somewhere in the default-container list($oldInfo, $typeManager) = $this->_getInfoTypeHandler($newInfo->key); // throws Exception $tgtPath = $this->fullContainer->getPath($newInfo->key); if (!CRM_Utils_File::isChildPath($this->defaultContainer->getBaseDir(), $tgtPath)) { // force installation in the default-container $oldPath = $tgtPath; $tgtPath = $this->defaultContainer->getBaseDir() . DIRECTORY_SEPARATOR . $newInfo->key; CRM_Core_Session::setStatus(ts('A copy of the extension (%1) is in a system folder (%2). The system copy will be preserved, but the new copy will be used.', array(1 => $newInfo->key, 2 => $oldPath))); } break; case self::STATUS_INSTALLED_MISSING: case self::STATUS_DISABLED_MISSING: // the extension does not exist in any container; we're free to put it anywhere $tgtPath = $this->defaultContainer->getBaseDir() . DIRECTORY_SEPARATOR . $newInfo->key; list($oldInfo, $typeManager) = $this->_getMissingInfoTypeHandler($newInfo->key); // throws Exception break; case self::STATUS_UNKNOWN: // the extension does not exist in any container; we're free to put it anywhere $tgtPath = $this->defaultContainer->getBaseDir() . DIRECTORY_SEPARATOR . $newInfo->key; $oldInfo = $typeManager = NULL; break; default: throw new CRM_Extension_Exception("Cannot install or enable extension: {$newInfo->key}"); } // move the code! switch ($oldStatus) { case self::STATUS_UNINSTALLED: case self::STATUS_UNKNOWN: // There are no DB records to worry about, so we'll just put the files in place if (!CRM_Utils_File::replaceDir($tmpCodeDir, $tgtPath)) { throw new CRM_Extension_Exception("Failed to move {$tmpCodeDir} to {$tgtPath}"); } break; case self::STATUS_INSTALLED: case self::STATUS_INSTALLED_MISSING: case self::STATUS_DISABLED: case self::STATUS_DISABLED_MISSING: // There are DB records; coordinate the file placement with the DB updates $typeManager->onPreReplace($oldInfo, $newInfo); if (!CRM_Utils_File::replaceDir($tmpCodeDir, $tgtPath)) { throw new CRM_Extension_Exception("Failed to move {$tmpCodeDir} to {$tgtPath}"); } $this->_updateExtensionEntry($newInfo); $typeManager->onPostReplace($oldInfo, $newInfo); break; default: throw new CRM_Extension_Exception("Cannot install or enable extension: {$newInfo->key}"); } $this->refresh(); CRM_Core_Invoke::rebuildMenuAndCaches(TRUE); }