[BUGFIX] Only valid package names will be processed.
[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 if ($this->objectManagementEnabled === NULL) {
99 $this->objectManagementEnabled = FALSE;
100 }
101 }
102
103 /**
104 * @return bool
105 */
106 public function isPartOfFactoryDefault() {
107 return $this->partOfFactoryDefault;
108 }
109
110 /**
111 * @return boolean
112 */
113 public function isPartOfMinimalUsableSystem() {
114 return $this->partOfMinimalUsableSystem;
115 }
116
117 /**
118 * @return bool
119 */
120 protected function getExtensionEmconf() {
121 $_EXTKEY = $this->packageKey;
122 $path = $this->packagePath . '/ext_emconf.php';
123 $EM_CONF = NULL;
124 if (@file_exists($path)) {
125 include $path;
126 if (is_array($EM_CONF[$_EXTKEY])) {
127 $this->extensionManagerConfiguration = $EM_CONF[$_EXTKEY];
128 $this->mapExtensionManagerConfigurationToComposerManifest();
129 }
130 }
131 return FALSE;
132 }
133
134 /**
135 *
136 */
137 protected function mapExtensionManagerConfigurationToComposerManifest() {
138 if (is_array($this->extensionManagerConfiguration)) {
139 $extensionManagerConfiguration = $this->extensionManagerConfiguration;
140 $composerManifest = $this->composerManifest = new \stdClass();
141 $composerManifest->name = $this->getPackageKey();
142 $composerManifest->type = 'typo3-cms-extension';
143 $composerManifest->description = $extensionManagerConfiguration['title'];
144 $composerManifest->version = $extensionManagerConfiguration['version'];
145 if (isset($extensionManagerConfiguration['constraints']['depends']) && is_array($extensionManagerConfiguration['constraints']['depends'])) {
146 $composerManifest->require = new \stdClass();
147 foreach ($extensionManagerConfiguration['constraints']['depends'] as $requiredPackageKey => $requiredPackageVersion) {
148 if (!empty($requiredPackageKey)) {
149 $composerManifest->require->$requiredPackageKey = $requiredPackageVersion;
150 } else {
151 // TODO: throw meaningful exception or fail silently?
152 }
153 }
154 }
155 if (isset($extensionManagerConfiguration['constraints']['conflicts']) && is_array($extensionManagerConfiguration['constraints']['conflicts'])) {
156 $composerManifest->conflict = new \stdClass();
157 foreach ($extensionManagerConfiguration['constraints']['conflicts'] as $conflictingPackageKey => $conflictingPackageVersion) {
158 if (!empty($conflictingPackageKey)) {
159 $composerManifest->conflict->$conflictingPackageKey = $conflictingPackageVersion;
160 } else {
161 // TODO: throw meaningful exception or fail silently?
162 }
163 }
164 }
165 }
166 }
167
168 /**
169 * @return array
170 */
171 public function getPackageReplacementKeys() {
172 // The cast to array is required since the manifest returns data with type mixed
173 return (array)$this->getComposerManifest('replace') ?: array();
174 }
175
176 /**
177 * Returns the PHP namespace of classes in this package.
178 *
179 * @return string
180 * @api
181 */
182 public function getNamespace() {
183 if(!$this->namespace) {
184 $manifest = $this->getComposerManifest();
185 if (isset($manifest->autoload->{'psr-0'})) {
186 $namespaces = $manifest->autoload->{'psr-0'};
187 if (count($namespaces) === 1) {
188 $this->namespace = key($namespaces);
189 } else {
190 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);
191 }
192 } else {
193 $packageKey = $this->getPackageKey();
194 if (strpos($packageKey, '.') === FALSE) {
195 // Old school with unknown vendor name
196 $this->namespace = '*\\' . \TYPO3\CMS\Core\Utility\GeneralUtility::underscoredToUpperCamelCase($packageKey);
197 } else {
198 $this->namespace = str_replace('.', '\\', $packageKey);
199 }
200 }
201 }
202 return $this->namespace;
203 }
204
205 /**
206 * @return array
207 */
208 public function getClassFiles() {
209 if (!is_array($this->classFiles)) {
210 $this->classFiles = $this->filterClassFiles($this->buildArrayOfClassFiles($this->classesPath . '/', $this->namespace . '\\'));
211 }
212 return $this->classFiles;
213 }
214
215 /**
216 * @param array $classFiles
217 * @return array
218 */
219 protected function filterClassFiles(array $classFiles) {
220 $classesNotMatchingClassRule = array_filter(array_keys($classFiles), function($className) {
221 return preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\\\x7f-\xff]*$/', $className) !== 1;
222 });
223 foreach ($classesNotMatchingClassRule as $forbiddenClassName) {
224 unset($classFiles[$forbiddenClassName]);
225 }
226 foreach ($this->ignoredClassNames as $ignoredClassName) {
227 if (isset($classFiles[$ignoredClassName])) {
228 unset($classFiles[$ignoredClassName]);
229 }
230 }
231 return $classFiles;
232 }
233
234 /**
235 * @return array
236 */
237 public function getClassFilesFromAutoloadRegistry() {
238 $autoloadRegistryPath = $this->packagePath . 'ext_autoload.php';
239 if (@file_exists($autoloadRegistryPath)) {
240 return require $autoloadRegistryPath;
241 }
242 return array();
243 }
244
245 /**
246 *
247 */
248 public function getClassAliases() {
249 if (!is_array($this->classAliases)) {
250 try {
251 $extensionClassAliasMapPathAndFilename = \TYPO3\Flow\Utility\Files::concatenatePaths(array(
252 $this->getPackagePath(),
253 'Migrations/Code/ClassAliasMap.php'
254 ));
255 if (@file_exists($extensionClassAliasMapPathAndFilename)) {
256 $this->classAliases = require $extensionClassAliasMapPathAndFilename;
257 }
258 } catch (\BadFunctionCallException $e) {
259 }
260 if (!is_array($this->classAliases)) {
261 $this->classAliases = array();
262 }
263 }
264 return $this->classAliases;
265 }
266
267 /**
268 * Check whether the given package requirement (like "typo3/flow" or "php") is a composer package or not
269 *
270 * @param string $requirement the composer requirement string
271 * @return boolean TRUE if $requirement is a composer package (contains a slash), FALSE otherwise
272 */
273 protected function packageRequirementIsComposerPackage($requirement) {
274 // According to http://getcomposer.org/doc/02-libraries.md#platform-packages
275 // the following regex should capture all non composer requirements.
276 // typo3 is included in the list because it's a meta package and not supported for now.
277 // composer/installers is included until extensionmanager can handle composer packages natively
278 return preg_match('/^(php(-64bit)?|ext-[^\/]+|lib-(curl|iconv|libxml|openssl|pcre|uuid|xsl)|typo3|composer\/installers)$/', $requirement) !== 1;
279 }
280
281
282 }