5b08186490acd8e927c7b649f686722b20603e05
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Package / Package.php
1 <?php
2 namespace TYPO3\CMS\Core\Package;
3
4 /* *
5 * This script belongs to the TYPO3 Flow framework. *
6 * *
7 * It is free software; you can redistribute it and/or modify it under *
8 * the terms of the GNU Lesser General Public License, either version 3 *
9 * of the License, or (at your option) any later version. *
10 * *
11 * The TYPO3 project - inspiring people to share! *
12 * */
13
14 /**
15 * A Package
16 * Adapted from FLOW for TYPO3 CMS
17 *
18 * @api
19 */
20 class Package extends \TYPO3\Flow\Package\Package implements PackageInterface {
21
22 const PATTERN_MATCH_EXTENSIONKEY = '/^[0-9a-z_-]+$/i';
23
24 /**
25 * @var array
26 */
27 protected $extensionManagerConfiguration = array();
28
29 /**
30 * @var array
31 */
32 protected $classAliases;
33
34 /**
35 * @var bool
36 */
37 protected $objectManagementEnabled = NULL;
38
39 /**
40 * @var array
41 */
42 protected $ignoredClassNames = array();
43
44 /**
45 * If this package is part of factory default, it will be activated
46 * during first installation.
47 *
48 * @var bool
49 */
50 protected $partOfFactoryDefault = FALSE;
51
52 /**
53 * If this package is part of minimal usable system, it will be
54 * activated if PackageStates is created from scratch.
55 *
56 * @var bool
57 */
58 protected $partOfMinimalUsableSystem = FALSE;
59
60 /**
61 * Constructor
62 *
63 * @param \TYPO3\Flow\Package\PackageManager $packageManager the package manager which knows this package
64 * @param string $packageKey Key of this package
65 * @param string $packagePath Absolute path to the location of the package's composer manifest
66 * @param string $classesPath Path the classes of the package are in, relative to $packagePath. Optional, read from Composer manifest if not set.
67 * @param string $manifestPath Path the composer manifest of the package, relative to $packagePath. Optional, defaults to ''.
68 * @throws \TYPO3\Flow\Package\Exception\InvalidPackageKeyException if an invalid package key was passed
69 * @throws \TYPO3\Flow\Package\Exception\InvalidPackagePathException if an invalid package path was passed
70 * @throws \TYPO3\Flow\Package\Exception\InvalidPackageManifestException if no composer manifest file could be found
71 */
72 public function __construct(\TYPO3\Flow\Package\PackageManager $packageManager, $packageKey, $packagePath, $classesPath = NULL, $manifestPath = '') {
73 if (!\TYPO3\CMS\Core\Package\PackageManager::isPackageKeyValid($packageKey)) {
74 throw new \TYPO3\Flow\Package\Exception\InvalidPackageKeyException('"' . $packageKey . '" is not a valid package key.', 1217959511);
75 }
76 if (!(@is_dir($packagePath) || (\TYPO3\Flow\Utility\Files::is_link($packagePath) && is_dir(\TYPO3\Flow\Utility\Files::getNormalizedPath($packagePath))))) {
77 throw new \TYPO3\Flow\Package\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);
78 }
79 if (substr($packagePath, -1, 1) !== '/') {
80 throw new \TYPO3\Flow\Package\Exception\InvalidPackagePathException(sprintf('The package path "%s" provided for package "%s" has no trailing forward slash.', $packagePath, $packageKey), 1166633722);
81 }
82 if ($classesPath[1] === '/') {
83 throw new \TYPO3\Flow\Package\Exception\InvalidPackagePathException(sprintf('The package classes path provided for package "%s" has a leading forward slash.', $packageKey), 1334841321);
84 }
85 if (!@file_exists($packagePath . $manifestPath . 'ext_emconf.php')) {
86 throw new \TYPO3\Flow\Package\Exception\InvalidPackageManifestException(sprintf('No ext_emconf file found for package "%s". Please create one at "%sext_emconf.php".', $packageKey, $manifestPath), 1360403545);
87 }
88 $this->packageManager = $packageManager;
89 $this->manifestPath = $manifestPath;
90 $this->packageKey = $packageKey;
91 $this->packagePath = \TYPO3\Flow\Utility\Files::getNormalizedPath($packagePath);
92 $this->classesPath = \TYPO3\Flow\Utility\Files::getNormalizedPath(\TYPO3\Flow\Utility\Files::concatenatePaths(array($this->packagePath, self::DIRECTORY_CLASSES)));
93 try {
94 $this->getComposerManifest();
95 } catch (\TYPO3\Flow\Package\Exception\MissingPackageManifestException $exception) {
96 $this->getExtensionEmconf($packageKey, $this->packagePath);
97 }
98 $this->loadFlagsFromComposerManifest();
99 if ($this->objectManagementEnabled === NULL) {
100 $this->objectManagementEnabled = FALSE;
101 }
102 }
103
104 /**
105 * Loads package management related flags from the "extra:typo3/cms:Package" section
106 * of extensions composer.json files into local properties
107 *
108 * @return void
109 */
110 protected function loadFlagsFromComposerManifest() {
111 $extraFlags = $this->getComposerManifest('extra');
112 if ($extraFlags !== NULL && isset($extraFlags->{"typo3/cms"}->{"Package"})) {
113 foreach ($extraFlags->{"typo3/cms"}->{"Package"} as $flagName => $flagValue) {
114 if (property_exists($this, $flagName)) {
115 $this->{$flagName} = $flagValue;
116 }
117 }
118 }
119 }
120
121 /**
122 * @return bool
123 */
124 public function isPartOfFactoryDefault() {
125 return $this->partOfFactoryDefault;
126 }
127
128 /**
129 * @return boolean
130 */
131 public function isPartOfMinimalUsableSystem() {
132 return $this->partOfMinimalUsableSystem;
133 }
134
135 /**
136 * @return bool
137 */
138 protected function getExtensionEmconf() {
139 $_EXTKEY = $this->packageKey;
140 $path = $this->packagePath . '/ext_emconf.php';
141 $EM_CONF = NULL;
142 if (@file_exists($path)) {
143 include $path;
144 if (is_array($EM_CONF[$_EXTKEY])) {
145 $this->extensionManagerConfiguration = $EM_CONF[$_EXTKEY];
146 $this->mapExtensionManagerConfigurationToComposerManifest();
147 }
148 }
149 return FALSE;
150 }
151
152 /**
153 *
154 */
155 protected function mapExtensionManagerConfigurationToComposerManifest() {
156 if (is_array($this->extensionManagerConfiguration)) {
157 $extensionManagerConfiguration = $this->extensionManagerConfiguration;
158 $composerManifest = $this->composerManifest = new \stdClass();
159 $composerManifest->name = $this->getPackageKey();
160 $composerManifest->type = 'typo3-cms-extension';
161 $composerManifest->description = $extensionManagerConfiguration['title'];
162 $composerManifest->version = $extensionManagerConfiguration['version'];
163 if (isset($extensionManagerConfiguration['constraints']['depends']) && is_array($extensionManagerConfiguration['constraints']['depends'])) {
164 $composerManifest->require = new \stdClass();
165 foreach ($extensionManagerConfiguration['constraints']['depends'] as $requiredPackageKey => $requiredPackageVersion) {
166 if (!empty($requiredPackageKey)) {
167 $composerManifest->require->$requiredPackageKey = $requiredPackageVersion;
168 } else {
169 // TODO: throw meaningful exception or fail silently?
170 }
171 }
172 }
173 if (isset($extensionManagerConfiguration['constraints']['conflicts']) && is_array($extensionManagerConfiguration['constraints']['conflicts'])) {
174 $composerManifest->conflict = new \stdClass();
175 foreach ($extensionManagerConfiguration['constraints']['conflicts'] as $conflictingPackageKey => $conflictingPackageVersion) {
176 if (!empty($conflictingPackageKey)) {
177 $composerManifest->conflict->$conflictingPackageKey = $conflictingPackageVersion;
178 } else {
179 // TODO: throw meaningful exception or fail silently?
180 }
181 }
182 }
183 }
184 }
185
186 /**
187 * @return array
188 */
189 public function getPackageReplacementKeys() {
190 // The cast to array is required since the manifest returns data with type mixed
191 return (array)$this->getComposerManifest('replace') ?: array();
192 }
193
194 /**
195 * Returns the PHP namespace of classes in this package.
196 *
197 * @return string
198 * @api
199 */
200 public function getNamespace() {
201 if(!$this->namespace) {
202 $manifest = $this->getComposerManifest();
203 if (isset($manifest->autoload->{'psr-0'})) {
204 $namespaces = $manifest->autoload->{'psr-0'};
205 if (count($namespaces) === 1) {
206 $this->namespace = key($namespaces);
207 } else {
208 throw new \TYPO3\Flow\Package\Exception\InvalidPackageStateException(sprintf('The Composer manifest of package "%s" contains multiple namespace definitions in its autoload section but Flow does only support one namespace per package.', $this->packageKey), 1348053246);
209 }
210 } else {
211 $packageKey = $this->getPackageKey();
212 if (strpos($packageKey, '.') === FALSE) {
213 // Old school with unknown vendor name
214 $this->namespace = '*\\' . \TYPO3\CMS\Core\Utility\GeneralUtility::underscoredToUpperCamelCase($packageKey);
215 } else {
216 $this->namespace = str_replace('.', '\\', $packageKey);
217 }
218 }
219 }
220 return $this->namespace;
221 }
222
223 /**
224 * @return array
225 */
226 public function getClassFiles() {
227 if (!is_array($this->classFiles)) {
228 $this->classFiles = $this->filterClassFiles($this->buildArrayOfClassFiles($this->classesPath . '/', $this->namespace . '\\'));
229 }
230 return $this->classFiles;
231 }
232
233 /**
234 * @param array $classFiles
235 * @return array
236 */
237 protected function filterClassFiles(array $classFiles) {
238 $classesNotMatchingClassRule = array_filter(array_keys($classFiles), function($className) {
239 return preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\\\x7f-\xff]*$/', $className) !== 1;
240 });
241 foreach ($classesNotMatchingClassRule as $forbiddenClassName) {
242 unset($classFiles[$forbiddenClassName]);
243 }
244 foreach ($this->ignoredClassNames as $ignoredClassName) {
245 if (isset($classFiles[$ignoredClassName])) {
246 unset($classFiles[$ignoredClassName]);
247 }
248 }
249 return $classFiles;
250 }
251
252 /**
253 * @return array
254 */
255 public function getClassFilesFromAutoloadRegistry() {
256 $autoloadRegistryPath = $this->packagePath . 'ext_autoload.php';
257 if (@file_exists($autoloadRegistryPath)) {
258 return require $autoloadRegistryPath;
259 }
260 return array();
261 }
262
263 /**
264 *
265 */
266 public function getClassAliases() {
267 if (!is_array($this->classAliases)) {
268 try {
269 $extensionClassAliasMapPathAndFilename = \TYPO3\Flow\Utility\Files::concatenatePaths(array(
270 $this->getPackagePath(),
271 'Migrations/Code/ClassAliasMap.php'
272 ));
273 if (@file_exists($extensionClassAliasMapPathAndFilename)) {
274 $this->classAliases = require $extensionClassAliasMapPathAndFilename;
275 }
276 } catch (\BadFunctionCallException $e) {
277 }
278 if (!is_array($this->classAliases)) {
279 $this->classAliases = array();
280 }
281 }
282 return $this->classAliases;
283 }
284
285 /**
286 * Check whether the given package requirement (like "typo3/flow" or "php") is a composer package or not
287 *
288 * @param string $requirement the composer requirement string
289 * @return boolean TRUE if $requirement is a composer package (contains a slash), FALSE otherwise
290 */
291 protected function packageRequirementIsComposerPackage($requirement) {
292 // According to http://getcomposer.org/doc/02-libraries.md#platform-packages
293 // the following regex should capture all non composer requirements.
294 // typo3 is included in the list because it's a meta package and not supported for now.
295 // composer/installers is included until extensionmanager can handle composer packages natively
296 return preg_match('/^(php(-64bit)?|ext-[^\/]+|lib-(curl|iconv|libxml|openssl|pcre|uuid|xsl)|typo3|composer\/installers)$/', $requirement) !== 1;
297 }
298
299
300 }