Skip to content
Snippets Groups Projects
Commit ceb8804f authored by Kevin Kaland's avatar Kevin Kaland
Browse files

Issue #2359213: WIP Implement file saving.

The saving part is almost working. Need to get FillPDF file contexts
being saved properly. Then, I need to ensure that access
checking on the files works like I expect. I will probably have to
implement some hook or another.
parent 08be2cad
No related branches found
No related tags found
No related merge requests found
......@@ -20,3 +20,11 @@ services:
plugin.manager.fillpdf_action.processor:
class: Drupal\fillpdf\Plugin\FillPdfActionPluginManager
parent: default_plugin_manager
fillpdf.output_handler:
class: Drupal\fillpdf\OutputHandler
arguments: ['@token', '@logger.channel.fillpdf']
logger.channel.fillpdf:
parent: logger.channel_base
arguments: ['fillpdf']
......@@ -47,7 +47,15 @@ class FillPdf {
}
/**
* Constructs a URI to FillPDF's default files location given a relative path.
* Constructs a URI to a location given a relative path.
*
* @param string $scheme
* A valid stream wrapper, such as 'public' or 'private'
*
* @param $path
* The path component that should come after the stream wrapper.
*
* @return string
*/
public static function buildFileUri($scheme, $path) {
$uri = $scheme . '://' . $path;
......
......@@ -126,18 +126,15 @@ class HandlePdfController extends ControllerBase {
// @todo: What if one fill pattern has tokens from multiple types in it? Figure out the best way to deal with that and rewrite this section accordingly. Probably some form of parallel arrays. Basically we'd have to run all combinations, although our logic still might not be smart enough to tell if *all* tokens in the source text have been replaced, or in which case both of them have been replaced last (which is what we want). I could deliberately pass each entity context separately and then count how many of them match, and only overwrite it if the match count is higher than the current one. Yeah, that's kind of inefficient but also a good start. I might just be able to scan for tokens myself and then check if they're still in the $uncleaned_base output, or do the cleaning myself so I only have to call Token::replace once. TBD.
$field_pattern = $field->value->value;
$maybe_replaced_string = $this->token->replace($field_pattern, [
$entity_type => $entity
$entity_type => $entity,
], [
'clean' => TRUE,
'sanitize' => FALSE,
]);
// Generate a non-cleaned version of the token string so we can
// tell if the non-empty string we got back actually replaced
// some tokens.
$uncleaned_base = $this->token->replace($field_pattern, [
$entity_type => $entity
], [
'sanitize' => FALSE,
$entity_type => $entity,
]);
// If we got a result that isn't what we put in, update the value
......@@ -156,7 +153,7 @@ class HandlePdfController extends ControllerBase {
// @todo: When Rules integration ported, emit an event or whatever.
// TODO: figure out what to do about $token_objects. Should I make buildObjects manually re-run everything or just use the final entities passed of each type? Maybe just the latter, since that is what I do in
// TODO: figure out what to do about $token_objects. Should I make buildFilename manually re-run everything or just use the final entities passed of each type? Maybe just the latter, since that is what I do in
$action_response = $this->handlePopulatedPdf($fillpdf_form, $populated_pdf, $context, []);
return $action_response;
......@@ -221,7 +218,7 @@ class HandlePdfController extends ControllerBase {
'context' => $context,
'token_objects' => $token_objects,
'data' => $pdf_data,
'generated_filename' => $output_name,
'filename' => $output_name,
];
/** @var FillPdfActionPluginInterface $fillpdf_action */
......
<?php
/**
* @file
* Contains \Drupal\fillpdf\OutputHandler.
*/
namespace Drupal\fillpdf;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Utility\Token;
use Drupal\file\FileInterface;
use Drupal\file\FileUsage\FileUsageInterface;
use Drupal\fillpdf\Component\Utility\FillPdf;
use Drupal\fillpdf\Entity\FillPdfForm;
use Psr\Log\LoggerInterface;
/**
* Class OutputHandler.
*
* @package Drupal\fillpdf
*/
class OutputHandler implements OutputHandlerInterface {
use StringTranslationTrait;
/** @var Token $token */
protected $token;
/** @var \Psr\Log\LoggerInterface $logger */
protected $logger;
/** @var \Drupal\file\FileUsage\FileUsageInterface $fileUsage */
protected $fileUsage;
/**
* OutputHandler constructor.
*/
public function __construct(FileUsageInterface $file_usage, Token $token, LoggerInterface $logger) {
$this->fileUsage = $file_usage;
$this->token = $token;
$this->logger = $logger;
}
/**
* {@inheritdoc}
*/
public function savePdfToFile($pdf_data, array $context, $destination_path_override = NULL) {
/** @var FillPdfForm $fillpdf_form */
$fillpdf_form = $context['form'];
/** @var array $token_objects */
$token_objects = $context['token_objects'];
$destination_path = 'fillpdf';
if (!empty($fillpdf_form->destination_path->value)) {
$destination_path = "fillpdf/{$destination_path}";
}
if (!empty($destination_path_override)) {
$destination_path = "fillpdf/{$destination_path_override}";
}
$resolved_destination_path = $this->processDestinationPath($destination_path, $token_objects, $fillpdf_form->scheme->value);
$path_exists = file_prepare_directory($resolved_destination_path, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
$saved_file = FALSE;
if ($path_exists === FALSE) {
$this->logger->critical($this->t("The path %destination_path does not exist and could not be
automatically created. Therefore, the previous submission was not saved. If
the URL contained download=1, then the PDF was still sent to the user's browser.
If you were redirecting them to the PDF, they were sent to the homepage instead.
If the destination path looks wrong and you have used tokens, check that you have
used the correct token and that it is available to FillPDF at the time of PDF
generation.",
['%destination_path' => $resolved_destination_path]));
}
else {
// Full steam ahead!
$saved_file = file_save_data($pdf_data, "{$resolved_destination_path}/{$context['filename']}", FILE_EXISTS_RENAME);
$this->addFileUsage($saved_file, 'fillpdf_file');
}
return $saved_file;
}
/**
* @param string $destination_path
* @param array $token_objects
* @param string $scheme
* @return string
*/
protected function processDestinationPath($destination_path, $token_objects, $scheme = 'public') {
$orig_path = $destination_path;
$destination_path = trim($orig_path);
// Replace any applicable tokens
$types = [];
if (isset($token_objects['node'])) {
$types[] = 'node';
}
elseif (isset($token_objects['webform'])) {
$types[] = 'webform';
}
// TODO: Do this kind of replacement with a common service instead, because I'm doing the same thing in like 3 places now.
foreach ($types as $type) {
$destination_path = $this->token->replace($destination_path, [$type => $token_objects[$type]], ['clear' => TRUE]);
}
// Slap on the files directory in front and return it
$destination_path = FillPdf::buildFileUri($scheme, $destination_path);
return $destination_path;
}
/**
* @param \Drupal\file\FileInterface $fillpdf_file
* @param array $context
* An array of the entities that were used to generate this file.
*/
protected function addFileUsage(FileInterface $fillpdf_file, array $context) {
// TODO: Add FillPdfFileContext content entity
// $fillpdf_file_context = FillPdfFileContext::create();
// $this->fileUsage->add($fillpdf_file, 'fillpdf', 'fillpdf_file', $fillpdf_file_context->fcid);
}
}
<?php
/**
* @file
* Contains \Drupal\fillpdf\OutputHandlerInterface.
*/
namespace Drupal\fillpdf;
use Drupal\file\Entity\File;
/**
* Contains functions to standardize output handling for generated PDFs.
*
* @package Drupal\fillpdf
*/
interface OutputHandlerInterface {
/**
* @param string $pdf_data
* A string containing the full contents of the PDF to be saved.
* @param array $context
* An array containing the following properties:
* form: The FillPdfForm object from which the PDF was generated.
* context: The FillPDF request context as returned by
* \Drupal\fillpdf\FillPdfLinkParserInterface.
* token_objects: The token data from which the PDF was generated.
* data: The populated PDF data itself.
* filename: The filename (not including path) with which
* the PDF should be presented.
*
* @param string $destination_path_override
* @return bool|\Drupal\file\Entity\File
*/
public function savePdfToFile($pdf_data, array $context, $destination_path_override = NULL);
}
<?php
/**
* @file
* Contains \Drupal\fillpdf\Plugin\FillPdfActionPlugin\FillPdfDownloadAction.
*/
namespace Drupal\fillpdf\Plugin\FillPdfActionPlugin;
use Drupal\Core\Annotation\Translation;
use Drupal\fillpdf\Annotation\FillPdfActionPlugin;
use Drupal\fillpdf\OutputHandler;
use Drupal\fillpdf\Plugin\FillPdfActionPluginBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
/**
* Class FillPdfDownloadAction
* @package Drupal\fillpdf\Plugin\FillPdfActionPlugin
*
* @FillPdfActionPlugin(
* id = "save",
* label = @Translation("Save PDF to file")
* )
*/
class FillPdfDownloadAction extends FillPdfActionPluginBase {
/** @var OutputHandler $outputHandler */
protected $outputHandler;
public function __construct(OutputHandler $output_handler, array $configuration, $plugin_id, $plugin_definition) {
$this->outputHandler = $output_handler;
parent::__construct($configuration, $plugin_id, $plugin_definition);
}
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static($container->get('fillpdf.output_handler'), $configuration, $plugin_id, $plugin_definition);
}
public function execute() {
// @todo: Error handling?
$this->outputHandler->savePdfToFile($this->configuration['data'], $this->configuration['context']);
// @todo: Fix based on value of post_save_redirect, once I add that
$response = new RedirectResponse('/');
return $response;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment