If the package installation plugin supports delete instructions, the import instruction can also be converted into a delete instruction.
See #2545
{/hascontent}
{if !$entryList->getEntries()|empty}
- <div class="section tabularBox jsShowOnlyMatches" id="syncPipMatches">
- <table class="table">
+ <div class="section tabularBox jsShowOnlyMatches">
+ <table class="table" id="devtoolsProjectPipEntryList">
<thead>
<tr>
{foreach from=$entryList->getKeys() item=languageItem name=entryListKeys}
<tbody>
{foreach from=$entryList->getEntries($startIndex-1, $itemsPerPage) key=identifier item=entry}
- <tr>
- <td class="columnIcon"><a href="{link controller='DevtoolsProjectPipEntryEdit' id=$project->projectID pip=$pip identifier=$identifier entryType=$entryType}{/link}" title="{lang}wcf.global.button.edit{/lang}" class="jsTooltip"><span class="icon icon16 fa-pencil"></span></a></td>
+ <tr class="jsPipEntryRow" data-identifier="{@$identifier}">
+ <td class="columnIcon">
+ <a href="{link controller='DevtoolsProjectPipEntryEdit' id=$project->projectID pip=$pip identifier=$identifier entryType=$entryType}{/link}" title="{lang}wcf.global.button.edit{/lang}" class="jsTooltip"><span class="icon icon16 fa-pencil"></span></a>
+ <span class="icon icon16 fa-times jsDeleteButton jsTooltip pointer" title="{lang}wcf.global.button.delete{/lang}"></span>
+ </td>
{foreach from=$entryList->getKeys() key=key item=languageItem}
<td>{$entry[$key]}</td>
{/foreach}
<p class="info">{lang}wcf.global.noItems{/lang}</p>
{/if}
+<script data-relocate="true">
+ require(['Language', 'WoltLabSuite/Core/Acp/Ui/Devtools/Project/Pip/Entry/List'], function(Language, DevtoolsProjectPipEntryList) {
+ Language.addObject({
+ 'wcf.acp.devtools.project.pip.entry.delete.addDeleteInstruction': '{lang}wcf.acp.devtools.project.pip.entry.delete.addDeleteInstruction{/lang}',
+ 'wcf.acp.devtools.project.pip.entry.delete.addDeleteInstruction.description': '{lang}wcf.acp.devtools.project.pip.entry.delete.addDeleteInstruction.description{/lang}',
+ 'wcf.acp.devtools.project.pip.entry.delete.confirmMessage': '{lang}wcf.acp.devtools.project.pip.entry.delete.confirmMessage{/lang}'
+ });
+
+ new DevtoolsProjectPipEntryList('devtoolsProjectPipEntryList', '{@$project->projectID}', '{@$pip}', '{@$entryType}', {if $pipObject->getPip()->supportsDeleteInstruction()}true{else}false{/if});
+ });
+</script>
+
{include file='footer'}
--- /dev/null
+/**
+ * Handles the JavaScript part of the devtools project pip entry list.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2018 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLabSuite/Core/Acp/Ui/Devtools/Project/Pip/Entry/List
+ */
+define([
+ 'Ajax',
+ 'Language',
+ 'Ui/Confirmation',
+ 'Ui/Notification'
+], function (
+ Ajax,
+ Language,
+ UiConfirmation,
+ UiNotification
+) {
+ "use strict";
+
+ /**
+ * @constructor
+ */
+ function DevtoolsProjectPipEntryList(tableId, projectId, pip, entryType, supportsDeleteInstruction) {
+ this.init(tableId, projectId, pip, entryType, supportsDeleteInstruction);
+ };
+ DevtoolsProjectPipEntryList.prototype = {
+ /**
+ * Initializes the devtools project pip entry list handler.
+ *
+ * @param {string} tableId id of the table containing the pip entries
+ * @param {integer} projectId id of the project the listed pip entries belong to
+ * @param {string} pip name of the pip the listed entries belong to
+ * @param {string} entryType type of the listed entries
+ * @param {boolean} supportsDeleteInstruction is `true` if the pip supports `<delete>`
+ */
+ init: function(tableId, projectId, pip, entryType, supportsDeleteInstruction) {
+ this._table = elById(tableId);
+ if (this._table === null) {
+ throw new Error("Unknown element with id '" + tableId + "'.");
+ }
+ if (this._table.tagName !== 'TABLE') {
+ throw new Error("Element with id '" + tableId + "' is no table.");
+ }
+
+ this._projectId = projectId;
+ this._pip = pip;
+ this._entryType = entryType;
+ this._supportsDeleteInstruction = supportsDeleteInstruction;
+
+ elBySelAll('.jsDeleteButton', this._table, function(deleteButton) {
+ deleteButton.addEventListener('click', this._confirmDeletePipEntry.bind(this));
+ }.bind(this));
+ },
+
+ /**
+ * Returns the data used to setup the AJAX request object.
+ *
+ * @return {object} setup data
+ */
+ _ajaxSetup: function () {
+ return {
+ data: {
+ actionName: 'deletePipEntry',
+ className: 'wcf\\data\\devtools\\project\\DevtoolsProjectAction'
+ }
+ };
+ },
+
+ /**
+ * Handles successful AJAX request.
+ *
+ * @param {object} data response data
+ */
+ _ajaxSuccess: function(data) {
+ UiNotification.show();
+
+ elBySelAll('tbody > tr', this._table, function(pipEntry) {
+ if (elData(pipEntry, 'identifier') === data.returnValues.identifier) {
+ elRemove(pipEntry);
+ }
+ }.bind(this));
+
+ // reload page if table is empty
+ if (elBySelAll('tbody > tr', this._table).length === 0) {
+ window.location.reload();
+ }
+ },
+
+ /**
+ * Shows the confirmation dialog when deleting a pip entry.
+ *
+ * @param {Event} event
+ */
+ _confirmDeletePipEntry: function(event) {
+ var pipEntry = event.currentTarget.closest('tr');
+
+ UiConfirmation.show({
+ confirm: this._deletePipEntry.bind(this),
+ message: Language.get('wcf.acp.devtools.project.pip.entry.delete.confirmMessage'),
+ template: this._supportsDeleteInstruction ? '' +
+ '<dl>' +
+ ' <dt></dt>' +
+ ' <dd>' +
+ ' <label><input type="checkbox" name="addDeleteInstruction" checked> ' + Language.get('wcf.acp.devtools.project.pip.entry.delete.addDeleteInstruction') + '</label>' +
+ ' <small>' + Language.get('wcf.acp.devtools.project.pip.entry.delete.addDeleteInstruction.description') + '</small>' +
+ ' </dd>' +
+ '</dl>' : '',
+ parameters: {
+ pipEntry: pipEntry
+ }
+ });
+ },
+
+ /**
+ * Sends the AJAX request to delete a pip entry.
+ *
+ * @param {object} parameters contains the deleted pip entry element
+ * @param {HTMLElement} content confirmation dialog containing the `addDeleteInstruction` instruction
+ */
+ _deletePipEntry: function(parameters, content) {
+ var addDeleteInstruction = false;
+ if (this._supportsDeleteInstruction) {
+ addDeleteInstruction = ~~elBySel('input[name=addDeleteInstruction]', content).checked;
+ }
+
+ Ajax.api(this, {
+ objectIDs: [this._projectId],
+ parameters: {
+ addDeleteInstruction: addDeleteInstruction,
+ entryType: this._entryType,
+ identifier: elData(parameters.pipEntry, 'identifier'),
+ pip: this._pip
+ }
+ });
+ }
+ };
+
+ return DevtoolsProjectPipEntryList;
+});
'pageNo' => $this->pageNo,
'pages' => $this->pages,
'pip' => $this->pip,
+ 'pipObject' => $this->pipObject,
'project' => $this->project,
'startIndex' => $this->startIndex
]);
use wcf\data\AbstractDatabaseObjectAction;
use wcf\data\package\installation\queue\PackageInstallationQueue;
use wcf\data\package\installation\queue\PackageInstallationQueueEditor;
+use wcf\system\devtools\pip\DevtoolsPip;
use wcf\system\exception\IllegalLinkException;
use wcf\system\WCF;
use wcf\util\DirectoryUtil;
/**
* @inheritDoc
*/
- protected $requireACP = ['delete', 'installPackage'];
+ protected $requireACP = ['delete', 'deletePipEntry', 'installPackage'];
/**
* @inheritDoc
*/
public $queue;
+ /**
+ * package installation plugin the deleted entry belongs to
+ * @var DevtoolsPip
+ * @since 3.2
+ */
+ protected $pip;
+
/**
* @inheritDoc
* @return DevtoolsProject
'queueID' => $this->queue->queueID
];
}
+
+ /**
+ * Checks if the `deletePipEntry` action can be executed.
+ *
+ * @throws IllegalLinkException
+ * @since 3.2
+ */
+ public function validateDeletePipEntry() {
+ if (!ENABLE_DEVELOPER_TOOLS) {
+ throw new IllegalLinkException();
+ }
+
+ WCF::getSession()->checkPermissions(['admin.configuration.package.canInstallPackage']);
+
+ $project = $this->getSingleObject();
+
+ // read and validate pip
+ $this->readString('pip');
+ $filteredPips = array_filter($project->getPips(), function(DevtoolsPip $pip) {
+ return $pip->pluginName === $this->parameters['pip'];
+ });
+ if (count($filteredPips) === 1) {
+ $this->pip = reset($filteredPips);
+ }
+ else {
+ throw new IllegalLinkException();
+ }
+
+ if (!$this->pip->supportsGui()) {
+ throw new IllegalLinkException();
+ }
+
+ // read and validate entry type
+ $this->readString('entryType', true);
+ if ($this->parameters['entryType'] !== '') {
+ try {
+ $this->pip->getPip()->setEntryType($this->parameters['entryType']);
+ }
+ catch (\InvalidArgumentException $e) {
+ throw new IllegalLinkException();
+ }
+ }
+ else if (!empty($this->pip->getPip()->getEntryTypes())) {
+ throw new IllegalLinkException();
+ }
+
+ // read and validate identifier
+ $this->readString('identifier');
+ $entryList = $this->pip->getPip()->getEntryList();
+ if (!$entryList->hasEntry($this->parameters['identifier'])) {
+ throw new IllegalLinkException();
+ }
+
+ $this->readBoolean('addDeleteInstruction', true);
+ }
+
+ /**
+ * Deletes a specific pip entry.
+ *
+ * @return string[] identifier of the deleted pip entry
+ * @since 3.2
+ */
+ public function deletePipEntry() {
+ $this->pip->getPip()->deleteEntry($this->parameters['identifier'], $this->parameters['addDeleteInstruction']);
+
+ return [
+ 'identifier' => $this->parameters['identifier']
+ ];
+ }
}
*/
public function addEntry(IFormDocument $form);
+ /**
+ * Deletes an existing pip entry and removes it from database.
+ *
+ * @param string $identifier identifier of deleted entry
+ * @param bool $addDeleteInstruction if `true`, an explicit delete instruction is added
+ *
+ * @throws \InvalidArgumentException if no such entry exists or delete instruction should be added but is not supported
+ */
+ public function deleteEntry($identifier, $addDeleteInstruction);
+
/**
* Edits the entry of this pip with the given identifier based on the data
* provided by the given form and returns the new identifier of the entry
* @throws \InvalidArgumentException if the given entry type is invalid (see `getEntryTypes()` method)
*/
public function setEntryType($entryType);
+
+ /**
+ * Returns `true` if this package installation plugin supports delete
+ * instructions.
+ *
+ * @return boolean
+ */
+ public function supportsDeleteInstruction();
}
use wcf\system\form\builder\IFormDocument;
use wcf\system\form\builder\IFormNode;
use wcf\system\package\PackageInstallationDispatcher;
+use wcf\system\WCF;
use wcf\util\DOMUtil;
use wcf\util\XML;
return $missingElements !== count($xmls);
}
+
+ /**
+ * @inheritDoc
+ */
+ public function deleteEntry($identifier, $addDeleteInstruction) {
+ foreach ($this->getProjectXmls() as $xml) {
+ $element = $this->getElementByIdentifier($xml, $identifier);
+
+ if ($element === null) {
+ throw new \InvalidArgumentException("Unknown entry with identifier '{$identifier}'.");
+ }
+
+ if (!$this->supportsDeleteInstruction() && $addDeleteInstruction) {
+ throw new \InvalidArgumentException("This package installation plugin does not support delete instructions.");
+ }
+
+ $this->deleteObject($element);
+
+ if ($addDeleteInstruction) {
+ $this->addDeleteElement($element);
+ }
+
+ $document = $element->ownerDocument;
+
+ DOMUtil::removeNode($element);
+
+ $deleteFile = $this->sanitizeXmlFileAfterDeleteEntry($document);
+
+ if ($deleteFile) {
+ unlink($xml->getPath());
+ }
+ else {
+ $xml->write($xml->getPath());
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function deleteObject(\DOMElement $element) {
+ $sql = "DELETE FROM wcf" . WCF_N . "_language_item
+ WHERE languageItem = ?
+ AND packageID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute([$element->getAttribute('name')]);
+ }
}
*
* @property PackageInstallationDispatcher|DevtoolsPackageInstallationDispatcher $installation
* @mixin AbstractXMLPackageInstallationPlugin
+ * @mixin IGuiPackageInstallationPlugin
*/
trait TXmlGuiPackageInstallationPlugin {
/**
*/
protected $entryType;
+ /**
+ * Adds a delete element to the xml file based on the given installation
+ * element.
+ *
+ * @param \DOMElement $element installation element
+ */
+ protected function addDeleteElement(\DOMElement $element) {
+ $document = $element->ownerDocument;
+
+ $data = $document->getElementsByTagName('data')->item(0);
+ $delete = $data->getElementsByTagName('delete')->item(0);
+
+ if ($delete === null) {
+ $delete = $document->createElement('delete');
+ $data->appendChild($delete);
+ }
+
+ $delete->appendChild($document->importNode($this->prepareDeleteXmlElement($element)));
+ }
+
/**
* Adds a new entry of this pip based on the data provided by the given
* form.
return $data['element'];
}
+ /**
+ * Deletes the entry of this pip with the given identifier and, based
+ * on the value of `$addDeleteInstruction`, adds a delete instruction.
+ *
+ * @param string $identifier
+ * @param boolean $addDeleteInstruction
+ */
+ public function deleteEntry($identifier, $addDeleteInstruction) {
+ $xml = $this->getProjectXml();
+
+ $element = $this->getElementByIdentifier($xml, $identifier);
+
+ if ($element === null) {
+ throw new \InvalidArgumentException("Unknown entry with identifier '{$identifier}'.");
+ }
+
+ if (!$this->supportsDeleteInstruction() && $addDeleteInstruction) {
+ throw new \InvalidArgumentException("This package installation plugin does not support delete instructions.");
+ }
+
+ $this->deleteObject($element);
+
+ if ($addDeleteInstruction) {
+ $this->addDeleteElement($element);
+ }
+
+ $document = $element->ownerDocument;
+
+ DOMUtil::removeNode($element);
+
+ /** @var DevtoolsProject $project */
+ $project = $this->installation->getProject();
+
+ $deleteFile = $this->sanitizeXmlFileAfterDeleteEntry($document);
+
+ if ($deleteFile) {
+ unlink($this->getXmlFileLocation($project));
+ }
+ else {
+ $xml->write($this->getXmlFileLocation($project));
+ }
+ }
+
+ /**
+ * Sanitizes the given document after an entry has been deleted by removing
+ * empty parent elements and returns `true` if the xml file should be deleted
+ * because there is no content left.
+ *
+ * @param \DOMDocument $document sanitized document
+ * @return boolean
+ */
+ protected function sanitizeXmlFileAfterDeleteEntry(\DOMDocument $document) {
+ $data = $document->getElementsByTagName('data')->item(0);
+ $import = $data->getElementsByTagName('import')->item(0);
+
+ // remove empty import node
+ if ($import->childNodes->length === 0) {
+ DOMUtil::removeNode($import);
+
+ // delete file if empty
+ if ($data->childNodes->length === 0) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Deletes the given element from database.
+ *
+ * @param \DOMElement $element
+ */
+ protected function deleteObject(\DOMElement $element) {
+ $name = $element->getAttribute('name');
+ if ($name !== '') {
+ $this->handleDelete([['attributes' => ['name' => $name]]]);
+ }
+ else {
+ $identifier = $element->getAttribute('identifier');
+ if ($identifier !== '') {
+ $this->handleDelete([['attributes' => ['identifier' => $identifier]]]);
+ }
+ else {
+ throw new \LogicException("Cannot delete object using the default implementations.");
+ }
+ }
+ }
+
/**
* Edits the entry of this pip with the given identifier based on the data
* provided by the given form and returns the new identifier of the entry
EventHandler::getInstance()->fireAction($this, 'afterAddFormFields', $eventParameters);
}
+ /**
+ * Returns a delete xml element based on the given import element.
+ *
+ * @param \DOMElement $element
+ * @return \DOMElement
+ */
+ protected function prepareDeleteXmlElement(\DOMElement $element) {
+ if (!$this->supportsDeleteInstruction()) {
+ throw new \BadMethodCallException("Cannot prepare delete xml element if delete instructions are not supported.");
+ }
+
+ $name = $element->getAttribute('name');
+ if ($name !== '') {
+ $element = $element->ownerDocument->createElement($this->tagName);
+ $element->setAttribute('name', $name);
+ }
+ else {
+ $identifier = $element->getAttribute('identifier');
+ if ($identifier !== '') {
+ $element = $element->ownerDocument->createElement($this->tagName);
+ $element->setAttribute('identifier', $identifier);
+ }
+ else {
+ throw new \LogicException("Cannot prepare delete xml element using the default implementations.");
+ }
+ }
+
+ return $element;
+ }
+
/**
* Saves an object represented by an XML element in the database by either
* creating a new element (if `$oldElement = null`) or updating an existing
$this->entryType = $entryType;
}
+
+ /**
+ * Returns `true` if this package installation plugin supports delete
+ * instructions.
+ *
+ * @return boolean
+ */
+ public function supportsDeleteInstruction() {
+ return true;
+ }
}
use wcf\system\form\builder\field\validation\FormFieldValidatorUtil;
use wcf\system\form\builder\IFormDocument;
use wcf\system\WCF;
+use wcf\util\DOMUtil;
/**
* This PIP installs, updates or deletes acl options.
throw new \LogicException('Unreachable');
}
+
+ /**
+ * @inheritDoc
+ * @since 3.2
+ */
+ protected function prepareDeleteXmlElement(\DOMElement $element) {
+ $deleteElement = parent::prepareDeleteXmlElement($element);
+
+ $deleteElement->appendChild($element->ownerDocument->createElement(
+ 'objecttype',
+ $element->getElementsByTagName('objecttype')->item(0)->nodeValue
+ ));
+
+ return $deleteElement;
+ }
+
+ /**
+ * @inheritDoc
+ * @since 3.2
+ */
+ protected function deleteObject(\DOMElement $element) {
+ $name = $element->getAttribute('name');
+ $objectType = $element->getElementsByTagName('objecttype')->item(0)->nodeValue;
+
+ switch ($this->entryType) {
+ case 'categories':
+ // also delete options
+ $sql = "DELETE FROM " . $this->application . WCF_N . "_" . $this->tableName . "
+ WHERE categoryName = ?
+ AND objectTypeID = ?
+ AND packageID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute([
+ $name,
+ $this->getObjectTypeID($objectType),
+ $this->installation->getPackageID()
+ ]);
+
+ $sql = "DELETE FROM " . $this->application . WCF_N . "_" . $this->tableName . "_category
+ WHERE categoryName = ?
+ AND objectTypeID = ?
+ AND packageID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute([
+ $name,
+ $this->getObjectTypeID($objectType),
+ $this->installation->getPackageID()
+ ]);
+
+ break;
+
+ case 'options':
+ $sql = "DELETE FROM ".$this->application . WCF_N . "_". $this->tableName ."
+ WHERE optionName = ?
+ AND objectTypeID = ?
+ AND packageID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute([
+ $name,
+ $this->getObjectTypeID($objectType),
+ $this->installation->getPackageID()
+ ]);
+
+ break;
+ }
+ }
+
+ /**
+ * @inheritDoc
+ * @since 3.2
+ */
+ protected function addDeleteElement(\DOMElement $element) {
+ $this->defaultAddDeleteElement($element);
+
+ // remove install instructions for options in delete categories;
+ // explicitly adding delete instructions for these options is not
+ // necessary as they will be deleted automatically
+ if ($this->entryType === 'categories') {
+ $categoryName = $element->getAttribute('name');
+
+ $xpath = new \DOMXPath($element->ownerDocument);
+ $xpath->registerNamespace('ns', $element->ownerDocument->documentElement->getAttribute('xmlns'));
+
+ $options = $xpath->query('/ns:data/ns:import/ns:options')->item(0);
+
+ /** @var \DOMElement $option */
+ foreach (DOMUtil::getElements($options, 'option') as $option) {
+ if ($option->getElementsByTagName('categoryname')->item(0)->nodeValue === $categoryName) {
+ DOMUtil::removeNode($option);
+ }
+ }
+ }
+ }
}
// provide the default implementation to ensure backwards compatibility
// with third-party packages containing classes that extend this abstract
// class
- use TXmlGuiPackageInstallationPlugin;
+ use TXmlGuiPackageInstallationPlugin {
+ addDeleteElement as defaultAddDeleteElement;
+ sanitizeXmlFileAfterDeleteEntry as defaultSanitizeXmlFileAfterDeleteEntry;
+ }
/**
* list of option types with i18n support
return array_combine($options, $options);
}
+
+ /**
+ * @inheritDoc
+ * @since 3.2
+ */
+ protected function prepareDeleteXmlElement(\DOMElement $element) {
+ $elementName = 'option';
+
+ if ($this->entryType === 'categories') {
+ $elementName .= 'category';
+ }
+
+ $deleteElement = $element->ownerDocument->createElement($elementName);
+ $deleteElement->setAttribute('name', $element->getAttribute('name'));
+
+ return $deleteElement;
+ }
+
+ /**
+ * @inheritDoc
+ * @since 3.2
+ */
+ protected function deleteObject(\DOMElement $element) {
+ $name = $element->getAttribute('name');
+
+ switch ($this->entryType) {
+ case 'categories':
+ // also delete options
+ $sql = "DELETE FROM " . $this->application . WCF_N . "_" . $this->tableName . "
+ WHERE categoryName = ?
+ AND packageID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute([
+ $name,
+ $this->installation->getPackageID()
+ ]);
+
+ $sql = "DELETE FROM " . $this->application . WCF_N . "_" . $this->tableName . "_category
+ WHERE categoryName = ?
+ AND packageID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute([
+ $name,
+ $this->installation->getPackageID()
+ ]);
+
+ break;
+
+ case 'options':
+ $sql = "DELETE FROM ".$this->application . WCF_N . "_". $this->tableName ."
+ WHERE optionName = ?
+ AND packageID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute([
+ $name,
+ $this->installation->getPackageID()
+ ]);
+
+ break;
+ }
+ }
+
+ /**
+ * @inheritDoc
+ * @since 3.2
+ */
+ protected function addDeleteElement(\DOMElement $element) {
+ $this->defaultAddDeleteElement($element);
+
+ // remove install instructions for options in delete categories;
+ // explicitly adding delete instructions for these options is not
+ // necessary as they will be deleted automatically
+ if ($this->entryType === 'categories') {
+ $categoryName = $element->getAttribute('name');
+ $objectType = $element->getElementsByTagName('objecttype')->item(0)->nodeValue;
+
+ $xpath = new \DOMXPath($element->ownerDocument);
+ $xpath->registerNamespace('ns', $element->ownerDocument->documentElement->getAttribute('xmlns'));
+
+ $options = $xpath->query('/ns:data/ns:import/ns:options')->item(0);
+
+ /** @var \DOMElement $option */
+ foreach (DOMUtil::getElements($options, 'option') as $option) {
+ $optionCategoryName = $option->getElementsByTagName('categoryname')->item(0);
+
+ if ($optionCategoryName !== null) {
+ $optionObjectType = $option->getElementsByTagName('objectType')->item(0);
+ if ($optionCategoryName->nodeValue === $categoryName && $optionObjectType->nodeValue === $objectType) {
+ DOMUtil::removeNode($option);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ * @since 3.2
+ */
+ protected function sanitizeXmlFileAfterDeleteEntry(\DOMDocument $document) {
+ $xpath = new \DOMXPath($document);
+ $xpath->registerNamespace('ns', $document->documentElement->getAttribute('xmlns'));
+
+ // remove empty categories and options elements
+ foreach (['options'] as $type) {
+ $element = $xpath->query('/ns:data/ns:import/ns:' . $type)->item(0);
+
+ // remove empty options node
+ if ($element !== null) {
+ if ($element->childNodes->length === 0) {
+ DOMUtil::removeNode($element);
+ }
+ }
+ }
+
+ return $this->defaultSanitizeXmlFileAfterDeleteEntry($document);
+ }
}
return $clipboardAction;
}
+
+ /**
+ * @inheritDoc
+ * @since 3.2
+ */
+ protected function prepareDeleteXmlElement(\DOMElement $element) {
+ $clipboardAction = $element->ownerDocument->createElement($this->tagName);
+ $clipboardAction->setAttribute('name', $element->getAttribute('name'));
+
+ $clipboardAction->appendChild($element->ownerDocument->createElement(
+ 'actionclassname',
+ $element->getElementsByTagName('actionclassname')->item(0)->nodeValue
+ ));
+
+ return $clipboardAction;
+ }
+
+ /**
+ * @inheritDoc
+ * @since 3.2
+ */
+ protected function deleteObject(\DOMElement $element) {
+ $actionClassName = $element->getElementsByTagName('actionclassname')->item(0)->nodeValue;
+
+ $this->handleDelete([[
+ 'attributes' => ['name' => $element->getAttribute('name')],
+ 'elements' => ['actionclassname' => $actionClassName]
+ ]]);
+ }
}
return $coreObject;
}
+
+ /**
+ * @inheritDoc
+ * @since 3.2
+ */
+ protected function prepareDeleteXmlElement(\DOMElement $element) {
+ $coreObject = $element->ownerDocument->createElement($this->tagName);
+ $coreObject->setAttribute(
+ 'name',
+ $element->getElementsByTagName('objectname')->item(0)->nodeValue
+ );
+
+ return $coreObject;
+ }
+
+ /**
+ * @inheritDoc
+ * @since 3.2
+ */
+ protected function deleteObject(\DOMElement $element) {
+ $name = $element->getElementsByTagName('objectname')->item(0)->nodeValue;
+
+ $this->handleDelete([['attributes' => ['name' => $name]]]);
+ }
}
return $newElement;
}
+
+ /**
+ * @inheritDoc
+ * @since 3.2
+ */
+ protected function deleteObject(\DOMElement $element) {
+ $sql = "DELETE FROM wcf" . WCF_N . "_language_item
+ WHERE languageItem = ?
+ AND packageID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute([
+ $element->getAttribute('name'),
+ $this->installation->getPackageID()
+ ]);
+ }
+
+ /**
+ * @inheritDoc
+ * @since 3.2
+ */
+ public function supportsDeleteInstruction() {
+ return false;
+ }
+
+ /**
+ * @inheritDoc
+ * @since 3.2
+ */
+ protected function sanitizeXmlFileAfterDeleteEntry(\DOMDocument $document) {
+ $language = $document->getElementsByTagName('language')->item(0);
+
+ foreach (DOMUtil::getElements($language, 'category') as $category) {
+ if ($category->childNodes->length === 0) {
+ DOMUtil::removeNode($category);
+ }
+ }
+
+ return $language->childNodes->length === 0;
+ }
}
return $definition;
}
+
+ /**
+ * @inheritDoc
+ * @since 3.2
+ */
+ protected function prepareDeleteXmlElement(\DOMElement $element) {
+ $objectTypeDefinition = $element->ownerDocument->createElement($this->tagName);
+ $objectTypeDefinition->setAttribute(
+ 'name',
+ $element->getElementsByTagName('name')->item(0)->nodeValue
+ );
+
+ return $objectTypeDefinition;
+ }
+
+ /**
+ * @inheritDoc
+ * @since 3.2
+ */
+ protected function deleteObject(\DOMElement $element) {
+ $this->handleDelete([['attributes' => [
+ 'name' => $element->getElementsByTagName('name')->item(0)->nodeValue
+ ]]]);
+ }
}
return $returnValue;
}
+
+ /**
+ * @inheritDoc
+ * @since 3.2
+ */
+ protected function prepareDeleteXmlElement(\DOMElement $element) {
+ $objectType = $element->ownerDocument->createElement($this->tagName);
+ $objectType->setAttribute(
+ 'name',
+ $element->getElementsByTagName('name')->item(0)->nodeValue
+ );
+
+ $objectType->appendChild($element->ownerDocument->createElement(
+ 'definitionname',
+ $element->getElementsByTagName('definitionname')->item(0)->nodeValue
+ ));
+
+ return $objectType;
+ }
+
+ /**
+ * @inheritDoc
+ * @since 3.2
+ */
+ protected function deleteObject(\DOMElement $element) {
+ $name = $element->getElementsByTagName('name')->item(0)->nodeValue;
+ $definitionName = $element->getElementsByTagName('definitionname')->item(0)->nodeValue;
+
+ $this->handleDelete([[
+ 'attributes' => ['name' => $name],
+ 'elements' => ['definitionname' => $definitionName]
+ ]]);
+ }
}
return $listener;
}
+
+ /**
+ * @inheritDoc
+ * @since 3.2
+ */
+ protected function prepareDeleteXmlElement(\DOMElement $element) {
+ $templateListener = $element->ownerDocument->createElement($this->tagName);
+ $templateListener->setAttribute('name', $element->getAttribute('name'));
+
+ foreach (['environment', 'templatename', 'eventname'] as $childElement) {
+ $templateListener->appendChild($element->ownerDocument->createElement(
+ $childElement,
+ $element->getElementsByTagName($childElement)->item(0)->nodeValue
+ ));
+ }
+
+ return $templateListener;
+ }
+
+ /**
+ * @inheritDoc
+ * @since 3.2
+ */
+ protected function deleteObject(\DOMElement $element) {
+ $elements= [];
+ foreach (['environment', 'templatename', 'eventname'] as $childElement) {
+ $elements[$childElement] = $element->getElementsByTagName($childElement)->item(0)->nodeValue;
+ }
+
+ $this->handleDelete([[
+ 'attributes' => ['name' => $element->getAttribute('name')],
+ 'elements' => $elements
+ ]]);
+ }
}
return $event;
}
+
+ /**
+ * @inheritDoc
+ * @since 3.2
+ */
+ protected function prepareDeleteXmlElement(\DOMElement $element) {
+ $userNotificationEvent = $element->ownerDocument->createElement($this->tagName);
+
+ foreach (['name', 'objecttype'] as $childElement) {
+ $userNotificationEvent->appendChild($element->ownerDocument->createElement(
+ $childElement,
+ $element->getElementsByTagName($childElement)->item(0)->nodeValue
+ ));
+ }
+
+ return $userNotificationEvent;
+ }
+
+ /**
+ * @inheritDoc
+ * @since 3.2
+ */
+ protected function deleteObject(\DOMElement $element) {
+ $elements= [];
+ foreach (['name', 'objecttype'] as $childElement) {
+ $elements[$childElement] = $element->getElementsByTagName($childElement)->item(0)->nodeValue;
+ }
+
+ $this->handleDelete([['elements' => $elements]]);
+ }
}
<item name="wcf.acp.devtools.project.add.info"><![CDATA[Inkompatible Pakete, Installations- und Aktualisierungsanweisungen können erst beim Bearbeiten eines vorhandenen Projekts hinzugefügt werden.]]></item>
<item name="wcf.acp.devtools.project.optionalPackage.error.missingFiles"><![CDATA[Die folgenden Paketdateien fehlen: {implode from=$missingFiles item=missingFile}<kbd>{$missingFile}</kbd>{/implode}.]]></item>
<item name="wcf.acp.devtools.project.requiredPackage.error.missingFiles"><![CDATA[Die folgenden Paketdateien fehlen: {implode from=$missingFiles item=missingFile}<kbd>{$missingFile}</kbd>{/implode}.]]></item>
+ <item name="wcf.acp.devtools.project.pip.entry.delete.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} den Eintrag wirklich löschen?]]></item>
+ <item name="wcf.acp.devtools.project.pip.entry.delete.addDeleteInstruction"><![CDATA[Löschanweisung hinzufügen]]></item>
+ <item name="wcf.acp.devtools.project.pip.entry.delete.addDeleteInstruction.description"><![CDATA[Der Eintrag wird nicht nur aus der Datenbank gelöscht und und die Installationsanweisung entfernt, sondern es wird auch explizit eine Löschanweisung hinzugefügt.]]></item>
</category>
<category name="wcf.acp.email">
<item name="wcf.acp.email.smtp.test"><![CDATA[SMTP-Verbindungstest]]></item>
<item name="wcf.acp.devtools.project.add.info"><![CDATA[Conflicting packages, installation instructions, and update instructions can only be added when editing an existing project.]]></item>
<item name="wcf.acp.devtools.project.optionalPackage.error.missingFiles"><![CDATA[The following package files are missing: {implode from=$missingFiles item=missingFile}<kbd>{$missingFile}</kbd>{/implode}.]]></item>
<item name="wcf.acp.devtools.project.requiredPackage.error.missingFiles"><![CDATA[The following package files are missing: {implode from=$missingFiles item=missingFile}<kbd>{$missingFile}</kbd>{/implode}.]]></item>
+ <item name="wcf.acp.devtools.project.pip.entry.delete.confirmMessage"><![CDATA[Do you really want to delete the entry?]]></item>
+ <item name="wcf.acp.devtools.project.pip.entry.delete.addDeleteInstruction"><![CDATA[Add delete instruction]]></item>
+ <item name="wcf.acp.devtools.project.pip.entry.delete.addDeleteInstruction.description"><![CDATA[The entry will not only be deleted from database and its installation instruction removed, but a delete instruction will also be explicitly added.]]></item>
</category>
<category name="wcf.acp.email">
<item name="wcf.acp.email.smtp.test"><![CDATA[SMTP Connection Test]]></item>