BackendService.php 16.9 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php

namespace SGalinski\SgMail\Service;

/***************************************************************
 *  Copyright notice
 *
 *  (c) sgalinski Internet Services (https://www.sgalinski.de)
 *
 *  All rights reserved
 *
 *  This script is part of the TYPO3 project. The TYPO3 project is
 *  free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  The GNU General Public License can be found at
 *  http://www.gnu.org/copyleft/gpl.html.
 *
 *  This script is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  This copyright notice MUST APPEAR in all copies of the script!
 ***************************************************************/

29
use SGalinski\SgMail\Domain\Model\Template;
30
use SGalinski\SgMail\Domain\Repository\FrontendUserGroupRepository;
Torsten Oppermann's avatar
Torsten Oppermann committed
31
use SGalinski\SgMail\Domain\Repository\MailRepository;
32
use SGalinski\SgMail\Domain\Repository\TemplateRepository;
33
use SGalinski\SgMail\Utility\ExtensionUtility;
34
35
36
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
use TYPO3\CMS\Backend\Template\Components\DocHeaderComponent;
use TYPO3\CMS\Backend\Utility\BackendUtility;
37
38
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
39
40
41
use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Utility\GeneralUtility;
42
use TYPO3\CMS\Core\Utility\VersionNumberUtility;
43
use TYPO3\CMS\Extbase\Mvc\Request;
44
use TYPO3\CMS\Extbase\Object\ObjectManager;
45
use TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager;
46
47
48
49
50
51
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;

/**
 * Backend Service class
 */
class BackendService {
Torsten Oppermann's avatar
Torsten Oppermann committed
52
	// options for the queue search filter
53
54
55
56
57
58
59
60
	const SENDER_FILTER_OPTION = 0;
	const RECIPIENT_FILTER_OPTION = 1;
	const SUBJECT_FILTER_OPTION = 2;
	const MAILTEXT_FILTER_OPTION = 3;
	const CC_FILTER_OPTION = 4;
	const BCC_FILTER_OPTION = 5;
	const FROM_NAME_FILTER_OPTION = 6;
	const REPLY_TO_NAME_FILTER_OPTION = 7;
61

62
	// constants for deetermining the backend mode
63
64
65
66
	const BACKEND_MODE_EDITOR = 'editor';
	const BACKEND_MODE_EDITOR_CONTROLLER = 'Mail';
	const BACKEND_MODE_QUEUE = 'queue';
	const BACKEND_MODE_QUEUE_CONTROLLER = 'Queue';
67
	const BACKEND_MODE_QUEUE_NEWSLETTER = 'Newsletter';
68

69
70
	const CSV_EXPORT_BATCH_SIZE = 10000;

71
72
73
74
75
76
	/**
	 * Get all pages the be user has access to
	 *
	 * @return array
	 * @throws \InvalidArgumentException
	 */
77
	public static function getPages(): array {
78
		$out = [];
79
80
81
82
83
84

		$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
		$queryBuilder = $connectionPool->getQueryBuilderForTable('pages');
		$queryBuilder->getRestrictions()
			->removeAll()
			->add(GeneralUtility::makeInstance(DeletedRestriction::class));
85
		$queryBuilder->select('*')
86
87
			->from('pages')
			->where(
88
89
90
91
92
93
94
				$queryBuilder->expr()->andX(
					$queryBuilder->expr()->eq(
						'sys_language_uid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
					),
					$queryBuilder->expr()->eq(
						'is_siteroot', $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT)
					)
95
				)
96
97
			);
		$rows = $queryBuilder->execute()->fetchAll();
98

99
100
101
		foreach ($rows as $row) {
			$pageInfo = BackendUtility::readPageAccess($row['uid'], $GLOBALS['BE_USER']->getPagePermsClause(1));
			if ($pageInfo) {
102
				$rootline = BackendUtility::BEgetRootLine($pageInfo['uid'], '', TRUE);
103
104
				ksort($rootline);
				$path = '/root';
105
				foreach ($rootline as $page) {
106
107
108
					$path .= '/p' . dechex($page['uid']);
				}
				$pageInfo['path'] = $path;
109
110
111
112
				$out[] = $pageInfo;
			}
		}
		return $out;
113
114
115
116
117
118
119
120
121
122
	}

	/**
	 * create buttons for the backend module header
	 *
	 * @param DocHeaderComponent $docHeaderComponent
	 * @param Request $request
	 * @throws \InvalidArgumentException
	 * @throws \UnexpectedValueException
	 */
