[TASK] Simplify PackageManager caching
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Package / Package.php
1 <?php
2 namespace TYPO3\CMS\Core\Package;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 /**
18 * A Package representing the details of an extension and/or a composer package
19 */
20 class Package implements PackageInterface
21 {
22 /**
23 * @var array
24 */
25 protected $extensionManagerConfiguration = [];
26
27 /**
28 * If this package is part of factory default, it will be activated
29 * during first installation.
30 *
31 * @var bool
32 */
33 protected $partOfFactoryDefault = false;
34
35 /**
36 * If this package is part of minimal usable system, it will be
37 * activated if PackageStates is created from scratch.
38 *
39 * @var bool
40 */
41 protected $partOfMinimalUsableSystem = false;
42
43 /**
44 * Unique key of this package.
45 * @var string
46 */
47 protected $packageKey;
48
49 /**
50 * Full path to this package's main directory
51 * @var string
52 */
53 protected $packagePath;
54
55 /**
56 * If this package is protected and therefore cannot be deactivated or deleted
57 * @var bool
58 */
59 protected $protected = false;
60
61 /**
62 * @var \stdClass
63 */
64 protected $composerManifest;
65
66 /**
67 * Meta information about this package
68 * @var MetaData
69 */
70 protected $packageMetaData;
71
72 /**
73 * Constructor
74 *
75 * @param PackageManager $packageManager the package manager which knows this package
76 * @param string $packageKey Key of this package
77 * @param string $packagePath Absolute path to the location of the package's composer manifest
78 * @throws Exception\InvalidPackageKeyException if an invalid package key was passed
79 * @throws Exception\InvalidPackagePathException if an invalid package path was passed
80 * @throws Exception\InvalidPackageManifestException if no composer manifest file could be found
81 */
82 public function __construct(PackageManager $packageManager, $packageKey, $packagePath)
83 {
84 if (!$packageManager->isPackageKeyValid($packageKey)) {
85 throw new Exception\InvalidPackageKeyException('"' . $packageKey . '" is not a valid package key.', 1217959511);
86 }
87 if (!(@is_dir($packagePath) || (is_link($packagePath) && is_dir($packagePath)))) {
88 throw new Exception\InvalidPackagePathException(sprintf('Tried to instantiate a package object for package "%s" with a non-existing package path "%s". Either the package does not exist anymore, or the code creating this object contains an error.', $packageKey, $packagePath), 1166631890);
89 }
90 if (substr($packagePath, -1, 1) !== '/') {
91 throw new Exception\InvalidPackagePathException(sprintf('The package path "%s" provided for package "%s" has no trailing forward slash.', $packagePath, $packageKey), 1166633722);
92 }
93 $this->packageKey = $packageKey;
94 $this->packagePath = $packagePath;
95 $this->composerManifest = $packageManager->getComposerManifest($this->packagePath);
96 $this->loadFlagsFromComposerManifest();
97 $this->createPackageMetadata($packageManager);
98 }
99
100 /**
101 * Loads package management related flags from the "extra:typo3/cms:Package" section
102 * of extensions composer.json files into local properties
103 */
104 protected function loadFlagsFromComposerManifest()
105 {
106 $extraFlags = $this->getValueFromComposerManifest('extra');
107 if ($extraFlags !== null && isset($extraFlags->{'typo3/cms'}->{'Package'})) {
108 foreach ($extraFlags->{'typo3/cms'}->{'Package'} as $flagName => $flagValue) {
109 if (property_exists($this, $flagName)) {
110 $this->{$flagName} = $flagValue;
111 }
112 }
113 }
114 }
115
116 /**
117 * Creates the package meta data object of this package.
118 *
119 * @param PackageManager $packageManager
120 */
121 protected function createPackageMetaData(PackageManager $packageManager)
122 {
123 $this->packageMetaData = new MetaData($this->getPackageKey());
124 $this->packageMetaData->setDescription($this->getValueFromComposerManifest('description'));
125 $this->packageMetaData->setVersion($this->getValueFromComposerManifest('version'));
126 $requirements = $this->getValueFromComposerManifest('require');
127 if ($requirements !== null) {
128 foreach ($requirements as $requirement => $version) {
129 $packageKey = $packageManager->getPackageKeyFromComposerName($requirement);
130 // dynamically migrate 'cms' dependency to 'core' dependency
131 // see also \TYPO3\CMS\Extensionmanager\Utility\ExtensionModelUtility::convertDependenciesToObjects
132 if ($packageKey === 'cms') {
133 trigger_error('Extension "' . $this->packageKey . '" defines a dependency on ext:cms, which has been removed. Please remove the dependency.', E_USER_DEPRECATED);
134 $packageKey = 'core';
135 }
136 $constraint = new MetaData\PackageConstraint(MetaData::CONSTRAINT_TYPE_DEPENDS, $packageKey);
137 $this->packageMetaData->addConstraint($constraint);
138 }
139 }
140 $suggestions = $this->getValueFromComposerManifest('suggest');
141 if ($suggestions !== null) {
142 foreach ($suggestions as $suggestion => $version) {
143 $packageKey = $packageManager->getPackageKeyFromComposerName($suggestion);
144 $constraint = new MetaData\PackageConstraint(MetaData::CONSTRAINT_TYPE_SUGGESTS, $packageKey);
145 $this->packageMetaData->addConstraint($constraint);
146 }
147 }
148 }
149
150 /**
151 * @return bool
152 */
153 public function isPartOfFactoryDefault()
154 {
155 return $this->partOfFactoryDefault;
156 }
157
158 /**
159 * @return bool
160 */
161 public function isPartOfMinimalUsableSystem()
162 {
163 return $this->partOfMinimalUsableSystem;
164 }
165
166 /**
167 * Returns the package key of this package.
168 *
169 * @return string
170 * @api
171 */
172 public function getPackageKey()
173 {
174 return $this->packageKey;
175 }
176
177 /**
178 * Tells if this package is protected and therefore cannot be deactivated or deleted
179 *
180 * @return bool
181 * @api
182 */
183 public function isProtected()
184 {
185 return $this->protected;
186 }
187
188 /**
189 * Sets the protection flag of the package
190 *
191 * @param bool $protected TRUE if the package should be protected, otherwise FALSE
192 * @api
193 */
194 public function setProtected($protected)
195 {
196 $this->protected = (bool)$protected;
197 }
198
199 /**
200 * Returns the full path to this package's main directory
201 *
202 * @return string Path to this package's main directory
203 * @api
204 */
205 public function getPackagePath()
206 {
207 return $this->packagePath;
208 }
209
210 /**
211 * Returns the package meta data object of this package.
212 *
213 * @return MetaData
214 */
215 public function getPackageMetaData()
216 {
217 return $this->packageMetaData;
218 }
219
220 /**
221 * Returns an array of packages this package replaces
222 *
223 * @return array
224 */
225 public function getPackageReplacementKeys()
226 {
227 // The cast to array is required since the manifest returns data with type mixed
228 return (array)$this->getValueFromComposerManifest('replace') ?: [];
229 }
230
231 /**
232 * Returns contents of Composer manifest - or part there of if a key is given.
233 *
234 * @param string $key Optional. Only return the part of the manifest indexed by 'key'
235 * @return mixed|null
236 * @see json_decode for return values
237 */
238 public function getValueFromComposerManifest($key = null)
239 {
240 if ($key === null) {
241 return $this->composerManifest;
242 }
243
244 if (isset($this->composerManifest->{$key})) {
245 $value = $this->composerManifest->{$key};
246 } else {
247 $value = null;
248 }
249 return $value;
250 }
251 }