Skip to main content

How to manage adding a file using an administration form in Drupal 8

Si queremos que un contenido en Drupal tenga un fichero, es tan fácil como añadir un campo de tipo fichero a la entidad correspondiente, normalmente un nodo aunque podría ser otra entidad como por ejemplo un usuario. De esta forma obtendremos tanto un campo para subir el fichero en el formulario de edición de la entidad como control sobre cómo se mostrará el fichero al visitante.

Pero, ¿cómo hacerlo para un formulario de administración? Estos formularios no son entidades y por tanto no podemos añadirles campos tan fácilmente como lo haríamos con un contenido normal.

La solución pasa por realizar la gestión del fichero mediante código. Podemos simplemente añadir a nuestro formulario un elemento de formulario de tipo managed_file. El problema principal que nos encontramos al enviar el formulario es que el archivo va a ser considerado como temporal por Drupal. Esto significa que aunque el fichero se sube correctamente y estará disponible, pasadas unas horas drupal borrará el fichero durante las tareas rutinarias de limpieza, dejando la funcionalidad que use ese fichero en un estado inconsistente.

Para solucionar el problema, en el submit del formulario cargamos el archivo y mediante la función setPermanent indicamos a Drupal que el fichero debe ser conservado. Básicamente estamos modificando el status a 1 (valor de la constante FILE_STATUS_PERMANENT definida en el fichero file.inc). Si no queremos invocar el Field API de Drupal, tenemos el recurso de la función file_unmanaged_save_data, que no vamos a tratar aquí.

If we want a Drupal content to have a file, it is as easy as adding a file type field to the corresponding entity, usually a node, although it could be another entity such as a user. This way we get both a field to upload the file in the entity's edit form and control over how the file will be displayed to the visitor.

But, how to do it for an administration form? These forms are not entities and so we can't add fields to them as easily as we would to normal content.

The solution is to manage the file using code. We can simply add a managed_file type form element to our form. The main problem we face when submitting the form is that the file will be considered temporary by Drupal. This means that although the file is uploaded correctly and will be available, after few hours Drupal will delete the file during routine cleanup tasks, leaving the functionality that uses that file in an inconsistent state.

To solve the problem, in the submit form we upload the file and through the setPermanent function we indicate to Drupal that the file should be kept. Basically we are changing the status to 1 (value of the constant FILE_STATUS_PERMANENT defined in the file.inc). If we don't want to invoke Drupal's Field API, we have the resource of the function file_unmanaged_save_data, that we are not going to treat here.

 

Here's an example of the code:

class MyModuleSettingsConfigurationForm extends ConfigFormBase {
  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'my_module_admin_settings';
  }

  /**
   * {@inheritdoc}
   */
  protected function getEditableConfigNames() {
    return [
      'my_module.settings',
    ];
  }

  public function buildForm(array $form, FormStateInterface $form_state, Request $request = NULL) {

    $config = $this->config('my_module.settings');
    $allowed_ext = 'doc docx pdf jpg';
    $max_upload = 25600000;

    $form['my_file_fid'] = [
      '#type' => 'managed_file',
      '#title' => $this->t('Document'),
      '#description' => $this->t('Valid extensions: @allowed_ext', ['@allowed_ext' => $allowed_ext]),
      // @NOTE: add your folder here!
      '#upload_location' => 'public://test/',
      '#multiple' => FALSE,
      '#required' => TRUE,
      // No need array, $config->get already give us an array format.
      '#default_value' => $config->get('my_file_fid'),
      '#upload_validators' => [
        'file_validate_extensions' => [
          $allowed_ext,
        ],
        'file_validate_size' => [
          $max_upload,
        ],
      ],
    ];

    return parent::buildForm($form, $form_state);
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    $values = $form_state->getValues();

    $fid = reset($form_state->getValue('my_file_fid'));
    $file = File::load($fid);

    // We don't want to lose it after 6 hours (default Drupal's value), since it is going to be used
    // in some forms (will be downloaded).
    $file->setPermanent();
    $file->save();

    // Save configuration settings.
    $this->config('my_module.settings')
      ->set('my_file_fid', $values['my_file_fid'])
      ->save();
  }
}

The buildForm method builds the form by adding the file upload field (my_file_fid). In the submit of the form we check the value of that field getting the fid of the file. We only need to upload it and make it permanent as we discussed earlier.

Technically, using the method:

$file->setPermanent();

we are indicating that the file should not be deleted by Drupal from the file_managed table, even if no module is using it (file_usage table). The use of the file_usage table, is also out of the scope of this post, but keep it in mind if the file is going to be used by any module (or yours).

Although we have explained it here for an administration form, this method is useful for any form that is not coming from an entity (since entities do automatically manage uploaded files).