[TASK] Add upgrade wizard to install extensions from TER
[Packages/TYPO3.CMS.git] / typo3 / sysext / install / Classes / CoreUpdates / InstallSysExtsUpdate.php
1 <?php
2 namespace TYPO3\CMS\Install\CoreUpdates;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2008-2011 Benjamin Mack <benni@typo3.org>
8 * (c) 2008-2011 Steffen Kamper <info@sk-typo3.de>
9 * (c) 2012 Kai Vogel <kai.vogel@speedprogs.de>
10 * All rights reserved
11 *
12 * This script is part of the TYPO3 project. The TYPO3 project is
13 * free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
17 *
18 * The GNU General Public License can be found at
19 * http://www.gnu.org/copyleft/gpl.html.
20 * A copy is found in the textfile GPL.txt and important notices to the license
21 * from the author is found in LICENSE.txt distributed with these scripts.
22 *
23 *
24 * This script is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 * GNU General Public License for more details.
28 *
29 * This copyright notice MUST APPEAR in all copies of the script!
30 ***************************************************************/
31 /**
32 * Contains the update class for adding new system extensions.
33 *
34 * @author Benjamin Mack <benni@typo3.org>
35 * @author Steffen Kamper <info@sk-typo3.de>
36 * @author Kai Vogel <kai.vogel@speedprogs.de>
37 */
38 class InstallSysExtsUpdate extends \TYPO3\CMS\Install\Updates\AbstractUpdate {
39
40 /**
41 * @var string
42 */
43 protected $title = 'Install System Extensions';
44
45 /**
46 * @var array
47 */
48 protected $systemExtensions = array(
49 'info',
50 'perm',
51 'func',
52 'filelist',
53 'about',
54 'cshmanual',
55 'feedit',
56 'opendocs',
57 'recycler',
58 't3editor',
59 'reports',
60 'scheduler',
61 'simulatestatic',
62 );
63
64 /**
65 * @var string
66 */
67 protected $extEmConfPath = 'typo3/sysext/@extensionKey/ext_emconf.php';
68
69 /**
70 * @var string
71 */
72 protected $repositoryUrl = 'http://typo3.org/fileadmin/ter/@filename';
73
74 /**
75 * @var string
76 */
77 protected $informationUrl = 'http://typo3.org/index.php?type=95832&tx_terfe2_pi1%5Baction%5D=show&tx_terfe2_pi1%5Bformat%5D=json&tx_terfe2_pi1%5BextensionKey%5D=@extensionKey';
78
79 /**
80 * @var bool
81 */
82 protected $updateSuccessful = TRUE;
83
84 /**
85 * Checks if an update is needed
86 *
87 * @param string &$description: The description for the update
88 * @return boolean whether an update is needed (TRUE) or not (FALSE)
89 */
90 public function checkForUpdate(&$description) {
91 $description = '
92 <br />
93 Uninstalled system extensions have been found.
94 It is now possible to install them automatically by this upgrade wizard.
95 ';
96
97 if ($this->isWizardDone()) {
98 return FALSE;
99 }
100
101 foreach ($this->systemExtensions as $extensionKey) {
102 if (!\TYPO3\CMS\Core\Extension\ExtensionManager::isLoaded($extensionKey)) {
103 return TRUE;
104 }
105 }
106
107 return FALSE;
108 }
109
110 /**
111 * Second step: Get user input for installing system extensions
112 *
113 * @param string $inputPrefix input prefix, all names of form fields have to start with this. Append custom name in [ ... ]
114 * @return string HTML output
115 */
116 public function getUserInput($inputPrefix) {
117 $list = '
118 <p>
119 Install the following system extensions:
120 </p>
121 <fieldset>
122 <ol class="t3-install-form-label-after">%s</ol>
123 </fieldset>';
124 $item = '
125 <li class="labelAfter">
126 <input type="checkbox" id="%1$s" name="%2$s[sysext][%1$s]" value="1" checked="checked" />
127 <label for="%1$s"><strong>%3$s [%1$s]</strong><br />%4$s</label>
128 </li>';
129 $items = array();
130
131 foreach ($this->systemExtensions as $extensionKey) {
132 if (\TYPO3\CMS\Core\Extension\ExtensionManager::isLoaded($extensionKey)) {
133 continue;
134 }
135 $extension = $this->getExtensionDetails($extensionKey);
136 $items[] = sprintf(
137 $item,
138 $extensionKey,
139 $inputPrefix,
140 htmlspecialchars($extension['title']),
141 htmlspecialchars($extension['description'])
142 );
143 }
144
145 return sprintf($list, implode('', $items));
146 }
147
148 /**
149 * Adds the outsourced extensions to the extList in TYPO3_CONF_VARS
150 *
151 * @param array &$dbQueries: queries done in this update
152 * @param mixed &$customMessages: custom messages
153 * @return boolean whether it worked (TRUE) or not (FALSE)
154 */
155 public function performUpdate(array &$dbQueries, &$customMessages) {
156 // Get extension keys that were submitted by the user to be installed and that are valid for this update wizard
157 if (is_array($this->pObj->INSTALL['update']['installSystemExtensions']['sysext'])) {
158 $extArray = array_intersect(
159 $this->systemExtensions,
160 array_keys($this->pObj->INSTALL['update']['installSystemExtensions']['sysext'])
161 );
162 $this->installExtensions($extArray, $customMessages);
163 }
164
165 // Show this wizard again only if new extension keys have been found
166 $this->markWizardAsDone();
167
168 return $this->updateSuccessful;
169 }
170
171 /**
172 * This method can be called to install extensions following all proper processes
173 * (e.g. installing in extList, respecting priority, etc.)
174 *
175 * @param array $extensionKeys List of keys of extensions to install
176 * @param mixed $customMessages
177 * @return void
178 */
179 protected function installExtensions($extensionKeys, &$customMessages) {
180 /** @var $extensionListUtility \TYPO3\CMS\Extensionmanager\Utility\ListUtility */
181 $extensionListUtility = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(
182 'TYPO3\\CMS\\Extensionmanager\\Utility\\ListUtility'
183 );
184
185 /** @var $extensionFileHandlingUtility \TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility */
186 $extensionFileHandlingUtility = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(
187 'TYPO3\\CMS\\Extensionmanager\\Utility\\FileHandlingUtility'
188 );
189 /** @var $objectManager \TYPO3\CMS\Extbase\Object\ObjectManager */
190 $objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Object\\ObjectManager');
191
192 /** @var $extensionInstallUtility \TYPO3\CMS\Extensionmanager\Utility\InstallUtility */
193 $extensionInstallUtility = $objectManager->get('TYPO3\\CMS\\Extensionmanager\\Utility\\InstallUtility');
194
195 /** @var $extensionTerUtility \TYPO3\CMS\Extensionmanager\Utility\Connection\TerUtility */
196 $extensionTerUtility = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(
197 'TYPO3\\CMS\\Extensionmanager\\Utility\\Connection\\TerUtility'
198 );
199 $availableExtensions = $extensionListUtility->getAvailableExtensions();
200 $availableAndInstalledExtensions = $extensionListUtility->getAvailableAndInstalledExtensions($availableExtensions);
201 foreach ($extensionKeys as $extensionKey) {
202 if (!is_array($availableAndInstalledExtensions[$extensionKey])) {
203 $extensionDetails = $this->getExtensionDetails($extensionKey);
204 $t3xContent = $this->fetchExtension($extensionKey, $extensionDetails['versionString']);
205 if (empty($t3xContent)) {
206 $this->updateSuccessful = FALSE;
207 $customMessages .= 'The extension ' . $extensionKey . ' could not be downloaded.';
208 continue;
209 }
210 $t3xExtracted = $extensionTerUtility->decodeExchangeData($t3xContent);
211 if (empty($t3xExtracted) || !is_array($t3xExtracted) || empty($t3xExtracted['extKey'])) {
212 $this->updateSuccessful = FALSE;
213 $customMessages .= 'The extension ' . $extensionKey . ' could not be extracted.';
214 continue;
215 }
216 $extensionFileHandlingUtility->unpackExtensionFromExtensionDataArray($t3xExtracted);
217 }
218 $extensionInstallUtility->install($extensionKey);
219 }
220 }
221
222 /**
223 * Returns the details of a local or external extension
224 *
225 * @param string $extensionKey Key of the extension to check
226 * @return array Extension details
227 */
228 protected function getExtensionDetails($extensionKey) {
229 // Local extension
230 $extEmConf = PATH_site . str_replace('@extensionKey', $extensionKey, $this->extEmConfPath);
231 if (file_exists($extEmConf)) {
232 $EM_CONF = FALSE;
233 require_once($extEmConf);
234 return reset($EM_CONF);
235 }
236
237 // Repository extension
238 $url = str_replace('@extensionKey', $extensionKey, $this->informationUrl);
239 $jsonResponse = $this->fetchUrl($url);
240 if (!empty($jsonResponse) && is_string($jsonResponse)) {
241 return json_decode($jsonResponse, TRUE);
242 }
243
244 return array();
245 }
246
247 /**
248 * Fetch extension from repository
249 *
250 * @param string $extensionKey The extension key to fetch
251 * @param string $version The version to fetch
252 * @return string T3X file content
253 */
254 protected function fetchExtension($extensionKey, $version) {
255 if (empty($extensionKey) || empty($version)) {
256 return '';
257 }
258
259 $filename = $extensionKey[0] . '/' . $extensionKey[1] . '/' . $extensionKey . '_' . $version . '.t3x';
260 $url = str_replace('@filename', $filename, $this->repositoryUrl);
261 return $this->fetchUrl($url);
262 }
263
264 /**
265 * Open an URL and return the response
266 *
267 * This wrapper method is required to try several download methods if
268 * the configuration is not valid or initially written by the installer.
269 *
270 * @param string $url The URL to file
271 * @throws \Exception
272 * @return string File content
273 */
274 protected function fetchUrl($url) {
275 if (empty($url)) {
276 return NULL;
277 }
278
279 $fileContent = \TYPO3\CMS\Core\Utility\GeneralUtility::getUrl($url, 0, array(TYPO3_user_agent));
280
281 // No content, force cURL if disabled in configuration but found in system
282 if ($fileContent === FALSE && function_exists('curl_init') && empty($GLOBALS['TYPO3_CONF_VARS']['SYS']['curlUse'])) {
283 $GLOBALS['TYPO3_CONF_VARS']['SYS']['curlUse'] = TRUE;
284 $fileContent = \TYPO3\CMS\Core\Utility\GeneralUtility::getUrl($url, 0, array(TYPO3_user_agent));
285 $GLOBALS['TYPO3_CONF_VARS']['SYS']['curlUse'] = FALSE;
286 }
287
288 // Still no content, try file_get_contents if allow_url_fopen is enabled
289 if ($fileContent === FALSE && function_exists('file_get_contents') && ini_get('allow_url_fopen')) {
290 $fileContent = file_get_contents($url);
291 }
292
293 // Can not fetch url, throw an exception
294 if ($fileContent === FALSE) {
295 throw new \Exception(
296 'Can not fetch URL "' . $url . '". Possibile reasons are network problems or misconfiguration.',
297 1344685036
298 );
299 }
300
301 return $fileContent;
302 }
303
304 /**
305 * Marks the wizard as being "seen" so that it not shown again until
306 * no new extension keys have been found.
307 *
308 * Writes the info in LocalConfiguration.php
309 *
310 * @return void
311 */
312 protected function markWizardAsDone() {
313 \TYPO3\CMS\Core\Configuration\ConfigurationManager::setLocalConfigurationValueByPath(
314 'INSTALL/wizardDone/' . get_class($this),
315 json_encode($this->systemExtensions)
316 );
317 }
318
319 /**
320 * Checks if all extensions have been "seen" before
321 *
322 * @return boolean TRUE if wizard has been done before, FALSE otherwise
323 */
324 protected function isWizardDone() {
325 $wizardClassName = get_class($this);
326 if (!empty($GLOBALS['TYPO3_CONF_VARS']['INSTALL']['wizardDone'][$wizardClassName])) {
327 $seenExtensions = json_decode($GLOBALS['TYPO3_CONF_VARS']['INSTALL']['wizardDone'][$wizardClassName], TRUE);
328 return (bool) array_diff($this->systemExtensions, $seenExtensions);
329 }
330 return FALSE;
331 }
332 }
333
334 ?>