3 namespace wcf\system\package\plugin;
5 use wcf\data\application\Application;
6 use wcf\data\package\Package;
7 use wcf\data\package\PackageCache;
8 use wcf\system\application\ApplicationHandler;
9 use wcf\system\devtools\pip\IDevtoolsPipEntryList;
10 use wcf\system\devtools\pip\IGuiPackageInstallationPlugin;
11 use wcf\system\devtools\pip\TXmlGuiPackageInstallationPlugin;
12 use wcf\system\form\builder\container\FormContainer;
13 use wcf\system\form\builder\field\SingleSelectionFormField;
14 use wcf\system\form\builder\field\TextFormField;
15 use wcf\system\form\builder\IFormDocument;
21 * Abstract implementation of a package installation plugin deleting a certain type of files.
23 * @author Matthias Schmidt
24 * @copyright 2001-2021 WoltLab GmbH
25 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
26 * @package WoltLabSuite\Core\System\Package\Plugin
29 abstract class AbstractFileDeletePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements
30 IGuiPackageInstallationPlugin
32 use TXmlGuiPackageInstallationPlugin;
35 * Returns the name of the database table that logs the installed files.
37 abstract protected function getLogTableName(): string;
40 * Returns the name of the column in the log table returned by `getLogTableName()` that contains
41 * the names of the relevant files.
43 abstract protected function getFilenameTableColumn(): string;
45 protected function getPipName(): string
47 return $this->getXsdFilename();
51 * Returns the actual absolute path of the given file.
53 protected function getFilePath(string $filename, string $application): string
55 return Application::getDirectory($application) . $filename;
61 protected function handleDelete(array $items)
63 $sql = "SELECT packageID
64 FROM {$this->getLogTableName()}
65 WHERE {$this->getFilenameTableColumn()} = ?
68 $searchStatement = WCF::getDB()->prepare($sql);
70 $sql = "DELETE FROM {$this->getLogTableName()}
72 AND {$this->getFilenameTableColumn()} = ?";
73 $deleteStatement = WCF::getDB()->prepare($sql);
75 foreach ($items as $item) {
76 $file = $item['value'];
78 if (!empty($item['attributes']['application'])) {
79 $application = $item['attributes']['application'];
80 } elseif ($this->installation->getPackage()->isApplication) {
81 $application = Package::getAbbreviation($this->installation->getPackage()->package);
84 $searchStatement->execute([
87 $this->installation->getPackageID(),
90 $filePackageID = $searchStatement->fetchSingleColumn();
91 if ($filePackageID !== false && $filePackageID != $this->installation->getPackageID()) {
92 throw new \UnexpectedValueException(
93 "'{$file}' does not belong to package '{$this->installation->getPackage()->package}'
94 but to package '" . PackageCache::getInstance()->getPackage($filePackageID)->package . "'."
98 $filePath = $this->getFilePath($file, $application);
99 if (\file_exists($filePath)) {
103 $deleteStatement->execute([
104 $this->installation->getPackageID(),
113 final protected function import(array $row, array $data)
115 // Does nothing, imports are not supported.
121 final protected function prepareImport(array $data)
129 final protected function findExistingItem(array $data)
137 public static function getSyncDependencies()
143 * Returns the language item with the description of the file field or `null` if no description
146 protected function getFileFieldDescription(): ?string
148 $languageItem = "wcf.acp.pip.{$this->getPipName()}.{$this->tagName}.description";
150 return WCF::getLanguage()->get($languageItem, true) ?: null;
156 protected function addFormFields(IFormDocument $form)
158 /** @var FormContainer $dataContainer */
159 $dataContainer = $form->getNodeById('data');
161 $dataContainer->appendChildren([
162 TextFormField::create($this->tagName)
163 ->label("wcf.acp.pip.{$this->getPipName()}.{$this->tagName}")
164 ->description($this->getFileFieldDescription())
166 SingleSelectionFormField::create('application')
167 ->label("wcf.acp.pip.{$this->getPipName()}.application")
168 ->options(static function (): array {
170 '' => 'wcf.global.noSelection',
173 $apps = ApplicationHandler::getInstance()->getApplications();
174 \usort($apps, static function (Application $a, Application $b) {
175 return $a->getPackage()->getTitle() <=> $b->getPackage()->getTitle();
178 foreach ($apps as $application) {
179 $options[$application->getAbbreviation()] = $application->getPackage()->getTitle();
191 protected function fetchElementData(\DOMElement $element, $saveData)
194 'application' => $element->getAttribute('application') ?? 'wcf',
195 $this->tagName => $element->nodeValue,
196 'packageID' => $this->installation->getPackage()->packageID,
203 public function getElementIdentifier(\DOMElement $element)
205 $app = $element->getAttribute('application') ?? 'wcf';
207 return \sha1($app . '_' . $element->nodeValue);
213 protected function setEntryListKeys(IDevtoolsPipEntryList $entryList)
215 $entryList->setKeys([
216 $this->tagName => "wcf.acp.pip.{$this->getPipName()}.{$this->tagName}",
217 'application' => "wcf.acp.pip.{$this->getPipName()}.application",
224 protected function insertNewXmlElement(XML $xml, \DOMElement $newElement)
226 $delete = $xml->xpath()->query('/ns:data/ns:delete')->item(0);
227 if ($delete === null) {
228 $data = $xml->xpath()->query('/ns:data')->item(0);
229 $delete = $xml->getDocument()->createElement('delete');
230 DOMUtil::prepend($delete, $data);
233 $delete->appendChild($newElement);
239 protected function prepareXmlElement(\DOMDocument $document, IFormDocument $form)
241 $file = $document->createElement($this->tagName);
243 $data = $form->getData()['data'];
244 if (!empty($data['application'])) {
245 $file->setAttribute('application', $data['application']);
247 $file->nodeValue = $data[$this->tagName];
255 final protected function prepareDeleteXmlElement(\DOMElement $element)
263 protected function saveObject(\DOMElement $newElement, ?\DOMElement $oldElement = null)
265 $newElementData = $this->getElementData($newElement, true);
267 $this->handleDelete([[
269 'application' => $newElementData['application'],
271 'value' => $newElementData[$this->tagName],
278 final protected function deleteObject(\DOMElement $element)
280 // Reverting file deletions is not supported. Use the `file` PIP instead.
286 protected function getImportElements(\DOMXPath $xpath)
288 return $xpath->query('/ns:data/ns:delete/ns:' . $this->tagName);
294 protected function getEmptyXml()
296 $xsdFilename = $this->getXsdFilename();
297 $apiVersion = WSC_API_VERSION;
300 <?xml version="1.0" encoding="UTF-8"?>
301 <data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/{$apiVersion}/{$xsdFilename}.xsd">