Projet

Général

Profil

Paste
Télécharger (21,1 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / themes / bootstrap / includes / cdn.inc @ eefc2ac0

1
<?php
2

    
3
/**
4
 * @file
5
 * cdn.inc
6
 *
7
 * Provides necessary CDN integration.
8
 */
9

    
10
define('BOOTSTRAP_CDN_PROVIDER_PATH', 'public://bootstrap/cdn_providers');
11

    
12
/**
13
 * Retrieves a list of available CDN providers for the Bootstrap framework.
14
 *
15
 * @param string $provider
16
 *   A specific provider data to return.
17
 * @param bool $reset
18
 *   Toggle determining whether or not to reset the database cache.
19
 *
20
 * @return array|false
21
 *   An associative array of CDN providers, keyed by their machine name if
22
 *   $provider is not set. If $provider is set and exists, its individual
23
 *   data array will be returned. If $provider is set and the data does not
24
 *   exist then FALSE will be returned.
25
 */
26
function bootstrap_cdn_provider($provider = NULL, $reset = FALSE) {
27
  $original_provider = $provider;
28

    
29
  // Use the advanced drupal_static() pattern, since this is called very often.
30
  static $drupal_static_fast;
31
  if (!isset($drupal_static_fast)) {
32
    $drupal_static_fast['providers'] = &drupal_static(__FUNCTION__);
33
  }
34
  $providers = &$drupal_static_fast['providers'];
35
  if ($reset || !isset($providers)) {
36
    $provider_path = BOOTSTRAP_CDN_PROVIDER_PATH;
37
    file_prepare_directory($provider_path, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
38

    
39
    $cid = 'theme_registry:bootstrap:cdn_providers';
40
    if (($cached = cache_get($cid)) && !empty($cached->data)) {
41
      $providers = $cached->data;
42
    }
43
    if ($reset || !isset($providers)) {
44
      $providers = array(
45
        'custom' => array(
46
          'title' => t('Custom'),
47
        ),
48
        'jsdelivr' => array(
49
          'api' => 'https://api.jsdelivr.com/v1/bootstrap/libraries',
50
          'title' => t('jsDelivr'),
51
          'description' => t('<p style="background:#EB4C36"><a href="!jsdelivr" target="_blank"><img src="//www.jsdelivr.com/img/logo.png" alt="jsDelivr Logo"/></a></p><p><a href="!jsdelivr" target="_blank">jsDelivr</a> is a free multi-CDN infrastructure that uses <a href="!maxcdn" target="_blank">MaxCDN</a>, <a href="!cloudflare" target="_blank">Cloudflare</a> and many others to combine their powers for the good of the open source community... <a href="!jsdelivr_about" target="_blank">read more</a></p>', array(
52
            '!jsdelivr' => 'https://www.jsdelivr.com',
53
            '!jsdelivr_about' => 'https://www.jsdelivr.com/about',
54
            '!maxcdn' => 'http://www.maxcdn.com',
55
            '!cloudflare' => 'http://www.cloudflare.com',
56
          )),
57
        ),
58
      );
59

    
60
      // Defaults properties each provider must have.
61
      $defaults = array(
62
        'api' => NULL,
63
        'css' => array(),
64
        'description' => NULL,
65
        'error' => FALSE,
66
        'js' => array(),
67
        'imported' => FALSE,
68
        'min' => array(
69
          'css' => array(),
70
          'js' => array(),
71
        ),
72
        'title' => NULL,
73
      );
74

    
75
      // Process the providers.
76
      foreach ($providers as $name => &$data) {
77
        $data += $defaults;
78
        $data['name'] = $name;
79
        if (empty($name)) {
80
          continue;
81
        }
82

    
83
        // Use manually imported API data, if it exists.
84
        $request = NULL;
85
        if (!empty($data['api']) && file_exists("$provider_path/$name.json") && ($imported_data = file_get_contents("$provider_path/$name.json"))) {
86
          $data['imported'] = TRUE;
87
          $request = new stdClass();
88
          $request->code = '200';
89
          $request->data = $imported_data;
90
        }
91
        // Otherwise, attempt to request API data if the provider has specified
92
        // an "api" URL to use.
93
        elseif (!empty($data['api'])) {
94
          $request = drupal_http_request($data['api']);
95
        }
96

    
97
        // Alter the specific provider.
98
        $function = 'bootstrap_bootstrap_cdn_provider_' . $name . '_alter';
99
        if (function_exists($function)) {
100
          $function($data, $request);
101
        }
102
      }
103
      cache_set($cid, $providers);
104
    }
105
  }
106
  if (isset($original_provider)) {
107
    if (!isset($providers[$original_provider])) {
108
      return FALSE;
109
    }
110
    return $providers[$original_provider];
111
  }
112
  return $providers;
113
}
114

    
115
/**
116
 * Callback for jsDelivr CDN.
117
 *
118
 * @param array $provider
119
 *   The provider data array, passed by reference.
120
 * @param \stdClass $request
121
 *   The raw request object, if the provider specified an "api" URL to retrieve
122
 *   data prior to this alter hook being called. It is up to whatever
123
 *   implements these hooks to parse the requested data.
124
 */
125
function bootstrap_bootstrap_cdn_provider_jsdelivr_alter(array &$provider, \stdClass $request = NULL) {
126
  $json = array();
127
  $provider['versions'] = array();
128
  $provider['themes'] = array();
129

    
130
  if ($request->code === '200' && !empty($request->data)) {
131
    $json = drupal_json_decode($request->data);
132
  }
133

    
134
  // Expected library names from jsDelivr API v1. Must use "twitter-bootstrap"
135
  // instead of "bootstrap" (which is just a folder alias).
136
  // @see https://www.drupal.org/node/2504343
137
  // @see https://github.com/jsdelivr/api/issues/94
138
  $bootstrap = 'twitter-bootstrap';
139
  $bootswatch = 'bootswatch';
140

    
141
  // Extract the raw asset files from the JSON data for each framework.
142
  $libraries = array();
143
  if ($json) {
144
    foreach ($json as $data) {
145
      if ($data['name'] === $bootstrap || $data['name'] === $bootswatch) {
146
        foreach ($data['assets'] as $asset) {
147
          if (preg_match('/^' . BOOTSTRAP_VERSION_MAJOR . '\.\d\.\d$/', $asset['version'])) {
148
            $libraries[$data['name']][$asset['version']] = $asset['files'];
149
          }
150
        }
151
      }
152
    }
153
  }
154

    
155
  // If the main bootstrap library could not be found, then provide defaults.
156
  if (!isset($libraries[$bootstrap])) {
157
    $provider['error'] = TRUE;
158
    $provider['versions'][BOOTSTRAP_VERSION] = BOOTSTRAP_VERSION;
159
    $provider['themes'][BOOTSTRAP_VERSION] = array(
160
      'bootstrap' => array(
161
        'title' => t('Bootstrap'),
162
        'css' => array('//cdn.jsdelivr.net/bootstrap/' . BOOTSTRAP_VERSION . '/css/bootstrap.css'),
163
        'js' => array('//cdn.jsdelivr.net/bootstrap/' . BOOTSTRAP_VERSION . '/js/bootstrap.js'),
164
        'min' => array(
165
          'css' => array('//cdn.jsdelivr.net/bootstrap/' . BOOTSTRAP_VERSION . '/css/bootstrap.min.css'),
166
          'js' => array('//cdn.jsdelivr.net/bootstrap/' . BOOTSTRAP_VERSION . '/js/bootstrap.min.js'),
167
        ),
168
      ),
169
    );
170
    return;
171
  }
172

    
173
  // Populate the provider array with the versions and themes available.
174
  foreach (array_keys($libraries[$bootstrap]) as $version) {
175
    $provider['versions'][$version] = $version;
176

    
177
    if (!isset($provider['themes'][$version])) {
178
      $provider['themes'][$version] = array();
179
    }
180

    
181
    // Extract Bootstrap themes.
182
    $provider['themes'][$version] = drupal_array_merge_deep($provider['themes'][$version], _bootstrap_cdn_provider_jsdelivr_extract_themes($libraries[$bootstrap][$version], "//cdn.jsdelivr.net/bootstrap/$version"));
183

    
184
    // Extract Bootswatch themes.
185
    if (isset($libraries[$bootswatch][$version])) {
186
      $provider['themes'][$version] = drupal_array_merge_deep($provider['themes'][$version], _bootstrap_cdn_provider_jsdelivr_extract_themes($libraries[$bootswatch][$version], "//cdn.jsdelivr.net/bootswatch/$version"));
187
    }
188
  }
189

    
190
  // Post process the themes to fill in any missing assets.
191
  foreach (array_keys($provider['themes']) as $version) {
192
    foreach (array_keys($provider['themes'][$version]) as $theme) {
193
      // Some themes actually require Bootstrap framework assets to still
194
      // function properly.
195
      if ($theme !== 'bootstrap') {
196
        foreach (array('css', 'js') as $type) {
197
          // Bootswatch themes include the Bootstrap framework in their CSS.
198
          // Skip the CSS portions.
199
          if ($theme !== 'bootstrap_theme' && $type === 'css') {
200
            continue;
201
          }
202
          if (!isset($provider['themes'][$version][$theme][$type]) && !empty($provider['themes'][$version]['bootstrap'][$type])) {
203
            $provider['themes'][$version][$theme][$type] = array();
204
          }
205
          $provider['themes'][$version][$theme][$type] = drupal_array_merge_deep($provider['themes'][$version]['bootstrap'][$type], $provider['themes'][$version][$theme][$type]);
206
          if (!isset($provider['themes'][$version][$theme]['min'][$type]) && !empty($provider['themes'][$version]['bootstrap']['min'][$type])) {
207
            $provider['themes'][$version][$theme]['min'][$type] = array();
208
          }
209
          $provider['themes'][$version][$theme]['min'][$type] = drupal_array_merge_deep($provider['themes'][$version]['bootstrap']['min'][$type], $provider['themes'][$version][$theme]['min'][$type]);
210
        }
211
      }
212
      // Some themes do not have a non-minified version, clone them to the
213
      // "normal" css/js arrays to ensure that the theme still loads if
214
      // aggregation (minification) is disabled.
215
      foreach (array('css', 'js') as $type) {
216
        if (!isset($provider['themes'][$version][$theme][$type]) && isset($provider['themes'][$version][$theme]['min'][$type])) {
217
          $provider['themes'][$version][$theme][$type] = $provider['themes'][$version][$theme]['min'][$type];
218
        }
219
      }
220
    }
221
  }
222
}
223

    
224
/**
225
 * Extracts theme information from files provided by the jsDelivr API.
226
 *
227
 * This will place the raw files into proper "css", "js" and "min" arrays
228
 * (if they exist) and prepends them with a base URL provided.
229
 *
230
 * @param array $files
231
 *   An array of files to process.
232
 * @param string $base_url
233
 *   The base URL each one of the $files are relative to, this usually
234
 *   should also include the version path prefix as well.
235
 *
236
 * @return array
237
 *   An associative array containing the following keys, if there were
238
 *   matching files found:
239
 *   - css
240
 *   - js
241
 *   - min:
242
 *     - css
243
 *     - js
244
 */
245
function _bootstrap_cdn_provider_jsdelivr_extract_themes(array $files, $base_url = '') {
246
  $themes = array();
247
  foreach ($files as $file) {
248
    preg_match('`([^/]*)/bootstrap(-theme)?(\.min)?\.(js|css)$`', $file, $matches);
249
    if (!empty($matches[1]) && !empty($matches[4])) {
250
      $path = $matches[1];
251
      $min = $matches[3];
252
      $filetype = $matches[4];
253

    
254
      // Determine the "theme" name.
255
      if ($path === 'css' || $path === 'js') {
256
        $theme = 'bootstrap';
257
        $title = t('Bootstrap');
258
      }
259
      else {
260
        $theme = $path;
261
        $title = ucfirst($path);
262
      }
263
      if ($matches[2]) {
264
        $theme = 'bootstrap_theme';
265
        $title = t('Bootstrap Theme');
266
      }
267

    
268
      $themes[$theme]['title'] = $title;
269
      if ($min) {
270
        $themes[$theme]['min'][$filetype][] = "$base_url/$path/bootstrap{$matches[2]}$min.$filetype";
271
      }
272
      else {
273
        $themes[$theme][$filetype][] = "$base_url/$path/bootstrap{$matches[2]}$min.$filetype";
274
      }
275
    }
276
  }
277
  return $themes;
278
}
279

    
280
/**
281
 * Callback for Custom CDN assets.
282
 */
283
function bootstrap_bootstrap_cdn_provider_custom_assets_alter(&$provider, $theme = NULL) {
284
  foreach (array('css', 'js') as $type) {
285
    if ($setting = bootstrap_setting('cdn_custom_' . $type, $theme)) {
286
      $provider[$type][] = $setting;
287
    }
288
    if ($setting = bootstrap_setting('cdn_custom_' . $type . '_min', $theme)) {
289
      $provider['min'][$type][] = $setting;
290
    }
291
  }
292
}
293

    
294
/**
295
 * Callback for jsDelivr CDN assets.
296
 */
297
function bootstrap_bootstrap_cdn_provider_jsdelivr_assets_alter(&$provider, $theme = NULL) {
298
  $error = !empty($provider['error']);
299
  $version = $error ? BOOTSTRAP_VERSION : bootstrap_setting('cdn_jsdelivr_version', $theme);
300
  $theme = $error ? 'bootstrap' : bootstrap_setting('cdn_jsdelivr_theme', $theme);
301
  if (isset($provider['themes'][$version][$theme])) {
302
    $provider = drupal_array_merge_deep($provider, $provider['themes'][$version][$theme]);
303
  }
304
}
305

    
306
/**
307
 * Custom callback for CDN provider settings.
308
 *
309
 * @see bootstrap_form_system_theme_settings_alter()
310
 */
311
function bootstrap_cdn_provider_settings_form(&$form, &$form_state, $theme) {
312
  // Retrieve the provider from form values or the setting.
313
  $provider = isset($form_state['values']['bootstrap_cdn_provider']) ? $form_state['values']['bootstrap_cdn_provider'] : bootstrap_setting('cdn_provider', $theme);
314

    
315
  // Intercept possible manual import of API data via AJAX callback.
316
  if (isset($form_state['clicked_button']['#value']) && $form_state['clicked_button']['#value'] === t('Save provider data')) {
317
    $provider_path = BOOTSTRAP_CDN_PROVIDER_PATH;
318
    file_prepare_directory($provider_path, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
319
    $provider_data = isset($form_state['values']['bootstrap_cdn_provider_import_data']) ? $form_state['values']['bootstrap_cdn_provider_import_data'] : FALSE;
320
    $provider_file = "$provider_path/$provider.json";
321
    if ($provider_data) {
322
      file_unmanaged_save_data($provider_data, $provider_file, FILE_EXISTS_REPLACE);
323
    }
324
    elseif ($provider_file && file_exists($provider_file)) {
325
      file_unmanaged_delete($provider_file);
326
    }
327
    bootstrap_cdn_provider(NULL, TRUE);
328
  }
329

    
330
  $description_label = t('NOTE');
331
  $description = t('Using one of the "CDN Provider" options below is the preferred method for loading Bootstrap CSS and JS on simpler sites that do not use a site-wide CDN. Using a "CDN Provider" for loading Bootstrap, however, does mean that it depends on a third-party service. There is no obligation or commitment by these third-parties that guarantees any up-time or service quality. If you need to customize Bootstrap and have chosen to compile the source code locally (served from this site), you must disable the "CDN Provider" option below by choosing "- None -" and alternatively enable a site-wide CDN implementation. All local (served from this site) versions of Bootstrap will be superseded by any enabled "CDN Provider" below. <strong>Do not do both</strong>.');
332
  $form['advanced']['cdn'] = array(
333
    '#type' => 'fieldset',
334
    '#title' => t('CDN (Content Delivery Network)'),
335
    '#description' => '<div class="alert alert-info messages warning"><strong>' . $description_label . ':</strong> ' . $description . '</div>',
336
    '#collapsible' => TRUE,
337
    '#collapsed' => !$provider,
338
  );
339

    
340
  $providers = bootstrap_cdn_provider();
341
  $options = array();
342
  foreach ($providers as $key => $data) {
343
    $options[$key] = $data['title'];
344
  }
345
  $form['advanced']['cdn']['bootstrap_cdn_provider'] = array(
346
    '#type' => 'select',
347
    '#title' => t('CDN Provider'),
348
    '#default_value' => $provider,
349
    '#empty_value' => '',
350
    '#options' => $options,
351
  );
352

    
353
  // Render each provider.
354
  foreach ($providers as $name => $data) {
355
    $form['advanced']['cdn']['provider'][$name] = array(
356
      '#type' => 'container',
357
      '#prefix' => '<div id="bootstrap-cdn-provider-' . $name . '">',
358
      '#suffix' => '</div>',
359
      '#states' => array(
360
        'visible' => array(
361
          ':input[name="bootstrap_cdn_provider"]' => array('value' => $name),
362
        ),
363
      ),
364
    );
365
    $element = &$form['advanced']['cdn']['provider'][$name];
366

    
367
    $element['description']['#markup'] = '<div class="lead">' . $data['description'] . '</div>';
368

    
369
    // Indicate there was an error retrieving the provider's API data.
370
    if (!empty($data['error']) || !empty($data['imported'])) {
371
      if (!empty($data['error'])) {
372
        $description_label = t('ERROR');
373
        $description = t('Unable to reach or parse the data provided by the @title API. Ensure the server this website is hosted on is able to initiate HTTP requests via <a href="!drupal_http_request" target="_blank">drupal_http_request()</a>. If the request consistently fails, it is likely that there are certain PHP functions that have been disabled by the hosting provider for security reasons. It is possible to manually copy and paste the contents of the following URL into the "Imported @title data" section below.<br /><br /><a href="!provider_api" target="_blank">!provider_api</a>.', array(
374
          '@title' => $data['title'],
375
          '!provider_api' => $data['api'],
376
          '!drupal_http_request' => 'https://api.drupal.org/api/drupal/includes%21common.inc/function/drupal_http_request/7',
377
        ));
378
        $element['#prefix'] .= '<div class="alert alert-danger messages error"><strong>' . $description_label . ':</strong> ' . $description . '</div>';
379
      }
380
      $element['import'] = array(
381
        '#type' => 'fieldset',
382
        '#title' => t('Imported @title data', array(
383
          '@title' => $data['title'],
384
        )),
385
        '#description' => t('The provider will attempt to parse the data entered here each time it is saved. If no data has been entered, any saved files associated with this provider will be removed and the provider will again attempt to request the API data normally through the following URL: <a href="!provider_api" target="_blank">!provider_api</a>.', array(
386
          '!provider_api' => $data['api'],
387
        )),
388
        '#weight' => 10,
389
        '#collapsible' => TRUE,
390
        '#collapsed' => TRUE,
391
      );
392
      $element['import']['bootstrap_cdn_provider_import_data'] = array(
393
        '#type' => 'textarea',
394
        '#default_value' => file_exists(BOOTSTRAP_CDN_PROVIDER_PATH . '/' . $name . '.json') ? file_get_contents(BOOTSTRAP_CDN_PROVIDER_PATH . '/' . $name . '.json') : NULL,
395
      );
396
      $element['import']['submit'] = array(
397
        '#type' => 'submit',
398
        '#value' => t('Save provider data'),
399
        '#executes_submit_callback' => FALSE,
400
        '#ajax' => array(
401
          'callback' => 'bootstrap_cdn_provider_settings_form_ajax_reload_provider',
402
          'wrapper' => 'bootstrap-cdn-provider-' . $name,
403
        ),
404
      );
405
    }
406

    
407
    // Alter the specific provider.
408
    $function = 'bootstrap_bootstrap_cdn_provider_' . $name . '_settings_form_alter';
409
    if (function_exists($function)) {
410
      $function($element, $form_state, $data, $theme);
411
    }
412
  }
413
}
414

    
415
/**
416
 * AJAX callback for reloading CDN provider elements.
417
 */
418
function bootstrap_cdn_provider_settings_form_ajax_reload_provider($form, $form_state) {
419
  return $form['advanced']['cdn']['provider'][$form_state['values']['bootstrap_cdn_provider']];
420
}
421

    
422
/**
423
 * Implements hook_bootstrap_cdn_provider_PROVIDER_settings_form_alter().
424
 */
425
function bootstrap_bootstrap_cdn_provider_custom_settings_form_alter(&$element, &$form_state, $provider = array(), $theme = NULL) {
426
  foreach (array('css', 'js') as $type) {
427
    $setting = bootstrap_setting('cdn_custom_' . $type, $theme);
428
    $setting_min = bootstrap_setting('cdn_custom_' . $type . '_min', $theme);
429
    $element['bootstrap_cdn_custom_' . $type] = array(
430
      '#type' => 'textfield',
431
      '#title' => t('Bootstrap @type URL', array(
432
        '@type' => drupal_strtoupper($type),
433
      )),
434
      '#description' => t('It is best to use protocol relative URLs (i.e. without http: or https:) here as it will allow more flexibility if the need ever arises.'),
435
      '#default_value' => $setting,
436
    );
437
    $element['bootstrap_cdn_custom_' . $type . '_min'] = array(
438
      '#type' => 'textfield',
439
      '#title' => t('Minified Bootstrap @type URL', array(
440
        '@type' => drupal_strtoupper($type),
441
      )),
442
      '#description' => t('Additionally, you can provide the minimized version of the file. It will be used instead if site aggregation is enabled.'),
443
      '#default_value' => $setting_min,
444
    );
445
  }
446
}
447

    
448
/**
449
 * Implements hook_bootstrap_cdn_provider_PROVIDER_settings_form_alter().
450
 */
451
function bootstrap_bootstrap_cdn_provider_jsdelivr_settings_form_alter(&$element, &$form_state, $provider = array(), $theme = NULL) {
452
  $version = isset($form_state['values']['bootstrap_cdn_jsdelivr_version']) ? $form_state['values']['bootstrap_cdn_jsdelivr_version'] : bootstrap_setting('cdn_jsdelivr_version', $theme);
453
  $element['bootstrap_cdn_jsdelivr_version'] = array(
454
    '#type' => 'select',
455
    '#title' => t('Version'),
456
    '#options' => isset($provider['versions']) ? $provider['versions'] : array(),
457
    '#default_value' => $version,
458
    '#ajax' => array(
459
      'callback' => 'bootstrap_cdn_provider_settings_form_ajax_reload_provider',
460
      'wrapper' => 'bootstrap-cdn-provider-jsdelivr',
461
    ),
462
  );
463
  if (empty($provider['error']) && empty($provider['imported'])) {
464
    $element['bootstrap_cdn_jsdelivr_version']['#description'] = t('These versions are automatically populated by the @provider API upon cache clear and newer versions may appear over time. It is highly recommended the version that the site was built with stays at that version. Until a newer version has been properly tested for updatability by the site maintainer, you should not arbitrarily "update" just because there is a newer version. This can cause many inconsistencies and undesired effects with an existing site.', array(
465
      '@provider' => $provider['title'],
466
    ));
467
  }
468

    
469
  // Bootswatch.
470
  $themes = array();
471
  if (isset($provider['themes'][$version])) {
472
    foreach ($provider['themes'][$version] as $_theme => $data) {
473
      $themes[$_theme] = $data['title'];
474
    }
475
  }
476
  $element['bootstrap_cdn_jsdelivr_theme'] = array(
477
    '#type' => 'select',
478
    '#title' => t('Theme'),
479
    '#description' => t('Choose the example <a href="!bootstrap_theme" target="_blank">Bootstrap Theme</a> provided by Bootstrap or one of the many, many <a href="!bootswatch" target="_blank">Bootswatch</a> themes!', array(
480
      '!bootswatch' => 'https://bootswatch.com',
481
      '!bootstrap_theme' => 'https://getbootstrap.com/docs/3.3/examples/theme/',
482
    )),
483
    '#default_value' => bootstrap_setting('cdn_jsdelivr_theme', $theme),
484
    '#options' => $themes,
485
    '#empty_option' => t('Bootstrap (default)'),
486
    '#empty_value' => 'bootstrap',
487
    '#suffix' => '<div id="bootstrap-theme-preview"></div>',
488
  );
489
}