Move into nested docroot

This commit is contained in:
Rob Davies 2017-02-13 15:31:17 +00:00
parent 83a0d3a149
commit c8b70abde9
13405 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,62 @@
<?php
namespace Drupal\file\Plugin\EntityReferenceSelection;
use Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection;
/**
* Provides specific access control for the file entity type.
*
* @EntityReferenceSelection(
* id = "default:file",
* label = @Translation("File selection"),
* entity_types = {"file"},
* group = "default",
* weight = 1
* )
*/
class FileSelection extends DefaultSelection {
/**
* {@inheritdoc}
*/
protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') {
$query = parent::buildEntityQuery($match, $match_operator);
// Allow referencing :
// - files with status "permanent"
// - or files uploaded by the current user (since newly uploaded files only
// become "permanent" after the containing entity gets validated and
// saved.)
$query->condition($query->orConditionGroup()
->condition('status', FILE_STATUS_PERMANENT)
->condition('uid', $this->currentUser->id()));
return $query;
}
/**
* {@inheritdoc}
*/
public function createNewEntity($entity_type_id, $bundle, $label, $uid) {
$file = parent::createNewEntity($entity_type_id, $bundle, $label, $uid);
// In order to create a referenceable file, it needs to have a "permanent"
// status.
/** @var \Drupal\file\FileInterface $file */
$file->setPermanent();
return $file;
}
/**
* {@inheritdoc}
*/
public function validateReferenceableNewEntities(array $entities) {
$entities = parent::validateReferenceableNewEntities($entities);
$entities = array_filter($entities, function ($file) {
/** @var \Drupal\file\FileInterface $file */
return $file->isPermanent() || $file->getOwnerId() === $this->currentUser->id();
});
return $entities;
}
}

View file

@ -0,0 +1,101 @@
<?php
namespace Drupal\file\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
/**
* Base class for file formatters, which allow to link to the file download URL.
*/
abstract class BaseFieldFileFormatterBase extends FormatterBase {
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
$settings['link_to_file'] = FALSE;
return $settings;
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$form = parent::settingsForm($form, $form_state);
$form['link_to_file'] = [
'#title' => $this->t('Link this field to the file download URL'),
'#type' => 'checkbox',
'#default_value' => $this->getSetting('link_to_file'),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = [];
$url = NULL;
// Add support to link to the entity itself.
if ($this->getSetting('link_to_file')) {
// @todo Wrap in file_url_transform_relative(). This is currently
// impossible. See below.
$url = file_create_url($items->getEntity()->uri->value);
}
foreach ($items as $delta => $item) {
$view_value = $this->viewValue($item);
if ($url) {
$elements[$delta] = [
'#type' => 'link',
'#title' => $view_value,
'#url' => Url::fromUri($url),
// @todo Remove the 'url.site' cache context by using a relative file
// URL (file_url_transform_relative()). This is currently impossible
// because #type => link requires a Url object, and Url objects do not
// support relative URLs: they require fully qualified URLs. Fix in
// https://www.drupal.org/node/2646744.
'#cache' => [
'contexts' => [
'url.site',
],
],
];
}
else {
$elements[$delta] = is_array($view_value) ? $view_value : ['#markup' => $view_value];
}
}
return $elements;
}
/**
* Generate the output appropriate for one field item.
*
* @param \Drupal\Core\Field\FieldItemInterface $item
* One field item.
*
* @return mixed
* The textual output generated.
*/
abstract protected function viewValue(FieldItemInterface $item);
/**
* {@inheritdoc}
*/
public static function isApplicable(FieldDefinitionInterface $field_definition) {
return $field_definition->getTargetEntityTypeId() === 'file';
}
}

View file

@ -0,0 +1,46 @@
<?php
namespace Drupal\file\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Form\FormStateInterface;
/**
* Formatter for a text field on a file entity that links the field to the file.
*
* @FieldFormatter(
* id = "file_link",
* label = @Translation("File link"),
* field_types = {
* "string"
* }
* )
*/
class DefaultFileFormatter extends BaseFieldFileFormatterBase {
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
$settings = parent::defaultSettings();
$settings['link_to_file'] = TRUE;
return $settings;
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
// We don't call the parent in order to bypass the link to file form.
return $form;
}
/**
* {@inheritdoc}
*/
protected function viewValue(FieldItemInterface $item) {
return $item->value;
}
}

View file

@ -0,0 +1,75 @@
<?php
namespace Drupal\file\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Form\FormStateInterface;
/**
* Formatter to render a filename as file extension.
*
* @FieldFormatter(
* id = "file_extension",
* label = @Translation("File extension"),
* field_types = {
* "string"
* }
* )
*/
class FileExtensionFormatter extends BaseFieldFileFormatterBase {
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
$settings = parent::defaultSettings();
$settings['extension_detect_tar'] = FALSE;
return $settings;
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$form = parent::settingsForm($form, $form_state);
$form['extension_detect_tar'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Include tar in extension'),
'#description' => $this->t("If the part of the filename just before the extension is '.tar', include this in the extension output."),
'#default_value' => $this->getSetting('extension_detect_tar'),
);
return $form;
}
/**
* {@inheritdoc}
*/
protected function viewValue(FieldItemInterface $item) {
$filename = $item->value;
if (!$this->getSetting('extension_detect_tar')) {
return pathinfo($filename, PATHINFO_EXTENSION);
}
else {
$file_parts = explode('.', basename($filename));
if (count($file_parts) > 1) {
$extension = array_pop($file_parts);
$last_part_in_name = array_pop($file_parts);
if ($last_part_in_name === 'tar') {
$extension = 'tar.' . $extension;
}
return $extension;
}
}
}
/**
* {@inheritdoc}
*/
public static function isApplicable(FieldDefinitionInterface $field_definition) {
// Just show this file extension formatter on the filename field.
return parent::isApplicable($field_definition) && $field_definition->getName() === 'filename';
}
}

View file

@ -0,0 +1,37 @@
<?php
namespace Drupal\file\Plugin\Field\FieldFormatter;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceFormatterBase;
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
/**
* Base class for file formatters.
*/
abstract class FileFormatterBase extends EntityReferenceFormatterBase {
/**
* {@inheritdoc}
*/
protected function needsEntityLoad(EntityReferenceItem $item) {
return parent::needsEntityLoad($item) && $item->isDisplayed();
}
/**
* {@inheritdoc}
*/
protected function checkAccess(EntityInterface $entity) {
// Only check access if the current file access control handler explicitly
// opts in by implementing FileAccessFormatterControlHandlerInterface.
$access_handler_class = $entity->getEntityType()->getHandlerClass('access');
if (is_subclass_of($access_handler_class, '\Drupal\file\FileAccessFormatterControlHandlerInterface')) {
return $entity->access('view', NULL, TRUE);
}
else {
return AccessResult::allowed();
}
}
}

View file

@ -0,0 +1,42 @@
<?php
namespace Drupal\file\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
/**
* Formatter that shows the file size in a human readable way.
*
* @FieldFormatter(
* id = "file_size",
* label = @Translation("File size"),
* field_types = {
* "integer"
* }
* )
*/
class FileSize extends FormatterBase {
/**
* {@inheritdoc}
*/
public static function isApplicable(FieldDefinitionInterface $field_definition) {
return parent::isApplicable($field_definition) && $field_definition->getName() === 'filesize';
}
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = [];
foreach ($items as $delta => $item) {
$elements[$delta] = ['#markup' => format_size($item->value)];
}
return $elements;
}
}

View file

@ -0,0 +1,68 @@
<?php
namespace Drupal\file\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Form\FormStateInterface;
/**
* Formatter to render the file URI to its download path.
*
* @FieldFormatter(
* id = "file_uri",
* label = @Translation("File URI"),
* field_types = {
* "uri"
* }
* )
*/
class FileUriFormatter extends BaseFieldFileFormatterBase {
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
$settings = parent::defaultSettings();
$settings['file_download_path'] = FALSE;
return $settings;
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$form = parent::settingsForm($form, $form_state);
$form['file_download_path'] = [
'#title' => $this->t('Display the file download URI'),
'#type' => 'checkbox',
'#default_value' => $this->getSetting('file_download_path'),
];
return $form;
}
/**
* {@inheritdoc}
*/
protected function viewValue(FieldItemInterface $item) {
$value = $item->value;
if ($this->getSetting('file_download_path')) {
// @todo Wrap in file_url_transform_relative(). This is currently
// impossible. See BaseFieldFileFormatterBase::viewElements(). Fix in
// https://www.drupal.org/node/2646744.
$value = file_create_url($value);
}
return $value;
}
/**
* {@inheritdoc}
*/
public static function isApplicable(FieldDefinitionInterface $field_definition) {
return parent::isApplicable($field_definition) && $field_definition->getName() === 'uri';
}
}

