From 0facb18db3fac7f98bb3729a7d860ed5d23bdb32 Mon Sep 17 00:00:00 2001
From: Kevin Kaland <kevin@wizonesolutions.com>
Date: Thu, 20 Nov 2014 15:38:23 +0100
Subject: [PATCH] Convert main route...mostly.

I'm getting an error when I actually try to populate/download the PDF. There is probably
something wrong with the Request object I'm returning. But this is closer.
---
 fillpdf.module                                |  16 +-
 fillpdf.routing.yml                           |   2 +-
 src/Controller/HandlePdfController.php        | 161 ++++++++++++++++++
 src/FillPdfBackendManager.php                 |   3 +-
 src/FillPdfBackendManagerInterface.php        |  12 --
 src/FillPdfBackendPluginInterface.php         |   4 +-
 src/FillPdfLinkManipulatorInterface.php       |   1 +
 src/Form/FillPdfOverviewForm.php              |  37 ++--
 .../FillPdfServiceFillPdfBackend.php          |  21 ++-
 src/Service/FillPdfLinkManipulator.php        |  56 +++++-
 10 files changed, 262 insertions(+), 51 deletions(-)
 delete mode 100644 src/FillPdfBackendManagerInterface.php

diff --git a/fillpdf.module b/fillpdf.module
index 7373121..f0fb5e8 100644
--- a/fillpdf.module
+++ b/fillpdf.module
@@ -390,14 +390,14 @@ function fillpdf_merge_pdf($fid, $nids = NULL, $webform_arr = NULL, $sample = NU
 
   $pdf_data = _fillpdf_get_file_contents($fillpdf_info->url, "<front>");
   switch (variable_get('fillpdf_service')) {
-    case 'remote': // use fillpdf-service.com's xmlrpc service (must be registered)
-      $api_key = variable_get('fillpdf_api_key', '0');
-      $result = _fillpdf_xmlrpc_request(DEFAULT_SERVLET_URL, 'merge_pdf_v3', base64_encode($pdf_data), $fields, $api_key, $flatten, $image_data);
-      if ($result->error == TRUE) {
-        drupal_goto();
-      } // after setting error message
-      $data = base64_decode($result->data);
-      break;
+//    case 'remote': // use fillpdf-service.com's xmlrpc service (must be registered)
+//      $api_key = variable_get('fillpdf_api_key', '0');
+//      $result = _fillpdf_xmlrpc_request(DEFAULT_SERVLET_URL, 'merge_pdf_v3', base64_encode($pdf_data), $fields, $api_key, $flatten, $image_data);
+//      if ($result->error == TRUE) {
+//        drupal_goto();
+//      } // after setting error message
+//      $data = base64_decode($result->data);
+//      break;
 
     case 'local': // use local php/java bridge (must have Tomcat & JavaBridge installed on VPS or dedicated
       $require = drupal_get_path('module', 'fillpdf') . '/lib/JavaBridge/java/Java.inc';
diff --git a/fillpdf.routing.yml b/fillpdf.routing.yml
index 0d6ecd1..30790fb 100644
--- a/fillpdf.routing.yml
+++ b/fillpdf.routing.yml
@@ -21,6 +21,6 @@ fillpdf.forms_admin:
 fillpdf.populate_pdf:
   path: '/fillpdf'
   defaults:
-    _content: '\Drupal\fillpdf\Controller\HandlePdfController::populatePdf'
+    _controller: '\Drupal\fillpdf\Controller\HandlePdfController::populatePdf'
   requirements:
     _custom_access: '\Drupal\fillpdf\FillPdfAccessController::checkLink'
diff --git a/src/Controller/HandlePdfController.php b/src/Controller/HandlePdfController.php
index 9f873e9..c2b1c93 100644
--- a/src/Controller/HandlePdfController.php
+++ b/src/Controller/HandlePdfController.php
@@ -7,11 +7,172 @@
 namespace Drupal\fillpdf\Controller;
 
 use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\Entity\Query\QueryFactory;
+use Drupal\fillpdf\Entity\FillPdfForm;
+use Drupal\fillpdf\Entity\FillPdfFormField;
+use Drupal\fillpdf\FillPdfBackendManager;
+use Drupal\fillpdf\FillPdfBackendPluginInterface;
+use Drupal\fillpdf\FillPdfFormInterface;
+use Drupal\fillpdf\FillPdfLinkManipulatorInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\ResponseHeaderBag;
 
 class HandlePdfController extends ControllerBase {
 
+  /** @var FillPdfLinkManipulatorInterface $linkManipulator */
+  protected $linkManipulator;
+
+  /** @var FillPdfBackendManager $backendManager */
+  protected $backendManager;
+
+  /** @var QueryFactory $entityQuery */
+  protected $entityQuery;
+
+  public function __construct(FillPdfLinkManipulatorInterface $link_manipulator, RequestStack $request_stack, FillPdfBackendManager $backend_manager, QueryFactory $entity_query) {
+    $this->linkManipulator = $link_manipulator;
+    $this->requestStack = $request_stack;
+    $this->backendManager = $backend_manager;
+    $this->entityQuery = $entity_query;
+  }
+
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('fillpdf.link_manipulator'),
+      $container->get('request_stack'),
+      $container->get('plugin.manager.fillpdf_backend'),
+      $container->get('entity.query')
+    );
+  }
+
   public function populatePdf() {
+    $context = $this->linkManipulator->parseLink($this->requestStack->getCurrentRequest());
+
+    $config = $this->config('fillpdf.settings');
+    $fillpdf_service = $config->get('fillpdf_service_backend');
+
+    // Load the backend plugin.
+    /** @var FillPdfBackendPluginInterface $backend */
+    $backend = $this->backendManager->createInstance($fillpdf_service, $config->get());
+
+    // @todo: Emit event (or call alter hook?) before populating PDF. fillpdf_merge_fields_alter -> should be renamed to fillpdf_populate_fields_alter
+
     // TODO: Extract the PDF parameters, and act accordingly - this is pretty much just calling FillPdfBackendPluginInterface::populateWithFieldData and then reading the FillPdfForm options to determine whether to serve as a file download or fill in the field data.
+    $fillpdf_form = FillPdfForm::load($context['fid']);
+    if (!$fillpdf_form) {
+      drupal_set_message($this->t('FillPDF Form (fid) not found in the system. Please check the value in your FillPDF Link.'), 'error');
+    }
+
+    $fields = FillPdfFormField::loadMultiple(
+      $this->entityQuery->get('fillpdf_form_field')
+        ->condition('fillpdf_form', $fillpdf_form->id())
+        ->execute()
+    );
+
+    $populated_pdf = $backend->populateWithFieldData($fillpdf_form, $fields, $context);
+
+    // @todo: When Rules integration ported, emit an event or whatever.
+
+    // TODO: Take the appropriate action on the PDF.
+    $this->handlePopulatedPdf($fillpdf_form, $populated_pdf, array());
+  }
+
+  /**
+   * @todo: Split this up better. There are a few things happening here. 1) We look at the function arguments to determine the action to take. But there's also the concept of a default action. In that case, we have to look at the $context array. 2) The actual handling code for the action, e.g. transmitting a download and saving a file, optionally redirecting to a file. These should be usable at will, albeit specific to routes.
+   *
+   * Figure out what to do with the PDF and do it.
+   *
+   * @return Nothing.
+   * @param \Drupal\fillpdf\Controller\An|\Drupal\fillpdf\FillPdfFormInterface $fillpdf_form An object containing the loaded record from {fillpdf_forms}
+   * .
+   * @param $pdf_data A string containing the content of the merged PDF.
+   * @param array|\Drupal\fillpdf\Controller\An $token_objects An array of objects to be used in replacing tokens.
+   * Here, specifically, it's for generating the filename of the handled PDF.
+   * @param \Drupal\fillpdf\Controller\One|string $action One of the following keywords: default, download, save,
+   * redirect. These correspond to performing the configured action (from
+   * admin/structure/fillpdf/%), sending the PDF to the user's browser, saving it
+   * to a file, and saving it to a file and then redirecting the user's browser to
+   * the saved file.
+   * @param array|\Drupal\fillpdf\Controller\If $options If set, this function will always end the request by
+   * sending the filled PDF to the user's browser.
+   */
+  protected function handlePopulatedPdf(FillPdfFormInterface $fillpdf_form, $pdf_data, array $token_objects, $action = 'download', array $options = array()) {
+    // TODO: Convert rest of this function.
+    $force_download = FALSE;
+    if (!empty($option['force_download'])) {
+      $force_download = TRUE;
+    }
+
+    if (in_array($action, array('default', 'download', 'save', 'redirect')) === FALSE) {
+      // Do nothing if the function is called with an invalid action.
+      return;
+    }
+    // Generate the filename of downloaded PDF from title of the PDF set in
+    // admin/structure/fillpdf/%fid
+    $output_name = $this->buildFilename($fillpdf_form->title->value, $token_objects);
+
+    // Now that we have a filename, we can build the response we're most likely
+    // to deliver. Content-Disposition is set further down in the actual
+    // download case. If it turns out to be a redirect, then we replace this
+    // variable with a RedirectResponse.
+    $response = new Response($pdf_data);
+
+    if ($action == 'default') {
+      // Determine the default action, then re-set $action to that.
+      if (empty($fillpdf_form->destination_path) === FALSE) {
+        $action = 'save';
+      }
+      else {
+        $action = 'download';
+      }
+    }
+
+    // Initialize variable containing whether or not we send the user's browser to
+    // the saved PDF after saving it (if we are)
+    $redirect_to_file = FALSE;
+
+    // Get a load of this switch...they all just fall through!
+    switch ($action) {
+      case 'redirect':
+        $redirect_to_file = $fillpdf_form->destination_redirect;
+      case 'save':
+        fillpdf_save_to_file($fillpdf_form, $pdf_data, $token_objects, $output_name, !$options, $redirect_to_file);
+      // FillPDF classic!
+      case 'download':
+//        drupal_add_http_header("Pragma", "public");
+//        drupal_add_http_header('Expires', 0);
+//        drupal_add_http_header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0');
+//        drupal_add_http_header('Content-type', 'application-download');
+//        // This must be strlen(), not drupal_strlen() because the length in bytes,
+//        // not in characters, is what is needed here.
+//        drupal_add_http_header('Content-Length', strlen($pdf_data));
+//        drupal_add_http_header('Content-disposition', 'attachment; filename="' . $output_name . '"');
+//        drupal_add_http_header('Content-Transfer-Encoding', 'binary');
+//        echo $pdf_data;
+//        drupal_exit();
+        $disposition = $response->headers->makeDisposition(
+          ResponseHeaderBag::DISPOSITION_ATTACHMENT,
+          $output_name
+        );
+        $response->headers->set('Content-Disposition', $disposition);
+        break;
+    }
+
+    return $response;
+  }
+
+  public function buildFilename($original, array $token_objects) {
+    // Replace tokens *before* sanitization
+    if (!empty($token_objects)) {
+      $original = token_replace($original, $token_objects);
+    }
+
+    $output_name = str_replace(' ', '_', $original);
+    $output_name = preg_replace('/\.pdf$/i', '', $output_name);
+    $output_name = preg_replace('/[^a-zA-Z0-9_.-]+/', '', $output_name) . '.pdf';
+
+    return $output_name;
   }
 
 }
