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