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