cc58a8cb4e5c4b05cfe13fdc78a97940a4d77b76
[TYPO3CMS/Extensions/vcc.git] / Classes / Service / CommunicationService.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2012 Nicole Cordes <cordes@cps-it.de>
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 *
17 * This script is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * This copyright notice MUST APPEAR in all copies of the script!
23 ***************************************************************/
24
25 /**
26 * Service to send the cache command to server
27 *
28 * @author Nicole Cordes <cordes@cps-it.de>
29 * @package TYPO3
30 * @subpackage vcc
31 */
32 class tx_vcc_service_communicationService implements t3lib_Singleton {
33
34 /**
35 * @var tslib_cObj|NULL
36 */
37 var $contentObject = NULL;
38
39 /**
40 * @var tx_vcc_service_extensionSettingService|NULL
41 */
42 var $extensionSettingService = NULL;
43
44 /**
45 * @var tx_vcc_service_loggingService|NULL
46 */
47 var $loggingService = NULL;
48
49 /**
50 * @var tx_vcc_service_tsConfigService|NULL
51 */
52 var $tsConfigService = NULL;
53
54 /**
55 * @var array
56 */
57 var $hookObjects = array();
58
59 /**
60 * @var string
61 */
62 var $httpMethod = '';
63
64 /**
65 * @var string
66 */
67 var $httpProtocol = '';
68
69 /**
70 * @var boolean
71 */
72 var $stripSlash = FALSE;
73
74 /**
75 * @var boolean
76 */
77 var $enableIndexScript = FALSE;
78
79 /**
80 * @var array
81 */
82 var $serverArray = array();
83
84 /**
85 * Initialize the object
86 *
87 * @return void
88 */
89 public function __construct() {
90 $extensionSettingService = t3lib_div::makeInstance('tx_vcc_service_extensionSettingService');
91 $this->injectExtensionSettingService($extensionSettingService);
92
93 $loggingService = t3lib_div::makeInstance('tx_vcc_service_loggingService');
94 $this->injectLoggingService($loggingService);
95
96 $tsConfigService = t3lib_div::makeInstance('tx_vcc_service_tsConfigService');
97 $this->injectTsConfigService($tsConfigService);
98
99 $configuration = $this->extensionSettingService->getConfiguration();
100 $this->serverArray = t3lib_div::trimExplode(',', $configuration['server'], 1);
101 $this->httpMethod = trim($configuration['httpMethod']);
102 $this->httpProtocol = trim($configuration['httpProtocol']);
103 $this->stripSlash = $configuration['stripSlash'];
104 $this->enableIndexScript = $configuration['enableIndexScript'];
105
106 if (!is_object($GLOBALS['TSFE'])) {
107 $this->createTSFE();
108 }
109
110 $this->contentObject = t3lib_div::makeInstance('tslib_cObj');
111
112 // Initialize hook objects
113 $this->initializeHookObjects();
114 }
115
116 /**
117 * Injects the extension setting service
118 *
119 * @param tx_vcc_service_extensionSettingService $extensionSettingService
120 *
121 * @return void
122 */
123 public function injectExtensionSettingService(tx_vcc_service_extensionSettingService $extensionSettingService) {
124 $this->extensionSettingService = $extensionSettingService;
125 }
126
127 /**
128 * Injects the logging service
129 *
130 * @param tx_vcc_service_loggingService $loggingService
131 *
132 * @return void
133 */
134 public function injectLoggingService(tx_vcc_service_loggingService $loggingService) {
135 $this->loggingService = $loggingService;
136 }
137
138 /**
139 * Injects the TSConfig service
140 *
141 * @param tx_vcc_service_tsConfigService $tsConfigService
142 *
143 * @return void
144 */
145 public function injectTsConfigService(tx_vcc_service_tsConfigService $tsConfigService) {
146 $this->tsConfigService = $tsConfigService;
147 }
148
149 protected function createTSFE() {
150 if (!is_object($GLOBALS['TT'])) {
151 $GLOBALS['TT'] = t3lib_div::makeInstance('t3lib_TimeTrackNull');
152 }
153
154 $GLOBALS['TSFE'] = t3lib_div::makeInstance('tslib_fe', $GLOBALS['TYPO3_CONF_VARS'], 1, '');
155 $GLOBALS['TSFE']->connectToDB();
156 $GLOBALS['TSFE']->initFEuser();
157 $GLOBALS['TSFE']->determineId();
158 $GLOBALS['TSFE']->getCompressedTCarray();
159 $GLOBALS['TSFE']->initTemplate();
160 $GLOBALS['TSFE']->getConfigArray();
161 if (TYPO3_MODE == 'BE') {
162 // Set current backend language
163 $GLOBALS['TSFE']->getPageRenderer()->setLanguage($GLOBALS['LANG']->lang);
164 }
165 $GLOBALS['TSFE']->newcObj();
166
167 TSpagegen::pagegenInit();
168 }
169
170 protected function initializeHookObjects() {
171 if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['vcc']['hooks']['communicationService'])) {
172 foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['vcc']['hooks']['communicationService'] as $classReference) {
173 $hookObject = t3lib_div::getUserObj($classReference);
174
175 // Hook objects have to implement interface
176 if ($hookObject instanceof tx_vcc_hook_communicationServiceHookInterface) {
177 $this->hookObjects[] = $hookObject;
178 }
179 }
180 unset($classReference);
181 }
182 }
183
184 /**
185 * Send clear cache commands for pages to defined server
186 *
187 * @param string $table
188 * @param integer $id
189 * @param string $host
190 *
191 * @return array
192 */
193 public function sendClearCacheCommandForTables($table, $uid, $host = '') {
194 // Get current record to process
195 $record = t3lib_BEfunc::getRecord($table, $uid);
196
197 // Build request
198 if ($table === 'pages') {
199 $pid = $record['uid'];
200 } else {
201 $pid = $record['pid'];
202 }
203
204 // Log debug information
205 $logData = array(
206 'table' => $table,
207 'uid' => $uid,
208 'host' => $host,
209 'pid' => $pid
210 );
211 $this->loggingService->debug('CommunicationService::sendClearCacheCommandForTables arguments', $logData, $pid);
212
213 $tsConfig = $this->tsConfigService->getConfiguration($pid);
214 $typolink = $tsConfig[$table . '.']['typolink.'];
215 $this->contentObject->data = $record;
216
217 $url = $this->contentObject->typoLink_URL($typolink);
218 $LD = $this->contentObject->lastTypoLinkLD;
219
220 // Check for root site
221 if ($url === '' && $table === 'pages') {
222 $rootline = t3lib_BEfunc::BEgetRootLine($uid);
223 if (is_array($rootline) && count($rootline) > 1) {
224 // If uid equals the site root we have to process
225 if ($uid == $rootline[1]['uid']) {
226 $url = '/';
227 }
228 }
229 }
230
231 // Log debug information
232 $logData['url'] = $url;
233 $this->loggingService->debug('CommunicationService::sendClearCacheCommandForTables built url', $logData, $pid);
234
235 // Process only for valid URLs
236 if ($url !== '') {
237
238 $url = $this->removeHost($url);
239 $responseArray = $this->processClearCacheCommand($url, $pid, $host);
240
241 // Check support of index.php script
242 if ($this->enableIndexScript) {
243 $url = $LD['url'] . $LD['linkVars'];
244 $url = $this->removeHost($url);
245
246 $indexResponseArray = $this->processClearCacheCommand($url, $pid, $host);
247 $responseArray = array_merge($responseArray, $indexResponseArray);
248 }
249
250 return $responseArray;
251 }
252
253 return array(
254 array(
255 'status' => t3lib_FlashMessage::ERROR,
256 'message' => array('No valid URL was generated.', 'table: ' . $table, 'uid: ' . $uid, 'host: ' . $host),
257 'requestHeader' => array($url)
258 )
259 );
260 }
261
262 /**
263 * Send clear cache commands for pages to defined server
264 *
265 * @param string $fileName
266 * @param string $host
267 *
268 * @return array
269 */
270 public function sendClearCacheCommandForFiles($fileName, $host = '') {
271 // Log debug information
272 $logData = array(
273 'fileName' => $fileName,
274 'host' => $host
275 );
276 $this->loggingService->debug('CommunicationService::sendClearCacheCommandForFiles arguments', $logData);
277
278 // If no host was given get all
279 if ($host === '') {
280 $hostArray = array();
281
282 // Get all domain records and check page access
283 $domainArray = t3lib_BEfunc::getRecordsByField('sys_domain', 'redirectTo', '', ' AND hidden=0');
284 if (is_array($domainArray) && !empty($domainArray)) {
285 $permsClause = $GLOBALS['BE_USER']->getPagePermsClause(2);
286 foreach ($domainArray as $domain) {
287 $pageinfo = t3lib_BEfunc::readPageAccess($domain['pid'], $permsClause);
288 if ($pageinfo !== FALSE) {
289 $hostArray[] = $domain['domainName'];
290 }
291 }
292 unset($domain);
293 }
294 $host = implode(',', $hostArray);
295
296 // Log debug information
297 $logData['host'] = $host;
298 $this->loggingService->debug('CommunicationService::sendClearCacheCommandForFiles built host', $logData);
299 }
300
301 return $this->processClearCacheCommand($fileName, 0, $host);
302 }
303
304 /**
305 * Remove any host from an url
306 *
307 * @param string $url
308 *
309 * @return string
310 */
311 protected function removeHost($url) {
312 if (strpos($url, '://') !== FALSE) {
313 $urlArray = parse_url($url);
314 $url = substr($url, strlen($urlArray['scheme'] . '://' . $urlArray['host']));
315 }
316
317 return $url;
318 }
319
320 /**
321 * Processes the CURL request and sends action to Varnish server
322 *
323 * @param string $url
324 * @param integer $pid
325 * @param string $host
326 *
327 * @return array
328 */
329 protected function processClearCacheCommand($url, $pid, $host = '') {
330 $responseArray = array();
331
332 foreach ($this->serverArray as $server) {
333 $response = array(
334 'server' => $server
335 );
336
337 // Build request
338 if ($this->stripSlash) {
339 $url = rtrim($url, '/');
340 }
341 $request = $server . '/' . ltrim($url, '/');
342 $response['request'] = $request;
343
344 // Check for curl functions
345 if (!function_exists('curl_init')) {
346 // TODO: Implement fallback to file_get_contents()
347 $response['status'] = t3lib_FlashMessage::ERROR;
348 $response['message'] = 'No curl_init available';
349 } else {
350 // If no host was given we need to loop over all
351 $hostArray = array();
352 if ($host !== '') {
353 $hostArray = t3lib_div::trimExplode(',', $host, 1);
354 } else {
355 // Get all (non-redirecting) domains from root
356 $rootLine = t3lib_BEfunc::BEgetRootLine($pid);
357 foreach ($rootLine as $row) {
358 $domainArray = t3lib_BEfunc::getRecordsByField('sys_domain', 'pid', $row['uid'], ' AND redirectTo="" AND hidden=0');
359 if (is_array($domainArray) && !empty($domainArray)) {
360 foreach ($domainArray as $domain) {
361 $hostArray[] = $domain['domainName'];
362 }
363 unset($domain);
364 }
365 }
366 unset($row);
367 }
368
369 // Fallback to current server
370 if (empty($hostArray)) {
371 $domain = rtrim(t3lib_div::getIndpEnv('TYPO3_SITE_URL'), '/');
372 $hostArray[] = substr($domain, strpos($domain, '://') + 3);
373 }
374
375 // Loop over hosts
376 foreach ($hostArray as $xHost) {
377 $response['host'] = $xHost;
378
379 // Curl initialization
380 $ch = curl_init();
381
382 // Disable direct output
383 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
384
385 // Only get header response
386 curl_setopt($ch, CURLOPT_HEADER, 1);
387 curl_setopt($ch, CURLOPT_NOBODY, 1);
388
389 // Set url
390 curl_setopt($ch, CURLOPT_URL, $request);
391
392 // Set method and protocol
393 curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->httpMethod);
394 curl_setopt($ch, CURLOPT_HTTP_VERSION, ($this->httpProtocol === 'http_10') ? CURL_HTTP_VERSION_1_0 : CURL_HTTP_VERSION_1_1);
395
396 // Set X-Host and X-Url header
397 // Set X-Host-Quoted and X-Url-Quoted with added slashes for regular expression in vcl
398 curl_setopt($ch, CURLOPT_HTTPHEADER, array(
399 'X-Host: ' . $xHost,
400 'X-Host-Quoted: ' . preg_quote($xHost),
401 'X-Url: ' . $url,
402 'X-Url-Quoted: ' . preg_quote($url)
403 ));
404
405 // Store outgoing header
406 curl_setopt($ch, CURLINFO_HEADER_OUT, 1);
407
408 // Include preProcess hook (e.g. to set some alternative curl options
409 foreach ($this->hookObjects as $hookObject) {
410 /** @var tx_vcc_hook_communicationServiceHookInterface $hookObject */
411 $hookObject->preProcess($ch, $request, $response, $this);
412 }
413 unset($hookObject);
414
415 $header = curl_exec($ch);
416 if (!curl_error($ch)) {
417 $response['status'] = (curl_getinfo($ch, CURLINFO_HTTP_CODE) == 200) ? t3lib_FlashMessage::OK : t3lib_FlashMessage::ERROR;
418 $response['message'] = preg_split('/(\r|\n)+/m', trim($header));
419 } else {
420 $response['status'] = t3lib_FlashMessage::ERROR;
421 $response['message'] = curl_error($ch);
422 }
423 $response['requestHeader'] = preg_split('/(\r|\n)+/m', trim(curl_getinfo($ch, CURLINFO_HEADER_OUT)));
424
425 // Include postProcess hook (e.g. to start some other jobs)
426 foreach ($this->hookObjects as $hookObject) {
427 /** @var tx_vcc_hook_communicationServiceHookInterface $hookObject */
428 $hookObject->postProcess($ch, $request, $response, $this);
429 }
430 unset($hookObject);
431
432 curl_close($ch);
433
434 // Log debug information
435 $logData = array(
436 'url' => $url,
437 'pid' => $pid,
438 'host' => $host,
439 'response' => $response
440 );
441 $logType = ($response['status'] == t3lib_FlashMessage::OK) ? tx_vcc_service_loggingService::OK : tx_vcc_service_loggingService::ERROR;
442 $this->loggingService->log('CommunicationService::processClearCacheCommand', $logData, $logType, $pid, 3);
443
444 $responseArray[] = $response;
445 }
446 unset($xHost);
447 }
448 }
449 unset($server);
450
451 return $responseArray;
452 }
453
454 /**
455 * Generates the flash messages for the requests
456 *
457 * @param array $resultArray
458 *
459 * @return string
460 */
461 public function generateBackendMessage(array $resultArray) {
462 $content = '';
463
464 if (is_array($resultArray)) {
465 foreach ($resultArray as $result) {
466 switch ($result['status']) {
467 case t3lib_FlashMessage::OK:
468 // Extend button marker with messages
469 $content .= '<script type="text/javascript">
470 parent.TYPO3.Flashmessage.display(
471 TYPO3.Severity.ok,
472 "Server: ' . $result['server'] . ' // Host: ' . $result['host'] . '",
473 "Request: ' . $result['request'] . '<br />Message: ' . $result['message'][0] . '",
474 5
475 );
476 </script>';
477 break;
478
479 default:
480 // Extend button marker with messages
481 $content .= '<script type="text/javascript">
482 parent.TYPO3.Flashmessage.display(
483 TYPO3.Severity.error,
484 "Server: ' . $result['server'] . ' // Host: ' . $result['host'] . '",
485 "Request: ' . $result['request'] . '<br />Message: ' . implode('<br />', $result['message']) . '<br />Sent:<br />' . implode('<br />', $result['requestHeader']) . '",
486 10
487 );
488 </script>';
489 break;
490 }
491 }
492 unset($result);
493 }
494
495 return $content;
496 }
497 }
498
499 if (defined('TYPO3_MODE') && $GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/vcc/Classes/Service/CommunicationService.php']) {
500 include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/vcc/Classes/Service/CommunicationService.php']);
501 }
502
503 ?>