core/core.libraries.yml | 166 ++++++++++++++++-----
core/core.services.yml | 2 +-
core/includes/common.inc | 3 +-
core/includes/theme.inc | 8 +
.../Drupal/Core/Asset/JsCollectionOptimizer.php | 39 ++++-
core/lib/Drupal/Core/Asset/LibraryDiscovery.php | 18 ++-
.../Controller/AssetLicenseInfoController.php | 158 ++++++++++++++++++++
.../Drupal/system/Tests/Common/JavaScriptTest.php | 11 +-
core/modules/system/system.routing.yml | 8 +
.../Tests/Core/Asset/LibraryDiscoveryTest.php | 67 ++++++++-
.../library_test_files/licenses.libraries.yml | 72 +++++++++
11 files changed, 504 insertions(+), 48 deletions(-)
diff --git a/core/core.libraries.yml b/core/core.libraries.yml
index fb13d9c..6b0caab 100644
--- a/core/core.libraries.yml
+++ b/core/core.libraries.yml
@@ -3,6 +3,10 @@
backbone:
remote: https://github.com/jashkenas/backbone
version: 1.1.0
+ license:
+ name: MIT
+ remote: https://github.com/jashkenas/backbone/blob/1.1.0/LICENSE
+ gpl-compatible: true
js:
assets/vendor/backbone/backbone.js: { weight: -19 }
dependencies:
@@ -12,6 +16,10 @@ classList:
remote: https://github.com/eligrey/classList.js
# @todo Stable release required for Drupal 8.0.
version: master
+ license:
+ name: Public Domain
+ remote: https://github.com/eligrey/classList.js/blob/master/LICENSE.md
+ gpl-compatible: true
js:
assets/vendor/classList/classList.min.js: { weight: -21, browsers: { IE: 'lte IE 9', '!IE': false } }
@@ -20,12 +28,20 @@ ckeditor:
# @todo Stable release required for Drupal 8.0.
version: 4.2-dev
commit: 887d81ac1824008b690e439a1b29eb4f13b51212
+ license:
+ name: GNU-GPL-2.0-or-later
+ remote: https://github.com/ckeditor/ckeditor-dev/blob/887d81ac1824008b690e439a1b29eb4f13b51212/LICENSE.md
+ gpl-compatible: true
js:
assets/vendor/ckeditor/ckeditor.js: { preprocess: false }
domready:
remote: https://github.com/ded/domready
version: 1.0.4
+ license:
+ name: MIT
+ remote: https://github.com/ded/domready/blob/v1.0.4/LICENSE
+ gpl-compatible: true
js:
assets/vendor/domready/ready.min.js: { weight: -21 }
@@ -279,18 +295,30 @@ drupal.vertical-tabs:
html5shiv:
remote: https://github.com/aFarkas/html5shiv
version: 3.6.2
+ license:
+ name: GPL2
+ remote: http://www.gnu.org/licenses/gpl-2.0.html
+ gpl-compatible: true
js:
assets/vendor/html5shiv/html5.js: { every_page: true, weight: -22, browsers: { IE: 'lte IE 8', '!IE': false } }
jquery:
remote: https://github.com/jquery/jquery
version: 2.1.0
+ license:
+ name: MIT
+ remote: https://github.com/jquery/jquery/blob/2.1.0/MIT-LICENSE.txt
+ gpl-compatible: true
js:
assets/vendor/jquery/jquery.js: { weight: -20 }
jquery.cookie:
remote: https://github.com/carhartl/jquery-cookie
version: v1.4.0
+ license:
+ name: MIT
+ remote: https://github.com/carhartl/jquery-cookie/blob/v1.4.0/MIT-LICENSE.txt
+ gpl-compatible: true
js:
assets/vendor/jquery.cookie/jquery.cookie.js: {}
dependencies:
@@ -300,6 +328,10 @@ jquery.farbtastic:
remote: https://github.com/mattfarina/farbtastic
# @todo Ping @robloach or @mattfarina to retroactively create this release.
version: 1.2
+ license:
+ name: GNU-GPL-2.0-or-later
+ remote: https://github.com/mattfarina/farbtastic/blob/master/LICENSE.txt
+ gpl-compatible: true
js:
assets/vendor/farbtastic/farbtastic.js: {}
css:
@@ -311,6 +343,10 @@ jquery.farbtastic:
jquery.form:
remote: https://github.com/malsup/form
version: 3.50
+ license:
+ name: GNU-GPL-2.0-or-later
+ remote: http://malsup.github.com/gpl-license-v2.txt
+ gpl-compatible: true
js:
assets/vendor/jquery-form/jquery.form.js: {}
dependencies:
@@ -326,6 +362,10 @@ jquery.intrinsic:
jquery.joyride:
remote: https://github.com/zurb/joyride
version: v2.0.3
+ license:
+ name: MIT
+ remote: https://github.com/zurb/joyride/blob/v2.0.3/README.markdown
+ gpl-compatible: true
js:
assets/vendor/jquery-joyride/jquery.joyride-2.0.3.js: {}
dependencies:
@@ -335,6 +375,10 @@ jquery.joyride:
jquery.once:
remote: https://github.com/RobLoach/jquery-once
version: 1.2.3
+ license:
+ name: GNU-GPL-2.0-or-later
+ remote: https://github.com/RobLoach/jquery-once/blob/1.2.3/LICENSE
+ gpl-compatible: true
js:
assets/vendor/jquery-once/jquery.once.js: { weight: -19 }
dependencies:
@@ -342,7 +386,11 @@ jquery.once:
jquery.ui:
remote: https://github.com/jquery/jquery-ui
- version: &jquery_ui 1.10.2
+ version: &jquery_ui_version 1.10.2
+ license: &jquery_ui_license
+ name: MIT
+ remote: https://github.com/jquery/jquery-ui/blob/1.10.2/MIT-LICENSE.txt
+ gpl-compatible: true
js:
assets/vendor/jquery.ui/ui/jquery.ui.core.js: { weight: -11 }
css:
@@ -354,7 +402,8 @@ jquery.ui:
- core/jquery
jquery.ui.accordion:
- version: *jquery_ui
+ version: *jquery_ui_version
+ license: *jquery_ui_license
js:
assets/vendor/jquery.ui/ui/jquery.ui.accordion.js: {}
css:
@@ -365,7 +414,8 @@ jquery.ui.accordion:
- core/jquery.ui.widget
jquery.ui.autocomplete:
- version: *jquery_ui
+ version: *jquery_ui_version
+ license: *jquery_ui_license
js:
assets/vendor/jquery.ui/ui/jquery.ui.autocomplete.js: {}
css:
@@ -378,7 +428,8 @@ jquery.ui.autocomplete:
- core/jquery.ui.menu
jquery.ui.button:
- version: *jquery_ui
+ version: *jquery_ui_version
+ license: *jquery_ui_license
js:
assets/vendor/jquery.ui/ui/jquery.ui.button.js: {}
css:
@@ -389,7 +440,8 @@ jquery.ui.button:
- core/jquery.ui.widget
jquery.ui.datepicker:
- version: *jquery_ui
+ version: *jquery_ui_version
+ license: *jquery_ui_license
js:
assets/vendor/jquery.ui/ui/jquery.ui.datepicker.js: {}
css:
@@ -399,7 +451,8 @@ jquery.ui.datepicker:
- core/jquery.ui
jquery.ui.dialog:
- version: *jquery_ui
+ version: *jquery_ui_version
+ license: *jquery_ui_license
js:
assets/vendor/jquery.ui/ui/jquery.ui.dialog.js: {}
css:
@@ -415,7 +468,8 @@ jquery.ui.dialog:
- core/jquery.ui.resizable
jquery.ui.draggable:
- version: *jquery_ui
+ version: *jquery_ui_version
+ license: *jquery_ui_license
js:
assets/vendor/jquery.ui/ui/jquery.ui.draggable.js: {}
dependencies:
@@ -424,7 +478,8 @@ jquery.ui.draggable:
- core/jquery.ui.widget
jquery.ui.droppable:
- version: *jquery_ui
+ version: *jquery_ui_version
+ license: *jquery_ui_license
js:
assets/vendor/jquery.ui/ui/jquery.ui.droppable.js: {}
dependencies:
@@ -434,103 +489,118 @@ jquery.ui.droppable:
- core/jquery.ui.draggable
jquery.ui.effects.core:
- version: *jquery_ui
+ version: *jquery_ui_version
+ license: *jquery_ui_license
js:
assets/vendor/jquery.ui/ui/jquery.effects.core.js: { weight: -9 }
jquery.ui.effects.blind:
- version: *jquery_ui
+ version: *jquery_ui_version
+ license: *jquery_ui_license
js:
assets/vendor/jquery.ui/ui/jquery.effects.blind.js: {}
dependencies:
- core/jquery.ui.effects.core
jquery.ui.effects.bounce:
- version: *jquery_ui
+ version: *jquery_ui_version
+ license: *jquery_ui_license
js:
assets/vendor/jquery.ui/ui/jquery.effects.bounce.js: {}
dependencies:
- core/jquery.ui.effects.core
jquery.ui.effects.clip:
- version: *jquery_ui
+ version: *jquery_ui_version
+ license: *jquery_ui_license
js:
assets/vendor/jquery.ui/ui/jquery.effects.clip.js: {}
dependencies:
- core/jquery.ui.effects.core
jquery.ui.effects.drop:
- version: *jquery_ui
+ version: *jquery_ui_version
+ license: *jquery_ui_license
js:
assets/vendor/jquery.ui/ui/jquery.effects.drop.js: {}
dependencies:
- core/jquery.ui.effects.core
jquery.ui.effects.explode:
- version: *jquery_ui
+ version: *jquery_ui_version
+ license: *jquery_ui_license
js:
assets/vendor/jquery.ui/ui/jquery.effects.explode.js: {}
dependencies:
- core/jquery.ui.effects.core
jquery.ui.effects.fade:
- version: *jquery_ui
+ version: *jquery_ui_version
+ license: *jquery_ui_license
js:
assets/vendor/jquery.ui/ui/jquery.effects.fade.js: {}
dependencies:
- core/jquery.ui.effects.core
jquery.ui.effects.fold:
- version: *jquery_ui
+ version: *jquery_ui_version
+ license: *jquery_ui_license
js:
assets/vendor/jquery.ui/ui/jquery.effects.fold.js: {}
dependencies:
- core/jquery.ui.effects.core
jquery.ui.effects.highlight:
- version: *jquery_ui
+ version: *jquery_ui_version
+ license: *jquery_ui_license
js:
assets/vendor/jquery.ui/ui/jquery.effects.highlight.js: {}
dependencies:
- core/jquery.ui.effects.core
jquery.ui.effects.pulsate:
- version: *jquery_ui
+ version: *jquery_ui_version
+ license: *jquery_ui_license
js:
assets/vendor/jquery.ui/ui/jquery.effects.pulsate.js: {}
dependencies:
- core/jquery.ui.effects.core
jquery.ui.effects.scale:
- version: *jquery_ui
+ version: *jquery_ui_version
+ license: *jquery_ui_license
js:
assets/vendor/jquery.ui/ui/jquery.effects.scale.js: {}
dependencies:
- core/jquery.ui.effects.core
jquery.ui.effects.shake:
- version: *jquery_ui
+ version: *jquery_ui_version
+ license: *jquery_ui_license
js:
assets/vendor/jquery.ui/ui/jquery.effects.shake.js: {}
dependencies:
- core/jquery.ui.effects.core
jquery.ui.effects.slide:
- version: *jquery_ui
+ version: *jquery_ui_version
+ license: *jquery_ui_license
js:
assets/vendor/jquery.ui/ui/jquery.effects.slide.js: {}
dependencies:
- core/jquery.ui.effects.core
jquery.ui.effects.transfer:
- version: *jquery_ui
+ version: *jquery_ui_version
+ license: *jquery_ui_license
js:
assets/vendor/jquery.ui/ui/jquery.effects.transfer.js: {}
dependencies:
- core/jquery.ui.effects.core
jquery.ui.menu:
- version: *jquery_ui
+ version: *jquery_ui_version
+ license: *jquery_ui_license
js:
assets/vendor/jquery.ui/ui/jquery.ui.menu.js: {}
css:
@@ -541,19 +611,22 @@ jquery.ui.menu:
- core/jquery.ui.widget
jquery.ui.mouse:
- version: *jquery_ui
+ version: *jquery_ui_version
+ license: *jquery_ui_license
js:
assets/vendor/jquery.ui/ui/jquery.ui.mouse.js: {}
dependencies:
- core/jquery.ui.widget
jquery.ui.position:
- version: *jquery_ui
+ version: *jquery_ui_version
+ license: *jquery_ui_license
js:
assets/vendor/jquery.ui/ui/jquery.ui.position.js: {}
jquery.ui.progressbar:
- version: *jquery_ui
+ version: *jquery_ui_version
+ license: *jquery_ui_license
js:
assets/vendor/jquery.ui/ui/jquery.ui.progressbar.js: {}
css:
@@ -564,7 +637,8 @@ jquery.ui.progressbar:
- core/jquery.ui.widget
jquery.ui.resizable:
- version: *jquery_ui
+ version: *jquery_ui_version
+ license: *jquery_ui_license
js:
assets/vendor/jquery.ui/ui/jquery.ui.resizable.js: {}
css:
@@ -576,7 +650,8 @@ jquery.ui.resizable:
- core/jquery.ui.mouse
jquery.ui.selectable:
- version: *jquery_ui
+ version: *jquery_ui_version
+ license: *jquery_ui_license
js:
assets/vendor/jquery.ui/ui/jquery.ui.selectable.js: {}
css:
@@ -588,7 +663,8 @@ jquery.ui.selectable:
- core/jquery.ui.widget
jquery.ui.slider:
- version: *jquery_ui
+ version: *jquery_ui_version
+ license: *jquery_ui_license
js:
assets/vendor/jquery.ui/ui/jquery.ui.slider.js: {}
css:
@@ -600,7 +676,8 @@ jquery.ui.slider:
- core/jquery.ui.widget
jquery.ui.sortable:
- version: *jquery_ui
+ version: *jquery_ui_version
+ license: *jquery_ui_license
js:
assets/vendor/jquery.ui/ui/jquery.ui.sortable.js: {}
dependencies:
@@ -609,7 +686,8 @@ jquery.ui.sortable:
- core/jquery.ui.widget
jquery.ui.spinner:
- version: *jquery_ui
+ version: *jquery_ui_version
+ license: *jquery_ui_license
js:
assets/vendor/jquery.ui/ui/jquery.ui.spinner.js: {}
dependencies:
@@ -618,7 +696,8 @@ jquery.ui.spinner:
- core/jquery.ui.button
jquery.ui.tabs:
- version: *jquery_ui
+ version: *jquery_ui_version
+ license: *jquery_ui_license
js:
assets/vendor/jquery.ui/ui/jquery.ui.tabs.js: {}
css:
@@ -629,7 +708,8 @@ jquery.ui.tabs:
- core/jquery.ui.widget
jquery.ui.tooltip:
- version: *jquery_ui
+ version: *jquery_ui_version
+ license: *jquery_ui_license
js:
assets/vendor/jquery.ui/ui/jquery.ui.tooltip.js: {}
css:
@@ -643,13 +723,18 @@ jquery.ui.tooltip:
jquery.ui.touch-punch:
remote: https://github.com/furf/jquery-ui-touch-punch
version: 0.2.2
+ license:
+ name: GNU-GPL-2.0-or-later
+ remote: https://github.com/furf/jquery-ui-touch-punch
+ gpl-compatible: true
js:
assets/vendor/jquery-ui-touch-punch/jquery.ui.touch-punch.js: {}
dependencies:
- core/jquery.ui
jquery.ui.widget:
- version: *jquery_ui
+ version: *jquery_ui_version
+ license: *jquery_ui_license
js:
assets/vendor/jquery.ui/ui/jquery.ui.widget.js: { weight: -10 }
dependencies:
@@ -667,6 +752,10 @@ matchmedia:
modernizr:
remote: https://github.com/Modernizr/Modernizr
+ license:
+ name: MIT
+ remote: https://github.com/Modernizr/Modernizr/blob/v2.6.2/readme.md
+ gpl-compatible: true
version: v2.6.2
js:
assets/vendor/modernizr/modernizr.min.js: { every_page: true, preprocess: 0, scope: header, weight: -21 }
@@ -674,6 +763,10 @@ modernizr:
normalize:
remote: https://github.com/necolas/normalize.css
version: 3.0.1
+ license:
+ name: MIT
+ remote: https://github.com/necolas/normalize.css/blob/master/LICENSE.md
+ gpl-compatible: true
css:
base:
assets/vendor/normalize-css/normalize.css: { every_page: true, weight: -20 }
@@ -691,6 +784,9 @@ picturefill:
underscore:
remote: https://github.com/jashkenas/underscore
version: 1.5.2
+ license:
+ name: MIT
+ remote: https://github.com/jashkenas/underscore/blob/1.5.2/LICENSE
+ gpl-compatible: true
js:
assets/vendor/underscore/underscore.js: { weight: -20 }
-
diff --git a/core/core.services.yml b/core/core.services.yml
index 51a41fa..5f22e4d 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -786,7 +786,7 @@ services:
arguments: [ '@state' ]
asset.js.collection_optimizer:
class: Drupal\Core\Asset\JsCollectionOptimizer
- arguments: [ '@asset.js.collection_grouper', '@asset.js.optimizer', '@asset.js.dumper', '@state' ]
+ arguments: [ '@asset.js.collection_grouper', '@asset.js.optimizer', '@asset.js.dumper', '@state', '@url_generator' ]
asset.js.optimizer:
class: Drupal\Core\Asset\JsOptimizer
asset.js.collection_grouper:
diff --git a/core/includes/common.inc b/core/includes/common.inc
index 0735dfa..8cc0cf7 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -2702,7 +2702,8 @@ function drupal_attach_tabledrag(&$element, array $options) {
*/
function drupal_clear_js_cache() {
\Drupal::state()->delete('system.javascript_parsed');
- \Drupal::state()->delete('system.js_cache_files');
+ \Drupal::state()->delete('system.js_optimized_aggregate_files');
+ \Drupal::state()->delete('system.js_unoptimized_aggregate_files');
file_scan_directory('public://js', '/.*/', array('callback' => 'drupal_delete_file_if_stale'));
}
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index b62ad74..51d4ece 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -2082,6 +2082,14 @@ function template_preprocess_html(&$variables) {
$variables['head'] = new RenderWrapper('drupal_get_html_head');
$variables['styles'] = new RenderWrapper('drupal_get_css');
$variables['scripts'] = new RenderWrapper('drupal_get_js');
+
+ // On each page that uses JavaScript, add the mandatory JavaScript Web License
+ // Labels link.
+ if (count(drupal_get_js('header')) || count(drupal_get_js('footer'))) {
+ $variables['page_bottom'][] = array(
+ '#markup' => l(t('JavaScript license information'), 'system/jslicense', array('attributes' => array('rel' => 'jslicense', 'class' => 'hidden'))),
+ );
+ }
}
/**
diff --git a/core/lib/Drupal/Core/Asset/JsCollectionOptimizer.php b/core/lib/Drupal/Core/Asset/JsCollectionOptimizer.php
index 4126e57..5d3ba68 100644
--- a/core/lib/Drupal/Core/Asset/JsCollectionOptimizer.php
+++ b/core/lib/Drupal/Core/Asset/JsCollectionOptimizer.php
@@ -6,9 +6,9 @@
namespace Drupal\Core\Asset;
+use Drupal\Core\Routing\UrlGeneratorInterface;
use Drupal\Core\State\StateInterface;
-
/**
* Optimizes JavaScript assets.
*/
@@ -43,6 +43,13 @@ class JsCollectionOptimizer implements AssetCollectionOptimizerInterface {
protected $state;
/**
+ * The URL generator.
+ *
+ * @var \Drupal\Core\Routing\UrlGeneratorInterface
+ */
+ protected $urlGenerator;
+
+ /**
* Constructs a JsCollectionOptimizer.
*
* @param \Drupal\Core\Asset\AssetCollectionGrouperInterface
@@ -53,12 +60,15 @@ class JsCollectionOptimizer implements AssetCollectionOptimizerInterface {
* The dumper for optimized JS assets.
* @param \Drupal\Core\State\StateInterface
* The state key/value store.
+ * @param \Drupal\Core\Routing\UrlGeneratorInterface
+ * The URL generator.
*/
- public function __construct(AssetCollectionGrouperInterface $grouper, AssetOptimizerInterface $optimizer, AssetDumperInterface $dumper, StateInterface $state) {
+ public function __construct(AssetCollectionGrouperInterface $grouper, AssetOptimizerInterface $optimizer, AssetDumperInterface $dumper, StateInterface $state, UrlGeneratorInterface $url_generator) {
$this->grouper = $grouper;
$this->optimizer = $optimizer;
$this->dumper = $dumper;
$this->state = $state;
+ $this->urlGenerator = $url_generator;
}
/**
@@ -81,11 +91,13 @@ public function optimize(array $js_assets) {
// Now optimize (concatenate, not minify) and dump each asset group, unless
// that was already done, in which case it should appear in
- // system.js_cache_files.
+ // system.js_optimized_aggregate_files (but also the unoptimized equivalent
+ // in system.js_unoptimized_aggregate_files, for JavaScript Web License
+ // Labels compliance).
// Drupal contrib can override this default JS aggregator to keep the same
// grouping, optimizing and dumping, but change the strategy that is used to
// determine when the aggregate should be rebuilt (e.g. mtime, HTTPS …).
- $map = $this->state->get('system.js_cache_files') ?: array();
+ $map = $this->state->get('system.js_optimized_aggregate_files') ?: array();
$js_assets = array();
foreach ($js_groups as $order => $js_group) {
// We have to return a single asset, not a group of assets. It is now up
@@ -109,7 +121,8 @@ public function optimize(array $js_assets) {
$uri = $map[$key];
}
if (empty($uri) || !file_exists($uri)) {
- // Concatenate each asset within the group.
+ $unoptimized_map = $this->state->get('system.js_unoptimized_aggregate_files') ?: array();
+ // Concatenate and optimize each asset within the group.
$data = '';
foreach ($js_group['items'] as $js_asset) {
$data .= $this->optimizer->optimize($js_asset);
@@ -123,7 +136,21 @@ public function optimize(array $js_assets) {
$js_assets[$order]['data'] = $uri;
// Persist the URI for this aggregate file.
$map[$key] = $uri;
- $this->state->set('system.js_cache_files', $map);
+ $this->state->set('system.js_optimized_aggregate_files', $map);
+
+ // For JavaScript License Web Labels compliance, we must also
+ // create an unoptimized (unminified) variant. These are tracked
+ // in another map that is kept in sync.
+ $data = '';
+ foreach ($js_group['items'] as $js_asset) {
+ $url = $this->urlGenerator->generateFromRoute('system.javascript_license_web_labels', array(), array('fragment' => drupal_clean_css_identifier($js_asset['data'])));
+ $data .= "/** JavaScript asset: " . $js_asset['data'] . '; for license information, see ' . $url . " **/\n\n";
+ $data .= file_get_contents($js_asset['data']);
+ $data .= ";\n";
+ }
+ $unoptimized_uri = $this->dumper->dump($data, 'js');
+ $unoptimized_map[$key] = $unoptimized_uri;
+ $this->state->set('system.js_unoptimized_aggregate_files', $unoptimized_map);
}
else {
// Use the persisted URI for the optimized JS file.
diff --git a/core/lib/Drupal/Core/Asset/LibraryDiscovery.php b/core/lib/Drupal/Core/Asset/LibraryDiscovery.php
index 2a25ea3..2798bf3 100644
--- a/core/lib/Drupal/Core/Asset/LibraryDiscovery.php
+++ b/core/lib/Drupal/Core/Asset/LibraryDiscovery.php
@@ -84,7 +84,7 @@ protected function ensureLibraryInformation($extension) {
$this->libraries[$extension] = $information;
}
else {
- $this->libraries[$extension] = FALSE;
+ $this->libraries[$extension] = array();
}
$this->setCache($extension, $this->libraries[$extension]);
}
@@ -174,6 +174,15 @@ protected function buildLibrariesByExtension($extension) {
}
}
+ // Assign Drupal's license to libraries that don't have license info.
+ if (!isset($library['license'])) {
+ $library['license'] = array(
+ 'name' => 'GNU-GPL-2.0-or-later',
+ 'remote' => 'https://drupal.org/licensing/faq',
+ 'gpl-compatible' => TRUE,
+ );
+ }
+
foreach (array('js', 'css') as $type) {
// Prepare (flatten) the SMACSS-categorized definitions.
// @todo After Asset(ic) changes, retain the definitions as-is and
@@ -249,6 +258,13 @@ protected function buildLibrariesByExtension($extension) {
$options['version'] = $library['version'];
}
+ // Don't aggregate JavaScript assets whose license is incompatible
+ // with the GPL; Drupal creates GPL-compatible JavaScript aggregates.
+ // Drupal does this for JavaScript License Web Labels compliance.
+ if ($type === 'js' && !$library['license']['gpl-compatible']) {
+ $options['preprocess'] = FALSE;
+ }
+
$library[$type][] = $options;
}
}
diff --git a/core/modules/system/lib/Drupal/system/Controller/AssetLicenseInfoController.php b/core/modules/system/lib/Drupal/system/Controller/AssetLicenseInfoController.php
new file mode 100644
index 0000000..ad2cedf
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Controller/AssetLicenseInfoController.php
@@ -0,0 +1,158 @@
+get('library.discovery')
+ );
+ }
+
+ /**
+ * Constructs a AssetLicenseInfoController object.
+ *
+ * @param \Drupal\Core\Asset\LibraryDiscoveryInterface $library_discovery
+ * The asset library discovery service.
+ */
+ public function __construct(LibraryDiscoveryInterface $library_discovery) {
+ $this->libraryDiscovery = $library_discovery;
+ }
+
+ /**
+ * Returns the JavaScript License Web Labels page for all JavaScript assets.
+ *
+ * @see http://www.gnu.org/licenses/javascript-labels.html
+ *
+ * @return array()
+ * A renderable array.
+ */
+ public function jslicense() {
+ $rows = array();
+
+ // List all extensions' JavaScript assets.
+ $module_list = $this->moduleHandler()->getModuleList();
+ $extensions = array_merge(array('core'), array_keys($module_list));
+ foreach ($extensions as $extension) {
+ $libraries = $this->libraryDiscovery->getLibrariesByExtension($extension);
+ $rows_for_extension = array();
+ foreach ($libraries as $library) {
+ $license_column = l($library['license']['name'], $library['license']['remote']);
+ if (!isset($library['js'])) {
+ continue;
+ }
+ foreach ($library['js'] as $js_asset) {
+ // This is core's special "drupalSettings" JavaScript asset.
+ if ($js_asset['type'] === 'setting') {
+ continue;
+ }
+ else if ($js_asset['type'] === 'external') {
+ $url = $js_asset['data'];
+ }
+ else {
+ $url = file_create_url($js_asset['data']);
+ }
+ $js_asset_basename = basename($js_asset['data']);
+ $js_file_column = l('' . $js_asset_basename . '
', $url, array('html' => TRUE));
+ $source_column = l('' . $js_asset_basename . '
', $url, array('html' => TRUE));
+ $rows_for_extension[] = array(
+ 'data' => array($js_file_column, $license_column, $source_column),
+ 'id' => drupal_clean_css_identifier($js_asset['data']),
+ );
+ }
+ }
+
+ // Add header for the current extension; add all rows for this extension.
+ if (count($rows_for_extension)) {
+ if ($extension !== 'core') {
+ $rows[] = array(
+ array(
+ 'data' => $this->t('JavaScript files of the @extension-name
@extension-type', array(
+ '@extension-name' => $module_list[$extension]->getName(),
+ '@extension-type' => $module_list[$extension]->getType(),
+ )),
+ 'header' => TRUE,
+ 'colspan' => 3,
+ ),
+ );
+ }
+ foreach ($rows_for_extension as $row) {
+ $rows[] = $row;
+ }
+ }
+ }
+
+ // List all aggregated (and optimized) JavaScript assets.
+ $rows[] = array(
+ array(
+ 'data' => $this->t('Aggregated & optimized (minified) JavaScript files'),
+ 'header' => TRUE,
+ 'colspan' => 3,
+ ),
+ );
+ $aggregated_js_assets = \Drupal::state()->get('system.js_optimized_aggregate_files');
+ if (isset($aggregated_js_assets)) {
+ $unoptimized_aggregated_js_assets = \Drupal::state()->get('system.js_unoptimized_aggregate_files');
+ $license_column = l('GNU-GPL-2.0-or-later', 'https://drupal.org/licensing/faq');
+ foreach (array_keys($aggregated_js_assets) as $hash) {
+ $optimized_js = $aggregated_js_assets[$hash];
+ $unoptimized_js = $unoptimized_aggregated_js_assets[$hash];
+ $js_file_column = l('' . basename($optimized_js) . '
', file_create_url($optimized_js), array('html' => TRUE));
+ $source_column = l('' . basename($unoptimized_js) . '
', file_create_url($unoptimized_js), array('html' => TRUE));
+ $rows[] = array($js_file_column, $license_column, $source_column);
+ }
+ }
+
+ $table = array(
+ '#type' => 'table',
+ '#header' => array(
+ 'javascript_asset' => array(
+ 'data' => $this->t('JavaScript file'),
+ ),
+ 'license' => array(
+ 'data' => $this->t('License'),
+ ),
+ 'source' => array(
+ 'data' => $this->t('Source code'),
+ 'class' => array(RESPONSIVE_PRIORITY_MEDIUM),
+ ),
+
+ ),
+ '#sticky' => TRUE,
+ '#responsive' => TRUE,
+ '#rows' => $rows,
+ '#attributes' => array(
+ // This ID is required by the JavaScript License Web Labels standard;
+ // see http://www.gnu.org/licenses/javascript-labels.html.
+ 'id' => 'jslicense-labels1',
+ ),
+ );
+
+ return $table;
+ }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/JavaScriptTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/JavaScriptTest.php
index f6f3da8..7131877 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Common/JavaScriptTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Common/JavaScriptTest.php
@@ -38,6 +38,11 @@ public static function getInfo() {
function setUp() {
parent::setUp();
+
+ // Ensure the system.javascript_license_web_labels route is available.
+ $this->installSchema('system', array('router'));
+ \Drupal::service('router.builder')->rebuild();
+
// There are dependencies in drupal_get_js() on the theme layer so we need
// to initialize it.
drupal_theme_initialize();
@@ -460,11 +465,11 @@ function testAggregationOrder() {
drupal_render($scripts_html);
// Store the expected key for the first item in the cache.
- $cache = array_keys(\Drupal::state()->get('system.js_cache_files') ?: array());
+ $cache = array_keys(\Drupal::state()->get('system.js_optimized_aggregate_files') ?: array());
$expected_key = $cache[0];
// Reset variables and add a file in a different scope first.
- \Drupal::state()->delete('system.js_cache_files');
+ \Drupal::state()->delete('system.js_optimized_aggregate_file');
drupal_static_reset('_drupal_add_js');
$attached = array();
$attached['#attached']['library'][] = 'core/drupal';
@@ -485,7 +490,7 @@ function testAggregationOrder() {
drupal_render($scripts_html);
// Compare the expected key for the first file to the current one.
- $cache = array_keys(\Drupal::state()->get('system.js_cache_files') ?: array());
+ $cache = array_keys(\Drupal::state()->get('system.js_optimized_aggregate_files') ?: array());
$key = $cache[0];
$this->assertEqual($key, $expected_key, 'JavaScript aggregation is not affected by ordering in different scopes.');
}
diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml
index 13019c2..dadbf74 100644
--- a/core/modules/system/system.routing.yml
+++ b/core/modules/system/system.routing.yml
@@ -408,3 +408,11 @@ system.batch_page.json:
system.update:
path: '/core/update.php'
+
+system.javascript_license_web_labels:
+ path: '/system/jslicense'
+ defaults:
+ _content: '\Drupal\system\Controller\AssetLicenseInfoController::jslicense'
+ _title: 'JavaScript license information'
+ requirements:
+ _access: 'TRUE'
diff --git a/core/tests/Drupal/Tests/Core/Asset/LibraryDiscoveryTest.php b/core/tests/Drupal/Tests/Core/Asset/LibraryDiscoveryTest.php
index 230cac6..815e942 100644
--- a/core/tests/Drupal/Tests/Core/Asset/LibraryDiscoveryTest.php
+++ b/core/tests/Drupal/Tests/Core/Asset/LibraryDiscoveryTest.php
@@ -173,7 +173,7 @@ public function testGetLibraryWithMissingLibraryFile() {
$this->libraryDiscovery->setPaths('module', 'example_module', $path);
$this->assertFalse($this->libraryDiscovery->getLibraryByName('example_module', 'example'));
- $this->assertFalse($this->libraryDiscovery->getLibrariesByExtension('example_module'));
+ $this->assertSame(array(), $this->libraryDiscovery->getLibrariesByExtension('example_module'));
}
/**
@@ -376,6 +376,71 @@ public function testLibraryWithDataTypes() {
}
/**
+ * Tests a library with various licenses, some GPL-compatible, some not.
+ *
+ * @covers ::buildLibrariesByExtension()
+ */
+ public function testLibraryWithLicenses() {
+ $this->moduleHandler->expects($this->atLeastOnce())
+ ->method('moduleExists')
+ ->with('licenses')
+ ->will($this->returnValue(TRUE));
+
+ $path = __DIR__ . '/library_test_files';
+ $path = substr($path, strlen(DRUPAL_ROOT) + 1);
+ $this->libraryDiscovery->setPaths('module', 'licenses', $path);
+
+ // For libraries without license info, the default license is applied.
+ $library = $this->libraryDiscovery->getLibraryByName('licenses', 'no-license-info');
+ $this->assertCount(1, $library['css']);
+ $this->assertCount(1, $library['js']);
+ $this->assertTrue(isset($library['license']));
+ $default_license = array(
+ 'name' => 'GNU-GPL-2.0-or-later',
+ 'remote' => 'https://drupal.org/licensing/faq',
+ 'gpl-compatible' => TRUE,
+ );
+ $this->assertEquals($library['license'], $default_license, 'Default license is applied.');
+ $this->assertFalse(isset($library['css'][0]['preprocess']), 'Library without license: CSS is preprocessable.');
+ $this->assertFalse(isset($library['js'][0]['preprocess']), 'Library without license: JS is preprocessable.');
+
+ // GPL2-licensed libraries are preprocessable.
+ $library = $this->libraryDiscovery->getLibraryByName('licenses', 'gpl2');
+ $this->assertCount(1, $library['css']);
+ $this->assertCount(1, $library['js']);
+ $this->assertFalse(isset($library['css'][0]['preprocess']), 'GPL2 library: CSS is preprocessable.');
+ $this->assertFalse(isset($library['js'][0]['preprocess']), 'GPL2 library: JS is preprocessable.');
+
+ // MIT-licensed libraries are preprocessable.
+ $library = $this->libraryDiscovery->getLibraryByName('licenses', 'mit');
+ $this->assertCount(1, $library['css']);
+ $this->assertCount(1, $library['js']);
+ $this->assertFalse(isset($library['css'][0]['preprocess']), 'MIT library: CSS is preprocessable.');
+ $this->assertFalse(isset($library['js'][0]['preprocess']), 'MIT library: JS is preprocessable.');
+
+ // Libraries in the Public Domain are preprocessable.
+ $library = $this->libraryDiscovery->getLibraryByName('licenses', 'public-domain');
+ $this->assertCount(1, $library['css']);
+ $this->assertCount(1, $library['js']);
+ $this->assertFalse(isset($library['css'][0]['preprocess']), 'Public Domain library: CSS is preprocessable.');
+ $this->assertFalse(isset($library['js'][0]['preprocess']), 'Public Domain library: JS is preprocessable.');
+
+ // Apache-licensed libraries are not preprocessable.
+ $library = $this->libraryDiscovery->getLibraryByName('licenses', 'apache');
+ $this->assertCount(1, $library['css']);
+ $this->assertCount(1, $library['js']);
+ $this->assertFalse(isset($library['css'][0]['preprocess']), 'Apache library: CSS is preprocessable.');
+ $this->assertFalse($library['js'][0]['preprocess'], 'Apache library: JS is not preprocessable.');
+
+ // Copyrighted libraries are preprocessable.
+ $library = $this->libraryDiscovery->getLibraryByName('licenses', 'copyright');
+ $this->assertCount(1, $library['css']);
+ $this->assertCount(1, $library['js']);
+ $this->assertFalse(isset($library['css'][0]['preprocess']), 'Copyrighted library: CSS is preprocessable.');
+ $this->assertFalse($library['js'][0]['preprocess'], 'Copyrighted library: JS is not preprocessable.');
+ }
+
+ /**
* Tests the internal static cache.
*
* @covers ::ensureLibraryInformation()
diff --git a/core/tests/Drupal/Tests/Core/Asset/library_test_files/licenses.libraries.yml b/core/tests/Drupal/Tests/Core/Asset/library_test_files/licenses.libraries.yml
new file mode 100644
index 0000000..3284c96
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Asset/library_test_files/licenses.libraries.yml
@@ -0,0 +1,72 @@
+# No license information: should default to GPL 2.0 or later..
+no-license-info:
+ version: 1.0
+ js:
+ no-license-info.js: {}
+ css:
+ base:
+ no-license-info.css: {}
+
+# A library with all assets licensed under the GPL-compatible GPL 2 license.
+gpl2:
+ version: 1.0
+ license:
+ name: gpl2
+ remote: https://url-to-gpl2-license
+ gpl-compatible: true
+ js:
+ gpl2.js: {}
+ css:
+ base:
+ gpl2.css: {}
+
+# A library with all assets licensed under the GPL-compatible MIT license.
+mit:
+ version: 1.0
+ license:
+ name: MIT
+ remote: https://url-to-mit-license
+ gpl-compatible: true
+ js:
+ mit.js: {}
+ css:
+ base:
+ mit.css: {}
+
+# A library with all assets licensed under the GPL-compatible Public Domain.
+public-domain:
+ version: 1.0
+ license:
+ name: Public Domain
+ remote: https://url-to-public-domain-license
+ gpl-compatible: true
+ js:
+ public-domain.js: {}
+ css:
+ base:
+ public-domain.css: {}
+
+# A library with all assets licensed under the GPL-incompatible Apache license.
+apache:
+ version: 1.0
+ license:
+ name: apache
+ remote: https://url-to-apache-license
+ gpl-compatible: false
+ js:
+ apache.js: {}
+ css:
+ base:
+ apache.css: {}
+
+# A library with all assets licensed under the GPL-incompatible copyright.
+copyright:
+ version: 1.0
+ license:
+ name: © Some company
+ gpl-compatible: false
+ js:
+ copyright.js: {}
+ css:
+ base:
+ copyright.css: {}