[BUGFIX] Avoid using hardcoded uid of repository record
[Packages/TYPO3.CMS.git] / typo3 / sysext / extensionmanager / Classes / Utility / Repository / Helper.php
1 <?php
2 namespace TYPO3\CMS\Extensionmanager\Utility\Repository;
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 use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
18 use TYPO3\CMS\Core\Core\Environment;
19 use TYPO3\CMS\Core\Database\ConnectionPool;
20 use TYPO3\CMS\Core\Utility\GeneralUtility;
21 use TYPO3\CMS\Extbase\Object\ObjectManager;
22 use TYPO3\CMS\Extensionmanager\Domain\Model\Repository;
23 use TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository;
24 use TYPO3\CMS\Extensionmanager\Domain\Repository\RepositoryRepository;
25 use TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException;
26 use TYPO3\CMS\Extensionmanager\Utility\Importer\ExtensionListUtility;
27 use TYPO3\CMS\Extensionmanager\Utility\Importer\MirrorListUtility;
28
29 /**
30 * Central utility class for repository handling.
31 * @internal This class is a specific ExtensionManager implementation and is not part of the Public TYPO3 API.
32 */
33 class Helper implements \TYPO3\CMS\Core\SingletonInterface
34 {
35 /**
36 * ##########################################
37 * Problem constants - to be used in bitmasks
38 * ##########################################
39 */
40
41 /**
42 * Type of problem: extension file not existing in file system.
43 *
44 * @var int
45 */
46 const PROBLEM_EXTENSION_FILE_NOT_EXISTING = 1;
47 /**
48 * Type of problem: wrong hash indicates outdated extension file.
49 *
50 * @var int
51 */
52 const PROBLEM_EXTENSION_HASH_CHANGED = 2;
53
54 /**
55 * Type of problem: no version records in database.
56 *
57 * @var int
58 */
59 const PROBLEM_NO_VERSIONS_IN_DATABASE = 4;
60
61 /**
62 * Keeps instance of repository class.
63 *
64 * @var Repository
65 */
66 protected $repository;
67
68 /**
69 * @var RepositoryRepository
70 */
71 protected $repositoryRepository;
72
73 /**
74 * @var ExtensionRepository
75 */
76 protected $extensionRepository;
77
78 /**
79 * @var ObjectManager
80 */
81 protected $objectManager;
82
83 public function __construct()
84 {
85 $this->objectManager = GeneralUtility::makeInstance(ObjectManager::class);
86 $repositoryRepository = $this->objectManager->get(RepositoryRepository::class);
87 $this->extensionRepository = $this->objectManager->get(ExtensionRepository::class);
88 /** @var Repository $repository */
89 $repository = $repositoryRepository->findOneTypo3OrgRepository();
90 if (is_object($repository)) {
91 $this->setRepository($repository);
92 }
93 }
94
95 /**
96 * Method registers required repository instance to work with.
97 *
98 * Repository instance is passed by reference.
99 *
100 * @param Repository $repository
101 * @see $repository
102 */
103 public function setRepository(Repository $repository)
104 {
105 $this->repository = $repository;
106 }
107
108 /**
109 * Method fetches extension list file from remote server.
110 *
111 * Delegates to {@link fetchFile()}.
112 *
113 * @throws ExtensionManagerException
114 * @see fetchFile()
115 */
116 public function fetchExtListFile()
117 {
118 $this->fetchFile($this->getRemoteExtListFile(), $this->getLocalExtListFile());
119 }
120
121 /**
122 * Method fetches mirror list file from remote server.
123 *
124 * Delegates to {@link fetchFile()}.
125 *
126 * @throws ExtensionManagerException
127 * @see fetchFile()
128 */
129 public function fetchMirrorListFile()
130 {
131 $this->fetchFile($this->getRemoteMirrorListFile(), $this->getLocalMirrorListFile());
132 }
133
134 /**
135 * Method fetches contents from remote server and
136 * writes them into a file in the local file system.
137 *
138 * @param string $remoteResource remote resource to read contents from
139 * @param string $localResource local resource (absolute file path) to store retrieved contents to (must be within typo3temp/)
140 * @see \TYPO3\CMS\Core\Utility\GeneralUtility::getUrl(), \TYPO3\CMS\Core\Utility\GeneralUtility::writeFile()
141 * @throws ExtensionManagerException
142 */
143 protected function fetchFile($remoteResource, $localResource)
144 {
145 $isOffline = (bool)GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('extensionmanager', 'offlineMode');
146 if ($isOffline) {
147 throw new ExtensionManagerException('Extension Manager is in offline mode. No TER connection available.', 1437078780);
148 }
149 if (is_string($remoteResource) && is_string($localResource) && !empty($remoteResource) && !empty($localResource)) {
150 $fileContent = GeneralUtility::getUrl($remoteResource);
151 if ($fileContent !== false) {
152 if (GeneralUtility::writeFileToTypo3tempDir($localResource, $fileContent) !== null) {
153 throw new ExtensionManagerException(sprintf('Could not write to file %s.', $localResource), 1342635378);
154 }
155 } else {
156 throw new ExtensionManagerException(sprintf('Could not access remote resource %s.', $remoteResource), 1342635425);
157 }
158 }
159 }
160
161 /**
162 * Method returns location of local extension list file.
163 *
164 * @return string local location of file
165 * @see getRemoteExtListFile()
166 */
167 public function getLocalExtListFile()
168 {
169 return Environment::getVarPath() . '/extensionmanager/' . (int)$this->repository->getUid() . '.extensions.xml.gz';
170 }
171
172 /**
173 * Method returns location of remote extension list file.
174 *
175 * @return string remote location of file
176 * @see getLocalExtListFile()
177 */
178 public function getRemoteExtListFile()
179 {
180 $mirror = $this->getMirrors(true)->getMirror();
181 $filePath = 'https://' . $mirror['host'] . $mirror['path'] . 'extensions.xml.gz';
182 return $filePath;
183 }
184
185 /**
186 * Method returns location of remote file containing
187 * the extension checksum hash.
188 *
189 * @return string remote location of file
190 */
191 public function getRemoteExtHashFile()
192 {
193 $mirror = $this->getMirrors(true)->getMirror();
194 $filePath = 'https://' . $mirror['host'] . $mirror['path'] . 'extensions.md5';
195 return $filePath;
196 }
197
198 /**
199 * Method returns location of local mirror list file.
200 *
201 * @return string local location of file
202 * @see getRemoteMirrorListFile()
203 */
204 public function getLocalMirrorListFile()
205 {
206 return Environment::getVarPath() . '/extensionmanager/' . (int)$this->repository->getUid() . '.mirrors.xml.gz';
207 }
208
209 /**
210 * Method returns location of remote mirror list file.
211 *
212 * @return string remote location of file
213 * @see getLocalMirrorListFile()
214 */
215 public function getRemoteMirrorListFile()
216 {
217 $filePath = $this->repository->getMirrorListUrl();
218 return $filePath;
219 }
220
221 /**
222 * Method returns available mirrors for registered repository.
223 *
224 * If there are no mirrors registered to the repository,
225 * the method will retrieve them from file system or remote
226 * server.
227 *
228 * @param bool $forcedUpdateFromRemote if boolean TRUE, mirror configuration will always retrieved from remote server
229 * @return \TYPO3\CMS\Extensionmanager\Domain\Model\Mirrors instance of repository mirrors class
230 * @throws ExtensionManagerException
231 */
232 public function getMirrors($forcedUpdateFromRemote = true)
233 {
234 $assignedMirror = $this->repository->getMirrors();
235 if ($forcedUpdateFromRemote || $assignedMirror === null || !is_object($assignedMirror)) {
236 if ($forcedUpdateFromRemote || !is_file($this->getLocalMirrorListFile())) {
237 $this->fetchMirrorListFile();
238 }
239 $objMirrorListImporter = GeneralUtility::makeInstance(MirrorListUtility::class);
240 $this->repository->addMirrors($objMirrorListImporter->getMirrors($this->getLocalMirrorListFile()));
241 }
242 return $this->repository->getMirrors();
243 }
244
245 /**
246 * Method returns information if currently available
247 * extension list might be outdated.
248 *
249 * @see \TYPO3\CMS\Extensionmanager\Utility\Repository\Helper::PROBLEM_NO_VERSIONS_IN_DATABASE,
250 * @throws ExtensionManagerException
251 * @return int "0" if everything is perfect, otherwise bitmask with problems
252 */
253 public function isExtListUpdateNecessary()
254 {
255 if ($this->repository === null) {
256 throw new ExtensionManagerException('No extension repository was found.', 1500060252);
257 }
258 $updateNecessity = 0;
259 if ($this->extensionRepository->countByRepository($this->repository->getUid()) <= 0) {
260 $updateNecessity |= self::PROBLEM_NO_VERSIONS_IN_DATABASE;
261 }
262 if (!is_file($this->getLocalExtListFile())) {
263 $updateNecessity |= self::PROBLEM_EXTENSION_FILE_NOT_EXISTING;
264 } else {
265 $remotemd5 = GeneralUtility::getUrl($this->getRemoteExtHashFile());
266 if ($remotemd5 !== false) {
267 $localmd5 = md5_file($this->getLocalExtListFile());
268 if ($remotemd5 !== $localmd5) {
269 $updateNecessity |= self::PROBLEM_EXTENSION_HASH_CHANGED;
270 }
271 } else {
272 throw new ExtensionManagerException('Could not retrieve extension hash file from remote server.', 1342635016);
273 }
274 }
275 return $updateNecessity;
276 }
277
278 /**
279 * Method updates TYPO3 database with up-to-date
280 * extension version records.
281 *
282 * @return bool TRUE if the extension list was successfully update, FALSE if no update necessary
283 * @throws ExtensionManagerException
284 * @see isExtListUpdateNecessary()
285 */
286 public function updateExtList()
287 {
288 $updated = false;
289 $updateNecessity = $this->isExtListUpdateNecessary();
290 if ($updateNecessity !== 0) {
291 // retrieval of file necessary
292 $tmpBitmask = self::PROBLEM_EXTENSION_FILE_NOT_EXISTING | self::PROBLEM_EXTENSION_HASH_CHANGED;
293 if (($tmpBitmask & $updateNecessity) > 0) {
294 $this->fetchExtListFile();
295 $updateNecessity &= ~$tmpBitmask;
296 }
297 // database table cleanup
298 if ($updateNecessity & self::PROBLEM_NO_VERSIONS_IN_DATABASE) {
299 $updateNecessity &= ~self::PROBLEM_NO_VERSIONS_IN_DATABASE;
300 } else {
301 // Use straight query as extbase "remove" is too slow here
302 // This truncates the whole table. It would be more correct to remove only rows of a specific
303 // repository, but multiple repository handling is not implemented, and truncate is quicker.
304 GeneralUtility::makeInstance(ConnectionPool::class)
305 ->getConnectionForTable('tx_extensionmanager_domain_model_extension')
306 ->truncate('tx_extensionmanager_domain_model_extension');
307 }
308 // no further problems - start of import process
309 if ($updateNecessity === 0) {
310 $uid = $this->repository->getUid();
311 $objExtListImporter = $this->objectManager->get(ExtensionListUtility::class);
312 $objExtListImporter->import($this->getLocalExtListFile(), $uid);
313 $updated = true;
314 }
315 }
316 return $updated;
317 }
318 }