Merge branch '2.0'
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / package / plugin / LanguagePackageInstallationPlugin.class.php
1 <?php
2 namespace wcf\system\package\plugin;
3 use wcf\data\language\LanguageEditor;
4 use wcf\system\database\util\PreparedStatementConditionBuilder;
5 use wcf\system\exception\SystemException;
6 use wcf\system\language\LanguageFactory;
7 use wcf\system\package\PackageArchive;
8 use wcf\system\WCF;
9 use wcf\util\XML;
10
11 /**
12 * Installs, updates and deletes languages, their categories and items.
13 *
14 * @author Alexander Ebert
15 * @copyright 2001-2014 WoltLab GmbH
16 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
17 * @package com.woltlab.wcf
18 * @subpackage system.package.plugin
19 * @category Community Framework
20 */
21 class LanguagePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
22 /**
23 * @see \wcf\system\package\plugin\AbstractPackageInstallationPlugin::$tableName
24 */
25 public $tableName = 'language_item';
26
27 /**
28 * @see \wcf\system\package\plugin\IPackageInstallationPlugin::install()
29 */
30 public function install() {
31 AbstractPackageInstallationPlugin::install();
32
33 // get language files
34 $languageFiles = array();
35 $multipleFiles = false;
36 $filename = $this->instruction['value'];
37 if (strpos($filename, '*') !== false) {
38 // wildcard syntax; import multiple files
39 $multipleFiles = true;
40 $files = $this->installation->getArchive()->getTar()->getContentList();
41 $pattern = str_replace("\*", ".*", preg_quote($filename));
42
43 foreach ($files as $file) {
44 if (preg_match('!'.$pattern.'!i', $file['filename'])) {
45 if (preg_match('~([a-z-]+)\.xml$~i', $file['filename'], $match)) {
46 $languageFiles[$match[1]] = $file['filename'];
47 }
48 else {
49 throw new SystemException("Can not determine language code of language file '".$file['filename']."'");
50 }
51 }
52 }
53 }
54 else {
55 if (!empty($this->instruction['attributes']['languagecode'])) {
56 $languageCode = $this->instruction['attributes']['languagecode'];
57 }
58 else if (!empty($this->instruction['attributes']['language'])) {
59 $languageCode = $this->instruction['attributes']['language'];
60 }
61 else if (preg_match('~([a-z-]+)\.xml$~i', $filename, $match)) {
62 $languageCode = $match[1];
63 }
64 else {
65 throw new SystemException("Can not determine language code of language file '".$filename."'");
66 }
67
68 $languageFiles[$languageCode] = $filename;
69 }
70
71 // get installed languages
72 $installedLanguages = array();
73 $sql = "SELECT *
74 FROM wcf".WCF_N."_language
75 ORDER BY isDefault DESC";
76 $statement = WCF::getDB()->prepareStatement($sql);
77 $statement->execute();
78 while ($row = $statement->fetchArray()) {
79 $installedLanguages[] = $row;
80 }
81
82 // install language
83 foreach ($installedLanguages as $installedLanguage) {
84 $languageFile = null;
85 $updateExistingItems = true;
86 if (isset($languageFiles[$installedLanguage['languageCode']])) {
87 $languageFile = $languageFiles[$installedLanguage['languageCode']];
88 }
89 else if ($multipleFiles) {
90 // do not update existing items, only add new ones
91 $updateExistingItems = false;
92
93 // use default language
94 if (isset($languageFiles[$installedLanguages[0]['languageCode']])) {
95 $languageFile = $languageFiles[$installedLanguages[0]['languageCode']];
96 }
97
98 // use english (if installed)
99 else if (isset($languageFiles['en'])) {
100 foreach ($installedLanguages as $installedLanguage2) {
101 if ($installedLanguage2['languageCode'] == 'en') {
102 $languageFile = $languageFiles['en'];
103 break;
104 }
105 }
106 }
107
108 // use any installed language
109 if ($languageFile === null) {
110 foreach ($installedLanguages as $installedLanguage2) {
111 if (isset($languageFiles[$installedLanguage2['languageCode']])) {
112 $languageFile = $languageFiles[$installedLanguage2['languageCode']];
113 break;
114 }
115 }
116 }
117
118 // use first delivered language
119 if ($languageFile === null) {
120 foreach ($languageFiles as $languageFile) break;
121 }
122 }
123
124 // save language
125 if ($languageFile !== null) {
126 if ($xml = $this->readLanguage($languageFile)) {
127 // get language object
128 $language = LanguageFactory::getInstance()->getLanguageByCode($installedLanguage['languageCode']);
129 $languageEditor = new LanguageEditor($language);
130
131 // import xml
132 // don't update language files if package is an application
133 $languageEditor->updateFromXML($xml, $this->installation->getPackageID(), !$this->installation->getPackage()->isApplication, $updateExistingItems);
134 }
135 }
136 }
137 }
138
139 /**
140 * @see \wcf\system\package\plugin\IPackageInstallationPlugin::uninstall()
141 */
142 public function uninstall() {
143 parent::uninstall();
144
145 // delete language items
146 // Get all items and their categories
147 // which where installed from this package.
148 $sql = "SELECT languageItemID, languageCategoryID, languageID
149 FROM wcf".WCF_N."_language_item
150 WHERE packageID = ?";
151 $statement = WCF::getDB()->prepareStatement($sql);
152 $statement->execute(array($this->installation->getPackageID()));
153 $itemIDs = array();
154 $categoryIDs = array();
155 while ($row = $statement->fetchArray()) {
156 $itemIDs[] = $row['languageItemID'];
157
158 // Store categories
159 $categoryIDs[$row['languageCategoryID']] = true;
160 }
161
162 if (!empty($itemIDs)) {
163 $sql = "DELETE FROM wcf".WCF_N."_".$this->tableName."
164 WHERE languageItemID = ?
165 AND packageID = ?";
166 $statement = WCF::getDB()->prepareStatement($sql);
167
168 foreach ($itemIDs as $itemID) {
169 $statement->execute(array(
170 $itemID,
171 $this->installation->getPackageID()
172 ));
173 }
174
175 $this->deleteEmptyCategories(array_keys($categoryIDs), $this->installation->getPackageID());
176 }
177 }
178
179 /**
180 * Extracts the language file and parses it. If the specified language file
181 * was not found, an exception message is thrown.
182 *
183 * @param string $filename
184 * @return \wcf\util\XML
185 */
186 protected function readLanguage($filename) {
187 // search language files in package archive
188 // throw error message if not found
189 if (($fileIndex = $this->installation->getArchive()->getTar()->getIndexByFilename($filename)) === false) {
190 throw new SystemException("language file '".$filename."' not found.");
191 }
192
193 // extract language file and parse with DOMDocument
194 $xml = new XML();
195 $xml->loadXML($filename, $this->installation->getArchive()->getTar()->extractToString($fileIndex));
196 return $xml;
197 }
198
199 /**
200 * Deletes categories which where changed by an update or deinstallation
201 * in case they are now empty.
202 *
203 * @param array $categoryIDs
204 * @param integer $packageID
205 */
206 protected function deleteEmptyCategories(array $categoryIDs, $packageID) {
207 // Get empty categories which where changed by this package.
208 $conditions = new PreparedStatementConditionBuilder();
209 $conditions->add("language_category.languageCategoryID IN (?)", array($categoryIDs));
210
211 $sql = "SELECT COUNT(item.languageItemID) AS count,
212 language_category.languageCategoryID,
213 language_category.languageCategory
214 FROM wcf".WCF_N."_language_category language_category
215 LEFT JOIN wcf".WCF_N."_language_item item
216 ON (item.languageCategoryID = language_category.languageCategoryID)
217 ".$conditions."
218 GROUP BY language_category.languageCategoryID ASC,
219 language_category.languageCategory ASC";
220 $statement = WCF::getDB()->prepareStatement($sql);
221 $statement->execute($conditions->getParameters());
222 $categoriesToDelete = array();
223 while ($row = $statement->fetchArray()) {
224 if ($row['count'] == 0) {
225 $categoriesToDelete[$row['languageCategoryID']] = $row['languageCategory'];
226 }
227 }
228
229 // Delete categories from DB.
230 if (!empty($categoriesToDelete)) {
231 $sql = "DELETE FROM wcf".WCF_N."_language_category
232 WHERE languageCategory = ?";
233 $statement = WCF::getDB()->prepareStatement($sql);
234
235 foreach ($categoriesToDelete as $category) {
236 $statement->execute(array($category));
237 }
238 }
239 }
240
241 /**
242 * @see \wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::handleDelete()
243 */
244 protected function handleDelete(array $items) { }
245
246 /**
247 * @see \wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::prepareImport()
248 */
249 protected function prepareImport(array $data) { }
250
251 /**
252 * @see \wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::findExistingItem()
253 */
254 protected function findExistingItem(array $data) { }
255
256 /**
257 * @see \wcf\system\package\plugin\IPackageInstallationPlugin::isValid()
258 */
259 public static function isValid(PackageArchive $archive, $instruction) {
260 return true;
261 }
262 }