View file

@ -0,0 +1,71 @@
<?php
namespace Drupal\file\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Form\FormStateInterface;
/**
* Formatter to render the file MIME type, with an optional icon.
*
* @FieldFormatter(
* id = "file_filemime",
* label = @Translation("File MIME"),
* field_types = {
* "string"
* }
* )
*/
class FilemimeFormatter extends BaseFieldFileFormatterBase {
/**
* {@inheritdoc}
*/
public static function isApplicable(FieldDefinitionInterface $field_definition) {
return parent::isApplicable($field_definition) && $field_definition->getName() === 'filemime';
}
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
$settings = parent::defaultSettings();
$settings['filemime_image'] = FALSE;
return $settings;
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$form = parent::settingsForm($form, $form_state);
$form['filemime_image'] = array(
'#title' => $this->t('Display an icon'),
'#description' => $this->t('The icon is representing the file type, instead of the MIME text (such as "image/jpeg")'),
'#type' => 'checkbox',
'#default_value' => $this->getSetting('filemime_image'),
);
return $form;
}
/**
* {@inheritdoc}
*/
protected function viewValue(FieldItemInterface $item) {
$value = $item->value;
if ($this->getSetting('filemime_image') && $value) {
$file_icon = [
'#theme' => 'image__file_icon',
'#file' => $item->getEntity(),
];
return $file_icon;
}
return $value;
}
}

View file

@ -0,0 +1,49 @@
<?php
namespace Drupal\file\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FieldItemListInterface;
/**
* Plugin implementation of the 'file_default' formatter.
*
* @FieldFormatter(
* id = "file_default",
* label = @Translation("Generic file"),
* field_types = {
* "file"
* }
* )
*/
class GenericFileFormatter extends FileFormatterBase {
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = array();
foreach ($this->getEntitiesToView($items, $langcode) as $delta => $file) {
$item = $file->_referringItem;
$elements[$delta] = array(
'#theme' => 'file_link',
'#file' => $file,
'#description' => $item->description,
'#cache' => array(
'tags' => $file->getCacheTags(),
),
);
// Pass field item attributes to the theme function.
if (isset($item->_attributes)) {
$elements[$delta] += array('#attributes' => array());
$elements[$delta]['#attributes'] += $item->_attributes;
// Unset field item attributes since they have been included in the
// formatter output and should not be rendered in the field template.
unset($item->_attributes);
}
}
return $elements;
}
}

View file

@ -0,0 +1,43 @@
<?php
namespace Drupal\file\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FieldItemListInterface;
/**
* Plugin implementation of the 'file_rss_enclosure' formatter.
*
* @FieldFormatter(
* id = "file_rss_enclosure",
* label = @Translation("RSS enclosure"),
* field_types = {
* "file"
* }
* )
*/
class RSSEnclosureFormatter extends FileFormatterBase {
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$entity = $items->getEntity();
// Add the first file as an enclosure to the RSS item. RSS allows only one
// enclosure per item. See: http://wikipedia.org/wiki/RSS_enclosure
foreach ($this->getEntitiesToView($items, $langcode) as $delta => $file) {
$entity->rss_elements[] = array(
'key' => 'enclosure',
'attributes' => array(
// In RSS feeds, it is necessary to use absolute URLs. The 'url.site'
// cache context is already associated with RSS feed responses, so it
// does not need to be specified here.
'url' => file_create_url($file->getFileUri()),
'length' => $file->getSize(),
'type' => $file->getMimeType(),
),
);
}
return [];
}
}

View file

@ -0,0 +1,57 @@
<?php
namespace Drupal\file\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FieldItemListInterface;
/**
* Plugin implementation of the 'file_table' formatter.
*
* @FieldFormatter(
* id = "file_table",
* label = @Translation("Table of files"),
* field_types = {
* "file"
* }
* )
*/
class TableFormatter extends FileFormatterBase {
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = array();
if ($files = $this->getEntitiesToView($items, $langcode)) {
$header = array(t('Attachment'), t('Size'));
$rows = array();
foreach ($files as $delta => $file) {
$rows[] = array(
array(
'data' => array(
'#theme' => 'file_link',
'#file' => $file,
'#cache' => array(
'tags' => $file->getCacheTags(),
),
),
),
array('data' => format_size($file->getSize())),
);
}
$elements[0] = array();
if (!empty($rows)) {
$elements[0] = array(
'#theme' => 'table__file_formatter_table',
'#header' => $header,
'#rows' => $rows,
);
}
}
return $elements;
}
}

View file

@ -0,0 +1,38 @@
<?php
namespace Drupal\file\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FieldItemListInterface;
/**
* Plugin implementation of the 'file_url_plain' formatter.
*
* @FieldFormatter(
* id = "file_url_plain",
* label = @Translation("URL to file"),
* field_types = {
* "file"
* }
* )
*/
class UrlPlainFormatter extends FileFormatterBase {
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = array();
foreach ($this->getEntitiesToView($items, $langcode) as $delta => $file) {
$elements[$delta] = array(
'#markup' => file_url_transform_relative(file_create_url($file->getFileUri())),
'#cache' => array(
'tags' => $file->getCacheTags(),
),
);
}
return $elements;
}
}

View file

@ -0,0 +1,105 @@
<?php
namespace Drupal\file\Plugin\Field\FieldType;
use Drupal\Core\Field\EntityReferenceFieldItemList;
use Drupal\Core\Form\FormStateInterface;
/**
* Represents a configurable entity file field.
*/
class FileFieldItemList extends EntityReferenceFieldItemList {
/**
* {@inheritdoc}
*/
public function defaultValuesForm(array &$form, FormStateInterface $form_state) { }
/**
* {@inheritdoc}
*/
public function postSave($update) {
$entity = $this->getEntity();
if (!$update) {
// Add a new usage for newly uploaded files.
foreach ($this->referencedEntities() as $file) {
\Drupal::service('file.usage')->add($file, 'file', $entity->getEntityTypeId(), $entity->id());
}
}
else {
// Get current target file entities and file IDs.
$files = $this->referencedEntities();
$ids = array();
/** @var \Drupal\file\FileInterface $file */
foreach ($files as $file) {
$ids[] = $file->id();
}
// On new revisions, all files are considered to be a new usage and no
// deletion of previous file usages are necessary.
if (!empty($entity->original) && $entity->getRevisionId() != $entity->original->getRevisionId()) {
foreach ($files as $file) {
\Drupal::service('file.usage')->add($file, 'file', $entity->getEntityTypeId(), $entity->id());
}
return;
}
// Get the file IDs attached to the field before this update.
$field_name = $this->getFieldDefinition()->getName();
$original_ids = array();
$langcode = $this->getLangcode();
$original = $entity->original;
if ($original->hasTranslation($langcode)) {
$original_items = $original->getTranslation($langcode)->{$field_name};
foreach ($original_items as $item) {
$original_ids[] = $item->target_id;
}
}
// Decrement file usage by 1 for files that were removed from the field.
$removed_ids = array_filter(array_diff($original_ids, $ids));
$removed_files = \Drupal::entityManager()->getStorage('file')->loadMultiple($removed_ids);
foreach ($removed_files as $file) {
\Drupal::service('file.usage')->delete($file, 'file', $entity->getEntityTypeId(), $entity->id());
}
// Add new usage entries for newly added files.
foreach ($files as $file) {
if (!in_array($file->id(), $original_ids)) {
\Drupal::service('file.usage')->add($file, 'file', $entity->getEntityTypeId(), $entity->id());
}
}
}
}
/**
* {@inheritdoc}
*/
public function delete() {
parent::delete();
$entity = $this->getEntity();
// If a translation is deleted only decrement the file usage by one. If the
// default translation is deleted remove all file usages within this entity.
$count = $entity->isDefaultTranslation() ? 0 : 1;
foreach ($this->referencedEntities() as $file) {
\Drupal::service('file.usage')->delete($file, 'file', $entity->getEntityTypeId(), $entity->id(), $count);
}
}
/**
* {@inheritdoc}
*/
public function deleteRevision() {
parent::deleteRevision();
$entity = $this->getEntity();
// Decrement the file usage by 1.
foreach ($this->referencedEntities() as $file) {
\Drupal::service('file.usage')->delete($file, 'file', $entity->getEntityTypeId(), $entity->id());
}
}
}

