Added detailed list of received/given likes in user profiles
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / data / package / Package.class.php
1 <?php
2 namespace wcf\data\package;
3 use wcf\data\DatabaseObject;
4 use wcf\system\io\File;
5 use wcf\system\package\PackageInstallationDispatcher;
6 use wcf\system\WCF;
7 use wcf\util\FileUtil;
8
9 /**
10 * Represents a package.
11 *
12 * @author Alexander Ebert
13 * @copyright 2001-2014 WoltLab GmbH
14 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
15 * @package com.woltlab.wcf
16 * @subpackage data.package
17 * @category Community Framework
18 */
19 class Package extends DatabaseObject {
20 /**
21 * list of packages that this package requires
22 * @var array<\wcf\data\package\Package>
23 */
24 protected $dependencies = null;
25
26 /**
27 * list of packages that require this package
28 * @var array<\wcf\data\package\Package>
29 */
30 protected $dependentPackages = null;
31
32 /**
33 * installation directory
34 * @var string
35 */
36 protected $dir = '';
37
38 /**
39 * list of packages that were given as required packages during installation
40 * @var array<\wcf\data\package\Package>
41 */
42 protected $requiredPackages = null;
43
44 /**
45 * @see \wcf\data\DatabaseObject::$databaseTableName
46 */
47 protected static $databaseTableName = 'package';
48
49 /**
50 * @see \wcf\data\DatabaseObject::$databaseTableIndexName
51 */
52 protected static $databaseTableIndexName = 'packageID';
53
54 /**
55 * list of ids of packages which are required by another package
56 * @var array<integer>
57 */
58 protected static $requiredPackageIDs = null;
59
60 /**
61 * package requirements
62 * @var array
63 */
64 protected static $requirements = null;
65
66 /**
67 * Returns true if this package is required by other packages.
68 *
69 * @return boolean
70 */
71 public function isRequired() {
72 self::loadRequirements();
73
74 return in_array($this->packageID, self::$requiredPackageIDs);
75 }
76
77 /**
78 * Returns true if package is a plugin.
79 *
80 * @return boolean
81 */
82 public function isPlugin() {
83 if ($this->isApplication) {
84 return false;
85 }
86
87 return true;
88 }
89
90 /**
91 * Returns the name of this package.
92 *
93 * @return string
94 */
95 public function getName() {
96 return WCF::getLanguage()->get($this->packageName);
97 }
98
99 /**
100 * @see \wcf\data\package\Package::getName()
101 */
102 public function __toString() {
103 return $this->getName();
104 }
105
106 /**
107 * Returns the abbreviation of the package name.
108 *
109 * @param string $package
110 * @return string
111 */
112 public static function getAbbreviation($package) {
113 $array = explode('.', $package);
114 return array_pop($array);
115 }
116
117 /**
118 * Returns the list of packages which are required by this package. The
119 * returned packages are the packages given in the <requiredpackages> tag
120 * in the package.xml of this package.
121 *
122 * @return array<\wcf\data\package\Package>
123 */
124 public function getRequiredPackages() {
125 if ($this->requiredPackages === null) {
126 self::loadRequirements();
127
128 $this->requiredPackages = array();
129 if (isset(self::$requirements[$this->packageID])) {
130 foreach (self::$requirements[$this->packageID] as $packageID) {
131 $this->requiredPackages[$packageID] = PackageCache::getInstance()->getPackage($packageID);
132 }
133 }
134 }
135
136 return $this->requiredPackages;
137 }
138
139 /**
140 * Returns true if current user can uninstall this package.
141 *
142 * @return boolean
143 */
144 public function canUninstall() {
145 if (!WCF::getSession()->getPermission('admin.system.package.canUninstallPackage')) {
146 return false;
147 }
148
149 // disallow uninstallation of WCF
150 if ($this->package == 'com.woltlab.wcf') {
151 return false;
152 }
153
154 // check if package is required by another package
155 if (self::isRequired($this->packageID)) {
156 return false;
157 }
158
159 return true;
160 }
161
162 /**
163 * Returns a list of packages dependent from current package.
164 *
165 * @return array<\wcf\data\package\Package>
166 */
167 public function getDependentPackages() {
168 if ($this->dependentPackages === null) {
169 self::loadRequirements();
170
171 $this->dependentPackages = array();
172 foreach (self::$requirements as $packageID => $requiredPackageIDs) {
173 if (in_array($this->packageID, $requiredPackageIDs)) {
174 $this->dependentPackages[$packageID] = PackageCache::getInstance()->getPackage($packageID);
175 }
176 }
177 }
178
179 return $this->dependentPackages;
180 }
181
182 /**
183 * Overwrites current package version.
184 *
185 * DO NOT call this method outside the package installation!
186 *
187 * @param string $packageVersion
188 */
189 public function setPackageVersion($packageVersion) {
190 $this->data['packageVersion'] = $packageVersion;
191 }
192
193 /**
194 * Loads package requirements.
195 */
196 protected static function loadRequirements() {
197 if (self::$requirements === null) {
198 $sql = "SELECT packageID, requirement
199 FROM wcf".WCF_N."_package_requirement";
200 $statement = WCF::getDB()->prepareStatement($sql);
201 $statement->execute();
202
203 self::$requiredPackageIDs = array();
204 self::$requirements = array();
205 while ($row = $statement->fetchArray()) {
206 if (!isset(self::$requirements[$row['packageID']])) {
207 self::$requirements[$row['packageID']] = array();
208 }
209
210 self::$requirements[$row['packageID']][] = $row['requirement'];
211
212 if (!in_array($row['requirement'], self::$requiredPackageIDs)) {
213 self::$requiredPackageIDs[] = $row['requirement'];
214 }
215 }
216 }
217 }
218
219 /**
220 * Returns true if package identified by $package is already installed.
221 *
222 * @param string $package
223 * @return boolean
224 */
225 public static function isAlreadyInstalled($package) {
226 $sql = "SELECT COUNT(*) AS count
227 FROM wcf".WCF_N."_package
228 WHERE package = ?";
229 $statement = WCF::getDB()->prepareStatement($sql);
230 $statement->execute(array($package));
231 $row = $statement->fetchArray();
232
233 return ($row['count'] ? true : false);
234 }
235
236 /**
237 * Checks if a package name is valid.
238 *
239 * A valid package name begins with at least one alphanumeric character
240 * or an underscore, followed by a dot, followed by at least one alphanumeric
241 * character or an underscore and the same again, possibly repeatedly.
242 * Example:
243 * com.woltlab.wcf
244 *
245 * Reminder: The package name being examined here contains the 'name' attribute
246 * of the 'package' tag noted in the 'packages.xml' file delivered inside
247 * the respective package.
248 *
249 * @param string $packageName
250 * @return boolean isValid
251 */
252 public static function isValidPackageName($packageName) {
253 return preg_match('%^[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$%', $packageName);
254 }
255
256 /**
257 * Returns true if package version is valid.
258 *
259 * Examples of valid package versions:
260 * 1.0.0 pl 3
261 * 4.0.0 Alpha 1
262 * 3.1.7 rC 4
263 *
264 * @param string $version
265 * @return boolean
266 */
267 public static function isValidVersion($version) {
268 return preg_match('~^([0-9]+)\.([0-9]+)\.([0-9]+)(\ (a|alpha|b|beta|d|dev|rc|pl)\ ([0-9]+))?$~is', $version);
269 }
270
271 /**
272 * Checks the version number of the installed package against the "fromversion"
273 * number of the update.
274 *
275 * The "fromversion" number may contain wildcards (asterisks) which means
276 * that the update covers the whole range of release numbers where the asterisk
277 * wildcards digits from 0 to 9.
278 * For example, if "fromversion" is "1.1.*" and this package updates to
279 * version 1.2.0, all releases from 1.1.0 to 1.1.9 may be updated using
280 * this package.
281 *
282 * @param string $currentVersion
283 * @param string $fromVersion
284 * @return boolean
285 */
286 public static function checkFromversion($currentVersion, $fromversion) {
287 if (mb_strpos($fromversion, '*') !== false) {
288 // from version with wildcard
289 // use regular expression
290 $fromversion = str_replace('\*', '.*', preg_quote($fromversion, '!'));
291 if (preg_match('!^'.$fromversion.'$!i', $currentVersion)) {
292 return true;
293 }
294 }
295 else {
296 if (self::compareVersion($currentVersion, $fromversion, '=')) {
297 return true;
298 }
299 }
300
301 return false;
302 }
303
304 /**
305 * Compares two version number strings.
306 *
307 * @param string $version1
308 * @param string $version2
309 * @param string $operator
310 * @return boolean result
311 * @see http://www.php.net/manual/en/function.version-compare.php
312 */
313 public static function compareVersion($version1, $version2, $operator = null) {
314 $version1 = self::formatVersionForCompare($version1);
315 $version2 = self::formatVersionForCompare($version2);
316 if ($operator === null) return version_compare($version1, $version2);
317 else return version_compare($version1, $version2, $operator);
318 }
319
320 /**
321 * Formats a package version string for comparing.
322 *
323 * @param string $version
324 * @return string formatted version
325 * @see http://www.php.net/manual/en/function.version-compare.php
326 */
327 private static function formatVersionForCompare($version) {
328 // remove spaces
329 $version = str_replace(' ', '', $version);
330
331 // correct special version strings
332 $version = str_ireplace('dev', 'dev', $version);
333 $version = str_ireplace('alpha', 'alpha', $version);
334 $version = str_ireplace('beta', 'beta', $version);
335 $version = str_ireplace('RC', 'RC', $version);
336 $version = str_ireplace('pl', 'pl', $version);
337
338 return $version;
339 }
340
341 /**
342 * Writes the config.inc.php for an application.
343 *
344 * @param integer $packageID
345 */
346 public static function writeConfigFile($packageID) {
347 $package = new Package($packageID);
348 $packageDir = FileUtil::addTrailingSlash(FileUtil::getRealPath(WCF_DIR.$package->packageDir));
349 $file = new File($packageDir.PackageInstallationDispatcher::CONFIG_FILE);
350 $file->write("<?php\n");
351 $prefix = strtoupper(self::getAbbreviation($package->package));
352
353 $file->write("// ".$package->package." (packageID ".$package->packageID.")\n");
354 $file->write("if (!defined('".$prefix."_DIR')) define('".$prefix."_DIR', dirname(__FILE__).'/');\n");
355 $file->write("if (!defined('RELATIVE_".$prefix."_DIR')) define('RELATIVE_".$prefix."_DIR', '');\n");
356 $file->write("\n");
357
358 // write general information
359 $file->write("// general info\n");
360 $file->write("if (!defined('RELATIVE_WCF_DIR')) define('RELATIVE_WCF_DIR', RELATIVE_".$prefix."_DIR.'".FileUtil::getRelativePath($packageDir, WCF_DIR)."');\n");
361 $file->write("if (!defined('PACKAGE_ID')) define('PACKAGE_ID', ".$packageID.");\n");
362 $file->write("if (!defined('PACKAGE_NAME')) define('PACKAGE_NAME', '".str_replace("'", "\'", $package->getName())."');\n");
363 $file->write("if (!defined('PACKAGE_VERSION')) define('PACKAGE_VERSION', '".$package->packageVersion."');\n");
364
365 // write end
366 $file->close();
367 }
368 }