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