From ef8c8b20b5b209e5afe2c19ddaa4092e057321dd Mon Sep 17 00:00:00 2001 From: Matthias Schmidt Date: Mon, 7 Jun 2021 15:08:06 +0200 Subject: [PATCH] Add PIP to delete files (#4267) See #4180 --- XSD/fileDelete.xsd | 27 ++ com.woltlab.wcf/packageInstallationPlugin.xml | 1 + ...TXmlGuiPackageInstallationPlugin.class.php | 8 +- ...eDeletePackageInstallationPlugin.class.php | 274 ++++++++++++++++++ wcfsetup/install/lang/de.xml | 3 + wcfsetup/install/lang/en.xml | 3 + 6 files changed, 313 insertions(+), 3 deletions(-) create mode 100644 XSD/fileDelete.xsd create mode 100644 wcfsetup/install/files/lib/system/package/plugin/FileDeletePackageInstallationPlugin.class.php diff --git a/XSD/fileDelete.xsd b/XSD/fileDelete.xsd new file mode 100644 index 0000000000..ca5331d88f --- /dev/null +++ b/XSD/fileDelete.xsd @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/com.woltlab.wcf/packageInstallationPlugin.xml b/com.woltlab.wcf/packageInstallationPlugin.xml index 943f68c06a..c64fb38ee6 100644 --- a/com.woltlab.wcf/packageInstallationPlugin.xml +++ b/com.woltlab.wcf/packageInstallationPlugin.xml @@ -32,5 +32,6 @@ wcf\system\package\plugin\MenuItemPackageInstallationPlugin wcf\system\package\plugin\MediaProviderPackageInstallationPlugin wcf\system\package\plugin\DatabasePackageInstallationPlugin + wcf\system\package\plugin\FileDeletePackageInstallationPlugin diff --git a/wcfsetup/install/files/lib/system/devtools/pip/TXmlGuiPackageInstallationPlugin.class.php b/wcfsetup/install/files/lib/system/devtools/pip/TXmlGuiPackageInstallationPlugin.class.php index f4b30c7dfe..9f03cd06e5 100644 --- a/wcfsetup/install/files/lib/system/devtools/pip/TXmlGuiPackageInstallationPlugin.class.php +++ b/wcfsetup/install/files/lib/system/devtools/pip/TXmlGuiPackageInstallationPlugin.class.php @@ -599,10 +599,12 @@ XML; if ($oldElement !== null) { $sqlData = $this->findExistingItem($this->getElementData($oldElement, true)); - $statement = WCF::getDB()->prepareStatement($sqlData['sql']); - $statement->execute($sqlData['parameters']); + if ($sqlData !== null) { + $statement = WCF::getDB()->prepareStatement($sqlData['sql']); + $statement->execute($sqlData['parameters']); - $existingRow = $statement->fetchArray(); + $existingRow = $statement->fetchArray(); + } } $this->import($existingRow, $newElementData); diff --git a/wcfsetup/install/files/lib/system/package/plugin/FileDeletePackageInstallationPlugin.class.php b/wcfsetup/install/files/lib/system/package/plugin/FileDeletePackageInstallationPlugin.class.php new file mode 100644 index 0000000000..89138fb1df --- /dev/null +++ b/wcfsetup/install/files/lib/system/package/plugin/FileDeletePackageInstallationPlugin.class.php @@ -0,0 +1,274 @@ + + * @package WoltLabSuite\Core\System\Package\Plugin + * @since 5.5 + */ +class FileDeletePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IGuiPackageInstallationPlugin +{ + use TXmlGuiPackageInstallationPlugin; + + /** + * @inheritDoc + */ + public $tagName = 'file'; + + /** + * @inheritDoc + */ + protected function handleDelete(array $items) + { + $sql = "SELECT packageID + FROM wcf1_package_installation_file_log + WHERE filename = ? + AND application = ? + AND packageID = ?"; + $searchStatement = WCF::getDB()->prepare($sql); + + $sql = "DELETE FROM wcf1_package_installation_file_log + WHERE packageID = ? + AND filename = ?"; + $deleteStatement = WCF::getDB()->prepare($sql); + + foreach ($items as $item) { + $file = $item['value']; + $application = 'wcf'; + if (!empty($item['attributes']['application'])) { + $application = $item['attributes']['application']; + } elseif ($this->installation->getPackage()->isApplication) { + $application = Package::getAbbreviation($this->installation->getPackage()->package); + } + + $searchStatement->execute([ + $file, + $application, + $this->installation->getPackageID(), + ]); + + $filePackageID = $searchStatement->fetchSingleColumn(); + if ($filePackageID !== false && $filePackageID != $this->installation->getPackageID()) { + throw new \UnexpectedValueException( + "File '{$file}' does not belong to package '{$this->installation->getPackage()->package}' + but to package '" . PackageCache::getInstance()->getPackage($filePackageID)->package . "'." + ); + } + + $filePath = Application::getDirectory($application) . $file; + if (\file_exists($filePath)) { + \unlink($filePath); + } + + $deleteStatement->execute([ + $this->installation->getPackageID(), + $file, + ]); + } + } + + /** + * @inheritDoc + */ + protected function import(array $row, array $data) + { + throw new \LogicException("The `fileDelete` package installation plugin does not support imports."); + } + + /** + * @inheritDoc + */ + protected function prepareImport(array $data) + { + return $data; + } + + /** + * @inheritDoc + */ + protected function findExistingItem(array $data) + { + return null; + } + + /** + * @inheritDoc + */ + public static function getSyncDependencies() + { + return []; + } + + /** + * @inheritDoc + */ + protected function addFormFields(IFormDocument $form) + { + /** @var FormContainer $dataContainer */ + $dataContainer = $form->getNodeById('data'); + + $dataContainer->appendChildren([ + TextFormField::create('file') + ->label('wcf.acp.pip.fileDelete.file') + ->required(), + SingleSelectionFormField::create('application') + ->label('wcf.acp.pip.fileDelete.application') + ->options(static function (): array { + $options = [ + '' => 'wcf.global.noSelection', + ]; + + $apps = ApplicationHandler::getInstance()->getApplications(); + \usort($apps, static function (Application $a, Application $b) { + return $a->getPackage()->getTitle() <=> $b->getPackage()->getTitle(); + }); + + foreach ($apps as $application) { + $options[$application->getAbbreviation()] = $application->getPackage()->getTitle(); + } + + return $options; + }) + ->nullable(), + ]); + } + + /** + * @inheritDoc + */ + protected function fetchElementData(\DOMElement $element, $saveData) + { + return [ + 'application' => $element->getAttribute('application') ?? 'wcf', + 'file' => $element->nodeValue, + 'packageID' => $this->installation->getPackage()->packageID, + ]; + } + + /** + * @inheritDoc + */ + public function getElementIdentifier(\DOMElement $element) + { + $app = $element->getAttribute('application') ?? 'wcf'; + + return \sha1($app . '_' . $element->nodeValue); + } + + /** + * @inheritDoc + */ + protected function setEntryListKeys(IDevtoolsPipEntryList $entryList) + { + $entryList->setKeys([ + 'file' => 'wcf.acp.pip.fileDelete.file', + 'application' => 'wcf.acp.pip.fileDelete.application', + ]); + } + + /** + * @inheritDoc + */ + protected function insertNewXmlElement(XML $xml, \DOMElement $newElement) + { + $delete = $xml->xpath()->query('/ns:data/ns:delete')->item(0); + if ($delete === null) { + $data = $xml->xpath()->query('/ns:data')->item(0); + $delete = $xml->getDocument()->createElement('delete'); + DOMUtil::prepend($delete, $data); + } + + $delete->appendChild($newElement); + } + + /** + * @inheritDoc + */ + protected function prepareXmlElement(\DOMDocument $document, IFormDocument $form) + { + $file = $document->createElement($this->tagName); + + $data = $form->getData()['data']; + if (!empty($data['application'])) { + $file->setAttribute('application', $data['application']); + } + $file->nodeValue = $data['file']; + + return $file; + } + + /** + * @inheritDoc + */ + protected function prepareDeleteXmlElement(\DOMElement $element) + { + return null; + } + + /** + * @inheritDoc + */ + protected function saveObject(\DOMElement $newElement, ?\DOMElement $oldElement = null) + { + $newElementData = $this->getElementData($newElement, true); + + $this->handleDelete([[ + 'attributes' => [ + 'application' => $newElementData['application'], + ], + 'value' => $newElementData['file'], + ]]); + } + + /** + * @inheritDoc + */ + protected function deleteObject(\DOMElement $element) + { + // Reverting file deletions is not supported. Use the `file` PIP instead. + } + + /** + * @inheritDoc + */ + protected function getImportElements(\DOMXPath $xpath) + { + return $xpath->query('/ns:data/ns:delete/ns:' . $this->tagName); + } + + /** + * @inheritDoc + */ + protected function getEmptyXml() + { + $xsdFilename = $this->getXsdFilename(); + $apiVersion = WSC_API_VERSION; + + return << + + + +XML; + } +} diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml index 2f96b207b6..c2298b269b 100644 --- a/wcfsetup/install/lang/de.xml +++ b/wcfsetup/install/lang/de.xml @@ -2643,6 +2643,9 @@ Kein Abschnitt darf leer sein und alle Abschnitten dürfen nur folgende Zeichen “”]]> + fileDelete-Package Installation Plugin löscht Dateien, die mit dem file-Package Installation Plugin installiert wurden. {if LANGUAGE_USE_INFORMAL_VARIANT}Du kannst{else}Sie können{/if} mehr Informationen in der Entwickler-Dokumentation finden.]]> + + diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index 57a335c617..027b0b02aa 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -2573,6 +2573,9 @@ If you have already bought the licenses for the listed apps, th “”]]> + fileDelete package installation plugin installs deletes files installed with the file package installation plugin. You can find more information in the developer documentation.]]> + + -- 2.20.1