5 * Contains \Drupal\video\Plugin\Field\FieldWidget\VideoEmbedWidget.
8 namespace Drupal\video\Plugin\Field\FieldWidget;
10 use Drupal\Core\Field\FieldItemListInterface;
11 use Drupal\Component\Utility\NestedArray;
12 use Drupal\Core\Form\FormStateInterface;
13 use Drupal\file\Entity\File;
14 use Drupal\file\Plugin\Field\FieldWidget\FileWidget;
15 use Drupal\Core\StreamWrapper\StreamWrapperInterface;
17 use Drupal\Core\Field\FieldItemBase;
18 use Drupal\Core\Field\FieldStorageDefinitionInterface;
19 use Drupal\Core\TypedData\DataDefinition;
20 use Drupal\Core\TypedData\TraversableTypedDataInterface;
22 use Drupal\Core\Field\WidgetBase;
24 use Drupal\Core\Field\FieldFilteredMarkup;
25 use Drupal\Component\Utility\Html;
26 use Drupal\Component\Utility\SortArray;
28 use Drupal\Core\Render\Element;
31 * Plugin implementation of the 'video_embed' widget.
35 * label = @Translation("Video Embed"),
41 class VideoEmbedWidget extends FileWidget {
46 public static function defaultSettings() {
48 'file_directory' => 'video-thumbnails/[date:custom:Y]-[date:custom:m]',
49 'allowed_providers' => ["youtube" => "youtube"],
50 'uri_scheme' => 'public'
58 public function settingsForm(array $form, FormStateInterface $form_state) {
60 $settings = $this->getSettings();
61 $provider_manager = \Drupal::service('video.provider_manager');
62 $element['allowed_providers'] = [
63 '#title' => t('Video Providers'),
64 '#type' => 'checkboxes',
65 '#default_value' => $this->getSetting('allowed_providers'),
66 '#options' => $provider_manager->getProvidersOptionList(),
68 $element['file_directory'] = array(
69 '#type' => 'textfield',
70 '#title' => t('Thumbnail directory'),
71 '#default_value' => $settings['file_directory'],
72 '#description' => t('Optional subdirectory within the upload destination where files will be stored. Do not include preceding or trailing slashes.'),
73 '#element_validate' => array(array(get_class($this), 'validateDirectory')),
76 $scheme_options = \Drupal::service('stream_wrapper_manager')->getNames(StreamWrapperInterface::WRITE_VISIBLE);
77 $element['uri_scheme'] = array(
79 '#title' => t('Thumbnail destination'),
80 '#options' => $scheme_options,
81 '#default_value' => $this->getSetting('uri_scheme'),
82 '#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.'),
91 * Removes slashes from the beginning and end of the destination value and
92 * ensures that the file directory path is not included at the beginning of the
95 * This function is assigned as an #element_validate callback in
98 public static function validateDirectory($element, FormStateInterface $form_state) {
99 // Strip slashes from the beginning and end of $element['file_directory'].
100 $value = trim($element['#value'], '\\/');
101 $form_state->setValueForElement($element, $value);
107 public function settingsSummary() {
109 $summary[] = t('Providers : @allowed_providers<br/>Thumbnail directory : @file_directory',
111 '@allowed_providers' => implode(', ', array_filter($this->getSetting('allowed_providers'))),
112 '@file_directory' => $this->getSetting('uri_scheme') . '://' . $this->getSetting('file_directory'),
120 public function formMultipleElements(FieldItemListInterface $items, array &$form, FormStateInterface $form_state) {
121 $field_name = $this->fieldDefinition->getName();
122 $cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality();
123 $parents = $form['#parents'];
125 // Load the items for form rebuilds from the field state as they might not
126 // be in $form_state->getValues() because of validation limitations. Also,
127 // they are only passed in as $items when editing existing entities.
128 $field_state = static::getWidgetState($parents, $field_name, $form_state);
129 if (isset($field_state['items'])) {
130 $items->setValue($field_state['items']);
133 // Determine the number of widgets to display.
134 switch ($cardinality) {
135 case FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED:
136 $field_state = static::getWidgetState($parents, $field_name, $form_state);
137 $max = $field_state['items_count'];
142 $max = $cardinality - 1;
143 $is_multiple = ($cardinality > 1);
147 $title = $this->fieldDefinition->getLabel();
148 $description = FieldFilteredMarkup::create(\Drupal::token()->replace($this->fieldDefinition->getDescription()));
152 for ($delta = 0; $delta <= $max; $delta++) {
153 // Add a new empty item if it doesn't exist yet at this delta.
154 if (!isset($items[$delta])) {
155 $items->appendItem();
158 // For multiple fields, title and description are handled by the wrapping
162 '#title' => $this->t('@title (value @number)', ['@title' => $title, '@number' => $delta + 1]),
163 '#title_display' => 'invisible',
164 '#description' => '',
170 '#title_display' => 'before',
171 '#description' => $description,
175 $element = $this->formSingleElement($items, $delta, $element, $form, $form_state);
178 // Input field for the delta (drag-n-drop reordering).
180 // We name the element '_weight' to avoid clashing with elements
181 // defined by widget.
182 $element['_weight'] = array(
184 '#title' => $this->t('Weight for row @number', array('@number' => $delta + 1)),
185 '#title_display' => 'invisible',
186 // Note: this 'delta' is the FAPI #type 'weight' element's property.
188 '#default_value' => $items[$delta]->_weight ? : $delta,
193 $elements[$delta] = $element;
199 '#theme' => 'field_multiple_value_form',
200 '#field_name' => $field_name,
201 '#cardinality' => $cardinality,
202 '#cardinality_multiple' => $this->fieldDefinition->getFieldStorageDefinition()->isMultiple(),
203 '#required' => $this->fieldDefinition->isRequired(),
205 '#description' => $description,
206 '#max_delta' => $max,
209 // Add 'add more' button, if not working with a programmed form.
210 if ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED && !$form_state->isProgrammed()) {
211 $id_prefix = implode('-', array_merge($parents, array($field_name)));
212 $wrapper_id = Html::getUniqueId($id_prefix . '-add-more-wrapper');
213 // $elements['#prefix'] = '<div id="' . $wrapper_id . '">';
214 // $elements['#suffix'] = '</div>';
216 $elements['add_more'] = array(
218 '#name' => strtr($id_prefix, '-', '_') . '_add_more',
219 '#value' => t('Add another item'),
220 '#attributes' => array('class' => array('field-add-more-submit')),
221 '#limit_validation_errors' => array(array_merge($parents, array($field_name))),
222 '#submit' => array(array(get_class($this), 'addMoreSubmit')),
224 'callback' => array(get_class($this), 'addMoreAjax'),
233 // The group of elements all-together need some extra functionality after
234 // building up the full list (like draggable table rows).
235 $elements['#file_upload_delta'] = $delta;
236 $elements['#process'] = array(array(get_class($this), 'processMultiple'));
237 $elements['#field_name'] = $field_name;
238 $elements['#language'] = $items->getLangcode();
245 * Form API callback: Processes a group of file_generic field elements.
247 * Adds the weight field to each row so it can be ordered and adds a new Ajax
248 * wrapper around the entire group so it can be replaced all at once.
250 * This method on is assigned as a #process callback in formMultipleElements()
253 public static function processMultiple($element, FormStateInterface $form_state, $form) {
254 $element_children = Element::children($element, TRUE);
255 $count = count($element_children);
257 // Count the number of already uploaded files, in order to display new
258 // items in \Drupal\file\Element\ManagedFile::uploadAjaxCallback().
259 if (!$form_state->isRebuilding()) {
260 $count_items_before = 0;
261 foreach ($element_children as $children) {
262 if (!empty($element[$children]['#default_value']['fids'])) {
263 $count_items_before++;
267 $form_state->set('file_upload_delta_initial', $count_items_before);
270 foreach ($element_children as $delta => $key) {
271 if ($delta != $element['#file_upload_delta']) {
272 $description = static::getDescriptionFromElement($element[$key]);
273 $element[$key]['_weight'] = array(
275 '#title' => $description ? t('Weight for @title', array('@title' => $description)) : t('Weight for new file'),
276 '#title_display' => 'invisible',
278 '#default_value' => $delta,
282 // The title needs to be assigned to the upload field so that validation
283 // errors include the correct widget label.
284 $element[$key]['#title'] = $element['#title'];
285 $element[$key]['_weight'] = array(
287 '#default_value' => $delta,
292 // Add a new wrapper around all the elements for Ajax replacement.
293 $element['#prefix'] = '<div id="' . $element['#id'] . '-ajax-wrapper">';
294 $element['#suffix'] = '</div>';
295 $element['add_more']['#ajax']['wrapper'] = $element['#id'] . '-ajax-wrapper';
303 public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
304 if(empty($items[$delta]->getValue())){
305 $element['value'] = $element + array(
306 '#type' => 'textfield',
307 '#attributes' => ['class' => ['js-text-full', 'text-full']],
308 '#element_validate' => [
309 [get_class($this), 'validateFormElement'],
311 '#allowed_providers' => $this->getSetting('allowed_providers')
315 $element += parent::formElement($items, $delta, $element, $form, $form_state);
322 * Form API callback: Processes a file_generic field element.
324 * Expands the file_generic type to include the description and display
327 * This method is assigned as a #process callback in formElement() method.
329 public static function process($element, FormStateInterface $form_state, $form) {
330 $element = parent::process($element, $form_state, $form);
331 $item = $element['#value'];
332 $element['data']['#value'] = $item['data'];
333 $element['data']['#type'] = 'hidden';
338 * Form element validation handler for URL alias form element.
340 * @param array $element
342 * @param \Drupal\Core\Form\FormStateInterface $form_state
345 public static function validateFormElement(array &$element, FormStateInterface $form_state) {
346 $value = $element['#value'];
350 $provider_manager = \Drupal::service('video.provider_manager');
351 $enabled_providers = $provider_manager->loadDefinitionsFromOptionList($element['#allowed_providers']);
352 if (!$provider_manager->loadApplicableDefinitionMatches($enabled_providers, $value)) {
353 $form_state->setError($element, t('Could not find a video provider to handle the given URL.'));
360 public function extractFormValues(FieldItemListInterface $items, array $form, FormStateInterface $form_state) {
361 $field_name = $this->fieldDefinition->getName();
362 // Extract the values from $form_state->getValues().
363 $path = array_merge($form['#parents'], array($field_name));
365 $values = NestedArray::getValue($form_state->getValues(), $path, $key_exists);
367 // Account for drag-and-drop reordering if needed.
368 if (!$this->handlesMultipleValues()) {
369 // Remove the 'value' of the 'add more' button.
370 unset($values['add_more']);
372 // The original delta, before drag-and-drop reordering, is needed to
373 // route errors to the correct form element.
374 foreach ($values as $delta => &$value) {
375 $value['_original_delta'] = $delta;
378 usort($values, function($a, $b) {
379 return SortArray::sortByKeyInt($a, $b, '_weight');
382 // Let the widget massage the submitted values.
383 foreach($values as $delta => &$value){
384 if(!empty($value['value']) && empty($value['fids'])){
385 // ready to save the file
386 $provider_manager = \Drupal::service('video.provider_manager');
387 $allowed_providers = $this->getSetting('allowed_providers');
388 $enabled_providers = $provider_manager->loadDefinitionsFromOptionList($allowed_providers);
389 if ($provider_matches = $provider_manager->loadApplicableDefinitionMatches($enabled_providers, $value['value'])) {
390 $definition = $provider_matches['definition'];
391 $matches = $provider_matches['matches'];
392 $uri = $definition['stream_wrapper'] . '://' . $matches['id'];
394 $storage = \Drupal::entityManager()->getStorage('file');
395 $results = $storage->getQuery()
396 ->condition('uri', $uri)
398 if(!(count($results) > 0)){
399 $user = \Drupal::currentUser();
400 $file = File::Create([
402 'filemime' => $definition['mimetype'],
407 unset($values[$delta]);
408 $values[] = array('fids' => array($file->id()), 'data' => serialize($matches));
411 unset($values[$delta]);
412 $values[] = array('fids' => array(reset($results)), 'data' => serialize($matches));
417 $values = $this->massageFormValues($values, $form, $form_state);
418 // Assign the values and remove the empty ones.
419 $items->setValue($values);
420 $items->filterEmptyItems();
422 // Put delta mapping in $form_state, so that flagErrors() can use it.
423 $field_state = static::getWidgetState($form['#parents'], $field_name, $form_state);
424 foreach ($items as $delta => $item) {
425 $field_state['original_deltas'][$delta] = isset($item->_original_delta) ? $item->_original_delta : $delta;
426 unset($item->_original_delta, $item->_weight);
428 static::setWidgetState($form['#parents'], $field_name, $form_state, $field_state);