View file

@ -0,0 +1,360 @@
<?php
namespace Drupal\file\Plugin\Field\FieldType;
use Drupal\Component\Utility\Bytes;
use Drupal\Component\Render\PlainTextOutput;
use Drupal\Component\Utility\Random;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
use Drupal\Core\TypedData\DataDefinition;
/**
* Plugin implementation of the 'file' field type.
*
* @FieldType(
* id = "file",
* label = @Translation("File"),
* description = @Translation("This field stores the ID of a file as an integer value."),
* category = @Translation("Reference"),
* default_widget = "file_generic",
* default_formatter = "file_default",
* list_class = "\Drupal\file\Plugin\Field\FieldType\FileFieldItemList",
* constraints = {"ReferenceAccess" = {}, "FileValidation" = {}}
* )
*/
class FileItem extends EntityReferenceItem {
/**
* {@inheritdoc}
*/
public static function defaultStorageSettings() {
return array(
'target_type' => 'file',
'display_field' => FALSE,
'display_default' => FALSE,
'uri_scheme' => file_default_scheme(),
) + parent::defaultStorageSettings();
}
/**
* {@inheritdoc}
*/
public static function defaultFieldSettings() {
return array(
'file_extensions' => 'txt',
'file_directory' => '[date:custom:Y]-[date:custom:m]',
'max_filesize' => '',
'description_field' => 0,
) + parent::defaultFieldSettings();
}
/**
* {@inheritdoc}
*/
public static function schema(FieldStorageDefinitionInterface $field_definition) {
return array(
'columns' => array(
'target_id' => array(
'description' => 'The ID of the file entity.',
'type' => 'int',
'unsigned' => TRUE,
),
'display' => array(
'description' => 'Flag to control whether this file should be displayed when viewing content.',
'type' => 'int',
'size' => 'tiny',
'unsigned' => TRUE,
'default' => 1,
),
'description' => array(
'description' => 'A description of the file.',
'type' => 'text',
),
),
'indexes' => array(
'target_id' => array('target_id'),
),
'foreign keys' => array(
'target_id' => array(
'table' => 'file_managed',
'columns' => array('target_id' => 'fid'),
),
),
);
}
/**
* {@inheritdoc}
*/
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
$properties = parent::propertyDefinitions($field_definition);
$properties['display'] = DataDefinition::create('boolean')
->setLabel(t('Display'))
->setDescription(t('Flag to control whether this file should be displayed when viewing content'));
$properties['description'] = DataDefinition::create('string')
->setLabel(t('Description'));
return $properties;
}
/**
* {@inheritdoc}
*/
public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) {
$element = array();
$element['#attached']['library'][] = 'file/drupal.file';
$element['display_field'] = array(
'#type' => 'checkbox',
'#title' => t('Enable <em>Display</em> field'),
'#default_value' => $this->getSetting('display_field'),
'#description' => t('The display option allows users to choose if a file should be shown when viewing the content.'),
);
$element['display_default'] = array(
'#type' => 'checkbox',
'#title' => t('Files displayed by default'),
'#default_value' => $this->getSetting('display_default'),
'#description' => t('This setting only has an effect if the display option is enabled.'),
'#states' => array(
'visible' => array(
':input[name="settings[display_field]"]' => array('checked' => TRUE),
),
),
);
$scheme_options = \Drupal::service('stream_wrapper_manager')->getNames(StreamWrapperInterface::WRITE_VISIBLE);
$element['uri_scheme'] = array(
'#type' => 'radios',
'#title' => t('Upload destination'),
'#options' => $scheme_options,
'#default_value' => $this->getSetting('uri_scheme'),
'#description' => t('Select where the final files should be stored. Private file storage has significantly more overhead than public files, but allows restricted access to files within this field.'),
'#disabled' => $has_data,
);
return $element;
}
/**
* {@inheritdoc}
*/
public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
$element = array();
$settings = $this->getSettings();
$element['file_directory'] = array(
'#type' => 'textfield',
'#title' => t('File directory'),
'#default_value' => $settings['file_directory'],
'#description' => t('Optional subdirectory within the upload destination where files will be stored. Do not include preceding or trailing slashes.'),
'#element_validate' => array(array(get_class($this), 'validateDirectory')),
'#weight' => 3,
);
// Make the extension list a little more human-friendly by comma-separation.
$extensions = str_replace(' ', ', ', $settings['file_extensions']);
$element['file_extensions'] = array(
'#type' => 'textfield',
'#title' => t('Allowed file extensions'),
'#default_value' => $extensions,
'#description' => t('Separate extensions with a space or comma and do not include the leading dot.'),
'#element_validate' => array(array(get_class($this), 'validateExtensions')),
'#weight' => 1,
'#maxlength' => 256,
// By making this field required, we prevent a potential security issue
// that would allow files of any type to be uploaded.
'#required' => TRUE,
);
$element['max_filesize'] = array(
'#type' => 'textfield',
'#title' => t('Maximum upload size'),
'#default_value' => $settings['max_filesize'],
'#description' => t('Enter a value like "512" (bytes), "80 KB" (kilobytes) or "50 MB" (megabytes) in order to restrict the allowed file size. If left empty the file sizes will be limited only by PHP\'s maximum post and file upload sizes (current limit <strong>%limit</strong>).', array('%limit' => format_size(file_upload_max_size()))),
'#size' => 10,
'#element_validate' => array(array(get_class($this), 'validateMaxFilesize')),
'#weight' => 5,
);
$element['description_field'] = array(
'#type' => 'checkbox',
'#title' => t('Enable <em>Description</em> field'),
'#default_value' => isset($settings['description_field']) ? $settings['description_field'] : '',
'#description' => t('The description field allows users to enter a description about the uploaded file.'),
'#weight' => 11,
);
return $element;
}
/**
* Form API callback
*
* Removes slashes from the beginning and end of the destination value and
* ensures that the file directory path is not included at the beginning of the
* value.
*
* This function is assigned as an #element_validate callback in
* fieldSettingsForm().
*/
public static function validateDirectory($element, FormStateInterface $form_state) {
// Strip slashes from the beginning and end of $element['file_directory'].
$value = trim($element['#value'], '\\/');
$form_state->setValueForElement($element, $value);
}
/**
* Form API callback.
*
* This function is assigned as an #element_validate callback in
* fieldSettingsForm().
*
* This doubles as a convenience clean-up function and a validation routine.
* Commas are allowed by the end-user, but ultimately the value will be stored
* as a space-separated list for compatibility with file_validate_extensions().
*/
public static function validateExtensions($element, FormStateInterface $form_state) {
if (!empty($element['#value'])) {
$extensions = preg_replace('/([, ]+\.?)/', ' ', trim(strtolower($element['#value'])));
$extensions = array_filter(explode(' ', $extensions));
$extensions = implode(' ', array_unique($extensions));
if (!preg_match('/^([a-z0-9]+([.][a-z0-9])* ?)+$/', $extensions)) {
$form_state->setError($element, t('The list of allowed extensions is not valid, be sure to exclude leading dots and to separate extensions with a comma or space.'));
}
else {
$form_state->setValueForElement($element, $extensions);
}
}
}
/**
* Form API callback.
*
* Ensures that a size has been entered and that it can be parsed by
* \Drupal\Component\Utility\Bytes::toInt().
*
* This function is assigned as an #element_validate callback in
* fieldSettingsForm().
*/
public static function validateMaxFilesize($element, FormStateInterface $form_state) {
if (!empty($element['#value']) && !is_numeric(Bytes::toInt($element['#value']))) {
$form_state->setError($element, t('The "@name" option must contain a valid value. You may either leave the text field empty or enter a string like "512" (bytes), "80 KB" (kilobytes) or "50 MB" (megabytes).', array('@name' => $element['title'])));
}
}
/**
* Determines the URI for a file field.
*
* @param array $data
* An array of token objects to pass to token_replace().
*
* @return string
* An unsanitized file directory URI with tokens replaced. The result of
* the token replacement is then converted to plain text and returned.
*
* @see token_replace()
*/
public function getUploadLocation($data = array()) {
return static::doGetUploadLocation($this->getSettings(), $data);
}
/**
* Determines the URI for a file field.
*
* @param array $settings
* The array of field settings.
* @param array $data
* An array of token objects to pass to token_replace().
*
* @return string
* An unsanitized file directory URI with tokens replaced. The result of
* the token replacement is then converted to plain text and returned.
*/
protected static function doGetUploadLocation(array $settings, $data = []) {
$destination = trim($settings['file_directory'], '/');
// Replace tokens. As the tokens might contain HTML we convert it to plain
// text.
$destination = PlainTextOutput::renderFromHtml(\Drupal::token()->replace($destination, $data));
return $settings['uri_scheme'] . '://' . $destination;
}
/**
* Retrieves the upload validators for a file field.
*
* @return array
* An array suitable for passing to file_save_upload() or the file field
* element's '#upload_validators' property.
*/
public function getUploadValidators() {
$validators = array();
$settings = $this->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'] = array($max_filesize);
// Add the extension check if necessary.
if (!empty($settings['file_extensions'])) {
$validators['file_validate_extensions'] = array($settings['file_extensions']);
}
return $validators;
}
/**
* {@inheritdoc}
*/
public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
$random = new Random();
$settings = $field_definition->getSettings();
// Prepare destination.
$dirname = static::doGetUploadLocation($settings);
file_prepare_directory($dirname, FILE_CREATE_DIRECTORY);
// Generate a file entity.
$destination = $dirname . '/' . $random->name(10, TRUE) . '.txt';
$data = $random->paragraphs(3);
$file = file_save_data($data, $destination, FILE_EXISTS_ERROR);
$values = array(
'target_id' => $file->id(),
'display' => (int)$settings['display_default'],
'description' => $random->sentences(10),
);
return $values;
}
/**
* Determines whether an item should be displayed when rendering the field.
*
* @return bool
* TRUE if the item should be displayed, FALSE if not.
*/
public function isDisplayed() {
if ($this->getSetting('display_field')) {
return (bool) $this->display;
}
return TRUE;
}
/**
* {@inheritdoc}
*/
public static function getPreconfiguredOptions() {
return [];
}
}