123
	public static function makeButtons($docHeaderComponent, $request) {
124
125
126
127
		/** @var ButtonBar $buttonBar */
		$buttonBar = $docHeaderComponent->getButtonBar();
		/** @var IconFactory $iconFactory */
		$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
Kevin Ditscheid's avatar
Kevin Ditscheid committed
128
		$locallangPath = 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:';
129
130
131
		// Refresh
		$refreshButton = $buttonBar->makeLinkButton()
			->setHref(GeneralUtility::getIndpEnv('REQUEST_URI'))
132
133
			->setTitle(
				LocalizationUtility::translate(
134
					$locallangPath . 'labels.reload'
135
136
				)
			)
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
			->setIcon($iconFactory->getIcon('actions-refresh', Icon::SIZE_SMALL));
		$buttonBar->addButton($refreshButton, ButtonBar::BUTTON_POSITION_RIGHT);

		// shortcut button
		$shortcutButton = $buttonBar->makeShortcutButton()
			->setModuleName($request->getPluginName())
			->setGetVariables(
				[
					'id',
					'M'
				]
			)
			->setSetVariables([]);

		$buttonBar->addButton($shortcutButton, ButtonBar::BUTTON_POSITION_RIGHT);
	}
Torsten Oppermann's avatar
Torsten Oppermann committed
153
154
155
156

	/**
	 * Retrieves the next site root in the page hierarchy from the current page
	 *
Torsten Oppermann's avatar
Torsten Oppermann committed
157
	 * @param int $currentPid
158
	 * @return int
Torsten Oppermann's avatar
Torsten Oppermann committed
159
	 */
160
	public static function getSiteRoot($currentPid): int {
Torsten Oppermann's avatar
Torsten Oppermann committed
161
162
		$rootLine = BackendUtility::BEgetRootLine((int) $currentPid);
		$siteRoot = ['uid' => 0];
163

Torsten Oppermann's avatar
Torsten Oppermann committed
164
		foreach ($rootLine as $page) {
165
			if ((int) $page['is_siteroot'] === 1) {
Torsten Oppermann's avatar
Torsten Oppermann committed
166
167
168
169
170
				$siteRoot = $page;
				break;
			}
		}

171
		return (int) $siteRoot['uid'];
Torsten Oppermann's avatar
Torsten Oppermann committed
172
	}
173

174
175
176
177
178
179
180
181
182
183
184
185
	/**
	 * Get the selected templates for the selected language
	 *
	 * @param string $selectedExtension
	 * @param string $selectedTemplate
	 * @param array $languages
	 * @param int $pid
	 * @return array
	 * @throws \InvalidArgumentException
	 */
	public static function getSelectedTemplates(
		$selectedExtension, $selectedTemplate, array $languages, $pid
186
	): array {
187
		$selectedTemplates = [];
188
189
190
191
192
193
194
		if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '10.4.0', '<')) {
			$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
			/** @var TemplateRepository $templateRepository */
			$templateRepository = $objectManager->get(TemplateRepository::class);
		} else {
			$templateRepository = GeneralUtility::makeInstance(TemplateRepository::class);
		}
195
196
197
198
199
200
201
202
203
204

		foreach ($languages as $language) {
			$selectedTemplates[$language['isocode']] = $templateRepository->findOneByTemplate(
				$selectedExtension, $selectedTemplate, $language['isocode'], $pid
			);
		}

		return $selectedTemplates;
	}

205
206
207
208
209
210
	/**
	 * get an array of all the locales for the activated languages
	 *
	 * @return array
	 * @throws \InvalidArgumentException
	 */
211
	public static function getLanguages(): array {
212
213
		$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_language');
		$rows = $queryBuilder->select('*')
214
			->from('sys_language')->execute()->fetchAll();
215

216
217
		$languages = [];

218
		// adding default language
219
		$languages[] = ['uid' => 0, 'isocode' => MailTemplateService::DEFAULT_LANGUAGE, 'name' => LocalizationUtility::translate(
220
			'backend.language_default', 'SgMail'
221
		)];
222

223
		foreach ($rows as $language) {
224
225
226
227
228
			$languages[] = [
				'uid' => $language['uid'],
				'isocode' => $language['language_isocode'],
				'name' => $language['title']
			];
229
230
231
232
		}

		return $languages;
	}
233

234
235
236
	/**
	 * get an array of all the labels for the activated languages
	 *
237
	 * @param array $languages
238
239
240
	 * @return array
	 * @throws \InvalidArgumentException
	 */
241
	public static function getLanguageLabels(array $languages): array {
242
		$languageLabels = [];
243

244
245
		foreach ($languages as $language) {
			$languageLabels[$language['isocode']] = $language['name'];
246
247
		}

248
		return $languageLabels;
249
250
	}

