[TASK] Fix system extensions composer.json type entry
[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 * Constructor
46 *
47 * @param \TYPO3\Flow\Package\PackageManager $packageManager the package manager which knows this package
48 * @param string $packageKey Key of this package
49 * @param string $packagePath Absolute path to the location of the package's composer manifest
50 * @param string $classesPath Path the classes of the package are in, relative to $packagePath. Optional, read from Composer manifest if not set.
51 * @param string $manifestPath Path the composer manifest of the package, relative to $packagePath. Optional, defaults to ''.
52 * @throws \TYPO3\Flow\Package\Exception\InvalidPackageKeyException if an invalid package key was passed
53 * @throws \TYPO3\Flow\Package\Exception\InvalidPackagePathException if an invalid package path was passed
54 * @throws \TYPO3\Flow\Package\Exception\InvalidPackageManifestException if no composer manifest file could be found
55 */
56 public function __construct(\TYPO3\Flow\Package\PackageManager $packageManager, $packageKey, $packagePath, $classesPath = NULL, $manifestPath = '') {
57 if (preg_match(self::PATTERN_MATCH_EXTENSIONKEY, $packageKey) !== 1 && preg_match(self::PATTERN_MATCH_PACKAGEKEY, $packageKey) !== 1) {
58 throw new \TYPO3\Flow\Package\Exception\InvalidPackageKeyException('"' . $packageKey . '" is not a valid package key.', 1217959510);
59 }
60 if (!(@is_dir($packagePath) || (\TYPO3\Flow\Utility\Files::is_link($packagePath) && is_dir(\TYPO3\Flow\Utility\Files::getNormalizedPath($packagePath))))) {
61 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), 1166631889);
62 }
63 if (substr($packagePath, -1, 1) !== '/') {
64 throw new \TYPO3\Flow\Package\Exception\InvalidPackagePathException(sprintf('The package path "%s" provided for package "%s" has no trailing forward slash.', $packagePath, $packageKey), 1166633720);
65 }
66 if (substr($classesPath, 1, 1) === '/') {
67 throw new \TYPO3\Flow\Package\Exception\InvalidPackagePathException(sprintf('The package classes path provided for package "%s" has a leading forward slash.', $packageKey), 1334841320);
68 }
69 if (!@file_exists($packagePath . $manifestPath . 'ext_emconf.php')) {
70 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);
71 }
72 $this->packageManager = $packageManager;
73 $this->manifestPath = $manifestPath;
74 $this->packageKey = $packageKey;
75 $this->packagePath = \TYPO3\Flow\Utility\Files::getNormalizedPath($packagePath);
76 $this->classesPath = \TYPO3\Flow\Utility\Files::getNormalizedPath(\TYPO3\Flow\Utility\Files::concatenatePaths(array($this->packagePath, self::DIRECTORY_CLASSES)));
77 try {
78 $this->getComposerManifest();
79 } catch (\TYPO3\Flow\Package\Exception\MissingPackageManifestException $exception) {
80 $this->getExtensionEmconf($packageKey, $this->packagePath);
81 }
82 if ($this->objectManagementEnabled === NULL) {
83 $this->objectManagementEnabled = FALSE;
84 }
85 }
86
87 /**
88 * @return bool
89 */
90 protected function getExtensionEmconf() {
91 $_EXTKEY = $this->packageKey;
92 $path = $this->packagePath . '/ext_emconf.php';
93 $EM_CONF = NULL;
94 if (@file_exists($path)) {
95 include $path;
96 if (is_array($EM_CONF[$_EXTKEY])) {
97 $this->extensionManagerConfiguration = $EM_CONF[$_EXTKEY];
98 $this->mapExtensionManagerConfigurationToComposerManifest();
99 }
100 }
101 return FALSE;
102 }
103
104 /**
105 *
106 */
107 protected function mapExtensionManagerConfigurationToComposerManifest() {
108 if (is_array($this->extensionManagerConfiguration)) {
109 $extensionManagerConfiguration = $this->extensionManagerConfiguration;
110 $composerManifest = $this->composerManifest = new \stdClass();
111 $composerManifest->name = $this->getPackageKey();
112 $composerManifest->type = 'typo3-cms-extension';
113 $composerManifest->description = $extensionManagerConfiguration['title'];
114 $composerManifest->version = $extensionManagerConfiguration['version'];
115 if (isset($extensionManagerConfiguration['constraints']['depends']) && is_array($extensionManagerConfiguration['constraints']['depends'])) {
116 $composerManifest->require = new \stdClass();
117 foreach ($extensionManagerConfiguration['constraints']['depends'] as $requiredPackageKey => $requiredPackageVersion) {
118 if (!empty($requiredPackageKey)) {
119 $composerManifest->require->$requiredPackageKey = $requiredPackageVersion;
120 } else {
121 // TODO: throw meaningful exception or fail silently?
122 }
123 }
124 }
125 if (isset($extensionManagerConfiguration['constraints']['conflicts']) && is_array($extensionManagerConfiguration['constraints']['conflicts'])) {
126 $composerManifest->conflict = new \stdClass();
127 foreach ($extensionManagerConfiguration['constraints']['conflicts'] as $conflictingPackageKey => $conflictingPackageVersion) {
128 if (!empty($conflictingPackageKey)) {
129 $composerManifest->conflict->$conflictingPackageKey = $conflictingPackageVersion;
130 } else {
131 // TODO: throw meaningful exception or fail silently?
132 }
133 }
134 }
135 }
136 }
137
138 /**
139 * @return array
140 */
141 public function getPackageReplacementKeys() {
142 return $this->getComposerManifest('replace') ?: array();
143 }
144
145 /**
146 * Returns the PHP namespace of classes in this package.
147 *
148 * @return string
149 * @api
150 */
151 public function getNamespace() {
152 if(!$this->namespace) {
153 $manifest = $this->getComposerManifest();
154 if (isset($manifest->autoload->{'psr-0'})) {
155 $namespaces = $manifest->autoload->{'psr-0'};
156 if (count($namespaces) === 1) {
157 $this->namespace = key($namespaces);
158 } else {
159 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), 1348053245);
160 }
161 } else {
162 $packageKey = $this->getPackageKey();
163 if (strpos($packageKey, '.') === FALSE) {
164 // Old school with unknown vendor name
165 $this->namespace = '*\\' . \TYPO3\CMS\Core\Utility\GeneralUtility::underscoredToUpperCamelCase($packageKey);
166 } else {
167 $this->namespace = str_replace('.', '\\', $packageKey);
168 }
169 }
170 }
171 return $this->namespace;
172 }
173
174 /**
175 * @return array
176 */
177 public function getClassFiles() {
178 if (!is_array($this->classFiles)) {
179 $this->classFiles = $this->filterClassFiles($this->buildArrayOfClassFiles($this->classesPath . '/', $this->namespace . '\\'));
180 }
181 return $this->classFiles;
182 }
183
184 /**
185 * @param array $classFiles
186 * @return array
187 */
188 protected function filterClassFiles(array $classFiles) {
189 $classesNotMatchingClassRule = array_filter(array_keys($classFiles), function($className) {
190 return preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\\\x7f-\xff]*$/', $className) !== 1;
191 });
192 foreach ($classesNotMatchingClassRule as $forbiddenClassName) {
193 unset($classFiles[$forbiddenClassName]);
194 }
195 foreach ($this->ignoredClassNames as $ignoredClassName) {
196 if (isset($classFiles[$ignoredClassName])) {
197 unset($classFiles[$ignoredClassName]);
198 }
199 }
200 return $classFiles;
201 }
202
203 /**
204 * @return array
205 */
206 public function getClassFilesFromAutoloadRegistry() {
207 $autoloadRegistryPath = $this->packagePath . 'ext_autoload.php';
208 if (@file_exists($autoloadRegistryPath)) {
209 return require $autoloadRegistryPath;
210 }
211 return array();
212 }
213
214 /**
215 *
216 */
217 public function getClassAliases() {
218 if (!is_array($this->classAliases)) {
219 try {
220 $extensionClassAliasMapPathAndFilename = \TYPO3\Flow\Utility\Files::concatenatePaths(array(
221 $this->getPackagePath(),
222 'Migrations/Code/ClassAliasMap.php'
223 ));
224 if (@file_exists($extensionClassAliasMapPathAndFilename)) {
225 $this->classAliases = require $extensionClassAliasMapPathAndFilename;
226 }
227 } catch (\BadFunctionCallException $e) {
228 }
229 if (!is_array($this->classAliases)) {
230 $this->classAliases = array();
231 }
232 }
233 return $this->classAliases;
234 }
235
236 /**
237 * Check whether the given package requirement (like "typo3/flow" or "php") is a composer package or not
238 *
239 * @param string $requirement the composer requirement string
240 * @return boolean TRUE if $requirement is a composer package (contains a slash), FALSE otherwise
241 */
242 protected function packageRequirementIsComposerPackage($requirement) {
243 //@TODO remove this workaround once extensionmanager can handle composer packages natively
244 if ($requirement === 'composer/installers') {
245 return FALSE;
246 }
247 return parent::packageRequirementIsComposerPackage($requirement);
248 }
249
250
251 }