View file

@ -0,0 +1,599 @@
<?php
namespace Drupal\file\Plugin\Field\FieldWidget;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Render\ElementInfoManagerInterface;
use Drupal\file\Element\ManagedFile;
use Drupal\file\Entity\File;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Validator\ConstraintViolationListInterface;
/**
* Plugin implementation of the 'file_generic' widget.
*
* @FieldWidget(
* id = "file_generic",
* label = @Translation("File"),
* field_types = {
* "file"
* }
* )
*/
class FileWidget extends WidgetBase implements ContainerFactoryPluginInterface {
/**
* {@inheritdoc}
*/
public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, ElementInfoManagerInterface $element_info) {
parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
$this->elementInfo = $element_info;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static($plugin_id, $plugin_definition, $configuration['field_definition'], $configuration['settings'], $configuration['third_party_settings'], $container->get('element_info'));
}
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return array(
'progress_indicator' => 'throbber',
) + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$element['progress_indicator'] = array(
'#type' => 'radios',
'#title' => t('Progress indicator'),
'#options' => array(
'throbber' => t('Throbber'),
'bar' => t('Bar with progress meter'),
),
'#default_value' => $this->getSetting('progress_indicator'),
'#description' => t('The throbber display does not show the status of uploads but takes up less space. The progress bar is helpful for monitoring progress on large uploads.'),
'#weight' => 16,
'#access' => file_progress_implementation(),
);
return $element;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = array();
$summary[] = t('Progress indicator: @progress_indicator', array('@progress_indicator' => $this->getSetting('progress_indicator')));
return $summary;
}
/**
* Overrides \Drupal\Core\Field\WidgetBase::formMultipleElements().
*
* Special handling for draggable multiple widgets and 'add more' button.
*/
protected function formMultipleElements(FieldItemListInterface $items, array &$form, FormStateInterface $form_state) {
$field_name = $this->fieldDefinition->getName();
$parents = $form['#parents'];
// Load the items for form rebuilds from the field state as they might not
// be in $form_state->getValues() because of validation limitations. Also,
// they are only passed in as $items when editing existing entities.
$field_state = static::getWidgetState($parents, $field_name, $form_state);
if (isset($field_state['items'])) {
$items->setValue($field_state['items']);
}
// Determine the number of widgets to display.
$cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality();
switch ($cardinality) {
case FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED:
$max = count($items);
$is_multiple = TRUE;
break;
default:
$max = $cardinality - 1;
$is_multiple = ($cardinality > 1);
break;
}
$title = $this->fieldDefinition->getLabel();
$description = $this->getFilteredDescription();
$elements = array();
$delta = 0;
// Add an element for every existing item.
foreach ($items as $item) {
$element = array(
'#title' => $title,
'#description' => $description,
);
$element = $this->formSingleElement($items, $delta, $element, $form, $form_state);
if ($element) {
// Input field for the delta (drag-n-drop reordering).
if ($is_multiple) {
// We name the element '_weight' to avoid clashing with elements
// defined by widget.
$element['_weight'] = array(
'#type' => 'weight',
'#title' => t('Weight for row @number', array('@number' => $delta + 1)),
'#title_display' => 'invisible',
// Note: this 'delta' is the FAPI #type 'weight' element's property.
'#delta' => $max,
'#default_value' => $item->_weight ?: $delta,
'#weight' => 100,
);
}
$elements[$delta] = $element;
$delta++;
}
}
$empty_single_allowed = ($cardinality == 1 && $delta == 0);
$empty_multiple_allowed = ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED || $delta < $cardinality) && !$form_state->isProgrammed();
// Add one more empty row for new uploads except when this is a programmed
// multiple form as it is not necessary.
if ($empty_single_allowed || $empty_multiple_allowed) {
// Create a new empty item.
$items->appendItem();
$element = array(
'#title' => $title,
'#description' => $description,
);
$element = $this->formSingleElement($items, $delta, $element, $form, $form_state);
if ($element) {
$element['#required'] = ($element['#required'] && $delta == 0);
$elements[$delta] = $element;
}
}
if ($is_multiple) {
// The group of elements all-together need some extra functionality after
// building up the full list (like draggable table rows).
$elements['#file_upload_delta'] = $delta;
$elements['#type'] = 'details';
$elements['#open'] = TRUE;
$elements['#theme'] = 'file_widget_multiple';
$elements['#theme_wrappers'] = array('details');
$elements['#process'] = array(array(get_class($this), 'processMultiple'));
$elements['#title'] = $title;
$elements['#description'] = $description;
$elements['#field_name'] = $field_name;
$elements['#language'] = $items->getLangcode();
// The field settings include defaults for the field type. However, this
// widget is a base class for other widgets (e.g., ImageWidget) that may
// act on field types without these expected settings.
$field_settings = $this->getFieldSettings() + array('display_field' => NULL);
$elements['#display_field'] = (bool) $field_settings['display_field'];
// Add some properties that will eventually be added to the file upload
// field. These are added here so that they may be referenced easily
// through a hook_form_alter().
$elements['#file_upload_title'] = t('Add a new file');
$elements['#file_upload_description'] = array(
'#theme' => 'file_upload_help',
'#description' => '',
'#upload_validators' => $elements[0]['#upload_validators'],
'#cardinality' => $cardinality,
);
}
return $elements;
}
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$field_settings = $this->getFieldSettings();
// The field settings include defaults for the field type. However, this
// widget is a base class for other widgets (e.g., ImageWidget) that may act
// on field types without these expected settings.
$field_settings += array(
'display_default' => NULL,
'display_field' => NULL,
'description_field' => NULL,
);
$cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality();
$defaults = array(
'fids' => array(),
'display' => (bool) $field_settings['display_default'],
'description' => '',
);
// Essentially we use the managed_file type, extended with some
// enhancements.
$element_info = $this->elementInfo->getInfo('managed_file');
$element += array(
'#type' => 'managed_file',
'#upload_location' => $items[$delta]->getUploadLocation(),
'#upload_validators' => $items[$delta]->getUploadValidators(),
'#value_callback' => array(get_class($this), 'value'),
'#process' => array_merge($element_info['#process'], array(array(get_class($this), 'process'))),
'#progress_indicator' => $this->getSetting('progress_indicator'),
// Allows this field to return an array instead of a single value.
'#extended' => TRUE,
// Add properties needed by value() and process() methods.
'#field_name' => $this->fieldDefinition->getName(),
'#entity_type' => $items->getEntity()->getEntityTypeId(),
'#display_field' => (bool) $field_settings['display_field'],
'#display_default' => $field_settings['display_default'],
'#description_field' => $field_settings['description_field'],
'#cardinality' => $cardinality,
);
$element['#weight'] = $delta;
// Field stores FID value in a single mode, so we need to transform it for
// form element to recognize it correctly.
if (!isset($items[$delta]->fids) && isset($items[$delta]->target_id)) {
$items[$delta]->fids = array($items[$delta]->target_id);
}
$element['#default_value'] = $items[$delta]->getValue() + $defaults;
$default_fids = $element['#extended'] ? $element['#default_value']['fids'] : $element['#default_value'];
if (empty($default_fids)) {
$file_upload_help = array(
'#theme' => 'file_upload_help',
'#description' => $element['#description'],
'#upload_validators' => $element['#upload_validators'],
'#cardinality' => $cardinality,
);
$element['#description'] = \Drupal::service('renderer')->renderPlain($file_upload_help);
$element['#multiple'] = $cardinality != 1 ? TRUE : FALSE;
if ($cardinality != 1 && $cardinality != -1) {
$element['#element_validate'] = array(array(get_class($this), 'validateMultipleCount'));
}
}
return $element;
}
/**
* {@inheritdoc}
*/
public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
// Since file upload widget now supports uploads of more than one file at a
// time it always returns an array of fids. We have to translate this to a
// single fid, as field expects single value.
$new_values = array();
foreach ($values as &$value) {
foreach ($value['fids'] as $fid) {
$new_value = $value;
$new_value['target_id'] = $fid;
unset($new_value['fids']);
$new_values[] = $new_value;
}
}
return $new_values;
}
/**
* {@inheritdoc}
*/
public function extractFormValues(FieldItemListInterface $items, array $form, FormStateInterface $form_state) {
parent::extractFormValues($items, $form, $form_state);
// Update reference to 'items' stored during upload to take into account
// changes to values like 'alt' etc.
// @see \Drupal\file\Plugin\Field\FieldWidget\FileWidget::submit()
$field_name = $this->fieldDefinition->getName();
$field_state = static::getWidgetState($form['#parents'], $field_name, $form_state);
$field_state['items'] = $items->getValue();
static::setWidgetState($form['#parents'], $field_name, $form_state, $field_state);
}
/**
* Form API callback. Retrieves the value for the file_generic field element.
*
* This method is assigned as a #value_callback in formElement() method.
*/
public static function value($element, $input = FALSE, FormStateInterface $form_state) {
if ($input) {
// Checkboxes lose their value when empty.
// If the display field is present make sure its unchecked value is saved.
if (empty($input['display'])) {
$input['display'] = $element['#display_field'] ? 0 : 1;
}
}
// We depend on the managed file element to handle uploads.
$return = ManagedFile::valueCallback($element, $input, $form_state);
// Ensure that all the required properties are returned even if empty.
$return += array(
'fids' => array(),
'display' => 1,
'description' => '',
);
return $return;
}
/**
* Form element validation callback for upload element on file widget. Checks
* if user has uploaded more files than allowed.
*
* This validator is used only when cardinality not set to 1 or unlimited.
*/
public static function validateMultipleCount($element, FormStateInterface $form_state, $form) {
$values = NestedArray::getValue($form_state->getValues(), $element['#parents']);
$array_parents = $element['#array_parents'];
array_pop($array_parents);
$previously_uploaded_count = count(Element::children(NestedArray::getValue($form, $array_parents))) - 1;
$field_storage_definitions = \Drupal::entityManager()->getFieldStorageDefinitions($element['#entity_type']);
$field_storage = $field_storage_definitions[$element['#field_name']];
$newly_uploaded_count = count($values['fids']);
$total_uploaded_count = $newly_uploaded_count + $previously_uploaded_count;
if ($total_uploaded_count > $field_storage->getCardinality()) {
$keep = $newly_uploaded_count - $total_uploaded_count + $field_storage->getCardinality();
$removed_files = array_slice($values['fids'], $keep);
$removed_names = array();
foreach ($removed_files as $fid) {
$file = File::load($fid);
$removed_names[] = $file->getFilename();
}
$args = [
'%field' => $field_storage->getName(),
'@max' => $field_storage->getCardinality(),
'@count' => $total_uploaded_count,
'%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');
$values['fids'] = array_slice($values['fids'], 0, $keep);
NestedArray::setValue($form_state->getValues(), $element['#parents'], $values);
}
}
/**
* Form API callback: Processes a file_generic field element.
*
* Expands the file_generic type to include the description and display
* fields.
*
* This method is assigned as a #process callback in formElement() method.
*/
public static function process($element, FormStateInterface $form_state, $form) {
$item = $element['#value'];
$item['fids'] = $element['fids']['#value'];
// Add the display field if enabled.
if ($element['#display_field']) {
$element['display'] = array(
'#type' => empty($item['fids']) ? 'hidden' : 'checkbox',
'#title' => t('Include file in display'),
'#attributes' => array('class' => array('file-display')),
);
if (isset($item['display'])) {
$element['display']['#value'] = $item['display'] ? '1' : '';
}
else {
$element['display']['#value'] = $element['#display_default'];
}
}
else {
$element['display'] = array(
'#type' => 'hidden',
'#value' => '1',
);
}
// Add the description field if enabled.
if ($element['#description_field'] && $item['fids']) {
$config = \Drupal::config('file.settings');
$element['description'] = array(
'#type' => $config->get('description.type'),
'#title' => t('Description'),
'#value' => isset($item['description']) ? $item['description'] : '',
'#maxlength' => $config->get('description.length'),
'#description' => t('The description may be used as the label of the link to the file.'),
);
}
// Adjust the Ajax settings so that on upload and remove of any individual
// file, the entire group of file fields is updated together.
if ($element['#cardinality'] != 1) {
$parents = array_slice($element['#array_parents'], 0, -1);
$new_options = array(
'query' => array(
'element_parents' => implode('/', $parents),
),
);
$field_element = NestedArray::getValue($form, $parents);
$new_wrapper = $field_element['#id'] . '-ajax-wrapper';
foreach (Element::children($element) as $key) {
if (isset($element[$key]['#ajax'])) {
$element[$key]['#ajax']['options'] = $new_options;
$element[$key]['#ajax']['wrapper'] = $new_wrapper;
}
}
unset($element['#prefix'], $element['#suffix']);
}
// Add another submit handler to the upload and remove buttons, to implement
// functionality needed by the field widget. This submit handler, along with
// the rebuild logic in file_field_widget_form() requires the entire field,
// not just the individual item, to be valid.
foreach (array('upload_button', 'remove_button') as $key) {
$element[$key]['#submit'][] = array(get_called_class(), 'submit');
$element[$key]['#limit_validation_errors'] = array(array_slice($element['#parents'], 0, -1));
}
return $element;
}
/**
* Form API callback: Processes a group of file_generic field elements.
*
* Adds the weight field to each row so it can be ordered and adds a new Ajax
* wrapper around the entire group so it can be replaced all at once.
*
* This method on is assigned as a #process callback in formMultipleElements()
* method.
*/
public static function processMultiple($element, FormStateInterface $form_state, $form) {
$element_children = Element::children($element, TRUE);
$count = count($element_children);
// Count the number of already uploaded files, in order to display new
// items in \Drupal\file\Element\ManagedFile::uploadAjaxCallback().
if (!$form_state->isRebuilding()) {
$count_items_before = 0;
foreach ($element_children as $children) {
if (!empty($element[$children]['#default_value']['fids'])) {
$count_items_before++;
}
}
$form_state->set('file_upload_delta_initial', $count_items_before);
}
foreach ($element_children as $delta => $key) {
if ($key != $element['#file_upload_delta']) {
$description = static::getDescriptionFromElement($element[$key]);
$element[$key]['_weight'] = array(
'#type' => 'weight',
'#title' => $description ? t('Weight for @title', array('@title' => $description)) : t('Weight for new file'),
'#title_display' => 'invisible',
'#delta' => $count,
'#default_value' => $delta,
);
}
else {
// The title needs to be assigned to the upload field so that validation
// errors include the correct widget label.
$element[$key]['#title'] = $element['#title'];
$element[$key]['_weight'] = array(
'#type' => 'hidden',
'#default_value' => $delta,
);
}
}
// Add a new wrapper around all the elements for Ajax replacement.
$element['#prefix'] = '<div id="' . $element['#id'] . '-ajax-wrapper">';
$element['#suffix'] = '</div>';
return $element;
}
/**
* Retrieves the file description from a field field element.
*
* This helper static method is used by processMultiple() method.
*
* @param array $element
* An associative array with the element being processed.
*
* @return array|false
* A description of the file suitable for use in the administrative
* interface.
*/
protected static function getDescriptionFromElement($element) {
// Use the actual file description, if it's available.
if (!empty($element['#default_value']['description'])) {
return $element['#default_value']['description'];
}
// Otherwise, fall back to the filename.
if (!empty($element['#default_value']['filename'])) {
return $element['#default_value']['filename'];
}
// This is probably a newly uploaded file; no description is available.
return FALSE;
}
/**
* Form submission handler for upload/remove button of formElement().
*
* This runs in addition to and after file_managed_file_submit().
*
* @see file_managed_file_submit()
*/
public static function submit($form, FormStateInterface $form_state) {
// During the form rebuild, formElement() will create field item widget
// elements using re-indexed deltas, so clear out FormState::$input to
// avoid a mismatch between old and new deltas. The rebuilt elements will
// have #default_value set appropriately for the current state of the field,
// so nothing is lost in doing this.
$button = $form_state->getTriggeringElement();
$parents = array_slice($button['#parents'], 0, -2);
NestedArray::setValue($form_state->getUserInput(), $parents, NULL);
// Go one level up in the form, to the widgets container.
$element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1));
$field_name = $element['#field_name'];
$parents = $element['#field_parents'];
$submitted_values = NestedArray::getValue($form_state->getValues(), array_slice($button['#parents'], 0, -2));
foreach ($submitted_values as $delta => $submitted_value) {
if (empty($submitted_value['fids'])) {
unset($submitted_values[$delta]);
}
}
// If there are more files uploaded via the same widget, we have to separate
// them, as we display each file in its own widget.
$new_values = array();
foreach ($submitted_values as $delta => $submitted_value) {
if (is_array($submitted_value['fids'])) {
foreach ($submitted_value['fids'] as $fid) {
$new_value = $submitted_value;
$new_value['fids'] = array($fid);
$new_values[] = $new_value;
}
}
else {
$new_value = $submitted_value;
}
}
// Re-index deltas after removing empty items.
$submitted_values = array_values($new_values);
// Update form_state values.
NestedArray::setValue($form_state->getValues(), array_slice($button['#parents'], 0, -2), $submitted_values);
// Update items.
$field_state = static::getWidgetState($parents, $field_name, $form_state);
$field_state['items'] = $submitted_values;
static::setWidgetState($parents, $field_name, $form_state, $field_state);
}
/**
* {@inheritdoc}
*/
public function flagErrors(FieldItemListInterface $items, ConstraintViolationListInterface $violations, array $form, FormStateInterface $form_state) {
// Never flag validation errors for the remove button.
$clicked_button = end($form_state->getTriggeringElement()['#parents']);
if ($clicked_button !== 'remove_button') {
parent::flagErrors($items, $violations, $form, $form_state);
}
}
}