251
252
253
254
255
256
	/**
	 * Returns the language array by a given ISO Code or NULL if it doesn't find
	 *
	 * @param $isoCode
	 * @return array|null
	 */
257
	public static function getLanguageByISOCode(string $isoCode) {
258
259
260
261
262
263
264
265
266
		$languages = static::getLanguages();
		foreach ($languages as $language) {
			if ($language['isocode'] === $isoCode) {
				return $language;
			}
		}
		return NULL;
	}

267
268
269
270
271
272
	/**
	 * Get the languages in an array suitable for filtering
	 *
	 * @return array
	 * @throws \InvalidArgumentException
	 */
273
	public static function getLanguagesForFilter(): array {
274
		$languages = self::getLanguages();
275
		array_unshift($languages, ['isocode' => '', 'name' => '']);
276

277
		$filterLanguages = [];
278
		if (\count($languages) > 0) {
279
280
			foreach ($languages as $language) {
				$filterLanguages[$language['isocode']] = $language['name'];
281
			}
282
283
284
285
286
287
		}

		return $filterLanguages;
	}

	/**
288
	 * Get the template keys in an array suitable for filtering
289
	 *
290
	 * @param int $pageUid
291
	 * @return array
292
293
294
	 * @throws \InvalidArgumentException
	 * @throws \BadFunctionCallException
	 * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
295
	 */
296
297
	public static function getTemplatesForFilter($pageUid): array {
		$pageUid = (int) $pageUid;
298
		$registerArray = self::getNonBlacklistedTemplates($pageUid);
299
		$templates = [];
300
		$registerService = GeneralUtility::makeInstance(RegisterService::class);
301
302
		foreach ($registerArray as $extensions) {
			foreach ($extensions as $template => $key) {
303
304
305
306
				$templates[$key['extension']][] = [
					'name' => $key['templateName'],
					'is_manual' => $registerService->isManuallyRegisteredTemplate($key['templateName']),
				];
307
308
309
310
311
312
			}
		}
		array_unshift($templates, '');

		return $templates;
	}
313
314
315
316
317
318
319
320
321
322
323
324
325
326

	/**
	 * Save or update the template in the DB, depending if it already exists or not
	 *
	 * @param int $pid
	 * @param string $selectedExtension
	 * @param string $selectedTemplate
	 * @param string $language
	 * @param array $templateData
	 * @return Template $template
	 * @throws \InvalidArgumentException
	 * @throws \TYPO3\CMS\Extbase\Persistence\Exception\IllegalObjectTypeException
	 * @throws \TYPO3\CMS\Extbase\Persistence\Exception\UnknownObjectException
	 */
327
328
	public static function saveTemplate($pid, $selectedExtension, $selectedTemplate, $language, $templateData
	): Template {
329
330
331
332
333
334
335
		if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '10.4.0', '<')) {
			$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
			/** @var TemplateRepository $templateRepository */
			$templateRepository = $objectManager->get(TemplateRepository::class);
		} else {
			$templateRepository = GeneralUtility::makeInstance(TemplateRepository::class);
		}
336
337
338
339
340
341
342
343
344

		/** @var Template $template */
		$template = $templateRepository->findOneByTemplate(
			$selectedExtension, $selectedTemplate, $language, $pid
		);

		$templateAlreadyExists = TRUE;
		if ($template === NULL) {
			$templateAlreadyExists = FALSE;
345
346
347
348
349
350
			if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '10.4.0', '<')) {
				/** @var Template $template */
				$template = $objectManager->get(Template::class);
			} else {
				$template = GeneralUtility::makeInstance(Template::class);
			}
351
352
353
354
355
		}

		$template->setExtensionKey($selectedExtension);
		$template->setTemplateName($selectedTemplate);
		$template->setLanguage($language);
Paul Ilea's avatar
Paul Ilea committed
356
		$template->setLayout($templateData['layout']);
357
358
359
360
361
362
363
		$template->setContent($templateData['content']);
		$template->setSubject($templateData['subject']);
		$template->setFromName($templateData['fromName']);
		$template->setFromMail($templateData['fromMail']);
		$template->setCc($templateData['cc']);
		$template->setBcc($templateData['bcc']);
		$template->setReplyTo($templateData['replyTo']);
364
		$template->setToAddress($templateData['toAddress']);
365
366
367
368
369
370
371

		if ($templateAlreadyExists) {
			$templateRepository->update($template);
		} else {
			$templateRepository->add($template);
		}

372
		$templateRepository->persist();
