4a05d563c15a31322faa843dc0d5908ab4c77cb6
[yaffs-website] / web / modules / contrib / slick / slick_ui / src / Form / SlickFormBase.php
1 <?php
2
3 namespace Drupal\slick_ui\Form;
4
5 use Drupal\Core\Url;
6 use Drupal\Core\Entity\EntityForm;
7 use Drupal\Core\Form\FormStateInterface;
8 use Drupal\Core\Entity\EntityTypeInterface;
9 use Symfony\Component\DependencyInjection\ContainerInterface;
10 use Drupal\slick\Form\SlickAdmin;
11 use Drupal\slick\SlickManagerInterface;
12
13 /**
14  * Provides base form for a slick instance configuration form.
15  */
16 abstract class SlickFormBase extends EntityForm {
17
18   /**
19    * The slick service.
20    *
21    * @var \Drupal\slick\Form\SlickAdmin
22    */
23   protected $admin;
24
25   /**
26    * Constructs a SlickForm object.
27    */
28   public function __construct(SlickAdmin $admin, SlickManagerInterface $manager) {
29     $this->admin = $admin;
30     $this->manager = $manager;
31   }
32
33   /**
34    * {@inheritdoc}
35    */
36   public static function create(ContainerInterface $container) {
37     return new static(
38       $container->get('slick.admin'),
39       $container->get('slick.manager')
40     );
41   }
42
43   /**
44    * {@inheritdoc}
45    */
46   public function form(array $form, FormStateInterface $form_state) {
47     // Change page title for the duplicate operation.
48     if ($this->operation == 'duplicate') {
49       $form['#title'] = $this->t('<em>Duplicate slick optionset</em>: @label', ['@label' => $this->entity->label()]);
50       $this->entity = $this->entity->createDuplicate();
51     }
52
53     // Change page title for the edit operation.
54     if ($this->operation == 'edit') {
55       $form['#title'] = $this->t('<em>Edit slick optionset</em>: @label', ['@label' => $this->entity->label()]);
56     }
57
58     $slick     = $this->entity;
59     $path      = drupal_get_path('module', 'slick');
60     $tooltip   = ['class' => ['is-tooltip']];
61     $readme    = Url::fromUri('base:' . $path . '/README.txt')->toString();
62     $admin_css = $this->manager->configLoad('admin_css', 'blazy.settings');
63
64     $form['#attributes']['class'][] = 'form--slick';
65     $form['#attributes']['class'][] = 'form--blazy';
66     $form['#attributes']['class'][] = 'form--optionset';
67
68     $form['label'] = [
69       '#type'          => 'textfield',
70       '#title'         => $this->t('Label'),
71       '#default_value' => $slick->label(),
72       '#maxlength'     => 255,
73       '#required'      => TRUE,
74       '#description'   => $this->t("Label for the Slick optionset."),
75       '#attributes'    => $tooltip,
76       '#prefix'        => '<div class="form__header form__half form__half--first has-tooltip clearfix">',
77     ];
78
79     // Keep the legacy CTools ID, i.e.: name as ID.
80     $form['name'] = [
81       '#type'          => 'machine_name',
82       '#default_value' => $slick->id(),
83       '#maxlength'     => EntityTypeInterface::BUNDLE_MAX_LENGTH,
84       '#machine_name'  => [
85         'source' => ['label'],
86         'exists' => '\Drupal\slick\Entity\Slick::load',
87       ],
88       '#attributes'    => $tooltip,
89       '#disabled'      => !$slick->isNew(),
90       '#suffix'        => '</div>',
91     ];
92
93     $form['skin'] = [
94       '#type'          => 'select',
95       '#title'         => $this->t('Skin'),
96       '#options'       => $this->admin->getSkinsByGroupOptions(),
97       '#empty_option'  => $this->t('- None -'),
98       '#default_value' => $slick->getSkin(),
99       '#description'   => $this->t('Skins allow swappable layouts like next/prev links, split image and caption, etc. However a combination of skins and options may lead to unpredictable layouts, get yourself dirty. See main <a href="@url">README</a> for details on Skins. Only useful for custom work, and ignored/overridden by slick formatters or sub-modules.', ['@url' => $readme]),
100       '#attributes'    => $tooltip,
101       '#prefix'        => '<div class="form__header form__half form__half--last has-tooltip clearfix">',
102     ];
103
104     $form['group'] = [
105       '#type'          => 'select',
106       '#title'         => $this->t('Group'),
107       '#options'       => [
108         'main'      => t('Main'),
109         'overlay'   => t('Overlay'),
110         'thumbnail' => t('Thumbnail'),
111       ],
112       '#empty_option'  => $this->t('- None -'),
113       '#default_value' => $slick->getGroup(),
114       '#description'   => $this->t('Group this optionset to avoid confusion for optionset selections. Leave empty to make it available for all.'),
115       '#attributes'    => $tooltip,
116     ];
117
118     $form['breakpoints'] = [
119       '#title'         => $this->t('Breakpoints'),
120       '#type'          => 'textfield',
121       '#default_value' => $form_state->hasValue('breakpoints') ? $form_state->getValue('breakpoints') : $slick->getBreakpoints(),
122       '#description'   => $this->t('The number of breakpoints added to Responsive display, max 9. This is not Breakpoint Width (480px, etc).'),
123       '#ajax' => [
124         'callback' => '::addBreakpoints',
125         'wrapper'  => 'edit-breakpoints-ajax-wrapper',
126         'event'    => 'change',
127         'progress' => ['type' => 'fullscreen'],
128         'effect'   => 'fade',
129         'speed'    => 'fast',
130       ],
131       '#attributes' => $tooltip,
132       '#maxlength'  => 1,
133     ];
134
135     $form['optimized'] = [
136       '#type'          => 'checkbox',
137       '#title'         => $this->t('Optimized'),
138       '#default_value' => $slick->optimized(),
139       '#description'   => $this->t('Check to optimize the stored options. Anything similar to defaults will not be stored, except those required by sub-modules and theme_slick(). Like you hand-code/ cherry-pick the needed options, and are smart enough to not repeat defaults, and free up memory. The rest are taken care of by JS. Uncheck only if theme_slick() can not satisfy the needs, and more hand-coded preprocess is needed which is less likely in most cases.'),
140       '#access'        => $slick->id() != 'default',
141       '#attributes'    => $tooltip,
142       '#wrapper_attributes' => ['class' => ['form-item--tooltip-wide']],
143     ];
144
145     if ($slick->id() == 'default') {
146       $form['breakpoints']['#suffix'] = '</div>';
147     }
148     else {
149       $form['optimized']['#suffix'] = '</div>';
150     }
151
152     if ($admin_css) {
153       $form['optimized']['#field_suffix'] = '&nbsp;';
154       $form['optimized']['#title_display'] = 'before';
155     }
156
157     return parent::form($form, $form_state);
158   }
159
160   /**
161    * {@inheritdoc}
162    */
163   public function submitForm(array &$form, FormStateInterface $form_state) {
164     parent::submitForm($form, $form_state);
165
166     // Optimized if so configured.
167     $slick   = $this->entity;
168     $default = $slick->id() == 'default';
169     if (!$default && !$form_state->isValueEmpty('optimized')) {
170       $defaults = $slick::defaultSettings();
171       $options  = $form_state->getValue('options');
172       $required = $this->getOptionsRequiredByTemplate();
173       $main     = array_diff_assoc($defaults, $required);
174       $settings = $form_state->getValue(['options', 'settings']);
175
176       // Cast the values.
177       $this->typecastOptionset($settings);
178
179       // Remove wasted dependent options if disabled, empty or not.
180       $slick->removeWastedDependentOptions($settings);
181
182       $main_settings = array_diff_assoc($settings, $main);
183       $slick->setSettings($main_settings);
184
185       $responsive_options = ['options', 'responsives', 'responsive'];
186       if ($responsives = $form_state->getValue($responsive_options)) {
187         foreach ($responsives as $delta => &$responsive) {
188           if (!empty($responsive['unslick'])) {
189             $slick->setResponsiveSettings([], $delta);
190           }
191           else {
192             $this->typecastOptionset($responsive['settings']);
193             $slick->removeWastedDependentOptions($responsive['settings']);
194
195             $responsive_settings = array_diff_assoc($responsive['settings'], $defaults);
196             $slick->setResponsiveSettings($responsive_settings, $delta);
197           }
198         }
199       }
200     }
201   }
202
203   /**
204    * Overrides Drupal\Core\Entity\EntityFormController::save().
205    *
206    * @todo revert #1497268, or use config_update instead.
207    */
208   public function save(array $form, FormStateInterface $form_state) {
209     $slick = $this->entity;
210
211     // Prevent leading and trailing spaces in slick names.
212     $slick->set('label', trim($slick->label()));
213     $slick->set('id', $slick->id());
214
215     $status        = $slick->save();
216     $label         = $slick->label();
217     $edit_link     = $slick->toLink($this->t('Edit'), 'edit-form')->toString();
218     $config_prefix = $slick->getEntityType()->getConfigPrefix();
219     $message       = ['@config_prefix' => $config_prefix, '%label' => $label];
220
221     $notice = [
222       '@config_prefix' => $config_prefix,
223       '%label' => $label,
224       'link' => $edit_link,
225     ];
226
227     if ($status == SAVED_UPDATED) {
228       // If we edited an existing entity.
229       // @todo #2278383.
230       drupal_set_message($this->t('@config_prefix %label has been updated.', $message));
231       $this->logger('slick')->notice('@config_prefix %label has been updated.', $notice);
232     }
233     else {
234       // If we created a new entity.
235       drupal_set_message($this->t('@config_prefix %label has been added.', $message));
236       $this->logger('slick')->notice('@config_prefix %label has been added.', $notice);
237     }
238   }
239
240   /**
241    * Handles switching the breakpoints based on the input value.
242    */
243   public function addBreakpoints($form, FormStateInterface $form_state) {
244     if (!$form_state->isValueEmpty('breakpoints')) {
245       $form_state->setValue('breakpoints_count', $form_state->getValue('breakpoints'));
246       if ($form_state->getValue('breakpoints') >= 6) {
247         $message = $this->t('You are trying to load too many Breakpoints. Try reducing it to reasonable numbers say, between 1 to 5.');
248         drupal_set_message($message, 'warning');
249       }
250     }
251
252     return $form['responsives']['responsive'];
253   }
254
255   /**
256    * Returns the typecast values.
257    *
258    * @param array $settings
259    *   An array of Optionset settings.
260    */
261   public function typecastOptionset(array &$settings = []) {
262     if (empty($settings)) {
263       return;
264     }
265
266     $slick    = $this->entity;
267     $defaults = $slick::defaultSettings();
268
269     foreach ($defaults as $name => $value) {
270       if (isset($settings[$name])) {
271         // Seems double is ignored, and causes a missing schema, unlike float.
272         $type = gettype($defaults[$name]);
273         $type = $type == 'double' ? 'float' : $type;
274
275         // Change float to integer if value is no longer float.
276         if ($name == 'edgeFriction') {
277           $type = $settings[$name] == '1' ? 'integer' : 'float';
278         }
279
280         settype($settings[$name], $type);
281       }
282     }
283   }
284
285   /**
286    * List of all easing methods available from jQuery Easing v1.3.
287    *
288    * @return array
289    *   An array of available jQuery Easing options as fallback for browsers that
290    *   don't support pure CSS easing.
291    */
292   public function getJsEasingOptions() {
293     $easings = &drupal_static(__METHOD__, NULL);
294
295     if (!isset($easings)) {
296       $easings = array(
297         'linear'           => 'Linear',
298         'swing'            => 'Swing',
299         'easeInQuad'       => 'easeInQuad',
300         'easeOutQuad'      => 'easeOutQuad',
301         'easeInOutQuad'    => 'easeInOutQuad',
302         'easeInCubic'      => 'easeInCubic',
303         'easeOutCubic'     => 'easeOutCubic',
304         'easeInOutCubic'   => 'easeInOutCubic',
305         'easeInQuart'      => 'easeInQuart',
306         'easeOutQuart'     => 'easeOutQuart',
307         'easeInOutQuart'   => 'easeInOutQuart',
308         'easeInQuint'      => 'easeInQuint',
309         'easeOutQuint'     => 'easeOutQuint',
310         'easeInOutQuint'   => 'easeInOutQuint',
311         'easeInSine'       => 'easeInSine',
312         'easeOutSine'      => 'easeOutSine',
313         'easeInOutSine'    => 'easeInOutSine',
314         'easeInExpo'       => 'easeInExpo',
315         'easeOutExpo'      => 'easeOutExpo',
316         'easeInOutExpo'    => 'easeInOutExpo',
317         'easeInCirc'       => 'easeInCirc',
318         'easeOutCirc'      => 'easeOutCirc',
319         'easeInOutCirc'    => 'easeInOutCirc',
320         'easeInElastic'    => 'easeInElastic',
321         'easeOutElastic'   => 'easeOutElastic',
322         'easeInOutElastic' => 'easeInOutElastic',
323         'easeInBack'       => 'easeInBack',
324         'easeOutBack'      => 'easeOutBack',
325         'easeInOutBack'    => 'easeInOutBack',
326         'easeInBounce'     => 'easeInBounce',
327         'easeOutBounce'    => 'easeOutBounce',
328         'easeInOutBounce'  => 'easeInOutBounce',
329       );
330     }
331     return $easings;
332   }
333
334   /**
335    * List of available CSS easing methods.
336    *
337    * @param bool $map
338    *   Flag to output the array as is for further processing if TRUE.
339    *
340    * @return array
341    *   An array of CSS easings for select options, or all for the mappings.
342    *
343    * @see https://github.com/kenwheeler/slick/issues/118
344    * @see http://matthewlein.com/ceaser/
345    * @see http://www.w3.org/TR/css3-transitions/
346    */
347   public function getCssEasingOptions($map = FALSE) {
348     $css_easings = [];
349     $available_easings = array(
350
351       // Defaults/ Native.
352       'ease'           => 'ease|ease',
353       'linear'         => 'linear|linear',
354       'ease-in'        => 'ease-in|ease-in',
355       'ease-out'       => 'ease-out|ease-out',
356       'swing'          => 'swing|ease-in-out',
357
358       // Penner Equations (approximated).
359       'easeInQuad'     => 'easeInQuad|cubic-bezier(0.550, 0.085, 0.680, 0.530)',
360       'easeInCubic'    => 'easeInCubic|cubic-bezier(0.550, 0.055, 0.675, 0.190)',
361       'easeInQuart'    => 'easeInQuart|cubic-bezier(0.895, 0.030, 0.685, 0.220)',
362       'easeInQuint'    => 'easeInQuint|cubic-bezier(0.755, 0.050, 0.855, 0.060)',
363       'easeInSine'     => 'easeInSine|cubic-bezier(0.470, 0.000, 0.745, 0.715)',
364       'easeInExpo'     => 'easeInExpo|cubic-bezier(0.950, 0.050, 0.795, 0.035)',
365       'easeInCirc'     => 'easeInCirc|cubic-bezier(0.600, 0.040, 0.980, 0.335)',
366       'easeInBack'     => 'easeInBack|cubic-bezier(0.600, -0.280, 0.735, 0.045)',
367       'easeOutQuad'    => 'easeOutQuad|cubic-bezier(0.250, 0.460, 0.450, 0.940)',
368       'easeOutCubic'   => 'easeOutCubic|cubic-bezier(0.215, 0.610, 0.355, 1.000)',
369       'easeOutQuart'   => 'easeOutQuart|cubic-bezier(0.165, 0.840, 0.440, 1.000)',
370       'easeOutQuint'   => 'easeOutQuint|cubic-bezier(0.230, 1.000, 0.320, 1.000)',
371       'easeOutSine'    => 'easeOutSine|cubic-bezier(0.390, 0.575, 0.565, 1.000)',
372       'easeOutExpo'    => 'easeOutExpo|cubic-bezier(0.190, 1.000, 0.220, 1.000)',
373       'easeOutCirc'    => 'easeOutCirc|cubic-bezier(0.075, 0.820, 0.165, 1.000)',
374       'easeOutBack'    => 'easeOutBack|cubic-bezier(0.175, 0.885, 0.320, 1.275)',
375       'easeInOutQuad'  => 'easeInOutQuad|cubic-bezier(0.455, 0.030, 0.515, 0.955)',
376       'easeInOutCubic' => 'easeInOutCubic|cubic-bezier(0.645, 0.045, 0.355, 1.000)',
377       'easeInOutQuart' => 'easeInOutQuart|cubic-bezier(0.770, 0.000, 0.175, 1.000)',
378       'easeInOutQuint' => 'easeInOutQuint|cubic-bezier(0.860, 0.000, 0.070, 1.000)',
379       'easeInOutSine'  => 'easeInOutSine|cubic-bezier(0.445, 0.050, 0.550, 0.950)',
380       'easeInOutExpo'  => 'easeInOutExpo|cubic-bezier(1.000, 0.000, 0.000, 1.000)',
381       'easeInOutCirc'  => 'easeInOutCirc|cubic-bezier(0.785, 0.135, 0.150, 0.860)',
382       'easeInOutBack'  => 'easeInOutBack|cubic-bezier(0.680, -0.550, 0.265, 1.550)',
383     );
384
385     foreach ($available_easings as $key => $easing) {
386       list($readable_easing, $css_easing) = array_pad(array_map('trim', explode("|", $easing, 2)), 2, NULL);
387       $css_easings[$key] = $map ? $easing : $readable_easing;
388       unset($css_easing);
389     }
390     return $css_easings;
391   }
392
393   /**
394    * Defines options required by theme_slick(), used with optimized option.
395    */
396   public function getOptionsRequiredByTemplate() {
397     $options = [
398       'lazyLoad'     => 'ondemand',
399       'slidesToShow' => 1,
400     ];
401
402     $this->manager->getModuleHandler()->alter('slick_options_required_by_template', $options);
403     return $options;
404   }
405
406   /**
407    * Maps existing jQuery easing value to equivalent CSS easing methods.
408    *
409    * @param string $easing
410    *   The name of the human readable easing.
411    *
412    * @return string
413    *   A string of unfriendly bezier equivalent, or NULL.
414    */
415   public function getBezier($easing = NULL) {
416     $css_easing = '';
417     if ($easing) {
418       $easings = $this->getCssEasingOptions(TRUE);
419       list($readable_easing, $bezier) = array_pad(array_map('trim', explode("|", $easings[$easing], 2)), 2, NULL);
420       $css_easing = $bezier;
421       unset($readable_easing);
422     }
423     return $css_easing;
424   }
425
426 }