View file

@ -0,0 +1,26 @@
<?php
namespace Drupal\file\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
/**
* Supports validating file URIs.
*
* @Constraint(
* id = "FileUriUnique",
* label = @Translation("File URI", context = "Validation")
* )
*/
class FileUriUnique extends Constraint {
public $message = 'The file %value already exists. Enter a unique file URI.';
/**
* {@inheritdoc}
*/
public function validatedBy() {
return '\Drupal\Core\Validation\Plugin\Validation\Constraint\UniqueFieldValueValidator';
}
}

View file

@ -0,0 +1,17 @@
<?php
namespace Drupal\file\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
/**
* Validation File constraint.
*
* @Constraint(
* id = "FileValidation",
* label = @Translation("File Validation", context = "Validation")
* )
*/
class FileValidationConstraint extends Constraint {
}

View file

@ -0,0 +1,29 @@
<?php
namespace Drupal\file\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
/**
* Checks that a file referenced in a file field is valid.
*/
class FileValidationConstraintValidator extends ConstraintValidator {
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint) {
// Get the file to execute validators.
$file = $value->get('entity')->getTarget()->getValue();
// Get the validators.
$validators = $value->getUploadValidators();
// Checks that a file meets the criteria specified by the validators.
if ($errors = file_validate($file, $validators)) {
foreach ($errors as $error) {
$this->context->addViolation($error);
}
}
}
}