373
374
		return $template;
	}
375

Torsten Oppermann's avatar
Torsten Oppermann committed
376
377
378
379
	/**
	 * Generate a csv string from the queues, respecting the given filters
	 *
	 * @param array $filters
Georgi Mateev's avatar
Georgi Mateev committed
380
	 * @return void
Torsten Oppermann's avatar
Torsten Oppermann committed
381
382
383
	 * @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException
	 * @throws \InvalidArgumentException
	 */
Georgi Mateev's avatar
Georgi Mateev committed
384
	public static function writeCsvFromQueue(array $filters = []) {
385
386
387
388
389
390
391
		if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '10.4.0', '<')) {
			$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
			/** @var MailRepository $mailRepository */
			$mailRepository = $objectManager->get(MailRepository::class);
		} else {
			$mailRepository = GeneralUtility::makeInstance(MailRepository::class);
		}
Torsten Oppermann's avatar
Torsten Oppermann committed
392
393

		$pageUid = (int) GeneralUtility::_GP('id');
Georgi Mateev's avatar
Georgi Mateev committed
394
395
396
397
		$ignoreFields = ['uid', 'pid', 'tstamp',
			'password', 'starttime', 'endtime', 'deleted', 'sent', 'priority', 'crdate', 'cruser_id', 'hidden'];
		$dateFields = ['tstamp', 'starttime', 'endtime', 'crdate'];

398
		$doctrineQuery = $mailRepository->getDoctrineQueryForFindAllEntries($pageUid, 0, $filters);
Georgi Mateev's avatar
Georgi Mateev committed
399
400
401
402
403
404
405
		$doctrineQuery->count('*');
		$totalRows = $doctrineQuery->execute()->fetchColumn(0);
		$doctrineQuery->select('*');

		$offset = 0;
		$first = TRUE;
		$handle = fopen('php://output', 'w+');
Fabian Galinski's avatar
Fabian Galinski committed
406
		while ($offset + self::CSV_EXPORT_BATCH_SIZE <= $totalRows) {
Georgi Mateev's avatar
Georgi Mateev committed
407
			$doctrineQuery->setFirstResult($offset);
Fabian Galinski's avatar
Fabian Galinski committed
408
			$doctrineQuery->setMaxResults(self::CSV_EXPORT_BATCH_SIZE);
Georgi Mateev's avatar
Georgi Mateev committed
409
410
411
			$rows = $doctrineQuery->execute();
			while ($mail = $rows->fetch()) {
				if ($first) { // Write column headers before the first row
Torsten Oppermann's avatar
Torsten Oppermann committed
412
					$first = FALSE;
Georgi Mateev's avatar
Georgi Mateev committed
413
					$row = [];
Torsten Oppermann's avatar
Torsten Oppermann committed
414
					foreach ($mail as $field => $value) {
415
						if (!\in_array($field, $ignoreFields, TRUE)) {
Torsten Oppermann's avatar
Torsten Oppermann committed
416
417
418
419
420
							$label = isset($GLOBALS['TCA']['tx_sgmail_domain_model_mail']['columns'][$field]) ?
								$GLOBALS['TCA']['fe_users']['columns'][$field]['label'] : '';
							if (strpos($label, 'LLL:') === 0) {
								$label = $GLOBALS['LANG']->sL($label);
							}
Georgi Mateev's avatar
Georgi Mateev committed
421
							$row[] = '"' . ($label ?: $field) . '"';
Torsten Oppermann's avatar
Torsten Oppermann committed
422
423
						}
					}
Kevin Ditscheid's avatar
Kevin Ditscheid committed
424

Georgi Mateev's avatar
Georgi Mateev committed
425
426
427
428
429
430
431
432
433
					fwrite(
						$handle, trim(
							preg_replace(
								'/\s\s+/', ' ', strip_tags(
									implode(',', $row) . ';' . LF
								)
							)
						)
					);
Torsten Oppermann's avatar
Torsten Oppermann committed
434
				}
Kevin Ditscheid's avatar
Kevin Ditscheid committed
435

Georgi Mateev's avatar
Georgi Mateev committed
436
				$row = [];
Torsten Oppermann's avatar
Torsten Oppermann committed
437
				foreach ($mail as $field => $value) {
438
439
					if (!\in_array($field, $ignoreFields, TRUE)) {
						if (\in_array($field, $dateFields, TRUE)) {
Georgi Mateev's avatar
Georgi Mateev committed
440
							$row[] = '"' . ($value ? date('d.m.Y', $value) : '') . '"';
Torsten Oppermann's avatar
Torsten Oppermann committed
441
						} else {
Georgi Mateev's avatar
Georgi Mateev committed
442
							$row[] = '"' . (string) $value . '"';
Torsten Oppermann's avatar
Torsten Oppermann committed
443
444
445
						}
					}
				}
Kevin Ditscheid's avatar
Kevin Ditscheid committed
446

Georgi Mateev's avatar
Georgi Mateev committed
447
448
449
450
451
452
453
454
455
				fwrite(
					$handle, trim(
						preg_replace(
							'/\s\s+/', ' ', strip_tags(
								implode(',', $row) . ';' . LF
							)
						)
					)
				);
Torsten Oppermann's avatar
Torsten Oppermann committed
456
			}
Kevin Ditscheid's avatar
Kevin Ditscheid committed
457

Fabian Galinski's avatar
Fabian Galinski committed
458
			$offset += self::CSV_EXPORT_BATCH_SIZE;
Torsten Oppermann's avatar
Torsten Oppermann committed
459
460
		}
	}
