Update Composer, update everything
This commit is contained in:
parent
ea3e94409f
commit
dda5c284b6
19527 changed files with 1135420 additions and 351004 deletions
|
@ -3,4 +3,4 @@ description:
|
|||
length: 128
|
||||
icon:
|
||||
directory: 'core/modules/file/icons'
|
||||
|
||||
make_unused_managed_files_temporary: false
|
||||
|
|
|
@ -565,7 +565,7 @@ display:
|
|||
decimal: .
|
||||
separator: ','
|
||||
format_plural: true
|
||||
format_plural_string: "1 place\x03@count places"
|
||||
format_plural_string: !!binary MSBwbGFjZQNAY291bnQgcGxhY2Vz
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
plugin_id: numeric
|
||||
|
@ -1007,7 +1007,7 @@ display:
|
|||
decimal: .
|
||||
separator: ','
|
||||
format_plural: false
|
||||
format_plural_string: "1\x03@count"
|
||||
format_plural_string: !!binary MQNAY291bnQ=
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
plugin_id: numeric
|
||||
|
|
|
@ -21,6 +21,9 @@ file.settings:
|
|||
directory:
|
||||
type: path
|
||||
label: 'Directory'
|
||||
make_unused_managed_files_temporary:
|
||||
type: boolean
|
||||
label: 'Controls if unused files should be marked temporary'
|
||||
|
||||
field.storage_settings.file:
|
||||
type: base_entity_reference_field_settings
|
||||
|
@ -48,7 +51,7 @@ base_file_field_field_settings:
|
|||
label: 'Reference method'
|
||||
handler_settings:
|
||||
type: entity_reference_selection.[%parent.handler]
|
||||
label: 'Entity reference selection settings'
|
||||
label: 'File selection handler settings'
|
||||
file_directory:
|
||||
type: string
|
||||
label: 'File directory'
|
||||
|
@ -67,16 +70,55 @@ field.field_settings.file:
|
|||
type: boolean
|
||||
label: 'Enable Description field'
|
||||
|
||||
file.formatter.media:
|
||||
type: mapping
|
||||
label: 'Media display format settings'
|
||||
mapping:
|
||||
controls:
|
||||
type: boolean
|
||||
label: 'Show playback controls'
|
||||
autoplay:
|
||||
type: boolean
|
||||
label: 'Autoplay'
|
||||
loop:
|
||||
type: boolean
|
||||
label: 'Loop'
|
||||
multiple_file_display_type:
|
||||
type: string
|
||||
label: 'Display of multiple files'
|
||||
|
||||
field.formatter.settings.file_audio:
|
||||
type: file.formatter.media
|
||||
label: 'Audio file display format settings'
|
||||
|
||||
field.formatter.settings.file_video:
|
||||
type: file.formatter.media
|
||||
label: 'Video file display format settings'
|
||||
mapping:
|
||||
muted:
|
||||
type: boolean
|
||||
label: 'Muted'
|
||||
width:
|
||||
type: integer
|
||||
label: 'Width'
|
||||
height:
|
||||
type: integer
|
||||
label: 'Height'
|
||||
|
||||
field.formatter.settings.file_default:
|
||||
type: mapping
|
||||
label: 'Generic file format settings'
|
||||
mapping:
|
||||
use_description_as_link_text:
|
||||
type: boolean
|
||||
label: 'Replace the file name by its description when available'
|
||||
|
||||
field.formatter.settings.file_rss_enclosure:
|
||||
type: mapping
|
||||
label: 'RSS enclosure format settings'
|
||||
|
||||
field.formatter.settings.file_table:
|
||||
type: mapping
|
||||
type: field.formatter.settings.file_default
|
||||
label: 'Table of files format settings'
|
||||
|
||||
field.formatter.settings.file_url_plain:
|
||||
|
|
303
web/core/modules/file/file.es6.js
Normal file
303
web/core/modules/file/file.es6.js
Normal file
|
@ -0,0 +1,303 @@
|
|||
/**
|
||||
* @file
|
||||
* Provides JavaScript additions to the managed file field type.
|
||||
*
|
||||
* This file provides progress bar support (if available), popup windows for
|
||||
* file previews, and disabling of other file fields during Ajax uploads (which
|
||||
* prevents separate file fields from accidentally uploading files).
|
||||
*/
|
||||
|
||||
(function($, Drupal) {
|
||||
/**
|
||||
* Attach behaviors to the file fields passed in the settings.
|
||||
*
|
||||
* @type {Drupal~behavior}
|
||||
*
|
||||
* @prop {Drupal~behaviorAttach} attach
|
||||
* Attaches validation for file extensions.
|
||||
* @prop {Drupal~behaviorDetach} detach
|
||||
* Detaches validation for file extensions.
|
||||
*/
|
||||
Drupal.behaviors.fileValidateAutoAttach = {
|
||||
attach(context, settings) {
|
||||
const $context = $(context);
|
||||
let elements;
|
||||
|
||||
function initFileValidation(selector) {
|
||||
$context
|
||||
.find(selector)
|
||||
.once('fileValidate')
|
||||
.on(
|
||||
'change.fileValidate',
|
||||
{ extensions: elements[selector] },
|
||||
Drupal.file.validateExtension,
|
||||
);
|
||||
}
|
||||
|
||||
if (settings.file && settings.file.elements) {
|
||||
elements = settings.file.elements;
|
||||
Object.keys(elements).forEach(initFileValidation);
|
||||
}
|
||||
},
|
||||
detach(context, settings, trigger) {
|
||||
const $context = $(context);
|
||||
let elements;
|
||||
|
||||
function removeFileValidation(selector) {
|
||||
$context
|
||||
.find(selector)
|
||||
.removeOnce('fileValidate')
|
||||
.off('change.fileValidate', Drupal.file.validateExtension);
|
||||
}
|
||||
|
||||
if (trigger === 'unload' && settings.file && settings.file.elements) {
|
||||
elements = settings.file.elements;
|
||||
Object.keys(elements).forEach(removeFileValidation);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Attach behaviors to file element auto upload.
|
||||
*
|
||||
* @type {Drupal~behavior}
|
||||
*
|
||||
* @prop {Drupal~behaviorAttach} attach
|
||||
* Attaches triggers for the upload button.
|
||||
* @prop {Drupal~behaviorDetach} detach
|
||||
* Detaches auto file upload trigger.
|
||||
*/
|
||||
Drupal.behaviors.fileAutoUpload = {
|
||||
attach(context) {
|
||||
$(context)
|
||||
.find('input[type="file"]')
|
||||
.once('auto-file-upload')
|
||||
.on('change.autoFileUpload', Drupal.file.triggerUploadButton);
|
||||
},
|
||||
detach(context, settings, trigger) {
|
||||
if (trigger === 'unload') {
|
||||
$(context)
|
||||
.find('input[type="file"]')
|
||||
.removeOnce('auto-file-upload')
|
||||
.off('.autoFileUpload');
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Attach behaviors to the file upload and remove buttons.
|
||||
*
|
||||
* @type {Drupal~behavior}
|
||||
*
|
||||
* @prop {Drupal~behaviorAttach} attach
|
||||
* Attaches form submit events.
|
||||
* @prop {Drupal~behaviorDetach} detach
|
||||
* Detaches form submit events.
|
||||
*/
|
||||
Drupal.behaviors.fileButtons = {
|
||||
attach(context) {
|
||||
const $context = $(context);
|
||||
$context
|
||||
.find('.js-form-submit')
|
||||
.on('mousedown', Drupal.file.disableFields);
|
||||
$context
|
||||
.find('.js-form-managed-file .js-form-submit')
|
||||
.on('mousedown', Drupal.file.progressBar);
|
||||
},
|
||||
detach(context, settings, trigger) {
|
||||
if (trigger === 'unload') {
|
||||
const $context = $(context);
|
||||
$context
|
||||
.find('.js-form-submit')
|
||||
.off('mousedown', Drupal.file.disableFields);
|
||||
$context
|
||||
.find('.js-form-managed-file .js-form-submit')
|
||||
.off('mousedown', Drupal.file.progressBar);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Attach behaviors to links within managed file elements for preview windows.
|
||||
*
|
||||
* @type {Drupal~behavior}
|
||||
*
|
||||
* @prop {Drupal~behaviorAttach} attach
|
||||
* Attaches triggers.
|
||||
* @prop {Drupal~behaviorDetach} detach
|
||||
* Detaches triggers.
|
||||
*/
|
||||
Drupal.behaviors.filePreviewLinks = {
|
||||
attach(context) {
|
||||
$(context)
|
||||
.find('div.js-form-managed-file .file a')
|
||||
.on('click', Drupal.file.openInNewWindow);
|
||||
},
|
||||
detach(context) {
|
||||
$(context)
|
||||
.find('div.js-form-managed-file .file a')
|
||||
.off('click', Drupal.file.openInNewWindow);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* File upload utility functions.
|
||||
*
|
||||
* @namespace
|
||||
*/
|
||||
Drupal.file = Drupal.file || {
|
||||
/**
|
||||
* Client-side file input validation of file extensions.
|
||||
*
|
||||
* @name Drupal.file.validateExtension
|
||||
*
|
||||
* @param {jQuery.Event} event
|
||||
* The event triggered. For example `change.fileValidate`.
|
||||
*/
|
||||
validateExtension(event) {
|
||||
event.preventDefault();
|
||||
// Remove any previous errors.
|
||||
$('.file-upload-js-error').remove();
|
||||
|
||||
// Add client side validation for the input[type=file].
|
||||
const extensionPattern = event.data.extensions.replace(/,\s*/g, '|');
|
||||
if (extensionPattern.length > 1 && this.value.length > 0) {
|
||||
const acceptableMatch = new RegExp(`\\.(${extensionPattern})$`, 'gi');
|
||||
if (!acceptableMatch.test(this.value)) {
|
||||
const error = Drupal.t(
|
||||
'The selected file %filename cannot be uploaded. Only files with the following extensions are allowed: %extensions.',
|
||||
{
|
||||
// According to the specifications of HTML5, a file upload control
|
||||
// should not reveal the real local path to the file that a user
|
||||
// has selected. Some web browsers implement this restriction by
|
||||
// replacing the local path with "C:\fakepath\", which can cause
|
||||
// confusion by leaving the user thinking perhaps Drupal could not
|
||||
// find the file because it messed up the file path. To avoid this
|
||||
// confusion, therefore, we strip out the bogus fakepath string.
|
||||
'%filename': this.value.replace('C:\\fakepath\\', ''),
|
||||
'%extensions': extensionPattern.replace(/\|/g, ', '),
|
||||
},
|
||||
);
|
||||
$(this)
|
||||
.closest('div.js-form-managed-file')
|
||||
.prepend(
|
||||
`<div class="messages messages--error file-upload-js-error" aria-live="polite">${error}</div>`,
|
||||
);
|
||||
this.value = '';
|
||||
// Cancel all other change event handlers.
|
||||
event.stopImmediatePropagation();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Trigger the upload_button mouse event to auto-upload as a managed file.
|
||||
*
|
||||
* @name Drupal.file.triggerUploadButton
|
||||
*
|
||||
* @param {jQuery.Event} event
|
||||
* The event triggered. For example `change.autoFileUpload`.
|
||||
*/
|
||||
triggerUploadButton(event) {
|
||||
$(event.target)
|
||||
.closest('.js-form-managed-file')
|
||||
.find('.js-form-submit')
|
||||
.trigger('mousedown');
|
||||
},
|
||||
|
||||
/**
|
||||
* Prevent file uploads when using buttons not intended to upload.
|
||||
*
|
||||
* @name Drupal.file.disableFields
|
||||
*
|
||||
* @param {jQuery.Event} event
|
||||
* The event triggered, most likely a `mousedown` event.
|
||||
*/
|
||||
disableFields(event) {
|
||||
const $clickedButton = $(this);
|
||||
$clickedButton.trigger('formUpdated');
|
||||
|
||||
// Check if we're working with an "Upload" button.
|
||||
let $enabledFields = [];
|
||||
if ($clickedButton.closest('div.js-form-managed-file').length > 0) {
|
||||
$enabledFields = $clickedButton
|
||||
.closest('div.js-form-managed-file')
|
||||
.find('input.js-form-file');
|
||||
}
|
||||
|
||||
// Temporarily disable upload fields other than the one we're currently
|
||||
// working with. Filter out fields that are already disabled so that they
|
||||
// do not get enabled when we re-enable these fields at the end of
|
||||
// behavior processing. Re-enable in a setTimeout set to a relatively
|
||||
// short amount of time (1 second). All the other mousedown handlers
|
||||
// (like Drupal's Ajax behaviors) are executed before any timeout
|
||||
// functions are called, so we don't have to worry about the fields being
|
||||
// re-enabled too soon. @todo If the previous sentence is true, why not
|
||||
// set the timeout to 0?
|
||||
const $fieldsToTemporarilyDisable = $(
|
||||
'div.js-form-managed-file input.js-form-file',
|
||||
)
|
||||
.not($enabledFields)
|
||||
.not(':disabled');
|
||||
$fieldsToTemporarilyDisable.prop('disabled', true);
|
||||
setTimeout(() => {
|
||||
$fieldsToTemporarilyDisable.prop('disabled', false);
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add progress bar support if possible.
|
||||
*
|
||||
* @name Drupal.file.progressBar
|
||||
*
|
||||
* @param {jQuery.Event} event
|
||||
* The event triggered, most likely a `mousedown` event.
|
||||
*/
|
||||
progressBar(event) {
|
||||
const $clickedButton = $(this);
|
||||
const $progressId = $clickedButton
|
||||
.closest('div.js-form-managed-file')
|
||||
.find('input.file-progress');
|
||||
if ($progressId.length) {
|
||||
const originalName = $progressId.attr('name');
|
||||
|
||||
// Replace the name with the required identifier.
|
||||
$progressId.attr(
|
||||
'name',
|
||||
originalName.match(/APC_UPLOAD_PROGRESS|UPLOAD_IDENTIFIER/)[0],
|
||||
);
|
||||
|
||||
// Restore the original name after the upload begins.
|
||||
setTimeout(() => {
|
||||
$progressId.attr('name', originalName);
|
||||
}, 1000);
|
||||
}
|
||||
// Show the progress bar if the upload takes longer than half a second.
|
||||
setTimeout(() => {
|
||||
$clickedButton
|
||||
.closest('div.js-form-managed-file')
|
||||
.find('div.ajax-progress-bar')
|
||||
.slideDown();
|
||||
}, 500);
|
||||
$clickedButton.trigger('fileUpload');
|
||||
},
|
||||
|
||||
/**
|
||||
* Open links to files within forms in a new window.
|
||||
*
|
||||
* @name Drupal.file.openInNewWindow
|
||||
*
|
||||
* @param {jQuery.Event} event
|
||||
* The event triggered, most likely a `click` event.
|
||||
*/
|
||||
openInNewWindow(event) {
|
||||
event.preventDefault();
|
||||
$(this).attr('target', '_blank');
|
||||
window.open(
|
||||
this.href,
|
||||
'filePreview',
|
||||
'toolbar=0,scrollbars=1,location=1,statusbar=1,menubar=0,resizable=1,width=500,height=550',
|
||||
);
|
||||
},
|
||||
};
|
||||
})(jQuery, Drupal);
|
|
@ -73,7 +73,7 @@ function template_preprocess_file_widget_multiple(&$variables) {
|
|||
|
||||
// Render everything else together in a column, without the normal wrappers.
|
||||
$widget['#theme_wrappers'] = [];
|
||||
$information = drupal_render($widget);
|
||||
$information = \Drupal::service('renderer')->render($widget);
|
||||
$display = '';
|
||||
if ($element['#display_field']) {
|
||||
unset($widget['display']['#title']);
|
||||
|
|
|
@ -5,4 +5,4 @@ package: Field types
|
|||
version: VERSION
|
||||
core: 8.x
|
||||
dependencies:
|
||||
- field
|
||||
- drupal:field
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* Install, update and uninstall functions for File module.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Entity\Entity\EntityViewDisplay;
|
||||
|
||||
/**
|
||||
* Implements hook_schema().
|
||||
*/
|
||||
|
@ -116,3 +118,49 @@ function file_requirements($phase) {
|
|||
|
||||
return $requirements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent unused files from being deleted.
|
||||
*/
|
||||
function file_update_8300() {
|
||||
// Disable deletion of unused permanent files.
|
||||
\Drupal::configFactory()->getEditable('file.settings')
|
||||
->set('make_unused_managed_files_temporary', FALSE)
|
||||
->save();
|
||||
|
||||
return t('Files that have no remaining usages are no longer deleted by default.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add 'use_description_as_link_text' setting to file field formatters.
|
||||
*/
|
||||
function file_update_8001() {
|
||||
$displays = EntityViewDisplay::loadMultiple();
|
||||
foreach ($displays as $display) {
|
||||
/** @var \Drupal\Core\Entity\Entity\EntityViewDisplay $display */
|
||||
$fields_settings = $display->get('content');
|
||||
$changed = FALSE;
|
||||
foreach ($fields_settings as $field_name => $settings) {
|
||||
if (!empty($settings['type'])) {
|
||||
switch ($settings['type']) {
|
||||
// The file_table formatter never displayed available descriptions
|
||||
// before, so we disable this option to ensure backward compatibility.
|
||||
case 'file_table':
|
||||
$fields_settings[$field_name]['settings']['use_description_as_link_text'] = FALSE;
|
||||
$changed = TRUE;
|
||||
break;
|
||||
|
||||
// The file_default formatter always displayed available descriptions
|
||||
// before, so we enable this option to ensure backward compatibility.
|
||||
case 'file_default':
|
||||
$fields_settings[$field_name]['settings']['use_description_as_link_text'] = TRUE;
|
||||
$changed = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($changed === TRUE) {
|
||||
$display->set('content', $fields_settings)->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,35 +1,18 @@
|
|||
/**
|
||||
* @file
|
||||
* Provides JavaScript additions to the managed file field type.
|
||||
*
|
||||
* This file provides progress bar support (if available), popup windows for
|
||||
* file previews, and disabling of other file fields during Ajax uploads (which
|
||||
* prevents separate file fields from accidentally uploading files).
|
||||
*/
|
||||
* DO NOT EDIT THIS FILE.
|
||||
* See the following change record for more information,
|
||||
* https://www.drupal.org/node/2815083
|
||||
* @preserve
|
||||
**/
|
||||
|
||||
(function ($, Drupal) {
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Attach behaviors to the file fields passed in the settings.
|
||||
*
|
||||
* @type {Drupal~behavior}
|
||||
*
|
||||
* @prop {Drupal~behaviorAttach} attach
|
||||
* Attaches validation for file extensions.
|
||||
* @prop {Drupal~behaviorDetach} detach
|
||||
* Detaches validation for file extensions.
|
||||
*/
|
||||
Drupal.behaviors.fileValidateAutoAttach = {
|
||||
attach: function (context, settings) {
|
||||
attach: function attach(context, settings) {
|
||||
var $context = $(context);
|
||||
var elements;
|
||||
var elements = void 0;
|
||||
|
||||
function initFileValidation(selector) {
|
||||
$context.find(selector)
|
||||
.once('fileValidate')
|
||||
.on('change.fileValidate', {extensions: elements[selector]}, Drupal.file.validateExtension);
|
||||
$context.find(selector).once('fileValidate').on('change.fileValidate', { extensions: elements[selector] }, Drupal.file.validateExtension);
|
||||
}
|
||||
|
||||
if (settings.file && settings.file.elements) {
|
||||
|
@ -37,14 +20,12 @@
|
|||
Object.keys(elements).forEach(initFileValidation);
|
||||
}
|
||||
},
|
||||
detach: function (context, settings, trigger) {
|
||||
detach: function detach(context, settings, trigger) {
|
||||
var $context = $(context);
|
||||
var elements;
|
||||
var elements = void 0;
|
||||
|
||||
function removeFileValidation(selector) {
|
||||
$context.find(selector)
|
||||
.removeOnce('fileValidate')
|
||||
.off('change.fileValidate', Drupal.file.validateExtension);
|
||||
$context.find(selector).removeOnce('fileValidate').off('change.fileValidate', Drupal.file.validateExtension);
|
||||
}
|
||||
|
||||
if (trigger === 'unload' && settings.file && settings.file.elements) {
|
||||
|
@ -54,204 +35,102 @@
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Attach behaviors to file element auto upload.
|
||||
*
|
||||
* @type {Drupal~behavior}
|
||||
*
|
||||
* @prop {Drupal~behaviorAttach} attach
|
||||
* Attaches triggers for the upload button.
|
||||
* @prop {Drupal~behaviorDetach} detach
|
||||
* Detaches auto file upload trigger.
|
||||
*/
|
||||
Drupal.behaviors.fileAutoUpload = {
|
||||
attach: function (context) {
|
||||
attach: function attach(context) {
|
||||
$(context).find('input[type="file"]').once('auto-file-upload').on('change.autoFileUpload', Drupal.file.triggerUploadButton);
|
||||
},
|
||||
detach: function (context, setting, trigger) {
|
||||
detach: function detach(context, settings, trigger) {
|
||||
if (trigger === 'unload') {
|
||||
$(context).find('input[type="file"]').removeOnce('auto-file-upload').off('.autoFileUpload');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Attach behaviors to the file upload and remove buttons.
|
||||
*
|
||||
* @type {Drupal~behavior}
|
||||
*
|
||||
* @prop {Drupal~behaviorAttach} attach
|
||||
* Attaches form submit events.
|
||||
* @prop {Drupal~behaviorDetach} detach
|
||||
* Detaches form submit events.
|
||||
*/
|
||||
Drupal.behaviors.fileButtons = {
|
||||
attach: function (context) {
|
||||
attach: function attach(context) {
|
||||
var $context = $(context);
|
||||
$context.find('.js-form-submit').on('mousedown', Drupal.file.disableFields);
|
||||
$context.find('.js-form-managed-file .js-form-submit').on('mousedown', Drupal.file.progressBar);
|
||||
},
|
||||
detach: function (context) {
|
||||
var $context = $(context);
|
||||
$context.find('.js-form-submit').off('mousedown', Drupal.file.disableFields);
|
||||
$context.find('.js-form-managed-file .js-form-submit').off('mousedown', Drupal.file.progressBar);
|
||||
detach: function detach(context, settings, trigger) {
|
||||
if (trigger === 'unload') {
|
||||
var $context = $(context);
|
||||
$context.find('.js-form-submit').off('mousedown', Drupal.file.disableFields);
|
||||
$context.find('.js-form-managed-file .js-form-submit').off('mousedown', Drupal.file.progressBar);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Attach behaviors to links within managed file elements for preview windows.
|
||||
*
|
||||
* @type {Drupal~behavior}
|
||||
*
|
||||
* @prop {Drupal~behaviorAttach} attach
|
||||
* Attaches triggers.
|
||||
* @prop {Drupal~behaviorDetach} detach
|
||||
* Detaches triggers.
|
||||
*/
|
||||
Drupal.behaviors.filePreviewLinks = {
|
||||
attach: function (context) {
|
||||
attach: function attach(context) {
|
||||
$(context).find('div.js-form-managed-file .file a').on('click', Drupal.file.openInNewWindow);
|
||||
},
|
||||
detach: function (context) {
|
||||
detach: function detach(context) {
|
||||
$(context).find('div.js-form-managed-file .file a').off('click', Drupal.file.openInNewWindow);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* File upload utility functions.
|
||||
*
|
||||
* @namespace
|
||||
*/
|
||||
Drupal.file = Drupal.file || {
|
||||
|
||||
/**
|
||||
* Client-side file input validation of file extensions.
|
||||
*
|
||||
* @name Drupal.file.validateExtension
|
||||
*
|
||||
* @param {jQuery.Event} event
|
||||
* The event triggered. For example `change.fileValidate`.
|
||||
*/
|
||||
validateExtension: function (event) {
|
||||
validateExtension: function validateExtension(event) {
|
||||
event.preventDefault();
|
||||
// Remove any previous errors.
|
||||
|
||||
$('.file-upload-js-error').remove();
|
||||
|
||||
// Add client side validation for the input[type=file].
|
||||
var extensionPattern = event.data.extensions.replace(/,\s*/g, '|');
|
||||
if (extensionPattern.length > 1 && this.value.length > 0) {
|
||||
var acceptableMatch = new RegExp('\\.(' + extensionPattern + ')$', 'gi');
|
||||
if (!acceptableMatch.test(this.value)) {
|
||||
var error = Drupal.t('The selected file %filename cannot be uploaded. Only files with the following extensions are allowed: %extensions.', {
|
||||
// According to the specifications of HTML5, a file upload control
|
||||
// should not reveal the real local path to the file that a user
|
||||
// has selected. Some web browsers implement this restriction by
|
||||
// replacing the local path with "C:\fakepath\", which can cause
|
||||
// confusion by leaving the user thinking perhaps Drupal could not
|
||||
// find the file because it messed up the file path. To avoid this
|
||||
// confusion, therefore, we strip out the bogus fakepath string.
|
||||
'%filename': this.value.replace('C:\\fakepath\\', ''),
|
||||
'%extensions': extensionPattern.replace(/\|/g, ', ')
|
||||
});
|
||||
$(this).closest('div.js-form-managed-file').prepend('<div class="messages messages--error file-upload-js-error" aria-live="polite">' + error + '</div>');
|
||||
this.value = '';
|
||||
// Cancel all other change event handlers.
|
||||
|
||||
event.stopImmediatePropagation();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Trigger the upload_button mouse event to auto-upload as a managed file.
|
||||
*
|
||||
* @name Drupal.file.triggerUploadButton
|
||||
*
|
||||
* @param {jQuery.Event} event
|
||||
* The event triggered. For example `change.autoFileUpload`.
|
||||
*/
|
||||
triggerUploadButton: function (event) {
|
||||
triggerUploadButton: function triggerUploadButton(event) {
|
||||
$(event.target).closest('.js-form-managed-file').find('.js-form-submit').trigger('mousedown');
|
||||
},
|
||||
disableFields: function disableFields(event) {
|
||||
var $clickedButton = $(this);
|
||||
$clickedButton.trigger('formUpdated');
|
||||
|
||||
/**
|
||||
* Prevent file uploads when using buttons not intended to upload.
|
||||
*
|
||||
* @name Drupal.file.disableFields
|
||||
*
|
||||
* @param {jQuery.Event} event
|
||||
* The event triggered, most likely a `mousedown` event.
|
||||
*/
|
||||
disableFields: function (event) {
|
||||
var $clickedButton = $(this).findOnce('ajax');
|
||||
|
||||
// Only disable upload fields for Ajax buttons.
|
||||
if (!$clickedButton.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we're working with an "Upload" button.
|
||||
var $enabledFields = [];
|
||||
if ($clickedButton.closest('div.js-form-managed-file').length > 0) {
|
||||
$enabledFields = $clickedButton.closest('div.js-form-managed-file').find('input.js-form-file');
|
||||
}
|
||||
|
||||
// Temporarily disable upload fields other than the one we're currently
|
||||
// working with. Filter out fields that are already disabled so that they
|
||||
// do not get enabled when we re-enable these fields at the end of
|
||||
// behavior processing. Re-enable in a setTimeout set to a relatively
|
||||
// short amount of time (1 second). All the other mousedown handlers
|
||||
// (like Drupal's Ajax behaviors) are executed before any timeout
|
||||
// functions are called, so we don't have to worry about the fields being
|
||||
// re-enabled too soon. @todo If the previous sentence is true, why not
|
||||
// set the timeout to 0?
|
||||
var $fieldsToTemporarilyDisable = $('div.js-form-managed-file input.js-form-file').not($enabledFields).not(':disabled');
|
||||
$fieldsToTemporarilyDisable.prop('disabled', true);
|
||||
setTimeout(function () {
|
||||
$fieldsToTemporarilyDisable.prop('disabled', false);
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add progress bar support if possible.
|
||||
*
|
||||
* @name Drupal.file.progressBar
|
||||
*
|
||||
* @param {jQuery.Event} event
|
||||
* The event triggered, most likely a `mousedown` event.
|
||||
*/
|
||||
progressBar: function (event) {
|
||||
progressBar: function progressBar(event) {
|
||||
var $clickedButton = $(this);
|
||||
var $progressId = $clickedButton.closest('div.js-form-managed-file').find('input.file-progress');
|
||||
if ($progressId.length) {
|
||||
var originalName = $progressId.attr('name');
|
||||
|
||||
// Replace the name with the required identifier.
|
||||
$progressId.attr('name', originalName.match(/APC_UPLOAD_PROGRESS|UPLOAD_IDENTIFIER/)[0]);
|
||||
|
||||
// Restore the original name after the upload begins.
|
||||
setTimeout(function () {
|
||||
$progressId.attr('name', originalName);
|
||||
}, 1000);
|
||||
}
|
||||
// Show the progress bar if the upload takes longer than half a second.
|
||||
|
||||
setTimeout(function () {
|
||||
$clickedButton.closest('div.js-form-managed-file').find('div.ajax-progress-bar').slideDown();
|
||||
}, 500);
|
||||
$clickedButton.trigger('fileUpload');
|
||||
},
|
||||
|
||||
/**
|
||||
* Open links to files within forms in a new window.
|
||||
*
|
||||
* @name Drupal.file.openInNewWindow
|
||||
*
|
||||
* @param {jQuery.Event} event
|
||||
* The event triggered, most likely a `click` event.
|
||||
*/
|
||||
openInNewWindow: function (event) {
|
||||
openInNewWindow: function openInNewWindow(event) {
|
||||
event.preventDefault();
|
||||
$(this).attr('target', '_blank');
|
||||
window.open(this.href, 'filePreview', 'toolbar=0,scrollbars=1,location=1,statusbar=1,menubar=0,resizable=1,width=500,height=550');
|
||||
}
|
||||
};
|
||||
|
||||
})(jQuery, Drupal);
|
||||
})(jQuery, Drupal);
|
|
@ -8,6 +8,7 @@
|
|||
use Drupal\Core\Datetime\Entity\DateFormat;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Messenger\MessengerInterface;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
|
@ -19,6 +20,11 @@ use Drupal\Component\Utility\Unicode;
|
|||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Template\Attribute;
|
||||
|
||||
/**
|
||||
* The regex pattern used when checking for insecure file types.
|
||||
*/
|
||||
define('FILE_INSECURE_EXTENSION_REGEX', '/\.(php|pl|py|cgi|asp|js)(\.|$)/i');
|
||||
|
||||
// Load all Field module hooks for File.
|
||||
require_once __DIR__ . '/file.field.inc';
|
||||
|
||||
|
@ -48,6 +54,16 @@ function file_help($route_name, RouteMatchInterface $route_match) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_widget_info_alter().
|
||||
*/
|
||||
function file_field_widget_info_alter(array &$info) {
|
||||
// Allows using the 'uri' widget for the 'file_uri' field type, which uses it
|
||||
// as the default widget.
|
||||
// @see \Drupal\file\Plugin\Field\FieldType\FileUriItem
|
||||
$info['uri']['field_types'][] = 'file_uri';
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads file entities from the database.
|
||||
*
|
||||
|
@ -140,13 +156,13 @@ function file_load($fid, $reset = FALSE) {
|
|||
*/
|
||||
function file_copy(FileInterface $source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
|
||||
if (!file_valid_uri($destination)) {
|
||||
if (($realpath = drupal_realpath($source->getFileUri())) !== FALSE) {
|
||||
if (($realpath = \Drupal::service('file_system')->realpath($source->getFileUri())) !== FALSE) {
|
||||
\Drupal::logger('file')->notice('File %file (%realpath) could not be copied because the destination %destination is invalid. This is often caused by improper use of file_copy() or a missing stream wrapper.', ['%file' => $source->getFileUri(), '%realpath' => $realpath, '%destination' => $destination]);
|
||||
}
|
||||
else {
|
||||
\Drupal::logger('file')->notice('File %file could not be copied because the destination %destination is invalid. This is often caused by improper use of file_copy() or a missing stream wrapper.', ['%file' => $source->getFileUri(), '%destination' => $destination]);
|
||||
}
|
||||
drupal_set_message(t('The specified file %file could not be copied because the destination is invalid. More information is available in the system log.', ['%file' => $source->getFileUri()]), 'error');
|
||||
\Drupal::messenger()->addError(t('The specified file %file could not be copied because the destination is invalid. More information is available in the system log.', ['%file' => $source->getFileUri()]));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
@ -215,13 +231,13 @@ function file_copy(FileInterface $source, $destination = NULL, $replace = FILE_E
|
|||
*/
|
||||
function file_move(FileInterface $source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
|
||||
if (!file_valid_uri($destination)) {
|
||||
if (($realpath = drupal_realpath($source->getFileUri())) !== FALSE) {
|
||||
if (($realpath = \Drupal::service('file_system')->realpath($source->getFileUri())) !== FALSE) {
|
||||
\Drupal::logger('file')->notice('File %file (%realpath) could not be moved because the destination %destination is invalid. This may be caused by improper use of file_move() or a missing stream wrapper.', ['%file' => $source->getFileUri(), '%realpath' => $realpath, '%destination' => $destination]);
|
||||
}
|
||||
else {
|
||||
\Drupal::logger('file')->notice('File %file could not be moved because the destination %destination is invalid. This may be caused by improper use of file_move() or a missing stream wrapper.', ['%file' => $source->getFileUri(), '%destination' => $destination]);
|
||||
}
|
||||
drupal_set_message(t('The specified file %file could not be moved because the destination is invalid. More information is available in the system log.', ['%file' => $source->getFileUri()]), 'error');
|
||||
\Drupal::messenger()->addError(t('The specified file %file could not be moved because the destination is invalid. More information is available in the system log.', ['%file' => $source->getFileUri()]));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
@ -396,7 +412,7 @@ function file_validate_is_image(FileInterface $file) {
|
|||
$image = $image_factory->get($file->getFileUri());
|
||||
if (!$image->isValid()) {
|
||||
$supported_extensions = $image_factory->getSupportedExtensions();
|
||||
$errors[] = t('Image type not supported. Allowed types: %types', ['%types' => implode(' ', $supported_extensions)]);
|
||||
$errors[] = t('The image file is invalid or the image type is not allowed. Allowed types: %types', ['%types' => implode(', ', $supported_extensions)]);
|
||||
}
|
||||
|
||||
return $errors;
|
||||
|
@ -434,24 +450,42 @@ function file_validate_image_resolution(FileInterface $file, $maximum_dimensions
|
|||
// Check first that the file is an image.
|
||||
$image_factory = \Drupal::service('image.factory');
|
||||
$image = $image_factory->get($file->getFileUri());
|
||||
|
||||
if ($image->isValid()) {
|
||||
$scaling = FALSE;
|
||||
if ($maximum_dimensions) {
|
||||
// Check that it is smaller than the given dimensions.
|
||||
list($width, $height) = explode('x', $maximum_dimensions);
|
||||
if ($image->getWidth() > $width || $image->getHeight() > $height) {
|
||||
// Try to resize the image to fit the dimensions.
|
||||
if ($image->scale($width, $height)) {
|
||||
$scaling = TRUE;
|
||||
$image->save();
|
||||
if (!empty($width) && !empty($height)) {
|
||||
$message = t('The image was resized to fit within the maximum allowed dimensions of %dimensions pixels.', ['%dimensions' => $maximum_dimensions]);
|
||||
$message = t('The image was resized to fit within the maximum allowed dimensions of %dimensions pixels. The new dimensions of the resized image are %new_widthx%new_height pixels.',
|
||||
[
|
||||
'%dimensions' => $maximum_dimensions,
|
||||
'%new_width' => $image->getWidth(),
|
||||
'%new_height' => $image->getHeight(),
|
||||
]);
|
||||
}
|
||||
elseif (empty($width)) {
|
||||
$message = t('The image was resized to fit within the maximum allowed height of %height pixels.', ['%height' => $height]);
|
||||
$message = t('The image was resized to fit within the maximum allowed height of %height pixels. The new dimensions of the resized image are %new_widthx%new_height pixels.',
|
||||
[
|
||||
'%height' => $height,
|
||||
'%new_width' => $image->getWidth(),
|
||||
'%new_height' => $image->getHeight(),
|
||||
]);
|
||||
}
|
||||
elseif (empty($height)) {
|
||||
$message = t('The image was resized to fit within the maximum allowed width of %width pixels.', ['%width' => $width]);
|
||||
$message = t('The image was resized to fit within the maximum allowed width of %width pixels. The new dimensions of the resized image are %new_widthx%new_height pixels.',
|
||||
[
|
||||
'%width' => $width,
|
||||
'%new_width' => $image->getWidth(),
|
||||
'%new_height' => $image->getHeight(),
|
||||
]);
|
||||
}
|
||||
drupal_set_message($message);
|
||||
\Drupal::messenger()->addStatus($message);
|
||||
}
|
||||
else {
|
||||
$errors[] = t('The image exceeds the maximum allowed dimensions and an attempt to resize it failed.');
|
||||
|
@ -463,7 +497,22 @@ function file_validate_image_resolution(FileInterface $file, $maximum_dimensions
|
|||
// Check that it is larger than the given dimensions.
|
||||
list($width, $height) = explode('x', $minimum_dimensions);
|
||||
if ($image->getWidth() < $width || $image->getHeight() < $height) {
|
||||
$errors[] = t('The image is too small; the minimum dimensions are %dimensions pixels.', ['%dimensions' => $minimum_dimensions]);
|
||||
if ($scaling) {
|
||||
$errors[] = t('The resized image is too small. The minimum dimensions are %dimensions pixels and after resizing, the image size will be %widthx%height pixels.',
|
||||
[
|
||||
'%dimensions' => $minimum_dimensions,
|
||||
'%width' => $image->getWidth(),
|
||||
'%height' => $image->getHeight(),
|
||||
]);
|
||||
}
|
||||
else {
|
||||
$errors[] = t('The image is too small. The minimum dimensions are %dimensions pixels and the image size is %widthx%height pixels.',
|
||||
[
|
||||
'%dimensions' => $minimum_dimensions,
|
||||
'%width' => $image->getWidth(),
|
||||
'%height' => $image->getHeight(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -504,7 +553,7 @@ function file_save_data($data, $destination = NULL, $replace = FILE_EXISTS_RENAM
|
|||
}
|
||||
if (!file_valid_uri($destination)) {
|
||||
\Drupal::logger('file')->notice('The data could not be saved because the destination %destination is invalid. This may be caused by improper use of file_save_data() or a missing stream wrapper.', ['%destination' => $destination]);
|
||||
drupal_set_message(t('The data could not be saved because the destination is invalid. More information is available in the system log.'), 'error');
|
||||
\Drupal::messenger()->addError(t('The data could not be saved because the destination is invalid. More information is available in the system log.'));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
@ -571,6 +620,12 @@ function file_theme() {
|
|||
'file_managed_file' => [
|
||||
'render element' => 'element',
|
||||
],
|
||||
'file_audio' => [
|
||||
'variables' => ['files' => [], 'attributes' => NULL],
|
||||
],
|
||||
'file_video' => [
|
||||
'variables' => ['files' => [], 'attributes' => NULL],
|
||||
],
|
||||
|
||||
// From file.field.inc.
|
||||
'file_widget_multiple' => [
|
||||
|
@ -673,6 +728,90 @@ function file_cron() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves form file uploads.
|
||||
*
|
||||
* The files will be added to the {file_managed} table as temporary files.
|
||||
* Temporary files are periodically cleaned. Use the 'file.usage' service to
|
||||
* register the usage of the file which will automatically mark it as permanent.
|
||||
*
|
||||
* @param array $element
|
||||
* The FAPI element whose values are being saved.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
* @param null|int $delta
|
||||
* (optional) The delta of the file to return the file entity.
|
||||
* Defaults to NULL.
|
||||
* @param int $replace
|
||||
* (optional) The replace behavior when the destination file already exists.
|
||||
* Possible values include:
|
||||
* - FILE_EXISTS_REPLACE: Replace the existing file.
|
||||
* - FILE_EXISTS_RENAME: (default) Append _{incrementing number} until the
|
||||
* filename is unique.
|
||||
* - FILE_EXISTS_ERROR: Do nothing and return FALSE.
|
||||
*
|
||||
* @return array|\Drupal\file\FileInterface|null|false
|
||||
* An array of file entities or a single file entity if $delta != NULL. Each
|
||||
* array element contains the file entity if the upload succeeded or FALSE if
|
||||
* there was an error. Function returns NULL if no file was uploaded.
|
||||
*
|
||||
* @deprecated in Drupal 8.4.x, will be removed before Drupal 9.0.0.
|
||||
* For backwards compatibility use core file upload widgets in forms.
|
||||
*
|
||||
* @internal
|
||||
* This function wraps file_save_upload() to allow correct error handling in
|
||||
* forms.
|
||||
*
|
||||
* @todo Revisit after https://www.drupal.org/node/2244513.
|
||||
*/
|
||||
function _file_save_upload_from_form(array $element, FormStateInterface $form_state, $delta = NULL, $replace = FILE_EXISTS_RENAME) {
|
||||
// Get all errors set before calling this method. This will also clear them
|
||||
// from $_SESSION.
|
||||
$errors_before = \Drupal::messenger()->deleteByType(MessengerInterface::TYPE_ERROR);
|
||||
|
||||
$upload_location = isset($element['#upload_location']) ? $element['#upload_location'] : FALSE;
|
||||
$upload_name = implode('_', $element['#parents']);
|
||||
$upload_validators = isset($element['#upload_validators']) ? $element['#upload_validators'] : [];
|
||||
|
||||
$result = file_save_upload($upload_name, $upload_validators, $upload_location, $delta, $replace);
|
||||
|
||||
// Get new errors that are generated while trying to save the upload. This
|
||||
// will also clear them from $_SESSION.
|
||||
$errors_new = \Drupal::messenger()->deleteByType(MessengerInterface::TYPE_ERROR);
|
||||
if (!empty($errors_new)) {
|
||||
|
||||
if (count($errors_new) > 1) {
|
||||
// Render multiple errors into a single message.
|
||||
// This is needed because only one error per element is supported.
|
||||
$render_array = [
|
||||
'error' => [
|
||||
'#markup' => t('One or more files could not be uploaded.'),
|
||||
],
|
||||
'item_list' => [
|
||||
'#theme' => 'item_list',
|
||||
'#items' => $errors_new,
|
||||
],
|
||||
];
|
||||
$error_message = \Drupal::service('renderer')->renderPlain($render_array);
|
||||
}
|
||||
else {
|
||||
$error_message = reset($errors_new);
|
||||
}
|
||||
|
||||
$form_state->setError($element, $error_message);
|
||||
}
|
||||
|
||||
// Ensure that errors set prior to calling this method are still shown to the
|
||||
// user.
|
||||
if (!empty($errors_before)) {
|
||||
foreach ($errors_before as $error) {
|
||||
\Drupal::messenger()->addError($error);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves file uploads to a new location.
|
||||
*
|
||||
|
@ -680,6 +819,10 @@ function file_cron() {
|
|||
* Temporary files are periodically cleaned. Use the 'file.usage' service to
|
||||
* register the usage of the file which will automatically mark it as permanent.
|
||||
*
|
||||
* Note that this function does not support correct form error handling. The
|
||||
* file upload widgets in core do support this. It is advised to use these in
|
||||
* any custom form, instead of calling this function.
|
||||
*
|
||||
* @param string $form_field_name
|
||||
* A string that is the associative array key of the upload form element in
|
||||
* the form array.
|
||||
|
@ -710,6 +853,10 @@ function file_cron() {
|
|||
* An array of file entities or a single file entity if $delta != NULL. Each
|
||||
* array element contains the file entity if the upload succeeded or FALSE if
|
||||
* there was an error. Function returns NULL if no file was uploaded.
|
||||
*
|
||||
* @see _file_save_upload_from_form()
|
||||
*
|
||||
* @todo: move this logic to a service in https://www.drupal.org/node/2244513.
|
||||
*/
|
||||
function file_save_upload($form_field_name, $validators = [], $destination = FALSE, $delta = NULL, $replace = FILE_EXISTS_RENAME) {
|
||||
$user = \Drupal::currentUser();
|
||||
|
@ -746,13 +893,13 @@ function file_save_upload($form_field_name, $validators = [], $destination = FAL
|
|||
switch ($file_info->getError()) {
|
||||
case UPLOAD_ERR_INI_SIZE:
|
||||
case UPLOAD_ERR_FORM_SIZE:
|
||||
drupal_set_message(t('The file %file could not be saved because it exceeds %maxsize, the maximum allowed size for uploads.', ['%file' => $file_info->getFilename(), '%maxsize' => format_size(file_upload_max_size())]), 'error');
|
||||
\Drupal::messenger()->addError(t('The file %file could not be saved because it exceeds %maxsize, the maximum allowed size for uploads.', ['%file' => $file_info->getFilename(), '%maxsize' => format_size(file_upload_max_size())]));
|
||||
$files[$i] = FALSE;
|
||||
continue;
|
||||
|
||||
case UPLOAD_ERR_PARTIAL:
|
||||
case UPLOAD_ERR_NO_FILE:
|
||||
drupal_set_message(t('The file %file could not be saved because the upload did not complete.', ['%file' => $file_info->getFilename()]), 'error');
|
||||
\Drupal::messenger()->addError(t('The file %file could not be saved because the upload did not complete.', ['%file' => $file_info->getFilename()]));
|
||||
$files[$i] = FALSE;
|
||||
continue;
|
||||
|
||||
|
@ -765,7 +912,7 @@ function file_save_upload($form_field_name, $validators = [], $destination = FAL
|
|||
|
||||
// Unknown error
|
||||
default:
|
||||
drupal_set_message(t('The file %file could not be saved. An unknown error has occurred.', ['%file' => $file_info->getFilename()]), 'error');
|
||||
\Drupal::messenger()->addError(t('The file %file could not be saved. An unknown error has occurred.', ['%file' => $file_info->getFilename()]));
|
||||
$files[$i] = FALSE;
|
||||
continue;
|
||||
|
||||
|
@ -812,7 +959,7 @@ function file_save_upload($form_field_name, $validators = [], $destination = FAL
|
|||
// rename filename.php.foo and filename.php to filename.php.foo.txt and
|
||||
// filename.php.txt, respectively). Don't rename if 'allow_insecure_uploads'
|
||||
// evaluates to TRUE.
|
||||
if (!\Drupal::config('system.file')->get('allow_insecure_uploads') && preg_match('/\.(php|pl|py|cgi|asp|js)(\.|$)/i', $file->getFilename()) && (substr($file->getFilename(), -4) != '.txt')) {
|
||||
if (!\Drupal::config('system.file')->get('allow_insecure_uploads') && preg_match(FILE_INSECURE_EXTENSION_REGEX, $file->getFilename()) && (substr($file->getFilename(), -4) != '.txt')) {
|
||||
$file->setMimeType('text/plain');
|
||||
// The destination filename will also later be used to create the URI.
|
||||
$file->setFilename($file->getFilename() . '.txt');
|
||||
|
@ -820,7 +967,7 @@ function file_save_upload($form_field_name, $validators = [], $destination = FAL
|
|||
// to add it here or else the file upload will fail.
|
||||
if (!empty($extensions)) {
|
||||
$validators['file_validate_extensions'][0] .= ' txt';
|
||||
drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', ['%filename' => $file->getFilename()]));
|
||||
\Drupal::messenger()->addStatus(t('For security reasons, your upload has been renamed to %filename.', ['%filename' => $file->getFilename()]));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -832,7 +979,7 @@ function file_save_upload($form_field_name, $validators = [], $destination = FAL
|
|||
// Assert that the destination contains a valid stream.
|
||||
$destination_scheme = file_uri_scheme($destination);
|
||||
if (!file_stream_wrapper_valid_scheme($destination_scheme)) {
|
||||
drupal_set_message(t('The file could not be uploaded because the destination %destination is invalid.', ['%destination' => $destination]), 'error');
|
||||
\Drupal::messenger()->addError(t('The file could not be uploaded because the destination %destination is invalid.', ['%destination' => $destination]));
|
||||
$files[$i] = FALSE;
|
||||
continue;
|
||||
}
|
||||
|
@ -846,7 +993,7 @@ function file_save_upload($form_field_name, $validators = [], $destination = FAL
|
|||
// If file_destination() returns FALSE then $replace === FILE_EXISTS_ERROR and
|
||||
// there's an existing file so we need to bail.
|
||||
if ($file->destination === FALSE) {
|
||||
drupal_set_message(t('The file %source could not be uploaded because a file by that name already exists in the destination %directory.', ['%source' => $form_field_name, '%directory' => $destination]), 'error');
|
||||
\Drupal::messenger()->addError(t('The file %source could not be uploaded because a file by that name already exists in the destination %directory.', ['%source' => $form_field_name, '%directory' => $destination]));
|
||||
$files[$i] = FALSE;
|
||||
continue;
|
||||
}
|
||||
|
@ -868,19 +1015,17 @@ function file_save_upload($form_field_name, $validators = [], $destination = FAL
|
|||
'#items' => $errors,
|
||||
],
|
||||
];
|
||||
// @todo Add support for render arrays in drupal_set_message()? See
|
||||
// https://www.drupal.org/node/2505497.
|
||||
drupal_set_message(\Drupal::service('renderer')->renderPlain($message), 'error');
|
||||
// @todo Add support for render arrays in
|
||||
// \Drupal\Core\Messenger\MessengerInterface::addMessage()?
|
||||
// @see https://www.drupal.org/node/2505497.
|
||||
\Drupal::messenger()->addError(\Drupal::service('renderer')->renderPlain($message));
|
||||
$files[$i] = FALSE;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Move uploaded files from PHP's upload_tmp_dir to Drupal's temporary
|
||||
// directory. This overcomes open_basedir restrictions for future file
|
||||
// operations.
|
||||
$file->setFileUri($file->destination);
|
||||
if (!drupal_move_uploaded_file($file_info->getRealPath(), $file->getFileUri())) {
|
||||
drupal_set_message(t('File upload error. Could not move uploaded file.'), 'error');
|
||||
\Drupal::messenger()->addError(t('File upload error. Could not move uploaded file.'));
|
||||
\Drupal::logger('file')->notice('Upload error. Could not move uploaded file %file to destination %destination.', ['%file' => $file->getFilename(), '%destination' => $file->getFileUri()]);
|
||||
$files[$i] = FALSE;
|
||||
continue;
|
||||
|
@ -1200,15 +1345,16 @@ function file_managed_file_save_upload($element, FormStateInterface $form_state)
|
|||
$files_uploaded = $element['#multiple'] && count(array_filter($file_upload)) > 0;
|
||||
$files_uploaded |= !$element['#multiple'] && !empty($file_upload);
|
||||
if ($files_uploaded) {
|
||||
if (!$files = file_save_upload($upload_name, $element['#upload_validators'], $destination)) {
|
||||
if (!$files = _file_save_upload_from_form($element, $form_state)) {
|
||||
\Drupal::logger('file')->notice('The file upload failed. %upload', ['%upload' => $upload_name]);
|
||||
$form_state->setError($element, t('Files in the @name field were unable to be uploaded.', ['@name' => $element['#title']]));
|
||||
return [];
|
||||
}
|
||||
|
||||
// Value callback expects FIDs to be keys.
|
||||
$files = array_filter($files);
|
||||
$fids = array_map(function($file) { return $file->id(); }, $files);
|
||||
$fids = array_map(function ($file) {
|
||||
return $file->id();
|
||||
}, $files);
|
||||
|
||||
return empty($files) ? [] : array_combine($fids, $files);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
services:
|
||||
file.usage:
|
||||
class: Drupal\file\FileUsage\DatabaseFileUsageBackend
|
||||
arguments: ['@database']
|
||||
arguments: ['@database', 'file_usage', '@config.factory']
|
||||
tags:
|
||||
- { name: backend_overridable }
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
# Every migration that references a file by Drupal 6 fid should specify this
|
||||
# migration as an optional dependency.
|
||||
id: d6_file
|
||||
label: Files
|
||||
label: Public files
|
||||
audit: true
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Content
|
||||
source:
|
||||
plugin: d6_file
|
||||
constants:
|
|
@ -2,14 +2,19 @@ id: d6_upload
|
|||
label: File uploads
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Content
|
||||
source:
|
||||
plugin: d6_upload
|
||||
process:
|
||||
nid: nid
|
||||
vid: vid
|
||||
langcode:
|
||||
plugin: user_langcode
|
||||
source: language
|
||||
fallback_to_site_default: true
|
||||
type: type
|
||||
upload:
|
||||
plugin: iterator
|
||||
plugin: sub_process
|
||||
source: upload
|
||||
process:
|
||||
target_id:
|
|
@ -2,6 +2,7 @@ id: d6_upload_entity_display
|
|||
label: Upload display configuration
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Configuration
|
||||
source:
|
||||
plugin: d6_upload_instance
|
||||
constants:
|
|
@ -2,6 +2,7 @@ id: d6_upload_entity_form_display
|
|||
label: Upload form display configuration
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Configuration
|
||||
source:
|
||||
plugin: d6_upload_instance
|
||||
constants:
|
|
@ -2,11 +2,12 @@ id: d6_upload_field
|
|||
label: Upload field configuration
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Configuration
|
||||
source:
|
||||
# We do an empty source and a proper destination to have an idmap for
|
||||
# migration_dependencies.
|
||||
plugin: md_empty
|
||||
provider: upload
|
||||
source_module: upload
|
||||
constants:
|
||||
entity_type: node
|
||||
type: file
|
|
@ -2,6 +2,7 @@ id: d6_upload_field_instance
|
|||
label: Upload field instance configuration
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Configuration
|
||||
source:
|
||||
plugin: d6_upload_instance
|
||||
constants:
|
|
@ -1,11 +1,14 @@
|
|||
# Every migration that references a file by Drupal 7 fid should specify this
|
||||
# migration as an optional dependency.
|
||||
id: d7_file
|
||||
label: Files
|
||||
label: Public files
|
||||
audit: true
|
||||
migration_tags:
|
||||
- Drupal 7
|
||||
- Content
|
||||
source:
|
||||
plugin: d7_file
|
||||
scheme: public
|
||||
constants:
|
||||
# The tool configuring this migration must set source_base_path. It
|
||||
# represents the fully qualified path relative to which URIs in the files
|
41
web/core/modules/file/migrations/d7_file_private.yml
Normal file
41
web/core/modules/file/migrations/d7_file_private.yml
Normal file
|
@ -0,0 +1,41 @@
|
|||
id: d7_file_private
|
||||
label: Private files
|
||||
audit: true
|
||||
migration_tags:
|
||||
- Drupal 7
|
||||
- Content
|
||||
source:
|
||||
plugin: d7_file
|
||||
scheme: private
|
||||
constants:
|
||||
# source_base_path must be set by the tool configuring this migration.
|
||||
# It represents the fully qualified path relative to which uris in the files
|
||||
# table are specified, and must end with a /. See source_full_path
|
||||
# configuration in this migration's process pipeline as an example.
|
||||
source_base_path: ''
|
||||
process:
|
||||
# If you are using this file to build a custom migration consider removing
|
||||
# the fid field to allow incremental migrations.
|
||||
fid: fid
|
||||
filename: filename
|
||||
source_full_path:
|
||||
-
|
||||
plugin: concat
|
||||
delimiter: /
|
||||
source:
|
||||
- constants/source_base_path
|
||||
- filepath
|
||||
uri:
|
||||
plugin: file_copy
|
||||
source:
|
||||
- '@source_full_path'
|
||||
- uri
|
||||
filemime: filemime
|
||||
status: status
|
||||
# Drupal 7 didn't keep track of the file's creation or update time -- all it
|
||||
# had was the vague "timestamp" column. So we'll use it for both.
|
||||
created: timestamp
|
||||
changed: timestamp
|
||||
uid: uid
|
||||
destination:
|
||||
plugin: entity:file
|
|
@ -3,12 +3,14 @@ label: File configuration
|
|||
migration_tags:
|
||||
- Drupal 6
|
||||
- Drupal 7
|
||||
- Configuration
|
||||
source:
|
||||
plugin: variable
|
||||
variables:
|
||||
- file_description_type
|
||||
- file_description_length
|
||||
- file_icon_directory
|
||||
source_module: system
|
||||
process:
|
||||
'description/type': file_description_type
|
||||
'description/length': file_description_length
|
47
web/core/modules/file/src/ComputedFileUrl.php
Normal file
47
web/core/modules/file/src/ComputedFileUrl.php
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\file;
|
||||
|
||||
use Drupal\Core\TypedData\TypedData;
|
||||
|
||||
/**
|
||||
* Computed file URL property class.
|
||||
*/
|
||||
class ComputedFileUrl extends TypedData {
|
||||
|
||||
/**
|
||||
* Computed root-relative file URL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $url = NULL;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getValue() {
|
||||
if ($this->url !== NULL) {
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
assert($this->getParent()->getEntity() instanceof FileInterface);
|
||||
|
||||
$uri = $this->getParent()->getEntity()->getFileUri();
|
||||
$this->url = file_url_transform_relative(file_create_url($uri));
|
||||
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setValue($value, $notify = TRUE) {
|
||||
$this->url = $value;
|
||||
|
||||
// Notify the parent of any changes.
|
||||
if ($notify && isset($this->parent)) {
|
||||
$this->parent->onChange($this->name);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -8,6 +8,7 @@ use Drupal\Component\Utility\NestedArray;
|
|||
use Drupal\Core\Ajax\AjaxResponse;
|
||||
use Drupal\Core\Ajax\ReplaceCommand;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Core\Render\Element\FormElement;
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Drupal\Core\Url;
|
||||
|
@ -175,6 +176,9 @@ class ManagedFile extends FormElement {
|
|||
|
||||
$form_parents = explode('/', $request->query->get('element_parents'));
|
||||
|
||||
// Sanitize form parents before using them.
|
||||
$form_parents = array_filter($form_parents, [Element::class, 'child']);
|
||||
|
||||
// Retrieve the element to be rendered.
|
||||
$form = NestedArray::getValue($form, $form_parents);
|
||||
|
||||
|
@ -295,6 +299,10 @@ class ManagedFile extends FormElement {
|
|||
|
||||
// Add the upload progress callback.
|
||||
$element['upload_button']['#ajax']['progress']['url'] = Url::fromRoute('file.ajax_progress', ['key' => $upload_progress_key]);
|
||||
|
||||
// Set a custom submit event so we can modify the upload progress
|
||||
// identifier element before the form gets submitted.
|
||||
$element['upload_button']['#ajax']['event'] = 'fileUpload';
|
||||
}
|
||||
|
||||
// The file upload field itself.
|
||||
|
@ -398,15 +406,23 @@ class ManagedFile extends FormElement {
|
|||
* Render API callback: Validates the managed_file element.
|
||||
*/
|
||||
public static function validateManagedFile(&$element, FormStateInterface $form_state, &$complete_form) {
|
||||
// If referencing an existing file, only allow if there are existing
|
||||
// references. This prevents unmanaged files from being deleted if this
|
||||
// item were to be deleted.
|
||||
$clicked_button = end($form_state->getTriggeringElement()['#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()) {
|
||||
// If referencing an existing file, only allow if there are existing
|
||||
// references. This prevents unmanaged files from being deleted if
|
||||
// this item were to be deleted. When files that are no longer in use
|
||||
// are automatically marked as temporary (now disabled by default),
|
||||
// it is not safe to reference a permanent file without usage. Adding
|
||||
// a usage and then later on removing it again would delete the file,
|
||||
// but it is unknown if and where it is currently referenced. However,
|
||||
// when files are not marked temporary (and then removed)
|
||||
// automatically, it is safe to add and remove usages, as it would
|
||||
// simply return to the current state.
|
||||
// @see https://www.drupal.org/node/2891902
|
||||
if ($file->isPermanent() && \Drupal::config('file.settings')->get('make_unused_managed_files_temporary')) {
|
||||
$references = static::fileUsage()->listUsage($file);
|
||||
if (empty($references)) {
|
||||
// We expect the field name placeholder value to be wrapped in t()
|
||||
|
|
|
@ -18,6 +18,13 @@ use Drupal\user\UserInterface;
|
|||
* @ContentEntityType(
|
||||
* id = "file",
|
||||
* label = @Translation("File"),
|
||||
* label_collection = @Translation("Files"),
|
||||
* label_singular = @Translation("file"),
|
||||
* label_plural = @Translation("files"),
|
||||
* label_count = @PluralTranslation(
|
||||
* singular = "@count file",
|
||||
* plural = "@count files",
|
||||
* ),
|
||||
* handlers = {
|
||||
* "storage" = "Drupal\file\FileStorage",
|
||||
* "storage_schema" = "Drupal\file\FileStorageSchema",
|
||||
|
@ -190,7 +197,10 @@ class File extends ContentEntityBase implements FileInterface {
|
|||
|
||||
// The file itself might not exist or be available right now.
|
||||
$uri = $this->getFileUri();
|
||||
if ($size = @filesize($uri)) {
|
||||
$size = @filesize($uri);
|
||||
|
||||
// Set size unless there was an error.
|
||||
if ($size !== FALSE) {
|
||||
$this->setSize($size);
|
||||
}
|
||||
}
|
||||
|
@ -240,7 +250,7 @@ class File extends ContentEntityBase implements FileInterface {
|
|||
->setLabel(t('Filename'))
|
||||
->setDescription(t('Name of the file with no path components.'));
|
||||
|
||||
$fields['uri'] = BaseFieldDefinition::create('uri')
|
||||
$fields['uri'] = BaseFieldDefinition::create('file_uri')
|
||||
->setLabel(t('URI'))
|
||||
->setDescription(t('The URI to access the file (either local or remote).'))
|
||||
->setSetting('max_length', 255)
|
||||
|
|
|
@ -22,8 +22,12 @@ class FileAccessControlHandler extends EntityAccessControlHandler {
|
|||
/** @var \Drupal\file\FileInterface $entity */
|
||||
if ($operation == 'download' || $operation == 'view') {
|
||||
if (\Drupal::service('file_system')->uriScheme($entity->getFileUri()) === 'public') {
|
||||
// Always allow access to file in public file system.
|
||||
return AccessResult::allowed();
|
||||
if ($operation === 'download') {
|
||||
return AccessResult::allowed();
|
||||
}
|
||||
else {
|
||||
return AccessResult::allowedIfHasPermission($account, 'access content');
|
||||
}
|
||||
}
|
||||
elseif ($references = $this->getFileReferences($entity)) {
|
||||
foreach ($references as $field_name => $entity_map) {
|
||||
|
@ -48,11 +52,11 @@ class FileAccessControlHandler extends EntityAccessControlHandler {
|
|||
// services can be more properly injected.
|
||||
$allowed_fids = \Drupal::service('session')->get('anonymous_allowed_file_ids', []);
|
||||
if (!empty($allowed_fids[$entity->id()])) {
|
||||
return AccessResult::allowed();
|
||||
return AccessResult::allowed()->addCacheContexts(['session', 'user']);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return AccessResult::allowed();
|
||||
return AccessResult::allowed()->addCacheContexts(['user']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -60,11 +64,11 @@ class FileAccessControlHandler extends EntityAccessControlHandler {
|
|||
if ($operation == 'delete' || $operation == 'update') {
|
||||
$account = $this->prepareUser($account);
|
||||
$file_uid = $entity->get('uid')->getValue();
|
||||
// Only the file owner can delete and update the file entity.
|
||||
// Only the file owner can update or delete the file entity.
|
||||
if ($account->id() == $file_uid[0]['target_id']) {
|
||||
return AccessResult::allowed();
|
||||
}
|
||||
return AccessResult::forbidden();
|
||||
return AccessResult::forbidden('Only the file owner can update or delete the file entity.');
|
||||
}
|
||||
|
||||
// No opinion.
|
||||
|
@ -123,8 +127,6 @@ class FileAccessControlHandler extends EntityAccessControlHandler {
|
|||
// create file entities that are referenced from another entity
|
||||
// (e.g. an image for a article). A contributed module is free to alter
|
||||
// this to allow file entities to be created directly.
|
||||
// @todo Update comment to mention REST module when
|
||||
// https://www.drupal.org/node/1927648 is fixed.
|
||||
return AccessResult::neutral();
|
||||
}
|
||||
|
||||
|
|
|
@ -22,4 +22,4 @@ use Drupal\Core\Entity\EntityAccessControlHandlerInterface;
|
|||
*
|
||||
* @see \Drupal\file\Plugin\Field\FieldFormatter\FileFormatterBase::needsAccessCheck()
|
||||
*/
|
||||
interface FileAccessFormatterControlHandlerInterface extends EntityAccessControlHandlerInterface { }
|
||||
interface FileAccessFormatterControlHandlerInterface extends EntityAccessControlHandlerInterface {}
|
||||
|
|
23
web/core/modules/file/src/FileServiceProvider.php
Normal file
23
web/core/modules/file/src/FileServiceProvider.php
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\file;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\Core\DependencyInjection\ServiceModifierInterface;
|
||||
use Drupal\Core\StackMiddleware\NegotiationMiddleware;
|
||||
|
||||
/**
|
||||
* Adds 'application/octet-stream' as a known (bin) format.
|
||||
*/
|
||||
class FileServiceProvider implements ServiceModifierInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function alter(ContainerBuilder $container) {
|
||||
if ($container->has('http_middleware.negotiation') && is_a($container->getDefinition('http_middleware.negotiation')->getClass(), NegotiationMiddleware::class, TRUE)) {
|
||||
$container->getDefinition('http_middleware.negotiation')->addMethodCall('registerFormat', ['bin', ['application/octet-stream']]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -17,7 +17,7 @@ class FileStorageSchema extends SqlContentEntityStorageSchema {
|
|||
$schema = parent::getSharedTableFieldSchema($storage_definition, $table_name, $column_mapping);
|
||||
$field_name = $storage_definition->getName();
|
||||
|
||||
if ($table_name == 'file_managed') {
|
||||
if ($table_name == $this->storage->getBaseTable()) {
|
||||
switch ($field_name) {
|
||||
case 'status':
|
||||
case 'changed':
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Drupal\file\FileUsage;
|
||||
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\file\FileInterface;
|
||||
|
||||
|
@ -32,8 +33,11 @@ class DatabaseFileUsageBackend extends FileUsageBase {
|
|||
* information.
|
||||
* @param string $table
|
||||
* (optional) The table to store file usage info. Defaults to 'file_usage'.
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* (optional) The config factory.
|
||||
*/
|
||||
public function __construct(Connection $connection, $table = 'file_usage') {
|
||||
public function __construct(Connection $connection, $table = 'file_usage', ConfigFactoryInterface $config_factory = NULL) {
|
||||
parent::__construct($config_factory);
|
||||
$this->connection = $connection;
|
||||
|
||||
$this->tableName = $table;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Drupal\file\FileUsage;
|
||||
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\file\FileInterface;
|
||||
|
||||
/**
|
||||
|
@ -9,6 +10,27 @@ use Drupal\file\FileInterface;
|
|||
*/
|
||||
abstract class FileUsageBase implements FileUsageInterface {
|
||||
|
||||
/**
|
||||
* The config factory.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* Creates a FileUsageBase object.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* (optional) The config factory. Defaults to NULL and will use
|
||||
* \Drupal::configFactory() instead.
|
||||
*
|
||||
* @deprecated The $config_factory parameter will become required in Drupal
|
||||
* 9.0.0.
|
||||
*/
|
||||
public function __construct(ConfigFactoryInterface $config_factory = NULL) {
|
||||
$this->configFactory = $config_factory ?: \Drupal::configFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -24,6 +46,10 @@ abstract class FileUsageBase implements FileUsageInterface {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete(FileInterface $file, $module, $type = NULL, $id = NULL, $count = 1) {
|
||||
// Do not actually mark files as temporary when the behavior is disabled.
|
||||
if (!$this->configFactory->get('file.settings')->get('make_unused_managed_files_temporary')) {
|
||||
return;
|
||||
}
|
||||
// If there are no more remaining usages of this file, mark it as temporary,
|
||||
// which result in a delete through system_cron().
|
||||
$usage = \Drupal::service('file.usage')->listUsage($file);
|
||||
|
|
|
@ -65,7 +65,7 @@ class FileViewsData extends EntityViewsData {
|
|||
$data['file_managed']['uid']['relationship']['title'] = $this->t('User who uploaded');
|
||||
$data['file_managed']['uid']['relationship']['label'] = $this->t('User who uploaded');
|
||||
|
||||
$data['file_usage']['table']['group'] = $this->t('File Usage');
|
||||
$data['file_usage']['table']['group'] = $this->t('File Usage');
|
||||
|
||||
// Provide field-type-things to several base tables; on the core files table
|
||||
// ("file_managed") so that we can create relationships from files to
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\file\Plugin\Field\FieldFormatter;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Base class for file formatters that have to deal with file descriptions.
|
||||
*/
|
||||
abstract class DescriptionAwareFileFormatterBase extends FileFormatterBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function defaultSettings() {
|
||||
$settings = parent::defaultSettings();
|
||||
|
||||
$settings['use_description_as_link_text'] = TRUE;
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsForm(array $form, FormStateInterface $form_state) {
|
||||
$form = parent::settingsForm($form, $form_state);
|
||||
|
||||
$form['use_description_as_link_text'] = [
|
||||
'#title' => $this->t('Use description as link text'),
|
||||
'#description' => $this->t('Replace the file name by its description when available'),
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => $this->getSetting('use_description_as_link_text'),
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsSummary() {
|
||||
$summary = parent::settingsSummary();
|
||||
|
||||
if ($this->getSetting('use_description_as_link_text')) {
|
||||
$summary[] = $this->t('Use description as link text');
|
||||
}
|
||||
|
||||
return $summary;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\file\Plugin\Field\FieldFormatter;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'file_audio' formatter.
|
||||
*
|
||||
* @FieldFormatter(
|
||||
* id = "file_audio",
|
||||
* label = @Translation("Audio"),
|
||||
* description = @Translation("Display the file using an HTML5 audio tag."),
|
||||
* field_types = {
|
||||
* "file"
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class FileAudioFormatter extends FileMediaFormatterBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getMediaType() {
|
||||
return 'audio';
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,220 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\file\Plugin\Field\FieldFormatter;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Field\EntityReferenceFieldItemListInterface;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Template\Attribute;
|
||||
|
||||
/**
|
||||
* Base class for media file formatter.
|
||||
*/
|
||||
abstract class FileMediaFormatterBase extends FileFormatterBase implements FileMediaFormatterInterface {
|
||||
|
||||
/**
|
||||
* Gets the HTML tag for the formatter.
|
||||
*
|
||||
* @return string
|
||||
* The HTML tag of this formatter.
|
||||
*/
|
||||
protected function getHtmlTag() {
|
||||
return static::getMediaType();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function defaultSettings() {
|
||||
return [
|
||||
'controls' => TRUE,
|
||||
'autoplay' => FALSE,
|
||||
'loop' => FALSE,
|
||||
'multiple_file_display_type' => 'tags',
|
||||
] + parent::defaultSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsForm(array $form, FormStateInterface $form_state) {
|
||||
return [
|
||||
'controls' => [
|
||||
'#title' => $this->t('Show playback controls'),
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => $this->getSetting('controls'),
|
||||
],
|
||||
'autoplay' => [
|
||||
'#title' => $this->t('Autoplay'),
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => $this->getSetting('autoplay'),
|
||||
],
|
||||
'loop' => [
|
||||
'#title' => $this->t('Loop'),
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => $this->getSetting('loop'),
|
||||
],
|
||||
'multiple_file_display_type' => [
|
||||
'#title' => $this->t('Display of multiple files'),
|
||||
'#type' => 'radios',
|
||||
'#options' => [
|
||||
'tags' => $this->t('Use multiple @tag tags, each with a single source.', ['@tag' => '<' . $this->getHtmlTag() . '>']),
|
||||
'sources' => $this->t('Use multiple sources within a single @tag tag.', ['@tag' => '<' . $this->getHtmlTag() . '>']),
|
||||
],
|
||||
'#default_value' => $this->getSetting('multiple_file_display_type'),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function isApplicable(FieldDefinitionInterface $field_definition) {
|
||||
if (!parent::isApplicable($field_definition)) {
|
||||
return FALSE;
|
||||
}
|
||||
/** @var \Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface $extension_mime_type_guesser */
|
||||
$extension_mime_type_guesser = \Drupal::service('file.mime_type.guesser.extension');
|
||||
$extension_list = array_filter(preg_split('/\s+/', $field_definition->getSetting('file_extensions')));
|
||||
|
||||
foreach ($extension_list as $extension) {
|
||||
$mime_type = $extension_mime_type_guesser->guess('fakedFile.' . $extension);
|
||||
|
||||
if (static::mimeTypeApplies($mime_type)) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsSummary() {
|
||||
$summary = [];
|
||||
$summary[] = $this->t('Playback controls: %controls', ['%controls' => $this->getSetting('controls') ? $this->t('visible') : $this->t('hidden')]);
|
||||
$summary[] = $this->t('Autoplay: %autoplay', ['%autoplay' => $this->getSetting('autoplay') ? $this->t('yes') : $this->t('no')]);
|
||||
$summary[] = $this->t('Loop: %loop', ['%loop' => $this->getSetting('loop') ? $this->t('yes') : $this->t('no')]);
|
||||
switch ($this->getSetting('multiple_file_display_type')) {
|
||||
case 'tags':
|
||||
$summary[] = $this->t('Multiple file display: Multiple HTML tags');
|
||||
break;
|
||||
|
||||
case 'sources':
|
||||
$summary[] = $this->t('Multiple file display: One HTML tag with multiple sources');
|
||||
break;
|
||||
}
|
||||
return $summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function viewElements(FieldItemListInterface $items, $langcode) {
|
||||
$elements = [];
|
||||
|
||||
$source_files = $this->getSourceFiles($items, $langcode);
|
||||
if (empty($source_files)) {
|
||||
return $elements;
|
||||
}
|
||||
|
||||
$attributes = $this->prepareAttributes();
|
||||
foreach ($source_files as $delta => $files) {
|
||||
$elements[$delta] = [
|
||||
'#theme' => $this->getPluginId(),
|
||||
'#attributes' => $attributes,
|
||||
'#files' => $files,
|
||||
'#cache' => ['tags' => []],
|
||||
];
|
||||
|
||||
$cache_tags = [];
|
||||
foreach ($files as $file) {
|
||||
$cache_tags = Cache::mergeTags($cache_tags, $file['file']->getCacheTags());
|
||||
}
|
||||
$elements[$delta]['#cache']['tags'] = $cache_tags;
|
||||
}
|
||||
|
||||
return $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the attributes according to the settings.
|
||||
*
|
||||
* @param string[] $additional_attributes
|
||||
* Additional attributes to be applied to the HTML element. Attribute names
|
||||
* will be used as key and value in the HTML element.
|
||||
*
|
||||
* @return \Drupal\Core\Template\Attribute
|
||||
* Container with all the attributes for the HTML tag.
|
||||
*/
|
||||
protected function prepareAttributes(array $additional_attributes = []) {
|
||||
$attributes = new Attribute();
|
||||
foreach (['controls', 'autoplay', 'loop'] + $additional_attributes as $attribute) {
|
||||
if ($this->getSetting($attribute)) {
|
||||
$attributes->setAttribute($attribute, $attribute);
|
||||
}
|
||||
}
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if given MIME type applies to the media type of the formatter.
|
||||
*
|
||||
* @param string $mime_type
|
||||
* The complete MIME type.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the MIME type applies, FALSE otherwise.
|
||||
*/
|
||||
protected static function mimeTypeApplies($mime_type) {
|
||||
list($type) = explode('/', $mime_type, 2);
|
||||
return $type === static::getMediaType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets source files with attributes.
|
||||
*
|
||||
* @param \Drupal\Core\Field\EntityReferenceFieldItemListInterface $items
|
||||
* The item list.
|
||||
* @param string $langcode
|
||||
* The language code of the referenced entities to display.
|
||||
*
|
||||
* @return array
|
||||
* Numerically indexed array, which again contains an associative array with
|
||||
* the following key/values:
|
||||
* - file => \Drupal\file\Entity\File
|
||||
* - source_attributes => \Drupal\Core\Template\Attribute
|
||||
*/
|
||||
protected function getSourceFiles(EntityReferenceFieldItemListInterface $items, $langcode) {
|
||||
$source_files = [];
|
||||
// Because we can have the files grouped in a single media tag, we do a
|
||||
// grouping in case the multiple file behavior is not 'tags'.
|
||||
/** @var \Drupal\file\Entity\File $file */
|
||||
foreach ($this->getEntitiesToView($items, $langcode) as $file) {
|
||||
if (static::mimeTypeApplies($file->getMimeType())) {
|
||||
$source_attributes = new Attribute();
|
||||
$source_attributes
|
||||
->setAttribute('src', file_url_transform_relative(file_create_url($file->getFileUri())))
|
||||
->setAttribute('type', $file->getMimeType());
|
||||
if ($this->getSetting('multiple_file_display_type') === 'tags') {
|
||||
$source_files[] = [
|
||||
[
|
||||
'file' => $file,
|
||||
'source_attributes' => $source_attributes,
|
||||
],
|
||||
];
|
||||
}
|
||||
else {
|
||||
$source_files[0][] = [
|
||||
'file' => $file,
|
||||
'source_attributes' => $source_attributes,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $source_files;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\file\Plugin\Field\FieldFormatter;
|
||||
|
||||
/**
|
||||
* Defines getter methods for FileMediaFormatterBase.
|
||||
*
|
||||
* This interface is used on the FileMediaFormatterBase class to ensure that
|
||||
* each file media formatter will be based on a media type.
|
||||
*
|
||||
* Abstract classes are not able to implement abstract static methods,
|
||||
* this interface will work around that.
|
||||
*
|
||||
* @see \Drupal\file\Plugin\Field\FieldFormatter\FileMediaFormatterBase
|
||||
*/
|
||||
interface FileMediaFormatterInterface {
|
||||
|
||||
/**
|
||||
* Gets the applicable media type for a formatter.
|
||||
*
|
||||
* @return string
|
||||
* The media type of this formatter.
|
||||
*/
|
||||
public static function getMediaType();
|
||||
|
||||
}
|
|
@ -13,7 +13,8 @@ use Drupal\Core\Form\FormStateInterface;
|
|||
* id = "file_uri",
|
||||
* label = @Translation("File URI"),
|
||||
* field_types = {
|
||||
* "uri"
|
||||
* "uri",
|
||||
* "file_uri",
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\file\Plugin\Field\FieldFormatter;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'file_video' formatter.
|
||||
*
|
||||
* @FieldFormatter(
|
||||
* id = "file_video",
|
||||
* label = @Translation("Video"),
|
||||
* description = @Translation("Display the file using an HTML5 video tag."),
|
||||
* field_types = {
|
||||
* "file"
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class FileVideoFormatter extends FileMediaFormatterBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getMediaType() {
|
||||
return 'video';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function defaultSettings() {
|
||||
return [
|
||||
'muted' => FALSE,
|
||||
'width' => 640,
|
||||
'height' => 480,
|
||||
] + parent::defaultSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsForm(array $form, FormStateInterface $form_state) {
|
||||
return parent::settingsForm($form, $form_state) + [
|
||||
'muted' => [
|
||||
'#title' => $this->t('Muted'),
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => $this->getSetting('muted'),
|
||||
],
|
||||
'width' => [
|
||||
'#type' => 'number',
|
||||
'#title' => $this->t('Width'),
|
||||
'#default_value' => $this->getSetting('width'),
|
||||
'#size' => 5,
|
||||
'#maxlength' => 5,
|
||||
'#field_suffix' => $this->t('pixels'),
|
||||
'#min' => 0,
|
||||
'#required' => TRUE,
|
||||
],
|
||||
'height' => [
|
||||
'#type' => 'number',
|
||||
'#title' => $this->t('Height'),
|
||||
'#default_value' => $this->getSetting('height'),
|
||||
'#size' => 5,
|
||||
'#maxlength' => 5,
|
||||
'#field_suffix' => $this->t('pixels'),
|
||||
'#min' => 0,
|
||||
'#required' => TRUE,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsSummary() {
|
||||
$summary = parent::settingsSummary();
|
||||
$summary[] = $this->t('Muted: %muted', ['%muted' => $this->getSetting('muted') ? $this->t('yes') : $this->t('no')]);
|
||||
$summary[] = $this->t('Size: %width x %height pixels', [
|
||||
'%width' => $this->getSetting('width'),
|
||||
'%height' => $this->getSetting('height'),
|
||||
]);
|
||||
return $summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function prepareAttributes(array $additional_attributes = []) {
|
||||
return parent::prepareAttributes(['muted'])
|
||||
->setAttribute('width', $this->getSetting('width'))
|
||||
->setAttribute('height', $this->getSetting('height'));
|
||||
}
|
||||
|
||||
}
|
|
@ -15,7 +15,7 @@ use Drupal\Core\Field\FieldItemListInterface;
|
|||
* }
|
||||
* )
|
||||
*/
|
||||
class GenericFileFormatter extends FileFormatterBase {
|
||||
class GenericFileFormatter extends DescriptionAwareFileFormatterBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
@ -28,7 +28,7 @@ class GenericFileFormatter extends FileFormatterBase {
|
|||
$elements[$delta] = [
|
||||
'#theme' => 'file_link',
|
||||
'#file' => $file,
|
||||
'#description' => $item->description,
|
||||
'#description' => $this->getSetting('use_description_as_link_text') ? $item->description : NULL,
|
||||
'#cache' => [
|
||||
'tags' => $file->getCacheTags(),
|
||||
],
|
||||
|
|
|
@ -15,7 +15,7 @@ use Drupal\Core\Field\FieldItemListInterface;
|
|||
* }
|
||||
* )
|
||||
*/
|
||||
class TableFormatter extends FileFormatterBase {
|
||||
class TableFormatter extends DescriptionAwareFileFormatterBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
@ -27,11 +27,13 @@ class TableFormatter extends FileFormatterBase {
|
|||
$header = [t('Attachment'), t('Size')];
|
||||
$rows = [];
|
||||
foreach ($files as $delta => $file) {
|
||||
$item = $file->_referringItem;
|
||||
$rows[] = [
|
||||
[
|
||||
'data' => [
|
||||
'#theme' => 'file_link',
|
||||
'#file' => $file,
|
||||
'#description' => $this->getSetting('use_description_as_link_text') ? $item->description : NULL,
|
||||
'#cache' => [
|
||||
'tags' => $file->getCacheTags(),
|
||||
],
|
||||
|
|
|
@ -13,7 +13,7 @@ class FileFieldItemList extends EntityReferenceFieldItemList {
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultValuesForm(array &$form, FormStateInterface $form_state) { }
|
||||
public function defaultValuesForm(array &$form, FormStateInterface $form_state) {}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
|
|
@ -333,7 +333,7 @@ class FileItem extends EntityReferenceItem {
|
|||
$file = file_save_data($data, $destination, FILE_EXISTS_ERROR);
|
||||
$values = [
|
||||
'target_id' => $file->id(),
|
||||
'display' => (int)$settings['display_default'],
|
||||
'display' => (int) $settings['display_default'],
|
||||
'description' => $random->sentences(10),
|
||||
];
|
||||
return $values;
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\file\Plugin\Field\FieldType;
|
||||
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\Core\Field\Plugin\Field\FieldType\UriItem;
|
||||
use Drupal\Core\TypedData\DataDefinition;
|
||||
use Drupal\file\ComputedFileUrl;
|
||||
|
||||
/**
|
||||
* File-specific plugin implementation of a URI item to provide a full URL.
|
||||
*
|
||||
* @FieldType(
|
||||
* id = "file_uri",
|
||||
* label = @Translation("File URI"),
|
||||
* description = @Translation("An entity field containing a file URI, and a computed root-relative file URL."),
|
||||
* no_ui = TRUE,
|
||||
* default_formatter = "file_uri",
|
||||
* default_widget = "uri",
|
||||
* )
|
||||
*/
|
||||
class FileUriItem extends UriItem {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
|
||||
$properties = parent::propertyDefinitions($field_definition);
|
||||
|
||||
$properties['url'] = DataDefinition::create('string')
|
||||
->setLabel(t('Root-relative file URL'))
|
||||
->setComputed(TRUE)
|
||||
->setInternal(FALSE)
|
||||
->setClass(ComputedFileUrl::class);
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
}
|
|
@ -365,7 +365,7 @@ class FileWidget extends WidgetBase implements ContainerFactoryPluginInterface {
|
|||
'%list' => implode(', ', $removed_names),
|
||||
];
|
||||
$message = t('Field %field can only hold @max values but there were @count uploaded. The following files have been omitted as a result: %list.', $args);
|
||||
drupal_set_message($message, 'warning');
|
||||
\Drupal::messenger()->addWarning($message);
|
||||
$values['fids'] = array_slice($values['fids'], 0, $keep);
|
||||
NestedArray::setValue($form_state->getValues(), $element['#parents'], $values);
|
||||
}
|
||||
|
|
|
@ -15,7 +15,12 @@ class FileValidationConstraintValidator extends ConstraintValidator {
|
|||
*/
|
||||
public function validate($value, Constraint $constraint) {
|
||||
// Get the file to execute validators.
|
||||
$file = $value->get('entity')->getTarget()->getValue();
|
||||
$target = $value->get('entity')->getTarget();
|
||||
if (!$target) {
|
||||
return;
|
||||
}
|
||||
|
||||
$file = $target->getValue();
|
||||
// Get the validators.
|
||||
$validators = $value->getUploadValidators();
|
||||
// Checks that a file meets the criteria specified by the validators.
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Drupal\file\Plugin\migrate\cckfield\d6;
|
||||
|
||||
@trigger_error('FileField is deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.x. Use \Drupal\file\Plugin\migrate\field\d6\FileField instead.', E_USER_DEPRECATED);
|
||||
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate_drupal\Plugin\migrate\cckfield\CckFieldPluginBase;
|
||||
|
@ -9,8 +11,15 @@ use Drupal\migrate_drupal\Plugin\migrate\cckfield\CckFieldPluginBase;
|
|||
/**
|
||||
* @MigrateCckField(
|
||||
* id = "filefield",
|
||||
* core = {6}
|
||||
* core = {6},
|
||||
* source_module = "filefield",
|
||||
* destination_module = "file"
|
||||
* )
|
||||
*
|
||||
* @deprecated in Drupal 8.3.x, to be removed before Drupal 9.0.x. Use
|
||||
* \Drupal\file\Plugin\migrate\field\d6\FileField instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2751897
|
||||
*/
|
||||
class FileField extends CckFieldPluginBase {
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Drupal\file\Plugin\migrate\cckfield\d7;
|
||||
|
||||
@trigger_error('FileField is deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.x. Use \Drupal\file\Plugin\migrate\field\d7\FileField instead.', E_USER_DEPRECATED);
|
||||
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate_drupal\Plugin\migrate\cckfield\CckFieldPluginBase;
|
||||
|
@ -9,8 +11,15 @@ use Drupal\migrate_drupal\Plugin\migrate\cckfield\CckFieldPluginBase;
|
|||
/**
|
||||
* @MigrateCckField(
|
||||
* id = "file",
|
||||
* core = {7}
|
||||
* core = {7},
|
||||
* source_module = "file",
|
||||
* destination_module = "file"
|
||||
* )
|
||||
*
|
||||
* @deprecated in Drupal 8.3.x, to be removed before Drupal 9.0.x. Use
|
||||
* \Drupal\file\Plugin\migrate\field\d7\FileField instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2751897
|
||||
*/
|
||||
class FileField extends CckFieldPluginBase {
|
||||
|
||||
|
@ -42,7 +51,7 @@ class FileField extends CckFieldPluginBase {
|
|||
*/
|
||||
public function processCckFieldValues(MigrationInterface $migration, $field_name, $data) {
|
||||
$process = [
|
||||
'plugin' => 'iterator',
|
||||
'plugin' => 'sub_process',
|
||||
'source' => $field_name,
|
||||
'process' => [
|
||||
'target_id' => 'fid',
|
||||
|
|
|
@ -2,40 +2,16 @@
|
|||
|
||||
namespace Drupal\file\Plugin\migrate\cckfield\d7;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate_drupal\Plugin\migrate\cckfield\CckFieldPluginBase;
|
||||
@trigger_error('ImageField is deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.x. Use \Drupal\image\Plugin\migrate\field\d7\ImageField instead. See https://www.drupal.org/node/2936061.', E_USER_DEPRECATED);
|
||||
|
||||
use Drupal\image\Plugin\migrate\cckfield\d7\ImageField as LegacyImageField;
|
||||
|
||||
/**
|
||||
* @MigrateCckField(
|
||||
* id = "image",
|
||||
* core = {7}
|
||||
* )
|
||||
* CCK plugin for image fields.
|
||||
*
|
||||
* @deprecated in Drupal 8.3.x, to be removed before Drupal 9.0.x. Use
|
||||
* \Drupal\image\Plugin\migrate\field\d7\ImageField instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2936061
|
||||
*/
|
||||
class ImageField extends CckFieldPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFieldFormatterMap() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processCckFieldValues(MigrationInterface $migration, $field_name, $data) {
|
||||
$process = [
|
||||
'plugin' => 'iterator',
|
||||
'source' => $field_name,
|
||||
'process' => [
|
||||
'target_id' => 'fid',
|
||||
'alt' => 'alt',
|
||||
'title' => 'title',
|
||||
'width' => 'width',
|
||||
'height' => 'height',
|
||||
],
|
||||
];
|
||||
$migration->mergeProcessOfProperty($field_name, $process);
|
||||
}
|
||||
|
||||
}
|
||||
class ImageField extends LegacyImageField {}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Drupal\file\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Field\Plugin\Field\FieldType\UriItem;
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate\MigrateException;
|
||||
|
@ -58,7 +57,7 @@ class EntityFile extends EntityContentBase {
|
|||
// Make it into a proper public file uri, stripping off the existing
|
||||
// scheme if present.
|
||||
$value = 'public://' . preg_replace('|^[a-z]+://|i', '', $value);
|
||||
$value = Unicode::substr($value, 0, $field_definitions['uri']->getSetting('max_length'));
|
||||
$value = mb_substr($value, 0, $field_definitions['uri']->getSetting('max_length'));
|
||||
// Create a real file, so File::preSave() can do filesize() on it.
|
||||
touch($value);
|
||||
$row->setDestinationProperty('uri', $value);
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\file\Plugin\migrate\field\d6;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate_drupal\Plugin\migrate\field\FieldPluginBase;
|
||||
|
||||
/**
|
||||
* @MigrateField(
|
||||
* id = "filefield",
|
||||
* core = {6},
|
||||
* source_module = "filefield",
|
||||
* destination_module = "file"
|
||||
* )
|
||||
*/
|
||||
class FileField extends FieldPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFieldWidgetMap() {
|
||||
return [
|
||||
'filefield_widget' => 'file_generic',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFieldFormatterMap() {
|
||||
return [
|
||||
'default' => 'file_default',
|
||||
'url_plain' => 'file_url_plain',
|
||||
'path_plain' => 'file_url_plain',
|
||||
'image_plain' => 'image',
|
||||
'image_nodelink' => 'image',
|
||||
'image_imagelink' => 'image',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defineValueProcessPipeline(MigrationInterface $migration, $field_name, $data) {
|
||||
$process = [
|
||||
'plugin' => 'd6_field_file',
|
||||
'source' => $field_name,
|
||||
];
|
||||
$migration->mergeProcessOfProperty($field_name, $process);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFieldType(Row $row) {
|
||||
return $row->getSourceProperty('widget_type') == 'imagefield_widget' ? 'image' : 'file';
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\file\Plugin\migrate\field\d6;
|
||||
|
||||
@trigger_error('ImageField is deprecated in Drupal 8.5.x and will be removed before Drupal 9.0.x. Use \Drupal\image\Plugin\migrate\field\d6\ImageField instead. See https://www.drupal.org/node/2936061.', E_USER_DEPRECATED);
|
||||
|
||||
use Drupal\image\Plugin\migrate\field\d6\ImageField as NonLegacyImageField;
|
||||
|
||||
/**
|
||||
* Field plugin for image fields.
|
||||
*
|
||||
* @deprecated in Drupal 8.5.x, to be removed before Drupal 9.0.x. Use
|
||||
* \Drupal\image\Plugin\migrate\field\d6\ImageField instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2936061
|
||||
*/
|
||||
class ImageField extends NonLegacyImageField {}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\file\Plugin\migrate\field\d7;
|
||||
|
||||
use Drupal\file\Plugin\migrate\field\d6\FileField as D6FileField;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
|
||||
/**
|
||||
* @MigrateField(
|
||||
* id = "file",
|
||||
* core = {7},
|
||||
* source_module = "file",
|
||||
* destination_module = "file"
|
||||
* )
|
||||
*/
|
||||
class FileField extends D6FileField {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defineValueProcessPipeline(MigrationInterface $migration, $field_name, $data) {
|
||||
$process = [
|
||||
'plugin' => 'sub_process',
|
||||
'source' => $field_name,
|
||||
'process' => [
|
||||
'target_id' => 'fid',
|
||||
'display' => 'display',
|
||||
'description' => 'description',
|
||||
],
|
||||
];
|
||||
$migration->mergeProcessOfProperty($field_name, $process);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\file\Plugin\migrate\field\d7;
|
||||
|
||||
@trigger_error('ImageField is deprecated in Drupal 8.5.x and will be removed before Drupal 9.0.x. Use \Drupal\image\Plugin\migrate\field\d7\ImageField instead. See https://www.drupal.org/node/2936061.', E_USER_DEPRECATED);
|
||||
|
||||
use Drupal\image\Plugin\migrate\field\d7\ImageField as NonLegacyImageField;
|
||||
|
||||
/**
|
||||
* Field plugin for image fields.
|
||||
*
|
||||
* @deprecated in Drupal 8.5.x, to be removed before Drupal 9.0.x. Use
|
||||
* \Drupal\image\Plugin\migrate\field\d7\ImageField instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2936061
|
||||
*/
|
||||
class ImageField extends NonLegacyImageField {}
|
|
@ -2,91 +2,16 @@
|
|||
|
||||
namespace Drupal\file\Plugin\migrate\process\d6;
|
||||
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Plugin\MigrateProcessInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
@trigger_error('CckFile is deprecated in Drupal 8.3.x and will be be removed before Drupal 9.0.x. Use \Drupal\file\Plugin\migrate\process\d6\FieldFile instead.', E_USER_DEPRECATED);
|
||||
|
||||
/**
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "d6_cck_file"
|
||||
* )
|
||||
*
|
||||
* @deprecated in Drupal 8.3.x, to be removed before Drupal 9.0.x. Use
|
||||
* \Drupal\file\Plugin\migrate\process\d6\FieldFile instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2751897
|
||||
*/
|
||||
class CckFile extends ProcessPluginBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The migration process plugin, configured for lookups in d6_file.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*/
|
||||
protected $migrationPlugin;
|
||||
|
||||
/**
|
||||
* Constructs a CckFile plugin instance.
|
||||
*
|
||||
* @param array $configuration
|
||||
* The plugin configuration.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin definition.
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The current migration.
|
||||
* @param \Drupal\migrate\Plugin\MigrateProcessInterface $migration_plugin
|
||||
* An instance of the 'migration' process plugin.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, MigrateProcessInterface $migration_plugin) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->migration = $migration;
|
||||
$this->migrationPlugin = $migration_plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
|
||||
// Configure the migration process plugin to look up migrated IDs from
|
||||
// a d6 file migration.
|
||||
$migration_plugin_configuration = $configuration + [
|
||||
'migration' => 'd6_file',
|
||||
'source' => ['fid'],
|
||||
];
|
||||
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$migration,
|
||||
$container->get('plugin.manager.migrate.process')->createInstance('migration', $migration_plugin_configuration, $migration)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
$options = unserialize($value['data']);
|
||||
|
||||
// Try to look up the ID of the migrated file. If one cannot be found, it
|
||||
// means the file referenced by the current field item did not migrate for
|
||||
// some reason -- file migration is notoriously brittle -- and we do NOT
|
||||
// want to send invalid file references into the field system (it causes
|
||||
// fatals), so return an empty item instead.
|
||||
if ($fid = $this->migrationPlugin->transform($value['fid'], $migrate_executable, $row, $destination_property)) {
|
||||
return [
|
||||
'target_id' => $fid,
|
||||
'display' => $value['list'],
|
||||
'description' => isset($options['description']) ? $options['description'] : '',
|
||||
'alt' => isset($options['alt']) ? $options['alt'] : '',
|
||||
'title' => isset($options['title']) ? $options['title'] : '',
|
||||
];
|
||||
}
|
||||
else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
class CckFile extends FieldFile {}
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\file\Plugin\migrate\process\d6;
|
||||
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Plugin\MigrateProcessInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "d6_field_file"
|
||||
* )
|
||||
*/
|
||||
class FieldFile extends ProcessPluginBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The migration process plugin, configured for lookups in d6_file.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*/
|
||||
protected $migrationPlugin;
|
||||
|
||||
/**
|
||||
* Constructs a FieldFile plugin instance.
|
||||
*
|
||||
* @param array $configuration
|
||||
* The plugin configuration.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin definition.
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The current migration.
|
||||
* @param \Drupal\migrate\Plugin\MigrateProcessInterface $migration_plugin
|
||||
* An instance of the 'migration' process plugin.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, MigrateProcessInterface $migration_plugin) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->migration = $migration;
|
||||
$this->migrationPlugin = $migration_plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
|
||||
// Configure the migration process plugin to look up migrated IDs from
|
||||
// a d6 file migration.
|
||||
$migration_plugin_configuration = $configuration + [
|
||||
'migration' => 'd6_file',
|
||||
'source' => ['fid'],
|
||||
];
|
||||
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$migration,
|
||||
$container->get('plugin.manager.migrate.process')->createInstance('migration', $migration_plugin_configuration, $migration)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
$options = unserialize($value['data']);
|
||||
|
||||
// Try to look up the ID of the migrated file. If one cannot be found, it
|
||||
// means the file referenced by the current field item did not migrate for
|
||||
// some reason -- file migration is notoriously brittle -- and we do NOT
|
||||
// want to send invalid file references into the field system (it causes
|
||||
// fatals), so return an empty item instead.
|
||||
if ($fid = $this->migrationPlugin->transform($value['fid'], $migrate_executable, $row, $destination_property)) {
|
||||
return [
|
||||
'target_id' => $fid,
|
||||
'display' => $value['list'],
|
||||
'description' => isset($options['description']) ? $options['description'] : '',
|
||||
'alt' => isset($options['alt']) ? $options['alt'] : '',
|
||||
'title' => isset($options['title']) ? $options['title'] : '',
|
||||
];
|
||||
}
|
||||
else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -9,7 +9,8 @@ use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
|||
* Drupal 6 file source from database.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d6_file"
|
||||
* id = "d6_file",
|
||||
* source_module = "system"
|
||||
* )
|
||||
*/
|
||||
class File extends DrupalSqlBase {
|
||||
|
@ -41,6 +42,7 @@ class File extends DrupalSqlBase {
|
|||
public function query() {
|
||||
return $this->select('files', 'f')
|
||||
->fields('f')
|
||||
->condition('filepath', '/tmp%', 'NOT LIKE')
|
||||
->orderBy('timestamp')
|
||||
// If two or more files have the same timestamp, they'll end up in a
|
||||
// non-deterministic order. Ordering by fid (or any other unique field)
|
||||
|
@ -87,6 +89,7 @@ class File extends DrupalSqlBase {
|
|||
'is_public' => $this->t('TRUE if the files directory is public otherwise FALSE.'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
|
|
@ -10,7 +10,7 @@ use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
|||
*
|
||||
* @MigrateSource(
|
||||
* id = "d6_upload",
|
||||
* source_provider = "upload"
|
||||
* source_module = "upload"
|
||||
* )
|
||||
*/
|
||||
class Upload extends DrupalSqlBase {
|
||||
|
@ -29,6 +29,7 @@ class Upload extends DrupalSqlBase {
|
|||
->fields('u', ['nid', 'vid']);
|
||||
$query->innerJoin('node', 'n', static::JOIN);
|
||||
$query->addField('n', 'type');
|
||||
$query->addField('n', 'language');
|
||||
return $query;
|
||||
}
|
||||
|
||||
|
@ -54,6 +55,7 @@ class Upload extends DrupalSqlBase {
|
|||
'nid' => $this->t('The node Id.'),
|
||||
'vid' => $this->t('The version Id.'),
|
||||
'type' => $this->t('The node type'),
|
||||
'language' => $this->t('The node language.'),
|
||||
'description' => $this->t('The file description.'),
|
||||
'list' => $this->t('Whether the list should be visible on the node page.'),
|
||||
'weight' => $this->t('The file weight.'),
|
||||
|
|
|
@ -10,7 +10,7 @@ use Drupal\migrate\Plugin\migrate\source\DummyQueryTrait;
|
|||
*
|
||||
* @MigrateSource(
|
||||
* id = "d6_upload_instance",
|
||||
* source_provider = "upload"
|
||||
* source_module = "upload"
|
||||
* )
|
||||
*/
|
||||
class UploadInstance extends DrupalSqlBase {
|
||||
|
@ -25,7 +25,9 @@ class UploadInstance extends DrupalSqlBase {
|
|||
->fields('nt', ['type'])
|
||||
->execute()
|
||||
->fetchCol();
|
||||
$variables = array_map(function($type) { return 'upload_' . $type; }, $node_types);
|
||||
$variables = array_map(function ($type) {
|
||||
return 'upload_' . $type;
|
||||
}, $node_types);
|
||||
|
||||
$max_filesize = $this->variableGet('upload_uploadsize_default', 1);
|
||||
$max_filesize = $max_filesize ? $max_filesize . 'MB' : '';
|
||||
|
@ -76,7 +78,7 @@ class UploadInstance extends DrupalSqlBase {
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function count() {
|
||||
public function count($refresh = FALSE) {
|
||||
return count($this->initializeIterator());
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,8 @@ use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
|||
* Drupal 7 file source from database.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d7_file"
|
||||
* id = "d7_file",
|
||||
* source_module = "file"
|
||||
* )
|
||||
*/
|
||||
class File extends DrupalSqlBase {
|
||||
|
@ -42,18 +43,21 @@ class File extends DrupalSqlBase {
|
|||
public function query() {
|
||||
$query = $this->select('file_managed', 'f')
|
||||
->fields('f')
|
||||
->condition('uri', 'temporary://%', 'NOT LIKE')
|
||||
->orderBy('f.timestamp');
|
||||
|
||||
// Filter by scheme(s), if configured.
|
||||
if (isset($this->configuration['scheme'])) {
|
||||
$schemes = [];
|
||||
// Remove 'temporary' scheme.
|
||||
$valid_schemes = array_diff((array) $this->configuration['scheme'], ['temporary']);
|
||||
// Accept either a single scheme, or a list.
|
||||
foreach ((array) $this->configuration['scheme'] as $scheme) {
|
||||
foreach ((array) $valid_schemes as $scheme) {
|
||||
$schemes[] = rtrim($scheme) . '://';
|
||||
}
|
||||
$schemes = array_map([$this->getDatabase(), 'escapeLike'], $schemes);
|
||||
|
||||
// uri LIKE 'public://%' OR uri LIKE 'private://%'
|
||||
// Add conditions, uri LIKE 'public://%' OR uri LIKE 'private://%'.
|
||||
$conditions = new Condition('OR');
|
||||
foreach ($schemes as $scheme) {
|
||||
$conditions->condition('uri', $scheme . '%', 'LIKE');
|
||||
|
|
|
@ -0,0 +1,586 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\file\Plugin\rest\resource;
|
||||
|
||||
use Drupal\Component\Utility\Bytes;
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use Drupal\Core\Config\Config;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\File\FileSystemInterface;
|
||||
use Drupal\Core\Lock\LockBackendInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Utility\Token;
|
||||
use Drupal\file\FileInterface;
|
||||
use Drupal\rest\ModifiedResourceResponse;
|
||||
use Drupal\rest\Plugin\ResourceBase;
|
||||
use Drupal\Component\Render\PlainTextOutput;
|
||||
use Drupal\Core\Entity\EntityFieldManagerInterface;
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\rest\Plugin\rest\resource\EntityResourceValidationTrait;
|
||||
use Drupal\rest\RequestHandler;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
|
||||
/**
|
||||
* File upload resource.
|
||||
*
|
||||
* This is implemented as a field-level resource for the following reasons:
|
||||
* - Validation for uploaded files is tied to fields (allowed extensions, max
|
||||
* size, etc..).
|
||||
* - The actual files do not need to be stored in another temporary location,
|
||||
* to be later moved when they are referenced from a file field.
|
||||
* - Permission to upload a file can be determined by a users field level
|
||||
* create access to the file field.
|
||||
*
|
||||
* @RestResource(
|
||||
* id = "file:upload",
|
||||
* label = @Translation("File Upload"),
|
||||
* serialization_class = "Drupal\file\Entity\File",
|
||||
* uri_paths = {
|
||||
* "https://www.drupal.org/link-relations/create" = "/file/upload/{entity_type_id}/{bundle}/{field_name}"
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class FileUploadResource extends ResourceBase {
|
||||
|
||||
use EntityResourceValidationTrait {
|
||||
validate as resourceValidate;
|
||||
}
|
||||
|
||||
/**
|
||||
* The regex used to extract the filename from the content disposition header.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const REQUEST_HEADER_FILENAME_REGEX = '@\bfilename(?<star>\*?)=\"(?<filename>.+)\"@';
|
||||
|
||||
/**
|
||||
* The amount of bytes to read in each iteration when streaming file data.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const BYTES_TO_READ = 8192;
|
||||
|
||||
/**
|
||||
* The file system service.
|
||||
*
|
||||
* @var \Drupal\Core\File\FileSystemInterface
|
||||
*/
|
||||
protected $fileSystem;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The entity field manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
|
||||
*/
|
||||
protected $entityFieldManager;
|
||||
|
||||
/**
|
||||
* The currently authenticated user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* The MIME type guesser.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface
|
||||
*/
|
||||
protected $mimeTypeGuesser;
|
||||
|
||||
/**
|
||||
* The token replacement instance.
|
||||
*
|
||||
* @var \Drupal\Core\Utility\Token
|
||||
*/
|
||||
protected $token;
|
||||
|
||||
/**
|
||||
* The lock service.
|
||||
*
|
||||
* @var \Drupal\Core\Lock\LockBackendInterface
|
||||
*/
|
||||
protected $lock;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Config\ImmutableConfig
|
||||
*/
|
||||
protected $systemFileConfig;
|
||||
|
||||
/**
|
||||
* Constructs a FileUploadResource instance.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param array $serializer_formats
|
||||
* The available serialization formats.
|
||||
* @param \Psr\Log\LoggerInterface $logger
|
||||
* A logger instance.
|
||||
* @param \Drupal\Core\File\FileSystemInterface $file_system
|
||||
* The file system service.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
|
||||
* The entity field manager.
|
||||
* @param \Drupal\Core\Session\AccountInterface $current_user
|
||||
* The currently authenticated user.
|
||||
* @param \Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface $mime_type_guesser
|
||||
* The MIME type guesser.
|
||||
* @param \Drupal\Core\Utility\Token $token
|
||||
* The token replacement instance.
|
||||
* @param \Drupal\Core\Lock\LockBackendInterface $lock
|
||||
* The lock service.
|
||||
* @param \Drupal\Core\Config\Config $system_file_config
|
||||
* The system file configuration.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, $serializer_formats, LoggerInterface $logger, FileSystemInterface $file_system, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, AccountInterface $current_user, MimeTypeGuesserInterface $mime_type_guesser, Token $token, LockBackendInterface $lock, Config $system_file_config) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger);
|
||||
$this->fileSystem = $file_system;
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->entityFieldManager = $entity_field_manager;
|
||||
$this->currentUser = $current_user;
|
||||
$this->mimeTypeGuesser = $mime_type_guesser;
|
||||
$this->token = $token;
|
||||
$this->lock = $lock;
|
||||
$this->systemFileConfig = $system_file_config;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->getParameter('serializer.formats'),
|
||||
$container->get('logger.factory')->get('rest'),
|
||||
$container->get('file_system'),
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('entity_field.manager'),
|
||||
$container->get('current_user'),
|
||||
$container->get('file.mime_type.guesser'),
|
||||
$container->get('token'),
|
||||
$container->get('lock'),
|
||||
$container->get('config.factory')->get('system.file')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function permissions() {
|
||||
// Access to this resource depends on field-level access so no explicit
|
||||
// permissions are required.
|
||||
// @see \Drupal\file\Plugin\rest\resource\FileUploadResource::validateAndLoadFieldDefinition()
|
||||
// @see \Drupal\rest\Plugin\rest\resource\EntityResource::permissions()
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a file from an endpoint.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The current request.
|
||||
* @param string $entity_type_id
|
||||
* The entity type ID.
|
||||
* @param string $bundle
|
||||
* The entity bundle. This will be the same as $entity_type_id for entity
|
||||
* types that don't support bundles.
|
||||
* @param string $field_name
|
||||
* The field name.
|
||||
*
|
||||
* @return \Drupal\rest\ModifiedResourceResponse
|
||||
* A 201 response, on success.
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
|
||||
* Thrown when temporary files cannot be written, a lock cannot be acquired,
|
||||
* or when temporary files cannot be moved to their new location.
|
||||
*/
|
||||
public function post(Request $request, $entity_type_id, $bundle, $field_name) {
|
||||
$filename = $this->validateAndParseContentDispositionHeader($request);
|
||||
|
||||
$field_definition = $this->validateAndLoadFieldDefinition($entity_type_id, $bundle, $field_name);
|
||||
|
||||
$destination = $this->getUploadLocation($field_definition->getSettings());
|
||||
|
||||
// Check the destination file path is writable.
|
||||
if (!file_prepare_directory($destination, FILE_CREATE_DIRECTORY)) {
|
||||
throw new HttpException(500, 'Destination file path is not writable');
|
||||
}
|
||||
|
||||
$validators = $this->getUploadValidators($field_definition);
|
||||
|
||||
$prepared_filename = $this->prepareFilename($filename, $validators);
|
||||
|
||||
// Create the file.
|
||||
$file_uri = "{$destination}/{$prepared_filename}";
|
||||
|
||||
$temp_file_path = $this->streamUploadData();
|
||||
|
||||
// This will take care of altering $file_uri if a file already exists.
|
||||
file_unmanaged_prepare($temp_file_path, $file_uri);
|
||||
|
||||
// Lock based on the prepared file URI.
|
||||
$lock_id = $this->generateLockIdFromFileUri($file_uri);
|
||||
|
||||
if (!$this->lock->acquire($lock_id)) {
|
||||
throw new HttpException(503, sprintf('File "%s" is already locked for writing'), NULL, ['Retry-After' => 1]);
|
||||
}
|
||||
|
||||
// Begin building file entity.
|
||||
$file = File::create([]);
|
||||
$file->setOwnerId($this->currentUser->id());
|
||||
$file->setFilename($prepared_filename);
|
||||
$file->setMimeType($this->mimeTypeGuesser->guess($prepared_filename));
|
||||
$file->setFileUri($file_uri);
|
||||
// Set the size. This is done in File::preSave() but we validate the file
|
||||
// before it is saved.
|
||||
$file->setSize(@filesize($temp_file_path));
|
||||
|
||||
// Validate the file entity against entity-level validation and field-level
|
||||
// validators.
|
||||
$this->validate($file, $validators);
|
||||
|
||||
// Move the file to the correct location after validation. Use
|
||||
// FILE_EXISTS_ERROR as the file location has already been determined above
|
||||
// in file_unmanaged_prepare().
|
||||
if (!file_unmanaged_move($temp_file_path, $file_uri, FILE_EXISTS_ERROR)) {
|
||||
throw new HttpException(500, 'Temporary file could not be moved to file location');
|
||||
}
|
||||
|
||||
$file->save();
|
||||
|
||||
$this->lock->release($lock_id);
|
||||
|
||||
// 201 Created responses return the newly created entity in the response
|
||||
// body. These responses are not cacheable, so we add no cacheability
|
||||
// metadata here.
|
||||
return new ModifiedResourceResponse($file, 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams file upload data to temporary file and moves to file destination.
|
||||
*
|
||||
* @return string
|
||||
* The temp file path.
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
|
||||
* Thrown when input data cannot be read, the temporary file cannot be
|
||||
* opened, or the temporary file cannot be written.
|
||||
*/
|
||||
protected function streamUploadData() {
|
||||
// 'rb' is needed so reading works correctly on Windows environments too.
|
||||
$file_data = fopen('php://input', 'rb');
|
||||
|
||||
$temp_file_path = $this->fileSystem->tempnam('temporary://', 'file');
|
||||
$temp_file = fopen($temp_file_path, 'wb');
|
||||
|
||||
if ($temp_file) {
|
||||
while (!feof($file_data)) {
|
||||
$read = fread($file_data, static::BYTES_TO_READ);
|
||||
|
||||
if ($read === FALSE) {
|
||||
// Close the file streams.
|
||||
fclose($temp_file);
|
||||
fclose($file_data);
|
||||
$this->logger->error('Input data could not be read');
|
||||
throw new HttpException(500, 'Input file data could not be read');
|
||||
}
|
||||
|
||||
if (fwrite($temp_file, $read) === FALSE) {
|
||||
// Close the file streams.
|
||||
fclose($temp_file);
|
||||
fclose($file_data);
|
||||
$this->logger->error('Temporary file data for "%path" could not be written', ['%path' => $temp_file_path]);
|
||||
throw new HttpException(500, 'Temporary file data could not be written');
|
||||
}
|
||||
}
|
||||
|
||||
// Close the temp file stream.
|
||||
fclose($temp_file);
|
||||
}
|
||||
else {
|
||||
// Close the file streams.
|
||||
fclose($temp_file);
|
||||
fclose($file_data);
|
||||
$this->logger->error('Temporary file "%path" could not be opened for file upload', ['%path' => $temp_file_path]);
|
||||
throw new HttpException(500, 'Temporary file could not be opened');
|
||||
}
|
||||
|
||||
// Close the input stream.
|
||||
fclose($file_data);
|
||||
|
||||
return $temp_file_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and extracts the filename from the Content-Disposition header.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The request object.
|
||||
*
|
||||
* @return string
|
||||
* The filename extracted from the header.
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
|
||||
* Thrown when the 'Content-Disposition' request header is invalid.
|
||||
*/
|
||||
protected function validateAndParseContentDispositionHeader(Request $request) {
|
||||
// Firstly, check the header exists.
|
||||
if (!$request->headers->has('content-disposition')) {
|
||||
throw new BadRequestHttpException('"Content-Disposition" header is required. A file name in the format "filename=FILENAME" must be provided');
|
||||
}
|
||||
|
||||
$content_disposition = $request->headers->get('content-disposition');
|
||||
|
||||
// Parse the header value. This regex does not allow an empty filename.
|
||||
// i.e. 'filename=""'. This also matches on a word boundary so other keys
|
||||
// like 'not_a_filename' don't work.
|
||||
if (!preg_match(static::REQUEST_HEADER_FILENAME_REGEX, $content_disposition, $matches)) {
|
||||
throw new BadRequestHttpException('No filename found in "Content-Disposition" header. A file name in the format "filename=FILENAME" must be provided');
|
||||
}
|
||||
|
||||
// Check for the "filename*" format. This is currently unsupported.
|
||||
if (!empty($matches['star'])) {
|
||||
throw new BadRequestHttpException('The extended "filename*" format is currently not supported in the "Content-Disposition" header');
|
||||
}
|
||||
|
||||
// Don't validate the actual filename here, that will be done by the upload
|
||||
// validators in validate().
|
||||
// @see \Drupal\file\Plugin\rest\resource\FileUploadResource::validate()
|
||||
$filename = $matches['filename'];
|
||||
|
||||
// Make sure only the filename component is returned. Path information is
|
||||
// stripped as per https://tools.ietf.org/html/rfc6266#section-4.3.
|
||||
return basename($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and loads a field definition instance.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type ID the field is attached to.
|
||||
* @param string $bundle
|
||||
* The bundle the field is attached to.
|
||||
* @param string $field_name
|
||||
* The field name.
|
||||
*
|
||||
* @return \Drupal\Core\Field\FieldDefinitionInterface
|
||||
* The field definition.
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
|
||||
* Thrown when the field does not exist.
|
||||
* @throws \Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException
|
||||
* Thrown when the target type of the field is not a file, or the current
|
||||
* user does not have 'edit' access for the field.
|
||||
*/
|
||||
protected function validateAndLoadFieldDefinition($entity_type_id, $bundle, $field_name) {
|
||||
$field_definitions = $this->entityFieldManager->getFieldDefinitions($entity_type_id, $bundle);
|
||||
if (!isset($field_definitions[$field_name])) {
|
||||
throw new NotFoundHttpException(sprintf('Field "%s" does not exist', $field_name));
|
||||
}
|
||||
|
||||
/** @var \Drupal\Core\Field\FieldDefinitionInterface $field_definition */
|
||||
$field_definition = $field_definitions[$field_name];
|
||||
if ($field_definition->getSetting('target_type') !== 'file') {
|
||||
throw new AccessDeniedHttpException(sprintf('"%s" is not a file field', $field_name));
|
||||
}
|
||||
|
||||
$entity_access_control_handler = $this->entityTypeManager->getAccessControlHandler($entity_type_id);
|
||||
$bundle = $this->entityTypeManager->getDefinition($entity_type_id)->hasKey('bundle') ? $bundle : NULL;
|
||||
$access_result = $entity_access_control_handler->createAccess($bundle, NULL, [], TRUE)
|
||||
->andIf($entity_access_control_handler->fieldAccess('edit', $field_definition, NULL, NULL, TRUE));
|
||||
if (!$access_result->isAllowed()) {
|
||||
throw new AccessDeniedHttpException($access_result->getReason());
|
||||
}
|
||||
|
||||
return $field_definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the file.
|
||||
*
|
||||
* @param \Drupal\file\FileInterface $file
|
||||
* The file entity to validate.
|
||||
* @param array $validators
|
||||
* An array of upload validators to pass to file_validate().
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException
|
||||
* Thrown when there are file validation errors.
|
||||
*/
|
||||
protected function validate(FileInterface $file, array $validators) {
|
||||
$this->resourceValidate($file);
|
||||
|
||||
// Validate the file based on the field definition configuration.
|
||||
$errors = file_validate($file, $validators);
|
||||
|
||||
if (!empty($errors)) {
|
||||
$message = "Unprocessable Entity: file validation failed.\n";
|
||||
$message .= implode("\n", array_map(function ($error) {
|
||||
return PlainTextOutput::renderFromHtml($error);
|
||||
}, $errors));
|
||||
|
||||
throw new UnprocessableEntityHttpException($message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the filename to strip out any malicious extensions.
|
||||
*
|
||||
* @param string $filename
|
||||
* The file name.
|
||||
* @param array $validators
|
||||
* The array of upload validators.
|
||||
*
|
||||
* @return string
|
||||
* The prepared/munged filename.
|
||||
*/
|
||||
protected function prepareFilename($filename, array &$validators) {
|
||||
if (!empty($validators['file_validate_extensions'][0])) {
|
||||
// If there is a file_validate_extensions validator and a list of
|
||||
// valid extensions, munge the filename to protect against possible
|
||||
// malicious extension hiding within an unknown file type. For example,
|
||||
// "filename.html.foo".
|
||||
$filename = file_munge_filename($filename, $validators['file_validate_extensions'][0]);
|
||||
}
|
||||
|
||||
// Rename potentially executable files, to help prevent exploits (i.e. will
|
||||
// rename filename.php.foo and filename.php to filename.php.foo.txt and
|
||||
// filename.php.txt, respectively). Don't rename if 'allow_insecure_uploads'
|
||||
// evaluates to TRUE.
|
||||
if (!$this->systemFileConfig->get('allow_insecure_uploads') && preg_match(FILE_INSECURE_EXTENSION_REGEX, $filename) && (substr($filename, -4) != '.txt')) {
|
||||
// The destination filename will also later be used to create the URI.
|
||||
$filename .= '.txt';
|
||||
|
||||
// The .txt extension may not be in the allowed list of extensions. We
|
||||
// have to add it here or else the file upload will fail.
|
||||
if (!empty($validators['file_validate_extensions'][0])) {
|
||||
$validators['file_validate_extensions'][0] .= ' txt';
|
||||
}
|
||||
}
|
||||
|
||||
return $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the URI for a file field.
|
||||
*
|
||||
* @param array $settings
|
||||
* The array of field settings.
|
||||
*
|
||||
* @return string
|
||||
* An un-sanitized file directory URI with tokens replaced. The result of
|
||||
* the token replacement is then converted to plain text and returned.
|
||||
*/
|
||||
protected function getUploadLocation(array $settings) {
|
||||
$destination = trim($settings['file_directory'], '/');
|
||||
|
||||
// Replace tokens. As the tokens might contain HTML we convert it to plain
|
||||
// text.
|
||||
$destination = PlainTextOutput::renderFromHtml($this->token->replace($destination, []));
|
||||
return $settings['uri_scheme'] . '://' . $destination;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the upload validators for a field definition.
|
||||
*
|
||||
* This is copied from \Drupal\file\Plugin\Field\FieldType\FileItem as there
|
||||
* is no entity instance available here that that a FileItem would exist for.
|
||||
*
|
||||
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
|
||||
* The field definition for which to get validators.
|
||||
*
|
||||
* @return array
|
||||
* An array suitable for passing to file_save_upload() or the file field
|
||||
* element's '#upload_validators' property.
|
||||
*/
|
||||
protected function getUploadValidators(FieldDefinitionInterface $field_definition) {
|
||||
$validators = [
|
||||
// Add in our check of the file name length.
|
||||
'file_validate_name_length' => [],
|
||||
];
|
||||
$settings = $field_definition->getSettings();
|
||||
|
||||
// Cap the upload size according to the PHP limit.
|
||||
$max_filesize = Bytes::toInt(file_upload_max_size());
|
||||
if (!empty($settings['max_filesize'])) {
|
||||
$max_filesize = min($max_filesize, Bytes::toInt($settings['max_filesize']));
|
||||
}
|
||||
|
||||
// There is always a file size limit due to the PHP server limit.
|
||||
$validators['file_validate_size'] = [$max_filesize];
|
||||
|
||||
// Add the extension check if necessary.
|
||||
if (!empty($settings['file_extensions'])) {
|
||||
$validators['file_validate_extensions'] = [$settings['file_extensions']];
|
||||
}
|
||||
|
||||
return $validators;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getBaseRoute($canonical_path, $method) {
|
||||
return new Route($canonical_path, [
|
||||
'_controller' => RequestHandler::class . '::handleRaw',
|
||||
],
|
||||
$this->getBaseRouteRequirements($method),
|
||||
[],
|
||||
'',
|
||||
[],
|
||||
// The HTTP method is a requirement for this route.
|
||||
[$method]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getBaseRouteRequirements($method) {
|
||||
$requirements = parent::getBaseRouteRequirements($method);
|
||||
|
||||
// Add the content type format access check. This will enforce that all
|
||||
// incoming requests can only use the 'application/octet-stream'
|
||||
// Content-Type header.
|
||||
$requirements['_content_type_format'] = 'bin';
|
||||
|
||||
return $requirements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a lock ID based on the file URI.
|
||||
*
|
||||
* @param $file_uri
|
||||
* The file URI.
|
||||
*
|
||||
* @return string
|
||||
* The generated lock ID.
|
||||
*/
|
||||
protected static function generateLockIdFromFileUri($file_uri) {
|
||||
return 'file:rest:' . Crypt::hashBase64($file_uri);
|
||||
}
|
||||
|
||||
}
|
|
@ -17,6 +17,8 @@ class File extends WizardPluginBase {
|
|||
|
||||
/**
|
||||
* Set the created column.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $createdColumn = 'created';
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Drupal\file\Tests;
|
||||
|
||||
@trigger_error('The ' . __NAMESPACE__ . '\FileFieldTestBase is deprecated in Drupal 8.5.x and will be removed before Drupal 9.0.0. Instead, use \Drupal\Tests\file\Functional\FileFieldTestBase. See https://www.drupal.org/node/2969361.', E_USER_DEPRECATED);
|
||||
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\file\FileInterface;
|
||||
|
@ -227,7 +229,7 @@ abstract class FileFieldTestBase extends WebTestBase {
|
|||
$edit[$name][] = $file_path;
|
||||
}
|
||||
}
|
||||
$this->drupalPostForm("node/$nid/edit", $edit, t('Save and keep published'));
|
||||
$this->drupalPostForm("node/$nid/edit", $edit, t('Save'));
|
||||
|
||||
return $nid;
|
||||
}
|
||||
|
@ -243,7 +245,7 @@ abstract class FileFieldTestBase extends WebTestBase {
|
|||
];
|
||||
|
||||
$this->drupalPostForm('node/' . $nid . '/edit', [], t('Remove'));
|
||||
$this->drupalPostForm(NULL, $edit, t('Save and keep published'));
|
||||
$this->drupalPostForm(NULL, $edit, t('Save'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -251,12 +253,12 @@ abstract class FileFieldTestBase extends WebTestBase {
|
|||
*/
|
||||
public function replaceNodeFile($file, $field_name, $nid, $new_revision = TRUE) {
|
||||
$edit = [
|
||||
'files[' . $field_name . '_0]' => drupal_realpath($file->getFileUri()),
|
||||
'files[' . $field_name . '_0]' => \Drupal::service('file_system')->realpath($file->getFileUri()),
|
||||
'revision' => (string) (int) $new_revision,
|
||||
];
|
||||
|
||||
$this->drupalPostForm('node/' . $nid . '/edit', [], t('Remove'));
|
||||
$this->drupalPostForm(NULL, $edit, t('Save and keep published'));
|
||||
$this->drupalPostForm(NULL, $edit, t('Save'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,7 +4,6 @@ namespace Drupal\file\Tests;
|
|||
|
||||
use Drupal\comment\Entity\Comment;
|
||||
use Drupal\comment\Tests\CommentTestTrait;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
@ -121,7 +120,7 @@ class FileFieldWidgetTest extends FileFieldTestBase {
|
|||
$this->assertTrue(isset($label[0]), 'Label for upload found.');
|
||||
|
||||
// Save the node and ensure it does not have the file.
|
||||
$this->drupalPostForm(NULL, [], t('Save and keep published'));
|
||||
$this->drupalPostForm(NULL, [], t('Save'));
|
||||
$node_storage->resetCache([$nid]);
|
||||
$node = $node_storage->load($nid);
|
||||
$this->assertTrue(empty($node->{$field_name}->target_id), 'File was successfully removed from the node.');
|
||||
|
@ -160,7 +159,7 @@ class FileFieldWidgetTest extends FileFieldTestBase {
|
|||
$this->drupalGet("node/add/$type_name");
|
||||
foreach ([$field_name2, $field_name] as $each_field_name) {
|
||||
for ($delta = 0; $delta < 3; $delta++) {
|
||||
$edit = ['files[' . $each_field_name . '_' . $delta . '][]' => drupal_realpath($test_file->getFileUri())];
|
||||
$edit = ['files[' . $each_field_name . '_' . $delta . '][]' => \Drupal::service('file_system')->realpath($test_file->getFileUri())];
|
||||
// If the Upload button doesn't exist, drupalPostForm() will automatically
|
||||
// fail with an assertion message.
|
||||
$this->drupalPostForm(NULL, $edit, t('Upload'));
|
||||
|
@ -238,8 +237,7 @@ class FileFieldWidgetTest extends FileFieldTestBase {
|
|||
$this->assertNoFieldByXPath('//input[@type="submit"]', t('Remove'), format_string('After removing all files, there is no "Remove" button displayed (JSMode=%type).', ['%type' => $type]));
|
||||
|
||||
// Save the node and ensure it does not have any files.
|
||||
$this->drupalPostForm(NULL, ['title[0][value]' => $this->randomMachineName()], t('Save and publish'));
|
||||
$matches = [];
|
||||
$this->drupalPostForm(NULL, ['title[0][value]' => $this->randomMachineName()], t('Save'));
|
||||
preg_match('/node\/([0-9]+)/', $this->getUrl(), $matches);
|
||||
$nid = $matches[1];
|
||||
$node_storage->resetCache([$nid]);
|
||||
|
@ -271,7 +269,7 @@ class FileFieldWidgetTest extends FileFieldTestBase {
|
|||
// Try to upload exactly the allowed number of files on revision. Create an
|
||||
// empty node first, to fill it in its first revision.
|
||||
$node = $this->drupalCreateNode([
|
||||
'type' => $type_name
|
||||
'type' => $type_name,
|
||||
]);
|
||||
$this->uploadNodeFile($test_file, $field_name, $node->id(), 1);
|
||||
$node_storage->resetCache([$nid]);
|
||||
|
@ -368,13 +366,13 @@ class FileFieldWidgetTest extends FileFieldTestBase {
|
|||
$edit = [
|
||||
'title[0][value]' => $this->randomMachineName(),
|
||||
];
|
||||
$this->drupalPostForm('node/add/article', $edit, t('Save and publish'));
|
||||
$this->drupalPostForm('node/add/article', $edit, t('Save'));
|
||||
$node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
|
||||
|
||||
// Add a comment with a file.
|
||||
$text_file = $this->getTestFile('text');
|
||||
$edit = [
|
||||
'files[field_' . $name . '_' . 0 . ']' => drupal_realpath($text_file->getFileUri()),
|
||||
'files[field_' . $name . '_' . 0 . ']' => \Drupal::service('file_system')->realpath($text_file->getFileUri()),
|
||||
'comment_body[0][value]' => $comment_body = $this->randomMachineName(),
|
||||
];
|
||||
$this->drupalPostForm('node/' . $node->id(), $edit, t('Save'));
|
||||
|
@ -402,7 +400,8 @@ class FileFieldWidgetTest extends FileFieldTestBase {
|
|||
|
||||
// Unpublishes node.
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->drupalPostForm('node/' . $node->id() . '/edit', [], t('Save and unpublish'));
|
||||
$edit = ['status[value]' => FALSE];
|
||||
$this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
|
||||
|
||||
// Ensures normal user can no longer download the file.
|
||||
$this->drupalLogin($user);
|
||||
|
@ -429,7 +428,7 @@ class FileFieldWidgetTest extends FileFieldTestBase {
|
|||
$name = 'files[' . $field_name . '_0]';
|
||||
|
||||
// Upload file with incorrect extension, check for validation error.
|
||||
$edit[$name] = drupal_realpath($test_file_image->getFileUri());
|
||||
$edit[$name] = \Drupal::service('file_system')->realpath($test_file_image->getFileUri());
|
||||
switch ($type) {
|
||||
case 'nojs':
|
||||
$this->drupalPostForm(NULL, $edit, t('Upload'));
|
||||
|
@ -443,7 +442,7 @@ class FileFieldWidgetTest extends FileFieldTestBase {
|
|||
$this->assertRaw($error_message, t('Validation error when file with wrong extension uploaded (JSMode=%type).', ['%type' => $type]));
|
||||
|
||||
// Upload file with correct extension, check that error message is removed.
|
||||
$edit[$name] = drupal_realpath($test_file_text->getFileUri());
|
||||
$edit[$name] = \Drupal::service('file_system')->realpath($test_file_text->getFileUri());
|
||||
switch ($type) {
|
||||
case 'nojs':
|
||||
$this->drupalPostForm(NULL, $edit, t('Upload'));
|
||||
|
@ -461,7 +460,7 @@ class FileFieldWidgetTest extends FileFieldTestBase {
|
|||
* Tests file widget element.
|
||||
*/
|
||||
public function testWidgetElement() {
|
||||
$field_name = Unicode::strtolower($this->randomMachineName());
|
||||
$field_name = mb_strtolower($this->randomMachineName());
|
||||
$html_name = str_replace('_', '-', $field_name);
|
||||
$this->createFileField($field_name, 'node', 'article', ['cardinality' => FieldStorageConfig::CARDINALITY_UNLIMITED]);
|
||||
$file = $this->getTestFile('text');
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Drupal\file\Tests;
|
||||
|
||||
@trigger_error('The ' . __NAMESPACE__ . '\FileManagedTestBase is deprecated in Drupal 8.5.x and will be removed before Drupal 9.0.0. Instead, use \Drupal\Tests\file\Functional\FileManagedTestBase. See https://www.drupal.org/node/2969361.', E_USER_DEPRECATED);
|
||||
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\file\FileInterface;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
|
22
web/core/modules/file/templates/file-audio.html.twig
Normal file
22
web/core/modules/file/templates/file-audio.html.twig
Normal file
|
@ -0,0 +1,22 @@
|
|||
{#
|
||||
/**
|
||||
* @file
|
||||
* Default theme implementation to display the file entity as an audio tag.
|
||||
*
|
||||
* Available variables:
|
||||
* - attributes: An array of HTML attributes, intended to be added to the
|
||||
* audio tag.
|
||||
* - files: And array of files to be added as sources for the audio tag. Each
|
||||
* element is an array with the following elements:
|
||||
* - file: The full file object.
|
||||
* - source_attributes: An array of HTML attributes for to be added to the
|
||||
* source tag.
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
#}
|
||||
<audio {{ attributes }}>
|
||||
{% for file in files %}
|
||||
<source {{ file.source_attributes }} />
|
||||
{% endfor %}
|
||||
</audio>
|
22
web/core/modules/file/templates/file-video.html.twig
Normal file
22
web/core/modules/file/templates/file-video.html.twig
Normal file
|
@ -0,0 +1,22 @@
|
|||
{#
|
||||
/**
|
||||
* @file
|
||||
* Default theme implementation to display the file entity as a video tag.
|
||||
*
|
||||
* Available variables:
|
||||
* - attributes: An array of HTML attributes, intended to be added to the
|
||||
* video tag.
|
||||
* - files: And array of files to be added as sources for the video tag. Each
|
||||
* element is an array with the following elements:
|
||||
* - file: The full file object.
|
||||
* - source_attributes: An array of HTML attributes for to be added to the
|
||||
* source tag.
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
#}
|
||||
<video {{ attributes }}>
|
||||
{% for file in files %}
|
||||
<source {{ file.source_attributes }} />
|
||||
{% endfor %}
|
||||
</video>
|
|
@ -7,6 +7,8 @@ use Drupal\Core\Form\FormStateInterface;
|
|||
|
||||
/**
|
||||
* Form controller for file_module_test module.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class FileModuleTestForm extends FormBase {
|
||||
|
||||
|
@ -83,7 +85,7 @@ class FileModuleTestForm extends FormBase {
|
|||
$fids[] = $fid;
|
||||
}
|
||||
|
||||
drupal_set_message($this->t('The file ids are %fids.', ['%fids' => implode(',', $fids)]));
|
||||
\Drupal::messenger()->addStatus($this->t('The file ids are %fids.', ['%fids' => implode(',', $fids)]));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,3 +4,9 @@ file.test:
|
|||
_form: 'Drupal\file_test\Form\FileTestForm'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
file.save_upload_from_form_test:
|
||||
path: '/file-test/save_upload_from_form_test'
|
||||
defaults:
|
||||
_form: 'Drupal\file_test\Form\FileTestSaveUploadFromForm'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
|
|
|
@ -108,13 +108,13 @@ class FileTestForm implements FormInterface {
|
|||
$file = file_save_upload('file_test_upload', $validators, $destination, 0, $form_state->getValue('file_test_replace'));
|
||||
if ($file) {
|
||||
$form_state->setValue('file_test_upload', $file);
|
||||
drupal_set_message(t('File @filepath was uploaded.', ['@filepath' => $file->getFileUri()]));
|
||||
drupal_set_message(t('File name is @filename.', ['@filename' => $file->getFilename()]));
|
||||
drupal_set_message(t('File MIME type is @mimetype.', ['@mimetype' => $file->getMimeType()]));
|
||||
drupal_set_message(t('You WIN!'));
|
||||
\Drupal::messenger()->addStatus(t('File @filepath was uploaded.', ['@filepath' => $file->getFileUri()]));
|
||||
\Drupal::messenger()->addStatus(t('File name is @filename.', ['@filename' => $file->getFilename()]));
|
||||
\Drupal::messenger()->addStatus(t('File MIME type is @mimetype.', ['@mimetype' => $file->getMimeType()]));
|
||||
\Drupal::messenger()->addStatus(t('You WIN!'));
|
||||
}
|
||||
elseif ($file === FALSE) {
|
||||
drupal_set_message(t('Epic upload FAIL!'), 'error');
|
||||
\Drupal::messenger()->addError(t('Epic upload FAIL!'));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,179 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\file_test\Form;
|
||||
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Messenger\MessengerInterface;
|
||||
use Drupal\Core\State\StateInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* File test form class.
|
||||
*/
|
||||
class FileTestSaveUploadFromForm extends FormBase {
|
||||
|
||||
/**
|
||||
* Stores the state storage service.
|
||||
*
|
||||
* @var \Drupal\Core\State\StateInterface
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* The messenger.
|
||||
*
|
||||
* @var \Drupal\Core\Messenger\MessengerInterface
|
||||
*/
|
||||
protected $messenger;
|
||||
|
||||
/**
|
||||
* Constructs a FileTestSaveUploadFromForm object.
|
||||
*
|
||||
* @param \Drupal\Core\State\StateInterface $state
|
||||
* The state key value store.
|
||||
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
|
||||
* The messenger.
|
||||
*/
|
||||
public function __construct(StateInterface $state, MessengerInterface $messenger) {
|
||||
$this->state = $state;
|
||||
$this->messenger = $messenger;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('state'),
|
||||
$container->get('messenger')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return '_file_test_save_upload_from_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$form['file_test_upload'] = [
|
||||
'#type' => 'file',
|
||||
'#multiple' => TRUE,
|
||||
'#title' => $this->t('Upload a file'),
|
||||
];
|
||||
$form['file_test_replace'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Replace existing image'),
|
||||
'#options' => [
|
||||
FILE_EXISTS_RENAME => $this->t('Appends number until name is unique'),
|
||||
FILE_EXISTS_REPLACE => $this->t('Replace the existing file'),
|
||||
FILE_EXISTS_ERROR => $this->t('Fail with an error'),
|
||||
],
|
||||
'#default_value' => FILE_EXISTS_RENAME,
|
||||
];
|
||||
$form['file_subdir'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Subdirectory for test file'),
|
||||
'#default_value' => '',
|
||||
];
|
||||
|
||||
$form['extensions'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Allowed extensions.'),
|
||||
'#default_value' => '',
|
||||
];
|
||||
|
||||
$form['allow_all_extensions'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Allow all extensions?'),
|
||||
'#default_value' => FALSE,
|
||||
];
|
||||
|
||||
$form['is_image_file'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Is this an image file?'),
|
||||
'#default_value' => TRUE,
|
||||
];
|
||||
|
||||
$form['error_message'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Custom error message.'),
|
||||
'#default_value' => '',
|
||||
];
|
||||
|
||||
$form['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Submit'),
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
// Process the upload and perform validation. Note: we're using the
|
||||
// form value for the $replace parameter.
|
||||
if (!$form_state->isValueEmpty('file_subdir')) {
|
||||
$destination = 'temporary://' . $form_state->getValue('file_subdir');
|
||||
file_prepare_directory($destination, FILE_CREATE_DIRECTORY);
|
||||
}
|
||||
else {
|
||||
$destination = FALSE;
|
||||
}
|
||||
|
||||
// Preset custom error message if requested.
|
||||
if ($form_state->getValue('error_message')) {
|
||||
$this->messenger->addError($form_state->getValue('error_message'));
|
||||
}
|
||||
|
||||
// Setup validators.
|
||||
$validators = [];
|
||||
if ($form_state->getValue('is_image_file')) {
|
||||
$validators['file_validate_is_image'] = [];
|
||||
}
|
||||
|
||||
if ($form_state->getValue('allow_all_extensions')) {
|
||||
$validators['file_validate_extensions'] = [];
|
||||
}
|
||||
elseif (!$form_state->isValueEmpty('extensions')) {
|
||||
$validators['file_validate_extensions'] = [$form_state->getValue('extensions')];
|
||||
}
|
||||
|
||||
// The test for drupal_move_uploaded_file() triggering a warning is
|
||||
// unavoidable. We're interested in what happens afterwards in
|
||||
// _file_save_upload_from_form().
|
||||
if ($this->state->get('file_test.disable_error_collection')) {
|
||||
define('SIMPLETEST_COLLECT_ERRORS', FALSE);
|
||||
}
|
||||
|
||||
$form['file_test_upload']['#upload_validators'] = $validators;
|
||||
$form['file_test_upload']['#upload_location'] = $destination;
|
||||
|
||||
$this->messenger->addStatus($this->t('Number of error messages before _file_save_upload_from_form(): @count.', ['@count' => count($this->messenger->messagesByType(MessengerInterface::TYPE_ERROR))]));
|
||||
$file = _file_save_upload_from_form($form['file_test_upload'], $form_state, 0, $form_state->getValue('file_test_replace'));
|
||||
$this->messenger->addStatus($this->t('Number of error messages after _file_save_upload_from_form(): @count.', ['@count' => count($this->messenger->messagesByType(MessengerInterface::TYPE_ERROR))]));
|
||||
|
||||
if ($file) {
|
||||
$form_state->setValue('file_test_upload', $file);
|
||||
$this->messenger->addStatus($this->t('File @filepath was uploaded.', ['@filepath' => $file->getFileUri()]));
|
||||
$this->messenger->addStatus($this->t('File name is @filename.', ['@filename' => $file->getFilename()]));
|
||||
$this->messenger->addStatus($this->t('File MIME type is @mimetype.', ['@mimetype' => $file->getMimeType()]));
|
||||
$this->messenger->addStatus($this->t('You WIN!'));
|
||||
}
|
||||
elseif ($file === FALSE) {
|
||||
$this->messenger->addError($this->t('Epic upload FAIL!'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {}
|
||||
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
uuid: 9ca49b35-b49d-4014-9337-965cdf15b61e
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- field.field.node.article.body
|
||||
- field.field.node.article.comment
|
||||
- field.field.node.article.field_file_generic_2677990
|
||||
- field.field.node.article.field_file_table_2677990
|
||||
- field.field.node.article.field_image
|
||||
- field.field.node.article.field_tags
|
||||
- image.style.large
|
||||
- node.type.article
|
||||
module:
|
||||
- comment
|
||||
- file
|
||||
- image
|
||||
- text
|
||||
- user
|
||||
_core:
|
||||
default_config_hash: JtAg_-waIt1quMtdDtHIaXJMxvTuSmxW7bWyO6Zd68E
|
||||
id: node.article.default
|
||||
targetEntityType: node
|
||||
bundle: article
|
||||
mode: default
|
||||
content:
|
||||
body:
|
||||
type: text_default
|
||||
weight: 0
|
||||
settings: { }
|
||||
third_party_settings: { }
|
||||
label: hidden
|
||||
comment:
|
||||
label: above
|
||||
type: comment_default
|
||||
weight: 20
|
||||
settings:
|
||||
pager_id: 0
|
||||
third_party_settings: { }
|
||||
field_file_generic_2677990:
|
||||
weight: 101
|
||||
label: above
|
||||
settings: { }
|
||||
third_party_settings: { }
|
||||
type: file_default
|
||||
field_file_table_2677990:
|
||||
weight: 102
|
||||
label: above
|
||||
settings: { }
|
||||
third_party_settings: { }
|
||||
type: file_table
|
||||
field_image:
|
||||
type: image
|
||||
weight: -1
|
||||
settings:
|
||||
image_style: large
|
||||
image_link: ''
|
||||
third_party_settings: { }
|
||||
label: hidden
|
||||
field_tags:
|
||||
type: entity_reference_label
|
||||
weight: 10
|
||||
label: above
|
||||
settings:
|
||||
link: true
|
||||
third_party_settings: { }
|
||||
links:
|
||||
weight: 100
|
||||
settings: { }
|
||||
third_party_settings: { }
|
||||
hidden: { }
|
88
web/core/modules/file/tests/fixtures/update/drupal-8.file_formatters_update_2677990.php
vendored
Normal file
88
web/core/modules/file/tests/fixtures/update/drupal-8.file_formatters_update_2677990.php
vendored
Normal file
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains database additions to drupal-8.bare.standard.php.gz for testing the
|
||||
* upgrade path of https://www.drupal.org/node/2677990.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Component\Serialization\Yaml;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
$connection = Database::getConnection();
|
||||
|
||||
// Configuration for a file field storage for generic display.
|
||||
$field_file_generic_2677990 = Yaml::decode(file_get_contents(__DIR__ . '/field.storage.node.field_file_generic_2677990.yml'));
|
||||
|
||||
// Configuration for a file field storage for table display.
|
||||
$field_file_table_2677990 = Yaml::decode(file_get_contents(__DIR__ . '/field.storage.node.field_file_table_2677990.yml'));
|
||||
|
||||
$connection->insert('config')
|
||||
->fields([
|
||||
'collection',
|
||||
'name',
|
||||
'data',
|
||||
])
|
||||
->values([
|
||||
'collection' => '',
|
||||
'name' => 'field.storage.' . $field_file_generic_2677990['id'],
|
||||
'data' => serialize($field_file_generic_2677990),
|
||||
])
|
||||
->values([
|
||||
'collection' => '',
|
||||
'name' => 'field.storage.' . $field_file_table_2677990['id'],
|
||||
'data' => serialize($field_file_table_2677990),
|
||||
])
|
||||
->execute();
|
||||
// We need to Update the registry of "last installed" field definitions.
|
||||
$installed = $connection->select('key_value')
|
||||
->fields('key_value', ['value'])
|
||||
->condition('collection', 'entity.definitions.installed')
|
||||
->condition('name', 'node.field_storage_definitions')
|
||||
->execute()
|
||||
->fetchField();
|
||||
$installed = unserialize($installed);
|
||||
$installed['field_file_generic_2677990'] = new FieldStorageConfig($field_file_generic_2677990);
|
||||
$installed['field_file_table_2677990'] = new FieldStorageConfig($field_file_table_2677990);
|
||||
$connection->update('key_value')
|
||||
->condition('collection', 'entity.definitions.installed')
|
||||
->condition('name', 'node.field_storage_definitions')
|
||||
->fields([
|
||||
'value' => serialize($installed),
|
||||
])
|
||||
->execute();
|
||||
|
||||
// Configuration for a file field storage for generic display.
|
||||
$field_file_generic_2677990 = Yaml::decode(file_get_contents(__DIR__ . '/field.field.node.article.field_file_generic_2677990.yml'));
|
||||
|
||||
// Configuration for a file field storage for table display.
|
||||
$field_file_table_2677990 = Yaml::decode(file_get_contents(__DIR__ . '/field.field.node.article.field_file_table_2677990.yml'));
|
||||
|
||||
$connection->insert('config')
|
||||
->fields([
|
||||
'collection',
|
||||
'name',
|
||||
'data',
|
||||
])
|
||||
->values([
|
||||
'collection' => '',
|
||||
'name' => 'field.field.' . $field_file_generic_2677990['id'],
|
||||
'data' => serialize($field_file_generic_2677990),
|
||||
])
|
||||
->values([
|
||||
'collection' => '',
|
||||
'name' => 'field.field.' . $field_file_table_2677990['id'],
|
||||
'data' => serialize($field_file_table_2677990),
|
||||
])
|
||||
->execute();
|
||||
|
||||
// Configuration of the view mode to set the proper formatters.
|
||||
$view_mode_2677990 = Yaml::decode(file_get_contents(__DIR__ . '/core.entity_view_display.node.article.default_2677990.yml'));
|
||||
|
||||
$connection->update('config')
|
||||
->fields([
|
||||
'data' => serialize($view_mode_2677990),
|
||||
])
|
||||
->condition('name', 'core.entity_view_display.' . $view_mode_2677990['id'])
|
||||
->execute();
|
|
@ -0,0 +1,27 @@
|
|||
uuid: d352a831-c267-4ecc-9b4e-7ae1896b2241
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- field.storage.node.field_file_generic_2677990
|
||||
- node.type.article
|
||||
module:
|
||||
- file
|
||||
id: node.article.field_file_generic_2677990
|
||||
field_name: field_file_generic_2677990
|
||||
entity_type: node
|
||||
bundle: article
|
||||
label: 'File generic'
|
||||
description: ''
|
||||
required: false
|
||||
translatable: false
|
||||
default_value: { }
|
||||
default_value_callback: ''
|
||||
settings:
|
||||
file_directory: '[date:custom:Y]-[date:custom:m]'
|
||||
file_extensions: txt
|
||||
max_filesize: ''
|
||||
description_field: true
|
||||
handler: 'default:file'
|
||||
handler_settings: { }
|
||||
field_type: file
|
|
@ -0,0 +1,27 @@
|
|||
uuid: 44c7a590-ffcc-4534-b01c-b17b8d57ed48
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- field.storage.node.field_file_table_2677990
|
||||
- node.type.article
|
||||
module:
|
||||
- file
|
||||
id: node.article.field_file_table_2677990
|
||||
field_name: field_file_table_2677990
|
||||
entity_type: node
|
||||
bundle: article
|
||||
label: 'File table'
|
||||
description: ''
|
||||
required: false
|
||||
translatable: false
|
||||
default_value: { }
|
||||
default_value_callback: ''
|
||||
settings:
|
||||
file_directory: '[date:custom:Y]-[date:custom:m]'
|
||||
file_extensions: txt
|
||||
max_filesize: ''
|
||||
description_field: true
|
||||
handler: 'default:file'
|
||||
handler_settings: { }
|
||||
field_type: file
|
23
web/core/modules/file/tests/fixtures/update/field.storage.node.field_file_generic_2677990.yml
vendored
Normal file
23
web/core/modules/file/tests/fixtures/update/field.storage.node.field_file_generic_2677990.yml
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
uuid: a7eb470d-e538-4221-a8ac-57f989d92d8e
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- file
|
||||
- node
|
||||
id: node.field_file_generic_2677990
|
||||
field_name: field_file_generic_2677990
|
||||
entity_type: node
|
||||
type: file
|
||||
settings:
|
||||
display_field: false
|
||||
display_default: false
|
||||
uri_scheme: public
|
||||
target_type: file
|
||||
module: file
|
||||
locked: false
|
||||
cardinality: 1
|
||||
translatable: true
|
||||
indexes: { }
|
||||
persist_with_no_fields: false
|
||||
custom_storage: false
|
23
web/core/modules/file/tests/fixtures/update/field.storage.node.field_file_table_2677990.yml
vendored
Normal file
23
web/core/modules/file/tests/fixtures/update/field.storage.node.field_file_table_2677990.yml
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
uuid: 43113a5e-3e07-4234-b045-4aa4cd217dca
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- file
|
||||
- node
|
||||
id: node.field_file_table_2677990
|
||||
field_name: field_file_table_2677990
|
||||
entity_type: node
|
||||
type: file
|
||||
settings:
|
||||
display_field: false
|
||||
display_default: false
|
||||
uri_scheme: public
|
||||
target_type: file
|
||||
module: file
|
||||
locked: false
|
||||
cardinality: 1
|
||||
translatable: true
|
||||
indexes: { }
|
||||
persist_with_no_fields: false
|
||||
custom_storage: false
|
|
@ -5,5 +5,5 @@ package: Testing
|
|||
version: VERSION
|
||||
core: 8.x
|
||||
dependencies:
|
||||
- file
|
||||
- views
|
||||
- drupal:file
|
||||
- drupal:views
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\file\Tests;
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
/**
|
||||
* Tests for download/file transfer functions.
|
||||
|
@ -8,6 +8,7 @@ namespace Drupal\file\Tests;
|
|||
* @group file
|
||||
*/
|
||||
class DownloadTest extends FileManagedTestBase {
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
// Clear out any hook calls.
|
||||
|
@ -25,16 +26,17 @@ class DownloadTest extends FileManagedTestBase {
|
|||
// encoded.
|
||||
$filename = $GLOBALS['base_url'] . '/' . \Drupal::service('stream_wrapper_manager')->getViaScheme('public')->getDirectoryPath() . '/' . rawurlencode($file->getFilename());
|
||||
$this->assertEqual($filename, $url, 'Correctly generated a URL for a created file.');
|
||||
$this->drupalHead($url);
|
||||
$this->assertResponse(200, 'Confirmed that the generated URL is correct by downloading the created file.');
|
||||
$http_client = $this->getHttpClient();
|
||||
$response = $http_client->head($url);
|
||||
$this->assertEquals(200, $response->getStatusCode(), 'Confirmed that the generated URL is correct by downloading the created file.');
|
||||
|
||||
// Test generating a URL to a shipped file (i.e. a file that is part of
|
||||
// Drupal core, a module or a theme, for example a JavaScript file).
|
||||
$filepath = 'core/assets/vendor/jquery/jquery.min.js';
|
||||
$url = file_create_url($filepath);
|
||||
$this->assertEqual($GLOBALS['base_url'] . '/' . $filepath, $url, 'Correctly generated a URL for a shipped file.');
|
||||
$this->drupalHead($url);
|
||||
$this->assertResponse(200, 'Confirmed that the generated URL is correct by downloading the shipped file.');
|
||||
$response = $http_client->head($url);
|
||||
$this->assertEquals(200, $response->getStatusCode(), 'Confirmed that the generated URL is correct by downloading the shipped file.');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -60,7 +62,7 @@ class DownloadTest extends FileManagedTestBase {
|
|||
$file->setPermanent();
|
||||
$file->save();
|
||||
|
||||
$url = file_create_url($file->getFileUri());
|
||||
$url = file_create_url($file->getFileUri());
|
||||
|
||||
// Set file_test access header to allow the download.
|
||||
file_test_set_return('download', ['x-foo' => 'Bar']);
|
||||
|
@ -70,17 +72,18 @@ class DownloadTest extends FileManagedTestBase {
|
|||
$this->assertResponse(200, 'Correctly allowed access to a file when file_test provides headers.');
|
||||
|
||||
// Test that the file transferred correctly.
|
||||
$this->assertEqual($contents, $this->content, 'Contents of the file are correct.');
|
||||
$this->assertSame($contents, $this->getSession()->getPage()->getContent(), 'Contents of the file are correct.');
|
||||
$http_client = $this->getHttpClient();
|
||||
|
||||
// Deny access to all downloads via a -1 header.
|
||||
file_test_set_return('download', -1);
|
||||
$this->drupalHead($url);
|
||||
$this->assertResponse(403, 'Correctly denied access to a file when file_test sets the header to -1.');
|
||||
$response = $http_client->head($url, ['http_errors' => FALSE]);
|
||||
$this->assertSame(403, $response->getStatusCode(), 'Correctly denied access to a file when file_test sets the header to -1.');
|
||||
|
||||
// Try non-existent file.
|
||||
$url = file_create_url('private://' . $this->randomMachineName());
|
||||
$this->drupalHead($url);
|
||||
$this->assertResponse(404, 'Correctly returned 404 response for a non-existent file.');
|
||||
$response = $http_client->head($url, ['http_errors' => FALSE]);
|
||||
$this->assertSame(404, $response->getStatusCode(), 'Correctly returned 404 response for a non-existent file.');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -91,9 +94,12 @@ class DownloadTest extends FileManagedTestBase {
|
|||
// Tilde (~) is excluded from this test because it is encoded by
|
||||
// rawurlencode() in PHP 5.2 but not in PHP 5.3, as per RFC 3986.
|
||||
// @see http://php.net/manual/function.rawurlencode.php#86506
|
||||
$basename = " -._!$'\"()*@[]?&+%#,;=:\n\x00" . // "Special" ASCII characters.
|
||||
"%23%25%26%2B%2F%3F" . // Characters that look like a percent-escaped string.
|
||||
"éøïвβ中國書۞"; // Characters from various non-ASCII alphabets.
|
||||
// "Special" ASCII characters.
|
||||
$basename = " -._!$'\"()*@[]?&+%#,;=:\n\x00" .
|
||||
// Characters that look like a percent-escaped string.
|
||||
"%23%25%26%2B%2F%3F" .
|
||||
// Characters from various non-ASCII alphabets.
|
||||
"éøïвβ中國書۞";
|
||||
$basename_encoded = '%20-._%21%24%27%22%28%29%2A%40%5B%5D%3F%26%2B%25%23%2C%3B%3D%3A__' .
|
||||
'%2523%2525%2526%252B%252F%253F' .
|
||||
'%C3%A9%C3%B8%C3%AF%D0%B2%CE%B2%E4%B8%AD%E5%9C%8B%E6%9B%B8%DB%9E';
|
||||
|
@ -115,16 +121,6 @@ class DownloadTest extends FileManagedTestBase {
|
|||
$this->checkUrl('private', '', $basename, $base_path . '/' . $script_path . 'system/files/' . $basename_encoded);
|
||||
}
|
||||
$this->assertEqual(file_create_url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==', t('Generated URL matches expected URL.'));
|
||||
// Test public files with a different host name from settings.
|
||||
$test_base_url = 'http://www.example.com/cdn';
|
||||
$this->settingsSet('file_public_base_url', $test_base_url);
|
||||
$filepath = file_create_filename('test.txt', '');
|
||||
$directory_uri = 'public://' . dirname($filepath);
|
||||
file_prepare_directory($directory_uri, FILE_CREATE_DIRECTORY);
|
||||
$file = $this->createFile($filepath, NULL, 'public');
|
||||
$url = file_create_url($file->getFileUri());
|
||||
$expected_url = $test_base_url . '/' . basename($filepath);
|
||||
$this->assertEqual($url, $expected_url);
|
||||
}
|
||||
|
||||
/**
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\file\Tests;
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\user\RoleInterface;
|
||||
|
@ -138,7 +138,7 @@ class FileFieldAnonymousSubmissionTest extends FileFieldTestBase {
|
|||
$label = 'Save';
|
||||
}
|
||||
else {
|
||||
$label = 'Save and publish';
|
||||
$label = 'Save';
|
||||
}
|
||||
$this->drupalPostForm(NULL, $edit, $label);
|
||||
$this->assertResponse(200);
|
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
|
||||
/**
|
||||
* Provides methods for creating file fields.
|
||||
*/
|
||||
trait FileFieldCreationTrait {
|
||||
|
||||
/**
|
||||
* Creates a new file field.
|
||||
*
|
||||
* @param string $name
|
||||
* The name of the new field (all lowercase), exclude the "field_" prefix.
|
||||
* @param string $entity_type
|
||||
* The entity type.
|
||||
* @param string $bundle
|
||||
* The bundle that this field will be added to.
|
||||
* @param array $storage_settings
|
||||
* A list of field storage settings that will be added to the defaults.
|
||||
* @param array $field_settings
|
||||
* A list of instance settings that will be added to the instance defaults.
|
||||
* @param array $widget_settings
|
||||
* A list of widget settings that will be added to the widget defaults.
|
||||
*
|
||||
* @return \Drupal\field\FieldStorageConfigInterface
|
||||
* The file field.
|
||||
*/
|
||||
public function createFileField($name, $entity_type, $bundle, $storage_settings = [], $field_settings = [], $widget_settings = []) {
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'entity_type' => $entity_type,
|
||||
'field_name' => $name,
|
||||
'type' => 'file',
|
||||
'settings' => $storage_settings,
|
||||
'cardinality' => !empty($storage_settings['cardinality']) ? $storage_settings['cardinality'] : 1,
|
||||
]);
|
||||
$field_storage->save();
|
||||
|
||||
$this->attachFileField($name, $entity_type, $bundle, $field_settings, $widget_settings);
|
||||
return $field_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches a file field to an entity.
|
||||
*
|
||||
* @param string $name
|
||||
* The name of the new field (all lowercase), exclude the "field_" prefix.
|
||||
* @param string $entity_type
|
||||
* The entity type this field will be added to.
|
||||
* @param string $bundle
|
||||
* The bundle this field will be added to.
|
||||
* @param array $field_settings
|
||||
* A list of field settings that will be added to the defaults.
|
||||
* @param array $widget_settings
|
||||
* A list of widget settings that will be added to the widget defaults.
|
||||
*/
|
||||
public function attachFileField($name, $entity_type, $bundle, $field_settings = [], $widget_settings = []) {
|
||||
$field = [
|
||||
'field_name' => $name,
|
||||
'label' => $name,
|
||||
'entity_type' => $entity_type,
|
||||
'bundle' => $bundle,
|
||||
'required' => !empty($field_settings['required']),
|
||||
'settings' => $field_settings,
|
||||
];
|
||||
FieldConfig::create($field)->save();
|
||||
|
||||
entity_get_form_display($entity_type, $bundle, 'default')
|
||||
->setComponent($name, [
|
||||
'type' => 'file_generic',
|
||||
'settings' => $widget_settings,
|
||||
])
|
||||
->save();
|
||||
// Assign display settings.
|
||||
entity_get_display($entity_type, $bundle, 'default')
|
||||
->setComponent($name, [
|
||||
'label' => 'hidden',
|
||||
'type' => 'file_default',
|
||||
])
|
||||
->save();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\file\Tests;
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\node\Entity\Node;
|
||||
|
||||
/**
|
||||
* Tests the display of file fields in node and views.
|
||||
|
@ -52,8 +53,7 @@ class FileFieldDisplayTest extends FileFieldTestBase {
|
|||
$this->assertNoText($field_name, format_string('Field label is hidden when no file attached for formatter %formatter', ['%formatter' => $formatter]));
|
||||
}
|
||||
|
||||
$test_file = $this->getTestFile('text');
|
||||
simpletest_generate_file('escaped-&-text', 64, 10, 'text');
|
||||
$this->generateFile('escaped-&-text', 64, 10, 'text');
|
||||
$test_file = File::create([
|
||||
'uri' => 'public://escaped-&-text.txt',
|
||||
'name' => 'escaped-&-text',
|
||||
|
@ -77,7 +77,7 @@ class FileFieldDisplayTest extends FileFieldTestBase {
|
|||
|
||||
// Turn the "display" option off and check that the file is no longer displayed.
|
||||
$edit = [$field_name . '[0][display]' => FALSE];
|
||||
$this->drupalPostForm('node/' . $nid . '/edit', $edit, t('Save and keep published'));
|
||||
$this->drupalPostForm('node/' . $nid . '/edit', $edit, t('Save'));
|
||||
|
||||
$this->assertNoRaw($default_output, 'Field is hidden when "display" option is unchecked.');
|
||||
|
||||
|
@ -87,7 +87,7 @@ class FileFieldDisplayTest extends FileFieldTestBase {
|
|||
$field_name . '[0][description]' => $description,
|
||||
$field_name . '[0][display]' => TRUE,
|
||||
];
|
||||
$this->drupalPostForm('node/' . $nid . '/edit', $edit, t('Save and keep published'));
|
||||
$this->drupalPostForm('node/' . $nid . '/edit', $edit, t('Save'));
|
||||
$this->assertText($description);
|
||||
|
||||
// Ensure the filename in the link's title attribute is escaped.
|
||||
|
@ -96,15 +96,17 @@ class FileFieldDisplayTest extends FileFieldTestBase {
|
|||
// Test that fields appear as expected after during the preview.
|
||||
// Add a second file.
|
||||
$name = 'files[' . $field_name . '_1][]';
|
||||
$edit[$name] = drupal_realpath($test_file->getFileUri());
|
||||
$edit_upload[$name] = \Drupal::service('file_system')->realpath($test_file->getFileUri());
|
||||
$this->drupalPostForm("node/$nid/edit", $edit_upload, t('Upload'));
|
||||
|
||||
// Uncheck the display checkboxes and go to the preview.
|
||||
$edit[$field_name . '[0][display]'] = FALSE;
|
||||
$edit[$field_name . '[1][display]'] = FALSE;
|
||||
$this->drupalPostForm("node/$nid/edit", $edit, t('Preview'));
|
||||
$this->drupalPostForm(NULL, $edit, t('Preview'));
|
||||
$this->clickLink(t('Back to content editing'));
|
||||
$this->assertRaw($field_name . '[0][display]', 'First file appears as expected.');
|
||||
$this->assertRaw($field_name . '[1][display]', 'Second file appears as expected.');
|
||||
$this->assertSession()->responseContains($field_name . '[1][description]', 'Description of second file appears as expected.');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -165,12 +167,57 @@ class FileFieldDisplayTest extends FileFieldTestBase {
|
|||
$title = $this->randomString();
|
||||
$edit = [
|
||||
'title[0][value]' => $title,
|
||||
'files[field_' . $field_name . '_0]' => drupal_realpath($file->uri),
|
||||
'files[field_' . $field_name . '_0]' => \Drupal::service('file_system')->realpath($file->uri),
|
||||
];
|
||||
$this->drupalPostForm('node/add/' . $type_name, $edit, t('Save and publish'));
|
||||
$this->drupalPostForm('node/add/' . $type_name, $edit, t('Save'));
|
||||
$node = $this->drupalGetNodeByTitle($title);
|
||||
$this->drupalGet('node/' . $node->id() . '/edit');
|
||||
$this->assertText(t('The description may be used as the label of the link to the file.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests description display of File Field.
|
||||
*/
|
||||
public function testDescriptionDefaultFileFieldDisplay() {
|
||||
$field_name = strtolower($this->randomMachineName());
|
||||
$type_name = 'article';
|
||||
$field_storage_settings = [
|
||||
'display_field' => '1',
|
||||
'display_default' => '1',
|
||||
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
|
||||
];
|
||||
$field_settings = [
|
||||
'description_field' => '1',
|
||||
];
|
||||
$widget_settings = [];
|
||||
$this->createFileField($field_name, 'node', $type_name, $field_storage_settings, $field_settings, $widget_settings);
|
||||
|
||||
$test_file = $this->getTestFile('text');
|
||||
|
||||
// Create a new node with the uploaded file.
|
||||
$nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
|
||||
|
||||
// Add file description.
|
||||
$description = 'This is the test file description';
|
||||
$this->drupalPostForm("node/$nid/edit", [$field_name . '[0][description]' => $description], t('Save'));
|
||||
|
||||
// Load uncached node.
|
||||
\Drupal::entityTypeManager()->getStorage('node')->resetCache([$nid]);
|
||||
$node = Node::load($nid);
|
||||
|
||||
// Test default formatter.
|
||||
$this->drupalGet('node/' . $nid);
|
||||
$this->assertFieldByXPath('//a[@href="' . $node->{$field_name}->entity->url() . '"]', $description);
|
||||
|
||||
// Change formatter to "Table of files".
|
||||
$display = \Drupal::entityTypeManager()->getStorage('entity_view_display')->load('node.' . $type_name . '.default');
|
||||
$display->setComponent($field_name, [
|
||||
'label' => 'hidden',
|
||||
'type' => 'file_table',
|
||||
])->save();
|
||||
|
||||
$this->drupalGet('node/' . $nid);
|
||||
$this->assertFieldByXPath('//a[@href="' . $node->{$field_name}->entity->url() . '"]', $description);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\file\Tests;
|
||||
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
/**
|
||||
* Tests file formatter access.
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\file\Tests;
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
use Drupal\file\Entity\File;
|
||||
|
||||
|
@ -10,6 +10,7 @@ use Drupal\file\Entity\File;
|
|||
* @group file
|
||||
*/
|
||||
class FileFieldPathTest extends FileFieldTestBase {
|
||||
|
||||
/**
|
||||
* Tests the normal formatter display on node display.
|
||||
*/
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\file\Tests;
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
use Drupal\file\Entity\File;
|
||||
|
||||
|
@ -60,12 +60,12 @@ class FileFieldRSSContentTest extends FileFieldTestBase {
|
|||
$this->drupalGet('rss.xml');
|
||||
$uploaded_filename = str_replace('public://', '', $node_file->getFileUri());
|
||||
$selector = sprintf(
|
||||
'enclosure[url="%s"][length="%s"][type="%s"]',
|
||||
'enclosure[@url="%s"][@length="%s"][@type="%s"]',
|
||||
file_create_url("public://$uploaded_filename", ['absolute' => TRUE]),
|
||||
$node_file->getSize(),
|
||||
$node_file->getMimeType()
|
||||
);
|
||||
$this->assertTrue(!empty($this->cssSelect($selector)), 'File field RSS enclosure is displayed when viewing the RSS feed.');
|
||||
$this->assertNotNull($this->getSession()->getDriver()->find('xpath', $selector), 'File field RSS enclosure is displayed when viewing the RSS feed.');
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\file\Tests;
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
use Drupal\file\Entity\File;
|
||||
|
||||
|
@ -10,6 +10,7 @@ use Drupal\file\Entity\File;
|
|||
* @group file
|
||||
*/
|
||||
class FileFieldRevisionTest extends FileFieldTestBase {
|
||||
|
||||
/**
|
||||
* Tests creating multiple revisions of a node and managing attached files.
|
||||
*
|
||||
|
@ -22,6 +23,11 @@ class FileFieldRevisionTest extends FileFieldTestBase {
|
|||
* should be deleted also.
|
||||
*/
|
||||
public function testRevisions() {
|
||||
// This test expects unused managed files to be marked as a temporary file
|
||||
// and then deleted up by file_cron().
|
||||
$this->config('file.settings')
|
||||
->set('make_unused_managed_files_temporary', TRUE)
|
||||
->save();
|
||||
$node_storage = $this->container->get('entity.manager')->getStorage('node');
|
||||
$type_name = 'article';
|
||||
$field_name = strtolower($this->randomMachineName());
|
||||
|
@ -63,7 +69,7 @@ class FileFieldRevisionTest extends FileFieldTestBase {
|
|||
|
||||
// Save a new version of the node without any changes.
|
||||
// Check that the file is still the same as the previous revision.
|
||||
$this->drupalPostForm('node/' . $nid . '/edit', ['revision' => '1'], t('Save and keep published'));
|
||||
$this->drupalPostForm('node/' . $nid . '/edit', ['revision' => '1'], t('Save'));
|
||||
$node_storage->resetCache([$nid]);
|
||||
$node = $node_storage->load($nid);
|
||||
$node_file_r3 = File::load($node->{$field_name}->target_id);
|
|
@ -7,12 +7,18 @@ use Drupal\field\Entity\FieldConfig;
|
|||
use Drupal\file\FileInterface;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\Tests\TestFileCreationTrait;
|
||||
|
||||
/**
|
||||
* Provides methods specifically for testing File module's field handling.
|
||||
*/
|
||||
abstract class FileFieldTestBase extends BrowserTestBase {
|
||||
|
||||
use FileFieldCreationTrait;
|
||||
use TestFileCreationTrait {
|
||||
getTestFiles as drupalGetTestFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
|
@ -57,76 +63,6 @@ abstract class FileFieldTestBase extends BrowserTestBase {
|
|||
return (int) db_query('SELECT MAX(fid) FROM {file_managed}')->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new file field.
|
||||
*
|
||||
* @param string $name
|
||||
* The name of the new field (all lowercase), exclude the "field_" prefix.
|
||||
* @param string $entity_type
|
||||
* The entity type.
|
||||
* @param string $bundle
|
||||
* The bundle that this field will be added to.
|
||||
* @param array $storage_settings
|
||||
* A list of field storage settings that will be added to the defaults.
|
||||
* @param array $field_settings
|
||||
* A list of instance settings that will be added to the instance defaults.
|
||||
* @param array $widget_settings
|
||||
* A list of widget settings that will be added to the widget defaults.
|
||||
*/
|
||||
public function createFileField($name, $entity_type, $bundle, $storage_settings = [], $field_settings = [], $widget_settings = []) {
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'entity_type' => $entity_type,
|
||||
'field_name' => $name,
|
||||
'type' => 'file',
|
||||
'settings' => $storage_settings,
|
||||
'cardinality' => !empty($storage_settings['cardinality']) ? $storage_settings['cardinality'] : 1,
|
||||
]);
|
||||
$field_storage->save();
|
||||
|
||||
$this->attachFileField($name, $entity_type, $bundle, $field_settings, $widget_settings);
|
||||
return $field_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches a file field to an entity.
|
||||
*
|
||||
* @param string $name
|
||||
* The name of the new field (all lowercase), exclude the "field_" prefix.
|
||||
* @param string $entity_type
|
||||
* The entity type this field will be added to.
|
||||
* @param string $bundle
|
||||
* The bundle this field will be added to.
|
||||
* @param array $field_settings
|
||||
* A list of field settings that will be added to the defaults.
|
||||
* @param array $widget_settings
|
||||
* A list of widget settings that will be added to the widget defaults.
|
||||
*/
|
||||
public function attachFileField($name, $entity_type, $bundle, $field_settings = [], $widget_settings = []) {
|
||||
$field = [
|
||||
'field_name' => $name,
|
||||
'label' => $name,
|
||||
'entity_type' => $entity_type,
|
||||
'bundle' => $bundle,
|
||||
'required' => !empty($field_settings['required']),
|
||||
'settings' => $field_settings,
|
||||
];
|
||||
FieldConfig::create($field)->save();
|
||||
|
||||
entity_get_form_display($entity_type, $bundle, 'default')
|
||||
->setComponent($name, [
|
||||
'type' => 'file_generic',
|
||||
'settings' => $widget_settings,
|
||||
])
|
||||
->save();
|
||||
// Assign display settings.
|
||||
entity_get_display($entity_type, $bundle, 'default')
|
||||
->setComponent($name, [
|
||||
'label' => 'hidden',
|
||||
'type' => 'file_default',
|
||||
])
|
||||
->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing file field with new settings.
|
||||
*/
|
||||
|
@ -224,7 +160,7 @@ abstract class FileFieldTestBase extends BrowserTestBase {
|
|||
$edit[$name][] = $file_path;
|
||||
}
|
||||
}
|
||||
$this->drupalPostForm("node/$nid/edit", $edit, t('Save and keep published'));
|
||||
$this->drupalPostForm("node/$nid/edit", $edit, t('Save'));
|
||||
|
||||
return $nid;
|
||||
}
|
||||
|
@ -240,7 +176,7 @@ abstract class FileFieldTestBase extends BrowserTestBase {
|
|||
];
|
||||
|
||||
$this->drupalPostForm('node/' . $nid . '/edit', [], t('Remove'));
|
||||
$this->drupalPostForm(NULL, $edit, t('Save and keep published'));
|
||||
$this->drupalPostForm(NULL, $edit, t('Save'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -248,18 +184,18 @@ abstract class FileFieldTestBase extends BrowserTestBase {
|
|||
*/
|
||||
public function replaceNodeFile($file, $field_name, $nid, $new_revision = TRUE) {
|
||||
$edit = [
|
||||
'files[' . $field_name . '_0]' => drupal_realpath($file->getFileUri()),
|
||||
'files[' . $field_name . '_0]' => \Drupal::service('file_system')->realpath($file->getFileUri()),
|
||||
'revision' => (string) (int) $new_revision,
|
||||
];
|
||||
|
||||
$this->drupalPostForm('node/' . $nid . '/edit', [], t('Remove'));
|
||||
$this->drupalPostForm(NULL, $edit, t('Save and keep published'));
|
||||
$this->drupalPostForm(NULL, $edit, t('Save'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a file exists physically on disk.
|
||||
*
|
||||
* Overrides PHPUnit_Framework_Assert::assertFileExists() to also work with
|
||||
* Overrides PHPUnit\Framework\Assert::assertFileExists() to also work with
|
||||
* file entities.
|
||||
*
|
||||
* @param \Drupal\File\FileInterface|string $file
|
||||
|
@ -286,7 +222,7 @@ abstract class FileFieldTestBase extends BrowserTestBase {
|
|||
/**
|
||||
* Asserts that a file does not exist on disk.
|
||||
*
|
||||
* Overrides PHPUnit_Framework_Assert::assertFileExists() to also work with
|
||||
* Overrides PHPUnit\Framework\Assert::assertFileExists() to also work with
|
||||
* file entities.
|
||||
*
|
||||
* @param \Drupal\File\FileInterface|string $file
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\file\Tests;
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
|
@ -29,7 +29,7 @@ class FileFieldValidateTest extends FileFieldTestBase {
|
|||
// Try to post a new node without uploading a file.
|
||||
$edit = [];
|
||||
$edit['title[0][value]'] = $this->randomMachineName();
|
||||
$this->drupalPostForm('node/add/' . $type_name, $edit, t('Save and publish'));
|
||||
$this->drupalPostForm('node/add/' . $type_name, $edit, t('Save'));
|
||||
$this->assertRaw(t('@title field is required.', ['@title' => $field->getLabel()]), 'Node save failed when required file field was empty.');
|
||||
|
||||
// Create a new node with the uploaded file.
|
||||
|
@ -50,7 +50,7 @@ class FileFieldValidateTest extends FileFieldTestBase {
|
|||
// Try to post a new node without uploading a file in the multivalue field.
|
||||
$edit = [];
|
||||
$edit['title[0][value]'] = $this->randomMachineName();
|
||||
$this->drupalPostForm('node/add/' . $type_name, $edit, t('Save and publish'));
|
||||
$this->drupalPostForm('node/add/' . $type_name, $edit, t('Save'));
|
||||
$this->assertRaw(t('@title field is required.', ['@title' => $field->getLabel()]), 'Node save failed when required multiple value file field was empty.');
|
||||
|
||||
// Create a new node with the uploaded file into the multivalue field.
|
||||
|
@ -71,8 +71,10 @@ class FileFieldValidateTest extends FileFieldTestBase {
|
|||
$field_name = strtolower($this->randomMachineName());
|
||||
$this->createFileField($field_name, 'node', $type_name, [], ['required' => '1']);
|
||||
|
||||
$small_file = $this->getTestFile('text', 131072); // 128KB.
|
||||
$large_file = $this->getTestFile('text', 1310720); // 1.2MB
|
||||
// 128KB.
|
||||
$small_file = $this->getTestFile('text', 131072);
|
||||
// 1.2MB
|
||||
$large_file = $this->getTestFile('text', 1310720);
|
||||
|
||||
// Test uploading both a large and small file with different increments.
|
||||
$sizes = [
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\file\Tests;
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\file\Entity\File;
|
||||
|
@ -30,6 +30,11 @@ class FileListingTest extends FileFieldTestBase {
|
|||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// This test expects unused managed files to be marked as a temporary file.
|
||||
$this->config('file.settings')
|
||||
->set('make_unused_managed_files_temporary', TRUE)
|
||||
->save();
|
||||
|
||||
$this->adminUser = $this->drupalCreateUser(['access files overview', 'bypass node access']);
|
||||
$this->baseUser = $this->drupalCreateUser();
|
||||
$this->createFileField('file', 'node', 'article', [], ['file_extensions' => 'txt png']);
|
||||
|
@ -91,7 +96,7 @@ class FileListingTest extends FileFieldTestBase {
|
|||
$file = $this->getTestFile('image');
|
||||
|
||||
$edit = [
|
||||
'files[file_0]' => drupal_realpath($file->getFileUri()),
|
||||
'files[file_0]' => \Drupal::service('file_system')->realpath($file->getFileUri()),
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, t('Save'));
|
||||
$node = Node::load($node->id());
|
||||
|
@ -105,7 +110,7 @@ class FileListingTest extends FileFieldTestBase {
|
|||
$this->assertLinkByHref(file_create_url($file->getFileUri()));
|
||||
$this->assertLinkByHref('admin/content/files/usage/' . $file->id());
|
||||
}
|
||||
$this->assertFalse(preg_match('/views-field-status priority-low\">\s*' . t('Temporary') . '/', $this->getRawContent()), 'All files are stored as permanent.');
|
||||
$this->assertFalse(preg_match('/views-field-status priority-low\">\s*' . t('Temporary') . '/', $this->getSession()->getPage()->getContent()), 'All files are stored as permanent.');
|
||||
|
||||
// Use one file two times and check usage information.
|
||||
$orphaned_file = $nodes[1]->file->target_id;
|
||||
|
@ -122,7 +127,7 @@ class FileListingTest extends FileFieldTestBase {
|
|||
$usage = $this->sumUsages($file_usage->listUsage($file));
|
||||
$this->assertRaw('admin/content/files/usage/' . $file->id() . '">' . $usage);
|
||||
|
||||
$result = $this->xpath("//td[contains(@class, 'views-field-status') and contains(text(), :value)]", [':value' => t('Temporary')]);
|
||||
$result = $this->xpath("//td[contains(@class, 'views-field-status') and contains(text(), :value)]", [':value' => 'Temporary']);
|
||||
$this->assertEqual(1, count($result), 'Unused file marked as temporary.');
|
||||
|
||||
// Test file usage page.
|
|
@ -3,6 +3,7 @@
|
|||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\user\Entity\Role;
|
||||
|
||||
/**
|
||||
* Tests access to managed files.
|
||||
|
@ -11,6 +12,19 @@ use Drupal\file\Entity\File;
|
|||
*/
|
||||
class FileManagedAccessTest extends FileManagedTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Give anonymous users permission to access content, so they can view and
|
||||
// download public files.
|
||||
$anonymous_role = Role::load(Role::ANONYMOUS_ID);
|
||||
$anonymous_role->grantPermission('access content');
|
||||
$anonymous_role->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if public file is always accessible.
|
||||
*/
|
||||
|
@ -29,7 +43,7 @@ class FileManagedAccessTest extends FileManagedTestBase {
|
|||
$file->save();
|
||||
|
||||
// Create authenticated user to check file access.
|
||||
$account = $this->createUser(['access site reports']);
|
||||
$account = $this->createUser(['access site reports', 'access content']);
|
||||
|
||||
$this->assertTrue($file->access('view', $account), 'Public file is viewable to authenticated user');
|
||||
$this->assertTrue($file->access('download', $account), 'Public file is downloadable to authenticated user');
|
||||
|
@ -54,7 +68,7 @@ class FileManagedAccessTest extends FileManagedTestBase {
|
|||
$file->save();
|
||||
|
||||
// Create authenticated user to check file access.
|
||||
$account = $this->createUser(['access site reports']);
|
||||
$account = $this->createUser(['access site reports', 'access content']);
|
||||
|
||||
$this->assertFalse($file->access('view', $account), 'Private file is not viewable to authenticated user');
|
||||
$this->assertFalse($file->access('download', $account), 'Private file is not downloadable to authenticated user');
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\file\Tests;
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
/**
|
||||
* Tests the 'managed_file' element type.
|
||||
|
@ -10,6 +10,7 @@ namespace Drupal\file\Tests;
|
|||
* that aren't related to fields into it.
|
||||
*/
|
||||
class FileManagedFileElementTest extends FileFieldTestBase {
|
||||
|
||||
/**
|
||||
* Tests the managed_file element type.
|
||||
*/
|
||||
|
@ -35,18 +36,20 @@ class FileManagedFileElementTest extends FileFieldTestBase {
|
|||
// Submit with a file, but with an invalid form token. Ensure the file
|
||||
// was not saved.
|
||||
$last_fid_prior = $this->getLastFileId();
|
||||
$this->drupalGet($path);
|
||||
$form_token_field = $this->assertSession()->hiddenFieldExists('form_token');
|
||||
$form_token_field->setValue('invalid token');
|
||||
$edit = [
|
||||
$file_field_name => drupal_realpath($test_file->getFileUri()),
|
||||
'form_token' => 'invalid token',
|
||||
$file_field_name => \Drupal::service('file_system')->realpath($test_file->getFileUri()),
|
||||
];
|
||||
$this->drupalPostForm($path, $edit, t('Save'));
|
||||
$this->drupalPostForm(NULL, $edit, t('Save'));
|
||||
$this->assertText('The form has become outdated. Copy any unsaved work in the form below');
|
||||
$last_fid = $this->getLastFileId();
|
||||
$this->assertEqual($last_fid_prior, $last_fid, 'File was not saved when uploaded with an invalid form token.');
|
||||
|
||||
// Submit a new file, without using the Upload button.
|
||||
$last_fid_prior = $this->getLastFileId();
|
||||
$edit = [$file_field_name => drupal_realpath($test_file->getFileUri())];
|
||||
$edit = [$file_field_name => \Drupal::service('file_system')->realpath($test_file->getFileUri())];
|
||||
$this->drupalPostForm($path, $edit, t('Save'));
|
||||
$last_fid = $this->getLastFileId();
|
||||
$this->assertTrue($last_fid > $last_fid_prior, 'New file got saved.');
|
||||
|
@ -56,71 +59,48 @@ class FileManagedFileElementTest extends FileFieldTestBase {
|
|||
$this->drupalPostForm($path . '/' . $last_fid, [], t('Save'));
|
||||
$this->assertRaw(t('The file ids are %fids.', ['%fids' => implode(',', [$last_fid])]), 'Empty submission did not change an existing file.');
|
||||
|
||||
// Now, test the Upload and Remove buttons, with and without Ajax.
|
||||
foreach ([FALSE, TRUE] as $ajax) {
|
||||
// Upload, then Submit.
|
||||
$last_fid_prior = $this->getLastFileId();
|
||||
$this->drupalGet($path);
|
||||
$edit = [$file_field_name => drupal_realpath($test_file->getFileUri())];
|
||||
if ($ajax) {
|
||||
$this->drupalPostAjaxForm(NULL, $edit, $input_base_name . '_upload_button');
|
||||
}
|
||||
else {
|
||||
$this->drupalPostForm(NULL, $edit, t('Upload'));
|
||||
}
|
||||
$last_fid = $this->getLastFileId();
|
||||
$this->assertTrue($last_fid > $last_fid_prior, 'New file got uploaded.');
|
||||
$this->drupalPostForm(NULL, [], t('Save'));
|
||||
$this->assertRaw(t('The file ids are %fids.', ['%fids' => implode(',', [$last_fid])]), 'Submit handler has correct file info.');
|
||||
// Upload, then Submit.
|
||||
$last_fid_prior = $this->getLastFileId();
|
||||
$this->drupalGet($path);
|
||||
$edit = [$file_field_name => \Drupal::service('file_system')->realpath($test_file->getFileUri())];
|
||||
$this->drupalPostForm(NULL, $edit, t('Upload'));
|
||||
$last_fid = $this->getLastFileId();
|
||||
$this->assertTrue($last_fid > $last_fid_prior, 'New file got uploaded.');
|
||||
$this->drupalPostForm(NULL, [], t('Save'));
|
||||
$this->assertRaw(t('The file ids are %fids.', ['%fids' => implode(',', [$last_fid])]), 'Submit handler has correct file info.');
|
||||
|
||||
// Remove, then Submit.
|
||||
$remove_button_title = $multiple ? t('Remove selected') : t('Remove');
|
||||
$remove_edit = [];
|
||||
if ($multiple) {
|
||||
$selected_checkbox = ($tree ? 'nested[file]' : 'file') . '[file_' . $last_fid . '][selected]';
|
||||
$remove_edit = [$selected_checkbox => '1'];
|
||||
}
|
||||
$this->drupalGet($path . '/' . $last_fid);
|
||||
if ($ajax) {
|
||||
$this->drupalPostAjaxForm(NULL, $remove_edit, $input_base_name . '_remove_button');
|
||||
}
|
||||
else {
|
||||
$this->drupalPostForm(NULL, $remove_edit, $remove_button_title);
|
||||
}
|
||||
$this->drupalPostForm(NULL, [], t('Save'));
|
||||
$this->assertRaw(t('The file ids are %fids.', ['%fids' => '']), 'Submission after file removal was successful.');
|
||||
|
||||
// Upload, then Remove, then Submit.
|
||||
$this->drupalGet($path);
|
||||
$edit = [$file_field_name => drupal_realpath($test_file->getFileUri())];
|
||||
if ($ajax) {
|
||||
$this->drupalPostAjaxForm(NULL, $edit, $input_base_name . '_upload_button');
|
||||
}
|
||||
else {
|
||||
$this->drupalPostForm(NULL, $edit, t('Upload'));
|
||||
}
|
||||
$remove_edit = [];
|
||||
if ($multiple) {
|
||||
$selected_checkbox = ($tree ? 'nested[file]' : 'file') . '[file_' . $this->getLastFileId() . '][selected]';
|
||||
$remove_edit = [$selected_checkbox => '1'];
|
||||
}
|
||||
if ($ajax) {
|
||||
$this->drupalPostAjaxForm(NULL, $remove_edit, $input_base_name . '_remove_button');
|
||||
}
|
||||
else {
|
||||
$this->drupalPostForm(NULL, $remove_edit, $remove_button_title);
|
||||
}
|
||||
|
||||
$this->drupalPostForm(NULL, [], t('Save'));
|
||||
$this->assertRaw(t('The file ids are %fids.', ['%fids' => '']), 'Submission after file upload and removal was successful.');
|
||||
// Remove, then Submit.
|
||||
$remove_button_title = $multiple ? t('Remove selected') : t('Remove');
|
||||
$remove_edit = [];
|
||||
if ($multiple) {
|
||||
$selected_checkbox = ($tree ? 'nested[file]' : 'file') . '[file_' . $last_fid . '][selected]';
|
||||
$remove_edit = [$selected_checkbox => '1'];
|
||||
}
|
||||
$this->drupalGet($path . '/' . $last_fid);
|
||||
$this->drupalPostForm(NULL, $remove_edit, $remove_button_title);
|
||||
$this->drupalPostForm(NULL, [], t('Save'));
|
||||
$this->assertRaw(t('The file ids are %fids.', ['%fids' => '']), 'Submission after file removal was successful.');
|
||||
|
||||
// Upload, then Remove, then Submit.
|
||||
$this->drupalGet($path);
|
||||
$edit = [$file_field_name => \Drupal::service('file_system')->realpath($test_file->getFileUri())];
|
||||
$this->drupalPostForm(NULL, $edit, t('Upload'));
|
||||
$remove_edit = [];
|
||||
if ($multiple) {
|
||||
$selected_checkbox = ($tree ? 'nested[file]' : 'file') . '[file_' . $this->getLastFileId() . '][selected]';
|
||||
$remove_edit = [$selected_checkbox => '1'];
|
||||
}
|
||||
$this->drupalPostForm(NULL, $remove_edit, $remove_button_title);
|
||||
|
||||
$this->drupalPostForm(NULL, [], t('Save'));
|
||||
$this->assertRaw(t('The file ids are %fids.', ['%fids' => '']), 'Submission after file upload and removal was successful.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The multiple file upload has additional conditions that need checking.
|
||||
$path = 'file/test/1/1/1';
|
||||
$edit = ['files[nested_file][]' => drupal_realpath($test_file->getFileUri())];
|
||||
$edit = ['files[nested_file][]' => \Drupal::service('file_system')->realpath($test_file->getFileUri())];
|
||||
$fid_list = [];
|
||||
|
||||
$this->drupalGet($path);
|
||||
|
@ -158,7 +138,7 @@ class FileManagedFileElementTest extends FileFieldTestBase {
|
|||
$test_file = $this->getTestFile('text');
|
||||
$file_field_name = 'files[nested_file][]';
|
||||
|
||||
$edit = [$file_field_name => drupal_realpath($test_file->getFileUri())];
|
||||
$edit = [$file_field_name => \Drupal::service('file_system')->realpath($test_file->getFileUri())];
|
||||
$this->drupalPostForm(NULL, $edit, t('Upload'));
|
||||
|
||||
$fid = $this->getLastFileId();
|
||||
|
@ -179,7 +159,7 @@ class FileManagedFileElementTest extends FileFieldTestBase {
|
|||
$test_file = $this->getTestFile('text');
|
||||
$file_field_name = 'files[nested_file][]';
|
||||
|
||||
$edit = [$file_field_name => drupal_realpath($test_file->getFileUri())];
|
||||
$edit = [$file_field_name => \Drupal::service('file_system')->realpath($test_file->getFileUri())];
|
||||
$this->drupalPostForm(NULL, $edit, t('Upload'));
|
||||
$this->drupalPostForm(NULL, [], t('Save'));
|
||||
|
||||
|
@ -194,4 +174,55 @@ class FileManagedFileElementTest extends FileFieldTestBase {
|
|||
$file->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that unused permanent files can be used.
|
||||
*/
|
||||
public function testUnusedPermanentFileValidation() {
|
||||
|
||||
// Create a permanent file without usages.
|
||||
$file = $this->getTestFile('image');
|
||||
$file->setPermanent();
|
||||
$file->save();
|
||||
|
||||
// By default, unused files are no longer marked temporary, and it must be
|
||||
// allowed to reference an unused file.
|
||||
$this->drupalGet('file/test/1/0/1/' . $file->id());
|
||||
$this->drupalPostForm(NULL, [], 'Save');
|
||||
$this->assertNoText('The file used in the Managed file & butter field may not be referenced.');
|
||||
$this->assertText('The file ids are ' . $file->id());
|
||||
|
||||
// Enable marking unused files as temporary, unused permanent files must not
|
||||
// be referenced now.
|
||||
$this->config('file.settings')
|
||||
->set('make_unused_managed_files_temporary', TRUE)
|
||||
->save();
|
||||
$this->drupalGet('file/test/1/0/1/' . $file->id());
|
||||
$this->drupalPostForm(NULL, [], 'Save');
|
||||
$this->assertText('The file used in the Managed file & butter field may not be referenced.');
|
||||
$this->assertNoText('The file ids are ' . $file->id());
|
||||
|
||||
// Make the file temporary, now using it is allowed.
|
||||
$file->setTemporary();
|
||||
$file->save();
|
||||
|
||||
$this->drupalGet('file/test/1/0/1/' . $file->id());
|
||||
$this->drupalPostForm(NULL, [], 'Save');
|
||||
$this->assertNoText('The file used in the Managed file & butter field may not be referenced.');
|
||||
$this->assertText('The file ids are ' . $file->id());
|
||||
|
||||
// Make the file permanent again and add a usage from itself, referencing is
|
||||
// still allowed.
|
||||
$file->setPermanent();
|
||||
$file->save();
|
||||
|
||||
/** @var \Drupal\file\FileUsage\FileUsageInterface $file_usage */
|
||||
$file_usage = \Drupal::service('file.usage');
|
||||
$file_usage->add($file, 'file', 'file', $file->id());
|
||||
|
||||
$this->drupalGet('file/test/1/0/1/' . $file->id());
|
||||
$this->drupalPostForm(NULL, [], 'Save');
|
||||
$this->assertNoText('The file used in the Managed file & butter field may not be referenced.');
|
||||
$this->assertText('The file ids are ' . $file->id());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\file\Tests;
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
use Drupal\file\Entity\File;
|
||||
|
||||
|
@ -29,6 +29,11 @@ class FileOnTranslatedEntityTest extends FileFieldTestBase {
|
|||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// This test expects unused managed files to be marked as temporary a file.
|
||||
$this->config('file.settings')
|
||||
->set('make_unused_managed_files_temporary', TRUE)
|
||||
->save();
|
||||
|
||||
// Create the "Basic page" node type.
|
||||
// @todo Remove the disabling of new revision creation in
|
||||
// https://www.drupal.org/node/1239558.
|
||||
|
@ -86,7 +91,7 @@ class FileOnTranslatedEntityTest extends FileFieldTestBase {
|
|||
// Edit the node to upload a file.
|
||||
$edit = [];
|
||||
$name = 'files[' . $this->fieldName . '_0]';
|
||||
$edit[$name] = drupal_realpath($this->drupalGetTestFiles('text')[0]->uri);
|
||||
$edit[$name] = \Drupal::service('file_system')->realpath($this->drupalGetTestFiles('text')[0]->uri);
|
||||
$this->drupalPostForm('node/' . $default_language_node->id() . '/edit', $edit, t('Save'));
|
||||
$first_fid = $this->getLastFileId();
|
||||
|
||||
|
@ -97,7 +102,7 @@ class FileOnTranslatedEntityTest extends FileFieldTestBase {
|
|||
$edit = [];
|
||||
$edit['title[0][value]'] = 'Bill Murray';
|
||||
$name = 'files[' . $this->fieldName . '_0]';
|
||||
$edit[$name] = drupal_realpath($this->drupalGetTestFiles('text')[1]->uri);
|
||||
$edit[$name] = \Drupal::service('file_system')->realpath($this->drupalGetTestFiles('text')[1]->uri);
|
||||
$this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
|
||||
// This inspects the HTML after the post of the translation, the file
|
||||
// should be displayed on the original node.
|
||||
|
@ -123,7 +128,7 @@ class FileOnTranslatedEntityTest extends FileFieldTestBase {
|
|||
$edit = [];
|
||||
$edit['title[0][value]'] = 'Scarlett Johansson';
|
||||
$name = 'files[' . $this->fieldName . '_0]';
|
||||
$edit[$name] = drupal_realpath($this->drupalGetTestFiles('text')[2]->uri);
|
||||
$edit[$name] = \Drupal::service('file_system')->realpath($this->drupalGetTestFiles('text')[2]->uri);
|
||||
$this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
|
||||
$third_fid = $this->getLastFileId();
|
||||
|
||||
|
@ -151,7 +156,7 @@ class FileOnTranslatedEntityTest extends FileFieldTestBase {
|
|||
$edit = [];
|
||||
$edit['title[0][value]'] = 'David Bowie';
|
||||
$name = 'files[' . $this->fieldName . '_0]';
|
||||
$edit[$name] = drupal_realpath($this->drupalGetTestFiles('text')[3]->uri);
|
||||
$edit[$name] = \Drupal::service('file_system')->realpath($this->drupalGetTestFiles('text')[3]->uri);
|
||||
$this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
|
||||
$replaced_second_fid = $this->getLastFileId();
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\file\Tests;
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
use Drupal\Core\Entity\Plugin\Validation\Constraint\ReferenceAccessConstraint;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Component\Render\FormattableMarkup;
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\user\RoleInterface;
|
||||
|
@ -27,6 +27,10 @@ class FilePrivateTest extends FileFieldTestBase {
|
|||
node_access_test_add_field(NodeType::load('article'));
|
||||
node_access_rebuild();
|
||||
\Drupal::state()->set('node_access_test.private', TRUE);
|
||||
// This test expects unused managed files to be marked as a temporary file.
|
||||
$this->config('file.settings')
|
||||
->set('make_unused_managed_files_temporary', TRUE)
|
||||
->save();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -34,6 +38,8 @@ class FilePrivateTest extends FileFieldTestBase {
|
|||
*/
|
||||
public function testPrivateFile() {
|
||||
$node_storage = $this->container->get('entity.manager')->getStorage('node');
|
||||
/** @var \Drupal\Core\File\FileSystemInterface $file_system */
|
||||
$file_system = \Drupal::service('file_system');
|
||||
$type_name = 'article';
|
||||
$field_name = strtolower($this->randomMachineName());
|
||||
$this->createFileField($field_name, 'node', $type_name, ['uri_scheme' => 'private']);
|
||||
|
@ -73,25 +79,31 @@ class FilePrivateTest extends FileFieldTestBase {
|
|||
// Attempt to reuse the file when editing a node.
|
||||
$edit = [];
|
||||
$edit['title[0][value]'] = $this->randomMachineName();
|
||||
$this->drupalPostForm('node/add/' . $type_name, $edit, t('Save and publish'));
|
||||
$this->drupalPostForm('node/add/' . $type_name, $edit, t('Save'));
|
||||
$new_node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
|
||||
$edit[$field_name . '[0][fids]'] = $node_file->id();
|
||||
$this->drupalPostForm('node/' . $new_node->id() . '/edit', $edit, t('Save and keep published'));
|
||||
|
||||
// Can't use drupalPostForm() to set hidden fields.
|
||||
$this->drupalGet('node/' . $new_node->id() . '/edit');
|
||||
$this->getSession()->getPage()->find('css', 'input[name="' . $field_name . '[0][fids]"]')->setValue($node_file->id());
|
||||
$this->getSession()->getPage()->pressButton(t('Save'));
|
||||
// Make sure the form submit failed - we stayed on the edit form.
|
||||
$this->assertUrl('node/' . $new_node->id() . '/edit');
|
||||
// Check that we got the expected constraint form error.
|
||||
$constraint = new ReferenceAccessConstraint();
|
||||
$this->assertRaw(SafeMarkup::format($constraint->message, ['%type' => 'file', '%id' => $node_file->id()]));
|
||||
$this->assertRaw(new FormattableMarkup($constraint->message, ['%type' => 'file', '%id' => $node_file->id()]));
|
||||
// Attempt to reuse the existing file when creating a new node, and confirm
|
||||
// that access is still denied.
|
||||
$edit = [];
|
||||
$edit['title[0][value]'] = $this->randomMachineName();
|
||||
$edit[$field_name . '[0][fids]'] = $node_file->id();
|
||||
$this->drupalPostForm('node/add/' . $type_name, $edit, t('Save and publish'));
|
||||
// Can't use drupalPostForm() to set hidden fields.
|
||||
$this->drupalGet('node/add/' . $type_name);
|
||||
$this->getSession()->getPage()->find('css', 'input[name="title[0][value]"]')->setValue($edit['title[0][value]']);
|
||||
$this->getSession()->getPage()->find('css', 'input[name="' . $field_name . '[0][fids]"]')->setValue($node_file->id());
|
||||
$this->getSession()->getPage()->pressButton(t('Save'));
|
||||
$new_node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
|
||||
$this->assertTrue(empty($new_node), 'Node was not created.');
|
||||
$this->assertUrl('node/add/' . $type_name);
|
||||
$this->assertRaw(SafeMarkup::format($constraint->message, ['%type' => 'file', '%id' => $node_file->id()]));
|
||||
$this->assertRaw(new FormattableMarkup($constraint->message, ['%type' => 'file', '%id' => $node_file->id()]));
|
||||
|
||||
// Now make file_test_file_download() return everything.
|
||||
\Drupal::state()->set('file_test.allow_all', TRUE);
|
||||
|
@ -124,7 +136,7 @@ class FilePrivateTest extends FileFieldTestBase {
|
|||
);
|
||||
$test_file = $this->getTestFile('text');
|
||||
$this->drupalGet('node/add/' . $type_name);
|
||||
$edit = ['files[' . $field_name . '_0]' => drupal_realpath($test_file->getFileUri())];
|
||||
$edit = ['files[' . $field_name . '_0]' => $file_system->realpath($test_file->getFileUri())];
|
||||
$this->drupalPostForm(NULL, $edit, t('Upload'));
|
||||
/** @var \Drupal\file\FileStorageInterface $file_storage */
|
||||
$file_storage = $this->container->get('entity.manager')->getStorage('file');
|
||||
|
@ -138,9 +150,7 @@ class FilePrivateTest extends FileFieldTestBase {
|
|||
$this->drupalGet($file_url);
|
||||
$this->assertResponse(200, 'Confirmed that the anonymous uploader has access to the temporary file.');
|
||||
// Close the prior connection and remove the session cookie.
|
||||
$this->curlClose();
|
||||
$this->curlCookies = [];
|
||||
$this->cookies = [];
|
||||
$this->getSession()->reset();
|
||||
$this->drupalGet($file_url);
|
||||
$this->assertResponse(403, 'Confirmed that another anonymous user cannot access the temporary file.');
|
||||
|
||||
|
@ -151,7 +161,7 @@ class FilePrivateTest extends FileFieldTestBase {
|
|||
$this->drupalGet('node/add/' . $type_name);
|
||||
$edit = [];
|
||||
$edit['title[0][value]'] = $this->randomMachineName();
|
||||
$edit['files[' . $field_name . '_0]'] = drupal_realpath($test_file->getFileUri());
|
||||
$edit['files[' . $field_name . '_0]'] = $file_system->realpath($test_file->getFileUri());
|
||||
$this->drupalPostForm(NULL, $edit, t('Save'));
|
||||
$new_node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
|
||||
$file_id = $new_node->{$field_name}->target_id;
|
||||
|
@ -168,9 +178,7 @@ class FilePrivateTest extends FileFieldTestBase {
|
|||
$this->drupalGet($file_url);
|
||||
$this->assertResponse(200, 'Confirmed that the anonymous uploader has access to the file whose references were removed.');
|
||||
// Close the prior connection and remove the session cookie.
|
||||
$this->curlClose();
|
||||
$this->curlCookies = [];
|
||||
$this->cookies = [];
|
||||
$this->getSession()->reset();
|
||||
$this->drupalGet($file_url);
|
||||
$this->assertResponse(403, 'Confirmed that another anonymous user cannot access the file whose references were removed.');
|
||||
|
||||
|
@ -180,7 +188,7 @@ class FilePrivateTest extends FileFieldTestBase {
|
|||
$this->drupalGet('node/add/' . $type_name);
|
||||
$edit = [];
|
||||
$edit['title[0][value]'] = $this->randomMachineName();
|
||||
$edit['files[' . $field_name . '_0]'] = drupal_realpath($test_file->getFileUri());
|
||||
$edit['files[' . $field_name . '_0]'] = $file_system->realpath($test_file->getFileUri());
|
||||
$this->drupalPostForm(NULL, $edit, t('Save'));
|
||||
$new_node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
|
||||
$file = File::load($new_node->{$field_name}->target_id);
|
||||
|
@ -191,9 +199,7 @@ class FilePrivateTest extends FileFieldTestBase {
|
|||
$this->drupalGet($file_url);
|
||||
$this->assertResponse(200, 'Confirmed that the anonymous uploader has access to the permanent file that is referenced by a published node.');
|
||||
// Close the prior connection and remove the session cookie.
|
||||
$this->curlClose();
|
||||
$this->curlCookies = [];
|
||||
$this->cookies = [];
|
||||
$this->getSession()->reset();
|
||||
$this->drupalGet($file_url);
|
||||
$this->assertResponse(200, 'Confirmed that another anonymous user also has access to the permanent file that is referenced by a published node.');
|
||||
|
||||
|
@ -205,10 +211,10 @@ class FilePrivateTest extends FileFieldTestBase {
|
|||
$this->drupalGet('node/add/' . $type_name);
|
||||
$edit = [];
|
||||
$edit['title[0][value]'] = $this->randomMachineName();
|
||||
$edit['files[' . $field_name . '_0]'] = drupal_realpath($test_file->getFileUri());
|
||||
$edit['files[' . $field_name . '_0]'] = $file_system->realpath($test_file->getFileUri());
|
||||
$this->drupalPostForm(NULL, $edit, t('Save'));
|
||||
$new_node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
|
||||
$new_node->setPublished(FALSE);
|
||||
$new_node->setUnpublished();
|
||||
$new_node->save();
|
||||
$file = File::load($new_node->{$field_name}->target_id);
|
||||
$this->assertTrue($file->isPermanent(), 'File is permanent.');
|
||||
|
@ -218,9 +224,7 @@ class FilePrivateTest extends FileFieldTestBase {
|
|||
$this->drupalGet($file_url);
|
||||
$this->assertResponse(403, 'Confirmed that the anonymous uploader cannot access the permanent file when it is referenced by an unpublished node.');
|
||||
// Close the prior connection and remove the session cookie.
|
||||
$this->curlClose();
|
||||
$this->curlCookies = [];
|
||||
$this->cookies = [];
|
||||
$this->getSession()->reset();
|
||||
$this->drupalGet($file_url);
|
||||
$this->assertResponse(403, 'Confirmed that another anonymous user cannot access the permanent file when it is referenced by an unpublished node.');
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\file\Tests;
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
|
@ -13,6 +13,7 @@ use Drupal\file\Entity\File;
|
|||
* @group file
|
||||
*/
|
||||
class FileTokenReplaceTest extends FileFieldTestBase {
|
||||
|
||||
/**
|
||||
* Creates a file, then tests the tokens generated from it.
|
||||
*/
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\FileUploadResourceTestBase;
|
||||
|
||||
/**
|
||||
* @group file
|
||||
*/
|
||||
class FileUploadJsonBasicAuthTest extends FileUploadResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\file\Functional;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\FileUploadResourceTestBase;
|
||||
|
||||
/**
|
||||
* @group file
|
||||
*/
|
||||
class FileUploadJsonCookieTest extends FileUploadResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\file\Functional\Formatter;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\file\Entity\File;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\file\Plugin\Field\FieldFormatter\FileAudioFormatter
|
||||
* @group file
|
||||
*/
|
||||
class FileAudioFormatterTest extends FileMediaFormatterTestBase {
|
||||
|
||||
/**
|
||||
* @covers ::viewElements
|
||||
*
|
||||
* @dataProvider dataProvider
|
||||
*/
|
||||
public function testRender($tag_count, $formatter_settings) {
|
||||
$field_config = $this->createMediaField('file_audio', 'mp3', $formatter_settings);
|
||||
|
||||
file_put_contents('public://file.mp3', str_repeat('t', 10));
|
||||
$file1 = File::create([
|
||||
'uri' => 'public://file.mp3',
|
||||
'filename' => 'file.mp3',
|
||||
]);
|
||||
$file1->save();
|
||||
|
||||
$file2 = File::create([
|
||||
'uri' => 'public://file.mp3',
|
||||
'filename' => 'file.mp3',
|
||||
]);
|
||||
$file2->save();
|
||||
|
||||
$entity = EntityTest::create([
|
||||
$field_config->getName() => [
|
||||
[
|
||||
'target_id' => $file1->id(),
|
||||
],
|
||||
[
|
||||
'target_id' => $file2->id(),
|
||||
],
|
||||
],
|
||||
]);
|
||||
$entity->save();
|
||||
|
||||
$this->drupalGet($entity->toUrl());
|
||||
|
||||
$file1_url = file_url_transform_relative(file_create_url($file1->getFileUri()));
|
||||
$file2_url = file_url_transform_relative(file_create_url($file2->getFileUri()));
|
||||
|
||||
$assert_session = $this->assertSession();
|
||||
$assert_session->elementsCount('css', 'audio[controls="controls"]', $tag_count);
|
||||
$assert_session->elementExists('css', "audio > source[src='$file1_url'][type='audio/mpeg']");
|
||||
$assert_session->elementExists('css', "audio > source[src='$file2_url'][type='audio/mpeg']");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\file\Functional\Formatter;
|
||||
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Provides methods specifically for testing File module's media formatter's.
|
||||
*/
|
||||
abstract class FileMediaFormatterTestBase extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'entity_test',
|
||||
'field',
|
||||
'file',
|
||||
'user',
|
||||
'system',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->drupalLogin($this->drupalCreateUser(['view test entity']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a file field and set's the correct formatter.
|
||||
*
|
||||
* @param string $formatter
|
||||
* The formatter ID.
|
||||
* @param string $file_extensions
|
||||
* The file extensions of the new field.
|
||||
* @param array $formatter_settings
|
||||
* Settings for the formatter.
|
||||
*
|
||||
* @return \Drupal\field\Entity\FieldConfig
|
||||
* Newly created file field.
|
||||
*/
|
||||
protected function createMediaField($formatter, $file_extensions, array $formatter_settings = []) {
|
||||
$entity_type = $bundle = 'entity_test';
|
||||
$field_name = mb_strtolower($this->randomMachineName());
|
||||
|
||||
FieldStorageConfig::create([
|
||||
'entity_type' => $entity_type,
|
||||
'field_name' => $field_name,
|
||||
'type' => 'file',
|
||||
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
|
||||
])->save();
|
||||
$field_config = FieldConfig::create([
|
||||
'entity_type' => $entity_type,
|
||||
'field_name' => $field_name,
|
||||
'bundle' => $bundle,
|
||||
'settings' => [
|
||||
'file_extensions' => trim($file_extensions),
|
||||
],
|
||||
]);
|
||||
$field_config->save();
|
||||
|
||||
$display = entity_get_display('entity_test', 'entity_test', 'full');
|
||||
$display->setComponent($field_name, [
|
||||
'type' => $formatter,
|
||||
'settings' => $formatter_settings,
|
||||
])->save();
|
||||
|
||||
return $field_config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testRender.
|
||||
*
|
||||
* @return array
|
||||
* An array of data arrays.
|
||||
* The data array contains:
|
||||
* - The number of expected HTML tags.
|
||||
* - An array of settings for the field formatter.
|
||||
*/
|
||||
public function dataProvider() {
|
||||
return [
|
||||
[2, []],
|
||||
[1, ['multiple_file_display_type' => 'sources']],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\file\Functional\Formatter;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\file\Entity\File;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\file\Plugin\Field\FieldFormatter\FileVideoFormatter
|
||||
* @group file
|
||||
*/
|
||||
class FileVideoFormatterTest extends FileMediaFormatterTestBase {
|
||||
|
||||
/**
|
||||
* @covers ::viewElements
|
||||
*
|
||||
* @dataProvider dataProvider
|
||||
*/
|
||||
public function testRender($tag_count, $formatter_settings) {
|
||||
$field_config = $this->createMediaField('file_video', 'mp4', $formatter_settings);
|
||||
|
||||
file_put_contents('public://file.mp4', str_repeat('t', 10));
|
||||
$file1 = File::create([
|
||||
'uri' => 'public://file.mp4',
|
||||
'filename' => 'file.mp4',
|
||||
]);
|
||||
$file1->save();
|
||||
|
||||
$file2 = File::create([
|
||||
'uri' => 'public://file.mp4',
|
||||
'filename' => 'file.mp4',
|
||||
]);
|
||||
$file2->save();
|
||||
|
||||
$entity = EntityTest::create([
|
||||
$field_config->getName() => [
|
||||
[
|
||||
'target_id' => $file1->id(),
|
||||
],
|
||||
[
|
||||
'target_id' => $file2->id(),
|
||||
],
|
||||
],
|
||||
]);
|
||||
$entity->save();
|
||||
|
||||
$this->drupalGet($entity->toUrl());
|
||||
|
||||
$file1_url = file_url_transform_relative(file_create_url($file1->getFileUri()));
|
||||
$file2_url = file_url_transform_relative(file_create_url($file2->getFileUri()));
|
||||
|
||||
$assert_session = $this->assertSession();
|
||||
$assert_session->elementsCount('css', 'video[controls="controls"]', $tag_count);
|
||||
$assert_session->elementExists('css', "video > source[src='$file1_url'][type='video/mp4']");
|
||||
$assert_session->elementExists('css', "video > source[src='$file2_url'][type='video/mp4']");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\file\Functional\Hal;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Tests\file\Functional\Rest\FileResourceTestBase;
|
||||
use Drupal\Tests\hal\Functional\EntityResource\HalEntityNormalizationTrait;
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group hal
|
||||
*/
|
||||
class FileHalJsonAnonTest extends FileResourceTestBase {
|
||||
|
||||
use HalEntityNormalizationTrait;
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['hal'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'hal_json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/hal+json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedNormalizedEntity() {
|
||||
$default_normalization = parent::getExpectedNormalizedEntity();
|
||||
|
||||
$normalization = $this->applyHalFieldNormalization($default_normalization);
|
||||
|
||||
$url = file_create_url($this->entity->getFileUri());
|
||||
// @see \Drupal\Tests\hal\Functional\EntityResource\File\FileHalJsonAnonTest::testGetBcUriField()
|
||||
if ($this->config('hal.settings')->get('bc_file_uri_as_url_normalizer')) {
|
||||
$normalization['uri'][0]['value'] = $url;
|
||||
}
|
||||
|
||||
$uid = $this->author->id();
|
||||
|
||||
return $normalization + [
|
||||
'_embedded' => [
|
||||
$this->baseUrl . '/rest/relation/file/file/uid' => [
|
||||
[
|
||||
'_links' => [
|
||||
'self' => [
|
||||
'href' => $this->baseUrl . "/user/$uid?_format=hal_json",
|
||||
],
|
||||
'type' => [
|
||||
'href' => $this->baseUrl . '/rest/type/user/user',
|
||||
],
|
||||
],
|
||||
'uuid' => [
|
||||
[
|
||||
'value' => $this->author->uuid(),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'_links' => [
|
||||
'self' => [
|
||||
'href' => $url,
|
||||
],
|
||||
'type' => [
|
||||
'href' => $this->baseUrl . '/rest/type/file/file',
|
||||
],
|
||||
$this->baseUrl . '/rest/relation/file/file/uid' => [
|
||||
[
|
||||
'href' => $this->baseUrl . "/user/$uid?_format=hal_json",
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNormalizedPostEntity() {
|
||||
return parent::getNormalizedPostEntity() + [
|
||||
'_links' => [
|
||||
'type' => [
|
||||
'href' => $this->baseUrl . '/rest/type/file/file',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedCacheTags() {
|
||||
return Cache::mergeTags(parent::getExpectedCacheTags(), ['config:hal.settings']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedCacheContexts() {
|
||||
return [
|
||||
'url.site',
|
||||
'user.permissions',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @see hal_update_8501()
|
||||
*/
|
||||
public function testGetBcUriField() {
|
||||
$this->config('hal.settings')->set('bc_file_uri_as_url_normalizer', TRUE)->save(TRUE);
|
||||
|
||||
$this->initAuthentication();
|
||||
$url = $this->getEntityResourceUrl();
|
||||
$url->setOption('query', ['_format' => static::$format]);
|
||||
$request_options = $this->getAuthenticationRequestOptions('GET');
|
||||
$this->provisionEntityResource();
|
||||
$this->setUpAuthorization('GET');
|
||||
$response = $this->request('GET', $url, $request_options);
|
||||
$expected = $this->getExpectedNormalizedEntity();
|
||||
static::recursiveKSort($expected);
|
||||
$actual = $this->serializer->decode((string) $response->getBody(), static::$format);
|
||||
static::recursiveKSort($actual);
|
||||
$this->assertSame($expected, $actual);
|
||||
|
||||
// Explicitly assert that $file->uri->value is an absolute file URL, unlike
|
||||
// the default normalization.
|
||||
$this->assertSame($this->baseUrl . '/' . $this->siteDirectory . '/files/drupal.txt', $actual['uri'][0]['value']);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\file\Functional\Hal;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group hal
|
||||
*/
|
||||
class FileHalJsonBasicAuthTest extends FileHalJsonAnonTest {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\file\Functional\Hal;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group hal
|
||||
*/
|
||||
class FileHalJsonCookieTest extends FileHalJsonAnonTest {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\file\Functional\Hal;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group hal
|
||||
*/
|
||||
class FileUploadHalJsonBasicAuthTest extends FileUploadHalJsonTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\file\Functional\Hal;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group hal
|
||||
*/
|
||||
class FileUploadHalJsonCookieTest extends FileUploadHalJsonTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue