Commit | Line | Data |
---|---|---|
11ade432 AE |
1 | <?php |
2 | namespace wcf\data\package; | |
3 | use wcf\data\DatabaseObject; | |
a17de04e | 4 | use wcf\system\package\PackageInstallationDispatcher; |
11ade432 AE |
5 | use wcf\system\WCF; |
6 | use wcf\util\FileUtil; | |
7 | ||
8 | /** | |
9 | * Represents a package. | |
a17de04e | 10 | * |
11ade432 | 11 | * @author Alexander Ebert |
c839bd49 | 12 | * @copyright 2001-2018 WoltLab GmbH |
11ade432 | 13 | * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> |
e71525e4 | 14 | * @package WoltLabSuite\Core\Data\Package |
e9335ed9 | 15 | * |
ed6a4e42 MS |
16 | * @property-read integer $packageID unique id of the package |
17 | * @property-read string $package unique textual identifier of the package | |
18 | * @property-read string $packageDir relative directory to Core in which the application is installed or empty if package is no application or Core | |
19 | * @property-read string $packageName name of the package or name of language item which contains the name | |
20 | * @property-read string $packageDescription description of the package or name of language item which contains the description | |
21 | * @property-read string $packageVersion installed version of package | |
22 | * @property-read integer $packageDate timestamp at which the installed package version has been released | |
23 | * @property-read integer $installDate timestamp at which the package has been installed | |
24 | * @property-read integer $updateDate timestamp at which the package has been updated or installed if it has not been updated yet | |
25 | * @property-read string $packageURL external url to website with more information about the package | |
26 | * @property-read integer $isApplication is `1` if the package delivers an application, otherwise `0` | |
27 | * @property-read string $author author of the package | |
28 | * @property-read string $authorURL external url to the website of the package author | |
11ade432 AE |
29 | */ |
30 | class Package extends DatabaseObject { | |
48050873 MS |
31 | /** |
32 | * list of packages that this package requires | |
7a23a706 | 33 | * @var Package[] |
48050873 MS |
34 | */ |
35 | protected $dependencies = null; | |
36 | ||
38bbf0b4 MS |
37 | /** |
38 | * list of packages that require this package | |
7a23a706 | 39 | * @var Package[] |
38bbf0b4 MS |
40 | */ |
41 | protected $dependentPackages = null; | |
42 | ||
11ade432 AE |
43 | /** |
44 | * installation directory | |
11ade432 AE |
45 | * @var string |
46 | */ | |
47 | protected $dir = ''; | |
48 | ||
48050873 MS |
49 | /** |
50 | * list of packages that were given as required packages during installation | |
7a23a706 | 51 | * @var Package[] |
48050873 MS |
52 | */ |
53 | protected $requiredPackages = null; | |
54 | ||
490ef799 | 55 | /** |
94db4109 | 56 | * list of ids of packages which are required by another package |
7a23a706 | 57 | * @var integer[] |
490ef799 | 58 | */ |
6cbea15c MS |
59 | protected static $requiredPackageIDs = null; |
60 | ||
61 | /** | |
62 | * package requirements | |
63 | * @var array | |
64 | */ | |
490ef799 MS |
65 | protected static $requirements = null; |
66 | ||
11ade432 | 67 | /** |
28410a97 | 68 | * Returns true if this package is required by other packages. |
9f959ced | 69 | * |
11ade432 AE |
70 | * @return boolean |
71 | */ | |
72 | public function isRequired() { | |
490ef799 | 73 | self::loadRequirements(); |
11ade432 | 74 | |
6cbea15c | 75 | return in_array($this->packageID, self::$requiredPackageIDs); |
11ade432 AE |
76 | } |
77 | ||
78 | /** | |
79 | * Returns true if package is a plugin. | |
9f959ced | 80 | * |
11ade432 AE |
81 | * @return boolean |
82 | */ | |
83 | public function isPlugin() { | |
b8741db6 AE |
84 | if ($this->isApplication) { |
85 | return false; | |
86 | } | |
11ade432 | 87 | |
b8741db6 | 88 | return true; |
11ade432 AE |
89 | } |
90 | ||
91 | /** | |
92 | * Returns the name of this package. | |
9f959ced | 93 | * |
11ade432 AE |
94 | * @return string |
95 | */ | |
96 | public function getName() { | |
07c78f25 AE |
97 | return WCF::getLanguage()->get($this->packageName); |
98 | } | |
99 | ||
100 | /** | |
0fcfe5f6 | 101 | * @inheritDoc |
07c78f25 AE |
102 | */ |
103 | public function __toString() { | |
104 | return $this->getName(); | |
11ade432 AE |
105 | } |
106 | ||
11ade432 AE |
107 | /** |
108 | * Returns the abbreviation of the package name. | |
9f959ced | 109 | * |
11ade432 AE |
110 | * @param string $package |
111 | * @return string | |
112 | */ | |
113 | public static function getAbbreviation($package) { | |
114 | $array = explode('.', $package); | |
115 | return array_pop($array); | |
116 | } | |
117 | ||
11ade432 | 118 | /** |
38bbf0b4 MS |
119 | * Returns the list of packages which are required by this package. The |
120 | * returned packages are the packages given in the <requiredpackages> tag | |
121 | * in the package.xml of this package. | |
9f959ced | 122 | * |
7a23a706 | 123 | * @return Package[] |
11ade432 AE |
124 | */ |
125 | public function getRequiredPackages() { | |
48050873 | 126 | if ($this->requiredPackages === null) { |
38bbf0b4 | 127 | self::loadRequirements(); |
48050873 | 128 | |
058cbd6a | 129 | $this->requiredPackages = []; |
38bbf0b4 MS |
130 | if (isset(self::$requirements[$this->packageID])) { |
131 | foreach (self::$requirements[$this->packageID] as $packageID) { | |
132 | $this->requiredPackages[$packageID] = PackageCache::getInstance()->getPackage($packageID); | |
133 | } | |
48050873 | 134 | } |
11ade432 AE |
135 | } |
136 | ||
48050873 | 137 | return $this->requiredPackages; |
11ade432 AE |
138 | } |
139 | ||
5ccce215 | 140 | /** |
28410a97 | 141 | * Returns true if current user can uninstall this package. |
5ccce215 AE |
142 | * |
143 | * @return boolean | |
144 | */ | |
145 | public function canUninstall() { | |
6476e7a1 | 146 | if (!WCF::getSession()->getPermission('admin.configuration.package.canUninstallPackage')) { |
5ccce215 AE |
147 | return false; |
148 | } | |
149 | ||
74622428 AE |
150 | // disallow uninstallation of WCF |
151 | if ($this->package == 'com.woltlab.wcf') { | |
5ccce215 AE |
152 | return false; |
153 | } | |
154 | ||
cd0fbe9a | 155 | // check if package is required by another package |
4879676b | 156 | if ($this->isRequired()) { |
5ccce215 AE |
157 | return false; |
158 | } | |
159 | ||
160 | return true; | |
161 | } | |
162 | ||
e852aa82 AE |
163 | /** |
164 | * Returns a list of packages dependent from current package. | |
165 | * | |
7a23a706 | 166 | * @return Package[] |
e852aa82 AE |
167 | */ |
168 | public function getDependentPackages() { | |
38bbf0b4 MS |
169 | if ($this->dependentPackages === null) { |
170 | self::loadRequirements(); | |
171 | ||
058cbd6a | 172 | $this->dependentPackages = []; |
38bbf0b4 MS |
173 | foreach (self::$requirements as $packageID => $requiredPackageIDs) { |
174 | if (in_array($this->packageID, $requiredPackageIDs)) { | |
175 | $this->dependentPackages[$packageID] = PackageCache::getInstance()->getPackage($packageID); | |
176 | } | |
e852aa82 AE |
177 | } |
178 | } | |
179 | ||
38bbf0b4 | 180 | return $this->dependentPackages; |
e852aa82 AE |
181 | } |
182 | ||
39935629 AE |
183 | /** |
184 | * Overwrites current package version. | |
185 | * | |
186 | * DO NOT call this method outside the package installation! | |
187 | * | |
188 | * @param string $packageVersion | |
189 | */ | |
190 | public function setPackageVersion($packageVersion) { | |
191 | $this->data['packageVersion'] = $packageVersion; | |
192 | } | |
193 | ||
9078d83e MS |
194 | /** |
195 | * Returns the absolute path to the package directory with a trailing slash. | |
196 | * | |
197 | * @return string | |
198 | */ | |
199 | public function getAbsolutePackageDir() { | |
200 | return FileUtil::addTrailingSlash(FileUtil::getRealPath(WCF_DIR . $this->packageDir)); | |
201 | } | |
202 | ||
5ccce215 | 203 | /** |
cd0fbe9a | 204 | * Loads package requirements. |
5ccce215 | 205 | */ |
cd0fbe9a | 206 | protected static function loadRequirements() { |
490ef799 | 207 | if (self::$requirements === null) { |
5ccce215 | 208 | $sql = "SELECT packageID, requirement |
cd0fbe9a | 209 | FROM wcf".WCF_N."_package_requirement"; |
5ccce215 AE |
210 | $statement = WCF::getDB()->prepareStatement($sql); |
211 | $statement->execute(); | |
212 | ||
058cbd6a MS |
213 | self::$requiredPackageIDs = []; |
214 | self::$requirements = []; | |
5ccce215 | 215 | while ($row = $statement->fetchArray()) { |
cd0fbe9a | 216 | if (!isset(self::$requirements[$row['packageID']])) { |
058cbd6a | 217 | self::$requirements[$row['packageID']] = []; |
5ccce215 AE |
218 | } |
219 | ||
cd0fbe9a | 220 | self::$requirements[$row['packageID']][] = $row['requirement']; |
6cbea15c MS |
221 | |
222 | if (!in_array($row['requirement'], self::$requiredPackageIDs)) { | |
223 | self::$requiredPackageIDs[] = $row['requirement']; | |
224 | } | |
5ccce215 AE |
225 | } |
226 | } | |
227 | } | |
228 | ||
100859d8 | 229 | /** |
28410a97 | 230 | * Returns true if package identified by $package is already installed. |
100859d8 AE |
231 | * |
232 | * @param string $package | |
233 | * @return boolean | |
234 | */ | |
235 | public static function isAlreadyInstalled($package) { | |
5c6ddd85 | 236 | $sql = "SELECT COUNT(*) |
100859d8 AE |
237 | FROM wcf".WCF_N."_package |
238 | WHERE package = ?"; | |
239 | $statement = WCF::getDB()->prepareStatement($sql); | |
058cbd6a | 240 | $statement->execute([$package]); |
100859d8 | 241 | |
5c6ddd85 | 242 | return $statement->fetchSingleColumn() > 0; |
100859d8 AE |
243 | } |
244 | ||
11ade432 AE |
245 | /** |
246 | * Checks if a package name is valid. | |
a17de04e MS |
247 | * |
248 | * A valid package name begins with at least one alphanumeric character | |
249 | * or an underscore, followed by a dot, followed by at least one alphanumeric | |
250 | * character or an underscore and the same again, possibly repeatedly. | |
e48ab932 S |
251 | * The package name cannot be any longer than 191 characters in total due to |
252 | * internal database character encoding limitations. | |
9a5d32b2 MS |
253 | * Example: |
254 | * com.woltlab.wcf | |
a17de04e MS |
255 | * |
256 | * Reminder: The package name being examined here contains the 'name' attribute | |
257 | * of the 'package' tag noted in the 'packages.xml' file delivered inside | |
258 | * the respective package. | |
9f959ced | 259 | * |
39bea7dd MS |
260 | * @param string $packageName |
261 | * @return boolean isValid | |
11ade432 AE |
262 | */ |
263 | public static function isValidPackageName($packageName) { | |
e48ab932 S |
264 | if (mb_strlen($packageName) < 3 || mb_strlen($packageName) > 191) { |
265 | return false; | |
266 | } | |
267 | ||
11ade432 AE |
268 | return preg_match('%^[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$%', $packageName); |
269 | } | |
270 | ||
b4cbf821 | 271 | /** |
28410a97 | 272 | * Returns true if package version is valid. |
b4cbf821 | 273 | * |
9a5d32b2 MS |
274 | * Examples of valid package versions: |
275 | * 1.0.0 pl 3 | |
276 | * 4.0.0 Alpha 1 | |
277 | * 3.1.7 rC 4 | |
b4cbf821 AE |
278 | * |
279 | * @param string $version | |
280 | * @return boolean | |
281 | */ | |
282 | public static function isValidVersion($version) { | |
283 | return preg_match('~^([0-9]+)\.([0-9]+)\.([0-9]+)(\ (a|alpha|b|beta|d|dev|rc|pl)\ ([0-9]+))?$~is', $version); | |
284 | } | |
285 | ||
da89969e | 286 | /** |
a17de04e MS |
287 | * Checks the version number of the installed package against the "fromversion" |
288 | * number of the update. | |
289 | * | |
5a095676 MW |
290 | * The "fromversion" number may contain wildcards (asterisks) which means |
291 | * that the update covers the whole range of release numbers where the asterisk | |
292 | * wildcards digits from 0 to 9. | |
293 | * For example, if "fromversion" is "1.1.*" and this package updates to | |
294 | * version 1.2.0, all releases from 1.1.0 to 1.1.9 may be updated using | |
295 | * this package. | |
296 | * | |
da89969e MW |
297 | * @param string $currentVersion |
298 | * @param string $fromVersion | |
299 | * @return boolean | |
300 | */ | |
ac52543a | 301 | public static function checkFromversion($currentVersion, $fromVersion) { |
5a095676 MW |
302 | if (mb_strpos($fromVersion, '*') !== false) { |
303 | // from version with wildcard | |
304 | // use regular expression | |
305 | $fromVersion = str_replace('\*', '.*', preg_quote($fromVersion, '!')); | |
306 | if (preg_match('!^'.$fromVersion.'$!i', $currentVersion)) { | |
307 | return true; | |
308 | } | |
309 | } | |
310 | else { | |
311 | if (self::compareVersion($currentVersion, $fromVersion, '=')) { | |
312 | return true; | |
313 | } | |
da89969e MW |
314 | } |
315 | ||
316 | return false; | |
317 | } | |
318 | ||
11ade432 AE |
319 | /** |
320 | * Compares two version number strings. | |
9f959ced | 321 | * |
da89969e MW |
322 | * @param string $version1 |
323 | * @param string $version2 | |
324 | * @param string $operator | |
325 | * @return boolean result | |
0c166126 | 326 | * @see http://www.php.net/manual/en/function.version-compare.php |
11ade432 AE |
327 | */ |
328 | public static function compareVersion($version1, $version2, $operator = null) { | |
329 | $version1 = self::formatVersionForCompare($version1); | |
330 | $version2 = self::formatVersionForCompare($version2); | |
331 | if ($operator === null) return version_compare($version1, $version2); | |
332 | else return version_compare($version1, $version2, $operator); | |
333 | } | |
334 | ||
335 | /** | |
336 | * Formats a package version string for comparing. | |
9f959ced | 337 | * |
11ade432 | 338 | * @param string $version |
39bea7dd | 339 | * @return string formatted version |
0c166126 | 340 | * @see http://www.php.net/manual/en/function.version-compare.php |
11ade432 AE |
341 | */ |
342 | private static function formatVersionForCompare($version) { | |
343 | // remove spaces | |
344 | $version = str_replace(' ', '', $version); | |
345 | ||
346 | // correct special version strings | |
347 | $version = str_ireplace('dev', 'dev', $version); | |
348 | $version = str_ireplace('alpha', 'alpha', $version); | |
349 | $version = str_ireplace('beta', 'beta', $version); | |
350 | $version = str_ireplace('RC', 'RC', $version); | |
351 | $version = str_ireplace('pl', 'pl', $version); | |
352 | ||
353 | return $version; | |
354 | } | |
355 | ||
11ade432 | 356 | /** |
aac1247e | 357 | * Writes the config.inc.php for an application. |
9f959ced | 358 | * |
11ade432 AE |
359 | * @param integer $packageID |
360 | */ | |
361 | public static function writeConfigFile($packageID) { | |
362 | $package = new Package($packageID); | |
363 | $packageDir = FileUtil::addTrailingSlash(FileUtil::getRealPath(WCF_DIR.$package->packageDir)); | |
b842faa1 | 364 | |
e421b813 | 365 | $prefix = strtoupper(self::getAbbreviation($package->package)); |
11ade432 | 366 | |
b842faa1 AE |
367 | $content = "<?php\n"; |
368 | $content .= "// {$package->package} (packageID {$packageID})\n"; | |
369 | $content .= "if (!defined('{$prefix}_DIR')) define('{$prefix}_DIR', __DIR__.'/');\n"; | |
370 | $content .= "if (!defined('PACKAGE_ID')) define('PACKAGE_ID', {$packageID});\n"; | |
371 | $content .= "if (!defined('PACKAGE_NAME')) define('PACKAGE_NAME', '" . addcslashes($package->getName(), "'") . "');\n"; | |
372 | $content .= "if (!defined('PACKAGE_VERSION')) define('PACKAGE_VERSION', '{$package->packageVersion}');\n"; | |
373 | ||
374 | if ($packageID != 1) { | |
375 | $content .= "\n"; | |
376 | $content .= "// helper constants for applications\n"; | |
377 | $content .= "if (!defined('RELATIVE_{$prefix}_DIR')) define('RELATIVE_{$prefix}_DIR', '');\n"; | |
378 | $content .= "if (!defined('RELATIVE_WCF_DIR')) define('RELATIVE_WCF_DIR', RELATIVE_{$prefix}_DIR.'" . FileUtil::getRelativePath($packageDir, WCF_DIR) . "');\n"; | |
379 | } | |
11ade432 | 380 | |
b842faa1 | 381 | file_put_contents($packageDir . PackageInstallationDispatcher::CONFIG_FILE, $content); |
11ade432 | 382 | |
9353ac84 AE |
383 | // add legacy config.inc.php file for backwards compatibility |
384 | if ($packageID != 1) { | |
385 | // force overwriting the `config.inc.php` unless it is the core itself | |
5ce29b55 | 386 | file_put_contents($packageDir.'config.inc.php', "<?php" . "\n" . "require_once(__DIR__ . '/".PackageInstallationDispatcher::CONFIG_FILE."');\n"); |
b842faa1 | 387 | } |
11ade432 | 388 | } |
11ade432 | 389 | } |