protected function validatePdfUpload(array &$form, FormStateInterface &$form_state, UploadedFile $file_upload, $file_field_name) {
    /**
     * @var $file_upload \Symfony\Component\HttpFoundation\File\UploadedFile
     */
    if ($file_upload && $file_upload->isValid()) {
      // Move it to somewhere we know.
      $uploaded_filename = $file_upload->getClientOriginalName();

      // Ensure the destination is unique; we deliberately use managed files,
      // but they are keyed on file URI, so we can't save the same one twice.
      $scheme = $this->config('fillpdf.settings')->get('scheme');
      $destination = file_destination(FillPdf::buildFileUri($scheme, 'fillpdf/' . $uploaded_filename), FILE_EXISTS_RENAME);

      // Ensure our directory exists.
      $fillpdf_directory = FillPdf::buildFileUri($scheme, 'fillpdf');
      $directory_exists = file_prepare_directory($fillpdf_directory, FILE_CREATE_DIRECTORY + FILE_MODIFY_PERMISSIONS);

      if ($directory_exists) {
        $file_moved = $this->fileSystem->moveUploadedFile($file_upload->getRealPath(), $destination);

        if ($file_moved) {
          // Create a File object from the uploaded file.
          $new_file = File::create([
            'uri' => $destination,
            'uid' => $this->currentUser()->id(),
          ]);

          $errors = file_validate_extensions($new_file, 'pdf');

          if (count($errors)) {
            $form_state->setErrorByName('upload_pdf', $this->t('Only PDF files are supported, and they must end in .pdf.'));
          }
          else {
            $form_state->setValue('upload_pdf', $new_file);
          }
        }
        else {
          $form_state->setErrorByName('upload_pdf', $this->t("Could not move your uploaded file from PHP's temporary location to Drupal file storage."));
        }
      }
      else {
        $form_state->setErrorByName('upload_pdf', $this->t('Could not automatically create the <em>fillpdf</em> subdirectory. Please create this manually before uploading your PDF form.'));
      }
    }
    else {
      $form_state->setErrorByName('upload_pdf', $this->t('Your PDF could not be uploaded. Did you select one?'));
    }
  }
  /**
   * @inheritdoc
   */
  public function parse(FillPdfFormInterface $fillpdf_form) {
    /** @var FileInterface $file */
    $file = File::load($fillpdf_form->file->target_id);
    $filename = $file->getFileUri();

    $path_to_pdftk = $this->getPdftkPath();
    $status = FillPdf::checkPdftkPath($path_to_pdftk);
    if ($status === FALSE) {
      drupal_set_message($this->t('pdftk not properly installed.'), 'error');
      return [];
    }

    // Use exec() to call pdftk (because it will be easier to go line-by-line parsing the output) and pass $content via stdin. Retrieve the fields with dump_data_fields.
    $output = [];
    exec($path_to_pdftk . ' ' . escapeshellarg($this->fileSystem->realpath($filename)) . ' dump_data_fields', $output, $status);
    if (count($output) === 0) {
      drupal_set_message($this->t('PDF does not contain fillable fields.'), 'warning');
      return [];
    }

    // Build a simple map of dump_data_fields keys to our own array keys
    $data_fields_map = [
      'FieldType' => 'type',
      'FieldName' => 'name',
      'FieldFlags' => 'flags',
      'FieldJustification' => 'justification',
    ];

    // Build the fields array
    $fields = [];
    $fieldindex = -1;
    foreach ($output as $lineitem) {
      if ($lineitem == '---') {
        $fieldindex++;
        continue;
      }
      // Separate the data key from the data value
      $linedata = explode(':', $lineitem);
      if (in_array($linedata[0], array_keys($data_fields_map), NULL)) {
        $fields[$fieldindex][$data_fields_map[$linedata[0]]] = trim($linedata[1]);
      }
    }

    return $fields;
  }
 /**
  * {@inheritdoc}
  */
 public function validateForm(array &$form, FormStateInterface $form_state) {
   if ($form_state->getValue('pdftk_path')) {
     $status = FillPdf::checkPdftkPath($form_state->getValue('pdftk_path'));
     if ($status === FALSE) {
       $form_state->setErrorByName('pdftk_path', $this->t('The path you have entered for
     <em>pdftk</em> is invalid. Please enter a valid path.'));
     }
   }
 }
  /**
   * @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;
  }
  /**
   * {@inheritdoc}
   */
  public function form(array $form, FormStateInterface $form_state) {
    $form = parent::form($form, $form_state);

    /** @var FillPdfFormInterface $entity */
    $entity = $this->entity;

    $form['tokens'] = [
      '#type' => 'details',
      '#title' => $this->t('Tokens'),
      '#weight' => 11,
      'token_tree' => $this->adminFormHelper->getAdminTokenForm(),
    ];

    $entity_types = [];
    $entity_type_definitions = $this->entityManager->getDefinitions();

    foreach ($entity_type_definitions as $machine_name => $definition) {
      $label = $definition->getLabel();
      $entity_types[$machine_name] = "$machine_name ($label)";
    }

    // @todo: Encapsulate this logic into a ::getDefaultEntityType() method on FillPdfForm
    $default_entity_type = $entity->get('default_entity_type')->first()->value;
    if (empty($default_entity_type)) {
      $default_entity_type = 'node';
    }

    $form['default_entity_type'] = [
      '#type' => 'select',
      '#title' => $this->t('Default entity type'),
      '#options' => $entity_types,
      '#weight' => 12.5,
      '#default_value' => $default_entity_type,
    ];

    $fid = $entity->id();

    /** @var FileInterface $file_entity */
    $file_entity = File::load($entity->get('file')->first()->target_id);
    $pdf_info_weight = 0;
    $form['pdf_info'] = [
      '#type' => 'fieldset',
      '#title' => $this->t('PDF form information'),
      '#weight' => $form['default_entity_id']['#weight'] + 1,
      'submitted_pdf' => [
        '#type' => 'item',
        '#title' => $this->t('Uploaded PDF'),
        '#description' => $file_entity->getFileUri(),
        '#weight' => $pdf_info_weight++,
      ],
      'upload_pdf' => [
        '#type' => 'file',
        '#title' => 'Update PDF template',
        '#description' => $this->t('Update the PDF template used by this form'),
        '#weight' => $pdf_info_weight++,
      ],
      'sample_populate' => [
        '#type' => 'item',
        '#title' => 'Sample PDF',
        '#description' => $this->l($this->t('See which fields are which in this PDF.'),
            $this->linkManipulator->generateLink([
              'fid' => $fid,
              'sample' => TRUE,
            ])) . '<br />' .
          $this->t('If you have set a custom path on this PDF, the sample will be saved there silently.'),
        '#weight' => $pdf_info_weight++,
      ],
      'form_id' => [
        '#type' => 'item',
        '#title' => 'Form Info',
        '#description' => $this->t("Form ID: [@fid].  Populate this form with entity IDs, such as /fillpdf?fid=$fid&entity_type=node&entity_id=10<br/>", ['@fid' => $fid]),
        '#weight' => $pdf_info_weight,
      ],
    ];

    if (!empty($entity->get('default_entity_id')->first()->value)) {
      $parameters = [
        'fid' => $fid,
      ];
      $form['pdf_info']['populate_default'] = [
        '#type' => 'item',
        '#title' => 'Fill PDF from default node',
        '#description' => $this->l($this->t('Download this PDF filled with data from the default entity (@entity_type:@entity).',
            [
              '@entity_type' => $entity->default_entity_type->value,
              '@entity' => $entity->default_entity_id->value,
            ]
          ),
            $this->linkManipulator->generateLink($parameters)) . '<br />' .
          $this->t('If you have set a custom path on this PDF, the sample will be saved there silently.'),
        '#weight' => $form['pdf_info']['form_id']['#weight'] - 0.1,
      ];
    }

    $additional_setting_set = $entity->destination_path->value || $entity->destination_redirect->value;
    $form['additional_settings'] = [
      '#type' => 'details',
      '#title' => $this->t('Additional settings'),
      '#weight' => $form['pdf_info']['#weight'] + 1,
      '#open' => $additional_setting_set,
    ];

    $form['destination_path']['#group'] = 'additional_settings';
    $form['scheme']['#group'] = 'additional_settings';
    $form['destination_redirect']['#group'] = 'additional_settings';
    $form['replacements']['#group'] = 'additional_settings';
    $form['replacements']['#weight'] = 1;

    // @todo: Add a button to let them attempt re-parsing if it failed.
    $form['fillpdf_fields']['fields'] = FillPdf::embedView('fillpdf_form_fields',
      'block_1',
      $entity->id());

    $form['fillpdf_fields']['#weight'] = 100;

    $form['export_fields'] = [
      '#prefix' => '<div>',
      '#markup' => $this->l($this->t('Export these field mappings'), Url::fromRoute('entity.fillpdf_form.export_form', ['fillpdf_form' => $entity->id()])),
      '#suffix' => '</div>',
      '#weight' => 100,
    ];

    $form['import_fields'] = [
      '#prefix' => '<div>',
      '#markup' => $this->l($this->t('Import a previous export into this PDF'), Url::fromRoute('entity.fillpdf_form.import_form', ['fillpdf_form' => $entity->id()])),
      '#suffix' => '</div>',
      '#weight' => 100,
    ];

    return $form;
  }