[TASK] Deprecate AbstractService
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Service / AbstractService.php
1 <?php
2
3 /*
4 * This file is part of the TYPO3 CMS project.
5 *
6 * It is free software; you can redistribute it and/or modify it under
7 * the terms of the GNU General Public License, either version 2
8 * of the License, or any later version.
9 *
10 * For the full copyright and license information, please read the
11 * LICENSE.txt file that was distributed with this source code.
12 *
13 * The TYPO3 project - inspiring people to share!
14 */
15
16 namespace TYPO3\CMS\Core\Service;
17
18 use Psr\Log\LoggerAwareInterface;
19 use Psr\Log\LoggerAwareTrait;
20 use TYPO3\CMS\Core\Security\BlockSerializationTrait;
21 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
22 use TYPO3\CMS\Core\Utility\CommandUtility;
23 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
24 use TYPO3\CMS\Core\Utility\GeneralUtility;
25
26 /**
27 * Parent class for "Services" classes
28 *
29 * @deprecated since v11, will be removed in v12.
30 */
31 abstract class AbstractService implements LoggerAwareInterface
32 {
33 use BlockSerializationTrait;
34 use LoggerAwareTrait;
35
36 // General error - something went wrong
37 const ERROR_GENERAL = -1;
38
39 // During execution it showed that the service is not available and
40 // should be ignored. The service itself should call $this->setNonAvailable()
41 const ERROR_SERVICE_NOT_AVAILABLE = -2;
42
43 // Passed subtype is not possible with this service
44 const ERROR_WRONG_SUBTYPE = -3;
45
46 // Passed subtype is not possible with this service
47 const ERROR_NO_INPUT = -4;
48
49 // File not found which the service should process
50 const ERROR_FILE_NOT_FOUND = -20;
51
52 // File not readable
53 const ERROR_FILE_NOT_READABLE = -21;
54
55 // File not writable
56 // @todo: check writeable vs. writable
57 const ERROR_FILE_NOT_WRITEABLE = -22;
58
59 // Passed subtype is not possible with this service
60 const ERROR_PROGRAM_NOT_FOUND = -40;
61
62 // Passed subtype is not possible with this service
63 const ERROR_PROGRAM_FAILED = -41;
64 /**
65 * @var array service description array
66 */
67 public $info = [];
68
69 /**
70 * @var array error stack
71 */
72 public $error = [];
73
74 /**
75 * @var string The output content. That's what the services produced as result.
76 */
77 public $out = '';
78
79 /**
80 * @var string The file that should be processed.
81 */
82 public $inputFile = '';
83
84 /**
85 * @var string The content that should be processed.
86 */
87 public $inputContent = '';
88
89 /**
90 * @var string The type of the input content (or file). Might be the same as the service subtypes.
91 */
92 public $inputType = '';
93
94 /**
95 * @var string The file where the output should be written to.
96 */
97 public $outputFile = '';
98
99 /**
100 * Temporary files which have to be deleted
101 *
102 * @private
103 * @var array
104 */
105 public $tempFiles = [];
106
107 /**
108 * @var array list of registered shutdown functions; should be used to prevent registering the same function multiple times
109 */
110 protected $shutdownRegistry = [];
111
112 /**
113 * @var string Prefix for temporary files
114 */
115 protected $prefixId = '';
116
117 /***************************************
118 *
119 * Get service meta information
120 *
121 ***************************************/
122 /**
123 * Returns internal information array for service
124 *
125 * @return array Service description array
126 */
127 public function getServiceInfo()
128 {
129 return $this->info;
130 }
131
132 /**
133 * Returns the service key of the service
134 *
135 * @return string Service key
136 */
137 public function getServiceKey()
138 {
139 return $this->info['serviceKey'];
140 }
141
142 /**
143 * Returns the title of the service
144 *
145 * @return string Service title
146 */
147 public function getServiceTitle()
148 {
149 return $this->info['title'];
150 }
151
152 /**
153 * Returns service configuration values from the $TYPO3_CONF_VARS['SVCONF'] array
154 *
155 * @param string $optionName Name of the config option
156 * @param mixed $defaultValue Default configuration if no special config is available
157 * @param bool $includeDefaultConfig If set the 'default' config will be returned if no special config for this service is available (default: TRUE)
158 * @return mixed Configuration value for the service
159 */
160 public function getServiceOption($optionName, $defaultValue = '', $includeDefaultConfig = true)
161 {
162 $config = null;
163 $serviceType = $this->info['serviceType'] ?? '';
164 $serviceKey = $this->info['serviceKey'] ?? '';
165 $svOptions = $GLOBALS['TYPO3_CONF_VARS']['SVCONF'][$serviceType] ?? [];
166 if (isset($svOptions[$serviceKey][$optionName])) {
167 $config = $svOptions[$serviceKey][$optionName];
168 } elseif ($includeDefaultConfig && isset($svOptions['default'][$optionName])) {
169 $config = $svOptions['default'][$optionName];
170 }
171 if (!isset($config)) {
172 $config = $defaultValue;
173 }
174 return $config;
175 }
176
177 /***************************************
178 *
179 * Error handling
180 *
181 ***************************************/
182
183 /**
184 * Puts an error on the error stack. Calling without parameter adds a general error.
185 *
186 * @param int $errNum Error number (see class constants)
187 * @param string $errMsg Error message
188 */
189 public function errorPush($errNum = self::ERROR_GENERAL, $errMsg = 'Unspecified error occurred')
190 {
191 $this->error[] = ['nr' => $errNum, 'msg' => $errMsg];
192 /** @var \TYPO3\CMS\Core\TimeTracker\TimeTracker $timeTracker */
193 $timeTracker = GeneralUtility::makeInstance(TimeTracker::class);
194 $timeTracker->setTSlogMessage($errMsg, 2);
195 }
196
197 /**
198 * Removes the last error from the error stack.
199 */
200 public function errorPull()
201 {
202 array_pop($this->error);
203 }
204
205 /**
206 * Returns the last error number from the error stack.
207 *
208 * @return int|bool Error number (or TRUE if no error)
209 */
210 public function getLastError()
211 {
212 // Means all is ok - no error
213 $lastError = true;
214 if (!empty($this->error)) {
215 $error = end($this->error);
216 $lastError = $error['nr'];
217 }
218 return $lastError;
219 }
220
221 /**
222 * Returns the last message from the error stack.
223 *
224 * @return string Error message
225 */
226 public function getLastErrorMsg()
227 {
228 $lastErrorMessage = '';
229 if (!empty($this->error)) {
230 $error = end($this->error);
231 $lastErrorMessage = $error['msg'];
232 }
233 return $lastErrorMessage;
234 }
235
236 /**
237 * Returns all error messages as array.
238 *
239 * @return array Error messages
240 */
241 public function getErrorMsgArray()
242 {
243 $errArr = [];
244 if (!empty($this->error)) {
245 foreach ($this->error as $error) {
246 $errArr[] = $error['msg'];
247 }
248 }
249 return $errArr;
250 }
251
252 /**
253 * Returns the last array from the error stack.
254 *
255 * @return array Error number and message
256 */
257 public function getLastErrorArray()
258 {
259 return end($this->error);
260 }
261
262 /**
263 * Reset the error stack.
264 */
265 public function resetErrors()
266 {
267 $this->error = [];
268 }
269
270 /***************************************
271 *
272 * General service functions
273 *
274 ***************************************/
275 /**
276 * check the availability of external programs
277 *
278 * @param string $progList Comma list of programs 'perl,python,pdftotext'
279 * @return bool Return FALSE if one program was not found
280 */
281 public function checkExec($progList)
282 {
283 $ret = true;
284 $progList = GeneralUtility::trimExplode(',', $progList, true);
285 foreach ($progList as $prog) {
286 if (!CommandUtility::checkCommand($prog)) {
287 // Program not found
288 $this->errorPush(self::ERROR_PROGRAM_NOT_FOUND, 'External program not found: ' . $prog);
289 $ret = false;
290 }
291 }
292 return $ret;
293 }
294
295 /**
296 * Deactivate the service. Use this if the service fails at runtime and will not be available.
297 */
298 public function deactivateService()
299 {
300 ExtensionManagementUtility::deactivateService($this->info['serviceType'], $this->info['serviceKey']);
301 }
302
303 /***************************************
304 *
305 * IO tools
306 *
307 ***************************************/
308 /**
309 * Check if a file exists and is readable.
310 *
311 * @param string $absFile File name with absolute path.
312 * @return string|bool File name or FALSE.
313 */
314 public function checkInputFile($absFile)
315 {
316 $checkResult = false;
317 if (GeneralUtility::isAllowedAbsPath($absFile) && @is_file($absFile)) {
318 if (@is_readable($absFile)) {
319 $checkResult = $absFile;
320 } else {
321 $this->errorPush(self::ERROR_FILE_NOT_READABLE, 'File is not readable: ' . $absFile);
322 }
323 } else {
324 $this->errorPush(self::ERROR_FILE_NOT_FOUND, 'File not found: ' . $absFile);
325 }
326 return $checkResult;
327 }
328
329 /**
330 * Read content from a file a file.
331 *
332 * @param string $absFile File name to read from.
333 * @param int $length Maximum length to read. If empty the whole file will be read.
334 * @return string|bool file content or false
335 */
336 public function readFile($absFile, $length = 0)
337 {
338 $out = false;
339 if ($this->checkInputFile($absFile)) {
340 $out = file_get_contents($absFile);
341 if ($out === false) {
342 $this->errorPush(self::ERROR_FILE_NOT_READABLE, 'Can not read from file: ' . $absFile);
343 }
344 }
345 return $out;
346 }
347
348 /**
349 * Write content to a file.
350 *
351 * @param string $content Content to write to the file
352 * @param string $absFile File name to write into. If empty a temp file will be created.
353 * @return string|bool File name or FALSE
354 */
355 public function writeFile($content, $absFile = '')
356 {
357 if (!$absFile) {
358 $absFile = $this->tempFile($this->prefixId);
359 if ($absFile === false) {
360 return false;
361 }
362 $absFile = (string)$absFile;
363 }
364 if (GeneralUtility::isAllowedAbsPath($absFile)) {
365 if ($fd = @fopen($absFile, 'wb')) {
366 @fwrite($fd, $content);
367 @fclose($fd);
368 } else {
369 $this->errorPush(self::ERROR_FILE_NOT_WRITEABLE, 'Can not write to file: ' . $absFile);
370 $absFile = false;
371 }
372 }
373 return $absFile;
374 }
375
376 /**
377 * Create a temporary file.
378 *
379 * @param string $filePrefix File prefix.
380 * @return string|bool File name or FALSE
381 */
382 public function tempFile($filePrefix)
383 {
384 $absFile = GeneralUtility::tempnam($filePrefix);
385 if ($absFile) {
386 $ret = $absFile;
387 $this->registerTempFile($absFile);
388 } else {
389 $ret = false;
390 $this->errorPush(self::ERROR_FILE_NOT_WRITEABLE, 'Can not create temp file.');
391 }
392 return $ret;
393 }
394
395 /**
396 * Register file which should be deleted afterwards.
397 *
398 * @param string $absFile File name with absolute path.
399 */
400 public function registerTempFile($absFile)
401 {
402 if (!isset($this->shutdownRegistry[__METHOD__])) {
403 register_shutdown_function([$this, 'unlinkTempFiles']);
404 $this->shutdownRegistry[__METHOD__] = true;
405 }
406 $this->tempFiles[] = $absFile;
407 }
408
409 /**
410 * Delete registered temporary files.
411 */
412 public function unlinkTempFiles()
413 {
414 foreach ($this->tempFiles as $absFile) {
415 GeneralUtility::unlink_tempfile($absFile);
416 }
417 $this->tempFiles = [];
418 }
419
420 /***************************************
421 *
422 * IO input
423 *
424 ***************************************/
425 /**
426 * Set the input content for service processing.
427 *
428 * @param mixed $content Input content (going into ->inputContent)
429 * @param string $type The type of the input content (or file). Might be the same as the service subtypes.
430 */
431 public function setInput($content, $type = '')
432 {
433 $this->inputContent = $content;
434 $this->inputFile = '';
435 $this->inputType = $type;
436 }
437
438 /**
439 * Set the input file name for service processing.
440 *
441 * @param string $absFile File name
442 * @param string $type The type of the input content (or file). Might be the same as the service subtypes.
443 */
444 public function setInputFile($absFile, $type = '')
445 {
446 $this->inputContent = '';
447 $this->inputFile = $absFile;
448 $this->inputType = $type;
449 }
450
451 /**
452 * Get the input content.
453 * Will be read from input file if needed. (That is if ->inputContent is empty and ->inputFile is not)
454 *
455 * @return string
456 */
457 public function getInput()
458 {
459 if ($this->inputContent == '') {
460 $this->inputContent = (string)$this->readFile($this->inputFile);
461 }
462 return $this->inputContent;
463 }
464
465 /**
466 * Get the input file name.
467 * If the content was set by setContent a file will be created.
468 *
469 * @param string $createFile File name. If empty a temp file will be created.
470 * @return string File name or FALSE if no input or file error.
471 */
472 public function getInputFile($createFile = '')
473 {
474 if ($this->inputFile) {
475 $this->inputFile = (string)$this->checkInputFile($this->inputFile);
476 } elseif ($this->inputContent) {
477 $this->inputFile = (string)$this->writeFile($this->inputContent, $createFile);
478 }
479 return $this->inputFile;
480 }
481
482 /***************************************
483 *
484 * IO output
485 *
486 ***************************************/
487 /**
488 * Set the output file name.
489 *
490 * @param string $absFile File name
491 */
492 public function setOutputFile($absFile)
493 {
494 $this->outputFile = $absFile;
495 }
496
497 /**
498 * Get the output content.
499 *
500 * @return string
501 */
502 public function getOutput()
503 {
504 if ($this->outputFile) {
505 $this->out = (string)$this->readFile($this->outputFile);
506 }
507 return $this->out;
508 }
509
510 /**
511 * Get the output file name. If no output file is set, the ->out buffer is written to the file given by input parameter filename
512 *
513 * @param string $absFile Absolute filename to write to
514 * @return mixed
515 */
516 public function getOutputFile($absFile = '')
517 {
518 if (!$this->outputFile) {
519 $this->outputFile = (string)$this->writeFile($this->out, $absFile);
520 }
521 return $this->outputFile;
522 }
523
524 /***************************************
525 *
526 * Service implementation
527 *
528 ***************************************/
529 /**
530 * Initialization of the service.
531 *
532 * The class have to do a strict check if the service is available.
533 * example: check if the perl interpreter is available which is needed to run an external perl script.
534 *
535 * @return bool TRUE if the service is available
536 */
537 public function init()
538 {
539 // look in makeInstanceService()
540 $this->reset();
541 // Check for external programs which are defined by $info['exec']
542 if (trim($this->info['exec'])) {
543 $this->checkExec($this->info['exec']);
544 }
545 return $this->getLastError() === true;
546 }
547
548 /**
549 * Resets the service.
550 * Will be called by init(). Should be used before every use if a service instance is used multiple times.
551 */
552 public function reset()
553 {
554 $this->unlinkTempFiles();
555 $this->resetErrors();
556 $this->out = '';
557 $this->inputFile = '';
558 $this->inputContent = '';
559 $this->inputType = '';
560 $this->outputFile = '';
561 }
562
563 /**
564 * Clean up the service.
565 * Child classes should explicitly call parent::__destruct() in their destructors for this to work
566 */
567 public function __destruct()
568 {
569 $this->unlinkTempFiles();
570 }
571 }