View source
<?php
namespace Drupal\file\Element;
use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Attribute\FormElement;
use Drupal\Core\Render\Element;
use Drupal\Core\Render\Element\FormElementBase;
use Drupal\Core\Site\Settings;
use Drupal\Core\Url;
use Drupal\file\Entity\File;
use Symfony\Component\HttpFoundation\Request;
class ManagedFile extends FormElementBase {
public function getInfo() {
$class = static::class;
return [
'#input' => TRUE,
'#process' => [
[
$class,
'processManagedFile',
],
],
'#element_validate' => [
[
$class,
'validateManagedFile',
],
],
'#pre_render' => [
[
$class,
'preRenderManagedFile',
],
],
'#theme' => 'file_managed_file',
'#theme_wrappers' => [
'form_element',
],
'#progress_indicator' => 'throbber',
'#progress_message' => NULL,
'#upload_validators' => [],
'#upload_location' => NULL,
'#size' => 22,
'#multiple' => FALSE,
'#extended' => FALSE,
'#attached' => [
'library' => [
'file/drupal.file',
],
],
'#accept' => NULL,
];
}
public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
$fids = !empty($input['fids']) ? explode(' ', $input['fids']) : [];
foreach ($fids as $key => $fid) {
$fids[$key] = (int) $fid;
}
$force_default = FALSE;
if ($input !== FALSE) {
$input['fids'] = $fids;
$return = $input;
if ($files = file_managed_file_save_upload($element, $form_state)) {
if ($element['#multiple']) {
$fids = array_merge($fids, array_keys($files));
}
else {
$fids = array_keys($files);
}
}
else {
if (isset($element['#file_value_callbacks'])) {
foreach ($element['#file_value_callbacks'] as $callback) {
$callback($element, $input, $form_state);
}
}
if (!empty($input['fids'])) {
$fids = [];
foreach ($input['fids'] as $fid) {
if ($file = File::load($fid)) {
$fids[] = $file
->id();
if (!$file
->access('download')) {
$force_default = TRUE;
break;
}
if ($file
->isTemporary()) {
if ($file
->getOwnerId() != \Drupal::currentUser()
->id()) {
$force_default = TRUE;
break;
}
elseif (\Drupal::currentUser()
->isAnonymous()) {
$token = NestedArray::getValue($form_state
->getUserInput(), array_merge($element['#parents'], [
'file_' . $file
->id(),
'fid_token',
]));
$file_hmac = Crypt::hmacBase64('file-' . $file
->id(), \Drupal::service('private_key')
->get() . Settings::getHashSalt());
if ($token === NULL || !hash_equals($file_hmac, $token)) {
$force_default = TRUE;
break;
}
}
}
}
}
if ($force_default) {
$fids = [];
}
}
}
}
if ($input === FALSE || $force_default) {
if ($element['#extended']) {
$default_fids = $element['#default_value']['fids'] ?? [];
$return = $element['#default_value'] ?? [
'fids' => [],
];
}
else {
$default_fids = $element['#default_value'] ?? [];
$return = [
'fids' => [],
];
}
if (!empty($default_fids)) {
$fids = [];
foreach ($default_fids as $fid) {
if ($file = File::load($fid)) {
$fids[] = $file
->id();
}
}
}
}
$return['fids'] = $fids;
return $return;
}
public static function uploadAjaxCallback(&$form, FormStateInterface &$form_state, Request $request) {
$renderer = \Drupal::service('renderer');
$form_parents = explode('/', $request->query
->get('element_parents'));
$form_parents = array_filter($form_parents, [
Element::class,
'child',
]);
$form = NestedArray::getValue($form, $form_parents);
$current_file_count = $form_state
->get('file_upload_delta_initial');
if (isset($form['#file_upload_delta']) && $current_file_count < $form['#file_upload_delta']) {
$form[$current_file_count]['#attributes']['class'][] = 'ajax-new-content';
}
$status_messages = [
'#type' => 'status_messages',
];
$form['#prefix'] .= $renderer
->renderRoot($status_messages);
$output = $renderer
->renderRoot($form);
$response = new AjaxResponse();
$response
->setAttachments($form['#attached']);
return $response
->addCommand(new ReplaceCommand(NULL, $output));
}
public static function processManagedFile(&$element, FormStateInterface $form_state, &$complete_form) {
$parents_prefix = implode('_', $element['#parents']);
$fids = $element['#value']['fids'] ?? [];
$element['#progress_indicator'] = empty($element['#progress_indicator']) ? 'none' : $element['#progress_indicator'];
$element['#files'] = !empty($fids) ? File::loadMultiple($fids) : [];
$element['#tree'] = TRUE;
$ajax_wrapper_id = Html::getUniqueId('ajax-wrapper');
$ajax_settings = [
'callback' => [
static::class,
'uploadAjaxCallback',
],
'options' => [
'query' => [
'element_parents' => implode('/', $element['#array_parents']),
],
],
'wrapper' => $ajax_wrapper_id,
'effect' => 'fade',
'progress' => [
'type' => $element['#progress_indicator'],
'message' => $element['#progress_message'],
],
];
$element['upload_button'] = [
'#name' => $parents_prefix . '_upload_button',
'#type' => 'submit',
'#value' => t('Upload'),
'#attributes' => [
'class' => [
'js-hide',
],
],
'#validate' => [],
'#submit' => [
'file_managed_file_submit',
],
'#limit_validation_errors' => [
$element['#parents'],
],
'#ajax' => $ajax_settings,
'#weight' => -5,
];
$ajax_settings['progress']['type'] = $element['#progress_indicator'] == 'none' ? 'none' : 'throbber';
$ajax_settings['progress']['message'] = NULL;
$ajax_settings['effect'] = 'none';
$element['remove_button'] = [
'#name' => $parents_prefix . '_remove_button',
'#type' => 'submit',
'#value' => $element['#multiple'] ? t('Remove selected') : t('Remove'),
'#validate' => [],
'#submit' => [
'file_managed_file_submit',
],
'#limit_validation_errors' => [
$element['#parents'],
],
'#ajax' => $ajax_settings,
'#weight' => 1,
];
$element['fids'] = [
'#type' => 'hidden',
'#value' => $fids,
];
if ($element['#progress_indicator'] == 'bar' && extension_loaded('uploadprogress')) {
$upload_progress_key = mt_rand();
$element['UPLOAD_IDENTIFIER'] = [
'#type' => 'hidden',
'#value' => $upload_progress_key,
'#attributes' => [
'class' => [
'file-progress',
],
],
'#weight' => -20,
];
$element['upload_button']['#ajax']['progress']['url'] = Url::fromRoute('file.ajax_progress', [
'key' => $upload_progress_key,
]);
$element['upload_button']['#ajax']['event'] = 'fileUpload';
}
$id = Html::getUniqueId('edit-' . implode('-', array_merge($element['#parents'], [
'upload',
])));
$element['upload'] = [
'#name' => 'files[' . $parents_prefix . ']',
'#type' => 'file',
'#title' => t('Choose a file'),
'#title_display' => 'invisible',
'#id' => $id,
'#size' => $element['#size'],
'#multiple' => $element['#multiple'],
'#theme_wrappers' => [],
'#weight' => -10,
'#error_no_message' => TRUE,
];
if (!empty($element['#accept'])) {
$element['upload']['#attributes'] = [
'accept' => $element['#accept'],
];
}
$element['#label_for'] = $element['upload']['#id'];
if (!empty($fids) && $element['#files']) {
foreach ($element['#files'] as $delta => $file) {
$file_link = [
'#theme' => 'file_link',
'#file' => $file,
];
if ($element['#multiple']) {
$element['file_' . $delta]['selected'] = [
'#type' => 'checkbox',
'#title' => \Drupal::service('renderer')
->renderInIsolation($file_link),
];
}
else {
$element['file_' . $delta]['filename'] = $file_link + [
'#weight' => -10,
];
}
if ($file
->isTemporary() && \Drupal::currentUser()
->isAnonymous()) {
$element['file_' . $delta]['fid_token'] = [
'#type' => 'hidden',
'#value' => Crypt::hmacBase64('file-' . $delta, \Drupal::service('private_key')
->get() . Settings::getHashSalt()),
];
}
}
}
if (isset($element['#upload_validators']['file_validate_extensions'][0]) || isset($element['#upload_validators']['FileExtension']['extensions'])) {
if (isset($element['#upload_validators']['file_validate_extensions'][0])) {
@trigger_error('\'file_validate_extensions\' is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use the \'FileExtension\' constraint instead. See https://www.drupal.org/node/3363700', E_USER_DEPRECATED);
$allowed_extensions = $element['#upload_validators']['file_validate_extensions'][0];
}
else {
$allowed_extensions = $element['#upload_validators']['FileExtension']['extensions'];
}
$extension_list = implode(',', array_filter(explode(' ', $allowed_extensions)));
$element['upload']['#attached']['drupalSettings']['file']['elements']['#' . $id] = $extension_list;
}
$element['#prefix'] = '<div id="' . $ajax_wrapper_id . '">';
$element['#suffix'] = '</div>';
return $element;
}
public static function preRenderManagedFile($element) {
if (!empty($element['#value']['fids'])) {
if (!$element['#multiple']) {
$element['upload']['#access'] = FALSE;
$element['upload_button']['#access'] = FALSE;
}
}
else {
$element['remove_button']['#access'] = FALSE;
}
return $element;
}
public static function validateManagedFile(&$element, FormStateInterface $form_state, &$complete_form) {
$triggering_element = $form_state
->getTriggeringElement();
$clicked_button = isset($triggering_element['#parents']) ? end($triggering_element['#parents']) : '';
if ($clicked_button != 'remove_button' && !empty($element['fids']['#value'])) {
$fids = $element['fids']['#value'];
foreach ($fids as $fid) {
if ($file = File::load($fid)) {
if ($file
->isPermanent() && \Drupal::config('file.settings')
->get('make_unused_managed_files_temporary')) {
$references = static::fileUsage()
->listUsage($file);
if (empty($references)) {
$form_state
->setError($element, t('The file used in the @name field may not be referenced.', [
'@name' => $element['#title'],
]));
}
}
}
else {
$form_state
->setError($element, t('The file referenced by the @name field does not exist.', [
'@name' => $element['#title'],
]));
}
}
}
if ($element['#required'] && empty($element['fids']['#value']) && !in_array($clicked_button, [
'upload_button',
'remove_button',
])) {
$form_state
->setError($element, t('@name field is required.', [
'@name' => $element['#title'],
]));
}
if (!$element['#extended']) {
$form_state
->setValueForElement($element, $element['fids']['#value']);
}
}
protected static function fileUsage() {
return \Drupal::service('file.usage');
}
}