diff --git a/src/FillPdfBackendManager.php b/src/FillPdfBackendManager.php
index 3005606..8636c7f 100644
--- a/src/FillPdfBackendManager.php
+++ b/src/FillPdfBackendManager.php
@@ -12,7 +12,7 @@ use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Language\LanguageManager;
 use Drupal\Core\Plugin\DefaultPluginManager;
 
-class FillPdfBackendManager extends DefaultPluginManager implements FillPdfBackendManagerInterface {
+class FillPdfBackendManager extends DefaultPluginManager {
 
   /**
    * Constructs a FillPdfBackendManager object.
@@ -28,7 +28,6 @@ class FillPdfBackendManager extends DefaultPluginManager implements FillPdfBacke
    *   The module handler to invoke the alter hook with.
    */
   public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
-    // @todo: Add a future FillPdfBackendPluginInterface to this?
     parent::__construct('Plugin/FillPdfBackend', $namespaces, $module_handler, '\Drupal\fillpdf\FillPdfBackendPluginInterface');
     $this->alterInfo('fillpdf_backend_info');
     $this->setCacheBackend($cache_backend, 'fillpdf_backend_info_plugins');
diff --git a/src/FillPdfBackendManagerInterface.php b/src/FillPdfBackendManagerInterface.php
deleted file mode 100644
index db2e14b..0000000
--- a/src/FillPdfBackendManagerInterface.php
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-/**
-  * @file
-  * Contains \Drupal\fillpdf\FillPdfBackendManagerInterface.
-  */
-
-
-namespace Drupal\fillpdf;
-
-interface FillPdfBackendManagerInterface {
-
-}
diff --git a/src/FillPdfBackendPluginInterface.php b/src/FillPdfBackendPluginInterface.php
index 61ee715..23450b7 100644
--- a/src/FillPdfBackendPluginInterface.php
+++ b/src/FillPdfBackendPluginInterface.php
@@ -38,9 +38,9 @@ interface FillPdfBackendPluginInterface {
    *     'Field 1' => 'value',
    *     'Checkbox Field' => 'On',
    *   );
-   * @param array $options @todo: Define
+   * @param array $context @todo: Define
    * @return string The raw file contents of the new PDF; the caller has to
    *   handle saving or serving the file accordingly.
    */
-  public function populateWithFieldData(FillPdfFormInterface $pdf_form, array $fields, array $options);
+  public function populateWithFieldData(FillPdfFormInterface $pdf_form, array $fields, array $context);
 }
diff --git a/src/FillPdfLinkManipulatorInterface.php b/src/FillPdfLinkManipulatorInterface.php
index 3d86751..26efd4f 100644
--- a/src/FillPdfLinkManipulatorInterface.php
+++ b/src/FillPdfLinkManipulatorInterface.php
@@ -6,6 +6,7 @@
 
 namespace Drupal\fillpdf;
 
+use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\RequestStack;
 
 /**
diff --git a/src/Form/FillPdfOverviewForm.php b/src/Form/FillPdfOverviewForm.php
index 79beb8c..421de2f 100644
--- a/src/Form/FillPdfOverviewForm.php
+++ b/src/Form/FillPdfOverviewForm.php
@@ -14,7 +14,8 @@ use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Url;
 use Drupal\file\Entity\File;
 use Drupal\fillpdf\Entity\FillPdfForm;
-use Drupal\fillpdf\FillPdfBackendManagerInterface;
+use Drupal\fillpdf\Entity\FillPdfFormField;
+use Drupal\fillpdf\FillPdfBackendManager;
 use Drupal\fillpdf\FillPdfBackendPluginInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -22,7 +23,7 @@ class FillPdfOverviewForm extends FillPdfAdminFormBase {
   /**
    * The backend manager (finds the filling plugin the user selected).
    *
-   * @var \Drupal\fillpdf\FillPdfBackendManagerInterface
+   * @var \Drupal\fillpdf\FillPdfBackendManager
    */
   protected $backendManager;
 
@@ -45,7 +46,7 @@ class FillPdfOverviewForm extends FillPdfAdminFormBase {
     return 'fillpdf_forms_admin';
   }
 
-  public function __construct(ModuleHandlerInterface $module_handler, FillPdfBackendManagerInterface $backend_manager, AccountInterface $current_user, QueryFactory $entity_query) {
+  public function __construct(ModuleHandlerInterface $module_handler, FillPdfBackendManager $backend_manager, AccountInterface $current_user, QueryFactory $entity_query) {
     parent::__construct();
     $this->backendManager = $backend_manager;
     $this->moduleHandler = $module_handler;
@@ -228,27 +229,21 @@ class FillPdfOverviewForm extends FillPdfAdminFormBase {
     $config = $this->config('fillpdf.settings');
     $fillpdf_service = $config->get('fillpdf_service_backend');
 
-    try {
-      /** @var FillPdfBackendPluginInterface $backend */
-      $backend = $this->backendManager->createInstance($fillpdf_service, $config->get());
-    }
-    catch (PluginNotFoundException $exception) {
-      $backend = NULL;
-    }
+    /** @var FillPdfBackendPluginInterface $backend */
+    $backend = $this->backendManager->createInstance($fillpdf_service, $config->get());
 
-    if ($backend) {
-      // Attempt to parse the fields in the PDF.
-      $fields = $backend->parse($fillpdf_form);
+    // Attempt to parse the fields in the PDF.
+    $fields = $backend->parse($fillpdf_form);
 
-      // Save the fields that were parsed out (if any). If none were, set a
-      // warning message telling the user that.
-      foreach ($fields as $fillpdf_form_field) {
-        $fillpdf_form_field->save();
-      }
+    // Save the fields that were parsed out (if any). If none were, set a
+    // warning message telling the user that.
+    foreach ($fields as $fillpdf_form_field) {
+      /** @var FillPdfFormField $fillpdf_form_field */
+      $fillpdf_form_field->save();
+    }
 
-      if (empty($fields)) {
-        drupal_set_message($this->t("No fields detected in PDF. Are you sure it contains editable fields?"), 'warning');
-      }
+    if (empty($fields)) {
+      drupal_set_message($this->t("No fields detected in PDF. Are you sure it contains editable fields?"), 'warning');
     }
   }
 }
\ No newline at end of file
diff --git a/src/Plugin/FillPdfBackend/FillPdfServiceFillPdfBackend.php b/src/Plugin/FillPdfBackend/FillPdfServiceFillPdfBackend.php
index 0301bdc..ac3f497 100644
--- a/src/Plugin/FillPdfBackend/FillPdfServiceFillPdfBackend.php
+++ b/src/Plugin/FillPdfBackend/FillPdfServiceFillPdfBackend.php
@@ -76,8 +76,25 @@ class FillPdfServiceFillPdfBackend implements FillPdfBackendPluginInterface {
   /**
    * @inheritdoc
    */
-  public function populateWithFieldData(FillPdfFormInterface $pdf_form, array $fields, array $options) {
-    // TODO: Implement populateWithFieldData() method.
+  public function populateWithFieldData(FillPdfFormInterface $pdf_form, array $fields, array $context) {
+    /** @var FileInterface $original_file */
+    $original_file = File::load($pdf_form->file->target_id);
+    $original_pdf = file_get_contents($original_file->getFileUri());
+
+    // @todo: Actually write this
+    $api_key = $this->config['fillpdf_api_key'];
+    $field_mapping = array();
+    // @todo: Image-filling support. Probably both the local and remote plugins
+    // could extend the same class.
+    $image_data = array();
+    $result = $this->xmlRpcRequest('merge_pdf_v3', base64_encode($original_pdf), $field_mapping, $api_key, $context['flatten'], $image_data);
+    // @todo: Error handling/exceptions
+//    if ($result->error == TRUE) {
+//      drupal_goto();
+//    }
+
+    $populated_pdf = base64_decode($result->data);
+    return $populated_pdf;
   }
 
 
diff --git a/src/Service/FillPdfLinkManipulator.php b/src/Service/FillPdfLinkManipulator.php
index 75f8e6b..ee900b8 100644
--- a/src/Service/FillPdfLinkManipulator.php
+++ b/src/Service/FillPdfLinkManipulator.php
@@ -7,22 +7,72 @@
 namespace Drupal\fillpdf\Service;
 
 use Drupal\fillpdf\FillPdfLinkManipulatorInterface;
-use Drupal\fillpdf\Request;
+use Symfony\Component\HttpFoundation\Request;
 
 class FillPdfLinkManipulator implements FillPdfLinkManipulatorInterface {
 
   /**
    * @param Request $request The request containing the query string to parse.
    * @return array
+   *
+   * @todo: Maybe this should return a FillPdfLinkContext object or something?
+   *   Guess it depends on how much I end up needing to change it.
    */
   public function parseLink(Request $request) {
-    // TODO: Implement parseLink() method.
+    $request_context = array(
+      'entity_ids' => NULL,
+      'fid' => NULL,
+      'sample' => NULL,
+      'force_download' => FALSE,
+      'flatten' => TRUE,
+    );
+
+    $request_context['sample'] = $request->get('sample'); // is this just the PDF populated with sample data?
+    $request_context['fid'] = $request->get('fid');
+
+    if ($request->get('entity_type')) {
+      $request_context['entity_type'] = $request->get('entity_type');
+    }
+
+    $request_context['entity_ids'] = $entity_ids = array();
+    if ($request->get('entity_id') || $request->get('entity_ids')) {
+      $entity_ids = ($request->get('entity_id') ? array($request->get('entity_id')) : $request->get('entity_ids'));
+
+      // Re-key entity IDs so they can be loaded easily with loadMultiple().
+      // If we have type information, add it to the types array, and remove it
+      // make sure we only store the ID in the entity_ids key.
+      foreach ($entity_ids as $entity_id) {
+        $entity_id_parts = explode(':', $entity_id);
+
+        if (count($entity_id_parts) == 2) {
+          $entity_type = $entity_id_parts[0];
+          $entity_id = $entity_id_parts[1];
+        }
+        else {
+          $entity_type = 'node';
+        }
+        $request_context['entity_ids'] += array(
+          $entity_type => array(),
+        );
+
+        $request_context['entity_ids'][$entity_type][$entity_id] = $entity_id;
+      }
+    }
+
+    if ($request->get('download') && (int) $request->get('download') == 1) {
+      $request_context['force_download'] = TRUE;
+    }
+    if ($request->get('flatten') && (int) $request->get('flatten') == 0) {
+      $request_context['flatten'] = FALSE;
+    }
+
+    return $request_context;
   }
 
   /**
    * @param array $parameters
    *   The array of parameters to be converted into a
-   *   URL and query string.
+   *     URL and query string.
    * @return string
    */
   public function generateLink(array $parameters) {
-- 
GitLab