5 * Fonts.com API module file.
8 use Drupal\Core\Entity\EntityInterface;
9 use Drupal\Core\Form\FormStateInterface;
10 use Drupal\Component\Utility\Html;
11 use Drupal\Component\Utility\UrlHelper;
15 use Drupal\fontyourface\FontInterface;
16 use Drupal\fontyourface\FontDisplayInterface;
17 use Drupal\fontyourface\Entity\Font;
19 define('FONTSCOM_API_BASE_URL', 'https://api.fonts.com');
20 define('FONTSCOM_API_APP_KEY', '1fdb130c-d5c0-4fab-8e2b-271508570323932606');
23 * Implements hook_fontyourface_api().
25 function fontscom_api_fontyourface_api() {
28 'name' => 'Fonts.com',
33 * Implements hook_modules_installed().
35 * Use this hook instead of hook_install, because the route "font.settings" is
36 * not defined otherwise.
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()]));
45 * Implements hook_entity_presave().
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']])) {
57 fontscom_api_add_font_to_current_project($metadata['FontID']);
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']);
77 * Implements hook_form_alter().
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'),
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'),
92 if (!empty($config->get('token'))) {
93 $projects = fontscom_api_get_projects();
95 if (count($projects) > 0) {
96 $options = ['' => '-- Select a project --'];
97 foreach ($projects as $key => $project) {
98 $options[$project->ProjectKey] = Html::escape($project->ProjectName);
100 $form['fontscom_api']['fontscom_api_project'] = [
102 '#title' => t('Project'),
103 '#options' => $options,
104 '#default_value' => $config->get('project'),
109 $form['#submit'][] = 'fontscom_api_form_font_settings_submit';
113 * Submits Font settings form data.
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']);
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'));
129 * Implements hook_page_attachments().
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'][] = [
137 '#type' => 'html_tag',
140 'src' => 'https://fast.fonts.net/jsapi/' . $config->get('project') . '.js',
142 ], 'fontyourface-fontscom-api-' . $config->get('project'),
146 $enabled_fonts = &drupal_static('fontyourface_fonts', []);
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);
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);
162 $page['#attached']['html_head'][] = [
164 '#type' => 'html_tag',
167 'rel' => 'stylesheet',
168 'href' => file_create_url($directory_location . '/fontyourface-stylesheet-' . $hash . '.css'),
171 ], 'fontyourface-fontscom-api-preview-fonts',
177 * Implements hook_fontyourface_font_css().
179 function fontscom_api_fontyourface_font_css(FontInterface $font, FontDisplayInterface $font_style = NULL, $separator = ' ') {
180 if ($font->pid->value == 'fontscom_api') {
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;
188 $family_list = "'" . $font->css_family->value . "'";
191 if ($font_style !== NULL) {
192 if ($font_style->css_fallbacks) {
193 $family_list .= ', ' . $font_style->css_fallbacks;
197 $css[] = 'font-family: ' . $family_list . ';';
198 $css[] = 'font-style: 400;';
199 $css[] = 'font-weight: normal;';
201 return implode($separator, $css);
206 * Implements hook_fontyourface_import().
208 function fontscom_api_fontyourface_import($font_context = []) {
209 $context = $font_context;
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();
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,
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,
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);
244 $font = fontyourface_save_font($font_data);
245 if (isset($context['sandbox']['enabled_fonts'][$fontscom_font->FontID])) {
248 $context['sandbox']['font_count']++;
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'];
256 drupal_set_message(t('Imported @count fonts from fonts.com', ['@count' => $context['sandbox']['font_count']]));
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.'));
266 * Provides headers with api parameters.
268 * @param string $path
269 * Fonts.com API endpoint.
272 * Header with fonts.com token for API request.
274 function fontscom_api_headers($path) {
275 $config = \Drupal::config('fontscom_api.settings');
277 $fontscom_token = $config->get('token');
279 if (empty($fontscom_token)) {
283 list($public_key, $private_key) = explode('--', $fontscom_token);
285 $encoded = base64_encode(hash_hmac('md5', $public_key . '|' . $path, $private_key, TRUE));
286 $auth = urlencode($public_key . ':' . $encoded);
288 return ['Authorization' => $auth, 'AppKey' => FONTSCOM_API_APP_KEY];
293 * Returns list of projects.
295 function fontscom_api_get_projects() {
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());
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');
308 if ($data->Projects->TotalRecords > 0) {
309 $project = $data->Projects->Project;
310 $projects = fontscom_api_unknown_to_array($project);
316 * Returns an array, regardless of input.
318 * @param mixed $unknown
319 * A parameter of unknown type.
322 * If parameter is already an array, return as-is. Otherwise, return array
323 * with param as first value.
325 function fontscom_api_unknown_to_array($unknown) {
326 if (is_array($unknown)) {
334 * Gets a list of all fonts, in given range.
337 * Pager request start value.
339 * Pager request limit. Max 50.
342 * Array of fonts.com font objects.
344 function fontscom_api_get_all_fonts($start = 0, $limit = 50) {
351 'wfspstart' => $start,
352 'wfsplimit' => $limit,
356 $filters = fontscom_api_get_allowed_api_filters();
357 if ($filters->FreeOrPaid == 0) {
358 $query['wfsfree'] = 'true';
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());
367 catch (Exception $e) {
368 drupal_set_message(t('There was an error importing fonts from Fonts.com. Error: %error', ['%error' => $e->getMessage()]), 'error');
371 return $data->AllFonts->Font;
375 * Gets total font count.
378 * If the cache should be flushed and force an API request.
381 * Total number of fonts on fonts.com.
383 function fontscom_api_get_all_remote_fonts_count($reset = FALSE) {
385 if (!$reset && $cache = \Drupal::cache()->get('fontscom_api_remote_fonts_count')) {
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';
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());
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');
402 \Drupal::cache()->set('fontscom_api_remote_fonts_count', $data->AllFonts->TotalRecords);
403 return $data->AllFonts->TotalRecords;
407 * Retrieves list of allowed api filters.
410 * If the cache should be flushed and force an API request.
413 * Key-value store of allowed filters from fonts.com.
415 function fontscom_api_get_allowed_api_filters($reset = FALSE) {
417 if (!$reset && $cache = \Drupal::cache()->get('fontscom_api_allowed_api_filters')) {
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());
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');
430 \Drupal::cache()->set('fontscom_api_allowed_api_filters', $data->FilterValues);
431 return $data->FilterValues;
435 * Retrieves list of all enabled fonts from Fonts.com.
438 * Array of enabled fonts.com font objects.
440 function fontscom_api_get_all_enabled_fonts() {
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());
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');
453 foreach ($data->Fonts->Font as $font) {
454 $enabled_fonts[$font->FontID] = $font;
456 return $enabled_fonts;
460 * Adds font to fonts.com project package.
466 * True if font added successfully. FALSE otherwise.
468 function fontscom_api_add_font_to_current_project($fid) {
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),
476 'body' => 'wfsfid=' . $fid,
478 $data = json_decode((string) $response->getBody());
479 fontscom_api_publish_updated_project();
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');
489 * Removes font from fonts.com project package.
495 * True if font removed successfully. FALSE otherwise.
497 function fontscom_api_remove_font_from_current_project($fid) {
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();
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');
514 * Updates fonts.com project package so updated font list is used.
517 * True if projects updated successfully. FALSE otherwise.
519 function fontscom_api_publish_updated_project() {
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]);
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');
533 * Parses and adds additional data to fonts.com font object.
535 * @param object $fontscom_font
536 * Fonts.com font object.
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';
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;
549 if (stripos('Thin', $fontscom_font->FontName) !== FALSE) {
550 $fontscom_font->css_weight = 200;
552 if (stripos('Light', $fontscom_font->FontName) !== FALSE) {
553 $fontscom_font->css_weight = 300;
555 if (stripos('Medium', $fontscom_font->FontName) !== FALSE) {
556 $fontscom_font->css_weight = 500;
558 elseif (stripos('SemiBold', $fontscom_font->FontName) !== FALSE || stripos('Semi Bold', $fontscom_font->FontName) !== FALSE) {
559 $fontscom_font->css_weight = 600;
561 elseif (stripos('Bold', $fontscom_font->FontName) !== FALSE) {
562 $fontscom_font->css_weight = 700;
564 elseif (stripos('Heavy', $fontscom_font->FontName) !== FALSE) {
565 $fontscom_font->css_weight = 800;
567 elseif (stripos('Black', $fontscom_font->FontName) !== FALSE) {
568 $fontscom_font->css_weight = 900;
573 * Generates @font-face css for fonts.com font.
575 * @param Drupal\fontyourface\FontInterface $font
576 * Font compatible with FontInterface.
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";
587 if ($metadata['eot']) {
588 $data .= "src: url('{$metadata['eot']}');\n";
589 $lines[] = "url('{$metadata['eot']}?#iefix') format('embedded-opentype')";
591 if ($metadata['ttf']) {
592 $lines[] = "url('{$metadata['ttf']}') format('truetype')";
594 if ($metadata['woff']) {
595 $lines[] = "url('{$metadata['woff']}') format('woff')";
597 if ($metadata['svg']) {
598 $lines[] = "url('{$metadata['svg']}#{$css_family}') format('svg')";
601 $data .= 'src: ' . implode(', ', $lines) . ";\n";
602 $data .= "font-weight: normal;\n";
603 $data .= "font-style: normal;\n";
604 return $data . "}\n";