class EmailedExport in Webform Scheduled Tasks 8.2
A task which emails an export of a list of webforms.
Plugin annotation
@WebformScheduledTask(
id = "export_email_results",
label = @Translation("Export and email results"),
)
Hierarchy
- class \Drupal\Component\Plugin\PluginBase implements DerivativeInspectionInterface, PluginInspectionInterface
- class \Drupal\Core\Plugin\PluginBase uses DependencySerializationTrait, MessengerTrait, StringTranslationTrait
- class \Drupal\webform_scheduled_tasks\Plugin\WebformScheduledTasks\TaskPluginBase implements TaskPluginInterface
- class \Drupal\webform_scheduled_tasks\Plugin\WebformScheduledTasks\Task\EmailedExport implements ContainerFactoryPluginInterface
- class \Drupal\webform_scheduled_tasks\Plugin\WebformScheduledTasks\TaskPluginBase implements TaskPluginInterface
- class \Drupal\Core\Plugin\PluginBase uses DependencySerializationTrait, MessengerTrait, StringTranslationTrait
Expanded class hierarchy of EmailedExport
3 files declare their use of EmailedExport
- EmailedExportFileDownloadAccessTest.php in tests/
src/ Functional/ EmailedExportFileDownloadAccessTest.php - EmailedExportTest.php in tests/
src/ Kernel/ EmailedExportTest.php - webform_scheduled_tasks.module in ./
webform_scheduled_tasks.module - Module file.
File
- src/
Plugin/ WebformScheduledTasks/ Task/ EmailedExport.php, line 29
Namespace
Drupal\webform_scheduled_tasks\Plugin\WebformScheduledTasks\TaskView source
class EmailedExport extends TaskPluginBase implements ContainerFactoryPluginInterface {
/**
* The submission exporter.
*
* @var \Drupal\webform\WebformSubmissionExporterInterface
*/
protected $exporter;
/**
* The exporter manager.
*
* @var \Drupal\webform\Plugin\WebformExporterManagerInterface
*/
protected $exporterManager;
/**
* The mail manager.
*
* @var \Drupal\Core\Mail\MailManagerInterface
*/
protected $mailManager;
/**
* The file usage service.
*
* @var \Drupal\file\FileUsage\FileUsageInterface
*/
protected $fileUsage;
/**
* @var \Drupal\Core\File\FileSystemInterface
*/
protected $fileSystem;
/**
* A storage type of sending an email.
*/
const STORAGE_TYPE_EMAIL = 'email';
/**
* A storage type of writing to the file system.
*/
const STORAGE_TYPE_FILESYSTEM = 'filesystem';
/**
* A destination directory for saved files.
*/
const DESTINATION_DIRECTORY = 'private://scheduled-exports';
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, WebformSubmissionExporterInterface $exporter, WebformExporterManagerInterface $exporterManager, MailManagerInterface $mailManager, FileUsageInterface $fileUsage, FileSystemInterface $fileSystem) {
$this->exporter = $exporter;
$this->exporterManager = $exporterManager;
$this->mailManager = $mailManager;
$this->fileUsage = $fileUsage;
$this->fileSystem = $fileSystem;
parent::__construct($configuration, $plugin_id, $plugin_definition);
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static($configuration, $plugin_id, $plugin_definition, $container
->get('webform_submission.exporter'), $container
->get('plugin.manager.webform.exporter'), $container
->get('plugin.manager.mail'), $container
->get('file.usage'), $container
->get('file_system'));
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
$exporter_options = $this->exporterManager
->getOptions();
return [
'exporter_settings' => [],
'exporter' => key($exporter_options),
'email_addresses' => '',
'storage_type' => static::STORAGE_TYPE_FILESYSTEM,
'delete_submissions' => FALSE,
'include_attachments' => TRUE,
];
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form['email_addresses'] = [
'#title' => $this
->t('Email addresses'),
'#required' => TRUE,
'#description' => $this
->t('Enter a list of email addresses to notify when the export is complete. The list should be coma separated values.'),
'#type' => 'textarea',
'#attributes' => [
'placeholder' => 'foo@example.com, bar@example.com',
],
'#default_value' => $this->configuration['email_addresses'],
];
$form['storage_type'] = [
'#type' => 'radios',
'#title' => $this
->t('Storage type'),
'#options' => [
static::STORAGE_TYPE_FILESYSTEM => $this
->t('Save to private filesystem'),
static::STORAGE_TYPE_EMAIL => $this
->t('Send as email attachment'),
],
'#default_value' => $this->configuration['storage_type'],
'#required' => TRUE,
'#description' => $this
->t('Select how the resulting file will be delivered to the configured users. Saving the file to the file system will generate a private file which only privileged roles will have access to.'),
];
$form['attachment_storage_type_warning'] = [
'#type' => 'webform_message',
'#message_type' => 'warning',
'#message_message' => $this
->t('<strong>Warning:</strong> Sending email file attachments requires webform to have already been configured for attachments. See <a target="_blank" href=":help">this help topic for more information</a>.', [
':help' => 'https://www.drupal.org/node/3021480',
]),
'#states' => [
'visible' => [
':input[name="task_settings[storage_type]"]' => [
'value' => 'email',
],
],
],
];
$form['delete_submissions'] = [
'#type' => 'checkbox',
'#title' => $this
->t('Delete submissions after export'),
'#default_value' => $this->configuration['delete_submissions'],
'#description' => $this
->t('Delete submissions after this task has been run.'),
];
$form['include_attachments'] = [
'#type' => 'checkbox',
'#title' => $this
->t('Include attachments'),
'#default_value' => $this->configuration['include_attachments'],
'#description' => $this
->t('Include attachments uploaded by users in the exported archive.'),
];
$this
->buildExportPluginForm($form, $form_state);
return $form;
}
/**
* Build the export plugin form.
*/
protected function buildExportPluginForm(&$form, FormStateInterface $form_state) {
$form['exporter'] = [
'#title' => $this
->t('Export format'),
'#type' => 'select',
'#options' => $this->exporterManager
->getOptions(),
'#default_value' => $this->configuration['exporter'],
'#ajax' => [
'callback' => [
static::class,
'ajaxCallback',
],
'wrapper' => 'exporter-settings',
],
];
$chosen_exporter = $form_state
->getValue('exporter', $this->configuration['exporter']);
$plugin = $this->exporterManager
->createInstance($chosen_exporter, $this
->getConfiguration()['exporter_settings']);
$form['exporter_settings'] = [
'#prefix' => '<div id="exporter-settings">',
'#suffix' => '</div>',
];
$subform_state = SubformState::createForSubform($form['exporter_settings'], $form, $form_state);
$form['exporter_settings'] += $plugin
->buildConfigurationForm($form['exporter_settings'], $subform_state);
}
/**
* An AJAX callback for the exporter_settings container.
*/
public static function ajaxCallback($form, FormStateInterface $form_state) {
return $form['task_settings']['exporter_settings'];
}
/**
* {@inheritdoc}
*/
public function executeTask(\Iterator $submissions) {
$exporter = $this
->initializeExporter();
$exporter
->writeHeader();
$processed_submission_ids = [];
foreach ($submissions as $submission) {
// @todo, ask that ::writeRecords accepts an iterator.
$exporter
->writeRecords([
$submission,
]);
$processed_submission_ids[] = $submission
->id();
}
$exporter
->writeFooter();
// This is required to add actual submission data file into the associated
// archive when files have been included inside the export.
if ($exporter
->isArchive()) {
$exporter
->writeExportToArchive();
}
// If no submissions were present while running this scheduled task, there
// is no need to send an export.
if (empty($processed_submission_ids)) {
return;
}
// Defensively catch errors in an export early to prevent any data loss,
// check the file exists and the file has some data in it.
if (!file_exists($this
->getExportFileOrArchivePath($exporter)) || filesize($this
->getExportFileOrArchivePath($exporter)) === 0) {
throw new HaltScheduledTaskException('Export files are failing to generate or are empty.');
}
// Archive tar can throw warnings and produce a corrupt tar file without
// ever throwing an exception, see d.o/project/drupal/issues/3026470.
// Attempt to be a bit more defensive about this and try to verify a valid
// archive was produced. With the php internal functions used to build the
// archiver throwing warnings, a big risk is silent failure. The current
// verification is a "best guess", given complexities around the number and
// size of various files which should be in the archive.
if ($exporter
->isArchive() && !$this
->verifyArchive($this
->getExportFileOrArchivePath($exporter))) {
throw new HaltScheduledTaskException('An invalid archive file was generated.');
}
$target_directory = static::DESTINATION_DIRECTORY;
if (!$this->fileSystem
->prepareDirectory($target_directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS)) {
throw new HaltScheduledTaskException('Could not create a directory for the exported files to be written to.');
}
$attempted_file_destination_uri = sprintf('%s/%s', static::DESTINATION_DIRECTORY, $this
->getExportFileOrArchiveName($exporter));
if (!($unique_destination_file_uri = $this->fileSystem
->copy($this
->getExportFileOrArchivePath($exporter), $attempted_file_destination_uri))) {
throw new HaltScheduledTaskException('Could not write the generated file to the private file system.');
}
// Save a file entity with the data so the file can be deleted from the UI
// if it has been downloaded.
$file = File::create([
'uri' => $unique_destination_file_uri,
'filename' => $this
->getExportFileOrArchiveName($exporter),
'status' => FILE_STATUS_PERMANENT,
]);
$file
->save();
// Register a file usage that tells Drupal the file URI was sent in an
// email, to prevent the make_unused_managed_files_temporary setting from
// deleting it. This will ensure the file sticks around until the user
// explicitly decides to delete the export.
$this->fileUsage
->add($file, 'webform_scheduled_tasks', 'email_uri', $this
->getScheduledTask()
->id());
// Add the export as an email attachment if the task is configured as such.
// Note, the file is still written to the private file system regardless, to
// ensure no data is lost in the case of dropped, rejected or undelivered
// mail.
$email_attachments = [];
if ($this->configuration['storage_type'] === static::STORAGE_TYPE_EMAIL) {
$email_attachments[] = [
'filecontent' => file_get_contents($file
->getFileUri()),
'filename' => $file
->getFilename(),
'filemime' => $file
->getMimeType(),
'filepath' => $this->fileSystem
->realpath($file
->getFileUri()),
];
}
$this
->emailRecipients('export_summary_filesystem', [
'file_url' => file_create_url($unique_destination_file_uri),
'task_id' => $this
->getScheduledTask()
->id(),
'attachments' => $email_attachments,
]);
if ($this->configuration['delete_submissions']) {
foreach ($processed_submission_ids as $submission_delete_id) {
WebformSubmission::load($submission_delete_id)
->delete();
}
}
}
/**
* Verify a tar file.
*
* @param string $file
* The path to the archive.
*
* @return bool
* Check if an archive is empty.
*/
protected function verifyArchive($file) {
// Reading the contents of the archive is enough to raise an exception in
// some cases and archives should always contain at least one file.
$archive = new ArchiveTar($file);
$content = $archive
->listContent();
return !empty($content);
}
/**
* Get the file or archive path, whichever is appropriate.
*
* @parram \Drupal\webform\WebformSubmissionExporterInterface $initializedExporter
* The initialized exporter.
*
* @return string
* A path to an archive or file.
*/
protected function getExportFileOrArchivePath(WebformSubmissionExporterInterface $initializedExporter) {
if ($initializedExporter
->isArchive()) {
return $initializedExporter
->getArchiveFilePath();
}
return $initializedExporter
->getExportFilePath();
}
/**
* Get the file or archive name, whichever is appropriate.
*
* @parram \Drupal\webform\WebformSubmissionExporterInterface $initializedExporter
* The initialized exporter.
*
* @return string
* The name of a file.
*/
protected function getExportFileOrArchiveName(WebformSubmissionExporterInterface $initializedExporter) {
if ($initializedExporter
->isArchive()) {
return $initializedExporter
->getArchiveFileName();
}
return $initializedExporter
->getExportFileName();
}
/**
* Email recipients of this task.
*
* @param string $key
* The mail key.
* @param array $params
* The mail params.
*/
protected function emailRecipients($key, array $params) {
foreach ($this
->getEmailAddresses() as $email_address) {
$this->mailManager
->mail('webform_scheduled_tasks', $key, $email_address, LanguageInterface::LANGCODE_DEFAULT, $params);
}
}
/**
* Get a list of email addresses configured for this plugin.
*
* @return string[]
* A list of email addresses to send the content to.
*/
protected function getEmailAddresses() {
return array_map('trim', explode(',', $this->configuration['email_addresses']));
}
/**
* Get an exporter with initialized settings.
*
* @return \Drupal\webform\WebformSubmissionExporterInterface
* The submissions exporter.
*/
protected function initializeExporter() {
// Set the scheduled task as the source entity so temporary files generated
// will be with the context of the scheduled task. This ensures if someone
// is generating an export from the UI, it does not use the same temporary
// filenames.
$this->exporter
->setWebform($this
->getScheduledTask()
->getWebform());
$this->exporter
->setSourceEntity($this
->getScheduledTask());
$exporter_options = $this->configuration['exporter_settings'] + [
'exporter' => $this->configuration['exporter'],
'files' => $this->configuration['include_attachments'],
];
$this->exporter
->setExporter($exporter_options);
return $this->exporter;
}
}
Members
Name![]() |
Modifiers | Type | Description | Overrides |
---|---|---|---|---|
DependencySerializationTrait:: |
protected | property | An array of entity type IDs keyed by the property name of their storages. | |
DependencySerializationTrait:: |
protected | property | An array of service IDs keyed by property name used for serialization. | |
DependencySerializationTrait:: |
public | function | 1 | |
DependencySerializationTrait:: |
public | function | 2 | |
EmailedExport:: |
protected | property | The submission exporter. | |
EmailedExport:: |
protected | property | The exporter manager. | |
EmailedExport:: |
protected | property | ||
EmailedExport:: |
protected | property | The file usage service. | |
EmailedExport:: |
protected | property | The mail manager. | |
EmailedExport:: |
public static | function | An AJAX callback for the exporter_settings container. | |
EmailedExport:: |
public | function |
Form constructor. Overrides TaskPluginBase:: |
|
EmailedExport:: |
protected | function | Build the export plugin form. | |
EmailedExport:: |
public static | function |
Creates an instance of the plugin. Overrides ContainerFactoryPluginInterface:: |
|
EmailedExport:: |
public | function |
Gets default configuration for this plugin. Overrides TaskPluginBase:: |
|
EmailedExport:: |
constant | A destination directory for saved files. | ||
EmailedExport:: |
protected | function | Email recipients of this task. | |
EmailedExport:: |
public | function |
Execute a task. Overrides TaskPluginInterface:: |
|
EmailedExport:: |
protected | function | Get a list of email addresses configured for this plugin. | |
EmailedExport:: |
protected | function | Get the file or archive name, whichever is appropriate. | |
EmailedExport:: |
protected | function | Get the file or archive path, whichever is appropriate. | |
EmailedExport:: |
protected | function | Get an exporter with initialized settings. | |
EmailedExport:: |
constant | A storage type of sending an email. | ||
EmailedExport:: |
constant | A storage type of writing to the file system. | ||
EmailedExport:: |
protected | function | Verify a tar file. | |
EmailedExport:: |
public | function |
TaskPluginBase constructor. Overrides TaskPluginBase:: |
|
MessengerTrait:: |
protected | property | The messenger. | 29 |
MessengerTrait:: |
public | function | Gets the messenger. | 29 |
MessengerTrait:: |
public | function | Sets the messenger. | |
PluginBase:: |
protected | property | Configuration information passed into the plugin. | 1 |
PluginBase:: |
protected | property | The plugin implementation definition. | 1 |
PluginBase:: |
protected | property | The plugin_id. | |
PluginBase:: |
constant | A string which is used to separate base plugin IDs from the derivative ID. | ||
PluginBase:: |
public | function |
Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface:: |
|
PluginBase:: |
public | function |
Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface:: |
|
PluginBase:: |
public | function |
Gets the definition of the plugin implementation. Overrides PluginInspectionInterface:: |
3 |
PluginBase:: |
public | function |
Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface:: |
|
PluginBase:: |
public | function | Determines if the plugin is configurable. | |
StringTranslationTrait:: |
protected | property | The string translation service. | 1 |
StringTranslationTrait:: |
protected | function | Formats a string containing a count of items. | |
StringTranslationTrait:: |
protected | function | Returns the number of plurals supported by a given language. | |
StringTranslationTrait:: |
protected | function | Gets the string translation service. | |
StringTranslationTrait:: |
public | function | Sets the string translation service to use. | 2 |
StringTranslationTrait:: |
protected | function | Translates a string to the current language or to a given language. | |
TaskPluginBase:: |
protected | property | The scheduled task this task is attached to. | |
TaskPluginBase:: |
public | function |
Calculates dependencies for the configured plugin. Overrides DependentPluginInterface:: |
|
TaskPluginBase:: |
public | function |
Gets this plugin's configuration. Overrides ConfigurableInterface:: |
|
TaskPluginBase:: |
public | function |
Get the scheduled task. Overrides ScheduledTaskAwarePluginInterface:: |
|
TaskPluginBase:: |
public | function |
Get the label of the task. Overrides TaskPluginInterface:: |
|
TaskPluginBase:: |
public | function |
Called when a task fails. Overrides ScheduledTaskNotifyInterface:: |
3 |
TaskPluginBase:: |
public | function |
Called when a task is successful. Overrides ScheduledTaskNotifyInterface:: |
3 |
TaskPluginBase:: |
public | function |
Sets the configuration for this plugin instance. Overrides ConfigurableInterface:: |
|
TaskPluginBase:: |
public | function |
Set the scheduled task as context for this plugin. Overrides ScheduledTaskAwarePluginInterface:: |
|
TaskPluginBase:: |
public | function |
Form submission handler. Overrides PluginFormInterface:: |
|
TaskPluginBase:: |
public | function |
Form validation handler. Overrides PluginFormInterface:: |