View file

@ -0,0 +1,58 @@
<?php
namespace Drupal\file\Plugin\migrate\cckfield\d6;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\cckfield\CckFieldPluginBase;
/**
* @MigrateCckField(
* id = "filefield",
* core = {6}
* )
*/
class FileField extends CckFieldPluginBase {
/**
* {@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 processCckFieldValues(MigrationInterface $migration, $field_name, $data) {
$process = [
'plugin' => 'd6_cck_file',
'source' => $field_name,
];
$migration->mergeProcessOfProperty($field_name, $process);
}
/**
* {@inheritdoc}
*/
public function getFieldType(Row $row) {
return $row->getSourceProperty('widget_type') == 'imagefield_widget' ? 'image' : 'file';
}
}

View file

@ -0,0 +1,63 @@
<?php
namespace Drupal\file\Plugin\migrate\cckfield\d7;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\cckfield\CckFieldPluginBase;
/**
* @MigrateCckField(
* id = "file",
* core = {7}
* )
*/
class FileField extends CckFieldPluginBase {
/**
* {@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 processCckFieldValues(MigrationInterface $migration, $field_name, $data) {
$process = [
'plugin' => 'iterator',
'source' => $field_name,
'process' => [
'target_id' => 'fid',
'display' => 'display',
'description' => 'description',
],
];
$migration->mergeProcessOfProperty($field_name, $process);
}
/**
* {@inheritdoc}
*/
public function getFieldType(Row $row) {
return $row->getSourceProperty('widget_type') == 'imagefield_widget' ? 'image' : 'file';
}
}

View file

