6677de68f203cc014f44a7ceccd23ac456a893c9
[yaffs-website] / web / modules / contrib / typogrify / src / Plugin / Filter / TypogrifyFilter.php
1 <?php
2
3 namespace Drupal\typogrify\Plugin\Filter;
4
5 use Drupal\typogrify\SmartyPants;
6 use Drupal\typogrify\Typogrify;
7 use Drupal\typogrify\UnicodeConversion;
8 use Drupal\Core\Form\FormStateInterface;
9 use Drupal\filter\FilterProcessResult;
10 use Drupal\filter\Plugin\FilterBase;
11 use Drupal\Core\Url;
12
13 /**
14  * Provides a filter to restrict images to site.
15  *
16  * @Filter(
17  *   id = "TypogrifyFilter",
18  *   title = @Translation("Typogrify"),
19  *   description = @Translation("Adds typographic refinements"),
20  *   type = Drupal\filter\Plugin\FilterInterface::TYPE_TRANSFORM_IRREVERSIBLE,
21  *   settings = {
22  *     "smartypants_enabled" = 1,
23  *     "smartypants_hyphens" = 3,
24  *     "space_hyphens" = 0,
25  *     "wrap_ampersand" = 1,
26  *     "widont_enabled"= 1,
27  *     "space_to_nbsp" = 1,
28  *     "hyphenate_shy" = 0,
29  *     "wrap_abbr" = 0,
30  *     "wrap_caps" = 1,
31  *     "wrap_initial_quotes" = 1,
32  *     "wrap_numbers" = 0,
33  *     "ligatures" = "a:0:{}",
34  *     "arrows" = "a:0:{}",
35  *     "fractions" = "a:0:{}",
36  *     "quotes" = "a:0:{}",
37  *   },
38  *   weight = 10
39  * )
40  */
41 class TypogrifyFilter extends FilterBase {
42
43   /**
44    * The keys in the settings array that are array-valued.
45    *
46    * @var array
47    */
48   protected static $arraySettingsKeys = array(
49     'ligatures',
50     'arrows',
51     'fractions',
52     'quotes',
53   );
54
55   /**
56    * Serialize array values.
57    *
58    * There must be a better way to do this, but it looks as though trying to
59    * save an array-valued plugin setting fails. Our solution is to serialize the
60    * settings before saving and unserialize them before using.
61    *
62    * Serialize $settings[$key] for each $key in $arraySettingsKeys.
63    *
64    * @param array &$settings
65    *   The array of plugin settings.
66    *
67    * @see settingsUnserialize()
68    */
69   protected static function settingsSerialize(array &$settings) {
70     foreach (static::$arraySettingsKeys as $key) {
71       if (isset($settings[$key]) && is_array($settings[$key])) {
72         $settings[$key] = serialize(array_filter($settings[$key]));
73       }
74     }
75   }
76
77   /**
78    * Unserialize array values.
79    *
80    * Unserialize $settings[$key] for each $key in $arraySettingsKeys.
81    *
82    * @param array &$settings
83    *   The array of plugin settings.
84    *
85    * @see settingsSerialize()
86    */
87   protected static function settingsUnserialize(array &$settings) {
88     foreach (static::$arraySettingsKeys as $key) {
89       if (isset($settings[$key]) && is_string($settings[$key])) {
90         $settings[$key] = unserialize($settings[$key]);
91       }
92     }
93   }
94
95   /**
96    * {@inheritdoc}
97    */
98   public function settingsForm(array $form, FormStateInterface $form_state) {
99     $settings = $this->settings;
100     static::settingsUnserialize($settings);
101
102     $form['help'] = array(
103       '#type' => 'markup',
104       '#value' => '<p>' . t('Enable the following typographic refinements:') . '</p>',
105     );
106
107     // Smartypants settings.
108     $form['smartypants_enabled'] = array(
109       '#type' => 'checkbox',
110       '#title' => t('Use typographers quotation marks and dashes (!smartylink)', array(
111         '!smartylink' => \Drupal::l('SmartyPants', Url::fromUri('http://daringfireball.net/projects/smartypants/')),
112       )),
113       '#default_value' => $settings['smartypants_enabled'],
114     );
115
116     // Smartypants hyphenation settings.
117     // Uses the same values as the parse attributes in the
118     // SmartyPants::process() function.
119     $form['smartypants_hyphens'] = array(
120       '#type' => 'select',
121       '#title' => t('Hyphenation settings for SmartyPants'),
122       '#default_value' => $settings['smartypants_hyphens'],
123       '#options' => array(
124         1 => t('“--” for em-dashes; no en-dash support'),
125         3 => t('“--” for em-dashes; “---” for en-dashes'),
126         2 => t('“---” for em-dashes; “--” for en-dashes'),
127       ),
128     );
129
130     // Replace space_hyphens with em-dash.
131     $form['space_hyphens'] = array(
132       '#type' => 'checkbox',
133       '#title' => t('Replace stand-alone dashes (normal dashes between whitespace) em-dashes.'),
134       '#description' => t('" - " will turn into " — ".'),
135       '#default_value' => $settings['space_hyphens'],
136     );
137
138     // Remove widows settings.
139     $form['widont_enabled'] = array(
140       '#type' => 'checkbox',
141       '#title' => t('Remove widows'),
142       '#default_value' => $settings['widont_enabled'],
143     );
144
145     // Remove widows settings.
146     $form['hyphenate_shy'] = array(
147       '#type' => 'checkbox',
148       '#title' => t('Replace <code>=</code> with <code>&amp;shy;</code>'),
149       '#description' => t('Words may be broken at the hyphenation points marked by “=”.'),
150       '#default_value' => $settings['hyphenate_shy'],
151     );
152
153     // Replace normal spaces with non-breaking spaces before "double punctuation
154     // marks". This is especially useful in french.
155     $form['space_to_nbsp'] = array(
156       '#type' => 'checkbox',
157       '#title' => t('Replace normal spaces with non-breaking spaces before "double punctuation marks" !marks.',
158         array('!marks' => '(<code>!?:;</code>)')),
159       '#description' => t('This is especially useful for french.'),
160       '#default_value' => $settings['space_to_nbsp'],
161     );
162
163     // Wrap caps settings.
164     $form['wrap_caps'] = array(
165       '#type' => 'checkbox',
166       '#title' => t('Wrap caps'),
167       '#default_value' => $settings['wrap_caps'],
168     );
169
170     // Wrap ampersand settings.
171     $form['wrap_ampersand'] = array(
172       '#type' => 'checkbox',
173       '#title' => t('Wrap ampersands'),
174       '#default_value' => $settings['wrap_ampersand'],
175     );
176
177     $form['wrap_abbr'] = array(
178       '#type' => 'select',
179       '#title' => t('Thin space in abbreviations'),
180       '#description' => t('Wraps abbreviations with !span and inserts space after the dots.', array('!span' => '<code>&lt;span class="abbr"&gt;…&lt;/span&gt;</code>')),
181       '#default_value' => $settings['wrap_abbr'],
182       '#options' => array(
183         0 => t('Do nothing'),
184         4 => t('Insert no space'),
185         1 => t('“U+202F“ Narrow no-break space'),
186         2 => t('“U+2009“ Thin space'),
187         3 => t('span with margin-left: 0.167em'),
188       ),
189     );
190
191     $form['wrap_numbers'] = array(
192       '#type' => 'select',
193       '#title' => t('Digit grouping in numbers'),
194       '#description' => t('Wraps numbers with !span and inserts thin space for digit grouping.', array('!span' => '<code>&lt;span class="number"&gt;…&lt;/span&gt;</code>')),
195       '#default_value' => $settings['wrap_numbers'],
196       '#options' => array(
197         0 => t('Do nothing'),
198         1 => t('“U+202F“ Narrow no-break space'),
199         2 => t('“U+2009“ Thin space'),
200         3 => t('span with margin-left: 0.167em'),
201         4 => t('just wrap numbers'),
202       ),
203     );
204
205     // Wrap initial quotes settings.
206     $form['wrap_initial_quotes'] = array(
207       '#type' => 'checkbox',
208       '#title' => t('Wrap quotation marks'),
209       '#default_value' => $settings['wrap_initial_quotes'],
210     );
211
212     // Ligature conversion settings.
213     $ligature_options = array();
214     foreach (UnicodeConversion::map('ligature') as $ascii => $unicode) {
215       $ligature_options[$ascii] = t('Convert <code>@ascii</code> to !unicode', array(
216         '@ascii' => $ascii,
217         '!unicode' => $unicode,
218       ));
219     }
220
221     $form['ligatures'] = array(
222       '#type' => 'checkboxes',
223       '#title' => t('Ligatures'),
224       '#options' => $ligature_options,
225       '#default_value' => $settings['ligatures'],
226     );
227
228     // Arrow conversion settings.
229     $arrow_options = array();
230     foreach (UnicodeConversion::map('arrow') as $ascii => $unicode) {
231       $arrow_options[$ascii] = t('Convert <code>@ascii</code> to !unicode', array(
232         '@ascii' => $this->unquote($ascii),
233         '!unicode' => $unicode,
234       ));
235
236     }
237
238     $form['arrows'] = array(
239       '#type' => 'checkboxes',
240       '#title' => t('Arrows'),
241       '#options' => $arrow_options,
242       '#default_value' => $settings['arrows'],
243     );
244
245     // Fraction conversion settings.
246     $fraction_options = array();
247     foreach (UnicodeConversion::map('fraction') as $ascii => $unicode) {
248       $fraction_options[$ascii] = t('Convert <code>@ascii</code> to !unicode', array(
249         '@ascii' => $ascii,
250         '!unicode' => $unicode,
251       ));
252
253     }
254
255     $form['fractions'] = array(
256       '#type' => 'checkboxes',
257       '#title' => t('Fractions'),
258       '#options' => $fraction_options,
259       '#default_value' => $settings['fractions'],
260     );
261
262     // Quotes conversion settings.
263     $quotes_options = array();
264     foreach (UnicodeConversion::map('quotes') as $quotes => $unicode) {
265       $quotes_options[$quotes] = t('Convert <code>@ascii</code> to !unicode', array(
266         '@ascii' => $this->unquote($quotes),
267         '!unicode' => $unicode,
268       ));
269     }
270
271     $form['quotes'] = array(
272       '#type' => 'checkboxes',
273       '#title' => t('Quotes'),
274       '#options' => $quotes_options,
275       '#default_value' => $settings['quotes'],
276     );
277
278     // Version Information Settings.
279     $version_strings = array();
280     $version_strings[] = t('SmartyPants PHP version: !version', array(
281       '!version' => \Drupal::l(SmartyPants::SMARTYPANTS_PHP_VERSION, Url::fromUri('http://www.michelf.com/projects/php-smartypants/')),
282     ));
283     $version_strings[] = t('PHP Typogrify Version: !version', array(
284       '!version' => \Drupal::l(PHP_TYPOGRIFY_VERSION, Url::fromUri('http://blog.hamstu.com/')),
285     ));
286
287     $form['info']['typogrify_status'] = [
288       '#theme' => 'item_list',
289       '#items' => $version_strings,
290       '#title' => t('Versions'),
291     ];
292
293     return $form;
294   }
295
296   /**
297    * {@inheritdoc}
298    */
299   public function setConfiguration(array $configuration) {
300     static::settingsSerialize($configuration['settings']);
301     parent::setConfiguration($configuration);
302   }
303
304   /**
305    * {@inheritdoc}
306    */
307   public function process($text, $langcode) {
308     $settings = $this->settings;
309     static::settingsUnserialize($settings);
310     $characters_to_convert = array();
311     $ctx = array();
312
313     if ($langcode == 'und') {
314       $language = \Drupal::languageManager()->getCurrentLanguage();
315
316       // @fixme, check language business for d8
317       $ctx['langcode'] = $language->language;
318     }
319     else {
320       $ctx['langcode'] = $langcode;
321     }
322
323     // Build a list of ligatures to convert.
324     foreach (UnicodeConversion::map('ligature') as $ascii => $unicode) {
325       if (isset($settings['ligatures'][$ascii]) && $settings['ligatures'][$ascii]) {
326         $characters_to_convert[] = $ascii;
327       }
328     }
329
330     // Wrap caps.
331     if ($settings['wrap_caps']) {
332       $text = Typogrify::caps($text);
333     }
334
335     // Build a list of arrows to convert.
336     foreach (UnicodeConversion::map('arrow') as $ascii => $unicode) {
337       $htmle = $this->unquote($ascii);
338       if ((isset($settings['arrows'][$ascii]) && $settings['arrows'][$ascii]) ||
339         (isset($settings['arrows'][$htmle]) && $settings['arrows'][$htmle])) {
340         $characters_to_convert[] = $ascii;
341       }
342     }
343
344     // Build a list of fractions to convert.
345     foreach (UnicodeConversion::map('fraction') as $ascii => $unicode) {
346       if (isset($settings['fractions'][$ascii]) && $settings['fractions'][$ascii]) {
347         $characters_to_convert[] = $ascii;
348       }
349     }
350
351     // Build a list of quotation marks to convert.
352     foreach (UnicodeConversion::map('quotes') as $ascii => $unicode) {
353       if (isset($settings['quotes'][$ascii]) && $settings['quotes'][$ascii]) {
354         $characters_to_convert[] = $ascii;
355       }
356     }
357
358     // Convert ligatures and arrows.
359     if (count($characters_to_convert) > 0) {
360       $text = UnicodeConversion::convertCharacters($text, $characters_to_convert);
361     }
362
363     // Wrap ampersands.
364     if ($settings['wrap_ampersand']) {
365       $text = SmartyPants::smartAmpersand($text);
366     }
367
368     // Smartypants formatting.
369     if ($settings['smartypants_enabled']) {
370       $text = SmartyPants::process($text, $settings['smartypants_hyphens'], $ctx);
371     }
372
373     // Wrap abbreviations.
374     if ($settings['wrap_abbr'] > 0) {
375       $text = SmartyPants::smartAbbreviation($text, $settings['wrap_abbr']);
376     }
377
378     // Wrap huge numbers.
379     if ($settings['wrap_numbers'] > 0) {
380       $text = SmartyPants::smartNumbers($text, $settings['wrap_numbers']);
381     }
382
383     // Wrap initial quotes.
384     if ($settings['wrap_initial_quotes']) {
385       $text = Typogrify::initial_quotes($text);
386     }
387
388     // Wrap initial quotes.
389     if ($settings['hyphenate_shy']) {
390       $text = SmartyPants::hyphenate($text);
391     }
392
393     // Remove widows.
394     if ($settings['widont_enabled']) {
395       $text = Typogrify::widont($text);
396     }
397
398     // Replace normal spaces with non-breaking spaces before "double punctuation
399     // marks". This is especially useful in french.
400     if (isset($settings['space_to_nbsp']) && $settings['space_to_nbsp']) {
401       $text = SmartyPants::spaceToNbsp($text);
402     }
403
404     // Replace normal whitespace '-' whitespace with em-dash.
405     if (isset($settings['space_hyphens']) && $settings['space_hyphens']) {
406       $text = SmartyPants::spaceHyphens($text);
407     }
408
409     return new FilterProcessResult($text);
410   }
411
412   /**
413    * {@inheritdoc}
414    */
415   public function tips($long = FALSE) {
416     $settings = $this->settings;
417
418     if ($long) {
419       $output = t('Typogrify.module brings the typographic refinements of Typogrify to Drupal.');
420       $output .= '<ul>';
421       if ($settings['wrap_ampersand']) {
422         $output .= '<li>' . t('Wraps ampersands (the “&amp;” character) with !span.', array('!span' => '<code>&lt;span class="amp"&gt;&amp;&lt;/span&gt;</code>')) . '</li>';
423       }
424       if ($settings['widont_enabled']) {
425         $output .= '<li>' . t("Prevents single words from wrapping onto their own line using Shaun Inman's Widont technique.") . '</li>';
426       }
427       if ($settings['wrap_initial_quotes']) {
428         $output .= '<li>' . t("Converts straight quotation marks to typographer's quotation marks, using SmartyPants.");
429         $output .= '</li><li>' . t('Wraps initial quotation marks with !quote or !dquote.', array(
430           '!quote' => '<code>&lt;span class="quo"&gt;&lt;/span&gt;</code>',
431           '!dquote' => '<code>&lt;span class="dquo"&gt;&lt;/span&gt;</code>',
432         )) . '</li>';
433       }
434       $output .= t('<li>Converts multiple hyphens to en dashes and em dashes (according to your preferences), using SmartyPants.</li>');
435       if ($settings['hyphenate_shy']) {
436         $output .= '<li>' . t('Words may be broken at the hyphenation points marked by “=”.') . '</li>';
437       }
438       if ($settings['wrap_abbr']) {
439         $output .= '<li>' . t('Wraps abbreviations as “e.g.” to !span and adds a thin space (1/6 em) after the dots.</li>', array('!span' => '<code>&lt;span class="abbr"&gt;e.g.&lt;/span&gt;</code>')) . '</li>';
440       }
441       if ($settings['wrap_numbers']) {
442         $output .= '<li>' . t('Wraps large numbers &gt; 1&thinsp;000 with !span and inserts thin space for digit grouping.', array('!span' => '<code>&lt;span class="number"&gt;…&lt;/span&gt;</code>')) . '</li>';
443       }
444       if ($settings['wrap_caps']) {
445         $output .= '<li>' . t('Wraps multiple capital letters with !span.', array('!span' => '<code>&lt;span class="caps"&gt;CAPS&lt;/span&gt;</code>')) . '</li>';
446       }
447       $output .= '<li>' . t('Adds a css style sheet that uses the &lt;span&gt; tags to substitute a showy ampersand in headlines, switch caps to small caps, and hang initial quotation marks.') . '</li>';
448       // Build a list of quotation marks to convert.
449       foreach (UnicodeConversion::map('quotes') as $ascii => $unicode) {
450         if ($settings['quotes'][$ascii]) {
451           $ascii_to_unicode .= t('Converts <code>!ascii</code> to !unicode', array(
452             '!ascii' => $ascii,
453             '!unicode' => $unicode,
454           ));
455           $output .= "<li>$ascii_to_unicode</li>\n";
456         }
457       }
458       $output .= '</ul>';
459     }
460     else {
461       $output = t('Typographic refinements will be added.');
462     }
463
464     return $output;
465   }
466
467   /**
468    * Helper function to unquote a string.
469    *
470    * Unquotes a string.
471    *
472    * @param string|array $text
473    *   String or array of strings to be unquoted.
474    *
475    * @return string|array
476    *   Original $text with simple '<' and '>' instead of HTML entities.
477    */
478   private function unquote($text) {
479     $text = str_replace(
480       array('&lt;', '&gt;'),
481       array('<', '>'),
482       $text);
483
484     return $text;
485   }
486
487 }