461
462
463
464
465
466
467
468

	/**
	 * Filter the register array to have only whitelisted templates for this domain
	 *
	 * @param int $siteRootId
	 * @throws \InvalidArgumentException
	 * @throws \BadFunctionCallException
	 * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
469
	 * @return array
470
	 */
471
	public static function getNonBlacklistedTemplates($siteRootId): array {
472
		$siteRootId = (int) $siteRootId;
473
474
475

		$registerService = GeneralUtility::makeInstance(RegisterService::class);
		$registerArray = $registerService->getRegisterArray();
Kevin Ditscheid's avatar
Kevin Ditscheid committed
476
		$extensionConfiguration = $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['sg_mail'] ?? [];
477
478
479
480
481
482
483
484
485
		if (isset($extensionConfiguration['excludeTemplates']) && $extensionConfiguration['excludeTemplates'] !== '') {
			$excludedTemplatesWithSiteId = GeneralUtility::trimExplode(
				';', $extensionConfiguration['excludeTemplates'], TRUE
			);

			foreach ($excludedTemplatesWithSiteId as $currentSite) {
				$currentSiteBlacklist = GeneralUtility::trimExplode(',', $currentSite, TRUE);
				if ((int) $currentSiteBlacklist[0] === $siteRootId) {
					foreach ($currentSiteBlacklist as $excludedTemplate) {
486
						[$extensionKey, $templateName] = GeneralUtility::trimExplode('.', $excludedTemplate);
487
488
489
490
491
492
493
494
						if ($extensionKey && $templateName && isset($registerArray[$extensionKey][$templateName])) {
							unset($registerArray[$extensionKey][$templateName]);
						}
					}
				}
			}
		}

495
		// filter out excluded templates from all domains
496
497
498
		if (isset($extensionConfiguration['excludeTemplatesAllDomains']) &&
			$extensionConfiguration['excludeTemplatesAllDomains'] !== ''
		) {
499
			$excludedTemplates = GeneralUtility::trimExplode(
500
				',', $extensionConfiguration['excludeTemplatesAllDomains'], TRUE
501
502
			);
			foreach ($excludedTemplates as $excludedTemplate) {
503
				[$extensionKey, $templateName] = GeneralUtility::trimExplode('.', $excludedTemplate);
504
505
506
507
508
509
				if ($extensionKey && $templateName && isset($registerArray[$extensionKey][$templateName])) {
					unset($registerArray[$extensionKey][$templateName]);
				}
			}
		}

510
511
		return $registerArray;
	}
512
513
514

	/**
	 * Fetches an array of FE_Users by groups
515
	 *
516
	 * @param array $groupIds
517
	 * @return \Doctrine\DBAL\Driver\Statement|int|\TYPO3\CMS\Core\Database\Query\QueryBuilder
518
519
520
521
522
	 */
	public static function getRecipientsByGroups(array $groupIds) {
		if (count($groupIds) < 1) {
			return [];
		}
523
524
		$frontendUserGroupRepository =  GeneralUtility::makeInstance(FrontendUserGroupRepository::class);
		$groupIds = $frontendUserGroupRepository->getFullGroupIdsWithChildren($groupIds);
525
526
527
528
529
530
531
		$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
		$queryBuilder = $connectionPool->getQueryBuilderForTable('fe_users');
		$queryBuilder->select('*')
			->from('fe_users');
		foreach ($groupIds as $groupId) {
			$queryBuilder->orWhere($queryBuilder->expr()->inSet('usergroup', (int) $groupId));
		}
532
		$queryBuilder->groupBy('email');
533
		return $queryBuilder->execute();
534
	}
535
}