@ -0,0 +1,41 @@
<?php
namespace Drupal\file\Plugin\migrate\cckfield\d7;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate_drupal\Plugin\migrate\cckfield\CckFieldPluginBase;
/**
* @MigrateCckField(
* id = "image",
* core = {7}
* )
*/
class ImageField extends CckFieldPluginBase {
/**
* {@inheritdoc}
*/
public function getFieldFormatterMap() {
return array();
}
/**
* {@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);
}
}

View file

@ -0,0 +1,69 @@
<?php
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;
use Drupal\migrate\Plugin\migrate\destination\EntityContentBase;
/**
* @MigrateDestination(
* id = "entity:file"
* )
*/
class EntityFile extends EntityContentBase {
/**
* {@inheritdoc}
*/
protected function getEntity(Row $row, array $old_destination_id_values) {
// For stub rows, there is no real file to deal with, let the stubbing
// process take its default path.
if ($row->isStub()) {
return parent::getEntity($row, $old_destination_id_values);
}
// By default the entity key (fid) would be used, but we want to make sure
// we're loading the matching URI.
$destination = $row->getDestinationProperty('uri');
if (empty($destination)) {
throw new MigrateException('Destination property uri not provided');
}
$entity = $this->storage->loadByProperties(['uri' => $destination]);
if ($entity) {
return reset($entity);
}
else {
return parent::getEntity($row, $old_destination_id_values);
}
}
/**
* {@inheritdoc}
*/
protected function processStubRow(Row $row) {
// We stub the uri value ourselves so we can create a real stub file for it.
if (!$row->getDestinationProperty('uri')) {
$field_definitions = $this->entityManager
->getFieldDefinitions($this->storage->getEntityTypeId(),
$this->getKey('bundle'));
$value = UriItem::generateSampleValue($field_definitions['uri']);
if (empty($value)) {
throw new MigrateException('Stubbing failed, unable to generate value for field uri');
}
// generateSampleValue() wraps the value in an array.
$value = reset($value);
// 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'));
// Create a real file, so File::preSave() can do filesize() on it.
touch($value);
$row->setDestinationProperty('uri', $value);
}
parent::processStubRow($row);
}
}

View file

@ -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_cck_file"
* )
*/
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 [];
}
}
}

View file

@ -0,0 +1,42 @@
<?php
namespace Drupal\file\Plugin\migrate\process\d6;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* Process the file url into a D8 compatible URL.
*
* @MigrateProcessPlugin(
* id = "file_uri"
* )
*/
class FileUri extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
// If we're stubbing a file entity, return a uri of NULL so it will get
// stubbed by the general process.
if ($row->isStub()) {
return NULL;
}
list($filepath, $file_directory_path, $temp_directory_path, $is_public) = $value;
// Specific handling using $temp_directory_path for temporary files.
if (substr($filepath, 0, strlen($temp_directory_path)) === $temp_directory_path) {
$uri = preg_replace('/^' . preg_quote($temp_directory_path, '/') . '/', '', $filepath);
return 'temporary://' . ltrim($uri, '/');
}
// Strip the files path from the uri instead of using basename
// so any additional folders in the path are preserved.
$uri = preg_replace('/^' . preg_quote($file_directory_path, '/') . '/', '', $filepath);
return ($is_public ? 'public' : 'private') . '://' . ltrim($uri, '/');
}
}

View file

@ -0,0 +1,98 @@
<?php
namespace Drupal\file\Plugin\migrate\source\d6;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
/**
* Drupal 6 file source from database.
*
* @MigrateSource(
* id = "d6_file"
* )
*/
class File extends DrupalSqlBase {
/**
* The file directory path.
*
* @var string
*/
protected $filePath;
/**
* The temporary file path.
*
* @var string
*/
protected $tempFilePath;
/**
* Flag for private or public file storage.
*
* @var bool
*/
protected $isPublic;
/**
* {@inheritdoc}
*/
public function query() {
return $this->select('files', 'f')
->fields('f')
->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)
// will prevent this.
->orderBy('f.fid');
}
/**
* {@inheritdoc}
*/
protected function initializeIterator() {
$site_path = isset($this->configuration['site_path']) ? $this->configuration['site_path'] : 'sites/default';
$this->filePath = $this->variableGet('file_directory_path', $site_path . '/files') . '/';
$this->tempFilePath = $this->variableGet('file_directory_temp', '/tmp') . '/';
// FILE_DOWNLOADS_PUBLIC == 1 and FILE_DOWNLOADS_PRIVATE == 2.
$this->isPublic = $this->variableGet('file_downloads', 1) == 1;
return parent::initializeIterator();
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
$row->setSourceProperty('file_directory_path', $this->filePath);
$row->setSourceProperty('temp_directory_path', $this->tempFilePath);
$row->setSourceProperty('is_public', $this->isPublic);
return parent::prepareRow($row);
}
/**
* {@inheritdoc}
*/
public function fields() {
return array(
'fid' => $this->t('File ID'),
'uid' => $this->t('The {users}.uid who added the file. If set to 0, this file was added by an anonymous user.'),
'filename' => $this->t('File name'),
'filepath' => $this->t('File path'),
'filemime' => $this->t('File MIME Type'),
'status' => $this->t('The published status of a file.'),
'timestamp' => $this->t('The time that the file was added.'),
'file_directory_path' => $this->t('The Drupal files path.'),
'is_public' => $this->t('TRUE if the files directory is public otherwise FALSE.'),
);
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['fid']['type'] = 'integer';
return $ids;
}
}

View file

@ -0,0 +1,72 @@
<?php
namespace Drupal\file\Plugin\migrate\source\d6;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
/**
* Drupal 6 upload source from database.
*
* @MigrateSource(
* id = "d6_upload",
* source_provider = "upload"
* )
*/
class Upload extends DrupalSqlBase {
/**
* The join options between the node and the upload table.
*/
const JOIN = 'n.nid = u.nid AND n.vid = u.vid';
/**
* {@inheritdoc}
*/
public function query() {
$query = $this->select('upload', 'u')
->distinct()
->fields('u', array('nid', 'vid'));
$query->innerJoin('node', 'n', static::JOIN);
$query->addField('n', 'type');
return $query;
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
$query = $this->select('upload', 'u')
->fields('u', array('fid', 'description', 'list'))
->condition('u.nid', $row->getSourceProperty('nid'))
->orderBy('u.weight');
$query->innerJoin('node', 'n', static::JOIN);
$row->setSourceProperty('upload', $query->execute()->fetchAll());
return parent::prepareRow($row);
}
/**
* {@inheritdoc}
*/
public function fields() {
return array(
'fid' => $this->t('The file Id.'),
'nid' => $this->t('The node Id.'),
'vid' => $this->t('The version Id.'),
'type' => $this->t('The node type'),
'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.'),
);
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['vid']['type'] = 'integer';
$ids['vid']['alias'] = 'u';
return $ids;
}
}

View file

