11e33491cd5c37f250d4198ba86af5542c4b7bba
[yaffs-website] / web / modules / contrib / fontyourface / modules / fontscom_api / fontscom_api.module
1 <?php
2
3 /**
4  * @file
5  * Fonts.com API module file.
6  */
7
8 use Drupal\Core\Entity\EntityInterface;
9 use Drupal\Core\Form\FormStateInterface;
10 use Drupal\Component\Utility\Html;
11 use Drupal\Component\Utility\UrlHelper;
12 use Drupal\Core\Link;
13 use GuzzleHttp\Psr7;
14
15 use Drupal\fontyourface\FontInterface;
16 use Drupal\fontyourface\FontDisplayInterface;
17 use Drupal\fontyourface\Entity\Font;
18
19 define('FONTSCOM_API_BASE_URL', 'https://api.fonts.com');
20 define('FONTSCOM_API_APP_KEY', '1fdb130c-d5c0-4fab-8e2b-271508570323932606');
21
22 /**
23  * Implements hook_fontyourface_api().
24  */
25 function fontscom_api_fontyourface_api() {
26   return [
27     'version' => '3',
28     'name' => 'Fonts.com',
29   ];
30 }
31
32 /**
33  * Implements hook_modules_installed().
34  *
35  * Use this hook instead of hook_install, because the route "font.settings" is
36  * not defined otherwise.
37  */
38 function fontscom_api_modules_installed($modules) {
39   if (in_array('fontscom_api', $modules)) {
40     drupal_set_message(t('Fonts.com settings needs to be set up in order to import fonts from Fonts.com. Please use @link to import Fonts.com fonts.', ['@link' => Link::createFromRoute('@font-your-face settings', 'font.settings')->toString()]));
41   }
42 }
43
44 /**
45  * Implements hook_entity_presave().
46  */
47 function fontscom_api_entity_presave(EntityInterface $entity) {
48   if ($entity instanceof Font) {
49     if ($entity->pid->value == 'fontscom_api' && $entity->isActivated()) {
50       $metadata = $entity->getMetadata();
51       $config = \Drupal::config('fontscom_api.settings');
52       $enabled_fonts = fontscom_api_get_all_enabled_fonts();
53       if (isset($enabled_fonts[$metadata['FontID']])) {
54         // Do nothing.
55       }
56       else {
57         fontscom_api_add_font_to_current_project($metadata['FontID']);
58       }
59     }
60     elseif ($entity->pid->value == 'fontscom_api' && $entity->isDeactivated()) {
61       if (!empty($entity->original)) {
62         $original_entity = $entity->original;
63         if ($original_entity->status->value != $entity->status->value) {
64           $metadata = $entity->getMetadata();
65           $config = \Drupal::config('fontscom_api.settings');
66           $enabled_fonts = fontscom_api_get_all_enabled_fonts();
67           if (isset($enabled_fonts[$metadata['FontID']])) {
68             fontscom_api_remove_font_from_current_project($metadata['FontID']);
69           }
70         }
71       }
72     }
73   }
74 }
75
76 /**
77  * Implements hook_form_alter().
78  */
79 function fontscom_api_form_font_settings_alter(&$form, FormStateInterface $form_state) {
80   $config = \Drupal::config('fontscom_api.settings');
81   $form['fontscom_api'] = [
82     '#type' => 'fieldset',
83     '#title' => t('FONTS.COM SETTINGS'),
84   ];
85   $form['fontscom_api']['fontscom_api_token'] = [
86     '#type' => 'textfield',
87     '#title' => t('Fonts.com Authentication Key'),
88     '#description' => t('Add your Fonts.com authentication key to import your projects. Available at <a target="_blank" href=":url">:url</a>', [':url' => 'https://www.fonts.com/account#authentification-section']),
89     '#default_value' => $config->get('token'),
90   ];
91
92   if (!empty($config->get('token'))) {
93     $projects = fontscom_api_get_projects();
94
95     if (count($projects) > 0) {
96       $options = ['' => '-- Select a project --'];
97       foreach ($projects as $key => $project) {
98         $options[$project->ProjectKey] = Html::escape($project->ProjectName);
99       }
100       $form['fontscom_api']['fontscom_api_project'] = [
101         '#type' => 'select',
102         '#title' => t('Project'),
103         '#options' => $options,
104         '#default_value' => $config->get('project'),
105         '#required' => TRUE,
106       ];
107     }
108   }
109   $form['#submit'][] = 'fontscom_api_form_font_settings_submit';
110 }
111
112 /**
113  * Submits Font settings form data.
114  */
115 function fontscom_api_form_font_settings_submit(&$form, FormStateInterface $form_state) {
116   $values = $form_state->getValues();
117   $config = \Drupal::configFactory()->getEditable('fontscom_api.settings');
118   $config->set('token', $values['fontscom_api_token']);
119   if (isset($values['fontscom_api_project'])) {
120     $config->set('project', $values['fontscom_api_project']);
121   }
122   $config->save();
123   fontscom_api_get_allowed_api_filters(TRUE);
124   fontscom_api_get_all_remote_fonts_count(TRUE);
125   drupal_set_message(t('Saved Fonts.com Authentication Key'));
126 }
127
128 /**
129  * Implements hook_page_attachments().
130  */
131 function fontscom_api_page_attachments(&$page) {
132   $config = \Drupal::config('fontscom_api.settings');
133   // Only get all fonts when we have set a project and token.
134   if (!empty($config->get('token')) && !empty($config->get('project'))) {
135     $page['#attached']['html_head'][] = [
136       [
137         '#type' => 'html_tag',
138         '#tag' => 'script',
139         '#attributes' => [
140           'src' => 'https://fast.fonts.net/jsapi/' . $config->get('project') . '.js',
141         ],
142       ], 'fontyourface-fontscom-api-' . $config->get('project'),
143     ];
144   }
145
146   $enabled_fonts = &drupal_static('fontyourface_fonts', []);
147   $font_css = '';
148   foreach ($enabled_fonts as $font) {
149     if ($font->pid->value == 'fontscom_api') {
150       if ($font->isDeactivated()) {
151         $font_css .= _fontscom_api_generate_font_css($font);
152       }
153     }
154   }
155   if (!empty($font_css)) {
156     $hash = hash('sha256', $font_css);
157     $directory_location = 'fontyourface/fontscom_api';
158     file_prepare_directory($directory_location, FILE_CREATE_DIRECTORY);
159     if (!file_exists($directory_location . '/fontyourface-stylesheet-' . $hash . '.css')) {
160       file_unmanaged_save_data($font_css, $directory_location . '/fontyourface-stylesheet-' . $hash . '.css', FILE_EXISTS_REPLACE);
161     }
162     $page['#attached']['html_head'][] = [
163       [
164         '#type' => 'html_tag',
165         '#tag' => 'link',
166         '#attributes' => [
167           'rel' => 'stylesheet',
168           'href' => file_create_url($directory_location . '/fontyourface-stylesheet-' . $hash . '.css'),
169           'media' => 'all',
170         ],
171       ], 'fontyourface-fontscom-api-preview-fonts',
172     ];
173   }
174 }
175
176 /**
177  * Implements hook_fontyourface_font_css().
178  */
179 function fontscom_api_fontyourface_font_css(FontInterface $font, FontDisplayInterface $font_style = NULL, $separator = ' ') {
180   if ($font->pid->value == 'fontscom_api') {
181     $css = [];
182
183     // Enclose font family definition in single quotes if not already enclosed.
184     if ($font->css_family->value[0] === "'") {
185       $family_list = $font->css_family->value;
186     }
187     else {
188       $family_list = "'" . $font->css_family->value . "'";
189     }
190
191     if ($font_style !== NULL) {
192       if ($font_style->css_fallbacks) {
193         $family_list .= ', ' . $font_style->css_fallbacks;
194       }
195     }
196
197     $css[] = 'font-family: ' . $family_list . ';';
198     $css[] = 'font-style: 400;';
199     $css[] = 'font-weight: normal;';
200
201     return implode($separator, $css);
202   }
203 }
204
205 /**
206  * Implements hook_fontyourface_import().
207  */
208 function fontscom_api_fontyourface_import($font_context = []) {
209   $context = $font_context;
210   $limit = 50;
211   $config = \Drupal::config('fontscom_api.settings');
212   // Only get all fonts when we have set a project and token.
213   if (!empty($config->get('token')) && !empty($config->get('project'))) {
214     if (empty($context['sandbox'])) {
215       $context['sandbox']['progress'] = 1;
216       $context['sandbox']['font_count'] = 0;
217       $context['sandbox']['max'] = ceil(fontscom_api_get_all_remote_fonts_count() / $limit);
218       $context['sandbox']['enabled_fonts'] = fontscom_api_get_all_enabled_fonts();
219     }
220     $fontscom_fonts = fontscom_api_get_all_fonts($context['sandbox']['progress'], $limit);
221     foreach ($fontscom_fonts as $fontscom_font) {
222       _fontscom_api_parse_imported_font($fontscom_font);
223       $font_data = new stdClass();
224       $font_data->metadata = [
225         'FontID' => $fontscom_font->FontID,
226         'eot' => $fontscom_font->EOTURL,
227         'svg' => $fontscom_font->SVGURL,
228         'ttf' => $fontscom_font->TTFURL,
229         'woff2' => $fontscom_font->WOFF2URL,
230         'woff' => $fontscom_font->WOFFURL,
231       ];
232       $font_data->provider = 'fontscom_api';
233       $font_data->name = $fontscom_font->name;
234       $font_data->url = 'https://www.fonts.com/fonts/' . $fontscom_font->FontID;
235       $font_data->css_family = $fontscom_font->FontCSSName;
236       $font_data->language = [
237         $fontscom_font->FontLanguage,
238       ];
239       $font_data->designer = $fontscom_font->Designer;
240       $font_data->foundry = $fontscom_font->FontFoundryName;
241       if (!empty($fontscom_font->Classification)) {
242         $font_data->classification = explode(',', $fontscom_font->Classification);
243       }
244       $font = fontyourface_save_font($font_data);
245       if (isset($context['sandbox']['enabled_fonts'][$fontscom_font->FontID])) {
246         $font->activate();
247       }
248       $context['sandbox']['font_count']++;
249     }
250     $context['message'] = "Working on batch {$context['sandbox']['progress']} of {$context['sandbox']['max']}";
251     $context['sandbox']['progress']++;
252     if ($context['sandbox']['progress'] < $context['sandbox']['max']) {
253       $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
254     }
255     else {
256       drupal_set_message(t('Imported @count fonts from fonts.com', ['@count' => $context['sandbox']['font_count']]));
257     }
258   }
259   else {
260     drupal_set_message(t('Due to the number of fonts, automated import from install for Fonts.com is disabled. Please enter your Authentication Key and Project ID first before trying to import fonts.'));
261   }
262   return $context;
263 }
264
265 /**
266  * Provides headers with api parameters.
267  *
268  * @param string $path
269  *   Fonts.com API endpoint.
270  *
271  * @return array
272  *   Header with fonts.com token for API request.
273  */
274 function fontscom_api_headers($path) {
275   $config = \Drupal::config('fontscom_api.settings');
276
277   $fontscom_token = $config->get('token');
278
279   if (empty($fontscom_token)) {
280     return [];
281   }
282
283   list($public_key, $private_key) = explode('--', $fontscom_token);
284
285   $encoded = base64_encode(hash_hmac('md5', $public_key . '|' . $path, $private_key, TRUE));
286   $auth = urlencode($public_key . ':' . $encoded);
287
288   return ['Authorization' => $auth, 'AppKey' => FONTSCOM_API_APP_KEY];
289
290 }
291
292 /**
293  * Returns list of projects.
294  */
295 function fontscom_api_get_projects() {
296   $projects = [];
297   try {
298     $path = '/rest/json/Projects/?wfspstart=0&wfsplimit=100';
299     $uri = FONTSCOM_API_BASE_URL . $path;
300     $response = \Drupal::httpClient()->get($uri, ['headers' => fontscom_api_headers($path), 'verify' => FALSE]);
301     $data = json_decode((string) $response->getBody());
302   }
303   catch (Exception $e) {
304     drupal_set_message(t('There was an error retrieving project list from Fonts.com. Error: %error', ['%error' => $e->getMessage()]), 'error');
305     return [];
306   }
307
308   if ($data->Projects->TotalRecords > 0) {
309     $project = $data->Projects->Project;
310     $projects = fontscom_api_unknown_to_array($project);
311   }
312   return $projects;
313 }
314
315 /**
316  * Returns an array, regardless of input.
317  *
318  * @param mixed $unknown
319  *   A parameter of unknown type.
320  *
321  * @return array
322  *   If parameter is already an array, return as-is. Otherwise, return array
323  *   with param as first value.
324  */
325 function fontscom_api_unknown_to_array($unknown) {
326   if (is_array($unknown)) {
327     return $unknown;
328   }
329
330   return [$unknown];
331 }
332
333 /**
334  * Gets a list of all fonts, in given range.
335  *
336  * @param int $start
337  *   Pager request start value.
338  * @param int $limit
339  *   Pager request limit. Max 50.
340  *
341  * @return array
342  *   Array of fonts.com font objects.
343  */
344 function fontscom_api_get_all_fonts($start = 0, $limit = 50) {
345   $result = [
346     'fonts' => [],
347     'count' => FALSE,
348   ];
349
350   $query = [
351     'wfspstart' => $start,
352     'wfsplimit' => $limit,
353     'wfsCSS' => 1,
354   ];
355
356   $filters = fontscom_api_get_allowed_api_filters();
357   if ($filters->FreeOrPaid == 0) {
358     $query['wfsfree'] = 'true';
359   }
360
361   try {
362     $path = '/rest/json/AllFonts/?' . UrlHelper::buildQuery($query);
363     $uri = FONTSCOM_API_BASE_URL . $path;
364     $response = \Drupal::httpClient()->get($uri, ['headers' => fontscom_api_headers($path), 'verify' => FALSE]);
365     $data = json_decode((string) $response->getBody());
366   }
367   catch (Exception $e) {
368     drupal_set_message(t('There was an error importing fonts from Fonts.com. Error: %error', ['%error' => $e->getMessage()]), 'error');
369     return FALSE;
370   }
371   return $data->AllFonts->Font;
372 }
373
374 /**
375  * Gets total font count.
376  *
377  * @param bool $reset
378  *   If the cache should be flushed and force an API request.
379  *
380  * @return int
381  *   Total number of fonts on fonts.com.
382  */
383 function fontscom_api_get_all_remote_fonts_count($reset = FALSE) {
384   $data = NULL;
385   if (!$reset && $cache = \Drupal::cache()->get('fontscom_api_remote_fonts_count')) {
386     return $cache->data;
387   }
388   try {
389     $filters = fontscom_api_get_allowed_api_filters();
390     $path = '/rest/json/AllFonts/?wfspstart=0&wfsplimit=1';
391     if ($filters->FreeOrPaid == 0) {
392       $path .= '&wfsfree=true';
393     }
394     $uri = FONTSCOM_API_BASE_URL . $path;
395     $response = \Drupal::httpClient()->get($uri, ['headers' => fontscom_api_headers($path), 'verify' => FALSE]);
396     $data = json_decode((string) $response->getBody());
397   }
398   catch (Exception $e) {
399     drupal_set_message(t('There was an error retrieving total Font count from Fonts.com. Error: %error', ['%error' => $e->getMessage()]), 'error');
400     return FALSE;
401   }
402   \Drupal::cache()->set('fontscom_api_remote_fonts_count', $data->AllFonts->TotalRecords);
403   return $data->AllFonts->TotalRecords;
404 }
405
406 /**
407  * Retrieves list of allowed api filters.
408  *
409  * @param bool $reset
410  *   If the cache should be flushed and force an API request.
411  *
412  * @return array
413  *   Key-value store of allowed filters from fonts.com.
414  */
415 function fontscom_api_get_allowed_api_filters($reset = FALSE) {
416   $data = NULL;
417   if (!$reset && $cache = \Drupal::cache()->get('fontscom_api_allowed_api_filters')) {
418     return $cache->data;
419   }
420   try {
421     $path = '/rest/json/AllFilterValues/';
422     $uri = FONTSCOM_API_BASE_URL . $path;
423     $response = \Drupal::httpClient()->get($uri, ['headers' => fontscom_api_headers($path), 'verify' => FALSE]);
424     $data = json_decode((string) $response->getBody());
425   }
426   catch (Exception $e) {
427     drupal_set_message(t('There was an error retrieving Font filters from Fonts.com. Error: %error', ['%error' => $e->getMessage()]), 'error');
428     return FALSE;
429   }
430   \Drupal::cache()->set('fontscom_api_allowed_api_filters', $data->FilterValues);
431   return $data->FilterValues;
432 }
433
434 /**
435  * Retrieves list of all enabled fonts from Fonts.com.
436  *
437  * @return array
438  *   Array of enabled fonts.com font objects.
439  */
440 function fontscom_api_get_all_enabled_fonts() {
441   try {
442     $config = \Drupal::config('fontscom_api.settings');
443     $path = '/rest/json/Fonts/?wfspid=' . $config->get('project');
444     $uri = FONTSCOM_API_BASE_URL . $path;
445     $response = \Drupal::httpClient()->get($uri, ['headers' => fontscom_api_headers($path), 'verify' => FALSE]);
446     $data = json_decode((string) $response->getBody());
447   }
448   catch (Exception $e) {
449     drupal_set_message(t('There was an error retrieving total Font count from Fonts.com. Error: %error', ['%error' => $e->getMessage()]), 'error');
450     return FALSE;
451   }
452   $enabled_fonts = [];
453   foreach ($data->Fonts->Font as $font) {
454     $enabled_fonts[$font->FontID] = $font;
455   }
456   return $enabled_fonts;
457 }
458
459 /**
460  * Adds font to fonts.com project package.
461  *
462  * @param int $fid
463  *   Fonts.com font ID.
464  *
465  * @return bool
466  *   True if font added successfully. FALSE otherwise.
467  */
468 function fontscom_api_add_font_to_current_project($fid) {
469   try {
470     $config = \Drupal::config('fontscom_api.settings');
471     $path = '/rest/json/Fonts/?wfspid=' . $config->get('project');
472     $uri = FONTSCOM_API_BASE_URL . $path;
473     $response = \Drupal::httpClient()->post($uri, [
474       'headers' => fontscom_api_headers($path),
475       'verify' => FALSE,
476       'body' => 'wfsfid=' . $fid,
477     ]);
478     $data = json_decode((string) $response->getBody());
479     fontscom_api_publish_updated_project();
480     return TRUE;
481   }
482   catch (Exception $e) {
483     drupal_set_message(t('There was an error adding font to Fonts.com project. Error: %error', ['%error' => Psr7\str($e->getResponse())]), 'error');
484     return FALSE;
485   }
486 }
487
488 /**
489  * Removes font from fonts.com project package.
490  *
491  * @param int $fid
492  *   Fonts.com font ID.
493  *
494  * @return bool
495  *   True if font removed successfully. FALSE otherwise.
496  */
497 function fontscom_api_remove_font_from_current_project($fid) {
498   try {
499     $config = \Drupal::config('fontscom_api.settings');
500     $path = '/rest/json/Fonts/?wfspid=' . $config->get('project') . '&wfsfid=' . $fid;
501     $uri = FONTSCOM_API_BASE_URL . $path;
502     $response = \Drupal::httpClient()->delete($uri, ['headers' => fontscom_api_headers($path), 'verify' => FALSE]);
503     $data = json_decode((string) $response->getBody());
504     fontscom_api_publish_updated_project();
505     return TRUE;
506   }
507   catch (Exception $e) {
508     drupal_set_message(t('There was an error removing font from Fonts.com project. Error: %error', ['%error' => Psr7\str($e->getResponse())]), 'error');
509     return FALSE;
510   }
511 }
512
513 /**
514  * Updates fonts.com project package so updated font list is used.
515  *
516  * @return bool
517  *   True if projects updated successfully. FALSE otherwise.
518  */
519 function fontscom_api_publish_updated_project() {
520   try {
521     $path = '/rest/json/Publish/';
522     $uri = FONTSCOM_API_BASE_URL . $path;
523     $response = \Drupal::httpClient()->get($uri, ['headers' => fontscom_api_headers($path), 'verify' => FALSE]);
524     return TRUE;
525   }
526   catch (Exception $e) {
527     drupal_set_message(t('There was an error publishing project on Fonts.com. Error: %error', ['%error' => Psr7\str($e->getResponse())]), 'error');
528     return FALSE;
529   }
530 }
531
532 /**
533  * Parses and adds additional data to fonts.com font object.
534  *
535  * @param object $fontscom_font
536  *   Fonts.com font object.
537  */
538 function _fontscom_api_parse_imported_font($fontscom_font) {
539   $fontscom_font->name = $fontscom_font->FontName;
540   $fontscom_font->css_style = 'normal';
541   if (stripos('Italic', $fontscom_font->FontName) !== FALSE) {
542     $fontscom_font->css_style = 'italic';
543   }
544
545   $fontscom_font->css_weight = 400;
546   if (stripos('Extra Light', $fontscom_font->FontName) !== FALSE || stripos('Ultra Light', $fontscom_font->FontName) !== FALSE) {
547     $fontscom_font->css_weight = 100;
548   }
549   if (stripos('Thin', $fontscom_font->FontName) !== FALSE) {
550     $fontscom_font->css_weight = 200;
551   }
552   if (stripos('Light', $fontscom_font->FontName) !== FALSE) {
553     $fontscom_font->css_weight = 300;
554   }
555   if (stripos('Medium', $fontscom_font->FontName) !== FALSE) {
556     $fontscom_font->css_weight = 500;
557   }
558   elseif (stripos('SemiBold', $fontscom_font->FontName) !== FALSE || stripos('Semi Bold', $fontscom_font->FontName) !== FALSE) {
559     $fontscom_font->css_weight = 600;
560   }
561   elseif (stripos('Bold', $fontscom_font->FontName) !== FALSE) {
562     $fontscom_font->css_weight = 700;
563   }
564   elseif (stripos('Heavy', $fontscom_font->FontName) !== FALSE) {
565     $fontscom_font->css_weight = 800;
566   }
567   elseif (stripos('Black', $fontscom_font->FontName) !== FALSE) {
568     $fontscom_font->css_weight = 900;
569   }
570 }
571
572 /**
573  * Generates @font-face css for fonts.com font.
574  *
575  * @param Drupal\fontyourface\FontInterface $font
576  *   Font compatible with FontInterface.
577  *
578  * @return string
579  *   CSS to load font.
580  */
581 function _fontscom_api_generate_font_css(FontInterface $font) {
582   $metadata = $font->getMetadata();
583   $data = "@font-face {\n";
584   $data .= "font-family: '{$font->css_family->value}';\n";
585   $lines = [];
586
587   if ($metadata['eot']) {
588     $data .= "src: url('{$metadata['eot']}');\n";
589     $lines[] = "url('{$metadata['eot']}?#iefix') format('embedded-opentype')";
590   }
591   if ($metadata['ttf']) {
592     $lines[] = "url('{$metadata['ttf']}') format('truetype')";
593   }
594   if ($metadata['woff']) {
595     $lines[] = "url('{$metadata['woff']}') format('woff')";
596   }
597   if ($metadata['svg']) {
598     $lines[] = "url('{$metadata['svg']}#{$css_family}') format('svg')";
599   }
600
601   $data .= 'src: ' . implode(', ', $lines) . ";\n";
602   $data .= "font-weight: normal;\n";
603   $data .= "font-style: normal;\n";
604   return $data . "}\n";
605 }