2 namespace wcf\system\package\plugin
;
3 use wcf\system\database\util\PreparedStatementConditionBuilder
;
4 use wcf\system\exception\SystemException
;
5 use wcf\system\package\PackageInstallationDispatcher
;
11 * Abstract implementation of a package installation plugin using a XML file.
13 * @author Alexander Ebert
14 * @copyright 2001-2014 WoltLab GmbH
15 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
16 * @package com.woltlab.wcf
17 * @subpackage system.package.plugin
18 * @category Community Framework
20 abstract class AbstractXMLPackageInstallationPlugin
extends AbstractPackageInstallationPlugin
{
22 * object editor class name
25 public $className = '';
28 * xml tag name, e.g. 'acpmenuitem'
34 * @see \wcf\system\package\plugin\AbstractPackageInstallationPlugin::install()
36 public function __construct(PackageInstallationDispatcher
$installation, $instruction = array()) {
37 parent
::__construct($installation, $instruction);
39 // autoset 'tableName' property
40 if (empty($this->tableName
) && !empty($this->className
)) {
41 $this->tableName
= call_user_func(array($this->className
, 'getDatabaseTableAlias'));
44 // autoset 'tagName' property
45 if (empty($this->tagName
) && !empty($this->tableName
)) {
46 $this->tagName
= str_replace('_', '', $this->tableName
);
51 * @see \wcf\system\package\plugin\IPackageInstallationPlugin::install()
53 public function install() {
57 $xml = $this->getXML($this->instruction
['value']);
58 $xpath = $xml->xpath();
60 // handle delete first
61 if ($this->installation
->getAction() == 'update') {
62 $this->deleteItems($xpath);
66 $this->importItems($xpath);
73 * @see \wcf\system\package\plugin\IPackageInstallationPlugin::uninstall()
75 public function uninstall() {
85 * @param \DOMXPath $xpath
87 protected function deleteItems(\DOMXPath
$xpath) {
88 $elements = $xpath->query('/ns:data/ns:delete/ns:'.$this->tagName
);
90 foreach ($elements as $element) {
92 'attributes' => array(),
93 'elements' => array(),
94 'value' => $element->nodeValue
98 $attributes = $xpath->query('attribute::*', $element);
99 foreach ($attributes as $attribute) {
100 $data['attributes'][$attribute->name
] = $attribute->value
;
103 // get child elements
104 $childNodes = $xpath->query('child::*', $element);
105 foreach ($childNodes as $childNode) {
106 $data['elements'][$childNode->nodeName
] = $childNode->nodeValue
;
113 if (!empty($items)) {
114 $this->handleDelete($items);
119 * Imports or updates items.
121 * @param \DOMXPath $xpath
123 protected function importItems(\DOMXPath
$xpath) {
124 $elements = $xpath->query('/ns:data/ns:import/ns:'.$this->tagName
);
125 foreach ($elements as $element) {
127 'attributes' => array(),
128 'elements' => array(),
133 $attributes = $xpath->query('attribute::*', $element);
134 foreach ($attributes as $attribute) {
135 $data['attributes'][$attribute->name
] = $attribute->value
;
138 // fetch child elements
139 $items = $xpath->query('child::*', $element);
140 foreach ($items as $item) {
141 $this->getElement($xpath, $data['elements'], $item);
144 // include node value if item does not contain any child elements (eg. pip)
145 if (empty($data['elements'])) {
146 $data['nodeValue'] = $element->nodeValue
;
149 // map element data to database fields
150 $data = $this->prepareImport($data);
152 // validate item data
153 $this->validateImport($data);
155 // try to find an existing item for updating
156 $sqlData = $this->findExistingItem($data);
158 // handle items which do not support updating (e.g. cronjobs)
159 if ($sqlData === null) $row = false;
161 $statement = WCF
::getDB()->prepareStatement($sqlData['sql']);
162 $statement->execute($sqlData['parameters']);
163 $row = $statement->fetchArray();
166 // ensure a valid parameter for import()
167 if ($row === false) $row = array();
170 $this->import($row, $data);
178 * Sets element value from XPath.
180 * @param \DOMXPath $xpath
181 * @param array $elements
182 * @param \DOMElement $element
184 protected function getElement(\DOMXPath
$xpath, array &$elements, \DOMElement
$element) {
185 $elements[$element->tagName
] = $element->nodeValue
;
189 * Inserts or updates new items.
193 * @return \wcf\data\IStorableObject
195 protected function import(array $row, array $data) {
198 $this->prepareCreate($data);
200 return call_user_func(array($this->className
, 'create'), $data);
203 // update existing item
204 $baseClass = call_user_func(array($this->className
, 'getBaseClass'));
206 $itemEditor = new $this->className(new $baseClass(null, $row));
207 $itemEditor->update($data);
214 * Executed after all items would have been imported, use this hook if you've
215 * overwritten import() to disable insert/update.
217 protected function postImport() { }
220 * Deletes the given items.
222 * @param array $items
224 abstract protected function handleDelete(array $items);
227 * Prepares import, use this to map xml tags and attributes
228 * to their corresponding database fields.
233 abstract protected function prepareImport(array $data);
236 * Validates given item, e.g. checking for invalid values. If validation
237 * fails you should throw an exception.
241 protected function validateImport(array $data) { }
244 * Find an existing item for updating, should return sql query.
249 abstract protected function findExistingItem(array $data);
252 * Append additional fields which are not to be updated if a corresponding
253 * item exists but are required for creation.
255 * Attention: $data is passed by reference
259 protected function prepareCreate(array &$data) {
260 $data['packageID'] = $this->installation
->getPackageID();
264 * Triggered after executing all delete and/or import actions.
266 protected function cleanup() { }
269 * Loads the xml file into a string and returns this string.
271 * @param string $filename
274 protected function getXML($filename = '') {
275 if (empty($filename)) {
276 $filename = $this->instruction
['value'];
279 // Search the xml-file in the package archive.
280 // Abort installation in case no file was found.
281 if (($fileIndex = $this->installation
->getArchive()->getTar()->getIndexByFilename($filename)) === false) {
282 throw new SystemException("xml file '".$filename."' not found in '".$this->installation
->getArchive()->getArchive()."'");
285 // Extract acpmenu file and parse XML
287 $tmpFile = FileUtil
::getTemporaryFilename('xml_');
289 $this->installation
->getArchive()->getTar()->extract($fileIndex, $tmpFile);
290 $xml->load($tmpFile);
292 catch (\Exception
$e) { // bugfix to avoid file caching problems
294 $this->installation
->getArchive()->getTar()->extract($fileIndex, $tmpFile);
295 $xml->load($tmpFile);
297 catch (\Exception
$e) {
298 $this->installation
->getArchive()->getTar()->extract($fileIndex, $tmpFile);
299 $xml->load($tmpFile);
308 * Returns the show order value.
310 * @param integer $showOrder
311 * @param string $parentName
312 * @param string $columnName
313 * @param string $tableNameExtension
316 protected function getShowOrder($showOrder, $parentName = null, $columnName = null, $tableNameExtension = '') {
317 if ($showOrder === null) {
318 // get greatest showOrder value
319 $conditions = new PreparedStatementConditionBuilder();
320 if ($columnName !== null) $conditions->add($columnName." = ?", array($parentName));
322 $sql = "SELECT MAX(showOrder) AS showOrder
323 FROM ".$this->application
.WCF_N
."_".$this->tableName
.$tableNameExtension."
325 $statement = WCF
::getDB()->prepareStatement($sql);
326 $statement->execute($conditions->getParameters());
327 $maxShowOrder = $statement->fetchArray();
328 return (!$maxShowOrder) ?
1 : ($maxShowOrder['showOrder'] +
1);
331 // increase all showOrder values which are >= $showOrder
332 $sql = "UPDATE ".$this->application
.WCF_N
."_".$this->tableName
.$tableNameExtension."
333 SET showOrder = showOrder + 1
335 ".($columnName !== null ?
"AND ".$columnName." = ?" : "");
336 $statement = WCF
::getDB()->prepareStatement($sql);
338 $data = array($showOrder);
339 if ($columnName !== null) $data[] = $parentName;
341 $statement->execute($data);
343 // return the wanted showOrder level