@ -0,0 +1,83 @@
<?php
namespace Drupal\file\Plugin\migrate\source\d6;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
use Drupal\migrate\Plugin\migrate\source\DummyQueryTrait;
/**
* Drupal 6 upload instance source from database.
*
* @MigrateSource(
* id = "d6_upload_instance",
* source_provider = "upload"
* )
*/
class UploadInstance extends DrupalSqlBase {
use DummyQueryTrait;
/**
* {@inheritdoc}
*/
protected function initializeIterator() {
$node_types = $this->select('node_type', 'nt')
->fields('nt', ['type'])
->execute()
->fetchCol();
$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' : '';
$file_extensions = $this->variableGet('upload_extensions_default', 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp');
$return = array();
$values = $this->select('variable', 'v')
->fields('v', ['name', 'value'])
->condition('v.name', $variables, 'IN')
->execute()
->fetchAllKeyed();
foreach ($node_types as $node_type) {
$name = 'upload_' . $node_type;
if (isset($values[$name])) {
$enabled = unserialize($values[$name]);
if ($enabled) {
$return[$node_type]['node_type'] = $node_type;
$return[$node_type]['max_filesize'] = $max_filesize;
$return[$node_type]['file_extensions'] = $file_extensions;
}
}
}
return new \ArrayIterator($return);
}
/**
* {@inheritdoc}
*/
public function getIds() {
return array(
'node_type' => array(
'type' => 'string',
),
);
}
/**
* {@inheritdoc}
*/
public function fields() {
return array(
'node_type' => $this->t('Node type'),
'max_filesize' => $this->t('Max filesize'),
'file_extensions' => $this->t('File extensions'),
);
}
/**
* {@inheritdoc}
*/
public function count() {
return count($this->initializeIterator());
}
}

View file

@ -0,0 +1,115 @@
<?php
namespace Drupal\file\Plugin\migrate\source\d7;
use Drupal\Core\Database\Query\Condition;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
/**
* Drupal 7 file source from database.
*
* @MigrateSource(
* id = "d7_file"
* )
*/
class File extends DrupalSqlBase {
/**
* The public file directory path.
*
* @var string
*/
protected $publicPath;
/**
* The private file directory path, if any.
*
* @var string
*/
protected $privatePath;
/**
* The temporary file directory path.
*
* @var string
*/
protected $temporaryPath;
/**
* {@inheritdoc}
*/
public function query() {
$query = $this->select('file_managed', 'f')
->fields('f')
->orderBy('f.timestamp');
// Filter by scheme(s), if configured.
if (isset($this->configuration['scheme'])) {
$schemes = array();
// Accept either a single scheme, or a list.
foreach ((array) $this->configuration['scheme'] as $scheme) {
$schemes[] = rtrim($scheme) . '://';
}
$schemes = array_map([$this->getDatabase(), 'escapeLike'], $schemes);
// uri LIKE 'public://%' OR uri LIKE 'private://%'
$conditions = new Condition('OR');
foreach ($schemes as $scheme) {
$conditions->condition('uri', $scheme . '%', 'LIKE');
}
$query->condition($conditions);
}
return $query;
}
/**
* {@inheritdoc}
*/
protected function initializeIterator() {
$this->publicPath = $this->variableGet('file_public_path', 'sites/default/files');
$this->privatePath = $this->variableGet('file_private_path', NULL);
$this->temporaryPath = $this->variableGet('file_temporary_path', '/tmp');
return parent::initializeIterator();
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
// Compute the filepath property, which is a physical representation of
// the URI relative to the Drupal root.
$path = str_replace(['public:/', 'private:/', 'temporary:/'], [$this->publicPath, $this->privatePath, $this->temporaryPath], $row->getSourceProperty('uri'));
// At this point, $path could be an absolute path or a relative path,
// depending on how the scheme's variable was set. So we need to shear out
// the source_base_path in order to make them all relative.
$path = str_replace($this->configuration['constants']['source_base_path'], NULL, $path);
$row->setSourceProperty('filepath', $path);
return parent::prepareRow($row);
}
/**
* {@inheritdoc}
*/
public function fields() {
return array(
'fid' => $this->t('File ID'),
'uid' => $this->t('The {users}.uid who added the file. If set to 0, this file was added by an anonymous user.'),
'filename' => $this->t('File name'),
'filepath' => $this->t('File path'),
'filemime' => $this->t('File MIME Type'),
'status' => $this->t('The published status of a file.'),
'timestamp' => $this->t('The time that the file was added.'),
);
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['fid']['type'] = 'integer';
return $ids;
}
}

View file

@ -0,0 +1,83 @@
<?php
namespace Drupal\file\Plugin\views\argument;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\Query\QueryFactory;
use Drupal\views\Plugin\views\argument\NumericArgument;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Argument handler to accept multiple file ids.
*
* @ingroup views_argument_handlers
*
* @ViewsArgument("file_fid")
*/
class Fid extends NumericArgument implements ContainerFactoryPluginInterface {
/**
* The entity manager service
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The entity query factory service.
*
* @var \Drupal\Core\Entity\Query\QueryFactory
*/
protected $entityQuery;
/**
* Constructs a Drupal\file\Plugin\views\argument\Fid object.
*
* @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 \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Entity\Query\QueryFactory $entity_query
* The entity query factory.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, QueryFactory $entity_query) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityManager = $entity_manager;
$this->entityQuery = $entity_query;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity.manager'),
$container->get('entity.query')
);
}
/**
* Override the behavior of titleQuery(). Get the filenames.
*/
public function titleQuery() {
$fids = $this->entityQuery->get('file')
->condition('fid', $this->value, 'IN')
->execute();
$controller = $this->entityManager->getStorage('file');
$files = $controller->loadMultiple($fids);
$titles = array();
foreach ($files as $file) {
$titles[] = $file->getFilename();
}
return $titles;
}
}

View file

@ -0,0 +1,87 @@
<?php
namespace Drupal\file\Plugin\views\field;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\ResultRow;
use Drupal\views\ViewExecutable;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Plugin\views\field\FieldPluginBase;
/**
* Field handler to provide simple renderer that allows linking to a file.
*
* @ingroup views_field_handlers
*
* @ViewsField("file")
*/
class File extends FieldPluginBase {
/**
* {@inheritdoc}
*/
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
parent::init($view, $display, $options);
if (!empty($options['link_to_file'])) {
$this->additional_fields['uri'] = 'uri';
}
}
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
$options['link_to_file'] = array('default' => FALSE);
return $options;
}
/**
* Provide link to file option
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
$form['link_to_file'] = array(
'#title' => $this->t('Link this field to download the file'),
'#description' => $this->t("Enable to override this field's links."),
'#type' => 'checkbox',
'#default_value' => !empty($this->options['link_to_file']),
);
parent::buildOptionsForm($form, $form_state);
}
/**
* Prepares link to the file.
*
* @param string $data
* The XSS safe string for the link text.
* @param \Drupal\views\ResultRow $values
* The values retrieved from a single row of a view's query result.
*
* @return string
* Returns a string for the link text.
*/
protected function renderLink($data, ResultRow $values) {
if (!empty($this->options['link_to_file']) && $data !== NULL && $data !== '') {
$this->options['alter']['make_link'] = TRUE;
// @todo Wrap in file_url_transform_relative(). This is currently
// impossible. As a work-around, we could add the 'url.site' cache context
// to ensure different file URLs are generated for different sites in a
// multisite setup, including HTTP and HTTPS versions of the same site.
// But unfortunately it's impossible to bubble a cache context here.
// Fix in https://www.drupal.org/node/2646744.
$this->options['alter']['path'] = file_create_url($this->getValue($values, 'uri'));
}
return $data;
}
/**
* {@inheritdoc}
*/
public function render(ResultRow $values) {
$value = $this->getValue($values);
return $this->renderLink($this->sanitizeValue($value), $values);
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace Drupal\file\Plugin\views\filter;
use Drupal\views\Plugin\views\filter\InOperator;
/**
* Filter by file status.
*
* @ingroup views_filter_handlers
*
* @ViewsFilter("file_status")
*/
class Status extends InOperator {
public function getValueOptions() {
if (!isset($this->valueOptions)) {
$this->valueOptions = _views_file_status();
}
return $this->valueOptions;
}
}

View file

@ -0,0 +1,58 @@
<?php
namespace Drupal\file\Plugin\views\wizard;
use Drupal\views\Plugin\views\wizard\WizardPluginBase;
/**
* Tests creating managed files views with the wizard.
*
* @ViewsWizard(
* id = "file_managed",
* base_table = "file_managed",
* title = @Translation("Files")
* )
*/
class File extends WizardPluginBase {
/**
* Set the created column.
*/
protected $createdColumn = 'created';
/**
* {@inheritdoc}
*/
protected function defaultDisplayOptions() {
$display_options = parent::defaultDisplayOptions();
// Add permission-based access control.
$display_options['access']['type'] = 'perm';
// Remove the default fields, since we are customizing them here.
unset($display_options['fields']);
/* Field: File: Name */
$display_options['fields']['filename']['id'] = 'filename';
$display_options['fields']['filename']['table'] = 'file_managed';
$display_options['fields']['filename']['field'] = 'filename';
$display_options['fields']['filename']['entity_type'] = 'file';
$display_options['fields']['filename']['entity_field'] = 'filename';
$display_options['fields']['filename']['label'] = '';
$display_options['fields']['filename']['alter']['alter_text'] = 0;
$display_options['fields']['filename']['alter']['make_link'] = 0;
$display_options['fields']['filename']['alter']['absolute'] = 0;
$display_options['fields']['filename']['alter']['trim'] = 0;
$display_options['fields']['filename']['alter']['word_boundary'] = 0;
$display_options['fields']['filename']['alter']['ellipsis'] = 0;
$display_options['fields']['filename']['alter']['strip_tags'] = 0;
$display_options['fields']['filename']['alter']['html'] = 0;
$display_options['fields']['filename']['hide_empty'] = 0;
$display_options['fields']['filename']['empty_zero'] = 0;
$display_options['fields']['filename']['plugin_id'] = 'field';
$display_options['fields']['filename']['type'] = 'file_link';
return $display_options;
}
}