From 1ba4f9a24c009eed561c21d8f5c3e2774667d5ca Mon Sep 17 00:00:00 2001
From: markcarver <markcarver@501638.no-reply.drupal.org>
Date: Tue, 18 Dec 2018 13:18:03 -0600
Subject: [PATCH] Issue #3020589 by markcarver, cilefen: Default to Bootstrap
 3.4.0

---
 README.md                                     |  12 +-
 bootstrap.drush.inc                           |   2 +-
 docs/Contributing.md                          |   2 +-
 docs/Getting-Started.md                       |  12 +-
 docs/README.md                                |   2 +-
 docs/Sub-Theming.md                           |   2 +-
 docs/Theme-Settings.md                        |  23 +--
 grunt/compile.js                              |  11 +-
 grunt/sync.js                                 | 174 +++++++++++-------
 icons.inc                                     |   2 +-
 js/theme-settings.js                          |   2 +-
 package.json                                  |   7 +-
 src/Bootstrap.php                             |   5 +-
 src/Plugin/Preprocess/FormElement.php         |   2 +-
 .../Setting/Advanced/Cdn/CdnCustomCss.php     |   2 +-
 .../Setting/Advanced/Cdn/CdnCustomCssMin.php  |   2 +-
 .../Setting/Advanced/Cdn/CdnCustomJs.php      |   2 +-
 .../Setting/Advanced/Cdn/CdnCustomJsMin.php   |   2 +-
 .../Setting/Advanced/Cdn/CdnJsdelivrTheme.php |   2 +-
 .../Setting/Components/Region/RegionWells.php |   2 +-
 .../General/Buttons/ButtonColorize.php        |   2 +-
 .../General/Container/FluidContainer.php      |   2 +-
 .../Setting/General/Images/ImageShape.php     |   2 +-
 .../JavaScript/Modals/ModalEnabled.php        |   2 +-
 .../JavaScript/Tooltips/TooltipEnabled.php    |   2 +-
 starterkits/cdn/README.md                     |   2 +-
 starterkits/less/README.md                    |   2 +-
 starterkits/sass/README.md                    |   2 +-
 yarn.lock                                     |  46 ++++-
 29 files changed, 213 insertions(+), 119 deletions(-)

diff --git a/README.md b/README.md
index 7abd6d20..91a2c752 100644
--- a/README.md
+++ b/README.md
@@ -15,11 +15,11 @@ This base theme bridges the gap between Drupal and the [Bootstrap Framework].
 - Extensive integration and template/preprocessor overrides for most of the
   [Bootstrap Framework] CSS, Components and JavaScript
 - Theme settings to further enhance the Drupal Bootstrap integration:
-  - [Breadcrumbs](https://getbootstrap.com/docs/3.3/components/#breadcrumbs)
-  - [Navbar](https://getbootstrap.com/docs/3.3/components/#navbar)
-  - [Popovers](https://getbootstrap.com/docs/3.3/javascript/#popovers)
-  - [Tooltips](https://getbootstrap.com/docs/3.3/javascript/#tooltips)
-  - [Wells](https://getbootstrap.com/docs/3.3/components/#wells) (per region)
+  - [Breadcrumbs](https://getbootstrap.com/docs/3.4/components/#breadcrumbs)
+  - [Navbar](https://getbootstrap.com/docs/3.4/components/#navbar)
+  - [Popovers](https://getbootstrap.com/docs/3.4/javascript/#popovers)
+  - [Tooltips](https://getbootstrap.com/docs/3.4/javascript/#tooltips)
+  - [Wells](https://getbootstrap.com/docs/3.4/components/#wells) (per region)
 
 ### Documentation
 Visit the project's [official documentation site](https://drupal-bootstrap.org)
@@ -41,4 +41,4 @@ or the markdown files inside the `./docs` directory.
 ### 5 Year Evolution (gource)
 https://youtu.be/Cvq6MPJp2dI
 
-[Bootstrap Framework]: https://getbootstrap.com/docs/3.3/
+[Bootstrap Framework]: https://getbootstrap.com/docs/3.4/
diff --git a/bootstrap.drush.inc b/bootstrap.drush.inc
index 9f2b6755..981c58bd 100644
--- a/bootstrap.drush.inc
+++ b/bootstrap.drush.inc
@@ -116,7 +116,7 @@ function _drush_bootstrap_generate_docs_settings(Theme $bootstrap) {
   // Ensure we have link references at the bottom.
   $output[] = '';
   $output[] = '[Drupal Bootstrap]: https://www.drupal.org/project/bootstrap';
-  $output[] = '[Bootstrap Framework]: https://getbootstrap.com/docs/3.3/';
+  $output[] = '[Bootstrap Framework]: https://getbootstrap.com/docs/3.4/';
 
   // Save the generated output to the appropriate file.
   return file_put_contents(realpath($bootstrap->getPath() . '/docs/Theme-Settings.md'), implode("\n", $output)) !== FALSE;
diff --git a/docs/Contributing.md b/docs/Contributing.md
index e37f40ce..32abc3ed 100644
--- a/docs/Contributing.md
+++ b/docs/Contributing.md
@@ -65,4 +65,4 @@ Please keep in mind though, this **IS NOT** a "support" channel. It's primary
 use is to discuss issues and to help fix bugs with the base theme itself.
 
 [Drupal Bootstrap]: https://www.drupal.org/project/bootstrap
-[Bootstrap Framework]: https://getbootstrap.com/docs/3.3/
+[Bootstrap Framework]: https://getbootstrap.com/docs/3.4/
diff --git a/docs/Getting-Started.md b/docs/Getting-Started.md
index e646d32f..11d6fa74 100644
--- a/docs/Getting-Started.md
+++ b/docs/Getting-Started.md
@@ -12,16 +12,16 @@ Generally speaking, you should really read the entire [Bootstrap Framework]
 documentation site, if you haven't already. Here are the four basic "sections"
 that site is split into:
 
-- [Getting Started](https://getbootstrap.com/docs/3.3/getting-started) - An overview of
+- [Getting Started](https://getbootstrap.com/docs/3.4/getting-started) - An overview of
   the [Bootstrap Framework], how to download and use, basic templates and
   examples, and more.
-- [CSS](https://getbootstrap.com/docs/3.3/css/) - Global CSS settings, fundamental HTML
+- [CSS](https://getbootstrap.com/docs/3.4/css/) - Global CSS settings, fundamental HTML
   elements styled and enhanced with extensible classes, and an advanced grid
   system.
-- [Components](https://getbootstrap.com/docs/3.3/components/) - Over a dozen reusable
+- [Components](https://getbootstrap.com/docs/3.4/components/) - Over a dozen reusable
   components built to provide iconography, dropdowns, input groups, navigation,
   alerts, and much more.
-- [JavaScript](https://getbootstrap.com/docs/3.3/javascript/) - Bring the
+- [JavaScript](https://getbootstrap.com/docs/3.4/javascript/) - Bring the
   [Bootstrap Framework] components to life with over a dozen custom jQuery
   plugins. Easily include them all, or one by one.
 
@@ -75,7 +75,7 @@ you are attempting to integrate the base theme with.
 **A: No, not "officially"**
 
 The [Bootstrap Framework] itself does not officially support older Internet
-Explorer [compatibility modes](https://getbootstrap.com/docs/3.3/getting-started/#support-ie-compatibility-modes).
+Explorer [compatibility modes](https://getbootstrap.com/docs/3.4/getting-started/#support-ie-compatibility-modes).
 To ensure you are using the latest rendering mode for IE, consider installing
 the [HTML5 Tools](https://www.drupal.org/project/html5_tools) module.
 
@@ -123,5 +123,5 @@ Instead, you should create a custom sub-theme that isn't hosted on Drupal.org.
 [Respond.js]: https://github.com/scottjehl/Respond
 [Drush]: http://www.drush.org
 [Drupal Bootstrap]: https://www.drupal.org/project/bootstrap
-[Bootstrap Framework]: https://getbootstrap.com/docs/3.3/
+[Bootstrap Framework]: https://getbootstrap.com/docs/3.4/
 [jQuery Update]: https://www.drupal.org/project/jquery_update
diff --git a/docs/README.md b/docs/README.md
index 237fd909..e495b101 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -52,7 +52,7 @@ documentation. For clarity, we will always attempt to use this word verbosely
 in one of the following ways:
 
 - **[Drupal Bootstrap]** refers to the Drupal base theme project.
-- **[Bootstrap Framework](https://getbootstrap.com/docs/3.3/)** refers to the external
+- **[Bootstrap Framework](https://getbootstrap.com/docs/3.4/)** refers to the external
   front end framework.
 - **[drupal_bootstrap](https://api.drupal.org/apis/drupal_bootstrap)** refers
   to Drupal's bootstrapping process or phase.
diff --git a/docs/Sub-Theming.md b/docs/Sub-Theming.md
index 78db638e..699fd1e8 100644
--- a/docs/Sub-Theming.md
+++ b/docs/Sub-Theming.md
@@ -61,7 +61,7 @@ enabled your starterkit, please refer to the starterkit's documentation page
 to customize.
 
 [Drupal Bootstrap]: https://www.drupal.org/project/bootstrap
-[Bootstrap Framework]: https://getbootstrap.com/docs/3.3/
+[Bootstrap Framework]: https://getbootstrap.com/docs/3.4/
 [jsDelivr CDN]: http://www.jsdelivr.com
 [Less]: http://lesscss.org
 [Sass]: http://sass-lang.com
diff --git a/docs/Theme-Settings.md b/docs/Theme-Settings.md
index 732f5bb9..23238420 100644
--- a/docs/Theme-Settings.md
+++ b/docs/Theme-Settings.md
@@ -96,7 +96,7 @@ flexibility if the need ever arises.
       </div>
       <pre class=" language-yaml"><code>
 cdn_custom_css:
-'https://cdn.jsdelivr.net/bootstrap/3.3.7/css/bootstrap.css'
+'https://cdn.jsdelivr.net/npm/bootstrap@3.4.0/dist/css/bootstrap.css'
 
 </code></pre>
     </td>
@@ -112,7 +112,7 @@ used instead if site aggregation is enabled.
       </div>
       <pre class=" language-yaml"><code>
 cdn_custom_css_min:
-'https://cdn.jsdelivr.net/bootstrap/3.3.7/css/bootstrap.min.css'
+'https://cdn.jsdelivr.net/npm/bootstrap@3.4.0/dist/css/bootstrap.min.css'
 
 </code></pre>
     </td>
@@ -127,7 +127,8 @@ It is best to use <code>https</code> protocols here as it will allow more
 flexibility if the need ever arises.
       </div>
       <pre class=" language-yaml"><code>
-cdn_custom_js: 'https://cdn.jsdelivr.net/bootstrap/3.3.7/js/bootstrap.js'
+cdn_custom_js:
+'https://cdn.jsdelivr.net/npm/bootstrap@3.4.0/dist/js/bootstrap.js'
 
 </code></pre>
     </td>
@@ -143,7 +144,7 @@ used instead if site aggregation is enabled.
       </div>
       <pre class=" language-yaml"><code>
 cdn_custom_js_min:
-'https://cdn.jsdelivr.net/bootstrap/3.3.7/js/bootstrap.min.js'
+'https://cdn.jsdelivr.net/npm/bootstrap@3.4.0/dist/js/bootstrap.min.js'
 
 </code></pre>
     </td>
@@ -157,7 +158,7 @@ cdn_jsdelivr_version
 Choose the Bootstrap version from jsdelivr
       </div>
       <pre class=" language-yaml"><code>
-cdn_jsdelivr_version: 3.3.7
+cdn_jsdelivr_version: 3.4.0
 
 </code></pre>
     </td>
@@ -673,10 +674,9 @@ modal_jquery_ui_bridge
     <td>
       <div class="help-block">
 Enabling this replaces the core/jquery.ui.dialog dependency in the
-core/drupal.dialog library with a bridge. This bridge adds support to
-Bootstrap Modals so that it may interpret jQuery UI Dialog functionality.
-It is highly recommended that this remain enabled unless you know what
-you're really doing.
+core/drupal.dialog library with a jQuery UI Dialog widget bridge. This
+bridge adds support to Bootstrap Modals so that it may interpret jQuery UI
+Dialog functionality.
       </div>
       <pre class=" language-yaml"><code>
 modal_jquery_ui_bridge: 1
@@ -720,7 +720,8 @@ modal_focus_input
     <td>
       <div class="help-block">
 Enabling this focuses on the first available and visible input found in the
-modal after it's opened.
+modal after it's opened. If no element is found, the close button (if
+visible) is focused instead.
       </div>
       <pre class=" language-yaml"><code>
 modal_focus_input: 1
@@ -1130,4 +1131,4 @@ tooltip_trigger: hover
 </table>
 
 [Drupal Bootstrap]: https://www.drupal.org/project/bootstrap
-[Bootstrap Framework]: https://getbootstrap.com/docs/3.3/
\ No newline at end of file
+[Bootstrap Framework]: https://getbootstrap.com/docs/3.4/
diff --git a/grunt/compile.js b/grunt/compile.js
index 33c7a8c0..55e739f8 100644
--- a/grunt/compile.js
+++ b/grunt/compile.js
@@ -113,7 +113,16 @@ module.exports = function (grunt) {
                   // Then, import the variable overrides.
                   '@import "' + path.join('starterkits', 'less', 'less', 'variable-overrides.less') + '"',
                   // Finally, import the base-theme overrides.
-                  '@import "' + path.join('starterkits', 'less', 'less', 'overrides.less') + '"'
+                  '@import "' + path.join('starterkits', 'less', 'less', 'overrides.less') + '"',
+                  // Add some default variables that may not be available.
+                  '@form-group-margin-bottom: 15px',
+                  '@screen-sm-min: @screen-sm',
+                  '@screen-md-min: @screen-md',
+                  '@screen-lg-min: @screen-lg',
+                  '@container-sm: @container-tablet',
+                  '@container-md: @container-desktop',
+                  '@container-large-desktop: (1140px + @grid-gutter-width)',
+                  '@container-lg: @container-large-desktop'
                 ];
                 grunt.log.debug("\noptions: " + JSON.stringify(options, null, 2));
                 grunt.log.debug(imports.join("\n"));
diff --git a/grunt/sync.js b/grunt/sync.js
index b88dd194..9308e589 100644
--- a/grunt/sync.js
+++ b/grunt/sync.js
@@ -7,6 +7,12 @@ module.exports = function (grunt) {
     var force = grunt.option('force');
     var path = require('path');
     var pkg = require('../package');
+    var mapSeries = require('promise-map-series');
+    var simpleJsonRequest = require('simple-json-request');
+    var semver = require('semver');
+    var getJson = function (uri) {
+      return simpleJsonRequest.get({url: uri});
+    };
 
     // Internal variables.
     var libraries = {};
@@ -27,81 +33,117 @@ module.exports = function (grunt) {
       grunt.verbose.writeln((expired ? 'EXPIRED' : 'VALID')[expired ? 'red' : 'green']);
     }
 
+    var getApiV1Json = function ($package) {
+      var $json = {name: $package, assets: []};
+      var $latest = '0.0.0';
+      var $versions = [];
+      return getJson('https://data.jsdelivr.com/v1/package/npm/' + $package)
+        .catch(function (error) {
+          if (!(error instanceof Error)) {
+            error = new Error(error);
+          }
+          grunt.verbose.error(error);
+        })
+        .then(function ($packageJson) {
+          if (!$packageJson) {
+            $packageJson = {versions: []};
+          }
+          if ($packageJson.versions === void 0) {
+            $packageJson.versions = [];
+          }
+          return mapSeries($packageJson.versions, function ($version, $key) {
+            // Skip irrelevant versions.
+            if (!$version.match(/^3\.\d+\.\d+$/)) {
+              return Promise.resolve();
+            }
+            return getJson('https://data.jsdelivr.com/v1/package/npm/' + $package + '@' + $version + '/flat')
+              .then(function ($versionJson) {
+                // Skip empty files.
+                if (!$versionJson.files || !$versionJson.files.length) {
+                  return;
+                }
+
+                $versions.push($version);
+                if (semver.compare($latest, $version) === -1) {
+                  $latest = $version;
+                }
+
+                var $asset = {files: [], version: $version};
+                $versionJson.files.forEach(function ($file) {
+                  // Skip old bootswatch file structure.
+                  if ($package === 'bootswatch' && $file.name.match(/^\/2|\/bower_components/)) {
+                    return;
+                  }
+                  var $matches = $file.name.match(/([^/]*)\/bootstrap(-theme)?(\.min)?\.(js|css)$/, $file['name']);
+                  if ($matches && $matches[1] !== void 0 && $matches[4] !== void 0 && $matches[1] !== 'custom') {
+                    $asset.files.push($file.name);
+                  }
+                });
+                $json.assets.push($asset);
+                $json.lastversion = $latest;
+                $json.versions = $versions;
+              })
+          });
+        })
+        .then(function () {
+          return $json;
+        });
+    };
+
     // Register a private sub-task. Doing this inside the main task prevents
     // this private sub-task from being executed directly and also prevents it
     // from showing up on the list of available tasks on --help.
     grunt.registerTask('sync:api', function () {
       var done = this.async();
-      var request = require('request');
-      grunt.verbose.write(pkg.urls.jsdelivr + ' ');
-      request(pkg.urls.jsdelivr, function (error, response, body) {
-        if (!error && response.statusCode == 200) {
-          grunt.verbose.ok();
-          var json;
-          grunt.verbose.write("\nParsing JSON response...");
-          try {
-            json = JSON.parse(body);
-            grunt.verbose.ok();
-          } catch (e) {
-            grunt.verbose.error();
-            throw grunt.util.error('Unable to parse the response value (' + e.message + ').', e);
-          }
-          grunt.verbose.write("\nExtracting versions and themes from libraries...");
-          libraries = {};
-          json.forEach(function (library) {
-            if (library.name === 'bootstrap' || library.name === 'bootswatch') {
-              library.assets.forEach(function (asset) {
-                if (asset.version.match(/^3.\d\.\d$/)) {
-                  if (!libraries[library.name]) libraries[library.name] = {};
-                  if (!libraries[library.name][asset.version]) libraries[library.name][asset.version] = {};
-                  asset.files.forEach(function (file) {
-                    if (!file.match(/bootstrap\.min\.css$/)) return;
-                    if (library.name === 'bootstrap') {
-                      libraries[library.name][asset.version]['bootstrap'] = true;
-                    }
-                    else {
-                      libraries[library.name][asset.version][file.split(path.sep)[0]] = true;
-                    }
-                  });
-                }
-              });
-            }
-          });
-          grunt.verbose.ok();
-
-          // Flatten themes.
-          for (var library in libraries) {
-            grunt.verbose.header(library);
-            if (!libraries.hasOwnProperty(library)) continue;
-            var versions = Object.keys(libraries[library]);
-            grunt.verbose.ok('Versions: ' + versions.join(', '));
-            var themeCount = 0;
-            for (var version in libraries[library]) {
-              if (!libraries[library].hasOwnProperty(version)) continue;
-              var themes = Object.keys(libraries[library][version]).sort();
-              libraries[library][version] = themes;
-              if (themes.length > themeCount) {
-                themeCount = themes.length;
+      mapSeries(['bootstrap', 'bootswatch'], function ($package) {
+        return getApiV1Json($package);
+      }).then(function (json) {
+        grunt.verbose.write("\nExtracting versions and themes from libraries...");
+        libraries = {};
+        json.forEach(function (library) {
+          if (library.name === 'bootstrap' || library.name === 'bootswatch') {
+            library.assets.forEach(function (asset) {
+              if (asset.version.match(/^3.\d\.\d$/)) {
+                if (!libraries[library.name]) libraries[library.name] = {};
+                if (!libraries[library.name][asset.version]) libraries[library.name][asset.version] = {};
+                asset.files.forEach(function (file) {
+                  if (!file.match(/bootstrap\.min\.css$/)) return;
+                  if (library.name === 'bootstrap') {
+                    libraries[library.name][asset.version]['bootstrap'] = true;
+                  }
+                  else {
+                    libraries[library.name][asset.version][file.split(path.sep).filter(Boolean)[0]] = true;
+                  }
+                });
               }
+            });
+          }
+        });
+        grunt.verbose.ok();
+
+        // Flatten themes.
+        for (var library in libraries) {
+          grunt.verbose.header(library);
+          if (!libraries.hasOwnProperty(library)) continue;
+          var versions = Object.keys(libraries[library]);
+          grunt.verbose.ok('Versions: ' + versions.join(', '));
+          var themeCount = 0;
+          for (var version in libraries[library]) {
+            if (!libraries[library].hasOwnProperty(version)) continue;
+            var themes = Object.keys(libraries[library][version]).sort();
+            libraries[library][version] = themes;
+            if (themes.length > themeCount) {
+              themeCount = themes.length;
             }
-            grunt.verbose.ok(grunt.util.pluralize(themeCount, 'Themes: 1/Themes: ' + themeCount));
           }
-          grunt.verbose.writeln();
-          grunt.file.write(librariesCache, JSON.stringify(libraries, null, 2));
-
-          grunt.log.ok('Synced');
+          grunt.verbose.ok(grunt.util.pluralize(themeCount, 'Themes: 1/Themes: ' + themeCount));
         }
-        else {
-          grunt.verbose.error();
-          if (error) grunt.verbose.error(error);
-          grunt.verbose.error('Request URL: ' + pkg.urls.jsdelivr);
-          grunt.verbose.error('Status Code: ' + response.statusCode);
-          grunt.verbose.error('Response Headers: ' + JSON.stringify(response.headers, null, 2));
-          grunt.verbose.error('Response:');
-          grunt.verbose.error(body);
-          grunt.fail.fatal('Unable to establish a connection. Run with --verbose to view the response received.');
-        }
-        return done(error);
+        grunt.verbose.writeln();
+        grunt.file.write(librariesCache, JSON.stringify(libraries, null, 2));
+
+        grunt.log.ok('Synced');
+
+        done();
       });
     });
 
diff --git a/icons.inc b/icons.inc
index d5a200ba..0829accf 100644
--- a/icons.inc
+++ b/icons.inc
@@ -13,7 +13,7 @@ use Drupal\bootstrap\Bootstrap;
 function bootstrap_icon_providers() {
   $providers['bootstrap'] = [
     'title' => t('Bootstrap'),
-    'url' => 'https://getbootstrap.com/docs/3.3/components/#glyphicons',
+    'url' => 'https://getbootstrap.com/docs/3.4/components/#glyphicons',
   ];
   return $providers;
 }
diff --git a/js/theme-settings.js b/js/theme-settings.js
index 95863933..12c72ccb 100644
--- a/js/theme-settings.js
+++ b/js/theme-settings.js
@@ -169,7 +169,7 @@
         // Unfortunately getbootstrap.com does not have HTTPS enabled, so the
         // preview image cannot be protocol relative.
         // @todo Make protocol relative if/when Bootstrap enables HTTPS.
-        $preview.append('<a id="bootstrap-theme-preview-bootstrap_theme" class="bootswatch-preview element-invisible" href="https://getbootstrap.com/docs/3.3/examples/theme/" target="_blank"><img class="img-responsive" src="//getbootstrap.com/docs/3.3/examples/screenshots/theme.jpg" alt="' + Drupal.t('Preview of the Bootstrap theme') + '" /></a>');
+        $preview.append('<a id="bootstrap-theme-preview-bootstrap_theme" class="bootswatch-preview element-invisible" href="https://getbootstrap.com/docs/3.4/examples/theme/" target="_blank"><img class="img-responsive" src="//getbootstrap.com/docs/3.4/examples/screenshots/theme.jpg" alt="' + Drupal.t('Preview of the Bootstrap theme') + '" /></a>');
 
         // Retrieve the Bootswatch theme preview images.
         // @todo This should be moved into PHP.
diff --git a/package.json b/package.json
index 472fc5ee..181d315a 100644
--- a/package.json
+++ b/package.json
@@ -15,8 +15,10 @@
     "less-plugin-autoprefix": "^1.4.2",
     "less-plugin-clean-css": "^1.5.1",
     "load-grunt-config": "^0.19.2",
+    "promise-map-series": "^0.2.3",
     "queue": "^4.4.1",
-    "request": "^2.83.0",
+    "semver": "^5.6.0",
+    "simple-json-request": "^0.5.2",
     "sync-exec": "^0.6.1",
     "time-grunt": "^1.4.0"
   },
@@ -31,9 +33,6 @@
     "js": "js",
     "libraries": "lib"
   },
-  "urls": {
-    "jsdelivr": "https://api.jsdelivr.com/v1/bootstrap/libraries"
-  },
   "githooks": {
     "post-checkout": [
       {
diff --git a/src/Bootstrap.php b/src/Bootstrap.php
index d30dfc53..3e97c487 100644
--- a/src/Bootstrap.php
+++ b/src/Bootstrap.php
@@ -61,14 +61,14 @@ class Bootstrap {
    *
    * @var string
    */
-  const FRAMEWORK_VERSION = '3.3.7';
+  const FRAMEWORK_VERSION = '3.4.0';
 
   /**
    * The Bootstrap Framework documentation site.
    *
    * @var string
    */
-  const FRAMEWORK_HOMEPAGE = 'https://getbootstrap.com/docs/3.3/';
+  const FRAMEWORK_HOMEPAGE = 'https://getbootstrap.com/docs/3.4/';
 
   /**
    * The Bootstrap Framework repository.
@@ -1009,6 +1009,7 @@ class Bootstrap {
       $versions['3.3.5'] = $versions['3.3.4'];
       $versions['3.3.6'] = $versions['3.3.5'];
       $versions['3.3.7'] = $versions['3.3.6'];
+      $versions['3.4.0'] = $versions['3.3.7'];
     }
 
     // Return a specific versions icon set.
diff --git a/src/Plugin/Preprocess/FormElement.php b/src/Plugin/Preprocess/FormElement.php
index e296d83b..5465a118 100644
--- a/src/Plugin/Preprocess/FormElement.php
+++ b/src/Plugin/Preprocess/FormElement.php
@@ -25,7 +25,7 @@ class FormElement extends PreprocessBase implements PreprocessInterface {
       $variables['is_autocomplete'] = TRUE;
     }
 
-    // See https://getbootstrap.com/docs/3.3/css/#forms-controls.
+    // See https://getbootstrap.com/docs/3.4/css/#forms-controls.
     $checkbox = $variables['is_checkbox'] = $element->isType('checkbox');
     $radio = $variables['is_radio'] = $element->isType('radio');
 
diff --git a/src/Plugin/Setting/Advanced/Cdn/CdnCustomCss.php b/src/Plugin/Setting/Advanced/Cdn/CdnCustomCss.php
index 8a43c44f..9e209e18 100644
--- a/src/Plugin/Setting/Advanced/Cdn/CdnCustomCss.php
+++ b/src/Plugin/Setting/Advanced/Cdn/CdnCustomCss.php
@@ -15,7 +15,7 @@ use Drupal\bootstrap\Plugin\Setting\SettingBase;
  *   type = "textfield",
  *   weight = 1,
  *   title = @Translation("Bootstrap CSS URL"),
- *   defaultValue = "https://cdn.jsdelivr.net/bootstrap/3.3.7/css/bootstrap.css",
+ *   defaultValue = "https://cdn.jsdelivr.net/npm/bootstrap@3.4.0/dist/css/bootstrap.css",
  *   description = @Translation("It is best to use <code>https</code> protocols here as it will allow more flexibility if the need ever arises."),
  *   groups = {
  *     "advanced" = @Translation("Advanced"),
diff --git a/src/Plugin/Setting/Advanced/Cdn/CdnCustomCssMin.php b/src/Plugin/Setting/Advanced/Cdn/CdnCustomCssMin.php
index 0684d64c..7fb95ca9 100644
--- a/src/Plugin/Setting/Advanced/Cdn/CdnCustomCssMin.php
+++ b/src/Plugin/Setting/Advanced/Cdn/CdnCustomCssMin.php
@@ -15,7 +15,7 @@ use Drupal\bootstrap\Plugin\Setting\SettingBase;
  *   type = "textfield",
  *   weight = 2,
  *   title = @Translation("Minified Bootstrap CSS URL"),
- *   defaultValue = "https://cdn.jsdelivr.net/bootstrap/3.3.7/css/bootstrap.min.css",
+ *   defaultValue = "https://cdn.jsdelivr.net/npm/bootstrap@3.4.0/dist/css/bootstrap.min.css",
  *   description = @Translation("Additionally, you can provide the minimized version of the file. It will be used instead if site aggregation is enabled."),
  *   groups = {
  *     "advanced" = @Translation("Advanced"),
diff --git a/src/Plugin/Setting/Advanced/Cdn/CdnCustomJs.php b/src/Plugin/Setting/Advanced/Cdn/CdnCustomJs.php
index 3a60cb24..a61045f3 100644
--- a/src/Plugin/Setting/Advanced/Cdn/CdnCustomJs.php
+++ b/src/Plugin/Setting/Advanced/Cdn/CdnCustomJs.php
@@ -15,7 +15,7 @@ use Drupal\bootstrap\Plugin\Setting\SettingBase;
  *   type = "textfield",
  *   weight = 3,
  *   title = @Translation("Bootstrap JavaScript URL"),
- *   defaultValue = "https://cdn.jsdelivr.net/bootstrap/3.3.7/js/bootstrap.js",
+ *   defaultValue = "https://cdn.jsdelivr.net/npm/bootstrap@3.4.0/dist/js/bootstrap.js",
  *   description = @Translation("It is best to use <code>https</code> protocols here as it will allow more flexibility if the need ever arises."),
  *   groups = {
  *     "advanced" = @Translation("Advanced"),
diff --git a/src/Plugin/Setting/Advanced/Cdn/CdnCustomJsMin.php b/src/Plugin/Setting/Advanced/Cdn/CdnCustomJsMin.php
index ed6c8069..22622919 100644
--- a/src/Plugin/Setting/Advanced/Cdn/CdnCustomJsMin.php
+++ b/src/Plugin/Setting/Advanced/Cdn/CdnCustomJsMin.php
@@ -15,7 +15,7 @@ use Drupal\bootstrap\Plugin\Setting\SettingBase;
  *   type = "textfield",
  *   weight = 4,
  *   title = @Translation("Minified Bootstrap JavaScript URL"),
- *   defaultValue = "https://cdn.jsdelivr.net/bootstrap/3.3.7/js/bootstrap.min.js",
+ *   defaultValue = "https://cdn.jsdelivr.net/npm/bootstrap@3.4.0/dist/js/bootstrap.min.js",
  *   description = @Translation("Additionally, you can provide the minimized version of the file. It will be used instead if site aggregation is enabled."),
  *   groups = {
  *     "advanced" = @Translation("Advanced"),
diff --git a/src/Plugin/Setting/Advanced/Cdn/CdnJsdelivrTheme.php b/src/Plugin/Setting/Advanced/Cdn/CdnJsdelivrTheme.php
index c3a07143..2c383166 100644
--- a/src/Plugin/Setting/Advanced/Cdn/CdnJsdelivrTheme.php
+++ b/src/Plugin/Setting/Advanced/Cdn/CdnJsdelivrTheme.php
@@ -39,7 +39,7 @@ class CdnJsdelivrTheme extends CdnProvider {
     $setting->setProperty('suffix', '<div id="bootstrap-theme-preview"></div>');
     $setting->setProperty('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!', [
       ':bootswatch' => 'https://bootswatch.com',
-      ':bootstrap_theme' => 'https://getbootstrap.com/docs/3.3/examples/theme/',
+      ':bootstrap_theme' => 'https://getbootstrap.com/docs/3.4/examples/theme/',
     ]));
 
     $options = [];
diff --git a/src/Plugin/Setting/Components/Region/RegionWells.php b/src/Plugin/Setting/Components/Region/RegionWells.php
index 503d563d..a6315626 100644
--- a/src/Plugin/Setting/Components/Region/RegionWells.php
+++ b/src/Plugin/Setting/Components/Region/RegionWells.php
@@ -31,7 +31,7 @@ use Drupal\Core\Form\FormStateInterface;
  *     "region_wells" = @Translation("Region Wells"),
  *   },
  *   see = {
- *     "https://getbootstrap.com/docs/3.3/components/#wells" = @Translation("Bootstrap Wells"),
+ *     "https://getbootstrap.com/docs/3.4/components/#wells" = @Translation("Bootstrap Wells"),
  *   },
  * )
  */
diff --git a/src/Plugin/Setting/General/Buttons/ButtonColorize.php b/src/Plugin/Setting/General/Buttons/ButtonColorize.php
index e05dce00..b00e0bf7 100644
--- a/src/Plugin/Setting/General/Buttons/ButtonColorize.php
+++ b/src/Plugin/Setting/General/Buttons/ButtonColorize.php
@@ -20,7 +20,7 @@ use Drupal\bootstrap\Plugin\Setting\SettingBase;
  *     "button" = @Translation("Buttons"),
  *   },
  *   see = {
- *     "https://getbootstrap.com/docs/3.3/css/#buttons" = @Translation("Buttons"),
+ *     "https://getbootstrap.com/docs/3.4/css/#buttons" = @Translation("Buttons"),
  *     "https://drupal-bootstrap.org/apis/hook_bootstrap_colorize_text_alter" = @Translation("hook_bootstrap_colorize_text_alter()"),
  *   },
  * )
diff --git a/src/Plugin/Setting/General/Container/FluidContainer.php b/src/Plugin/Setting/General/Container/FluidContainer.php
index f83f9426..ded3b390 100644
--- a/src/Plugin/Setting/General/Container/FluidContainer.php
+++ b/src/Plugin/Setting/General/Container/FluidContainer.php
@@ -20,7 +20,7 @@ use Drupal\bootstrap\Plugin\Setting\SettingBase;
  *     "container" = @Translation("Container"),
  *   },
  *   see = {
- *     "https://getbootstrap.com/docs/3.3/css/#grid-example-fluid" = @Translation("Fluid container"),
+ *     "https://getbootstrap.com/docs/3.4/css/#grid-example-fluid" = @Translation("Fluid container"),
  *   },
  * )
  */
diff --git a/src/Plugin/Setting/General/Images/ImageShape.php b/src/Plugin/Setting/General/Images/ImageShape.php
index 3ebc72e2..aaea5dd0 100644
--- a/src/Plugin/Setting/General/Images/ImageShape.php
+++ b/src/Plugin/Setting/General/Images/ImageShape.php
@@ -26,7 +26,7 @@ use Drupal\bootstrap\Plugin\Setting\SettingBase;
  *     "img-thumbnail" = @Translation("Thumbnail"),
  *   },
  *   see = {
- *     "https://getbootstrap.com/docs/3.3/css/#images-shapes" = @Translation("Image Shapes"),
+ *     "https://getbootstrap.com/docs/3.4/css/#images-shapes" = @Translation("Image Shapes"),
  *   },
  * )
  */
diff --git a/src/Plugin/Setting/JavaScript/Modals/ModalEnabled.php b/src/Plugin/Setting/JavaScript/Modals/ModalEnabled.php
index 20e911fd..57002e25 100644
--- a/src/Plugin/Setting/JavaScript/Modals/ModalEnabled.php
+++ b/src/Plugin/Setting/JavaScript/Modals/ModalEnabled.php
@@ -32,7 +32,7 @@ class ModalEnabled extends SettingBase {
     parent::alterFormElement($form, $form_state, $form_id);
     $group = $this->getGroupElement($form, $form_state);
     $group->setProperty('description', t('Modals are streamlined, but flexible, dialog prompts with the minimum required functionality and smart defaults. See <a href=":url" target="_blank">Bootstrap Modals</a> for more documentation.', [
-      ':url' => 'https://getbootstrap.com/docs/3.3/javascript/#modals',
+      ':url' => 'https://getbootstrap.com/docs/3.4/javascript/#modals',
     ]));
   }
 
diff --git a/src/Plugin/Setting/JavaScript/Tooltips/TooltipEnabled.php b/src/Plugin/Setting/JavaScript/Tooltips/TooltipEnabled.php
index cfdaac41..8d53026c 100644
--- a/src/Plugin/Setting/JavaScript/Tooltips/TooltipEnabled.php
+++ b/src/Plugin/Setting/JavaScript/Tooltips/TooltipEnabled.php
@@ -34,7 +34,7 @@ class TooltipEnabled extends SettingBase {
 
     $group = $this->getGroupElement($form, $form_state);
     $group->setProperty('description', t('Inspired by the excellent jQuery.tipsy plugin written by Jason Frame; Tooltips are an updated version, which don\'t rely on images, use CSS3 for animations, and data-attributes for local title storage. See <a href=":url" target="_blank">Bootstrap tooltips</a> for more documentation.', [
-      ':url' => 'https://getbootstrap.com/docs/3.3/javascript/#tooltips',
+      ':url' => 'https://getbootstrap.com/docs/3.4/javascript/#tooltips',
     ]));
   }
 
diff --git a/starterkits/cdn/README.md b/starterkits/cdn/README.md
index 3b02bb28..9458c816 100644
--- a/starterkits/cdn/README.md
+++ b/starterkits/cdn/README.md
@@ -25,5 +25,5 @@ Please refer to the @link theme_settings Sub-theme Settings @endlink topic.
 Please refer to the @link registry Theme Registry @endlink topic.
 
 [Drupal Bootstrap]: https://www.drupal.org/project/bootstrap
-[Bootstrap Framework]: https://getbootstrap.com/docs/3.3/
+[Bootstrap Framework]: https://getbootstrap.com/docs/3.4/
 [jsDelivr CDN]: http://www.jsdelivr.com
diff --git a/starterkits/less/README.md b/starterkits/less/README.md
index 5eac1266..735b52d0 100644
--- a/starterkits/less/README.md
+++ b/starterkits/less/README.md
@@ -61,6 +61,6 @@ extension of course).
 - @link templates Templates @endlink
 - @link plugins Plugin System @endlink
 
-[Bootstrap Framework]: https://getbootstrap.com/docs/3.3/
+[Bootstrap Framework]: https://getbootstrap.com/docs/3.4/
 [Bootstrap Framework Source Files]: https://github.com/twbs/bootstrap/releases
 [Less]: http://lesscss.org
diff --git a/starterkits/sass/README.md b/starterkits/sass/README.md
index b894f03b..e9ca6d2e 100644
--- a/starterkits/sass/README.md
+++ b/starterkits/sass/README.md
@@ -54,6 +54,6 @@ name, using a different extension of course).
 - @link templates Templates @endlink
 - @link plugins Plugin System @endlink
 
-[Bootstrap Framework]: https://getbootstrap.com/docs/3.3/
+[Bootstrap Framework]: https://getbootstrap.com/docs/3.4/
 [Bootstrap Framework Source Files]: https://github.com/twbs/bootstrap-sass
 [Sass]: http://sass-lang.com
diff --git a/yarn.lock b/yarn.lock
index 6a4ff408..1683a2a7 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -351,6 +351,12 @@ decamelize@^1.1.2:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
 
+decompress-response@^3.3.0:
+  version "3.3.0"
+  resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3"
+  dependencies:
+    mimic-response "^1.0.0"
+
 delayed-stream@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
@@ -995,6 +1001,10 @@ mimic-fn@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18"
 
+mimic-response@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
+
 "minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.0, minimatch@~3.0.2:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
@@ -1073,7 +1083,7 @@ on-finished@~2.3.0:
   dependencies:
     ee-first "1.1.1"
 
-once@^1.3.0:
+once@^1.3.0, once@^1.3.1:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
   dependencies:
@@ -1180,6 +1190,12 @@ pretty-ms@^2.1.0:
     parse-ms "^1.0.0"
     plur "^1.0.0"
 
+promise-map-series@^0.2.3:
+  version "0.2.3"
+  resolved "https://registry.yarnpkg.com/promise-map-series/-/promise-map-series-0.2.3.tgz#c2d377afc93253f6bd03dbb77755eb88ab20a847"
+  dependencies:
+    rsvp "^3.0.14"
+
 promise@^7.1.1:
   version "7.3.1"
   resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
@@ -1248,7 +1264,7 @@ repeating@^2.0.0:
   dependencies:
     is-finite "^1.0.0"
 
-request@^2.72.0, request@^2.83.0:
+request@^2.72.0:
   version "2.83.0"
   resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356"
   dependencies:
@@ -1302,6 +1318,10 @@ rimraf@~2.2.8:
   version "2.2.8"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582"
 
+rsvp@^3.0.14:
+  version "3.6.2"
+  resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.6.2.tgz#2e96491599a96cde1b515d5674a8f7a91452926a"
+
 run-async@^2.2.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0"
@@ -1333,10 +1353,32 @@ safefs@^4.0.0:
   version "5.4.1"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e"
 
+semver@^5.6.0:
+  version "5.6.0"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
+
 signal-exit@^3.0.0, signal-exit@^3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
 
+simple-concat@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.0.tgz#7344cbb8b6e26fb27d66b2fc86f9f6d5997521c6"
+
+simple-get@^3.0.2:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.0.3.tgz#924528ac3f9d7718ce5e9ec1b1a69c0be4d62efa"
+  dependencies:
+    decompress-response "^3.3.0"
+    once "^1.3.1"
+    simple-concat "^1.0.0"
+
+simple-json-request@^0.5.2:
+  version "0.5.2"
+  resolved "https://registry.yarnpkg.com/simple-json-request/-/simple-json-request-0.5.2.tgz#9ccb9376dbb600b150e666336197c9371c874075"
+  dependencies:
+    simple-get "^3.0.2"
+
 sntp@2.x.x:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.0.2.tgz#5064110f0af85f7cfdb7d6b67a40028ce52b4b2b"
-- 
GitLab