diff --git a/composer.json b/composer.json index 2db2bf6f3c3fa43e289842a6dc9125ca10c05459..9c7e329b12e500ed1be85965e95ffbc93c5606ea 100644 --- a/composer.json +++ b/composer.json @@ -58,11 +58,30 @@ "type": "zip" } } + }, + { + "type": "package", + "package": { + "name": "ckeditor/indentblock", + "version": "4.8.0", + "type": "drupal-library", + "extra": { + "installer-name": "ckeditor.indentblock" + }, + "dist": { + "url": "https://download.ckeditor.com/indentblock/releases/indentblock_4.8.0.zip", + "type": "zip" + }, + "require": { + "composer/installers": "~1.0" + } + } } ], "require": { "php": ">=7.0.8", "browserstate/history.js": "^1.8", + "ckeditor/indentblock": "4.8.0", "composer/installers": "^1.0.20", "cweagans/composer-patches": "^1.0", "desandro/imagesloaded": "4.1.4", @@ -75,10 +94,12 @@ "drupal/administerusersbyrole": "2.0-alpha6", "drupal/allowed_formats": "1.1", "drupal/better_exposed_filters": "3.0-alpha4", + "drupal/bigmenu": "^1.0@alpha", "drupal/block_field": "^1.0@alpha", "drupal/block_permissions": "^1.0", "drupal/block_region_permissions": "^1.2", "drupal/bootstrap": "3.16", + "drupal/ckeditor_indentblock": "^1.0@beta", "drupal/config_direct_save": "^1.0", "drupal/config_ignore": "^2.1", "drupal/config_installer": "^1.0", @@ -110,6 +131,7 @@ "drupal/honeypot": "^1.28", "drupal/image_popup": "1.1", "drupal/inline_entity_form": "1.0-rc1", + "drupal/libraries": "^3.0@alpha", "drupal/link_attributes": "1.0", "drupal/linkit": "5.0-beta6", "drupal/magnific_popup": "1.3", diff --git a/composer.lock b/composer.lock index d42fac30dfb34ccb96b9ea9819374ba606cd6649..265e32e693ed73033aa8ae10f3ec000bc7f91c17 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "8e0a476fe788ae140996a08bbf6f6798", + "content-hash": "1c264aa2c7957e793131a928d4e80a02", "packages": [ { "name": "alchemy/zippy", @@ -132,6 +132,23 @@ }, "type": "drupal-library" }, + { + "name": "ckeditor/indentblock", + "version": "4.8.0", + "dist": { + "type": "zip", + "url": "https://download.ckeditor.com/indentblock/releases/indentblock_4.8.0.zip", + "reference": null, + "shasum": null + }, + "require": { + "composer/installers": "~1.0" + }, + "type": "drupal-library", + "extra": { + "installer-name": "ckeditor.indentblock" + } + }, { "name": "commerceguys/addressing", "version": "v1.0.0", @@ -1627,6 +1644,57 @@ "source": "http://cgit.drupalcode.org/better_exposed_filters" } }, + { + "name": "drupal/bigmenu", + "version": "1.0.0-alpha1", + "source": { + "type": "git", + "url": "https://git.drupal.org/project/bigmenu", + "reference": "8.x-1.0-alpha1" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/bigmenu-8.x-1.0-alpha1.zip", + "reference": "8.x-1.0-alpha1", + "shasum": "61295e62f244be63aa40db4b3f4493790cd7e0e3" + }, + "require": { + "drupal/core": "*" + }, + "type": "drupal-module", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + }, + "drupal": { + "version": "8.x-1.0-alpha1", + "datestamp": "1539211680", + "security-coverage": { + "status": "not-covered", + "message": "Alpha releases are not covered by Drupal security advisories." + } + } + }, + "notification-url": "https://packages.drupal.org/8/downloads", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "acbramley", + "homepage": "https://www.drupal.org/user/1036766" + }, + { + "name": "dman", + "homepage": "https://www.drupal.org/user/33240" + } + ], + "description": "Scalable replacement for core menu management screen. Uses AJAX to replace the global menu management page, suitable for thousands of items", + "homepage": "https://www.drupal.org/project/bigmenu", + "support": { + "source": "http://cgit.drupalcode.org/bigmenu" + } + }, { "name": "drupal/block_field", "version": "1.0.0-alpha8", @@ -1848,6 +1916,60 @@ "irc": "irc://irc.freenode.org/drupal-bootstrap" } }, + { + "name": "drupal/ckeditor_indentblock", + "version": "1.0.0-beta1", + "source": { + "type": "git", + "url": "https://git.drupal.org/project/ckeditor_indentblock", + "reference": "8.x-1.0-beta1" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/ckeditor_indentblock-8.x-1.0-beta1.zip", + "reference": "8.x-1.0-beta1", + "shasum": "3841d9e9f15d6e143715aa518dd92033febf6443" + }, + "require": { + "drupal/ckeditor": "*", + "drupal/core": "^8.1.0", + "drupal/libraries": "*" + }, + "type": "drupal-module", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + }, + "drupal": { + "version": "8.x-1.0-beta1", + "datestamp": "1507249143", + "security-coverage": { + "status": "not-covered", + "message": "Project has not opted into security advisory coverage!" + } + } + }, + "notification-url": "https://packages.drupal.org/8/downloads", + "license": [ + "GPL-2.0+" + ], + "authors": [ + { + "name": "Christian Meilinger (meichr)", + "homepage": "https://www.drupal.org/u/meichr", + "role": "Maintainer" + } + ], + "description": "Provides integration of the CKEditor IndentBlock plugin with the Drupal 8 CKEditor.", + "homepage": "https://www.drupal.org/project/ckeditor_indentblock", + "keywords": [ + "Drupal" + ], + "support": { + "source": "http://cgit.drupalcode.org/ckeditor_indentblock/", + "issues": "https://www.drupal.org/project/issues/ckeditor_indentblock" + } + }, { "name": "drupal/config_direct_save", "version": "1.0.0", @@ -4284,6 +4406,67 @@ "source": "http://cgit.drupalcode.org/inline_entity_form" } }, + { + "name": "drupal/libraries", + "version": "3.0.0-alpha1", + "source": { + "type": "git", + "url": "https://git.drupal.org/project/libraries", + "reference": "8.x-3.0-alpha1" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/libraries-8.x-3.0-alpha1.zip", + "reference": "8.x-3.0-alpha1", + "shasum": "bb07036b1eaeea7d736fc7e72416238830cd8d67" + }, + "require": { + "drupal/core": "~8.0" + }, + "type": "drupal-module", + "extra": { + "branch-alias": { + "dev-3.x": "3.x-dev" + }, + "drupal": { + "version": "8.x-3.0-alpha1", + "datestamp": "1517046484", + "security-coverage": { + "status": "not-covered", + "message": "Alpha releases are not covered by Drupal security advisories." + } + } + }, + "notification-url": "https://packages.drupal.org/8/downloads", + "license": [ + "GPL-2.0+" + ], + "authors": [ + { + "name": "Pol", + "homepage": "https://www.drupal.org/user/47194" + }, + { + "name": "rjacobs", + "homepage": "https://www.drupal.org/user/422459" + }, + { + "name": "sun", + "homepage": "https://www.drupal.org/user/54136" + }, + { + "name": "tstoeckler", + "homepage": "https://www.drupal.org/user/107158" + } + ], + "description": "Allows version-dependent and shared usage of external libraries in Drupal.", + "homepage": "http://drupal.org/project/libraries", + "support": { + "source": "http://cgit.drupalcode.org/libraries", + "issues": "http://drupal.org/project/issues/libraries", + "irc": "irc://irc.freenode.org/drupal-contribute" + } + }, { "name": "drupal/link_attributes", "version": "1.0.0", @@ -10844,8 +11027,11 @@ "aliases": [], "minimum-stability": "dev", "stability-flags": { + "drupal/bigmenu": 15, "drupal/block_field": 15, + "drupal/ckeditor_indentblock": 10, "drupal/entity_clone": 10, + "drupal/libraries": 15, "drupal/migrate_devel": 20, "drupal/realname": 5, "drupal/roleassign": 15, diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index 98462dba9d2a86da8046ac7b08ef466c5a869549..42f12c17650196a3c03746eb6ae2a5b6cee85eeb 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -131,6 +131,25 @@ "type": "drupal-library", "installation-source": "source" }, + { + "name": "ckeditor/indentblock", + "version": "4.8.0", + "version_normalized": "4.8.0.0", + "dist": { + "type": "zip", + "url": "https://download.ckeditor.com/indentblock/releases/indentblock_4.8.0.zip", + "reference": null, + "shasum": null + }, + "require": { + "composer/installers": "~1.0" + }, + "type": "drupal-library", + "extra": { + "installer-name": "ckeditor.indentblock" + }, + "installation-source": "dist" + }, { "name": "commerceguys/addressing", "version": "v1.0.0", @@ -1680,6 +1699,59 @@ "source": "http://cgit.drupalcode.org/better_exposed_filters" } }, + { + "name": "drupal/bigmenu", + "version": "1.0.0-alpha1", + "version_normalized": "1.0.0.0-alpha1", + "source": { + "type": "git", + "url": "https://git.drupal.org/project/bigmenu", + "reference": "8.x-1.0-alpha1" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/bigmenu-8.x-1.0-alpha1.zip", + "reference": "8.x-1.0-alpha1", + "shasum": "61295e62f244be63aa40db4b3f4493790cd7e0e3" + }, + "require": { + "drupal/core": "*" + }, + "type": "drupal-module", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + }, + "drupal": { + "version": "8.x-1.0-alpha1", + "datestamp": "1539211680", + "security-coverage": { + "status": "not-covered", + "message": "Alpha releases are not covered by Drupal security advisories." + } + } + }, + "installation-source": "dist", + "notification-url": "https://packages.drupal.org/8/downloads", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "acbramley", + "homepage": "https://www.drupal.org/user/1036766" + }, + { + "name": "dman", + "homepage": "https://www.drupal.org/user/33240" + } + ], + "description": "Scalable replacement for core menu management screen. Uses AJAX to replace the global menu management page, suitable for thousands of items", + "homepage": "https://www.drupal.org/project/bigmenu", + "support": { + "source": "http://cgit.drupalcode.org/bigmenu" + } + }, { "name": "drupal/block_field", "version": "1.0.0-alpha8", @@ -1909,6 +1981,62 @@ "irc": "irc://irc.freenode.org/drupal-bootstrap" } }, + { + "name": "drupal/ckeditor_indentblock", + "version": "1.0.0-beta1", + "version_normalized": "1.0.0.0-beta1", + "source": { + "type": "git", + "url": "https://git.drupal.org/project/ckeditor_indentblock", + "reference": "8.x-1.0-beta1" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/ckeditor_indentblock-8.x-1.0-beta1.zip", + "reference": "8.x-1.0-beta1", + "shasum": "3841d9e9f15d6e143715aa518dd92033febf6443" + }, + "require": { + "drupal/ckeditor": "*", + "drupal/core": "^8.1.0", + "drupal/libraries": "*" + }, + "type": "drupal-module", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + }, + "drupal": { + "version": "8.x-1.0-beta1", + "datestamp": "1507249143", + "security-coverage": { + "status": "not-covered", + "message": "Project has not opted into security advisory coverage!" + } + } + }, + "installation-source": "dist", + "notification-url": "https://packages.drupal.org/8/downloads", + "license": [ + "GPL-2.0+" + ], + "authors": [ + { + "name": "Christian Meilinger (meichr)", + "homepage": "https://www.drupal.org/u/meichr", + "role": "Maintainer" + } + ], + "description": "Provides integration of the CKEditor IndentBlock plugin with the Drupal 8 CKEditor.", + "homepage": "https://www.drupal.org/project/ckeditor_indentblock", + "keywords": [ + "Drupal" + ], + "support": { + "source": "http://cgit.drupalcode.org/ckeditor_indentblock/", + "issues": "https://www.drupal.org/project/issues/ckeditor_indentblock" + } + }, { "name": "drupal/config_direct_save", "version": "1.0.0", @@ -4415,6 +4543,69 @@ "source": "http://cgit.drupalcode.org/inline_entity_form" } }, + { + "name": "drupal/libraries", + "version": "3.0.0-alpha1", + "version_normalized": "3.0.0.0-alpha1", + "source": { + "type": "git", + "url": "https://git.drupal.org/project/libraries", + "reference": "8.x-3.0-alpha1" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/libraries-8.x-3.0-alpha1.zip", + "reference": "8.x-3.0-alpha1", + "shasum": "bb07036b1eaeea7d736fc7e72416238830cd8d67" + }, + "require": { + "drupal/core": "~8.0" + }, + "type": "drupal-module", + "extra": { + "branch-alias": { + "dev-3.x": "3.x-dev" + }, + "drupal": { + "version": "8.x-3.0-alpha1", + "datestamp": "1517046484", + "security-coverage": { + "status": "not-covered", + "message": "Alpha releases are not covered by Drupal security advisories." + } + } + }, + "installation-source": "dist", + "notification-url": "https://packages.drupal.org/8/downloads", + "license": [ + "GPL-2.0+" + ], + "authors": [ + { + "name": "Pol", + "homepage": "https://www.drupal.org/user/47194" + }, + { + "name": "rjacobs", + "homepage": "https://www.drupal.org/user/422459" + }, + { + "name": "sun", + "homepage": "https://www.drupal.org/user/54136" + }, + { + "name": "tstoeckler", + "homepage": "https://www.drupal.org/user/107158" + } + ], + "description": "Allows version-dependent and shared usage of external libraries in Drupal.", + "homepage": "http://drupal.org/project/libraries", + "support": { + "source": "http://cgit.drupalcode.org/libraries", + "issues": "http://drupal.org/project/issues/libraries", + "irc": "irc://irc.freenode.org/drupal-contribute" + } + }, { "name": "drupal/link_attributes", "version": "1.0.0", diff --git a/web/libraries/ckeditor.indentblock/plugin.js b/web/libraries/ckeditor.indentblock/plugin.js new file mode 100644 index 0000000000000000000000000000000000000000..e501941de5a272297091bdc97016466d2cd0722a --- /dev/null +++ b/web/libraries/ckeditor.indentblock/plugin.js @@ -0,0 +1,312 @@ +/** + * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @fileOverview Handles the indentation of block elements. + */ + +( function() { + 'use strict'; + + var $listItem = CKEDITOR.dtd.$listItem, + $list = CKEDITOR.dtd.$list, + TRISTATE_DISABLED = CKEDITOR.TRISTATE_DISABLED, + TRISTATE_OFF = CKEDITOR.TRISTATE_OFF; + + CKEDITOR.plugins.add( 'indentblock', { + requires: 'indent', + init: function( editor ) { + var globalHelpers = CKEDITOR.plugins.indent, + classes = editor.config.indentClasses; + + // Register commands. + globalHelpers.registerCommands( editor, { + indentblock: new commandDefinition( editor, 'indentblock', true ), + outdentblock: new commandDefinition( editor, 'outdentblock' ) + } ); + + function commandDefinition() { + globalHelpers.specificDefinition.apply( this, arguments ); + + this.allowedContent = { + 'div h1 h2 h3 h4 h5 h6 ol p pre ul': { + // Do not add elements, but only text-align style if element is validated by other rule. + propertiesOnly: true, + styles: !classes ? 'margin-left,margin-right' : null, + classes: classes || null + } + }; + + this.contentTransformations = [ + [ 'div: splitMarginShorthand' ], + [ 'h1: splitMarginShorthand' ], + [ 'h2: splitMarginShorthand' ], + [ 'h3: splitMarginShorthand' ], + [ 'h4: splitMarginShorthand' ], + [ 'h5: splitMarginShorthand' ], + [ 'h6: splitMarginShorthand' ], + [ 'ol: splitMarginShorthand' ], + [ 'p: splitMarginShorthand' ], + [ 'pre: splitMarginShorthand' ], + [ 'ul: splitMarginShorthand' ] + ]; + + if ( this.enterBr ) + this.allowedContent.div = true; + + this.requiredContent = ( this.enterBr ? 'div' : 'p' ) + + ( classes ? '(' + classes.join( ',' ) + ')' : '{margin-left}' ); + + this.jobs = { + '20': { + refresh: function( editor, path ) { + var firstBlock = path.block || path.blockLimit; + + // Switch context from somewhere inside list item to list item, + // if not found just assign self (doing nothing). + if ( !firstBlock.is( $listItem ) ) { + var ascendant = firstBlock.getAscendant( $listItem ); + + firstBlock = ( ascendant && path.contains( ascendant ) ) || firstBlock; + } + + // Switch context from list item to list + // because indentblock can indent entire list + // but not a single list element. + + if ( firstBlock.is( $listItem ) ) + firstBlock = firstBlock.getParent(); + + // [-] Context in the path or ENTER_BR + // + // Don't try to indent if the element is out of + // this plugin's scope. This assertion is omitted + // if ENTER_BR is in use since there may be no block + // in the path. + + if ( !this.enterBr && !this.getContext( path ) ) + return TRISTATE_DISABLED; + + else if ( classes ) { + + // [+] Context in the path or ENTER_BR + // [+] IndentClasses + // + // If there are indentation classes, check if reached + // the highest level of indentation. If so, disable + // the command. + + if ( indentClassLeft.call( this, firstBlock, classes ) ) + return TRISTATE_OFF; + else + return TRISTATE_DISABLED; + } else { + + // [+] Context in the path or ENTER_BR + // [-] IndentClasses + // [+] Indenting + // + // No indent-level limitations due to indent classes. + // Indent-like command can always be executed. + + if ( this.isIndent ) + return TRISTATE_OFF; + + // [+] Context in the path or ENTER_BR + // [-] IndentClasses + // [-] Indenting + // [-] Block in the path + // + // No block in path. There's no element to apply indentation + // so disable the command. + + else if ( !firstBlock ) + return TRISTATE_DISABLED; + + // [+] Context in the path or ENTER_BR + // [-] IndentClasses + // [-] Indenting + // [+] Block in path. + // + // Not using indentClasses but there is firstBlock. + // We can calculate current indentation level and + // try to increase/decrease it. + + else { + return CKEDITOR[ + ( getIndent( firstBlock ) || 0 ) <= 0 ? 'TRISTATE_DISABLED' : 'TRISTATE_OFF' + ]; + } + } + }, + + exec: function( editor ) { + var selection = editor.getSelection(), + range = selection && selection.getRanges()[ 0 ], + nearestListBlock; + + // If there's some list in the path, then it will be + // a full-list indent by increasing or decreasing margin property. + if ( ( nearestListBlock = editor.elementPath().contains( $list ) ) ) + indentElement.call( this, nearestListBlock, classes ); + + // If no list in the path, use iterator to indent all the possible + // paragraphs in the range, creating them if necessary. + else { + var iterator = range.createIterator(), + enterMode = editor.config.enterMode, + block; + + iterator.enforceRealBlocks = true; + iterator.enlargeBr = enterMode != CKEDITOR.ENTER_BR; + + while ( ( block = iterator.getNextParagraph( enterMode == CKEDITOR.ENTER_P ? 'p' : 'div' ) ) ) { + if ( !block.isReadOnly() ) + indentElement.call( this, block, classes ); + } + } + + return true; + } + } + }; + } + + CKEDITOR.tools.extend( commandDefinition.prototype, globalHelpers.specificDefinition.prototype, { + // Elements that, if in an elementpath, will be handled by this + // command. They restrict the scope of the plugin. + context: { div: 1, dl: 1, h1: 1, h2: 1, h3: 1, h4: 1, h5: 1, h6: 1, ul: 1, ol: 1, p: 1, pre: 1, table: 1 }, + + // A regex built on config#indentClasses to detect whether an + // element has some indentClass or not. + classNameRegex: classes ? new RegExp( '(?:^|\\s+)(' + classes.join( '|' ) + ')(?=$|\\s)' ) : null + } ); + } + } ); + + // Generic indentation procedure for indentation of any element + // either with margin property or config#indentClass. + function indentElement( element, classes, dir ) { + if ( element.getCustomData( 'indent_processed' ) ) + return; + + var editor = this.editor, + isIndent = this.isIndent; + + if ( classes ) { + // Transform current class f to indent step index. + var indentClass = element.$.className.match( this.classNameRegex ), + indentStep = 0; + + if ( indentClass ) { + indentClass = indentClass[ 1 ]; + indentStep = CKEDITOR.tools.indexOf( classes, indentClass ) + 1; + } + + // Operate on indent step index, transform indent step index + // back to class name. + if ( ( indentStep += isIndent ? 1 : -1 ) < 0 ) + return; + + indentStep = Math.min( indentStep, classes.length ); + indentStep = Math.max( indentStep, 0 ); + element.$.className = CKEDITOR.tools.ltrim( element.$.className.replace( this.classNameRegex, '' ) ); + + if ( indentStep > 0 ) + element.addClass( classes[ indentStep - 1 ] ); + } else { + var indentCssProperty = getIndentCss( element, dir ), + currentOffset = parseInt( element.getStyle( indentCssProperty ), 10 ), + indentOffset = editor.config.indentOffset || 40; + + if ( isNaN( currentOffset ) ) + currentOffset = 0; + + currentOffset += ( isIndent ? 1 : -1 ) * indentOffset; + + if ( currentOffset < 0 ) + return; + + currentOffset = Math.max( currentOffset, 0 ); + currentOffset = Math.ceil( currentOffset / indentOffset ) * indentOffset; + + element.setStyle( + indentCssProperty, + currentOffset ? currentOffset + ( editor.config.indentUnit || 'px' ) : '' + ); + + if ( element.getAttribute( 'style' ) === '' ) + element.removeAttribute( 'style' ); + } + + CKEDITOR.dom.element.setMarker( this.database, element, 'indent_processed', 1 ); + + return; + } + + // Method that checks if current indentation level for an element + // reached the limit determined by config#indentClasses. + function indentClassLeft( node, classes ) { + var indentClass = node.$.className.match( this.classNameRegex ), + isIndent = this.isIndent; + + // If node has one of the indentClasses: + // * If it holds the topmost indentClass, then + // no more classes have left. + // * If it holds any other indentClass, it can use the next one + // or the previous one. + // * Outdent is always possible. We can remove indentClass. + if ( indentClass ) + return isIndent ? indentClass[ 1 ] != classes.slice( -1 ) : true; + + // If node has no class which belongs to indentClasses, + // then it is at 0-level. It can be indented but not outdented. + else + return isIndent; + } + + // Determines indent CSS property for an element according to + // what is the direction of such element. It can be either `margin-left` + // or `margin-right`. + function getIndentCss( element, dir ) { + return ( dir || element.getComputedStyle( 'direction' ) ) == 'ltr' ? 'margin-left' : 'margin-right'; + } + + // Return the numerical indent value of margin-left|right of an element, + // considering element's direction. If element has no margin specified, + // NaN is returned. + function getIndent( element ) { + return parseInt( element.getStyle( getIndentCss( element ) ), 10 ); + } +} )(); + +/** + * A list of classes to use for indenting the contents. If set to `null`, no classes will be used + * and instead the {@link #indentUnit} and {@link #indentOffset} properties will be used. + * + * // Use the 'Indent1', 'Indent2', 'Indent3' classes. + * config.indentClasses = ['Indent1', 'Indent2', 'Indent3']; + * + * @cfg {Array} [indentClasses=null] + * @member CKEDITOR.config + */ + +/** + * The size in {@link CKEDITOR.config#indentUnit indentation units} of each indentation step. + * + * config.indentOffset = 4; + * + * @cfg {Number} [indentOffset=40] + * @member CKEDITOR.config + */ + +/** + * The unit used for {@link CKEDITOR.config#indentOffset indentation offset}. + * + * config.indentUnit = 'em'; + * + * @cfg {String} [indentUnit='px'] + * @member CKEDITOR.config + */ diff --git a/web/modules/bigmenu/LICENSE.txt b/web/modules/bigmenu/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..d159169d1050894d3ea3b98e1c965c4058208fe1 --- /dev/null +++ b/web/modules/bigmenu/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/web/modules/bigmenu/bigmenu.info.yml b/web/modules/bigmenu/bigmenu.info.yml new file mode 100644 index 0000000000000000000000000000000000000000..e2e6beecf1a9b9d1501b1f44a4b24903cfc686fa --- /dev/null +++ b/web/modules/bigmenu/bigmenu.info.yml @@ -0,0 +1,12 @@ +name: Big Menu +type: module +description: Scalable replacement for core menu management screen. Uses AJAX to replace the global menu management page, suitable for thousands of items +# core: 8.x +dependencies: + - menu_ui + +# Information added by Drupal.org packaging script on 2018-10-10 +version: '8.x-1.0-alpha1' +core: '8.x' +project: 'bigmenu' +datestamp: 1539211683 diff --git a/web/modules/bigmenu/bigmenu.module b/web/modules/bigmenu/bigmenu.module new file mode 100644 index 0000000000000000000000000000000000000000..055e1c291f05e7b84d4a72462e2630c20be5cfe5 --- /dev/null +++ b/web/modules/bigmenu/bigmenu.module @@ -0,0 +1,18 @@ +<?php + +/** + * @file + * Alternative to core menu management. + * + * Needed when menus get too big to load on one page. + */ + +use Drupal\bigmenu\BigMenuForm; + +/** + * Implements hook_entity_type_alter(). + */ +function bigmenu_entity_type_alter(array &$entity_types) { + /** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */ + $entity_types['menu']->setFormClass('edit', BigMenuForm::class); +} diff --git a/web/modules/bigmenu/src/BigMenuForm.php b/web/modules/bigmenu/src/BigMenuForm.php new file mode 100644 index 0000000000000000000000000000000000000000..0fa3d514777d5bfc776abcf04ef8bf119f6ccada --- /dev/null +++ b/web/modules/bigmenu/src/BigMenuForm.php @@ -0,0 +1,249 @@ +<?php + +namespace Drupal\bigmenu; + +use Drupal\Core\Url; +use Drupal\menu_ui\MenuForm; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Menu\MenuLinkTreeElement; +use Drupal\Core\Menu\MenuTreeParameters; +use Drupal\Core\Render\Element; + +/** + * Defines class for BigMenuForm. + */ +class BigMenuForm extends MenuForm { + + /** + * The menu tree. + * + * @var array + */ + protected $tree = []; + + /** + * Overrides Drupal\menu_ui\MenuForm::buildOverviewForm() to limit the depth. + * + * @param array $form + * The form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state object. + * + * @return array + * The form. + */ + protected function buildOverviewForm(array &$form, FormStateInterface $form_state) { + $menu_link = $this->getRequest()->query->get('menu_link'); + $form['#cache']['contexts'][] = 'url.query_args:menu_link'; + return $this->buildOverviewFormWithDepth($form, $form_state, 1, $menu_link); + } + + /** + * Build a shallow version of the overview form. + * + * @param array $form + * The form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state object. + * @param int $depth + * The depth. + * @param string $menu_link + * (Optional) The starting menu link id. + * + * @return array + * The form. + */ + protected function buildOverviewFormWithDepth(array &$form, FormStateInterface $form_state, $depth = 1, $menu_link = NULL) { + // Ensure that menu_overview_form_submit() knows the parents of this form + // section. + if (!$form_state->has('menu_overview_form_parents')) { + $form_state->set('menu_overview_form_parents', []); + } + + // Use Menu UI adminforms. + $form['#attached']['library'][] = 'menu_ui/drupal.menu_ui.adminforms'; + + // Add a link to go back to the full menu. + if ($menu_link) { + $form['back_link'] = $this->entity->toLink($this->t('Back to @label top level', [ + '@label' => $this->entity->label(), + ]), 'edit-form')->toRenderable(); + } + + $form['links'] = [ + '#type' => 'table', + '#theme' => 'table__menu_overview', + '#header' => [ + $this->t('Menu link'), + $this->t('Edit children'), + [ + 'data' => $this->t('Enabled'), + 'class' => ['checkbox'], + ], + $this->t('Weight'), + [ + 'data' => $this->t('Operations'), + 'colspan' => 3, + ], + ], + '#attributes' => [ + 'id' => 'menu-overview', + ], + '#tabledrag' => [ + [ + 'action' => 'match', + 'relationship' => 'parent', + 'group' => 'menu-parent', + 'subgroup' => 'menu-parent', + 'source' => 'menu-id', + 'hidden' => TRUE, + 'limit' => $this->menuTree->maxDepth() - 1, + ], + [ + 'action' => 'order', + 'relationship' => 'sibling', + 'group' => 'menu-weight', + ], + ], + ]; + + // No Links available (Empty menu) + $form['links']['#empty'] = $this->t('There are no menu links yet. <a href=":url">Add link</a>.', [ + ':url' => $this->entity->toUrl('add-link-form', [ + 'query' => ['destination' => $this->entity->toUrl('edit-form')->toString()], + ])->toString(), + ]); + + // Get the menu tree if it's not in our property. + if (empty($this->tree)) { + $this->tree = $this->getTree($depth, $menu_link); + } + + // Determine the delta; the number of weights to be made available. + $count = function (array $tree) { + $sum = function ($carry, MenuLinkTreeElement $item) { + return $carry + $item->count(); + }; + return array_reduce($tree, $sum); + }; + + // Tree maximum or 50. + $delta = max($count($this->tree), 50); + + $links = $this->buildOverviewTreeForm($this->tree, $delta); + + $this->processLinks($form, $links, $menu_link); + + return $form; + } + + /** + * Format the links appropriately so draggable views will work. + * + * @param array $form + * The form array. + * @param array $links + * An array of links. + * @param string $menu_link + * A menu link plugin id. + */ + public function processLinks(array &$form, array &$links, $menu_link) { + foreach (Element::children($links) as $id) { + if (isset($links[$id]['#item'])) { + $element = $links[$id]; + + $form['links'][$id]['#item'] = $element['#item']; + + // TableDrag: Mark the table row as draggable. + $form['links'][$id]['#attributes'] = $element['#attributes']; + $form['links'][$id]['#attributes']['class'][] = 'draggable'; + + // TableDrag: Sort the table row according to its existing/configured + // weight. + $form['links'][$id]['#weight'] = $element['#item']->link->getWeight(); + + // Add special classes to be used for tabledrag.js. + $element['parent']['#attributes']['class'] = ['menu-parent']; + $element['weight']['#attributes']['class'] = ['menu-weight']; + $element['id']['#attributes']['class'] = ['menu-id']; + + $form['links'][$id]['title'] = [ + [ + '#theme' => 'indentation', + '#size' => $element['#item']->depth - 1, + ], + $element['title'], + ]; + + $form['links'][$id]['root'][] = []; + + if ($form['links'][$id]['#item']->hasChildren) { + if (is_null($menu_link) || (isset($menu_link) && $menu_link != $element['#item']->link->getPluginId())) { + $uri = $this->entity->toUrl('edit-form', [ + 'query' => ['menu_link' => $element['#item']->link->getPluginId()], + ]); + + $form['links'][$id]['root'][] = [ + '#type' => 'link', + '#title' => $this->t('Edit child items'), + '#url' => $uri, + ]; + } + } + + $form['links'][$id]['enabled'] = $element['enabled']; + $form['links'][$id]['enabled']['#wrapper_attributes']['class'] = ['checkbox', 'menu-enabled']; + + $form['links'][$id]['weight'] = $element['weight']; + + // Operations (dropbutton) column. + $form['links'][$id]['operations'] = $element['operations']; + + $form['links'][$id]['id'] = $element['id']; + $form['links'][$id]['parent'] = $element['parent']; + } + } + } + + /** + * Gets the menu tree. + * + * @param int $depth + * The depth. + * @param string $root + * An optional root menu link plugin id. + * + * @return \Drupal\Core\Menu\MenuLinkTreeElement[] + * An array of menu link tree elements. + */ + protected function getTree($depth, $root = NULL) { + $tree_params = new MenuTreeParameters(); + $tree_params->setMaxDepth($depth); + + if ($root) { + $tree_params->setRoot($root); + } + + $tree = $this->menuTree->load($this->entity->id(), $tree_params); + + // We indicate that a menu administrator is running the menu access check. + $this->getRequest()->attributes->set('_menu_admin', TRUE); + $manipulators = [ + ['callable' => 'menu.default_tree_manipulators:checkAccess'], + ['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'], + ]; + $tree = $this->menuTree->transform($tree, $manipulators); + $this->getRequest()->attributes->set('_menu_admin', FALSE); + + return $tree; + } + + /** + * {@inheritdoc} + */ + public function save(array $form, FormStateInterface $form_state) { + parent::save($form, $form_state); + $form_state->setRedirectUrl(Url::fromUserInput($this->getRedirectDestination()->get())); + } + +} diff --git a/web/modules/bigmenu/tests/src/Functional/BigMenuUiTest.php b/web/modules/bigmenu/tests/src/Functional/BigMenuUiTest.php new file mode 100644 index 0000000000000000000000000000000000000000..5af89c801494d142393978d5a3651caf4f46ed80 --- /dev/null +++ b/web/modules/bigmenu/tests/src/Functional/BigMenuUiTest.php @@ -0,0 +1,98 @@ +<?php + +namespace Drupal\Tests\bigmenu\Functional; + +use Drupal\menu_link_content\Entity\MenuLinkContent; +use Drupal\system\Entity\Menu; +use Drupal\Tests\BrowserTestBase; + +/** + * Tests the Big Menu interface. + * + * @group bigmenu + */ +class BigMenuUiTest extends BrowserTestBase { + + /** + * A user with administration rights. + * + * @var \Drupal\user\UserInterface + */ + protected $adminUser; + + /** + * A test menu. + * + * @var \Drupal\system\Entity\Menu + */ + protected $menu; + + /** + * {@inheritdoc} + */ + protected static $modules = [ + 'bigmenu', + 'menu_link_content', + 'menu_ui', + ]; + + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + + $this->adminUser = $this->drupalCreateUser(['access administration pages', 'administer menu']); + $this->menu = Menu::load('main'); + } + + /** + * Tests the Big Menu interface. + */ + public function testBigMenuUi() { + $this->drupalLogin($this->adminUser); + + // Add new menu items in a hierarchy. + $item1 = MenuLinkContent::create([ + 'title' => 'Item 1', + 'link' => [['uri' => 'internal:/']], + 'menu_name' => 'main', + ]); + $item1->save(); + $item1_1 = MenuLinkContent::create([ + 'title' => 'Item 1 - 1', + 'link' => [['uri' => 'internal:/']], + 'menu_name' => 'main', + 'parent' => 'menu_link_content:' . $item1->uuid(), + ]); + $item1_1->save(); + $item1_1_1 = MenuLinkContent::create([ + 'title' => 'Item 1 - 1 - 1', + 'link' => [['uri' => 'internal:/']], + 'menu_name' => 'main', + 'parent' => 'menu_link_content:' . $item1_1->uuid(), + ]); + $item1_1_1->save(); + + $this->drupalGet('admin/structure/menu/manage/main'); + $this->assertSession()->linkExistsExact('Item 1'); + $this->assertSession()->linkNotExistsExact('Item 1 - 1'); + $this->assertSession()->linkNotExistsExact('Item 1 - 1 - 1'); + + $href = $this->menu->toUrl('edit-form', [ + 'query' => ['menu_link' => 'menu_link_content:' . $item1->uuid()], + ])->toString(); + $this->assertSession()->linkByHrefExists($href); + + $this->clickLink('Edit child items'); + $this->assertSession()->linkExistsExact('Item 1'); + $this->assertSession()->linkExistsExact('Item 1 - 1'); + $this->assertSession()->linkNotExistsExact('Item 1 - 1 - 1'); + + $this->clickLink('Edit child items'); + $this->assertSession()->linkNotExistsExact('Item 1'); + $this->assertSession()->linkExistsExact('Item 1 - 1'); + $this->assertSession()->linkExistsExact('Item 1 - 1 - 1'); + } + +} diff --git a/web/modules/ckeditor_indentblock/LICENSE.txt b/web/modules/ckeditor_indentblock/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..d159169d1050894d3ea3b98e1c965c4058208fe1 --- /dev/null +++ b/web/modules/ckeditor_indentblock/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/web/modules/ckeditor_indentblock/README.txt b/web/modules/ckeditor_indentblock/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..4b217e710e236cdced838cd11ca324882df1a5b8 --- /dev/null +++ b/web/modules/ckeditor_indentblock/README.txt @@ -0,0 +1,37 @@ +CKEditor IndentBlock +==================== + +Description +=========== +The CKEditor IndentBlock plugin adds the functionality of indenting text paragraphs using CKEditor. The plugin doesn't +come with its own buttons, but uses the same buttons as the built-in CKEditor Indent plugin. The configuration allows +to enable/disable the CKEditor IndentBlock plugin individually for each text format. + +Installation +============ +1. Download the plugin from http://ckeditor.com/addon/indentblock +2. Place the plugin in the root libraries folder (/libraries). +3. Enable the CKEditor IndentBlock module in the Drupal admin menu 'Extend >> List'. + +Adding paragraph indentation to a text format +============================================= +1. Go to admin menu 'Configuration >> Text formats and editors'. +2. Click on the Configuration button of the text format (i.e. Simple HTML). +3. If not already in the toolbar, drag the buttons with the title tags indent and outdent into it, which enables the + built-in CKEditor Indent plugin for lists. +4. Open the vertical tab 'Indent Block' and make sure the plugin is enabled. +5. Make sure, the tag <p class> is added to the field 'Allowed HTM tags', otherwise the 'Indent' and 'Outdent' buttons + will not become active for paragraphs despite the IndentBlock plugin being enabled. + +Dependencies +============ +This module requires the core CKEditor module and the contributed Libraries module. + +Uninstallation +============== +1. Uninstall the module from the admin menu 'Extend >> Uninstall'. + + +MAINTAINERS +============ +Christian Meilinger - https://www.drupal.org/u/meichr diff --git a/web/modules/ckeditor_indentblock/ckeditor_indentblock.info.yml b/web/modules/ckeditor_indentblock/ckeditor_indentblock.info.yml new file mode 100644 index 0000000000000000000000000000000000000000..42dce146eb4488d13df2793a80a48082eaac7210 --- /dev/null +++ b/web/modules/ckeditor_indentblock/ckeditor_indentblock.info.yml @@ -0,0 +1,16 @@ +name: CKEditor IndentBlock +type: module +description: 'Provides integration of the CKEditor IndentBlock plugin with the Drupal 8 CKEditor.' +package: CKEditor +# core: 8.x + +dependencies: + - libraries:libraries + - drupal:ckeditor + - drupal:system (>= 8.1) + +# Information added by Drupal.org packaging script on 2017-10-06 +version: '8.x-1.0-beta1' +core: '8.x' +project: 'ckeditor_indentblock' +datestamp: 1507249145 diff --git a/web/modules/ckeditor_indentblock/ckeditor_indentblock.install b/web/modules/ckeditor_indentblock/ckeditor_indentblock.install new file mode 100644 index 0000000000000000000000000000000000000000..1ed569d08f646bcb61f873999d71e4bd92906f85 --- /dev/null +++ b/web/modules/ckeditor_indentblock/ckeditor_indentblock.install @@ -0,0 +1,39 @@ +<?php + +/** + * @file + * CKEditor IndentBlock install file. + */ + +/** + * Implements hook_requirements(). + */ +function ckeditor_indentblock_requirements($phase) { + $requirements = []; + + if ($phase == 'install' || $phase == 'runtime') { + if (!function_exists('libraries_get_path')) { + module_load_include('module', 'libraries'); + } + $plugin_path = libraries_get_path('indentblock') . '/plugin.js'; + $plugin_detected = file_exists($plugin_path); + + if ($plugin_detected) { + $requirements['indentblock'] = [ + 'title' => t('CKEditor IndentBlock'), + 'value' => t('Plugin detected'), + 'severity' => REQUIREMENT_OK, + ]; + } + else { + $requirements['indentblock'] = [ + 'title' => t('CKEditor IndentBlock'), + 'value' => t('Plugin not detected'), + 'severity' => REQUIREMENT_ERROR, + 'description' => t('You will need to install the "Indent Block" CKEditor plugin under the libraries path before enabling this module. <a href=":plugin_url">Get the plugin from CKEditor.com</a>.', [':plugin_url' => 'http://ckeditor.com/addon/indentblock']), + ]; + } + } + + return $requirements; +} diff --git a/web/modules/ckeditor_indentblock/ckeditor_indentblock.libraries.yml b/web/modules/ckeditor_indentblock/ckeditor_indentblock.libraries.yml new file mode 100644 index 0000000000000000000000000000000000000000..0b6ad09469a247b39c3d5252c2bbd2c4186b31ed --- /dev/null +++ b/web/modules/ckeditor_indentblock/ckeditor_indentblock.libraries.yml @@ -0,0 +1,5 @@ +indentblock: + version: 1.x + css: + theme: + css/plugins/indentblock/ckeditor.indentblock.css: {} diff --git a/web/modules/ckeditor_indentblock/ckeditor_indentblock.module b/web/modules/ckeditor_indentblock/ckeditor_indentblock.module new file mode 100644 index 0000000000000000000000000000000000000000..7bf6e5d3eb6ab8689f436b6838031cd8bff07e9f --- /dev/null +++ b/web/modules/ckeditor_indentblock/ckeditor_indentblock.module @@ -0,0 +1,16 @@ +<?php + +/** + * @file + * Adds a hook for attaching a css file with indentation information to each page. + */ + + +/** + * Implements hook_page_attachments(). + * + * Adds the ckeditor.indentblock.css with indentation css to each page. + */ +function ckeditor_indentblock_page_attachments(array &$page) { + $page['#attached']['library'][] = 'ckeditor_indentblock/indentblock'; +} diff --git a/web/modules/ckeditor_indentblock/composer.json b/web/modules/ckeditor_indentblock/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..68981e719686f555a9ffab1f70ca67f4322dd7db --- /dev/null +++ b/web/modules/ckeditor_indentblock/composer.json @@ -0,0 +1,25 @@ +{ + "name": "drupal/ckeditor_indentblock", + "type": "drupal-module", + "description": "Provides integration of the CKEditor IndentBlock plugin with the Drupal 8 CKEditor.", + "keywords": ["Drupal"], + "license": "GPL-2.0+", + "homepage": "https://www.drupal.org/project/ckeditor_indentblock", + "minimum-stability": "dev", + "authors": [ + { + "name": "Christian Meilinger (meichr)", + "homepage": "https://www.drupal.org/u/meichr", + "role": "Maintainer" + } + ], + "support": { + "issues": "https://www.drupal.org/project/issues/ckeditor_indentblock", + "source": "http://cgit.drupalcode.org/ckeditor_indentblock/" + }, + "require": { + "drupal/core": "^8.1.0", + "drupal/ckeditor": "*", + "drupal/libraries": "*" + } +} diff --git a/web/modules/ckeditor_indentblock/css/plugins/indentblock/ckeditor.indentblock.css b/web/modules/ckeditor_indentblock/css/plugins/indentblock/ckeditor.indentblock.css new file mode 100644 index 0000000000000000000000000000000000000000..482ca9f379f0936356fc3e507e6cc10c7762fc49 --- /dev/null +++ b/web/modules/ckeditor_indentblock/css/plugins/indentblock/ckeditor.indentblock.css @@ -0,0 +1,36 @@ +@charset "UTF-8"; +/** + * Elements + * - set css for block indent. + ============================================================================ */ +p.Indent1 { + margin-left: 2em; +} +p.Indent2 { + margin-left: 4em; +} +p.Indent3 { + margin-left: 6em; +} +p.Indent4 { + margin-left: 8em; +} +p.Indent5 { + margin-left: 10em; +} +p.Indent6 { + margin-left: 12em; +} +p.Indent7 { + margin-left: 14em; +} +p.Indent8 { + margin-left: 16em; +} +p.Indent9 { + margin-left: 18em; +} +p.Indent10 { + margin-left: 20em; +} + diff --git a/web/modules/ckeditor_indentblock/src/Plugin/CKEditorPlugin/IndentBlock.php b/web/modules/ckeditor_indentblock/src/Plugin/CKEditorPlugin/IndentBlock.php new file mode 100644 index 0000000000000000000000000000000000000000..8bccb0b7920b7267795787cdb74cb1be18d57dae --- /dev/null +++ b/web/modules/ckeditor_indentblock/src/Plugin/CKEditorPlugin/IndentBlock.php @@ -0,0 +1,123 @@ +<?php + +namespace Drupal\ckeditor_indentblock\Plugin\CKEditorPlugin; + +use Drupal\ckeditor\CKEditorPluginBase; +use Drupal\ckeditor\CKEditorPluginConfigurableInterface; +use Drupal\ckeditor\CKEditorPluginContextualInterface; +use Drupal\ckeditor\CKEditorPluginCssInterface; +use Drupal\Core\Form\FormStateInterface; +use Drupal\editor\Entity\Editor; + +/** + * Defines the "Indent Block" plugin. + * + * NOTE: The plugin ID ('id' key) corresponds to the CKEditor plugin name. + * It is the first argument of the CKEDITOR.plugins.add() function in the + * plugin.js file. + * + * @CKEditorPlugin( + * id = "indentblock", + * label = @Translation("Indent Block") + * ) + */ +class IndentBlock extends CKEditorPluginBase implements CKEditorPluginContextualInterface, CKEditorPluginConfigurableInterface, CKEditorPluginCssInterface { + + /** + * {@inheritdoc} + */ + public function getCssFiles(Editor $editor) { + return [ + drupal_get_path('module', 'ckeditor_indentblock') . '/css/plugins/indentblock/ckeditor.indentblock.css', + ]; + } + + /** + * {@inheritdoc} + * + * NOTE: The keys of the returned array corresponds to the CKEditor button + * names. They are the first argument of the editor.ui.addButton() or + * editor.ui.addRichCombo() functions in the plugin.js file. + */ + public function getButtons() { + return []; + } + + /** + * {@inheritdoc} + */ + public function getFile() { + return libraries_get_path('indentblock') . '/plugin.js'; + } + + /** + * {@inheritdoc} + */ + public function isEnabled(Editor $editor) { + // Enable this plugin, if it is configured as being enabled and at least one + // of the buttons, Indent or Outdent, is enabled. + $settings = $editor->getSettings(); + if (isset($settings['plugins']['indentblock']) && $settings['plugins']['indentblock']['enable']) { + foreach ($settings['toolbar']['rows'] as $row) { + foreach ($row as $group) { + foreach ($group['items'] as $button) { + if ($button === 'Indent' || $button === 'Outdent') { + return TRUE; + } + } + } + } + } + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function isInternal() { + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function getDependencies(Editor $editor) { + // The Indent plugin is internal for Drupal 8 CKEditor and thus can't be + // defined as a dependency. + return []; + } + + /** + * {@inheritdoc} + */ + public function getLibraries(Editor $editor) { + return []; + } + + /** + * {@inheritdoc} + */ + public function getConfig(Editor $editor) { + return [ + 'indentClasses' => ['Indent1', 'Indent2', 'Indent3', 'Indent4', 'Indent5', 'Indent6', 'Indent7', 'Indent8', 'Indent9', 'Indent10'], + 'indentOffset' => 2, + 'indentUnit' => 'em', + ]; + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, FormStateInterface $form_state, Editor $editor) { + $settings = $editor->getSettings(); + + $form['enable'] = array( + '#type' => 'checkbox', + '#title' => $this->t('Enable indentation on paragraphs'), + '#default_value' => !empty($settings['plugins']['indentblock']) && $settings['plugins']['indentblock']['enable'] === 1 ? $settings['plugins']['indentblock']['enable'] : 0, + ); + + return $form; + } + +} diff --git a/web/modules/libraries/CHANGELOG.txt b/web/modules/libraries/CHANGELOG.txt new file mode 100644 index 0000000000000000000000000000000000000000..6d1daa8351781ca92e24b008ece84bc2a2e27539 --- /dev/null +++ b/web/modules/libraries/CHANGELOG.txt @@ -0,0 +1,163 @@ + +Libraries 8.x-3.x, xxxx-xx-xx +----------------------------- +#2833756 by 20th: Check that public://library-definitions directory does not exist +#2825940 by tstoeckler, rjacobs: Remove LibrariesServiceProvider +#2816115 by rjacobs: Remove ExtensionHandler service +#2770983 by rjacobs: Update services.yml to reference new registry URL of http://cgit.drupalcode.org/libraries_registry/plain/registry/8 +#2770983 by rjacobs: Update shipped config to reference new registry URL of http://cgit.drupalcode.org/libraries_registry/plain/registry/8 +#2815189 by rjacobs, tstoeckler: Library dependency checking can fail for install profile +#2770983 by tstoeckler, rjacobs: Fetch definitions remotely and store them locally. +#2770983 by tstoeckler: Make Libraries API work on Windows. +#2770983 by tstoeckler: Allow libraries to be symlinked to their right place. +#2756265 by rajeshwari10: Replace deprecated usage of SafeMarkup::checkPlain(). +#2742333 by jrockowitz: Fix missing @group in LinePatternDetectorTest +by tstoeckler: Document plugin alter hooks +by tstoeckler: Fix coding standards +by tstoeckler: Split out library dependencies to a separate interface +by tstoeckler: Implement version detection for libraries +by tstoeckler: Add deprecation notices to all legacy functions and hooks +#2090623 by tstoeckler: Introduce the notion of library types +#2090623 by tstoeckler: Provide support for remote asset libraries +#2090623 by tstoeckler: Introduce a stream wrapper for asset libraries +#2090623 by tstoeckler: Introduce the concept of locators +#2606420 by googletorp, tstoeckler: Fix profile library detection +#2090623 by tstoeckler: Add an external library registry +#2572401 by rjacobs, yas: Fix missing @group annotation in PhpFileLibraryTest +#2090623 by tstoeckler: Add a test for PHP file loading +#2090623 by tstoeckler: Provide a modern, flexible library API +#2525898 by rjacobs, jonhattan: Fix obsoleted cache bin declaration. +#2471501 by LKS90: Replace all occurrences of String with the SafeMarkup equivalent. +by tstoeckler: Fix drush libraries-list and drush cache-clear libraries. +#2427801 by Anushka-mp, tstoeckler: Replace module_invoke() call. +by tstoeckler: Fix tests. +#2390301 by rjacobs: Fix DrupalUnitTestBase no longer exists so tests can't load. +#2332157 by tstoeckler: Add a composer.json. +#2287529 by drupalshrek, tstoeckler: Update installation link in README.txt. +by tstoeckler: Fix tests. +#2309203 by JayeshSolanki: Replace removed functions with module handler service. +#2290767 by yukare: Replace removed cache() function. +#2183087 by tstoeckler, rjacobs: Update for removed core functions. +by tstoeckler: Fix tests. +by tstoeckler: Provide required 'type' key in test library info file. +#2090351 by tstoeckler: Remove obsolete hook_flush_caches(). +#2090425 by tstoeckler: Adapt for renamed ControllerInterface. +#2090323 by tstoeckler: Remove obsolete libraries_parse_dependency(). +#2090379 by tstoeckler: Change 'pattern' to 'path' in routing YAML file. +#2058371 by gordon: Re-port to Drupal 8 (.info.yml, controllers, cache service, ...). +#1779714 by tstoeckler, klonos: Wrong filepath in README.txt and fix JS testing. +#1167496 by tstoeckler: Remove leftover libraries.test file. +#1167496 by tstoeckler, benshell: Port to Drupal 8. + + +Libraries 7.x-3.x, xxxx-xx-xx +----------------------------- +#1938638 by tstoeckler: Remove unneeded check. + + +Libraries 7.x-2.x, xxxx-xx-xx +----------------------------- +#2352251 by netw3rker: Fix incorrect hook name in libraries.api.php. +#2352237 by netw3rker, tstoeckler: Allow clearing the libraries cache from Drush. +#2193969 by tstoeckler: Avoid warnings for stale library caches. +#2287529 by drupalshrek, tstoeckler: Update installation link in README.txt. + +Libraries 7.x-2.2, 2014-02-09 +----------------------------- +#2046919 by tstoeckler: Clarify 'version' docs. +#1946110 by munroe_richard: Allow uppercase letters as library machine names. +#1953260 by tstoeckler: Improve documentation of libraries_get_version(). +#1855918 by tstoeckler: Make integration file loading backwards-compatible. +#1876124 by tstoeckler: Fix integration files for themes. +#1876124 by tstoeckler: Add tests for theme-provided library information. +#1876124 by tstoeckler: Prepare for adding a test theme. +#1876124 by tstoeckler | whastings, fubhy: Fix hook_libraries_info() for themes. +#2015721 by tstoeckler, CaptainHook: Protect against files overriding local variables. +#2046919 by tstoeckler: Improve documentation around 'version callback'. +#1844272 by tstoeckler, jweowu: Fix typos in libraries.api.php. +#1938638 by tstoeckler: Prevent weird PHP notice on update. +#1329388 by RobLoach, tstoeckler: Clear static caches in libraries_flush_caches(). +#1855918 by rbayliss: Load integration files after library files. +#1938638 by Pol: Fix typo in libraries.api.php. + +Libraries 7.x-2.1, 2013-03-09 +----------------------------- +#1937446 by Pol, tstoeckler: Add a 'pre-dependencies-load' callback group. +#1775668 by tstoeckler: Fix bogus assertion message in assertLibraryFiles(). +#1773640 by tstoeckler: Use drupal_get_path() to find the profile directory. +#1565426 by tstoeckler: Invoke hook_libraries_info() in enabled themes. + +Libraries 7.x-2.0, 2012-07-29 +----------------------------- +#1606018 by chemical: Tests fail if the module is downloaded from Drupal.org. +#1386250 by tstoeckler: Clarify module and library installation in README.txt. +#1578618 by iamEAP: Fixed Fatal cache flush failure on major version upgrades. +#1449346 by tstoeckler, sun: Clean-up libraries.test + +Libraries 7.x-2.0-alpha2, 2011-12-15 +------------------------------------ +#1299076 by tstoeckler: Improve testing of JS, CSS, and PHP files. +#1347214 by rfay: Improve update function 7200. +#1323530 by tstoeckler: Document libraries_get_version() pattern matching. +#1325524 by sun, Rob Loach, tstoeckler: Statically cache libraries_detect(). +#1321372 by Rob Loach: Provide a 'post-load' callback group. +#1205854 by tstoeckler, sun: Test library caching. + +Libraries 7.x-2.0-alpha1, 2011-10-01 +------------------------------------ +#1268342 by tstoeckler: Clean up drush libraries-list command. +#962214 by tstoeckler, sun: Add support for library dependencies. +#1224838 by sun, mjpa: Fix library path not being prepended to JS/CSS files. +#1023258 by tstoeckler: Make 'files' consistently keyed by filename. +#958162 by sun, tstoeckler: Add pre-detect callback group. +#958162 by sun, tstoeckler: Make tests debuggable and provide libraries_info_defaults(). +#961476 by tstoeckler: Changed libraries_get_path() to return FALSE by default. +#958162 by tstoeckler, sun, good_man: Allow to apply callbacks to libraries. +#1125904 by tstoeckler, boombatower: Fix drush libraries-list. +#1050076 by tstoeckler: Re-utilize libraries_detect() and remove libraries_detect_library(). +#466090 by tstoeckler: Add update function. +#466090 by tstoeckler: Allow cache to be flushed. +#466090 by tstoeckler, sun: Cache library information. +#1064008 by tstoeckler, bfroehle: Fix outdated API examples in libraries.api.php. +#1028744 by tstoeckler: Code clean-up. +#1023322 by tstoeckler, sun: Fixed libraries shouldn't be loaded multiple times. +#1024080 by hswong3i, tstoeckler: Fixed installation profile retrieval. +#995988 by good_man: Wrong default install profile. +#975498 by Gábor Hojtsy: Update JS/CSS-loading to new drupal_add_js/css() API. +#958162 by tsteoeckler, sun: Consistent variable naming. +#924130 by aaronbauman: Fixed libraries_get_path() should use drupal_static(). +#958162 by tstoeckler, sun: Code clean-up, tests revamp, more robust loading. +#919632 by tstoeckler, sun: Allow library information to be stored in info files. +by sun: Fixed testbot breaks upon directory name/info file name mismatch. +#864376 by tstoeckler, sun: Code-cleanup, allow hard-coded 'version'. +#939174 by sun, tstoeckler: Rename example.info to libraries_example.info. +by sun: Fixed testbot breaks upon .info file without .module file. +#542940 by tstoeckler, sun: Add libraries-list command. +#919632 by tstoeckler: Add example library info file for testing purposes. +#719896 by tstoeckler, sun: Documentation clean-up and tests improvement. +#542940 by sun: Added initial Drush integration file. +#719896 by tstoeckler, sun: Improved library detection and library loading. +#855050 by Gábor Hojtsy: Avoid call-time pass by reference in libraries_detect(). +#719896 by tstoeckler, sun: Added starting point for hook_libraries_info(). + + +Libraries 7.x-1.x, xxxx-xx-xx +----------------------------- + +Libraries 7.x-1.0, 2010-01-27 +----------------------------- +#743522 by sun: Ported to D7. + + +Libraries 6.x-1.x, xxxx-xx-xx +----------------------------- + +Libraries 6.x-1.0, 2010-01-27 +----------------------------- +#1028744 by tstoeckler: Code clean-up. +#496732 by tstoeckler, robphillips: Allow placing libraries in root directory. + +Libraries 6.x-1.0-alpha1, 2009-12-30 +------------------------------------ +#480440 by markus_petrux: Fixed base_path() not applied to default library path. +#320562 by sun: Added basic functions. diff --git a/web/modules/libraries/LICENSE.txt b/web/modules/libraries/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..d159169d1050894d3ea3b98e1c965c4058208fe1 --- /dev/null +++ b/web/modules/libraries/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/web/modules/libraries/README.txt b/web/modules/libraries/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..9ff0ece53b15239fbe50aac61ebf9db03f9819d0 --- /dev/null +++ b/web/modules/libraries/README.txt @@ -0,0 +1,40 @@ + +# Libraries API + +## General information + +Libraries API provides external library handling for Drupal modules. + +Relevant links: +- [Project page](https://www.drupal.org/project/libraries) +- [Issue tracker](https://www.drupal.org/project/issues/libraries) +- [Repository viewer](http://cgit.drupalcode.org/libraries) + +### Installation + +Install like any module, see the +[Drupal.org handbook](https://www.drupal.org/documentation/install/modules-themes/modules-8) +for further information. Note that installing external libraries is separate from +installing this module and should happen in the top-level `libraries` directory. +See the online [module documentation](https://www.drupal.org/node/1440066) for more +information. + + +### Maintainers + +Current maintainers: +- Daniel F. Kudwien ([sun](https://www.drupal.org/u/sun)) +- Tobias Stöckler ([tstoeckler](http://www.drupal.org/u/tstoeckler)) +- Ryan Jacobs ([rjacobs](http://www.drupal.org/u/rjacobs)) +- Pol Dellaiera ([pol](http://www.drupal.org/u/pol)) + +### Sponsorship + +This project has been sponsored by: +- **UNLEASHED MIND** + Specialized in consulting and planning of Drupal powered sites, UNLEASHED + MIND offers installation, development, theming, customization, and hosting + to get you started. Visit + [http://www.unleashedmind.com](http://www.unleashedmind.com) for more + information. + diff --git a/web/modules/libraries/composer.json b/web/modules/libraries/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..4af11e12e838280670e4095241afeef46d2d91ca --- /dev/null +++ b/web/modules/libraries/composer.json @@ -0,0 +1,14 @@ +{ + "name": "drupal/libraries", + "description": "Allows version-dependent and shared usage of external libraries in Drupal.", + "type": "drupal-module", + "homepage": "http://drupal.org/project/libraries", + "authors": [ + ], + "support": { + "issues": "http://drupal.org/project/issues/libraries", + "irc": "irc://irc.freenode.org/drupal-contribute", + "source": "http://cgit.drupalcode.org/libraries" + }, + "license": "GPL-2.0+" +} diff --git a/web/modules/libraries/config/install/libraries.settings.yml b/web/modules/libraries/config/install/libraries.settings.yml new file mode 100644 index 0000000000000000000000000000000000000000..0981884b1ad3ee32726149b1d6b4f4e950a16354 --- /dev/null +++ b/web/modules/libraries/config/install/libraries.settings.yml @@ -0,0 +1,10 @@ +definition: + local: + # @todo Implement a stream wrapper that finds library definitions in e.g. + # sites/all/libraries. + path: 'public://library-definitions' + remote: + enable: TRUE + urls: + - 'http://cgit.drupalcode.org/libraries_registry/plain/registry/8' +global_locators: [] \ No newline at end of file diff --git a/web/modules/libraries/config/schema/libraries.schema.yml b/web/modules/libraries/config/schema/libraries.schema.yml new file mode 100644 index 0000000000000000000000000000000000000000..72c4c52f2e80873654a90422578c90d5ae68948f --- /dev/null +++ b/web/modules/libraries/config/schema/libraries.schema.yml @@ -0,0 +1,53 @@ +# Configuration schema for the Libraries API module. + +# Base configuration schema +libraries.settings: + type: config_object + label: 'Libraries API settings' + mapping: + definition: + type: mapping + label: 'Library definition settings' + mapping: + local: + type: mapping + label: 'Local' + mapping: + path: + type: path + label: 'Local path' + remote: + type: mapping + title: 'Remote' + mapping: + enable: + type: boolean + label: 'Enable remote fetching of library definitions' + urls: + type: sequence + label: 'A list of remote library registry URLs' + sequence: + type: uri + label: 'The URL of a remote library registry' + global_locators: + type: sequence + title: 'Global library locators' + sequence: + type: mapping + title: 'Global locator plugins' + mapping: + id: + type: string + title: 'The locator plugin id' + configuration: + type: libraries.locator.[%parent.id] + title: 'The plugin configuration' + +# Dynamic locator plugin schema +libraries.locator.uri: + type: mapping + label: 'URI locator configuration' + mapping: + uri: + type: uri + label: 'The locator URI' \ No newline at end of file diff --git a/web/modules/libraries/libraries.api.php b/web/modules/libraries/libraries.api.php new file mode 100644 index 0000000000000000000000000000000000000000..859e6023eb4529bcdb3731fefc7a44971744aabe --- /dev/null +++ b/web/modules/libraries/libraries.api.php @@ -0,0 +1,609 @@ +<?php + +/** + * @file + * Documents API functions for Libraries module. + */ + +/** + * @defgroup libraries External libraries + * @{ + * External libraries are not shipped as part of contributed modules for + * licensing and maintenance reasons. The Libraries API module aims to solve the + * problem of integrating with and loading external libraries as part of the + * Drupal request-response process in a generic way. + * + * @section sec_definitions Library definitions + * In order to be useful to other modules Libraries API needs a list of known + * libraries and metadata about each of the libraries. Because multiple modules + * themes may integrate with the same external library a key objective of + * Libraries API is to keep this information separate from any one module or + * theme. + * + * Definitions are accessed via a discovery that is responsible for checking + * whether a given definition exists and fetching it, if it is. See + * LibraryRegistryInterface and StreamDefinitionDiscovery for more information. + * + * @subsection sub_definitions_machine_name + * A central part of a library's metadata is the library's machine name or ID. + * For maximum interoperability it must consist of only lowercase ASCII letters, + * numbers, and underscores. As the machine name is the single identifier of a + * library and is independent of any given module or theme name it must be + * unique among all libraries known to Libraries API. + * + * @subsection sub_definitions_history Historical background + * In Drupal 7 library information could already be provided by + * module-independent info files, but this was not widely used, because there + * was no way to distribute these info files properly. The information was + * predominantly provided by a hook that modules could implement, which caused + * race conditions between modules providing information for the same library. + * Thus, in Drupal 8 there is no longer a hook making it necessary to properly + * solve the problem of centrally maintaining and distributing library info + * files. This has yet to be done. See https://www.drupal.org/node/773508 for + * more information. + * + * @section sec_types Library types + * Libraries are classed objects that implement LibraryInterface. This generic + * interface only dictates that a library is aware of its ID. Any further + * functionality depends on the type of library, each type of library comes with + * a dedicated interface. See LibraryInterface for more information. + * + * @subsection sub_types_version Version detection + * A central aspect of Libraries API is version detection. Modules or themes may + * only work with a specific version of an external library, so Libraries API + * needs a way to detect the version of a library by inspecting the library + * files. + * + * As the mechanism for doing this is generally not specific to any one + * library, it is handled by version detector plugins. A 'line_pattern' plugin + * that scans a file line by line whether for whether a pattern containing the + * version is matched. It can be used if the version is always specified in a + * particular place in a particular file, for example a changelog. See + * VersionDetectorInterface and LinePatternDetector for more information. + * + * @subsection sub_types_dependency Dependency handling + * Many libraries depend on other libraries to function. Thus, most library + * classes should implement DependentLibraryInterface to allow libraries to + * declare their dependencies as part of their metadata. In case of API changes + * in the dependencies libraries need to be able to declare dependencies on + * specific versions or version ranges of other libraries. This has yet to be + * implemented. + * + * Furthermore, Libraries API must also maintain a list of libraries that are + * required by the installed installation profile, modules, and themes + * (extensions). With this information installation of extensions with library + * dependencies can be prevented until the libraries are properly installed. + * This is currently not implemented. In the future this will be used to + * automatically retrieve library definitions of required libraries, and + * possibly to automatically download the libraries themselves. + * + * To declare library dependencies extensions can place a 'library_dependencies' + * key in their info file with a list of library machine names as the value. + * For example: + * @code + * name: My module + * type: module + * core: 8.x + * library_dependencies: + * - flexslider + * - jquery_mobile + * @endcode + * + * @subsection sub_types_asset Asset libraries + * With Drupal 8 relying on Composer for autoloading and dependency resolution + * of PHP libraries, asset libraries are the primary use-case for Libraries API. + * Because asset libraries cannot be loaded ad-hoc, but must be attached to a + * renderable element, Libraries API registers external asset libraries that are + * required by the installed extensions with the core asset library system. See + * AssetLibraryInterface for more information. + * + * @subsection sub_types_php_file + * For feature parity with the Drupal 7 version of this module, a PHP file + * library type is provided, that can load a list of PHP files on demand. + * Generally, it is encouraged to use Composer instead of this library type and + * avoid Libraries API altogether for PHP libraries. See PhpFileLibraryInterface + * for more information. + * + * This library type might be removed in a future version of Libraries API. + * + * @see \Drupal\libraries\ExternalLibrary\Definition\DefinitionDiscoveryInterface + * @see \Drupal\libraries\ExternalLibrary\Definition\StreamDefinitionDiscovery + * @see \Drupal\libraries\ExternalLibrary\LibraryInterface + * @see \Drupal\libraries\ExternalLibrary\Version\VersionDetectorInterface + * @see \Drupal\libraries\Plugin\libraries\VersionDetector\LinePatternDetector + * @see \Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface + * @see \Drupal\libraries\ExternalLibrary\Dependency\DependentLibraryInterface + * @see \Drupal\libraries\ExternalLibrary\Asset\AssetLibraryInterface + * @see \Drupal\libraries\ExternalLibrary\PhpFile\PhpFileLibraryInterface + * + * @} + */ + +/** + * Alter library type information. + * + * @param array $library_types + * An array of library types keyed by ID. Each library type is an array with + * the following keys: + * - id: The ID of the library type. + * - class: The class to use for this library type. + * - provider: The provider of this library type. + */ +function hook_libraries_library_type_info_alter(array &$library_types) { + // Use a different class for the asset library type. Note that this class is + // distinct from the class actually for asset libraries themselves. + $library_types['asset']['class'] = 'Drupal\mymodule\ExternalLibrary\BetterAssetLibraryType'; +} + +/** + * Alter library locator information. + * + * @param array $locators + * An array of library locators keyed by ID. Each locator is an array with the + * following keys: + * - id: The ID of the library locator. + * - class: The class to use for this library locator. + * - provider: The provider of this library locator. + */ +function hook_libraries_locator_info_alter(array &$locators) { + // Use a different class for the stream locator. + $locators['stream']['class'] = 'Drupal\mymodule\ExternalLibrary\BetterStreamLocator'; +} + +/** + * Alter library version detector information. + * + * @param array $version_detectors + * An array of library version detectors keyed by ID. Each detector is an + * array with the following keys: + * - id: The ID of the library version detector. + * - class: The class to use for this library version detector. + * - provider: The provider of this library version detector. + */ +function hook_libraries_version_detector_info_alter(array &$version_detectors) { + // Use a different class for the line pattern locator. + $version_detectors['line_pattern']['class'] = 'Drupal\mymodule\ExternalLibrary\BetterLinePatternDetector'; +} + +/** + * Return information about external libraries. + * + * @return + * An associative array whose keys are internal names of libraries and whose + * values are describing each library. Each key is the directory name below + * the 'libraries' directory, in which the library may be found. Each value is + * an associative array containing: + * - name: The official, human-readable name of the library. + * - vendor url: The URL of the homepage of the library. + * - download url: The URL of a web page on which the library can be obtained. + * - path: (optional) A relative path from the directory of the library to the + * actual library. Only required if the extracted download package contains + * the actual library files in a sub-directory. + * - library path: (optional) The absolute path to the library directory. This + * should not be declared normally, as it is automatically detected, to + * allow for multiple possible library locations. A valid use-case is an + * external library, in which case the full URL to the library should be + * specified here. + * - version: (optional) The version of the library. This should not be + * declared normally, as it is automatically detected (see 'version + * callback' below) to allow for version changes of libraries without code + * changes of implementing modules and to support different versions of a + * library simultaneously (though only one version can be installed per + * site). A valid use-case is an external library whose version cannot be + * determined programmatically. + * - version callback: (optional) The name of a function that detects and + * returns the full version string of the library. The first argument is + * always $library, an array containing all library information as described + * here. There are two ways to declare the version callback's additional + * arguments, either as a single $options parameter or as multiple + * parameters, which correspond to the two ways to specify the argument + * values (see 'version arguments'). Defaults to libraries_get_version(). + * - version arguments: A list of arguments to pass to the version callback. + * Version arguments can be declared either as an associative array whose + * keys are the argument names or as an indexed array without specifying + * keys. If declared as an associative array, the arguments get passed to + * the version callback as a single $options parameter whose keys are the + * argument names (i.e. $options is identical to the specified array). If + * declared as an indexed array, the array values get passed to the version + * callback as separate arguments in the order they were declared. The + * default version callback libraries_get_version() expects a single, + * associative array with named keys: + * - file: The filename to parse for the version, relative to the library + * path. For example: 'docs/changelog.txt'. + * - pattern: A string containing a regular expression (PCRE) to match the + * library version. For example: '@version\s+([0-9a-zA-Z\.-]+)@'. Note + * that the returned version is not the match of the entire pattern (i.e. + * '@version 1.2.3' in the above example) but the match of the first + * sub-pattern (i.e. '1.2.3' in the above example). + * - lines: (optional) The maximum number of lines to search the pattern in. + * Defaults to 20. + * - cols: (optional) The maximum number of characters per line to take into + * account. Defaults to 200. In case of minified or compressed files, this + * prevents reading the entire file into memory. + * - files: An associative array of library files to load. Supported keys are: + * - js: A list of JavaScript files to load, using the same syntax as Drupal + * core's hook_library(). + * - css: A list of CSS files to load, using the same syntax as Drupal + * core's hook_library(). + * - php: A list of PHP files to load. + * - dependencies: An array of libraries this library depends on. Similar to + * declaring module dependencies, the dependency declaration may contain + * information on the supported version. Examples of supported declarations: + * @code + * $libraries['dependencies'] = array( + * // Load the 'example' library, regardless of the version available: + * 'example', + * // Only load the 'example' library, if version 1.2 is available: + * 'example (1.2)', + * // Only load a version later than 1.3-beta2 of the 'example' library: + * 'example (>1.3-beta2)' + * // Only load a version equal to or later than 1.3-beta3: + * 'example (>=1.3-beta3)', + * // Only load a version earlier than 1.5: + * 'example (<1.5)', + * // Only load a version equal to or earlier than 1.4: + * 'example (<=1.4)', + * // Combinations of the above are allowed as well: + * 'example (>=1.3-beta2, <1.5)', + * ); + * @endcode + * - variants: (optional) An associative array of available library variants. + * For example, the top-level 'files' property may refer to a default + * variant that is compressed. If the library also ships with a minified and + * uncompressed/source variant, those can be defined here. Each key should + * describe the variant type, e.g. 'minified' or 'source'. Each value is an + * associative array of top-level properties that are entirely overridden by + * the variant, most often just 'files'. Additionally, each variant can + * contain following properties: + * - variant callback: (optional) The name of a function that detects the + * variant and returns TRUE or FALSE, depending on whether the variant is + * available or not. The first argument is always $library, an array + * containing all library information as described here. The second + * argument is always a string containing the variant name. There are two + * ways to declare the variant callback's additional arguments, either as a + * single $options parameter or as multiple parameters, which correspond + * to the two ways to specify the argument values (see 'variant + * arguments'). If omitted, the variant is expected to always be + * available. + * - variant arguments: A list of arguments to pass to the variant callback. + * Variant arguments can be declared either as an associative array whose + * keys are the argument names or as an indexed array without specifying + * keys. If declared as an associative array, the arguments get passed to + * the variant callback as a single $options parameter whose keys are the + * argument names (i.e. $options is identical to the specified array). If + * declared as an indexed array, the array values get passed to the + * variant callback as separate arguments in the order they were declared. + * Variants can be version-specific (see 'versions'). + * - versions: (optional) An associative array of supported library versions. + * Naturally, libraries evolve over time and so do their APIs. In case a + * library changes between versions, different 'files' may need to be + * loaded, different 'variants' may become available, or Drupal modules need + * to load different integration files adapted to the new version. Each key + * is a version *string* (PHP does not support floats as keys). Each value + * is an associative array of top-level properties that are entirely + * overridden by the version. + * - integration files: (optional) An associative array whose keys are module + * names and whose values are sets of files to load for the module, using + * the same notion as the top-level 'files' property. Each specified file + * should contain the path to the file relative to the module it belongs to. + * - callbacks: An associative array whose keys are callback groups and whose + * values are arrays of callbacks to apply to the library in that group. + * Each callback receives the following arguments: + * - $library: An array of library information belonging to the top-level + * library, a specific version, a specific variant or a specific variant + * of a specific version. Because library information such as the 'files' + * property (see above) can be declared in all these different locations + * of the library array, but a callback may have to act on all these + * different parts of the library, it is called recursively for each + * library with a certain part of the libraries array passed as $library + * each time. + * - $version: If the $library array belongs to a certain version (see + * above), a string containing the version. This argument may be empty, so + * NULL should be specified as the default value. + * - $variant: If the $library array belongs to a certain variant (see + * above), a string containing the variant name. This argument may be + * empty, so NULL should be specified as the default value. + * Valid callback groups are: + * - info: Callbacks registered in this group are applied after the library + * information has been retrieved via hook_libraries_info() or info files. + * - pre-detect: Callbacks registered in this group are applied after the + * library path has been determined and before the version callback is + * invoked. At this point the following additional information is available: + * - $library['library path']: The path on the file system to the library. + * - post-detect: Callbacks registered in this group are applied after the + * library has been successfully detected. At this point the library + * contains the version-specific information, if specified, and following + * additional information is available: + * - $library['installed']: A boolean indicating whether the library is + * installed or not. + * - $library['version']: If it could be detected, a string containing the + * version of the library. + * - $library['variants'][$variant]['installed']: For each specified + * variant, a boolean indicating whether the variant is installed or + * not. + * Note that in this group the 'versions' property is no longer available. + * - pre-load: Callbacks registered in this group are applied directly + * before this library is loaded. At this point the library contains + * variant-specific information, if specified. Note that in this group the + * 'variants' property is no longer available. + * - post-load: Callbacks registered in this group are applied directly + * after this library is loaded. At this point, the library contains a + * 'loaded' key, which contains the number of files that were loaded. + * Additional top-level properties can be registered as needed. + * + * @see hook_library() + * + * @deprecated Will be removed before a stable Drupal 8 release. + */ +function hook_libraries_info() { + // The following is a full explanation of all properties. See below for more + // concrete example implementations. + + // This array key lets Libraries API search for 'sites/all/libraries/example' + // directory, which should contain the entire, original extracted library. + $libraries['example'] = array( + // Only used in administrative UI of Libraries API. + 'name' => 'Example library', + 'vendor url' => 'http://example.com', + 'download url' => 'http://example.com/download', + // Optional: If, after extraction, the actual library files are contained in + // 'sites/all/libraries/example/lib', specify the relative path here. + 'path' => 'lib', + // Optional: Define a custom version detection callback, if required. + 'version callback' => 'mymodule_get_version', + // Specify arguments for the version callback. By default, + // libraries_get_version() takes a named argument array: + 'version arguments' => array( + 'file' => 'docs/CHANGELOG.txt', + 'pattern' => '@version\s+([0-9a-zA-Z\.-]+)@', + 'lines' => 5, + 'cols' => 20, + ), + // Default list of files of the library to load. Important: Only specify + // third-party files belonging to the library here, not integration files of + // your module. + 'files' => array( + // 'js' and 'css' follow the syntax of hook_library(), but file paths are + // relative to the library path. + 'js' => array( + 'exlib.js', + 'gadgets/foo.js', + ), + 'css' => array( + 'lib_style.css', + 'skin/example.css', + ), + // For PHP libraries, specify include files here, still relative to the + // library path. + 'php' => array( + 'exlib.php', + 'exlib.inc', + ), + ), + // Optional: Specify alternative variants of the library, if available. + 'variants' => array( + // All properties defined for 'minified' override top-level properties. + 'minified' => array( + 'files' => array( + 'js' => array( + 'exlib.min.js', + 'gadgets/foo.min.js', + ), + 'css' => array( + 'lib_style.css', + 'skin/example.css', + ), + ), + 'variant callback' => 'mymodule_check_variant', + 'variant arguments' => array( + 'variant' => 'minified', + ), + ), + ), + // Optional, but usually required: Override top-level properties for later + // versions of the library. The properties of the minimum version that is + // matched override the top-level properties. Note: + // - When registering 'versions', it usually does not make sense to register + // 'files', 'variants', and 'integration files' on the top-level, as most + // of those likely need to be different per version and there are no + // defaults. + // - The array keys have to be strings, as PHP does not support floats for + // array keys. + 'versions' => array( + '2' => array( + 'files' => array( + 'js' => array('exlib.js'), + 'css' => array('exlib_style.css'), + ), + ), + '3.0' => array( + 'files' => array( + 'js' => array('exlib.js'), + 'css' => array('lib_style.css'), + ), + ), + '3.2' => array( + 'files' => array( + 'js' => array( + 'exlib.js', + 'gadgets/foo.js', + ), + 'css' => array( + 'lib_style.css', + 'skin/example.css', + ), + ), + ), + ), + // Optional: Register files to auto-load for your module. All files must be + // keyed by module, and follow the syntax of the 'files' property. + 'integration files' => array( + 'mymodule' => array( + 'js' => array('ex_lib.inc'), + ), + ), + // Optionally register callbacks to apply to the library during different + // stages of its lifetime ('callback groups'). + 'callbacks' => array( + // Used to alter the info associated with the library. + 'info' => array( + 'mymodule_example_libraries_info_callback', + ), + // Called before detecting the given library. + 'pre-detect' => array( + 'mymodule_example_libraries_predetect_callback', + ), + // Called after detecting the library. + 'post-detect' => array( + 'mymodule_example_libraries_postdetect_callback', + ), + // Called before the library is loaded. + 'pre-load' => array( + 'mymodule_example_libraries_preload_callback', + ), + // Called after the library is loaded. + 'post-load' => array( + 'mymodule_example_libraries_postload_callback', + ), + ), + ); + + // A very simple library. No changing APIs (hence, no versions), no variants. + // Expected to be extracted into 'sites/all/libraries/simple'. + $libraries['simple'] = array( + 'name' => 'Simple library', + 'vendor url' => 'http://example.com/simple', + 'download url' => 'http://example.com/simple', + 'version arguments' => array( + 'file' => 'readme.txt', + // Best practice: Document the actual version strings for later reference. + // 1.x: Version 1.0 + 'pattern' => '/Version (\d+)/', + 'lines' => 5, + ), + 'files' => array( + 'js' => array('simple.js'), + ), + ); + + // A library that (naturally) evolves over time with API changes. + $libraries['tinymce'] = array( + 'name' => 'TinyMCE', + 'vendor url' => 'http://tinymce.moxiecode.com', + 'download url' => 'http://tinymce.moxiecode.com/download.php', + 'path' => 'jscripts/tiny_mce', + // The regular expression catches two parts (the major and the minor + // version), which libraries_get_version() doesn't allow. + 'version callback' => 'tinymce_get_version', + 'version arguments' => array( + // It can be easier to parse the first characters of a minified file + // instead of doing a multi-line pattern matching in a source file. See + // 'lines' and 'cols' below. + 'file' => 'jscripts/tiny_mce/tiny_mce.js', + // Best practice: Document the actual version strings for later reference. + // 2.x: this.majorVersion="2";this.minorVersion="1.3" + // 3.x: majorVersion:'3',minorVersion:'2.0.1' + 'pattern' => '@majorVersion[=:]["\'](\d).+?minorVersion[=:]["\']([\d\.]+)@', + 'lines' => 1, + 'cols' => 100, + ), + 'versions' => array( + '2.1' => array( + 'files' => array( + 'js' => array('tiny_mce.js'), + ), + 'variants' => array( + 'source' => array( + 'files' => array( + 'js' => array('tiny_mce_src.js'), + ), + ), + ), + 'integration files' => array( + 'wysiwyg' => array( + 'js' => array('editors/js/tinymce-2.js'), + 'css' => array('editors/js/tinymce-2.css'), + ), + ), + ), + // Definition used if 3.1 or above is detected. + '3.1' => array( + // Does not support JS aggregation. + 'files' => array( + 'js' => array( + 'tiny_mce.js' => array('preprocess' => FALSE), + ), + ), + 'variants' => array( + // New variant leveraging jQuery. Not stable yet; therefore not the + // default variant. + 'jquery' => array( + 'files' => array( + 'js' => array( + 'tiny_mce_jquery.js' => array('preprocess' => FALSE), + ), + ), + ), + 'source' => array( + 'files' => array( + 'js' => array( + 'tiny_mce_src.js' => array('preprocess' => FALSE), + ), + ), + ), + ), + 'integration files' => array( + 'wysiwyg' => array( + 'js' => array('editors/js/tinymce-3.js'), + 'css' => array('editors/js/tinymce-3.css'), + ), + ), + ), + ), + ); + return $libraries; +} + +/** + * Alter the library information before detection and caching takes place. + * + * The library definitions are passed by reference. A common use-case is adding + * a module's integration files to the library array, so that the files are + * loaded whenever the library is. As noted above, it is important to declare + * integration files inside of an array, whose key is the module name. + * + * @see hook_libraries_info() + * + * @deprecated Will be removed before a stable Drupal 8 release. + */ +function hook_libraries_info_alter(&$libraries) { + $files = array( + 'php' => array('example_module.php_spellchecker.inc'), + ); + $libraries['php_spellchecker']['integration files']['example_module'] = $files; +} + +/** + * Specify paths to look for library info files. + * + * Libraries API looks in the following directories for library info files by + * default: + * - libraries + * - profiles/$profile/libraries + * - sites/all/libraries + * - sites/$site/libraries + * This hook allows you to specify additional locations to look for library info + * files. This should only be used for modules that declare many libraries. + * Modules that only implement a few libraries should implement + * hook_libraries_info(). + * + * @return + * An array of paths. + * + * @deprecated Will be removed before a stable Drupal 8 release. + */ +function hook_libraries_info_file_paths() { + // Taken from the Libraries test module, which needs to specify the path to + // the test library. + return array(drupal_get_path('module', 'libraries_test') . '/example'); +} diff --git a/web/modules/libraries/libraries.drush.inc b/web/modules/libraries/libraries.drush.inc new file mode 100644 index 0000000000000000000000000000000000000000..22b7d62da06a06d4e827d689126d42c61a5e25f1 --- /dev/null +++ b/web/modules/libraries/libraries.drush.inc @@ -0,0 +1,169 @@ +<?php + +/** + * @file + * Drush integration for Libraries API. + */ + +use Drupal\Component\Utility\Unicode; + +/** + * Implements hook_drush_command(). + */ +function libraries_drush_command() { + $items['libraries-list'] = array( + 'callback' => 'libraries_drush_list', + 'description' => dt('Lists registered library information.'), + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL, + ); + /**$items['libraries-download'] = array( + 'callback' => 'libraries_drush_download', + 'description' => dt('Downloads a registered library into the libraries directory for the active site.'), + 'arguments' => array( + 'name' => dt('The internal name of the registered library.'), + ), + );*/ + return $items; +} + +/** + * Implements hook_drush_help(). + */ +function libraries_drush_help($section) { + switch ($section) { + case 'drush:libraries-list': + return dt('Lists registered library information.'); + + case 'drush:libraries-download': + return dt('Downloads a registered library into the libraries directory for the active site. + +See libraries-list for a list of registered libraries.'); + } +} + +/** + * Implements hook_drush_cache_clear(). + * + * @see drush_cache_clear_types() + */ +function libraries_drush_cache_clear(&$types) { + $types['libraries'] = 'libraries_drush_invalidate_cache'; +} + +/** + * Clears the library cache. + */ +function libraries_drush_invalidate_cache() { + \Drupal::cache('libraries')->deleteAll(); +} + +/** + * Lists registered library information. + */ +function libraries_drush_list() { + $libraries = array(); + foreach (libraries_info() as $name => $info) { + $libraries[$name] = libraries_detect($name); + } + ksort($libraries); + + if (empty($libraries)) { + drush_print('There are no registered libraries.'); + } + + else { + $rows = array(); + // drush_print_table() automatically treats the first row as the header, if + // $header is TRUE. + $rows[] = array(dt('Name'), dt('Status'), dt('Version'), dt('Variants'), dt('Dependencies')); + foreach ($libraries as $name => $library) { + $status = ($library['installed'] ? dt('OK') : Unicode::ucfirst($library['error'])); + $version = (($library['installed'] && !empty($library['version'])) ? $library['version'] : '-'); + + // Only list installed variants. + $variants = array(); + foreach ($library['variants'] as $variant_name => $variant) { + if ($variant['installed']) { + $variants[] = $variant_name; + } + } + $variants = (empty($variants) ? '-' : implode(', ', $variants)); + + $dependencies = (!empty($library['dependencies']) ? implode(', ', $library['dependencies']) : '-'); + + $rows[] = array($name, $status, $version, $variants, $dependencies); + } + // Make the possible values for the 'Status' column and the 'Version' header + // wrap nicely. + $widths = array(0, 12, 7, 0, 0); + drush_print_table($rows, TRUE, $widths); + } +} + +/** + * Downloads a library. + * + * @param $name + * The internal name of the library to download. + */ +function libraries_drush_download($name) { + return; + + // @todo Looks wonky? + if (!drush_shell_exec('type unzip')) { + return drush_set_error(dt('Missing dependency: unzip. Install it before using this command.')); + } + + // @todo Simply use current drush site. + $args = func_get_args(); + if ($args[0]) { + $path = $args[0]; + } + else { + $path = 'sites/all/libraries'; + } + + // Create the path if it does not exist. + if (!is_dir($path)) { + drush_op('mkdir', $path); + drush_log(dt('Directory @path was created', array('@path' => $path)), 'notice'); + } + + // Set the directory to the download location. + $olddir = getcwd(); + chdir($path); + + $filename = basename(COLORBOX_DOWNLOAD_URI); + $dirname = basename(COLORBOX_DOWNLOAD_URI, '.zip'); + + // Remove any existing Colorbox plugin directory + if (is_dir($dirname)) { + drush_log(dt('A existing Colorbox plugin was overwritten at @path', array('@path' => $path)), 'notice'); + } + // Remove any existing Colorbox plugin zip archive + if (is_file($filename)) { + drush_op('unlink', $filename); + } + + // Download the zip archive + if (!drush_shell_exec('wget '. COLORBOX_DOWNLOAD_URI)) { + drush_shell_exec('curl -O '. COLORBOX_DOWNLOAD_URI); + } + + if (is_file($filename)) { + // Decompress the zip archive + drush_shell_exec('unzip -qq -o '. $filename); + // Remove the zip archive + drush_op('unlink', $filename); + } + + // Set working directory back to the previous working directory. + chdir($olddir); + + if (is_dir($path .'/'. $dirname)) { + drush_log(dt('Colorbox plugin has been downloaded to @path', array('@path' => $path)), 'success'); + } + else { + drush_log(dt('Drush was unable to download the Colorbox plugin to @path', array('@path' => $path)), 'error'); + } +} diff --git a/web/modules/libraries/libraries.info.yml b/web/modules/libraries/libraries.info.yml new file mode 100644 index 0000000000000000000000000000000000000000..02136222c77638e32a78040d0087cc6853c16d30 --- /dev/null +++ b/web/modules/libraries/libraries.info.yml @@ -0,0 +1,10 @@ +name: Libraries +type: module +description: Allows version-dependent and shared usage of external libraries. +# core: 8.x + +# Information added by Drupal.org packaging script on 2018-01-27 +version: '8.x-3.0-alpha1' +core: '8.x' +project: 'libraries' +datestamp: 1517046488 diff --git a/web/modules/libraries/libraries.install b/web/modules/libraries/libraries.install new file mode 100644 index 0000000000000000000000000000000000000000..a2edea4928282ff1d92f9450917fe12d6f584911 --- /dev/null +++ b/web/modules/libraries/libraries.install @@ -0,0 +1,28 @@ +<?php + +/** + * @file + * Containsinstall, uninstall and update functions for Libraries API. + */ + +use Drupal\libraries\ExternalLibrary\Definition\FileDefinitionDiscovery; + +/** + * Implements hook_install(). + */ +function libraries_install() { + if (!is_dir('public://library-definitions')) { + /** @var \Drupal\Core\File\FileSystemInterface $file_system */ + $file_system = \Drupal::service('file_system'); + $file_system->mkdir('public://library-definitions'); + } +} + +/** + * Implements hook_uninstall(). + */ +function libraries_uninstall() { + if (is_dir('public://library-definitions')) { + file_unmanaged_delete_recursive('public://library-definitions'); + } +} diff --git a/web/modules/libraries/libraries.module b/web/modules/libraries/libraries.module new file mode 100644 index 0000000000000000000000000000000000000000..3006c345ceb1716bce03c98045f108430fb6a39f --- /dev/null +++ b/web/modules/libraries/libraries.module @@ -0,0 +1,868 @@ +<?php + +/** + * @file + * External library handling for Drupal modules. + */ + +use Drupal\Core\DrupalKernel; +use Drupal\Core\Extension\ModuleHandler; +use Drupal\libraries\ExternalLibrary\Asset\AttachableAssetLibraryRegistrationInterface; +use Drupal\libraries\ExternalLibrary\Utility\LibraryAccessorInterface; +use Drupal\libraries\ExternalLibrary\Utility\LibraryIdAccessorInterface; +use Symfony\Component\Yaml\Parser; + +/** + * Implements hook_library_info_build(). + * + * Register external asset libraries with Drupal core's library APIs. + */ +function libraries_library_info_build() { + /** @var \Drupal\libraries\ExternalLibrary\LibraryManagerInterface $library_manager */ + $library_manager = \Drupal::service('libraries.manager'); + $attachable_libraries = []; + $libraries_with_errors = []; + foreach ($library_manager->getRequiredLibraryIds() as $external_library_id) { + try { + $external_library = $library_manager->getLibrary($external_library_id); + $library_type = $external_library->getType(); + if ($library_type instanceof AttachableAssetLibraryRegistrationInterface) { + $attachable_libraries += $library_type->getAttachableAssetLibraries($external_library, $library_manager); + } + } + catch (\Exception $e) { + // Library-specific exceptions should not be allowed to kill the rest of + // the build process, but should be logged. + if ($e instanceof LibraryIdAccessorInterface || $e instanceof LibraryAccessorInterface) { + $libraries_with_errors[] = $external_library_id; + watchdog_exception('libraries', $e); + } + else { + // Re-throw exceptions that are not library-specific. + throw $e; + } + } + } + // If we had library specific errors also log an informative message to + // tell admins that detection will not be run again without a cache clear. + if ($libraries_with_errors) { + \Drupal::logger('libraries')->error('The following external libraries could not successfully be registered with Drupal core: @libs. See earlier log entries for more details. Once these issues are addressed please be sure to clear your Drupal library cache to ensure external library detection is run again.', ['@libs' => implode(',', $libraries_with_errors)]); + } + return $attachable_libraries; +} + +/** + * Gets the path of a library. + * + * @param $name + * The machine name of a library to return the path for. + * @param $base_path + * Whether to prefix the resulting path with base_path(). + * + * @return + * The path to the specified library or FALSE if the library wasn't found. + * + * @ingroup libraries + * + * @deprecated Will be removed before a stable Drupal 8 release. Please use the + * new library load and managment concepts described at: + * https://www.drupal.org/node/2170763 + */ +function libraries_get_path($name, $base_path = FALSE) { + $libraries = &drupal_static(__FUNCTION__); + + if (!isset($libraries)) { + $libraries = libraries_get_libraries(); + } + + $path = ($base_path ? base_path() : ''); + if (!isset($libraries[$name])) { + return FALSE; + } + else { + $path .= $libraries[$name]; + } + + return $path; +} + +/** + * Returns an array of library directories. + * + * Returns an array of library directories from the all-sites directory + * (i.e. sites/all/libraries/), the profiles directory, and site-specific + * directory (i.e. sites/somesite/libraries/). The returned array will be keyed + * by the library name. Site-specific libraries are prioritized over libraries + * in the default directories. That is, if a library with the same name appears + * in both the site-wide directory and site-specific directory, only the + * site-specific version will be listed. + * + * @return + * A list of library directories. + * + * @ingroup libraries + * + * @deprecated Will be removed before a stable Drupal 8 release. Please use the + * new library load and managment concepts described at: + * https://www.drupal.org/node/2170763 + */ +function libraries_get_libraries() { + $searchdir = array(); + $config = DrupalKernel::findSitePath(\Drupal::request()); + + // @todo core/libraries + + // Similar to 'modules' and 'themes' directories inside an installation + // profile, installation profiles may want to place libraries into a + // 'libraries' directory. + if ($profile = drupal_get_profile()) { + $profile_path = drupal_get_path('profile', $profile); + $searchdir[] = "$profile_path/libraries"; + }; + + // Search sites/all/libraries for backwards-compatibility. + $searchdir[] = 'sites/all/libraries'; + + // Always search the root 'libraries' directory. + $searchdir[] = 'libraries'; + + // Also search sites/<domain>/*. + $searchdir[] = "$config/libraries"; + + // Retrieve list of directories. + $directories = array(); + $nomask = array('CVS'); + foreach ($searchdir as $dir) { + if (is_dir($dir) && $handle = opendir($dir)) { + while (FALSE !== ($file = readdir($handle))) { + if (!in_array($file, $nomask) && $file[0] != '.') { + if (is_dir("$dir/$file")) { + $directories[$file] = "$dir/$file"; + } + } + } + closedir($handle); + } + } + + return $directories; +} + +/** + * Looks for library info files. + * + * This function scans the following directories for info files: + * - libraries + * - profiles/$profilename/libraries + * - sites/all/libraries + * - sites/$sitename/libraries + * - any directories specified via hook_libraries_info_file_paths() + * + * @return + * An array of info files, keyed by library name. The values are the paths of + * the files. + * + * @deprecated Will be removed before a stable Drupal 8 release. Please use the + * new library load and managment concepts described at: + * https://www.drupal.org/node/2170763 + */ +function libraries_scan_info_files() { + $profile = drupal_get_path('profile', drupal_get_profile()); + $config = DrupalKernel::findSitePath(\Drupal::request()); + + // Build a list of directories. + $directories = \Drupal::moduleHandler()->invokeAll('libraries_info_file_paths', $args = array()); + $directories[] = "$profile/libraries"; + $directories[] = 'sites/all/libraries'; + $directories[] = 'libraries'; + $directories[] = "$config/libraries"; + + // Scan for info files. + $files = array(); + foreach ($directories as $dir) { + if (file_exists($dir)) { + $files = array_merge($files, file_scan_directory($dir, '@^[a-z0-9._-]+\.libraries\.info\.yml$@', array( + 'key' => 'name', + 'recurse' => FALSE, + ))); + } + } + + foreach ($files as $filename => $file) { + $files[basename($filename, '.libraries.info')] = $file; + unset($files[$filename]); + } + + return $files; +} + +/** + * Invokes library callbacks. + * + * @param $group + * A string containing the group of callbacks that is to be applied. Should be + * either 'info', 'pre-detect', 'post-detect', or 'load'. + * @param $library + * An array of library information, passed by reference. + * + * @deprecated Will be removed before a stable Drupal 8 release. Please use the + * new library load and managment concepts described at: + * https://www.drupal.org/node/2170763 + */ +function libraries_invoke($group, &$library) { + foreach ($library['callbacks'][$group] as $callback) { + libraries_traverse_library($library, $callback); + } +} + +/** + * Helper function to apply a callback to all parts of a library. + * + * Because library declarations can include variants and versions, and those + * version declarations can in turn include variants, modifying e.g. the 'files' + * property everywhere it is declared can be quite cumbersome, in which case + * this helper function is useful. + * + * @param $library + * An array of library information, passed by reference. + * @param $callback + * A string containing the callback to apply to all parts of a library. + * + * @deprecated Will be removed before a stable Drupal 8 release. Please use the + * new library load and managment concepts described at: + * https://www.drupal.org/node/2170763 + */ +function libraries_traverse_library(&$library, $callback) { + // Always apply the callback to the top-level library. + $callback($library, NULL, NULL); + + // Apply the callback to versions. + if (isset($library['versions'])) { + foreach ($library['versions'] as $version_string => &$version) { + $callback($version, $version_string, NULL); + // Versions can include variants as well. + if (isset($version['variants'])) { + foreach ($version['variants'] as $version_variant_name => &$version_variant) { + $callback($version_variant, $version_string, $version_variant_name); + } + } + } + } + + // Apply the callback to variants. + if (isset($library['variants'])) { + foreach ($library['variants'] as $variant_name => &$variant) { + $callback($variant, NULL, $variant_name); + } + } +} + +/** + * Library info callback to make all 'files' properties consistent. + * + * This turns libraries' file information declared as e.g. + * @code + * $library['files']['js'] = array('example_1.js', 'example_2.js'); + * @endcode + * into + * @code + * $library['files']['js'] = array( + * 'example_1.js' => array(), + * 'example_2.js' => array(), + * ); + * @endcode + * It does the same for the 'integration files' property. + * + * @param $library + * An associative array of library information or a part of it, passed by + * reference. + * @param $version + * If the library information belongs to a specific version, the version + * string. NULL otherwise. + * @param $variant + * If the library information belongs to a specific variant, the variant name. + * NULL otherwise. + * + * @see libraries_info() + * @see libraries_invoke() + * + * @deprecated Will be removed before a stable Drupal 8 release. Please use the + * new library load and managment concepts described at: + * https://www.drupal.org/node/2170763 + */ +function libraries_prepare_files(&$library, $version = NULL, $variant = NULL) { + // Both the 'files' property and the 'integration files' property contain file + // declarations, and we want to make both consistent. + $file_types = array(); + if (isset($library['files'])) { + $file_types[] = &$library['files']; + } + if (isset($library['integration files'])) { + // Integration files are additionally keyed by module. + foreach ($library['integration files'] as &$integration_files) { + $file_types[] = &$integration_files; + } + } + foreach ($file_types as &$files) { + // Go through all supported types of files. + foreach (array('js', 'css', 'php') as $type) { + if (isset($files[$type])) { + foreach ($files[$type] as $key => $value) { + // Unset numeric keys and turn the respective values into keys. + if (is_numeric($key)) { + $files[$type][$value] = array(); + unset($files[$type][$key]); + } + } + } + } + } +} + +/** + * Library post-detect callback to process and detect dependencies. + * + * It checks whether each of the dependencies of a library are installed and + * available in a compatible version. + * + * @param $library + * An associative array of library information or a part of it, passed by + * reference. + * @param $version + * If the library information belongs to a specific version, the version + * string. NULL otherwise. + * @param $variant + * If the library information belongs to a specific variant, the variant name. + * NULL otherwise. + * + * @see libraries_info() + * @see libraries_invoke() + * + * @deprecated Will be removed before a stable Drupal 8 release. Please use the + * new library load and managment concepts described at: + * https://www.drupal.org/node/2170763 + */ +function libraries_detect_dependencies(&$library, $version = NULL, $variant = NULL) { + if (isset($library['dependencies'])) { + foreach ($library['dependencies'] as &$dependency_string) { + $dependency_info = ModuleHandler::parseDependency($dependency_string); + $dependency = libraries_detect($dependency_info['name']); + if (!$dependency['installed']) { + $library['installed'] = FALSE; + $library['error'] = 'missing dependency'; + $library['error message'] = t('The %dependency library, which the %library library depends on, is not installed.', array( + '%dependency' => $dependency['name'], + '%library' => $library['name'], + )); + } + elseif (drupal_check_incompatibility($dependency_info, $dependency['version'])) { + $library['installed'] = FALSE; + $library['error'] = 'incompatible dependency'; + $library['error message'] = t('The version %dependency_version of the %dependency library is not compatible with the %library library.', array( + '%dependency_version' => $dependency['version'], + '%dependency' => $dependency['name'], + '%library' => $library['name'], + )); + } + + // Remove the version string from the dependency, so libraries_load() can + // load the libraries directly. + $dependency_string = $dependency_info['name']; + } + } +} + +/** + * Returns information about registered libraries. + * + * The returned information is unprocessed; i.e., as registered by modules. + * + * @param $name + * (optional) The machine name of a library to return registered information + * for. If omitted, information about all registered libraries is returned. + * + * @return array|false + * An associative array containing registered information for all libraries, + * the registered information for the library specified by $name, or FALSE if + * the library $name is not registered. + * + * @see hook_libraries_info() + * + * @todo Re-introduce support for include file plugin system - either by copying + * Wysiwyg's code, or directly switching to CTools. + * + * @deprecated Will be removed before a stable Drupal 8 release. Please use the + * new library load and managment concepts described at: + * https://www.drupal.org/node/2170763 + */ +function &libraries_info($name = NULL) { + // This static cache is re-used by libraries_detect() to save memory. + $libraries = &drupal_static(__FUNCTION__); + + if (!isset($libraries)) { + $libraries = array(); + // Gather information from hook_libraries_info(). + $module_handler = \Drupal::moduleHandler(); + foreach ($module_handler->getImplementations('libraries_info') as $module) { + foreach ($module_handler->invoke($module, 'libraries_info') as $machine_name => $properties) { + $properties['module'] = $module; + $libraries[$machine_name] = $properties; + } + } + // Gather information from hook_libraries_info() in enabled themes. + // @see drupal_alter() + global $theme, $base_theme_info; + if (isset($theme)) { + $theme_keys = array(); + foreach ($base_theme_info as $base) { + $theme_keys[] = $base->name; + } + $theme_keys[] = $theme; + foreach ($theme_keys as $theme_key) { + $function = $theme_key . '_' . 'libraries_info'; + if (function_exists($function)) { + foreach ($function() as $machine_name => $properties) { + $properties['theme'] = $theme_key; + $libraries[$machine_name] = $properties; + } + } + } + } + + // Gather information from .info files. + // .info files override module definitions. + // In order to stop Drupal's extension and the Drupal.org packaging + // system from finding library info files we use the 'libraries.info.yml' + // file extension. Therefore, having a 'type' key, like info files of + // modules, themes, and profiles have, is superfluous. + // \Drupal\Core\Extension\InfoParser, however, enforces the existence of a + // 'type' key in info files. We therefore use Symfony's YAML parser + // directly. + // @todo Consider creating a dedicating InfoParser for library info files + // similar to \Drupal\Core\Extension\InfoParser + $parser = new Parser(); + foreach (libraries_scan_info_files() as $machine_name => $file) { + $properties = $parser->parse(file_get_contents($file->uri)); + $properties['info file'] = $file->uri; + $libraries[$machine_name] = $properties; + } + + // Provide defaults. + foreach ($libraries as $machine_name => &$properties) { + libraries_info_defaults($properties, $machine_name); + } + + // Allow modules to alter the registered libraries. + $module_handler->alter('libraries_info', $libraries); + + // Invoke callbacks in the 'info' group. + foreach ($libraries as &$properties) { + libraries_invoke('info', $properties); + } + } + + if (isset($name)) { + if (!empty($libraries[$name])) { + return $libraries[$name]; + } + else { + $false = FALSE; + return $false; + } + } + return $libraries; +} + +/** + * Applies default properties to a library definition. + * + * @library + * An array of library information, passed by reference. + * @name + * The machine name of the passed-in library. + * + * @deprecated Will be removed before a stable Drupal 8 release. Please use the + * new library load and managment concepts described at: + * https://www.drupal.org/node/2170763 + */ +function libraries_info_defaults(&$library, $name) { + $library += array( + 'machine name' => $name, + 'name' => $name, + 'vendor url' => '', + 'download url' => '', + 'path' => '', + 'library path' => NULL, + 'version callback' => 'libraries_get_version', + 'version arguments' => array(), + 'files' => array(), + 'dependencies' => array(), + 'variants' => array(), + 'versions' => array(), + 'integration files' => array(), + 'callbacks' => array(), + ); + $library['callbacks'] += array( + 'info' => array(), + 'pre-detect' => array(), + 'post-detect' => array(), + 'pre-load' => array(), + 'post-load' => array(), + ); + + // Add our own callbacks before any others. + array_unshift($library['callbacks']['info'], 'libraries_prepare_files'); + array_unshift($library['callbacks']['post-detect'], 'libraries_detect_dependencies'); + + return $library; +} + +/** + * Tries to detect a library and its installed version. + * + * @param $name + * The machine name of a library to return registered information for. + * + * @return array|false + * An associative array containing registered information for the library + * specified by $name, or FALSE if the library $name is not registered. + * In addition to the keys returned by libraries_info(), the following keys + * are contained: + * - installed: A boolean indicating whether the library is installed. Note + * that not only the top-level library, but also each variant contains this + * key. + * - version: If the version could be detected, the full version string. + * - error: If an error occurred during library detection, one of the + * following error statuses: "not found", "not detected", "not supported". + * - error message: If an error occurred during library detection, a detailed + * error message. + * + * @see libraries_info() + * + * @deprecated Will be removed before a stable Drupal 8 release. Please use the + * new library load and managment concepts described at: + * https://www.drupal.org/node/2170763 + */ +function libraries_detect($name) { + // Re-use the statically cached value of libraries_info() to save memory. + $library = &libraries_info($name); + + if ($library === FALSE) { + return $library; + } + // If 'installed' is set, library detection ran already. + if (isset($library['installed'])) { + return $library; + } + + $library['installed'] = FALSE; + + // Check whether the library exists. + if (!isset($library['library path'])) { + $library['library path'] = libraries_get_path($library['machine name']); + } + if ($library['library path'] === FALSE || !file_exists($library['library path'])) { + $library['error'] = 'not found'; + $library['error message'] = t('The %library library could not be found.', array( + '%library' => $library['name'], + )); + return $library; + } + + // Invoke callbacks in the 'pre-detect' group. + libraries_invoke('pre-detect', $library); + + // Detect library version, if not hardcoded. + if (!isset($library['version'])) { + // We support both a single parameter, which is an associative array, and an + // indexed array of multiple parameters. + if (isset($library['version arguments'][0])) { + // Add the library as the first argument. + $library['version'] = call_user_func_array($library['version callback'], array_merge(array($library), $library['version arguments'])); + } + else { + $library['version'] = $library['version callback']($library, $library['version arguments']); + } + if (empty($library['version'])) { + $library['error'] = 'not detected'; + $library['error message'] = t('The version of the %library library could not be detected.', array( + '%library' => $library['name'], + )); + return $library; + } + } + + // Determine to which supported version the installed version maps. + if (!empty($library['versions'])) { + ksort($library['versions']); + $version = 0; + foreach ($library['versions'] as $supported_version => $version_properties) { + if (version_compare($library['version'], $supported_version, '>=')) { + $version = $supported_version; + } + } + if (!$version) { + $library['error'] = 'not supported'; + $library['error message'] = t('The installed version %version of the %library library is not supported.', array( + '%version' => $library['version'], + '%library' => $library['name'], + )); + return $library; + } + + // Apply version specific definitions and overrides. + $library = array_merge($library, $library['versions'][$version]); + unset($library['versions']); + } + + // Check each variant if it is installed. + if (!empty($library['variants'])) { + foreach ($library['variants'] as $variant_name => &$variant) { + // If no variant callback has been set, assume the variant to be + // installed. + if (!isset($variant['variant callback'])) { + $variant['installed'] = TRUE; + } + else { + // We support both a single parameter, which is an associative array, + // and an indexed array of multiple parameters. + if (isset($variant['variant arguments'][0])) { + // Add the library as the first argument, and the variant name as the second. + $variant['installed'] = call_user_func_array($variant['variant callback'], array_merge(array($library, $variant_name), $variant['variant arguments'])); + } + else { + $variant['installed'] = $variant['variant callback']($library, $variant_name, $variant['variant arguments']); + } + if (!$variant['installed']) { + $variant['error'] = 'not found'; + $variant['error message'] = t('The %variant variant of the %library library could not be found.', array( + '%variant' => $variant_name, + '%library' => $library['name'], + )); + } + } + } + } + + // If we end up here, the library should be usable. + $library['installed'] = TRUE; + + // Invoke callbacks in the 'post-detect' group. + libraries_invoke('post-detect', $library); + + return $library; +} + +/** + * Loads a library. + * + * @param $name + * The name of the library to load. + * @param $variant + * The name of the variant to load. Note that only one variant of a library + * can be loaded within a single request. The variant that has been passed + * first is used; different variant names in subsequent calls are ignored. + * + * @return + * An associative array of the library information as returned from + * libraries_info(). The top-level properties contain the effective definition + * of the library (variant) that has been loaded. Additionally: + * - installed: Whether the library is installed, as determined by + * libraries_detect_library(). + * - loaded: Either the amount of library files that have been loaded, or + * FALSE if the library could not be loaded. + * See hook_libraries_info() for more information. + * + * @deprecated Will be removed before a stable Drupal 8 release. Please use the + * new library load and managment concepts described at: + * https://www.drupal.org/node/2170763 + */ +function libraries_load($name, $variant = NULL) { + $loaded = &drupal_static(__FUNCTION__, array()); + + if (!isset($loaded[$name])) { + $library = \Drupal::cache('libraries')->get($name); + if ($library) { + $library = $library->data; + } + else { + $library = libraries_detect($name); + \Drupal::cache('libraries')->set($name, $library); + } + // If a variant was specified, override the top-level properties with the + // variant properties. + if (isset($variant)) { + // Ensure that the $variant key exists, and if it does not, set its + // 'installed' property to FALSE by default. This will prevent the loading + // of the library files below. + $library['variants'] += array($variant => array('installed' => FALSE)); + $library = array_merge($library, $library['variants'][$variant]); + } + // Regardless of whether a specific variant was requested or not, there can + // only be one variant of a library within a single request. + unset($library['variants']); + + // If the library (variant) is installed, load it. + $library['loaded'] = FALSE; + if ($library['installed']) { + // Load library dependencies. + if (isset($library['dependencies'])) { + foreach ($library['dependencies'] as $dependency) { + libraries_load($dependency); + } + } + + // Invoke callbacks in the 'pre-load' group. + libraries_invoke('pre-load', $library); + + // Load all the files associated with the library. + $library['loaded'] = libraries_load_files($library); + + // Invoke callbacks in the 'post-load' group. + libraries_invoke('post-load', $library); + } + $loaded[$name] = $library; + } + + return $loaded[$name]; +} + +/** + * Loads a library's files. + * + * @param $library + * An array of library information as returned by libraries_info(). + * + * @return + * The number of loaded files. + * + * @deprecated Will be removed before a stable Drupal 8 release. Please use the + * new library load and managment concepts described at: + * https://www.drupal.org/node/2170763 + */ +function libraries_load_files($library) { + + // Construct the full path to the library for later use. + $path = $library['library path']; + $path = ($library['path'] !== '' ? $path . '/' . $library['path'] : $path); + + // Count the number of loaded files for the return value. + $count = 0; + + // Load both the JavaScript and the CSS files. + // The parameters for drupal_add_js() and drupal_add_css() require special + // handling. + // @see drupal_process_attached() + foreach (array('js', 'css') as $type) { + // Given the removal of core functions like _drupal_add_js and + // _drupal_add_css the logic below cannot safely be run anymore. + // @see https://www.drupal.org/node/2702563 + break; + if (!empty($library['files'][$type])) { + foreach ($library['files'][$type] as $data => $options) { + // If the value is not an array, it's a filename and passed as first + // (and only) argument. + if (!is_array($options)) { + $data = $options; + $options = array(); + } + // In some cases, the first parameter ($data) is an array. Arrays can't + // be passed as keys in PHP, so we have to get $data from the value + // array. + if (is_numeric($data)) { + $data = $options['data']; + unset($options['data']); + } + // Prepend the library path to the file name. + $data = "$path/$data"; + // Apply the default group if the group isn't explicitly given. + if (!isset($options['group'])) { + $options['group'] = ($type == 'js') ? JS_DEFAULT : CSS_AGGREGATE_DEFAULT; + } + if ($type === 'js') { + $options['version'] = -1; + } + // @todo Avoid the usage of _drupal_add_js() and _drupal_add_css() + call_user_func('_drupal_add_' . $type, $data, $options); + $count++; + } + } + } + + // Load PHP files. + if (!empty($library['files']['php'])) { + foreach ($library['files']['php'] as $file => $array) { + $file_path = DRUPAL_ROOT . '/' . $path . '/' . $file; + if (file_exists($file_path)) { + require_once $file_path; + $count++; + } + } + } + + // Load integration files. + if (!empty($library['integration files'])) { + foreach ($library['integration files'] as $module => $files) { + libraries_load_files(array( + 'files' => $files, + 'path' => '', + 'library path' => drupal_get_path('module', $module), + )); + } + } + + return $count; +} + +/** + * Gets the version information from an arbitrary library. + * + * @param $library + * An associative array containing all information about the library. + * @param $options + * An associative array containing with the following keys: + * - file: The filename to parse for the version, relative to the library + * path. For example: 'docs/changelog.txt'. + * - pattern: A string containing a regular expression (PCRE) to match the + * library version. For example: '@version\s+([0-9a-zA-Z\.-]+)@'. Note that + * the returned version is not the match of the entire pattern (i.e. + * '@version 1.2.3' in the above example) but the match of the first + * sub-pattern (i.e. '1.2.3' in the above example). + * - lines: (optional) The maximum number of lines to search the pattern in. + * Defaults to 20. + * - cols: (optional) The maximum number of characters per line to take into + * account. Defaults to 200. In case of minified or compressed files, this + * prevents reading the entire file into memory. + * + * @return + * A string containing the version of the library. + * + * @see libraries_get_path() + * + * @deprecated Will be removed before a stable Drupal 8 release. Please use the + * new library load and managment concepts described at: + * https://www.drupal.org/node/2170763 + */ +function libraries_get_version($library, $options) { + // Provide defaults. + $options += array( + 'file' => '', + 'pattern' => '', + 'lines' => 20, + 'cols' => 200, + ); + + $file = DRUPAL_ROOT . '/' . $library['library path'] . '/' . $options['file']; + if (empty($options['file']) || !file_exists($file)) { + return; + } + $file = fopen($file, 'r'); + while ($options['lines'] && $line = fgets($file, $options['cols'])) { + if (preg_match($options['pattern'], $line, $version)) { + fclose($file); + return $version[1]; + } + $options['lines']--; + } + fclose($file); +} diff --git a/web/modules/libraries/libraries.services.yml b/web/modules/libraries/libraries.services.yml new file mode 100644 index 0000000000000000000000000000000000000000..b2880ad5724639f929802a12a69d90ca7818599f --- /dev/null +++ b/web/modules/libraries/libraries.services.yml @@ -0,0 +1,73 @@ +services: + libraries.manager: + class: Drupal\libraries\ExternalLibrary\LibraryManager + arguments: + - '@libraries.definition.discovery' + - '@plugin.manager.libraries.library_type' + + # By default Libraries API downloads library definitions from a number of + # remote library registries, the canonical one being + # https://www.drupal.org/project/libraries_registry, and stores them locally + # in the public://library-definitions directory. The URLs of the remote + # library registries and the local base path can be configured. The remote + # fetching can also be disabled altogether. + libraries.definition.discovery: + class: Drupal\libraries\ExternalLibrary\Definition\DefinitionDiscoveryInterface + factory: 'libraries.definition.discovery.factory:get' + libraries.definition.discovery.factory: + class: Drupal\libraries\ExternalLibrary\Definition\DefinitionDiscoveryFactory + arguments: + - '@config.factory' + - '@serialization.json' + - '@http_client' + - '@serialization.json' + # If you instead want to check your library definitions into version control + # and use YAML for them instead of JSON, you can place the following service + # definition in your site's services.yml file: + # libraries.definition.discovery: + # class: Drupal\libraries\ExternalLibrary\Definition\FileDefinitionDiscovery + # arguments: + # - '@serialization.yaml' + # # Replace this with the location of the library definitions in your setup. + # - '../library-definitions' + + plugin.manager.libraries.library_type: + class: Drupal\libraries\ExternalLibrary\Type\LibraryTypeFactory + parent: default_plugin_manager + plugin.manager.libraries.locator: + class: Drupal\libraries\ExternalLibrary\Local\LocatorManager + parent: default_plugin_manager + plugin.manager.libraries.version_detector: + class: Drupal\libraries\ExternalLibrary\Version\VersionDetectorManager + parent: default_plugin_manager + + libraries.config_subscriber: + class: Drupal\libraries\Config\LibrariesConfigSubscriber + arguments: ['@service_container'] + tags: + - { name: event_subscriber } + + libraries.php_file_loader: + class: Drupal\libraries\ExternalLibrary\PhpFile\PhpRequireLoader + + stream_wrapper.library_definitions: + class: Drupal\libraries\StreamWrapper\LibraryDefinitionsStream + arguments: ['@config.factory'] + tags: + - { name: stream_wrapper, scheme: 'library-definitions' } + stream_wrapper.asset_libraries: + class: Drupal\libraries\StreamWrapper\AssetLibrariesStream + tags: + - { name: stream_wrapper, scheme: 'asset' } + stream_wrapper.php_file_libraries: + class: Drupal\libraries\StreamWrapper\PhpFileLibrariesStream + tags: + - { name: stream_wrapper, scheme: 'php-file' } + + + cache.libraries: + class: Drupal\Core\Cache\CacheBackendInterface + tags: + - { name: cache.bin } + factory: cache_factory:get + arguments: [library] diff --git a/web/modules/libraries/src/Annotation/LibraryType.php b/web/modules/libraries/src/Annotation/LibraryType.php new file mode 100644 index 0000000000000000000000000000000000000000..3cf5491165e6eddf763632ea2522e458f962196f --- /dev/null +++ b/web/modules/libraries/src/Annotation/LibraryType.php @@ -0,0 +1,14 @@ +<?php + +namespace Drupal\libraries\Annotation; + +use Drupal\Component\Annotation\PluginID; + +/** + * Provides an annotation class for locator plugins. + * + * @Annotation + */ +class LibraryType extends PluginID { + +} diff --git a/web/modules/libraries/src/Annotation/Locator.php b/web/modules/libraries/src/Annotation/Locator.php new file mode 100644 index 0000000000000000000000000000000000000000..ab4311d156da8712ab7a73aa44500633356218ac --- /dev/null +++ b/web/modules/libraries/src/Annotation/Locator.php @@ -0,0 +1,14 @@ +<?php + +namespace Drupal\libraries\Annotation; + +use Drupal\Component\Annotation\PluginID; + +/** + * Provides an annotation class for locator plugins. + * + * @Annotation + */ +class Locator extends PluginID { + +} diff --git a/web/modules/libraries/src/Annotation/VersionDetector.php b/web/modules/libraries/src/Annotation/VersionDetector.php new file mode 100644 index 0000000000000000000000000000000000000000..a664f994f2b9a46b1a20d44026a7ed92f8e94160 --- /dev/null +++ b/web/modules/libraries/src/Annotation/VersionDetector.php @@ -0,0 +1,14 @@ +<?php + +namespace Drupal\libraries\Annotation; + +use Drupal\Component\Annotation\PluginID; + +/** + * Provides an annotation class for version detector plugins. + * + * @Annotation + */ +class VersionDetector extends PluginID { + +} diff --git a/web/modules/libraries/src/Config/LibrariesConfigSubscriber.php b/web/modules/libraries/src/Config/LibrariesConfigSubscriber.php new file mode 100644 index 0000000000000000000000000000000000000000..6578f42c6d6eaae190575fb48d42832ad92f28bf --- /dev/null +++ b/web/modules/libraries/src/Config/LibrariesConfigSubscriber.php @@ -0,0 +1,51 @@ +<?php + +namespace Drupal\libraries\Config; + +use Drupal\Core\Config\ConfigCrudEvent; +use Drupal\Core\Config\ConfigEvents; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Reacts to configuration changes of the 'libraries.settings' configuration. + */ +class LibrariesConfigSubscriber implements EventSubscriberInterface { + + /** + * The service container. + * + * @var \Symfony\Component\DependencyInjection\ContainerInterface + */ + protected $container; + + /** + * Constructs a Libraries API configuration subscriber. + * + * @param \Symfony\Component\DependencyInjection\ContainerInterface $container + * The service container. + */ + public function __construct(ContainerInterface $container) { + $this->container = $container; + } + + /** + * Unsets the definition discovery service when its configuration changes. + * + * @param \Drupal\Core\Config\ConfigCrudEvent $event + * The configuration event. + */ + public function onConfigSave(ConfigCrudEvent $event) { + if (($event->getConfig()->getName() === 'libraries.settings') && $event->isChanged('definition')) { + $this->container->set('libraries.definition.discovery', NULL); + } + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + return [ConfigEvents::SAVE => 'onConfigSave']; + } + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Asset/AssetLibrary.php b/web/modules/libraries/src/ExternalLibrary/Asset/AssetLibrary.php new file mode 100644 index 0000000000000000000000000000000000000000..50a31868682d824ee0f3cdcc7fa35d0b3f963a8d --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Asset/AssetLibrary.php @@ -0,0 +1,128 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Asset; + +use Drupal\Component\Plugin\Factory\FactoryInterface; +use Drupal\libraries\ExternalLibrary\Exception\LibraryNotInstalledException; +use Drupal\libraries\ExternalLibrary\LibraryBase; +use Drupal\libraries\ExternalLibrary\LibraryManagerInterface; +use Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface; +use Drupal\libraries\ExternalLibrary\Local\LocalLibraryTrait; +use Drupal\libraries\ExternalLibrary\Remote\RemoteLibraryInterface; +use Drupal\libraries\ExternalLibrary\Remote\RemoteLibraryTrait; +use Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface; + +/** + * Provides a class for a single attachable asset library. + */ +class AssetLibrary extends LibraryBase implements + AssetLibraryInterface, + LocalLibraryInterface, + RemoteLibraryInterface +{ + + use + LocalLibraryTrait, + RemoteLibraryTrait, + LocalRemoteAssetTrait + ; + + /** + * An array containing the CSS assets of the library. + * + * @var array + */ + protected $cssAssets = []; + + /** + * An array containing the JavaScript assets of the library. + * + * @var array + */ + protected $jsAssets = []; + + /** + * An array of attachable asset library IDs that this library depends on. + * + * @todo Explain the difference to regular dependencies. + */ + protected $attachableDependencies = []; + + /** + * Construct an external library. + * + * @param string $id + * The library ID. + * @param array $definition + * The library definition array. + * @param \Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface $library_type + * The library type of the library. + */ + public function __construct($id, array $definition, LibraryTypeInterface $library_type) { + parent::__construct($id, $definition, $library_type); + $this->remoteUrl = $definition['remote_url']; + $this->cssAssets = $definition['css']; + $this->jsAssets = $definition['js']; + $this->attachableDependencies = $definition['attachable_dependencies']; + } + + /** + * {@inheritdoc} + */ + protected static function processDefinition(array &$definition) { + parent::processDefinition($definition); + $definition += [ + 'remote_url' => '', + 'css' => [], + 'js' => [], + 'attachable_dependencies' => [], + ]; + } + + /** + * Returns a core library array structure for this library. + * + * @param \Drupal\libraries\ExternalLibrary\LibraryManagerInterface $library_manager + * The library manager that can be used to fetch dependencies. + * + * @return array + * + * @see \Drupal\libraries\ExternalLibrary\Asset\getAttachableAssetLibraries::getAttachableAssetLibraries() + * + * @throws \Drupal\libraries\ExternalLibrary\Exception\InvalidLibraryDependencyException + * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException + * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryTypeNotFoundException + * @throws \Drupal\Component\Plugin\Exception\PluginException + * + * @todo Document the return value. + */ + public function getAttachableAssetLibrary(LibraryManagerInterface $library_manager) { + if (!$this->canBeAttached()) { + throw new LibraryNotInstalledException($this); + } + return [ + 'version' => $this->getVersion(), + 'css' => $this->processCssAssets($this->cssAssets), + 'js' => $this->processJsAssets($this->jsAssets), + 'dependencies' => $this->attachableDependencies, + ]; + } + + /** + * Gets the locator of this library using the locator factory. + * + * @param \Drupal\Component\Plugin\Factory\FactoryInterface $locator_factory + * + * @return \Drupal\libraries\ExternalLibrary\Local\LocatorInterface + * + * @see \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface::getLocator() + */ + public function getLocator(FactoryInterface $locator_factory) { + // @todo Consider consolidating the stream wrappers used here. For now we + // allow asset libs to live almost anywhere. + return $locator_factory->createInstance('chain') + ->addLocator($locator_factory->createInstance('uri', ['uri' => 'asset://'])) + ->addLocator($locator_factory->createInstance('uri', ['uri' => 'php-file://'])); + } + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Asset/AssetLibraryInterface.php b/web/modules/libraries/src/ExternalLibrary/Asset/AssetLibraryInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..eac629024d5ce2e3928d1af45531e2263a7d1827 --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Asset/AssetLibraryInterface.php @@ -0,0 +1,65 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Asset; + +use Drupal\libraries\ExternalLibrary\Dependency\DependentLibraryInterface; +use Drupal\libraries\ExternalLibrary\LibraryInterface; +use Drupal\libraries\ExternalLibrary\LibraryManagerInterface; +use Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface; + +/** + * Provides an interface for external asset libraries with a single library. + * + * Asset is the generic term for CSS and JavaScript files. + * + * In order to load assets of external libraries as part of a page request the + * assets must be registered with Drupal core's library system. Therefore, + * Libraries API makes all libraries that are required by the installed + * installation profile, modules, and themes available as core asset libraries + * with the identifier 'libraries/[machine_name]' where '[machine_name]' is + * the Libraries API machine name of the external library. + * + * Thus, assuming that the external library 'flexslider' has been declared as a + * dependency, for example, it can be attached to a render array in the $build + * variable with the following code: + * @code + * $build['#attached']['library'] = ['libraries/flexslider']; + * @endcode + * + * In some cases an external library may contain multiple components, that + * should be loadable independently from each other. In this case, implement + * MultipleAssetLibraryInterface instead. + * + * @see libraries_library_info_build() + * @see \Drupal\libraries\ExternalLibrary\Asset\AssetLibraryTrait + * @see \Drupal\libraries\ExternalLibrary\Asset\MultipleAssetLibraryInterface + * + * @todo Support loading of source or minified assets. + * @todo Document how library dependencies work. + * + * @ingroup libraries + */ +interface AssetLibraryInterface extends + LibraryInterface, + VersionedLibraryInterface, + DependentLibraryInterface +{ + + /** + * Returns a core asset library array structure for this library. + * + * @param \Drupal\libraries\ExternalLibrary\LibraryManagerInterface $library_manager + * The library manager that can be used to fetch dependencies. + * + * @return array + * + * @see \Drupal\libraries\ExternalLibrary\Asset\SingleAssetLibraryTrait + * + * @throws \Drupal\libraries\ExternalLibrary\Exception\InvalidLibraryDependencyException + * + * @todo Document the return value. + * @todo Reconsider passing the library manager. + */ + public function getAttachableAssetLibrary(LibraryManagerInterface $library_manager); + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Asset/AttachableAssetLibraryRegistrationInterface.php b/web/modules/libraries/src/ExternalLibrary/Asset/AttachableAssetLibraryRegistrationInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..8a8a6e5cfb1496dce64fa7d1f6c43069cad13e41 --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Asset/AttachableAssetLibraryRegistrationInterface.php @@ -0,0 +1,22 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Asset; + +use Drupal\libraries\ExternalLibrary\LibraryInterface; +use Drupal\libraries\ExternalLibrary\LibraryManagerInterface; + +/** + * An interface for library types that want to react to library instantiation. + */ +interface AttachableAssetLibraryRegistrationInterface { + + /** + * Reacts to the instantiation of a library. + * + * @param \Drupal\libraries\ExternalLibrary\LibraryInterface $external_library + * The library that is being instantiated. + * @param \Drupal\libraries\ExternalLibrary\LibraryManagerInterface $library_manager + */ + public function getAttachableAssetLibraries(LibraryInterface $external_library, LibraryManagerInterface $library_manager); + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Asset/LocalRemoteAssetTrait.php b/web/modules/libraries/src/ExternalLibrary/Asset/LocalRemoteAssetTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..e673caba975e86faf32d2d7249d77d3dfb5d1cc7 --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Asset/LocalRemoteAssetTrait.php @@ -0,0 +1,108 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Asset; + +/** + * A trait for asset libraries that serve local and remote files. + * + * If the library files are available locally, they are served locally. + * Otherwise, the remote files are served, assuming a remote URL is specified. + * + * This trait should only be used in classes implementing LocalLibraryInterface + * and RemoteLibraryInterface. + * + * @see \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface + * @see \Drupal\libraries\ExternalLibrary\Remote\RemoteLibraryInterface + */ +trait LocalRemoteAssetTrait { + + /** + * Checks whether this library can be attached. + * + * @return bool + * TRUE if the library can be attached; FALSE otherwise. + * + * @see \Drupal\libraries\ExternalLibrary\Asset\SingleAssetLibraryTrait::canBeAttached() + */ + protected function canBeAttached() { + /** @var \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface|\Drupal\libraries\ExternalLibrary\Remote\RemoteLibraryInterface $this */ + return ($this->isInstalled() || $this->hasRemoteUrl()); + } + + /** + * Gets the prefix to prepend to file paths. + * + * For local libraries this is the library path, for remote libraries this is + * the remote URL. + * + * @return string + * The path prefix. + */ + protected function getPathPrefix() { + /** @var \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface|\Drupal\libraries\ExternalLibrary\Remote\RemoteLibraryInterface $this */ + if ($this->isInstalled()) { + // LocalLibraryInterface::getLocalPath() returns the path relative to the + // app root. In order for the core core asset system to register the path + // as relative to the app root, a leading slash is required. + /** @see \Drupal\Core\Asset\LibraryDiscoveryParser::buildByExtension() */ + return '/' . $this->getLocalPath(); + } + elseif ($this->hasRemoteUrl()) { + return $this->getRemoteUrl(); + } + else { + // @todo Throw an exception. + } + } + + /** + * Gets the CSS assets attached to this library. + * + * @param array $assets + * + * @return array + * An array of CSS assets of the library following the core library CSS + * structure. The keys of the array must be among the SMACSS categories + * 'base', 'layout, 'component', 'state', and 'theme'. The value of each + * category is in turn an array where the keys are the file paths of the CSS + * files and values are CSS options. + * + * @see https://smacss.com/ + * + * @see \Drupal\libraries\ExternalLibrary\Asset\SingleAssetLibraryTrait::getCssAssets() + */ + protected function processCssAssets(array $assets) { + // @todo Consider somehow caching the processed information. + $processed_assets = []; + foreach ($assets as $category => $category_assets) { + // @todo Somehow consolidate this with getJsAssets(). + foreach ($category_assets as $filename => $options) { + $processed_assets[$category][$this->getPathPrefix() . '/' . $filename] = $options; + } + } + return $processed_assets; + } + + /** + * Gets the JavaScript assets attached to this library. + * + * @param array $assets + * + * @return array + * An array of JavaScript assets of the library. The keys of the array are + * the file paths of the JavaScript files and the values are JavaScript + * options. + * + * @see \Drupal\libraries\ExternalLibrary\Asset\SingleAssetLibraryTrait::getJsAssets() + */ + protected function processJsAssets(array $assets) { + // @todo Consider somehow caching the processed information. + $processed_assets = []; + // @todo Somehow consolidate this with getCssAssets(). + foreach ($assets as $filename => $options) { + $processed_assets[$this->getPathPrefix() . '/' . $filename] = $options; + } + return $processed_assets; + } + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Asset/MultipleAssetLibrary.php b/web/modules/libraries/src/ExternalLibrary/Asset/MultipleAssetLibrary.php new file mode 100644 index 0000000000000000000000000000000000000000..f2c3d3c46c23d1b5d25acb405d6d1096ea2392f0 --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Asset/MultipleAssetLibrary.php @@ -0,0 +1,123 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Asset; + +use Drupal\Component\Plugin\Factory\FactoryInterface; +use Drupal\libraries\ExternalLibrary\Dependency\DependentLibraryInterface; +use Drupal\libraries\ExternalLibrary\Exception\LibraryNotInstalledException; +use Drupal\libraries\ExternalLibrary\LibraryBase; +use Drupal\libraries\ExternalLibrary\LibraryManagerInterface; +use Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface; +use Drupal\libraries\ExternalLibrary\Local\LocalLibraryTrait; +use Drupal\libraries\ExternalLibrary\Remote\RemoteLibraryInterface; +use Drupal\libraries\ExternalLibrary\Remote\RemoteLibraryTrait; +use Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface; +use Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface; + +/** + * Provides a class for a library with multiple attachable asset libraries. + */ +class MultipleAssetLibrary extends LibraryBase implements + MultipleAssetLibraryInterface, + VersionedLibraryInterface, + DependentLibraryInterface, + LocalLibraryInterface, + RemoteLibraryInterface +{ + + use + LocalLibraryTrait, + RemoteLibraryTrait, + LocalRemoteAssetTrait + ; + + /** + * An array of attachable asset libraries. + */ + protected $libraries = []; + + /** + * Construct an external library. + * + * @param string $id + * The library ID. + * @param array $definition + * The library definition array. + * @param \Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface $library_type + * The library type of the library. + */ + public function __construct($id, array $definition, LibraryTypeInterface $library_type) { + parent::__construct($id, $definition, $library_type); + $this->remoteUrl = $definition['remote_url']; + $this->libraries = $definition['libraries']; + } + + /** + * {@inheritdoc} + */ + protected static function processDefinition(array &$definition) { + parent::processDefinition($definition); + $definition += [ + 'remote_url' => '', + 'libraries' => [], + ]; + foreach ($definition['libraries'] as &$library) { + $library += [ + 'css' => [], + 'js' => [], + 'dependencies' => [], + ]; + } + } + + /** + * Returns a core library array structure for this library. + * + * @param \Drupal\libraries\ExternalLibrary\LibraryManagerInterface $library_manager + * The library manager that can be used to fetch dependencies. + * + * @return array + * + * @see \Drupal\libraries\ExternalLibrary\Asset\getAttachableAssetLibraries::getAttachableAssetLibraries() + * + * @throws \Drupal\libraries\ExternalLibrary\Exception\InvalidLibraryDependencyException + * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException + * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryTypeNotFoundException + * @throws \Drupal\Component\Plugin\Exception\PluginException + * + * @todo Document the return value. + */ + public function getAttachableAssetLibraries(LibraryManagerInterface $library_manager) { + if (!$this->canBeAttached()) { + throw new LibraryNotInstalledException($this); + } + $attachable_libraries = []; + foreach ($this->libraries as $attachable_library_id => $attachable_library) { + $attachable_libraries[$attachable_library_id] = [ + 'version' => $this->getVersion(), + 'css' => $this->processCssAssets($attachable_library['css']), + 'js' => $this->processJsAssets($attachable_library['js']), + 'dependencies' => $attachable_library['dependencies'], + ]; + } + return $attachable_libraries; + } + + /** + * Gets the locator of this library using the locator factory. + * + * @param \Drupal\Component\Plugin\Factory\FactoryInterface $locator_factory + * + * @return \Drupal\libraries\ExternalLibrary\Local\LocatorInterface + * + * @see \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface::getLocator() + */ + public function getLocator(FactoryInterface $locator_factory) { + // @todo Consider consolidating the stream wrappers used here. For now we + // allow asset libs to live almost anywhere. + return $locator_factory->createInstance('chain') + ->addLocator($locator_factory->createInstance('uri', ['uri' => 'asset://'])) + ->addLocator($locator_factory->createInstance('uri', ['uri' => 'php-file://'])); + } + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Asset/MultipleAssetLibraryInterface.php b/web/modules/libraries/src/ExternalLibrary/Asset/MultipleAssetLibraryInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..5dc43ffdc700714fad11a149a0e69aac727411a3 --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Asset/MultipleAssetLibraryInterface.php @@ -0,0 +1,64 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Asset; + +use Drupal\libraries\ExternalLibrary\LibraryInterface; +use Drupal\libraries\ExternalLibrary\LibraryManagerInterface; + +/** + * Provides an interface for external asset libraries with multiple libraries. + * + * See SingleAssetLibraryInterface for more information on external asset + * libraries in general. + * + * In case an external asset library contains multiple components that should + * be loadable independently from each other, Libraries API registers each + * library component as a separate library in the core asset library system. The + * resulting core library identifier is + * 'libraries/[machine_name].[component_name]' where '[machine_name]' is the + * Libraries API machine name of the external library and '[component_name]' is + * the component name specified by the library definition. + * + * Thus, assuming that the external library 'bootstrap' has been declared as a + * dependency, for example, and it has 'button' and 'form' components, they can + * be attached to a render array in the $build variable with the following code: + * @code + * $build['#attached']['library'] = [ + * 'libraries/bootstrap.button', + * 'libraries/bootstrap.form', + * ]; + * @endcode + * + * @see \Drupal\libraries\ExternalLibrary\Asset\AssetLibraryInterface + * + * @todo Support loading of source or minified assets. + * @todo Document how library dependencies work. + */ +interface MultipleAssetLibraryInterface extends LibraryInterface { + + /** + * Separates the library machine name from its component name. + * + * The period is chosen in alignment with core asset libraries, which are + * named, for example, 'core/jquery.once'. + */ + const SEPARATOR = '.'; + + /** + * Returns a core asset library array structure for this library. + * + * @param \Drupal\libraries\ExternalLibrary\LibraryManagerInterface $library_manager + * The library manager that can be used to fetch dependencies. + * + * @return array + * + * @see \Drupal\libraries\ExternalLibrary\Asset\SingleAssetLibraryTrait + * + * @throws \Drupal\libraries\ExternalLibrary\Exception\InvalidLibraryDependencyException + * + * @todo Document the return value. + * @todo Reconsider passing the library manager. + */ + public function getAttachableAssetLibraries(LibraryManagerInterface $library_manager); + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Definition/ChainDefinitionDiscovery.php b/web/modules/libraries/src/ExternalLibrary/Definition/ChainDefinitionDiscovery.php new file mode 100644 index 0000000000000000000000000000000000000000..f58801816d39fafd7e172cfa368b8ec1f576eab7 --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Definition/ChainDefinitionDiscovery.php @@ -0,0 +1,79 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Definition; + +use Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException; + +/** + * Provides a definition discovery that checks a list of other discoveries. + * + * The discoveries are checked sequentially. If the definition was not present + * in some discoveries but is found in a later discovery the definition will be + * written to the earlier discoveries if they implement + * WritableDefinitionDiscoveryInterface. + * + * @see \Drupal\libraries\ExternalLibrary\Definition\WritableDefinitionDiscoveryInterface + */ +class ChainDefinitionDiscovery implements DefinitionDiscoveryInterface { + + /** + * The list of definition discoveries that will be checked. + * + * @var \Drupal\libraries\ExternalLibrary\Definition\DefinitionDiscoveryInterface[] + */ + protected $discoveries = []; + + /** + * {@inheritdoc} + */ + public function hasDefinition($id) { + foreach ($this->discoveries as $discovery) { + if ($discovery->hasDefinition($id)) { + return TRUE; + } + } + + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function getDefinition($id) { + /** @var \Drupal\libraries\ExternalLibrary\Definition\WritableDefinitionDiscoveryInterface[] $discoveries_to_write */ + $discoveries_to_write = []; + foreach ($this->discoveries as $discovery) { + if ($discovery->hasDefinition($id)) { + $definition = $discovery->getDefinition($id); + break; + } + elseif ($discovery instanceof WritableDefinitionDiscoveryInterface) { + $discoveries_to_write[] = $discovery; + } + } + + if (!isset($definition)) { + throw new LibraryDefinitionNotFoundException($id); + } + + foreach ($discoveries_to_write as $discovery_to_write) { + $discovery_to_write->writeDefinition($id, $definition); + } + + return $definition; + } + + /** + * Adds a definition discovery to the list to check. + * + * @param \Drupal\libraries\ExternalLibrary\Definition\DefinitionDiscoveryInterface $discovery + * The definition discovery to add. + * + * @return $this + */ + public function addDiscovery(DefinitionDiscoveryInterface $discovery) { + $this->discoveries[] = $discovery; + return $this; + } + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Definition/DefinitionDiscoveryFactory.php b/web/modules/libraries/src/ExternalLibrary/Definition/DefinitionDiscoveryFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..970385e5f0ebacd40ab50952b84a55377207bd70 --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Definition/DefinitionDiscoveryFactory.php @@ -0,0 +1,104 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Definition; + +use Drupal\Component\Serialization\SerializationInterface; +use Drupal\Core\Config\ConfigFactoryInterface; +use GuzzleHttp\ClientInterface; + +/** + * Instantiates a library definition discovery based on configuration. + */ +class DefinitionDiscoveryFactory { + + /** + * The configuration factory. + * + * @var \Drupal\Core\Config\ConfigFactoryInterface + */ + protected $configFactory; + + /** + * The serializer for local definition files. + * + * @var \Drupal\Component\Serialization\SerializationInterface + */ + protected $localSerializer; + + /** + * The HTTP client used to fetch remote definitions. + * + * @var \GuzzleHttp\ClientInterface + */ + protected $httpClient; + + /** + * The serializer for remote definitions. + * + * @var \Drupal\Component\Serialization\SerializationInterface + */ + protected $remoteSerializer; + + /** + * Constructs a definition discovery factory. + * + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The configuration factory. + * @param \Drupal\Component\Serialization\SerializationInterface $local_serializer + * The serializer for local definition files. + * @param \GuzzleHttp\ClientInterface $http_client + * The HTTP client used to fetch remote definitions. + * @param \Drupal\Component\Serialization\SerializationInterface $remote_serializer + * The serializer for remote definitions. + */ + public function __construct( + ConfigFactoryInterface $config_factory, + SerializationInterface $local_serializer, + ClientInterface $http_client, + SerializationInterface $remote_serializer + ) { + $this->configFactory = $config_factory; + $this->localSerializer = $local_serializer; + $this->httpClient = $http_client; + $this->remoteSerializer = $remote_serializer; + } + + /** + * Gets a library definition discovery. + * + * @return \Drupal\libraries\ExternalLibrary\Definition\DefinitionDiscoveryInterface + * The library definition discovery. + */ + public function get() { + $config = $this->configFactory->get('libraries.settings'); + + if ($config->get('definition.remote.enable')) { + $discovery = new ChainDefinitionDiscovery(); + + $local_discovery = new WritableFileDefinitionDiscovery( + $this->localSerializer, + $config->get('definition.local.path') + ); + $discovery->addDiscovery($local_discovery); + + foreach ($config->get('definition.remote.urls') as $remote_url) { + $remote_discovery = new GuzzleDefinitionDiscovery( + $this->httpClient, + $this->remoteSerializer, + $remote_url + ); + + $discovery->addDiscovery($remote_discovery); + } + } + else { + $discovery = new FileDefinitionDiscovery( + $this->localSerializer, + $config->get('definition.local.path') + ); + } + + return $discovery; + } + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Definition/DefinitionDiscoveryInterface.php b/web/modules/libraries/src/ExternalLibrary/Definition/DefinitionDiscoveryInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..61fc589ccab3fabec86faf97a7b3db0caab25bdc --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Definition/DefinitionDiscoveryInterface.php @@ -0,0 +1,45 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Definition; + +/** + * Provides an interface for library definition discoveries. + * + * This is similar to the plugin system's DiscoveryInterface, except that this + * does not require knowing all definitions upfront, so there is no + * getDefinitions() method. + * + * @see \Drupal\Component\Plugin\Discovery\DiscoveryInterface + * + * @ingroup libraries + */ +interface DefinitionDiscoveryInterface { + + /** + * Checks whether a library definition exists. + * + * @param string $id + * The library ID. + * + * @return bool + * TRUE if a library definition with the given ID exists; FALSE otherwise. + */ + public function hasDefinition($id); + + /** + * Gets a library definition by its ID. + * + * @param string $id + * The library ID. + * + * @return array + * The library definition. + * + * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException + * + * @todo Consider returning a classed object instead of an array or at least + * document and validate the array structure. + */ + public function getDefinition($id); + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Definition/FileDefinitionDiscovery.php b/web/modules/libraries/src/ExternalLibrary/Definition/FileDefinitionDiscovery.php new file mode 100644 index 0000000000000000000000000000000000000000..6ad62c491516d1174a49b1db2274c782627985dc --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Definition/FileDefinitionDiscovery.php @@ -0,0 +1,36 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Definition; + +use Drupal\Component\Serialization\SerializationInterface; +use Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException; + +/** + * Provides a libraries definition discovery using PHP's native file functions. + * + * It supports either a URI with a stream wrapper, an absolute file path or a + * file path relative to the Drupal root as a base URI. + * + * By default YAML files are used. + * + * @see \Drupal\libraries\StreamWrapper\LibraryDefinitionsStream + * + * @ingroup libraries + */ +class FileDefinitionDiscovery extends FileDefinitionDiscoveryBase implements DefinitionDiscoveryInterface { + + /** + * {@inheritdoc} + */ + public function hasDefinition($id) { + return file_exists($this->getFileUri($id)); + } + + /** + * {@inheritdoc} + */ + protected function getSerializedDefinition($id) { + return file_get_contents($this->getFileUri($id)); + } + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Definition/FileDefinitionDiscoveryBase.php b/web/modules/libraries/src/ExternalLibrary/Definition/FileDefinitionDiscoveryBase.php new file mode 100644 index 0000000000000000000000000000000000000000..0151f2794980042ce22a912823c93f5792d4373a --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Definition/FileDefinitionDiscoveryBase.php @@ -0,0 +1,82 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Definition; + +use Drupal\Component\Serialization\SerializationInterface; +use Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException; + +/** + * Provides a base implementation for file-based definition discoveries. + * + * This discovery assumes that library files contain the serialized library + * definition and are accessible under a common base URI. The expected library + * file URI will be constructed from this by appending '/$id.$extension' to + * this, where $id is the library ID and $extension is the serializer extension. + */ +abstract class FileDefinitionDiscoveryBase implements DefinitionDiscoveryInterface { + + /** + * The serializer for the library definition files. + * + * @var \Drupal\Component\Serialization\SerializationInterface + */ + protected $serializer; + + /** + * The base URI for the library files. + * + * @var string + */ + protected $baseUri; + + /** + * Constructs a stream-based library definition discovery. + * + * @param \Drupal\Component\Serialization\SerializationInterface $serializer + * The serializer for the library definition files. + * @param string $base_uri + * The base URI for the library files. + */ + public function __construct(SerializationInterface $serializer, $base_uri) { + $this->serializer = $serializer; + $this->baseUri = $base_uri; + } + + /** + * {@inheritdoc} + */ + public function getDefinition($id) { + if (!$this->hasDefinition($id)) { + throw new LibraryDefinitionNotFoundException($id); + } + return $this->serializer->decode($this->getSerializedDefinition($id)); + } + + /** + * Gets the contents of the library file. + * + * @param $id + * The library ID to retrieve the serialized definition for. + * + * @return string + * The serialized library definition. + * + * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException + */ + abstract protected function getSerializedDefinition($id); + + /** + * Returns the file URI of the library definition file for a given library ID. + * + * @param $id + * The ID of the external library. + * + * @return string + * The file URI of the file the library definition resides in. + */ + protected function getFileUri($id) { + $filename = $id . '.' . $this->serializer->getFileExtension(); + return "$this->baseUri/$filename"; + } + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Definition/GuzzleDefinitionDiscovery.php b/web/modules/libraries/src/ExternalLibrary/Definition/GuzzleDefinitionDiscovery.php new file mode 100644 index 0000000000000000000000000000000000000000..59745b78c7fbd6523287da0bce6f14ce601d7f22 --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Definition/GuzzleDefinitionDiscovery.php @@ -0,0 +1,71 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Definition; + +use Drupal\Component\Serialization\SerializationInterface; +use Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException; +use GuzzleHttp\ClientInterface; +use GuzzleHttp\Exception\GuzzleException; + +/** + * Provides a definition discovery that fetches remote definitions using Guzzle. + * + * By default JSON files are assumed to be in JSON format. + * + * @todo Cache responses statically by ID to avoid multiple HTTP requests when + * calling hasDefinition() and getDefinition() sequentially. + */ +class GuzzleDefinitionDiscovery extends FileDefinitionDiscoveryBase implements DefinitionDiscoveryInterface { + + /** + * The HTTP client. + * + * @var \GuzzleHttp\ClientInterface + */ + protected $httpClient; + + /** + * Constructs a Guzzle-based definition discvoery. + * + * @param \GuzzleHttp\ClientInterface $http_client + * The HTTP client. + * @param \Drupal\Component\Serialization\SerializationInterface $serializer + * The serializer for the library definition files. + * @param string $base_url + * The base URL for the library files. + */ + public function __construct(ClientInterface $http_client, SerializationInterface $serializer, $base_url) { + parent::__construct($serializer, $base_url); + $this->httpClient = $http_client; + } + + /** + * {@inheritdoc} + */ + public function hasDefinition($id) { + try { + $response = $this->httpClient->request('GET', $this->getFileUri($id)); + return $response->getStatusCode() === 200; + } + catch (GuzzleException $exception) { + return FALSE; + } + } + + /** + * {@inheritdoc} + */ + protected function getSerializedDefinition($id) { + try { + $response = $this->httpClient->request('GET', $this->getFileUri($id)); + return (string) $response->getBody(); + } + catch (GuzzleException $exception) { + throw new LibraryDefinitionNotFoundException($id, '', 0, $exception); + } + catch (\RuntimeException $exception) { + throw new LibraryDefinitionNotFoundException($id, '', 0, $exception); + } + } + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Definition/WritableDefinitionDiscoveryInterface.php b/web/modules/libraries/src/ExternalLibrary/Definition/WritableDefinitionDiscoveryInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..f644a0d36ee9a49385105b195c4621960f210810 --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Definition/WritableDefinitionDiscoveryInterface.php @@ -0,0 +1,25 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Definition; + +/** + * Provides an interface for library definition discoveries that are writable. + * + * @see \Drupal\libraries\ExternalLibrary\Definition\DefinitionDiscoveryInterface + * @see \Drupal\libraries\ExternalLibrary\Definition\ChainDefinitionDiscovery + */ +interface WritableDefinitionDiscoveryInterface extends DefinitionDiscoveryInterface { + + /** + * Writes a library definition persistently. + * + * @param string $id + * The library ID. + * @param array $definition + * The library definition to write. + * + * @return $this + */ + public function writeDefinition($id, $definition); + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Definition/WritableFileDefinitionDiscovery.php b/web/modules/libraries/src/ExternalLibrary/Definition/WritableFileDefinitionDiscovery.php new file mode 100644 index 0000000000000000000000000000000000000000..b8204fb9e508abdcbd0c5758b90ddc7329455361 --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Definition/WritableFileDefinitionDiscovery.php @@ -0,0 +1,20 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Definition; + +/** + * Provides a definition discovery based on a writable directory or stream. + * + * @see \Drupal\libraries\ExternalLibrary\Definition\FileDefinitionDiscovery + */ +class WritableFileDefinitionDiscovery extends FileDefinitionDiscovery implements WritableDefinitionDiscoveryInterface { + + /** + * {@inheritdoc} + */ + public function writeDefinition($id, $definition) { + file_put_contents($this->getFileUri($id), $this->serializer->encode($definition)); + return $this; + } + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Dependency/DependentLibraryInterface.php b/web/modules/libraries/src/ExternalLibrary/Dependency/DependentLibraryInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..fd838101f325f81b6dc8c8a8c5e4d5d3ebab7827 --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Dependency/DependentLibraryInterface.php @@ -0,0 +1,22 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Dependency; + +use Drupal\libraries\ExternalLibrary\LibraryInterface; + +/** + * Provides an interface for libraries that depend on other libraries. + * + * @todo Implement versioned dependencies. + */ +interface DependentLibraryInterface extends LibraryInterface { + + /** + * Returns the libraries dependencies, if any. + * + * @return string[] + * An array of library IDs of libraries that the library depends on. + */ + public function getDependencies(); + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Dependency/DependentLibraryTrait.php b/web/modules/libraries/src/ExternalLibrary/Dependency/DependentLibraryTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..e7623ae7a2ec36c8a622e818c10ed87881459e16 --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Dependency/DependentLibraryTrait.php @@ -0,0 +1,29 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Dependency; + +/** + * Provides a trait for libraries that depend on other libraries. + */ +trait DependentLibraryTrait { + + /** + * An array of library IDs of libraries that the library depends on. + * + * @return string[] + */ + protected $dependencies; + + /** + * Returns the libraries dependencies, if any. + * + * @return string[] + * An array of library IDs of libraries that the library depends on. + * + * @see \Drupal\libraries\ExternalLibrary\Dependency\DependentLibraryInterface::getDependencies() + */ + public function getDependencies() { + return $this->dependencies; + } + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Exception/InvalidLibraryDependencyException.php b/web/modules/libraries/src/ExternalLibrary/Exception/InvalidLibraryDependencyException.php new file mode 100644 index 0000000000000000000000000000000000000000..7ab3eeb7e4ce73dd3b3bbef97f2571ab13b214e0 --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Exception/InvalidLibraryDependencyException.php @@ -0,0 +1,45 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Exception; + +use Drupal\libraries\ExternalLibrary\Utility\DependencyAccessorTrait; +use Drupal\libraries\ExternalLibrary\LibraryInterface; +use Drupal\libraries\ExternalLibrary\Utility\LibraryAccessorTrait; +use Drupal\libraries\ExternalLibrary\Utility\LibraryAccessorInterface; + +/** + * Provides an exception for an invalid library exception. + */ +class InvalidLibraryDependencyException extends \UnexpectedValueException implements LibraryAccessorInterface { + + use LibraryAccessorTrait; + use DependencyAccessorTrait; + + /** + * Constructs a library exception. + * + * @param \Drupal\libraries\ExternalLibrary\LibraryInterface $library + * The library with the invalid dependency. + * @param \Drupal\libraries\ExternalLibrary\LibraryInterface $dependency + * The dependency. + * @param string $message + * (optional) The exception message. + * @param int $code + * (optional) The error code. + * @param \Exception $previous + * (optional) The previous exception. + */ + public function __construct( + LibraryInterface $library, + LibraryInterface $dependency, + $message = '', + $code = 0, + \Exception $previous = NULL + ) { + $this->library = $library; + $this->dependency = $dependency; + $message = $message ?: "The library '{$this->library->getId()}' cannot depend on the library '{$this->dependency->getId()}'."; + parent::__construct($message, $code, $previous); + } + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Exception/LibraryDefinitionNotFoundException.php b/web/modules/libraries/src/ExternalLibrary/Exception/LibraryDefinitionNotFoundException.php new file mode 100644 index 0000000000000000000000000000000000000000..c274302d699892a8b2950f7964b3f90af253f571 --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Exception/LibraryDefinitionNotFoundException.php @@ -0,0 +1,38 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Exception; + +use Drupal\libraries\ExternalLibrary\Utility\LibraryIdAccessorTrait; +use Drupal\libraries\ExternalLibrary\Utility\LibraryIdAccessorInterface; + +/** + * Provides an exception for a library definition that cannot be found. + */ +class LibraryDefinitionNotFoundException extends \RuntimeException implements LibraryIdAccessorInterface { + + use LibraryIdAccessorTrait; + + /** + * Constructs a library exception. + * + * @param string $library_id + * The library ID. + * @param string $message + * (optional) The exception message. + * @param int $code + * (optional) The error code. + * @param \Exception $previous + * (optional) The previous exception. + */ + public function __construct( + $library_id, + $message = '', + $code = 0, + \Exception $previous = NULL + ) { + $this->libraryId = (string) $library_id; + $message = $message ?: "The library definition for the library '{$this->libraryId}' could not be found."; + parent::__construct($message, $code, $previous); + } + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Exception/LibraryNotInstalledException.php b/web/modules/libraries/src/ExternalLibrary/Exception/LibraryNotInstalledException.php new file mode 100644 index 0000000000000000000000000000000000000000..ed48a629c458165cd5a30e0584e6cc70e92a9a2d --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Exception/LibraryNotInstalledException.php @@ -0,0 +1,39 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Exception; + +use Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface; +use Drupal\libraries\ExternalLibrary\Utility\LibraryAccessorTrait; +use Drupal\libraries\ExternalLibrary\Utility\LibraryAccessorInterface; + +/** + * Provides an exception for a library that is not installed. + */ +class LibraryNotInstalledException extends \RuntimeException implements LibraryAccessorInterface { + + use LibraryAccessorTrait; + + /** + * Constructs a library exception. + * + * @param \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface $library + * The library that is not installed. + * @param string $message + * (optional) The exception message. + * @param int $code + * (optional) The error code. + * @param \Exception $previous + * (optional) The previous exception. + */ + public function __construct( + LocalLibraryInterface $library, + $message = '', + $code = 0, + \Exception $previous = NULL + ) { + $this->library = $library; + $message = $message ?: "The library '{$this->library->getId()}' is not installed."; + parent::__construct($message, $code, $previous); + } + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Exception/LibraryTypeNotFoundException.php b/web/modules/libraries/src/ExternalLibrary/Exception/LibraryTypeNotFoundException.php new file mode 100644 index 0000000000000000000000000000000000000000..b5655bba09a626a1d7b73a60b2971a1fc19c5a83 --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Exception/LibraryTypeNotFoundException.php @@ -0,0 +1,38 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Exception; + +use Drupal\libraries\ExternalLibrary\Utility\LibraryIdAccessorTrait; +use Drupal\libraries\ExternalLibrary\Utility\LibraryIdAccessorInterface; + +/** + * Provides an exception for a library definition without a type declaration. + */ +class LibraryTypeNotFoundException extends \RuntimeException implements LibraryAccessorInterface { + + use LibraryIdAccessorTrait; + + /** + * Constructs a library exception. + * + * @param string $library_id + * The library ID. + * @param string $message + * (optional) The exception message. + * @param int $code + * (optional) The error code. + * @param \Exception $previous + * (optional) The previous exception. + */ + public function __construct( + $library_id, + $message = '', + $code = 0, + \Exception $previous = NULL + ) { + $this->libraryId = (string) $library_id; + $message = $message ?: "The library type for the library '{$this->libraryId}' could not be found."; + parent::__construct($message, $code, $previous); + } + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Exception/UnknownLibraryVersionException.php b/web/modules/libraries/src/ExternalLibrary/Exception/UnknownLibraryVersionException.php new file mode 100644 index 0000000000000000000000000000000000000000..4afe0593feb4f48770fdbd1651164bf337f5af8b --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Exception/UnknownLibraryVersionException.php @@ -0,0 +1,39 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Exception; + +use Drupal\libraries\ExternalLibrary\Utility\LibraryAccessorTrait; +use Drupal\libraries\ExternalLibrary\Utility\LibraryAccessorInterface; +use Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface; + +/** + * Provides an exception for libraries whose version has not been detected. + */ +class UnknownLibraryVersionException extends \RuntimeException implements LibraryAccessorInterface { + + use LibraryAccessorTrait; + + /** + * Constructs a library exception. + * + * @param \Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface $library + * The library. + * @param string $message + * (optional) The exception message. + * @param int $code + * (optional) The error code. + * @param \Exception $previous + * (optional) The previous exception. + */ + public function __construct( + VersionedLibraryInterface $library, + $message = '', + $code = 0, + \Exception $previous = NULL + ) { + $this->library = $library; + $message = $message ?: "The version of library '{$this->library->getId()}' could not be detected."; + parent::__construct($message, $code, $previous); + } + +} diff --git a/web/modules/libraries/src/ExternalLibrary/LibraryBase.php b/web/modules/libraries/src/ExternalLibrary/LibraryBase.php new file mode 100644 index 0000000000000000000000000000000000000000..8571552c7dda49504545397e96472a2c14b360d2 --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/LibraryBase.php @@ -0,0 +1,83 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary; + +use Drupal\libraries\ExternalLibrary\Dependency\DependentLibraryInterface; +use Drupal\libraries\ExternalLibrary\Dependency\DependentLibraryTrait; +use Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface; +use Drupal\libraries\ExternalLibrary\Utility\IdAccessorTrait; +use Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface; +use Drupal\libraries\ExternalLibrary\Version\VersionedLibraryTrait; + +/** + * Provides a base external library implementation. + */ +abstract class LibraryBase implements + LibraryInterface, + DependentLibraryInterface, + VersionedLibraryInterface +{ + + use + IdAccessorTrait, + DependentLibraryTrait, + VersionedLibraryTrait + ; + + /** + * The library type of this library. + * + * @var \Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface + */ + protected $type; + + /** + * Constructs a library. + * + * @param string $id + * The library ID. + * @param array $definition + * The library definition array. + * @param \Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface $type + * The library type of this library. + */ + public function __construct($id, array $definition, LibraryTypeInterface $type) { + $this->id = (string) $id; + $this->type = $type; + $this->dependencies = $definition['dependencies']; + $this->versionDetector = $definition['version_detector']; + } + + /** + * {@inheritdoc} + */ + public static function create($id, array $definition, LibraryTypeInterface $type) { + static::processDefinition($definition); + return new static($id, $definition, $type); + } + + /** + * Gets library definition defaults. + * + * @param array $definition + * A library definition array. + */ + protected static function processDefinition(array &$definition) { + $definition += [ + 'dependencies' => [], + // @todo This fallback is not very elegant. + 'version_detector' => [ + 'id' => 'static', + 'configuration' => ['version' => ''], + ], + ]; + } + + /** + * {@inheritdoc} + */ + public function getType() { + return $this->type; + } + +} diff --git a/web/modules/libraries/src/ExternalLibrary/LibraryInterface.php b/web/modules/libraries/src/ExternalLibrary/LibraryInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..ebf43a9b9fdccffc2fd80145265b77c4446c6dc6 --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/LibraryInterface.php @@ -0,0 +1,44 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary; + +use Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface; + +/** + * Provides an interface for different types of external libraries. + * + * @ingroup libraries + */ +interface LibraryInterface { + + /** + * Creates an instance of the library from its definition. + * + * @param string $id + * The library ID. + * @param array $definition + * The library definition array. + * @param \Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface $type + * The library type of this library. + * + * @return static + */ + public static function create($id, array $definition, LibraryTypeInterface $type); + + /** + * Returns the ID of the library. + * + * @return string + * The library ID. This must be unique among all known libraries. + */ + public function getId(); + + /** + * Returns the library type of the library. + * + * @return \Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface + * The library of the library. + */ + public function getType(); + +} diff --git a/web/modules/libraries/src/ExternalLibrary/LibraryManager.php b/web/modules/libraries/src/ExternalLibrary/LibraryManager.php new file mode 100644 index 0000000000000000000000000000000000000000..17a62236e292b2ddf0b2a71e3914bf97c080bb9e --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/LibraryManager.php @@ -0,0 +1,120 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary; + +use Drupal\Component\Plugin\Factory\FactoryInterface; +use Drupal\libraries\ExternalLibrary\Exception\LibraryTypeNotFoundException; +use Drupal\libraries\ExternalLibrary\Type\LibraryCreationListenerInterface; +use Drupal\libraries\ExternalLibrary\Type\LibraryLoadingListenerInterface; +use Drupal\libraries\ExternalLibrary\Definition\DefinitionDiscoveryInterface; + +/** + * Provides a manager for external libraries. + * + * @todo Dispatch events at various points in the library lifecycle. + * @todo Automatically load PHP file libraries that are required by modules or + * themes. + */ +class LibraryManager implements LibraryManagerInterface { + + /** + * The library definition discovery. + * + * @var \Drupal\libraries\ExternalLibrary\Definition\DefinitionDiscoveryInterface + */ + protected $definitionDiscovery; + + /** + * The library type factory. + * + * @var \Drupal\Component\Plugin\Factory\FactoryInterface + */ + protected $libraryTypeFactory; + + /** + * Constructs an external library manager. + * + * @param \Drupal\libraries\ExternalLibrary\Definition\DefinitionDiscoveryInterface $definition_disovery + * The library registry. + * @param \Drupal\Component\Plugin\Factory\FactoryInterface $library_type_factory + * The library type factory. + */ + public function __construct( + DefinitionDiscoveryInterface $definition_disovery, + FactoryInterface $library_type_factory + ) { + $this->definitionDiscovery = $definition_disovery; + $this->libraryTypeFactory = $library_type_factory; + } + + /** + * {@inheritdoc} + */ + public function getLibrary($id) { + $definition = $this->definitionDiscovery->getDefinition($id); + return $this->getLibraryFromDefinition($id, $definition); + } + + /** + * {@inheritdoc} + */ + public function getRequiredLibraryIds() { + $library_ids = []; + foreach (['module', 'theme'] as $type) { + foreach (system_get_info($type) as $info) { + if (isset($info['library_dependencies'])) { + $library_ids = array_merge($library_ids, $info['library_dependencies']); + } + } + } + return array_unique($library_ids); + } + + /** + * {@inheritdoc} + */ + public function load($id) { + $definition = $this->definitionDiscovery->getDefinition($id); + $library_type = $this->getLibraryType($id, $definition); + // @todo Throw an exception instead of silently failing. + if ($library_type instanceof LibraryLoadingListenerInterface) { + $library_type->onLibraryLoad($this->getLibraryFromDefinition($id, $definition)); + } + } + + /** + * @param $id + * @param $definition + * @return mixed + */ + protected function getLibraryFromDefinition($id, $definition) { + $library_type = $this->getLibraryType($id, $definition); + + // @todo Make this alter-able. + $class = $library_type->getLibraryClass(); + + // @todo Make sure that the library class implements the correct interface. + $library = $class::create($id, $definition, $library_type); + + if ($library_type instanceof LibraryCreationListenerInterface) { + $library_type->onLibraryCreate($library); + return $library; + } + return $library; + } + + /** + * @param string $id + * @param array $definition + * + * @return \Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface + */ + protected function getLibraryType($id, $definition) { + // @todo Validate that the type is a string. + if (!isset($definition['type'])) { + throw new LibraryTypeNotFoundException($id); + } + return $this->libraryTypeFactory->createInstance($definition['type']); + } + +} diff --git a/web/modules/libraries/src/ExternalLibrary/LibraryManagerInterface.php b/web/modules/libraries/src/ExternalLibrary/LibraryManagerInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..0c4383f37ae3efc8625089098f1d0c806183e13d --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/LibraryManagerInterface.php @@ -0,0 +1,51 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary; + +/** + * Provides an interface for external library managers. + */ +interface LibraryManagerInterface { + + /** + * Gets a library by its ID. + * + * @param string $id + * The library ID. + * + * @return \Drupal\libraries\ExternalLibrary\LibraryInterface + * The library object. + * + * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException + * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryTypeNotFoundException + * @throws \Drupal\Component\Plugin\Exception\PluginException + */ + public function getLibrary($id); + + /** + * Gets the list of libraries that are required by enabled extensions. + * + * Modules, themes, and installation profiles can declare library dependencies + * by specifying a 'library_dependencies' key in their info files. + * + * @return string[] + * An array of library IDs. + */ + public function getRequiredLibraryIds(); + + /** + * Loads library files for a library. + * + * Note that not all library types support explicit loading. Asset libraries, + * in particular, are declared to Drupal core's library system and are then + * loaded using that. + * + * @param string $id + * The ID of the library. + * + * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException + * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryNotInstalledException + */ + public function load($id); + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Local/LocalLibraryInterface.php b/web/modules/libraries/src/ExternalLibrary/Local/LocalLibraryInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..f8c1fbe5730f04b6f6e1f1183d98179ab741d049 --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Local/LocalLibraryInterface.php @@ -0,0 +1,82 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Local; + +use Drupal\Component\Plugin\Factory\FactoryInterface; +use Drupal\libraries\ExternalLibrary\LibraryInterface; + +/** + * Provides an interface for local libraries. + * + * Local libraries are libraries that can be found on the filesystem. If the + * library files can be found in the filesystem a library is considered + * installed and its library path can be retrieved. + * + * Because determining whether or not the library is available locally is not + * the responsibility of the library itself, but of a designated locator, this + * interface declares setter methods, as well. + * + * @see \Drupal\libraries\ExternalLibrary\Local\LocatorInterface + */ +interface LocalLibraryInterface extends LibraryInterface { + + /** + * Checks whether the library is installed. + * + * @return bool + * TRUE if the library is installed; FALSE otherwise; + */ + public function isInstalled(); + + /** + * Marks the library as uninstalled. + * + * A corresponding method to mark the library as installed is not provided as + * an installed library should have a library path, so that + * LocalLibraryInterface::setLibraryPath() can be used instead. + * + * @return $this + * + * @see \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface::isInstalled() + * @see \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface::setLocalPath() + */ + public function setUninstalled(); + + /** + * Gets the local path to the library. + * + * @return string + * The absolute path to the library on the filesystem. + * + * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryNotInstalledException + * + * @see \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface::setLocalPath() + */ + public function getLocalPath(); + + /** + * Sets the local path of the library. + * + * @param string $path + * The path to the library. + * + * @see \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface::getLocalPath() + */ + public function setLocalPath($path); + + /** + * Gets the locator of this library using the locator factory. + * + * Because determining the installation status and library path of a library + * is not specific to any library or even any library type, this logic is + * offloaded to separate locator objects. + * + * @param \Drupal\Component\Plugin\Factory\FactoryInterface $locator_factory + * + * @return \Drupal\libraries\ExternalLibrary\Local\LocatorInterface + * + * @see \Drupal\libraries\ExternalLibrary\Local\LocatorInterface + */ + public function getLocator(FactoryInterface $locator_factory); + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Local/LocalLibraryTrait.php b/web/modules/libraries/src/ExternalLibrary/Local/LocalLibraryTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..3f08e4ba4c1f2d346aa6158f6594e56ea3034cba --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Local/LocalLibraryTrait.php @@ -0,0 +1,99 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Local; +use Drupal\libraries\ExternalLibrary\Exception\LibraryNotInstalledException; + +/** + * Provides a trait for local libraries utilizing a stream wrapper. + * + * It assumes that the library files can be accessed using a specified stream + * wrapper and that the first component of the file URIs are the library IDs. + * Thus, file URIs are of the form: + * stream-wrapper-scheme://library-id/path/to/file/within/the/library/filename + * + * This trait should only be used by classes implementing LocalLibraryInterface. + * + * @see \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface + */ +trait LocalLibraryTrait { + + /** + * Whether or not the library is installed. + * + * A library being installed means its files can be found on the filesystem. + * + * @var bool + */ + protected $installed = FALSE; + + /** + * The local path to the library relative to the app root. + * + * @var string + */ + protected $localPath; + + /** + * Checks whether the library is installed. + * + * @return bool + * TRUE if the library is installed; FALSE otherwise; + * + * @see \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface::isInstalled() + */ + public function isInstalled() { + return $this->installed; + } + + /** + * Marks the library as uninstalled. + * + * A corresponding method to mark the library as installed is not provided as + * an installed library should have a library path, so that + * LocalLibraryInterface::setLibraryPath() can be used instead. + * + * @return $this + * + * @see \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface::setUninstalled() + */ + public function setUninstalled() { + $this->installed = FALSE; + return $this; + } + + /** + * Gets the path to the library. + * + * @return string + * The path to the library relative to the app root. + * + * @throws \Drupal\libraries\ExternalLibrary\Exception\LibraryNotInstalledException + * + * @see \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface::getLocalPath() + */ + public function getLocalPath() { + if (!$this->isInstalled()) { + /** @var \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface $this */ + throw new LibraryNotInstalledException($this); + } + + return $this->localPath; + } + + /** + * Sets the library path of the library. + * + * @param string $path + * The path to the library. + * + * @see \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface::getLocalPath() + */ + public function setLocalPath($path) { + $this->installed = TRUE; + $this->localPath = (string) $path; + + assert('$this->localPath !== ""'); + assert('$this->localPath[0] !== "/"'); + } + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Local/LocatorInterface.php b/web/modules/libraries/src/ExternalLibrary/Local/LocatorInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..a1be89440c07d4f540de456b67f0cede16a4ff5d --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Local/LocatorInterface.php @@ -0,0 +1,22 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Local; + +/** + * Provides an interface for library locators. + * + * Because determining the installation status and library path of a library + * is not specific to any library or even any library type, this logic can be + * implemented generically in form of a locator. + */ +interface LocatorInterface { + + /** + * Locates a library. + * + * @param \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface $library + * The library to locate. + */ + public function locate(LocalLibraryInterface $library); + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Local/LocatorManager.php b/web/modules/libraries/src/ExternalLibrary/Local/LocatorManager.php new file mode 100644 index 0000000000000000000000000000000000000000..2f377ff06542a1e2b90e06a4a9d0946beb804fd9 --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Local/LocatorManager.php @@ -0,0 +1,34 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Local; + +use Drupal\Core\Cache\CacheBackendInterface; +use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Plugin\DefaultPluginManager; +use Drupal\libraries\Annotation\Locator; + +/** + * Provides a plugin manager for library locator plugins. + * + * @see \Drupal\libraries\ExternalLibrary\Local\LocatorInterface + */ +class LocatorManager extends DefaultPluginManager { + + /** + * Constructs a locator manager. + * + * @param \Traversable $namespaces + * An object that implements \Traversable which contains the root paths + * keyed by the corresponding namespace to look for plugin implementations. + * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend + * Cache backend instance to use. + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * The module handler to invoke the alter hook with. + */ + public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) { + parent::__construct('Plugin/libraries/Locator', $namespaces, $module_handler, LocatorInterface::class, Locator::class); + $this->alterInfo('libraries_locator_info'); + $this->setCacheBackend($cache_backend, 'libraries_locator_info'); + } + +} diff --git a/web/modules/libraries/src/ExternalLibrary/PhpFile/PhpFileLibrary.php b/web/modules/libraries/src/ExternalLibrary/PhpFile/PhpFileLibrary.php new file mode 100644 index 0000000000000000000000000000000000000000..bf3c68328c205225920ebb79e6770d0f6e7b88fd --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/PhpFile/PhpFileLibrary.php @@ -0,0 +1,73 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\PhpFile; + +use Drupal\Component\Plugin\Factory\FactoryInterface; +use Drupal\libraries\ExternalLibrary\Exception\LibraryNotInstalledException; +use Drupal\libraries\ExternalLibrary\LibraryBase; +use Drupal\libraries\ExternalLibrary\Local\LocalLibraryTrait; +use Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface; + +/** + * Provides a base PHP file library implementation. + */ +class PhpFileLibrary extends LibraryBase implements PhpFileLibraryInterface { + + use LocalLibraryTrait; + + /** + * An array of PHP files for this library. + * + * @var array + */ + protected $files = []; + + /** + * Constructs a PHP file library. + * + * @param string $id + * The library ID. + * @param array $definition + * The library definition array. + * @param \Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface $type + * The library type of this library. + */ + public function __construct($id, array $definition, LibraryTypeInterface $type) { + parent::__construct($id, $definition, $type); + $this->files = $definition['files']; + } + + /** + * {@inheritdoc} + */ + protected static function processDefinition(array &$definition) { + parent::processDefinition($definition); + $definition += [ + 'files' => [], + ]; + } + + /** + * {@inheritdoc} + */ + public function getPhpFiles() { + if (!$this->isInstalled()) { + throw new LibraryNotInstalledException($this); + } + + $processed_files = []; + foreach ($this->files as $file) { + $processed_files[] = $this->getLocalPath() . '/' . $file; + } + return $processed_files; + } + + /** + * {@inheritdoc} + */ + public function getLocator(FactoryInterface $locator_factory) { + // @todo Consider refining the stream wrapper used here. + return $locator_factory->createInstance('uri', ['uri' => 'php-file://']); + } + +} diff --git a/web/modules/libraries/src/ExternalLibrary/PhpFile/PhpFileLibraryInterface.php b/web/modules/libraries/src/ExternalLibrary/PhpFile/PhpFileLibraryInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..01ca453713e55045af6947fcb5f39c86c15540fe --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/PhpFile/PhpFileLibraryInterface.php @@ -0,0 +1,22 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\PhpFile; + +use Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface; + +/** + * Provides an interface for libraries with PHP files. + * + * @see \Drupal\libraries\ExternalLibrary\PhpFile\PhpFileLoaderInterface + */ +interface PhpFileLibraryInterface extends LocalLibraryInterface { + + /** + * Returns the PHP files of this library. + * + * @return string[] + * An array of absolute file paths of PHP files. + */ + public function getPhpFiles(); + +} diff --git a/web/modules/libraries/src/ExternalLibrary/PhpFile/PhpFileLoaderInterface.php b/web/modules/libraries/src/ExternalLibrary/PhpFile/PhpFileLoaderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..52c1c620f5182ab6baa8d754bb756f1c2a087312 --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/PhpFile/PhpFileLoaderInterface.php @@ -0,0 +1,20 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\PhpFile; + +/** + * Provides an interface for PHP file loaders. + * + * @see \Drupal\libraries\ExternalLibrary\PhpFile\PhpFileLibraryInterface + */ +interface PhpFileLoaderInterface { + + /** + * Loads a PHP file. + * + * @param string $file + * The absolute file path to the PHP file. + */ + public function load($file); + +} diff --git a/web/modules/libraries/src/ExternalLibrary/PhpFile/PhpRequireLoader.php b/web/modules/libraries/src/ExternalLibrary/PhpFile/PhpRequireLoader.php new file mode 100644 index 0000000000000000000000000000000000000000..ef6b695f64ae3aa1a6adc04e3104e0938b59e571 --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/PhpFile/PhpRequireLoader.php @@ -0,0 +1,22 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\PhpFile; + +/** + * Provides a PHP file loader using PHP's require_once. + * + * @todo Provide a separate PhpIncludeOnceLoader. + */ +class PhpRequireLoader implements PhpFileLoaderInterface { + + /** + * {@inheritdoc} + */ + public function load($file) { + // @todo Because libraries cannot be loaded twice it should be possible to + // use 'require' instead of 'require_once'. + /** @noinspection PhpIncludeInspection */ + require_once $file; + } + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Remote/RemoteLibraryInterface.php b/web/modules/libraries/src/ExternalLibrary/Remote/RemoteLibraryInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..1fe7016ad020c17ec0081bd1eef48747aa389218 --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Remote/RemoteLibraryInterface.php @@ -0,0 +1,39 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Remote; + +use Drupal\libraries\ExternalLibrary\LibraryInterface; + +/** + * Provides an interface for remote libraries. + * + * Assuming they declare a remote URL, remote libraries are always loaded. It is + * not checked whether or not the Drupal site has network access or the remote + * resource is available. + */ +interface RemoteLibraryInterface extends LibraryInterface { + + /** + * Checks whether the library has a remote URL. + * + * This check allows using the same library class for multiple libraries only + * some of which are available remotely. + * + * @return bool + * TRUE if the library has a remote URL; FALSE otherwise. + * + * @see \Drupal\libraries\ExternalLibrary\Asset\AssetLibraryInterface + */ + public function hasRemoteUrl(); + + /** + * Returns the remote URL of the library. + * + * @return string + * The remote URL of the library. + * + * @todo Consider throwing an exception if hasRemoteUrl() return FALSE. + */ + public function getRemoteUrl(); + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Remote/RemoteLibraryTrait.php b/web/modules/libraries/src/ExternalLibrary/Remote/RemoteLibraryTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..d55f3c23891f4f5cb6922889bb5511ef21d4abba --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Remote/RemoteLibraryTrait.php @@ -0,0 +1,44 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Remote; + +/** + * Provides a trait for remote libraries. + */ +trait RemoteLibraryTrait { + + /** + * The remote library URL. + * + * @var string + */ + protected $remoteUrl; + + /** + * Checks whether the library has a remote URL. + * + * This check allows using the same library class for multiple libraries only + * some of which are available remotely. + * + * @return bool + * TRUE if the library has a remote URL; FALSE otherwise. + * + * @see \Drupal\libraries\ExternalLibrary\Remote\RemoteLibraryInterface::hasRemoteUrl() + */ + public function hasRemoteUrl() { + return !empty($this->remoteUrl); + } + + /** + * Returns the remote URL of the library. + * + * @return string + * The remote URL of the library. + * + * \Drupal\libraries\ExternalLibrary\Remote\RemoteLibraryInterface::getRemoteUrl() + */ + public function getRemoteUrl() { + return $this->remoteUrl; + } + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Type/LibraryCreationListenerInterface.php b/web/modules/libraries/src/ExternalLibrary/Type/LibraryCreationListenerInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..e86115f26e8ccbad23091abb5f6299ccb0de3bec --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Type/LibraryCreationListenerInterface.php @@ -0,0 +1,20 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Type; + +use Drupal\libraries\ExternalLibrary\LibraryInterface; + +/** + * An interface for library types that want to react to library instantiation. + */ +interface LibraryCreationListenerInterface { + + /** + * Reacts to the instantiation of a library. + * + * @param \Drupal\libraries\ExternalLibrary\LibraryInterface $library + * The library that is being instantiated. + */ + public function onLibraryCreate(LibraryInterface $library); + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Type/LibraryLoadingListenerInterface.php b/web/modules/libraries/src/ExternalLibrary/Type/LibraryLoadingListenerInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..962e9dbb1e151c7478d9ebebb814f85224d5d795 --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Type/LibraryLoadingListenerInterface.php @@ -0,0 +1,20 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Type; + +use Drupal\libraries\ExternalLibrary\LibraryInterface; + +/** + * An interface for library types that want to react to library instantiation. + */ +interface LibraryLoadingListenerInterface { + + /** + * Reacts to the instantiation of a library. + * + * @param \Drupal\libraries\ExternalLibrary\LibraryInterface $library + * The library that is being instantiated. + */ + public function onLibraryLoad(LibraryInterface $library); + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Type/LibraryTypeBase.php b/web/modules/libraries/src/ExternalLibrary/Type/LibraryTypeBase.php new file mode 100644 index 0000000000000000000000000000000000000000..6cd9f59d099c8b3d9f88dca4b27297088b595efe --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Type/LibraryTypeBase.php @@ -0,0 +1,87 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Type; + +use Drupal\Component\Plugin\Factory\FactoryInterface; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\libraries\ExternalLibrary\LibraryInterface; +use Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface; +use Drupal\libraries\ExternalLibrary\Utility\IdAccessorTrait; +use Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Provides a base class for library types. + */ +abstract class LibraryTypeBase implements + LibraryTypeInterface, + LibraryCreationListenerInterface, + ContainerFactoryPluginInterface +{ + + use IdAccessorTrait; + + /** + * The locator factory. + * + * @var \Drupal\Component\Plugin\Factory\FactoryInterface + */ + protected $locatorFactory; + + /** + * The version detector factory. + * + * @var \Drupal\Component\Plugin\Factory\FactoryInterface + */ + protected $detectorFactory; + + /** + * Constructs the asset library type. + * + * @param string $plugin_id + * The plugin ID taken from the class annotation. + * @param \Drupal\Component\Plugin\Factory\FactoryInterface $locator_factory + * The locator factory. + * @param \Drupal\Component\Plugin\Factory\FactoryInterface $detector_factory + * The version detector factory. + */ + public function __construct($plugin_id, FactoryInterface $locator_factory, FactoryInterface $detector_factory) { + $this->id = $plugin_id; + $this->locatorFactory = $locator_factory; + $this->detectorFactory = $detector_factory; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $plugin_id, + $container->get('plugin.manager.libraries.locator'), + $container->get('plugin.manager.libraries.version_detector') + ); + } + + /** + * {@inheritdoc} + */ + public function onLibraryCreate(LibraryInterface $library) { + if ($library instanceof LocalLibraryInterface) { + $library->getLocator($this->locatorFactory)->locate($library); + // Fallback on global locators. + // @todo Consider if global locators should be checked as a fallback or as + // the primary locator source. + if (!$library->isInstalled()) { + $this->locatorFactory->createInstance('global')->locate($library); + } + // Also fetch version information. + if ($library instanceof VersionedLibraryInterface) { + // @todo Consider if this should be wrapped in some conditional logic + // or exception handling so that version detection errors do not + // prevent a library from being loaded. + $library->getVersionDetector($this->detectorFactory)->detectVersion($library); + } + } + } + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Type/LibraryTypeFactory.php b/web/modules/libraries/src/ExternalLibrary/Type/LibraryTypeFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..6689f51b8734b56538bd2e12edf6fa68cc6159dd --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Type/LibraryTypeFactory.php @@ -0,0 +1,32 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Type; + +use Drupal\Core\Cache\CacheBackendInterface; +use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Plugin\DefaultPluginManager; +use Drupal\libraries\Annotation\LibraryType; + +/** + * Provides a plugin manager for library type plugins. + */ +class LibraryTypeFactory extends DefaultPluginManager { + + /** + * Constructs a locator manager. + * + * @param \Traversable $namespaces + * An object that implements \Traversable which contains the root paths + * keyed by the corresponding namespace to look for plugin implementations. + * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend + * Cache backend instance to use. + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * The module handler to invoke the alter hook with. + */ + public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) { + parent::__construct('Plugin/libraries/Type', $namespaces, $module_handler, LibraryTypeInterface::class, LibraryType::class); + $this->alterInfo('libraries_library_type_info'); + $this->setCacheBackend($cache_backend, 'libraries_library_type_info'); + } + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Type/LibraryTypeInterface.php b/web/modules/libraries/src/ExternalLibrary/Type/LibraryTypeInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..d6537fb370f7b4b58afdba199e9a031e30aee8cd --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Type/LibraryTypeInterface.php @@ -0,0 +1,28 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Type; + +/** + * Provides an interface for library types. + */ +interface LibraryTypeInterface { + + /** + * Returns the ID of the library type. + * + * @return string + * The library type ID. + */ + public function getId(); + + /** + * Returns the class used for libraries of this type. + * + * @return string|\Drupal\libraries\ExternalLibrary\LibraryInterface + * The library class for this library type. + * + * @todo Consider adding a getLibraryInterface() method, as well. + */ + public function getLibraryClass(); + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Utility/DependencyAccessorTrait.php b/web/modules/libraries/src/ExternalLibrary/Utility/DependencyAccessorTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..3ec56fee7306660996ee4bf29894ade02ab78397 --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Utility/DependencyAccessorTrait.php @@ -0,0 +1,27 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Utility; + +/** + * Provides a trait for classes giving access to a library dependency. + */ +trait DependencyAccessorTrait { + + /** + * The dependency. + * + * @var \Drupal\libraries\ExternalLibrary\LibraryInterface + */ + protected $dependency; + + /** + * Returns the dependency. + * + * @return \Drupal\libraries\ExternalLibrary\LibraryInterface + * The library. + */ + public function getLibrary() { + return $this->dependency; + } + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Utility/IdAccessorTrait.php b/web/modules/libraries/src/ExternalLibrary/Utility/IdAccessorTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..34476f2cc4cdae7429eade5694b6214a214b3fe5 --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Utility/IdAccessorTrait.php @@ -0,0 +1,30 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Utility; + +/** + * Provides a trait for classes that have a string identifier. + */ +trait IdAccessorTrait { + + /** + * The ID. + * + * @var string + */ + protected $id; + + /** + * Returns the ID. + * + * @return string + * The ID. + * + * @see \Drupal\libraries\ExternalLibrary\LibraryInterface::getId() + * @see \Drupal\libraries\ExternalLibrary\LibraryType\LibraryTypeInterface::getId() + */ + public function getId() { + return $this->id; + } + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Utility/LibraryAccessorInterface.php b/web/modules/libraries/src/ExternalLibrary/Utility/LibraryAccessorInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..72b0093c4dcb732ae8f4d21aa03a320408cd9e22 --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Utility/LibraryAccessorInterface.php @@ -0,0 +1,18 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Utility; + +/** + * Provides an interface for classes giving access to a library. + */ +interface LibraryAccessorInterface { + + /** + * Returns the library. + * + * @return \Drupal\libraries\ExternalLibrary\LibraryInterface + * The library. + */ + public function getLibrary(); + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Utility/LibraryAccessorTrait.php b/web/modules/libraries/src/ExternalLibrary/Utility/LibraryAccessorTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..f8ad92037d479a4167414fb601581151a155fad3 --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Utility/LibraryAccessorTrait.php @@ -0,0 +1,27 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Utility; + +/** + * Provides a trait for classes giving access to a library. + */ +trait LibraryAccessorTrait { + + /** + * The library. + * + * @var \Drupal\libraries\ExternalLibrary\LibraryInterface + */ + protected $library; + + /** + * Returns the library. + * + * @return \Drupal\libraries\ExternalLibrary\LibraryInterface + * The library. + */ + public function getLibrary() { + return $this->library; + } + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Utility/LibraryIdAccessorInterface.php b/web/modules/libraries/src/ExternalLibrary/Utility/LibraryIdAccessorInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..98928e9deccd67a28b7734199456885bf43087c0 --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Utility/LibraryIdAccessorInterface.php @@ -0,0 +1,18 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Utility; + +/** + * Provides an interface for classes giving access to a library ID. + */ +interface LibraryAccessorIdInterface { + + /** + * Returns the ID of the library. + * + * @return string + * The library ID. + */ + public function getLibraryId(); + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Utility/LibraryIdAccessorTrait.php b/web/modules/libraries/src/ExternalLibrary/Utility/LibraryIdAccessorTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..8ec901f534432cc6f2f2e7ed42f5a653ddb4b243 --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Utility/LibraryIdAccessorTrait.php @@ -0,0 +1,27 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Utility; + +/** + * Provides a trait for classes giving access to a library ID. + */ +trait LibraryIdAccessorTrait { + + /** + * The ID of the library. + * + * @var string + */ + protected $libraryId; + + /** + * Returns the ID of the library. + * + * @return string + * The library ID. + */ + public function getLibraryId() { + return $this->libraryId; + } + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Version/VersionDetectorInterface.php b/web/modules/libraries/src/ExternalLibrary/Version/VersionDetectorInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..8b6d2a5bd04a2661ce569aeca00e78ae3d635295 --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Version/VersionDetectorInterface.php @@ -0,0 +1,25 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Version; + +/** + * Provides an interface for version detectors. + * + * @ingroup libraries + */ +interface VersionDetectorInterface { + + /** + * Detects the version of a library. + * + * @param \Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface $library + * The library whose version to detect. + * + * @throws \Drupal\libraries\ExternalLibrary\Exception\UnknownLibraryVersionException + * + * @todo Provide a mechanism for version detectors to provide a reason for + * failing. + */ + public function detectVersion(VersionedLibraryInterface $library); + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Version/VersionDetectorManager.php b/web/modules/libraries/src/ExternalLibrary/Version/VersionDetectorManager.php new file mode 100644 index 0000000000000000000000000000000000000000..6272c5a263c8b5fb7a5ec6e32a7e052d873187d9 --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Version/VersionDetectorManager.php @@ -0,0 +1,34 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Version; + +use Drupal\Core\Cache\CacheBackendInterface; +use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Plugin\DefaultPluginManager; +use Drupal\libraries\Annotation\VersionDetector; + +/** + * Provides a plugin manager for library version detector plugins. + * + * @see \Drupal\libraries\ExternalLibrary\Version\VersionDetectorInterface + */ +class VersionDetectorManager extends DefaultPluginManager { + + /** + * Constructs a version detector manager. + * + * @param \Traversable $namespaces + * An object that implements \Traversable which contains the root paths + * keyed by the corresponding namespace to look for plugin implementations. + * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend + * Cache backend instance to use. + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * The module handler to invoke the alter hook with. + */ + public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) { + parent::__construct('Plugin/libraries/VersionDetector', $namespaces, $module_handler, VersionDetectorInterface::class, VersionDetector::class); + $this->alterInfo('libraries_version_detector_info'); + $this->setCacheBackend($cache_backend, 'libraries_version_detector_info'); + } + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Version/VersionedLibraryInterface.php b/web/modules/libraries/src/ExternalLibrary/Version/VersionedLibraryInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..abeb758cd2974a3a2a50b94d1eef673c54a86d95 --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Version/VersionedLibraryInterface.php @@ -0,0 +1,65 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Version; + +use Drupal\Component\Plugin\Factory\FactoryInterface; +use Drupal\libraries\ExternalLibrary\LibraryInterface; + +/** + * Provides an interface for versioned libraries. + * + * Version detection and negotiation is a key aspect of Libraries API's + * functionality so most libraries should implement this interface. In theory, + * however, it might be possible for the same library to be available in + * multiple versions and, for example, different versions being loaded on + * different pages. In this case, a simple getVersion() method, does not make + * sense. To support such advanced version detection behavior in the future or + * in a separate module, version detection is split into a separate interface. + * + * @ingroup libraries + * + * @todo Support versioned metadata, i.e. different library file names or + * locations for different library versions. + */ +interface VersionedLibraryInterface extends LibraryInterface { + + /** + * Gets the version of the library. + * + * @return string + * The version string, for example 1.0, 2.1.4, or 3.0.0-alpha5. + * + * @throws \Drupal\libraries\ExternalLibrary\Exception\UnknownLibraryVersionException + * + * @see \Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface::setVersion() + */ + public function getVersion(); + + /** + * Sets the version of the library. + * + * @param string $version + * The version of the library. + * + * @reutrn $this + * + * @see \Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface::getVersion() + */ + public function setVersion($version); + + /** + * Gets the version detector of this library using the detector factory. + * + * Because determining the installation version of a library is not specific + * to any library or even any library type, this logic is offloaded to + * separate detector objects. + * + * @param \Drupal\Component\Plugin\Factory\FactoryInterface $detector_factory + * + * @return \Drupal\libraries\ExternalLibrary\Version\VersionDetectorInterface + * + * @see \Drupal\libraries\ExternalLibrary\Version\VersionDetectorInterface + */ + public function getVersionDetector(FactoryInterface $detector_factory); + +} diff --git a/web/modules/libraries/src/ExternalLibrary/Version/VersionedLibraryTrait.php b/web/modules/libraries/src/ExternalLibrary/Version/VersionedLibraryTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..2404a109ca29f6506672b3c9da9d8a1fd86bcb4e --- /dev/null +++ b/web/modules/libraries/src/ExternalLibrary/Version/VersionedLibraryTrait.php @@ -0,0 +1,85 @@ +<?php + +namespace Drupal\libraries\ExternalLibrary\Version; + +use Drupal\Component\Plugin\Factory\FactoryInterface; +use Drupal\libraries\ExternalLibrary\Exception\UnknownLibraryVersionException; + +/** + * Provides a trait for versioned libraries. + * + * @see \Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface + */ +trait VersionedLibraryTrait { + + /** + * The library version. + * + * @var string + */ + protected $version; + + /** + * Information about the version detector to use fo rthis library. + * + * Contains the following keys: + * id: The plugin ID of the version detector. + * configuration: The plugin configuration of the version detector. + * + * @var array + */ + protected $versionDetector = [ + 'id' => NULL, + 'configuration' => [], + ]; + + /** + * Gets the version of the library. + * + * @return string + * The version string, for example 1.0, 2.1.4, or 3.0.0-alpha5. + * + * @throws \Drupal\libraries\ExternalLibrary\Exception\UnknownLibraryVersionException + * + * @see \Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface::getVersion() + */ + public function getVersion() { + if (!isset($this->version)) { + throw new UnknownLibraryVersionException($this); + } + return $this->version; + } + + /** + * Sets the version of the library. + * + * @param string $version + * The version of the library. + * + * @return $this + * + * @see \Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface::setVersion() + */ + public function setVersion($version) { + $this->version = (string) $version; + return $this; + } + + /** + * Gets the version detector of this library using the detector factory. + * + * Because determining the installation version of a library is not specific + * to any library or even any library type, this logic is offloaded to + * separate detector objects. + * + * @param \Drupal\Component\Plugin\Factory\FactoryInterface $detector_factory + * + * @return \Drupal\libraries\ExternalLibrary\Version\VersionDetectorInterface + * + * @see \Drupal\libraries\ExternalLibrary\Version\VersionDetectorInterface + */ + public function getVersionDetector(FactoryInterface $detector_factory) { + return $detector_factory->createInstance($this->versionDetector['id'], $this->versionDetector['configuration']); + } + +} diff --git a/web/modules/libraries/src/Plugin/MissingPluginConfigurationException.php b/web/modules/libraries/src/Plugin/MissingPluginConfigurationException.php new file mode 100644 index 0000000000000000000000000000000000000000..515f133405ffb4e29ab8607c74e0eb288bc43af4 --- /dev/null +++ b/web/modules/libraries/src/Plugin/MissingPluginConfigurationException.php @@ -0,0 +1,50 @@ +<?php + +namespace Drupal\libraries\Plugin; + +use Drupal\Component\Plugin\Exception\PluginException; + +/** + * Provides an exception class for missing plugin configuration. + * + * The plugin system allows passing arbitrary data to plugins in form of the + * $configuration array. Some plugins, however, may depend on certain keys to + * be present in $configuration. This exception class can be used if such keys + * are missing. + * + * @todo Provide accessors for the passed-in information. + */ +class MissingPluginConfigurationException extends PluginException { + + /** + * Constructs an exception for a missing plugin configuration value. + * + * @param string $plugin_id + * The plugin ID. + * @param $plugin_definition + * The plugin definition + * @param array $configuration + * The plugin configuration. + * @param $missing_key + * The missing key in the configuration. + * @param string $message + * (optional) The exception message. + * @param int $code + * (optional) The error code. + * @param \Exception $previous + * (optional) The previous exception. + */ + public function __construct( + $plugin_id, + $plugin_definition, + array $configuration, + $missing_key, + $message = '', + $code = 0, + \Exception $previous = NULL + ) { + $message = $message ?: "The '{$missing_key}' key is missing in the configuration of the '{$plugin_id}' plugin."; + parent::__construct($message, $code, $previous); + } + +} diff --git a/web/modules/libraries/src/Plugin/libraries/Locator/ChainLocator.php b/web/modules/libraries/src/Plugin/libraries/Locator/ChainLocator.php new file mode 100644 index 0000000000000000000000000000000000000000..4e0acff84e1630c44e5552e2114e32b7eb9538de --- /dev/null +++ b/web/modules/libraries/src/Plugin/libraries/Locator/ChainLocator.php @@ -0,0 +1,53 @@ +<?php + +namespace Drupal\libraries\Plugin\libraries\Locator; + +use Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface; +use Drupal\libraries\ExternalLibrary\Local\LocatorInterface; + +/** + * Provides a locator utilizing a chain of other individual locators. + * + * @Locator("chain") + * + * @see \Drupal\libraries\ExternalLibrary\Local\LocatorInterface + */ +class ChainLocator implements LocatorInterface { + + /** + * The locators to check. + * + * @var \Drupal\libraries\ExternalLibrary\Local\LocatorInterface[] + */ + protected $locators = []; + + /** + * Add a locator to the chain. + * + * @param \Drupal\libraries\ExternalLibrary\Local\LocatorInterface $locator + * A locator to add to the chain. + */ + public function addLocator(LocatorInterface $locator) { + $this->locators[] = $locator; + return $this; + } + + /** + * Locates a library. + * + * @param \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface $library + * The library to locate. + * + * @see \Drupal\libraries\ExternalLibrary\Local\LocatorInterface::locate() + */ + public function locate(LocalLibraryInterface $library) { + foreach ($this->locators as $locator) { + $locator->locate($library); + if ($library->isInstalled()) { + return; + } + } + $library->setUninstalled(); + } + +} diff --git a/web/modules/libraries/src/Plugin/libraries/Locator/GlobalLocator.php b/web/modules/libraries/src/Plugin/libraries/Locator/GlobalLocator.php new file mode 100644 index 0000000000000000000000000000000000000000..3501338d4f66133e9c2d49ecb32ec9b70ef45650 --- /dev/null +++ b/web/modules/libraries/src/Plugin/libraries/Locator/GlobalLocator.php @@ -0,0 +1,76 @@ +<?php + +namespace Drupal\libraries\Plugin\libraries\Locator; + +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Component\Plugin\Factory\FactoryInterface; +use Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface; +use Drupal\libraries\ExternalLibrary\Local\LocatorInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Provides a locator based on global configuration. + * + * @Locator("global") + * + * @see \Drupal\libraries\ExternalLibrary\Local\LocatorInterface + */ +class GlobalLocator implements LocatorInterface, ContainerFactoryPluginInterface { + + /** + * The Drupal config factory service. + * + * @var \Drupal\Core\Config\ConfigFactoryInterface + */ + protected $configFactory; + + /** + * The locator factory. + * + * @var \Drupal\Component\Plugin\Factory\FactoryInterface + */ + protected $locatorFactory; + + /** + * Constructs a global locator. + * + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The Drupal config factory service. + * @param \Drupal\Component\Plugin\Factory\FactoryInterface $locator_factory + * The locator factory. + */ + public function __construct(ConfigFactoryInterface $config_factory, FactoryInterface $locator_factory) { + $this->configFactory = $config_factory; + $this->locatorFactory = $locator_factory; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $container->get('config.factory'), + $container->get('plugin.manager.libraries.locator') + ); + } + + /** + * Locates a library. + * + * @param \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface $library + * The library to locate. + * + * @see \Drupal\libraries\ExternalLibrary\Local\LocatorInterface::locate() + */ + public function locate(LocalLibraryInterface $library) { + foreach ($this->configFactory->get('libraries.settings')->get('global_locators') as $locator) { + $this->locatorFactory->createInstance($locator['id'], $locator['configuration'])->locate($library); + if ($library->isInstalled()) { + return; + } + } + $library->setUninstalled(); + } + +} diff --git a/web/modules/libraries/src/Plugin/libraries/Locator/UriLocator.php b/web/modules/libraries/src/Plugin/libraries/Locator/UriLocator.php new file mode 100644 index 0000000000000000000000000000000000000000..348be0c247c8aec40cbba68e28c132437b9c0938 --- /dev/null +++ b/web/modules/libraries/src/Plugin/libraries/Locator/UriLocator.php @@ -0,0 +1,89 @@ +<?php + +namespace Drupal\libraries\Plugin\libraries\Locator; + +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface; +use Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface; +use Drupal\libraries\ExternalLibrary\Local\LocatorInterface; +use Drupal\libraries\Plugin\MissingPluginConfigurationException; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Provides a locator utilizing a URI. + * + * It makes the following assumptions: + * - The library files can be accessed using a specified stream. + * - The stream wrapper is local (i.e. it is a subclass of + * \Drupal\Core\StreamWrapper\LocalStream). + * - The first component of the file URIs are the library IDs (i.e. file URIs + * are of the form: scheme://library-id/path/to/file/filename). + * + * @Locator("uri") + * + * @see \Drupal\libraries\ExternalLibrary\Local\LocatorInterface + */ +class UriLocator implements LocatorInterface, ContainerFactoryPluginInterface { + + /** + * The stream wrapper manager. + * + * @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface + */ + protected $streamWrapperManager; + + /** + * The URI to check. + * + * @var string + */ + protected $uri; + + /** + * Constructs a URI locator. + * + * @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager + * The stream wrapper manager. + * @param string $uri + * The URI to check. + */ + public function __construct(StreamWrapperManagerInterface $stream_wrapper_manager, $uri) { + $this->streamWrapperManager = $stream_wrapper_manager; + $this->uri = (string) $uri; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + if (!isset($configuration['uri'])) { + throw new MissingPluginConfigurationException($plugin_id, $plugin_definition, $configuration, 'uri'); + } + return new static($container->get('stream_wrapper_manager'), $configuration['uri']); + } + + /** + * Locates a library. + * + * @param \Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface $library + * The library to locate. + * + * @see \Drupal\libraries\ExternalLibrary\Local\LocatorInterface::locate() + */ + public function locate(LocalLibraryInterface $library) { + /** @var \Drupal\Core\StreamWrapper\LocalStream $stream_wrapper */ + $stream_wrapper = $this->streamWrapperManager->getViaUri($this->uri); + assert('$stream_wrapper instanceof \Drupal\Core\StreamWrapper\LocalStream'); + // Calling LocalStream::getDirectoryPath() explicitly avoids the realpath() + // usage in LocalStream::getLocalPath(), which breaks if Libraries API is + // symbolically linked into the Drupal installation. + list($scheme, $target) = explode('://', $this->uri, 2); + $base_path = str_replace('//', '/', $stream_wrapper->getDirectoryPath() . '/' . $target . '/' . $library->getId()); + if (is_dir($base_path) && is_readable($base_path)) { + $library->setLocalPath($base_path); + return; + } + $library->setUninstalled(); + } + +} diff --git a/web/modules/libraries/src/Plugin/libraries/Type/AssetLibraryType.php b/web/modules/libraries/src/Plugin/libraries/Type/AssetLibraryType.php new file mode 100644 index 0000000000000000000000000000000000000000..ce0b276b17eb58aa46789392ae6eb6fb92e1c866 --- /dev/null +++ b/web/modules/libraries/src/Plugin/libraries/Type/AssetLibraryType.php @@ -0,0 +1,32 @@ +<?php + +namespace Drupal\libraries\Plugin\libraries\Type; + +use Drupal\libraries\ExternalLibrary\Asset\AssetLibrary; +use Drupal\libraries\ExternalLibrary\Asset\AttachableAssetLibraryRegistrationInterface; +use Drupal\libraries\ExternalLibrary\LibraryInterface; +use Drupal\libraries\ExternalLibrary\LibraryManagerInterface; +use Drupal\libraries\ExternalLibrary\Type\LibraryTypeBase; + +/** + * @LibraryType("asset") + */ +class AssetLibraryType extends LibraryTypeBase implements AttachableAssetLibraryRegistrationInterface { + + /** + * {@inheritdoc} + */ + public function getLibraryClass() { + return AssetLibrary::class; + } + + /** + * {@inheritdoc} + */ + public function getAttachableAssetLibraries(LibraryInterface $library, LibraryManagerInterface $library_manager) { + assert('$library instanceof \Drupal\libraries\ExternalLibrary\Asset\AssetLibraryInterface'); + /** @var \Drupal\libraries\ExternalLibrary\Asset\AssetLibraryInterface $library */ + return [$library->getId() => $library->getAttachableAssetLibrary($library_manager)]; + } + +} diff --git a/web/modules/libraries/src/Plugin/libraries/Type/MultipleAssetLibraryType.php b/web/modules/libraries/src/Plugin/libraries/Type/MultipleAssetLibraryType.php new file mode 100644 index 0000000000000000000000000000000000000000..31ec6a62c6c41aace30735496788f4f85118900d --- /dev/null +++ b/web/modules/libraries/src/Plugin/libraries/Type/MultipleAssetLibraryType.php @@ -0,0 +1,48 @@ +<?php + +namespace Drupal\libraries\Plugin\libraries\Type; + +use Drupal\libraries\ExternalLibrary\Asset\AttachableAssetLibraryRegistrationInterface; +use Drupal\libraries\ExternalLibrary\Asset\MultipleAssetLibrary; +use Drupal\libraries\ExternalLibrary\Asset\MultipleAssetLibraryInterface; +use Drupal\libraries\ExternalLibrary\LibraryInterface; +use Drupal\libraries\ExternalLibrary\LibraryManagerInterface; +use Drupal\libraries\ExternalLibrary\Type\LibraryTypeBase; + +/** + * @LibraryType("asset_multiple") + */ +class MultipleAssetLibraryType extends LibraryTypeBase implements AttachableAssetLibraryRegistrationInterface { + + /** + * {@inheritdoc} + */ + public function getLibraryClass() { + return MultipleAssetLibrary::class; + } + + /** + * {@inheritdoc} + */ + public function getAttachableAssetLibraries(LibraryInterface $external_library, LibraryManagerInterface $library_manager) { + assert('$external_library instanceof \Drupal\libraries\ExternalLibrary\Asset\MultipleAssetLibraryInterface'); + /** @var \Drupal\libraries\ExternalLibrary\Asset\MultipleAssetLibraryInterface $external_library */ + $attachable_libraries = []; + foreach ($external_library->getAttachableAssetLibraries($library_manager) as $component_name => $attachable_library) { + $attachable_library_id = $this->getAttachableLibraryId($external_library, $component_name); + $attachable_libraries[$attachable_library_id] = $attachable_library; + } + return $attachable_libraries; + } + + /** + * @param \Drupal\libraries\ExternalLibrary\LibraryInterface $external_library + * @param string $component_name + * + * @return string + */ + protected function getAttachableLibraryId(LibraryInterface $external_library, $component_name) { + return $external_library->getId() . MultipleAssetLibraryInterface::SEPARATOR . $component_name; + } + +} diff --git a/web/modules/libraries/src/Plugin/libraries/Type/PhpFileLibraryType.php b/web/modules/libraries/src/Plugin/libraries/Type/PhpFileLibraryType.php new file mode 100644 index 0000000000000000000000000000000000000000..aa93b003c2976c84935cb04afb770a8b88c22d33 --- /dev/null +++ b/web/modules/libraries/src/Plugin/libraries/Type/PhpFileLibraryType.php @@ -0,0 +1,72 @@ +<?php + +namespace Drupal\libraries\Plugin\libraries\Type; + +use Drupal\Component\Plugin\Factory\FactoryInterface; +use Drupal\libraries\ExternalLibrary\LibraryInterface; +use Drupal\libraries\ExternalLibrary\Type\LibraryLoadingListenerInterface; +use Drupal\libraries\ExternalLibrary\PhpFile\PhpFileLibrary; +use Drupal\libraries\ExternalLibrary\PhpFile\PhpFileLoaderInterface; +use Drupal\libraries\ExternalLibrary\Type\LibraryTypeBase; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * @LibraryType("php_file") + */ +class PhpFileLibraryType extends LibraryTypeBase implements LibraryLoadingListenerInterface { + + /** + * The PHP file loader. + * + * @var \Drupal\libraries\ExternalLibrary\PhpFile\PhpFileLoaderInterface + */ + protected $phpFileLoader; + + /** + * Constructs the PHP file library type. + * + * @param string $plugin_id + * The plugin ID taken from the class annotation. + * @param \Drupal\Component\Plugin\Factory\FactoryInterface $locator_factory + * The locator factory. + * @param \Drupal\Component\Plugin\Factory\FactoryInterface $detector_factory + * The version detector factory. + * @param \Drupal\libraries\ExternalLibrary\PhpFile\PhpFileLoaderInterface $php_file_loader + * The PHP file loader. + */ + public function __construct($plugin_id, FactoryInterface $locator_factory, FactoryInterface $detector_factory, PhpFileLoaderInterface $php_file_loader) { + parent::__construct($plugin_id, $locator_factory, $detector_factory); + $this->phpFileLoader = $php_file_loader; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $plugin_id, + $container->get('plugin.manager.libraries.locator'), + $container->get('plugin.manager.libraries.version_detector'), + $container->get('libraries.php_file_loader') + ); + } + + /** + * {@inheritdoc} + */ + public function getLibraryClass() { + return PhpFileLibrary::class; + } + + /** + * {@inheritdoc} + */ + public function onLibraryLoad(LibraryInterface $library) { + /** @var \Drupal\libraries\ExternalLibrary\PhpFile\PhpFileLibraryInterface $library */ + // @todo Prevent loading a library multiple times. + foreach ($library->getPhpFiles() as $file) { + $this->phpFileLoader->load($file); + } + } + +} diff --git a/web/modules/libraries/src/Plugin/libraries/VersionDetector/LinePatternDetector.php b/web/modules/libraries/src/Plugin/libraries/VersionDetector/LinePatternDetector.php new file mode 100644 index 0000000000000000000000000000000000000000..25a799c536d344e105400274e6ce2de5924a60d5 --- /dev/null +++ b/web/modules/libraries/src/Plugin/libraries/VersionDetector/LinePatternDetector.php @@ -0,0 +1,89 @@ +<?php + +namespace Drupal\libraries\Plugin\libraries\VersionDetector; + +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\Core\Plugin\PluginBase; +use Drupal\libraries\ExternalLibrary\Exception\UnknownLibraryVersionException; +use Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface; +use Drupal\libraries\ExternalLibrary\Version\VersionDetectorInterface; +use Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Detects the version by matching lines in a file against a specified pattern. + * + * This version detector can be used if the library version is denoted in a + * particular format in a changelog or readme file, for example. + * + * @VersionDetector("line_pattern") + * + * @ingroup libraries + */ +class LinePatternDetector extends PluginBase implements VersionDetectorInterface, ContainerFactoryPluginInterface { + + /** + * The app root. + * + * @var string + */ + protected $appRoot; + + /** + * Constructs a line pattern version detector. + * + * @param array $configuration + * @param string $plugin_id + * @param array $plugin_definition + * @param string $app_root + */ + public function __construct(array $configuration, $plugin_id, array $plugin_definition, $app_root) { + $configuration += [ + 'file' => '', + 'pattern' => '', + 'lines' => 20, + 'columns' => 200, + ]; + parent::__construct($configuration, $plugin_id, $plugin_definition); + $this->appRoot = $app_root; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('app.root') + ); + } + + /** + * {@inheritdoc} + */ + public function detectVersion(VersionedLibraryInterface $library) { + if (!($library instanceof LocalLibraryInterface)) { + throw new UnknownLibraryVersionException($library); + } + + $filepath = $this->appRoot . '/' . $library->getLocalPath() . '/' . $this->configuration['file']; + if (!file_exists($filepath)) { + throw new UnknownLibraryVersionException($library); + } + + $file = fopen($filepath, 'r'); + $lines = $this->configuration['lines']; + while ($lines && $line = fgets($file, $this->configuration['columns'])) { + if (preg_match($this->configuration['pattern'], $line, $version)) { + fclose($file); + $library->setVersion($version[1]); + return; + } + $lines--; + } + fclose($file); + } + +} diff --git a/web/modules/libraries/src/Plugin/libraries/VersionDetector/StaticDetector.php b/web/modules/libraries/src/Plugin/libraries/VersionDetector/StaticDetector.php new file mode 100644 index 0000000000000000000000000000000000000000..ed697de50b07452f1178adbdd12631657130b5c7 --- /dev/null +++ b/web/modules/libraries/src/Plugin/libraries/VersionDetector/StaticDetector.php @@ -0,0 +1,46 @@ +<?php + +namespace Drupal\libraries\Plugin\libraries\VersionDetector; + +use Drupal\Core\Plugin\PluginBase; +use Drupal\libraries\ExternalLibrary\Exception\UnknownLibraryVersionException; +use Drupal\libraries\ExternalLibrary\Version\VersionDetectorInterface; +use Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface; + +/** + * Detects the version by returning a static string. + * + * As this does not perform any actual detection and, thus, circumvents any + * negotiation of versions by Libraries API it should only be used for testing + * or when the version of a library cannot be determined from the source code + * itself. + * + * @VersionDetector("static") + */ +class StaticDetector extends PluginBase implements VersionDetectorInterface { + + /** + * Constructs a static version detector. + * + * @param array $configuration + * @param string $plugin_id + * @param array $plugin_definition + */ + public function __construct(array $configuration, $plugin_id, array $plugin_definition) { + $configuration += [ + 'version' => NULL, + ]; + parent::__construct($configuration, $plugin_id, $plugin_definition); + } + + /** + * {@inheritdoc} + */ + public function detectVersion(VersionedLibraryInterface $library) { + if (!isset($this->configuration['version'])) { + throw new UnknownLibraryVersionException($library); + } + $library->setVersion($this->configuration['version']); + } + +} diff --git a/web/modules/libraries/src/StreamWrapper/AssetLibrariesStream.php b/web/modules/libraries/src/StreamWrapper/AssetLibrariesStream.php new file mode 100644 index 0000000000000000000000000000000000000000..54893a7d04a82d53365f0afb6a0348c852e0903b --- /dev/null +++ b/web/modules/libraries/src/StreamWrapper/AssetLibrariesStream.php @@ -0,0 +1,40 @@ +<?php + +namespace Drupal\libraries\StreamWrapper; + +use Drupal\Core\StreamWrapper\LocalStream; + +/** + * Provides a stream wrapper for asset libraries. + * + * Can be used with the 'asset://' scheme, for example + * 'asset://jquery/jquery.js'. + */ +class AssetLibrariesStream extends LocalStream { + + use LocalHiddenStreamTrait; + use PrivateStreamTrait; + + /** + * {@inheritdoc} + */ + public function getName() { + return t('Assets'); + } + + /** + * {@inheritdoc} + */ + public function getDescription() { + return t('Provides access to asset library files.'); + } + + /** + * {@inheritdoc} + */ + public function getDirectoryPath() { + // @todo Provide support for site-specific directories, etc. + return 'sites/all/assets/vendor'; + } + +} diff --git a/web/modules/libraries/src/StreamWrapper/LibraryDefinitionsStream.php b/web/modules/libraries/src/StreamWrapper/LibraryDefinitionsStream.php new file mode 100644 index 0000000000000000000000000000000000000000..7cf0764ec0c75d3cd9cc2d0384c15f395c2811cd --- /dev/null +++ b/web/modules/libraries/src/StreamWrapper/LibraryDefinitionsStream.php @@ -0,0 +1,86 @@ +<?php + +namespace Drupal\libraries\StreamWrapper; + +use Drupal\Core\StreamWrapper\LocalStream; + +/** + * Provides a stream wrapper for library definitions. + * + * Can be used with the 'library-definitions' scheme, for example + * 'library-definitions://example.json' for a library ID of 'example'. + * + * By default this stream wrapper reads from a single directory that is + * configurable and points to the 'library-definitions' directory within the + * public files directory by default. This makes library definitions writable + * by the webserver by default, which is in anticipation of a user interface + * that fetches definitions from a remote repository and stores them locally. + * For improved security the library definitions can be managed manually (or put + * under version control) and placed in a directory that is not writable by the + * webserver. + * + * The idea of using a stream wrapper for this as well as the default location + * is taken from the 'translations' stream wrapper provided by the Interface + * Translation module. + * + * @see \Drupal\locale\StreamWrapper\TranslationsStream + * + * @todo Use a setting instead of configuration for the directory. + */ +class LibraryDefinitionsStream extends LocalStream { + + use LocalHiddenStreamTrait; + use PrivateStreamTrait; + + /** + * The config factory + * + * @var \Drupal\Core\Config\ConfigFactoryInterface + */ + protected $configFactory; + + /** + * Constructs an external library registry. + * + * @todo Dependency injection. + */ + public function __construct() { + $this->configFactory = \Drupal::configFactory(); + } + + /** + * {@inheritdoc} + */ + public function getName() { + return t('Library definitions'); + } + + /** + * {@inheritdoc} + */ + public function getDescription() { + return t('Provides access to library definition files.'); + } + + /** + * {@inheritdoc} + */ + public function getDirectoryPath() { + return $this->getConfig('local.path'); + } + + /** + * Fetches a configuration value from the library definitions configuration. + * @param $key + * The configuration key to fetch. + * + * @return array|mixed|null + * The configuration value. + */ + protected function getConfig($key) { + return $this->configFactory + ->get('libraries.settings') + ->get("definitions.$key"); + } + +} diff --git a/web/modules/libraries/src/StreamWrapper/LocalHiddenStreamTrait.php b/web/modules/libraries/src/StreamWrapper/LocalHiddenStreamTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..f71c0de99c735dc3cae2c0062c8f78e5267602c1 --- /dev/null +++ b/web/modules/libraries/src/StreamWrapper/LocalHiddenStreamTrait.php @@ -0,0 +1,23 @@ +<?php + +namespace Drupal\libraries\StreamWrapper; + +use Drupal\Core\StreamWrapper\StreamWrapperInterface; + +/** + * Provides a trait for local hidden streams. + */ +trait LocalHiddenStreamTrait { + + /** + * Returns the type of stream wrapper. + * + * @return int + * + * @see \Drupal\Core\StreamWrapper\StreamWrapperInterface::getType() + */ + public static function getType() { + return StreamWrapperInterface::LOCAL_HIDDEN; + } + +} diff --git a/web/modules/libraries/src/StreamWrapper/PhpFileLibrariesStream.php b/web/modules/libraries/src/StreamWrapper/PhpFileLibrariesStream.php new file mode 100644 index 0000000000000000000000000000000000000000..a4933858e106d5f63129f2c3956420abe82ff2e1 --- /dev/null +++ b/web/modules/libraries/src/StreamWrapper/PhpFileLibrariesStream.php @@ -0,0 +1,40 @@ +<?php + +namespace Drupal\libraries\StreamWrapper; + +use Drupal\Core\StreamWrapper\LocalStream; + +/** + * Provides a stream wrapper for PHP file libraries. + * + * Can be used with the 'php-file://' scheme, for example + * 'php-file-library://guzzle/src/functions_include.php'. + */ +class PhpFileLibrariesStream extends LocalStream { + + use LocalHiddenStreamTrait; + use PrivateStreamTrait; + + /** + * {@inheritdoc} + */ + public function getName() { + return t('PHP library files'); + } + + /** + * {@inheritdoc} + */ + public function getDescription() { + return t('Provides access to PHP library files.'); + } + + /** + * {@inheritdoc} + */ + public function getDirectoryPath() { + // @todo Provide support for site-specific directories, etc. + return 'sites/all/libraries'; + } + +} diff --git a/web/modules/libraries/src/StreamWrapper/PrivateStreamTrait.php b/web/modules/libraries/src/StreamWrapper/PrivateStreamTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..4690551b9bcb26de73b42c9bf9ffb5a59bde3d9b --- /dev/null +++ b/web/modules/libraries/src/StreamWrapper/PrivateStreamTrait.php @@ -0,0 +1,39 @@ +<?php + +namespace Drupal\libraries\StreamWrapper; + +/** + * Provides a trait for local streams that are not publicly accessible. + * + * @see \Drupal\locale\StreamWrapper\TranslationsStream + */ +trait PrivateStreamTrait { + + /** + * Returns a web accessible URL for the resource. + * + * This function should return a URL that can be embedded in a web page + * and accessed from a browser. For example, the external URL of + * "youtube://xIpLd0WQKCY" might be + * "http://www.youtube.com/watch?v=xIpLd0WQKCY". + * + * @return string + * Returns a string containing a web accessible URL for the resource. + * + * @see \Drupal\Core\StreamWrapper\StreamWrapperInterface::getExternalUrl() + */ + function getExternalUrl() { + throw new \LogicException("{$this->getName()} should not be public."); + } + + /** + * Returns the name of the stream wrapper for use in the UI. + * + * @return string + * The stream wrapper name. + * + * @see \Drupal\Core\StreamWrapper\StreamWrapperInterface::getName() + */ + abstract public function getName(); + +} diff --git a/web/modules/libraries/src/Tests/LibrariesUnitTest.php b/web/modules/libraries/src/Tests/LibrariesUnitTest.php new file mode 100644 index 0000000000000000000000000000000000000000..f616a92b8ea4caa3855b53244a81fa65f2b7ed0a --- /dev/null +++ b/web/modules/libraries/src/Tests/LibrariesUnitTest.php @@ -0,0 +1,53 @@ +<?php + +namespace Drupal\libraries\Tests; + +use Drupal\simpletest\KernelTestBase; + +/** + * Tests basic Libraries API functions. + * + * @group libraries + */ +class LibrariesUnitTest extends KernelTestBase { + + /** + * {@inheritdoc} + */ + public static $modules = array('libraries'); + + /** + * Tests libraries_get_path(). + */ + function testLibrariesGetPath() { + // Note that, even though libraries_get_path() doesn't find the 'example' + // library, we are able to make it 'installed' by specifying the 'library + // path' up-front. This is only used for testing purposed and is strongly + // discouraged as it defeats the purpose of Libraries API in the first + // place. + $this->assertEqual(libraries_get_path('example'), FALSE, 'libraries_get_path() returns FALSE for a missing library.'); + } + + /** + * Tests libraries_prepare_files(). + */ + function testLibrariesPrepareFiles() { + $expected = array( + 'files' => array( + 'js' => array('example.js' => array()), + 'css' => array('example.css' => array()), + 'php' => array('example.php' => array()), + ), + ); + $library = array( + 'files' => array( + 'js' => array('example.js'), + 'css' => array('example.css'), + 'php' => array('example.php'), + ), + ); + libraries_prepare_files($library, NULL, NULL); + $this->assertEqual($expected, $library, 'libraries_prepare_files() works correctly.'); + } + +} diff --git a/web/modules/libraries/src/Tests/LibrariesWebTest.php b/web/modules/libraries/src/Tests/LibrariesWebTest.php new file mode 100644 index 0000000000000000000000000000000000000000..2c9254c45323010c0145c28f58785d39e4e46f81 --- /dev/null +++ b/web/modules/libraries/src/Tests/LibrariesWebTest.php @@ -0,0 +1,525 @@ +<?php + +namespace Drupal\libraries\Tests; + +use Drupal\Component\Utility\Html; +use Drupal\simpletest\WebTestBase; + +/** + * Tests basic detection and loading of libraries. + * + * @group libraries + */ +class LibrariesWebTest extends WebTestBase { + + /** + * {@inheritdoc} + */ + protected $profile = 'testing'; + + /** + * Modules to install. + * + * @var array + */ + public static $modules = array('libraries', 'libraries_test'); + + /** + * The URL generator used in this test. + * + * @var \Drupal\Core\Utility\UnroutedUrlAssemblerInterface + */ + protected $urlAssembler; + + /** + * The state service used in this test. + * + * @var \Drupal\Core\State\StateInterface + */ + protected $state; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->urlAssembler = $this->container->get('unrouted_url_assembler'); + $this->state = $this->container->get('state'); + } + + /** + * Tests libraries_detect_dependencies(). + */ + function testLibrariesDetectDependencies() { + $library = array( + 'name' => 'Example', + 'dependencies' => array('example_missing'), + ); + libraries_detect_dependencies($library); + $this->assertEqual($library['error'], 'missing dependency', 'libraries_detect_dependencies() detects missing dependency'); + $error_message = t('The %dependency library, which the %library library depends on, is not installed.', array( + '%dependency' => 'Example missing', + '%library' => $library['name'], + )); + $this->verbose("Expected:<br>$error_message"); + $this->verbose('Actual:<br>' . $library['error message']); + $this->assertEqual($library['error message'], $error_message, 'Correct error message for a missing dependency'); + // Test versioned dependencies. + $version = '1.1'; + $compatible = array( + '1.1', + '<=1.1', + '>=1.1', + '<1.2', + '<2.0', + '>1.0', + '>1.0-rc1', + '>1.0-beta2', + '>1.0-alpha3', + '>0.1', + '<1.2, >1.0', + '>0.1, <=1.1', + ); + $incompatible = array( + '1.2', + '2.0', + '<1.1', + '>1.1', + '<=1.0', + '<=1.0-rc1', + '<=1.0-beta2', + '<=1.0-alpha3', + '>=1.2', + '<1.1, >0.9', + '>=0.1, <1.1', + ); + $library = array( + 'name' => 'Example', + ); + foreach ($compatible as $version_string) { + $library['dependencies'][0] = "example_dependency ($version_string)"; + // libraries_detect_dependencies() is a post-detect callback, so + // 'installed' is already set, when it is called. It sets the value to + // FALSE for missing or incompatible dependencies. + $library['installed'] = TRUE; + libraries_detect_dependencies($library); + $this->assertTrue($library['installed'], "libraries_detect_dependencies() detects compatible version string: '$version_string' is compatible with '$version'"); + } + foreach ($incompatible as $version_string) { + $library['dependencies'][0] = "example_dependency ($version_string)"; + $library['installed'] = TRUE; + unset($library['error'], $library['error message']); + libraries_detect_dependencies($library); + $this->assertEqual($library['error'], 'incompatible dependency', "libraries_detect_dependencies() detects incompatible version strings: '$version_string' is incompatible with '$version'"); + } + // Instead of repeating this assertion for each version string, we just + // re-use the $library variable from the foreach loop. + $error_message = t('The version %dependency_version of the %dependency library is not compatible with the %library library.', array( + '%dependency_version' => $version, + '%dependency' => 'Example dependency', + '%library' => $library['name'], + )); + $this->verbose("Expected:<br>$error_message"); + $this->verbose('Actual:<br>' . $library['error message']); + $this->assertEqual($library['error message'], $error_message, 'Correct error message for an incompatible dependency'); + } + + /** + * Tests libraries_scan_info_files(). + */ + function testLibrariesScanInfoFiles() { + $expected = array('example_info_file' => (object) array( + 'uri' => drupal_get_path('module', 'libraries') . '/tests/example/example_info_file.libraries.info.yml', + 'filename' => 'example_info_file.libraries.info.yml', + 'name' => 'example_info_file.libraries.info', + )); + $actual = libraries_scan_info_files(); + $this->verbose('Expected:<pre>' . var_export($expected, TRUE) . '</pre>'); + $this->verbose('Actual:<pre>' . var_export($actual, TRUE) . '</pre>'); + $this->assertEqual($actual, $expected, 'libraries_scan_info_files() correctly finds the example info file.'); + $this->verbose('<pre>' . var_export(libraries_scan_info_files(), TRUE) . '</pre>'); + } + + /** + * Tests libraries_info(). + */ + function testLibrariesInfo() { + // Test that library information is found correctly. + $expected = array( + 'name' => 'Example files', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'version' => '1', + 'files' => array( + 'js' => array('example_1.js' => array()), + 'css' => array('example_1.css' => array()), + 'php' => array('example_1.php' => array()), + ), + 'module' => 'libraries_test', + ); + libraries_info_defaults($expected, 'example_files'); + $library = libraries_info('example_files'); + $this->verbose('Expected:<pre>' . var_export($expected, TRUE) . '</pre>'); + $this->verbose('Actual:<pre>' . var_export($library, TRUE) . '</pre>'); + $this->assertEqual($library, $expected, 'Library information is correctly gathered.'); + + // Test a library specified with an .info file gets detected. + $expected = array( + 'name' => 'Example info file', + 'info file' => drupal_get_path('module', 'libraries') . '/tests/example/example_info_file.libraries.info.yml', + ); + libraries_info_defaults($expected, 'example_info_file'); + $library = libraries_info('example_info_file'); + // If this module was downloaded from Drupal.org, the Drupal.org packaging + // system has corrupted the test info file. + // @see http://drupal.org/node/1606606 + unset($library['core'], $library['datestamp'], $library['project'], $library['version']); + $this->verbose('Expected:<pre>' . var_export($expected, TRUE) . '</pre>'); + $this->verbose('Actual:<pre>' . var_export($library, TRUE) . '</pre>'); + $this->assertEqual($library, $expected, 'Library specified with an .info file found'); + } + + /** + * Tests libraries_detect(). + */ + function testLibrariesDetect() { + // Test missing library. + $library = libraries_detect('example_missing'); + $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>'); + $this->assertEqual($library['error'], 'not found', 'Missing library not found.'); + $error_message = t('The %library library could not be found.', array( + '%library' => $library['name'], + )); + $this->assertEqual($library['error message'], $error_message, 'Correct error message for a missing library.'); + + // Test unknown library version. + $library = libraries_detect('example_undetected_version'); + $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>'); + $this->assertEqual($library['error'], 'not detected', 'Undetected version detected as such.'); + $error_message = t('The version of the %library library could not be detected.', array( + '%library' => $library['name'], + )); + $this->assertEqual($library['error message'], $error_message, 'Correct error message for a library with an undetected version.'); + + // Test unsupported library version. + $library = libraries_detect('example_unsupported_version'); + $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>'); + $this->assertEqual($library['error'], 'not supported', 'Unsupported version detected as such.'); + $error_message = t('The installed version %version of the %library library is not supported.', array( + '%version' => $library['version'], + '%library' => $library['name'], + )); + $this->assertEqual($library['error message'], $error_message, 'Correct error message for a library with an unsupported version.'); + + // Test supported library version. + $library = libraries_detect('example_supported_version'); + $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>'); + $this->assertEqual($library['installed'], TRUE, 'Supported library version found.'); + + // Test libraries_get_version(). + $library = libraries_detect('example_default_version_callback'); + $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>'); + $this->assertEqual($library['version'], '1', 'Expected version returned by default version callback.'); + + // Test a multiple-parameter version callback. + $library = libraries_detect('example_multiple_parameter_version_callback'); + $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>'); + $this->assertEqual($library['version'], '1', 'Expected version returned by multiple parameter version callback.'); + + // Test a top-level files property. + $library = libraries_detect('example_files'); + $files = array( + 'js' => array('example_1.js' => array()), + 'css' => array('example_1.css' => array()), + 'php' => array('example_1.php' => array()), + ); + $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>'); + $this->assertEqual($library['files'], $files, 'Top-level files property works.'); + + // Test version-specific library files. + $library = libraries_detect('example_versions'); + $files = array( + 'js' => array('example_2.js' => array()), + 'css' => array('example_2.css' => array()), + 'php' => array('example_2.php' => array()), + ); + $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>'); + $this->assertEqual($library['files'], $files, 'Version-specific library files found.'); + + // Test missing variant. + $library = libraries_detect('example_variant_missing'); + $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>'); + $this->assertEqual($library['variants']['example_variant']['error'], 'not found', 'Missing variant not found'); + $error_message = t('The %variant variant of the %library library could not be found.', array( + '%variant' => 'example_variant', + '%library' => 'Example variant missing', + )); + $this->assertEqual($library['variants']['example_variant']['error message'], $error_message, 'Correct error message for a missing variant.'); + + // Test existing variant. + $library = libraries_detect('example_variant'); + $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>'); + $this->assertEqual($library['variants']['example_variant']['installed'], TRUE, 'Existing variant found.'); + } + + /** + * Tests libraries_load(). + * + * @todo Remove or rewrite to accomodate integration with core Libraries. + */ + function _testLibrariesLoad() { + // Test dependencies. + $library = libraries_load('example_dependency_missing'); + $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>'); + $this->assertFalse($library['loaded'], 'Library with missing dependency cannot be loaded'); + $library = libraries_load('example_dependency_incompatible'); + $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>'); + $this->assertFalse($library['loaded'], 'Library with incompatible dependency cannot be loaded'); + $library = libraries_load('example_dependency_compatible'); + $this->verbose('<pre>' . var_export($library, TRUE) . '</pre>'); + $this->assertEqual($library['loaded'], 1, 'Library with compatible dependency is loaded'); + $loaded = &drupal_static('libraries_load'); + $this->verbose('<pre>' . var_export($loaded, TRUE) . '</pre>'); + $this->assertEqual($loaded['example_dependency']['loaded'], 1, 'Dependency library is also loaded'); + } + + /** + * Tests the applying of callbacks. + */ + function testCallbacks() { + $expected = array( + 'name' => 'Example callback', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'version' => '1', + 'versions' => array( + '1' => array( + 'variants' => array( + 'example_variant' => array( + 'info callback' => 'not applied', + 'pre-detect callback' => 'not applied', + 'post-detect callback' => 'not applied', + 'pre-load callback' => 'not applied', + 'post-load callback' => 'not applied', + ), + ), + 'info callback' => 'not applied', + 'pre-detect callback' => 'not applied', + 'post-detect callback' => 'not applied', + 'pre-load callback' => 'not applied', + 'post-load callback' => 'not applied', + ), + ), + 'variants' => array( + 'example_variant' => array( + 'info callback' => 'not applied', + 'pre-detect callback' => 'not applied', + 'post-detect callback' => 'not applied', + 'pre-load callback' => 'not applied', + 'post-load callback' => 'not applied', + ), + ), + 'callbacks' => array( + 'info' => array('_libraries_test_info_callback'), + 'pre-detect' => array('_libraries_test_pre_detect_callback'), + 'post-detect' => array('_libraries_test_post_detect_callback'), + 'pre-load' => array('_libraries_test_pre_load_callback'), + 'post-load' => array('_libraries_test_post_load_callback'), + ), + 'info callback' => 'not applied', + 'pre-detect callback' => 'not applied', + 'post-detect callback' => 'not applied', + 'pre-load callback' => 'not applied', + 'post-load callback' => 'not applied', + 'module' => 'libraries_test', + ); + libraries_info_defaults($expected, 'example_callback'); + + // Test a callback in the 'info' group. + $expected['info callback'] = 'applied (top-level)'; + $expected['versions']['1']['info callback'] = 'applied (version 1)'; + $expected['versions']['1']['variants']['example_variant']['info callback'] = 'applied (version 1, variant example_variant)'; + $expected['variants']['example_variant']['info callback'] = 'applied (variant example_variant)'; + $library = libraries_info('example_callback'); + $this->verbose('Expected:<pre>' . var_export($expected, TRUE) . '</pre>'); + $this->verbose('Actual:<pre>' . var_export($library, TRUE) . '</pre>'); + $this->assertEqual($library, $expected, 'Prepare callback was applied correctly.'); + + // Test a callback in the 'pre-detect' and 'post-detect' phases. + // Successfully detected libraries should only contain version information + // for the detected version and thus, be marked as installed. + unset($expected['versions']); + $expected['installed'] = TRUE; + // Additionally, version-specific properties of the detected version are + // supposed to override the corresponding top-level properties. + $expected['info callback'] = 'applied (version 1)'; + $expected['variants']['example_variant']['installed'] = TRUE; + $expected['variants']['example_variant']['info callback'] = 'applied (version 1, variant example_variant)'; + // Version-overloading takes place after the 'pre-detect' callbacks have + // been applied. + $expected['pre-detect callback'] = 'applied (version 1)'; + $expected['post-detect callback'] = 'applied (top-level)'; + $expected['variants']['example_variant']['pre-detect callback'] = 'applied (version 1, variant example_variant)'; + $expected['variants']['example_variant']['post-detect callback'] = 'applied (variant example_variant)'; + $library = libraries_detect('example_callback'); + $this->verbose('Expected:<pre>' . var_export($expected, TRUE) . '</pre>'); + $this->verbose('Actual:<pre>' . var_export($library, TRUE) . '</pre>'); + $this->assertEqual($library, $expected, 'Detect callback was applied correctly.'); + + // Test a callback in the 'pre-load' and 'post-load' phases. + // Successfully loaded libraries should only contain information about the + // already loaded variant. + unset($expected['variants']); + $expected['loaded'] = 0; + $expected['pre-load callback'] = 'applied (top-level)'; + $expected['post-load callback'] = 'applied (top-level)'; + $library = libraries_load('example_callback'); + $this->verbose('Expected:<pre>' . var_export($expected, TRUE) . '</pre>'); + $this->verbose('Actual:<pre>' . var_export($library, TRUE) . '</pre>'); + $this->assertEqual($library, $expected, 'Pre-load and post-load callbacks were applied correctly.'); + // This is not recommended usually and is only used for testing purposes. + drupal_static_reset('libraries_load'); + // Successfully loaded library variants are supposed to contain the specific + // variant information only. + $expected['info callback'] = 'applied (version 1, variant example_variant)'; + $expected['pre-detect callback'] = 'applied (version 1, variant example_variant)'; + $expected['post-detect callback'] = 'applied (variant example_variant)'; + $library = libraries_load('example_callback', 'example_variant'); + $this->verbose('Expected:<pre>' . var_export($expected, TRUE) . '</pre>'); + $this->verbose('Actual:<pre>' . var_export($library, TRUE) . '</pre>'); + $this->assertEqual($library, $expected, 'Pre-detect and post-detect callbacks were applied correctly to a variant.'); + } + + /** + * Tests that library files are properly added to the page output. + * + * We check for JavaScript and CSS files directly in the DOM and add a list of + * included PHP files manually to the page output. + * + * @todo Remove or rewrite to accomodate integration with core Libraries. + * @see _libraries_test_load() + */ + function _testLibrariesOutput() { + // Test loading of a simple library with a top-level files property. + $this->drupalGet('libraries_test/files'); + $this->assertLibraryFiles('example_1', 'File loading'); + + // Test loading of integration files. + $this->drupalGet('libraries_test/integration_files'); + $this->assertRaw('libraries_test.js', 'Integration file loading: libraries_test.js found'); + $this->assertRaw('libraries_test.css', 'Integration file loading: libraries_test.css found'); + $this->assertRaw('libraries_test.inc', 'Integration file loading: libraries_test.inc found'); + + // Test version overloading. + $this->drupalGet('libraries_test/versions'); + $this->assertLibraryFiles('example_2', 'Version overloading'); + + // Test variant loading. + $this->drupalGet('libraries_test/variant'); + $this->assertLibraryFiles('example_3', 'Variant loading'); + + // Test version overloading and variant loading. + $this->drupalGet('libraries_test/versions_and_variants'); + $this->assertLibraryFiles('example_4', 'Concurrent version and variant overloading'); + + // Test caching. + \Drupal::state()->set('libraries_test.cache', TRUE); + \Drupal::cache('libraries')->delete('example_callback'); + // When the library information is not cached, all callback groups should be + // invoked. + $this->drupalGet('libraries_test/cache'); + $this->assertRaw('The <em>info</em> callback group was invoked.', 'Info callback invoked for uncached libraries.'); + $this->assertRaw('The <em>pre-detect</em> callback group was invoked.', 'Pre-detect callback invoked for uncached libraries.'); + $this->assertRaw('The <em>post-detect</em> callback group was invoked.', 'Post-detect callback invoked for uncached libraries.'); + $this->assertRaw('The <em>pre-load</em> callback group was invoked.', 'Pre-load callback invoked for uncached libraries.'); + $this->assertRaw('The <em>post-load</em> callback group was invoked.', 'Post-load callback invoked for uncached libraries.'); + // When the library information is cached only the 'pre-load' and + // 'post-load' callback groups should be invoked. + $this->drupalGet('libraries_test/cache'); + $this->assertNoRaw('The <em>info</em> callback group was not invoked.', 'Info callback not invoked for cached libraries.'); + $this->assertNoRaw('The <em>pre-detect</em> callback group was not invoked.', 'Pre-detect callback not invoked for cached libraries.'); + $this->assertNoRaw('The <em>post-detect</em> callback group was not invoked.', 'Post-detect callback not invoked for cached libraries.'); + $this->assertRaw('The <em>pre-load</em> callback group was invoked.', 'Pre-load callback invoked for cached libraries.'); + $this->assertRaw('The <em>post-load</em> callback group was invoked.', 'Post-load callback invoked for cached libraries.'); + \Drupal::state()->set('libraries_test.cache', FALSE); + } + + /** + * Helper function to assert that a library was correctly loaded. + * + * Asserts that all the correct files were loaded and all the incorrect ones + * were not. + * + * @param $name + * The name of the files that should be loaded. The current testing system + * knows of 'example_1', 'example_2', 'example_3' and 'example_4'. Each name + * has an associated JavaScript, CSS and PHP file that will be asserted. All + * other files will be asserted to not be loaded. See + * tests/example/README.txt for more information on how the loading of the + * files is tested. + * @param $label + * (optional) A label to prepend to the assertion messages, to make them + * less ambiguous. + * @param $extensions + * (optional) The expected file extensions of $name. Defaults to + * array('js', 'css', 'php'). + */ + function assertLibraryFiles($name, $label = '', $extensions = array('js', 'css', 'php')) { + $label = ($label !== '' ? "$label: " : ''); + + // Test that the wrong files are not loaded... + $names = array( + 'example_1' => FALSE, + 'example_2' => FALSE, + 'example_3' => FALSE, + 'example_4' => FALSE, + ); + // ...and the correct ones are. + $names[$name] = TRUE; + + // Test for the specific HTML that the different file types appear as in the + // DOM. + $html = array( + 'js' => array('<script src="', '"></script>'), + 'css' => array('<link rel="stylesheet" href="', '" media="all" />'), + // PHP files do not get added to the DOM directly. + // @see _libraries_test_load() + 'php' => array('<li>', '</li>'), + ); + + $html_expected = array(); + $html_not_expected = array(); + + foreach ($names as $name => $expected) { + foreach ($extensions as $extension) { + $filepath = drupal_get_path('module', 'libraries') . "/tests/example/$name.$extension"; + // JavaScript and CSS files appear as full URLs and with an appended + // query string. + if (in_array($extension, array('js', 'css'))) { + $filepath = $this->urlAssembler->assemble("base://$filepath", [ + 'query' => [ + $this->state->get('system.css_js_query_string') ?: '0' => NULL, + ], + 'absolute' => TRUE, + ]); + // If index.php is part of the generated URLs, we need to strip it. + //$filepath = str_replace('index.php/', '', $filepath); + } + list($prefix, $suffix) = $html[$extension]; + $raw = $prefix . $filepath . $suffix; + if ($expected) { + $html_expected[] = Html::escape($raw); + $this->assertRaw($raw, "$label$name.$extension found."); + } + else { + $html_not_expected[] = Html::escape($raw); + $this->assertNoRaw($raw, "$label$name.$extension not found."); + } + } + } + + $html_expected = '<ul><li><pre>' . implode('</pre></li><li><pre>', $html_expected) . '</pre></li></ul>'; + $html_not_expected = '<ul><li><pre>' . implode('</pre></li><li><pre>', $html_not_expected) . '</pre></li></ul>'; + $this->verbose("Strings of HTML that are expected to be present:{$html_expected}Strings of HTML that are expected to not be present:{$html_not_expected}"); + } + +} diff --git a/web/modules/libraries/tests/assets/vendor/test_asset_library/example.css b/web/modules/libraries/tests/assets/vendor/test_asset_library/example.css new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/web/modules/libraries/tests/assets/vendor/test_asset_library/example.js b/web/modules/libraries/tests/assets/vendor/test_asset_library/example.js new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/web/modules/libraries/tests/assets/vendor/test_asset_multiple_library/example.first.css b/web/modules/libraries/tests/assets/vendor/test_asset_multiple_library/example.first.css new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/web/modules/libraries/tests/assets/vendor/test_asset_multiple_library/example.first.js b/web/modules/libraries/tests/assets/vendor/test_asset_multiple_library/example.first.js new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/web/modules/libraries/tests/assets/vendor/test_asset_multiple_library/example.second.css b/web/modules/libraries/tests/assets/vendor/test_asset_multiple_library/example.second.css new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/web/modules/libraries/tests/assets/vendor/test_asset_multiple_library/example.second.js b/web/modules/libraries/tests/assets/vendor/test_asset_multiple_library/example.second.js new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/web/modules/libraries/tests/example/README.txt b/web/modules/libraries/tests/example/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..e3582c2abbbb822f47fc834c058ad87d88c50265 --- /dev/null +++ b/web/modules/libraries/tests/example/README.txt @@ -0,0 +1,42 @@ + +Example library + +Version 1 + +This file is an example file to test version detection. + +The various other files in this directory are to test the loading of JavaScript, +CSS and PHP files. +- JavaScript: The filenames of the JavaScript files are asserted to be in the + raw HTML via SimpleTest. Since the filename could appear, for instance, in an + error message, this is not very robust. Explicit testing of JavaScript, + though, is not yet possible with SimpleTest. To allow for easier debugging, we + place the following text on the page: + "If this text shows up, no JavaScript test file was loaded." + This text is replaced via JavaScript by a text of the form: + "If this text shows up, [[file] was loaded successfully." + [file] is either 'example_1.js', 'example_2.js', 'example_3.js', + 'example_4.js' or 'libraries_test.js'. If you have SimpleTest's verbose mode + enabled and see the above text in one of the debug pages, the noted JavaScript + file was loaded successfully. +- CSS: The filenames of the CSS files are asserted to be in the raw HTML via + SimpleTest. Since the filename could appear, for instance, in an error + message, this is not very robust. Explicit testing of CSS, though, is not yet + possible with SimpleTest. Hence, the CSS files, if loaded, make the following + text a certain color: + "If one of the CSS test files has been loaded, this text will be colored: + - example_1: red + - example_2: green + - example_3: orange + - example_4: blue + - libraries_test: purple" + If you have SimpleTest's verbose mode enabled, and see the above text in a + certain color (i.e. not in black), a CSS file was loaded successfully. Which + file depends on the color as referenced in the text above. +- PHP: The loading of PHP files is tested by defining a dummy function in the + PHP files and then checking whether this function was defined using + function_exists(). This can be checked programatically with SimpleTest. +The loading of integration files is tested with the same method. The integration +files are libraries_test.js, libraries_test.css, libraries_test.inc and are +located in the tests directory alongside libraries_test.module (i.e. they are +not in the same directory as this file). diff --git a/web/modules/libraries/tests/example/example_1.css b/web/modules/libraries/tests/example/example_1.css new file mode 100644 index 0000000000000000000000000000000000000000..a732bda5fca4cfcc1734b46a16cb8ac84e6856a2 --- /dev/null +++ b/web/modules/libraries/tests/example/example_1.css @@ -0,0 +1,11 @@ + +/** + * @file + * Test CSS file for Libraries loading. + * + * Color the 'libraries-test-css' div red. See README.txt for more information. + */ + +.libraries-test-css { + color: red; +} diff --git a/web/modules/libraries/tests/example/example_1.js b/web/modules/libraries/tests/example/example_1.js new file mode 100644 index 0000000000000000000000000000000000000000..8a1b9a27f45fc82535cddf79569848b8edcfd6c5 --- /dev/null +++ b/web/modules/libraries/tests/example/example_1.js @@ -0,0 +1,18 @@ + +/** + * @file + * Test JavaScript file for Libraries loading. + * + * Replace the text in the 'libraries-test-javascript' div. See README.txt for + * more information. + */ + +(function ($) { + +Drupal.behaviors.librariesTest = { + attach: function(context, settings) { + $('.libraries-test-javascript').text('If this text shows up, example_1.js was loaded successfully.') + } +}; + +})(jQuery); diff --git a/web/modules/libraries/tests/example/example_1.php b/web/modules/libraries/tests/example/example_1.php new file mode 100644 index 0000000000000000000000000000000000000000..92e6711a792fee3acac339a8c79c8227ac6ba393 --- /dev/null +++ b/web/modules/libraries/tests/example/example_1.php @@ -0,0 +1,12 @@ +<?php + +/** + * @file + * Test PHP file for Libraries loading. + */ + +/** + * Dummy function to see if this file was loaded. + */ +function _libraries_test_example_1() { +} diff --git a/web/modules/libraries/tests/example/example_2.css b/web/modules/libraries/tests/example/example_2.css new file mode 100644 index 0000000000000000000000000000000000000000..c8f92899de569ec1caf4ee44e720f0e604d24c59 --- /dev/null +++ b/web/modules/libraries/tests/example/example_2.css @@ -0,0 +1,11 @@ + +/** + * @file + * Test CSS file for Libraries loading. + * + * Color the 'libraries-test-css' div green. See README.txt for more information. + */ + +.libraries-test-css { + color: green; +} diff --git a/web/modules/libraries/tests/example/example_2.js b/web/modules/libraries/tests/example/example_2.js new file mode 100644 index 0000000000000000000000000000000000000000..1b744bb166754c8320a19822c5b05c8ed3a9c7ca --- /dev/null +++ b/web/modules/libraries/tests/example/example_2.js @@ -0,0 +1,18 @@ + +/** + * @file + * Test JavaScript file for Libraries loading. + * + * Replace the text in the 'libraries-test-javascript' div. See README.txt for + * more information. + */ + +(function ($) { + +Drupal.behaviors.librariesTest = { + attach: function(context, settings) { + $('.libraries-test-javascript').text('If this text shows up, example_2.js was loaded successfully.') + } +}; + +})(jQuery); diff --git a/web/modules/libraries/tests/example/example_2.php b/web/modules/libraries/tests/example/example_2.php new file mode 100644 index 0000000000000000000000000000000000000000..ddded40c3c3c65fff4bc488b5f69b0fc54e879cc --- /dev/null +++ b/web/modules/libraries/tests/example/example_2.php @@ -0,0 +1,12 @@ +<?php + +/** + * @file + * Test PHP file for Libraries loading. + */ + +/** + * Dummy function to see if this file was loaded. + */ +function _libraries_test_example_2() { +} diff --git a/web/modules/libraries/tests/example/example_3.css b/web/modules/libraries/tests/example/example_3.css new file mode 100644 index 0000000000000000000000000000000000000000..ffef054a43d8aa575bdbed0b722c0ee7064780b4 --- /dev/null +++ b/web/modules/libraries/tests/example/example_3.css @@ -0,0 +1,11 @@ + +/** + * @file + * Test CSS file for Libraries loading. + * + * Color the 'libraries-test-css' div orange. See README.txt for more information. + */ + +.libraries-test-css { + color: orange; +} diff --git a/web/modules/libraries/tests/example/example_3.js b/web/modules/libraries/tests/example/example_3.js new file mode 100644 index 0000000000000000000000000000000000000000..d6a3fa4e5b0f1623fc318f360324f1302c382ada --- /dev/null +++ b/web/modules/libraries/tests/example/example_3.js @@ -0,0 +1,18 @@ + +/** + * @file + * Test JavaScript file for Libraries loading. + * + * Replace the text in the 'libraries-test-javascript' div. See README.txt for + * more information. + */ + +(function ($) { + +Drupal.behaviors.librariesTest = { + attach: function(context, settings) { + $('.libraries-test-javascript').text('If this text shows up, example_3.js was loaded successfully.') + } +}; + +})(jQuery); diff --git a/web/modules/libraries/tests/example/example_3.php b/web/modules/libraries/tests/example/example_3.php new file mode 100644 index 0000000000000000000000000000000000000000..cf74cf0811bda306b39a22dda9d13fa11cfbf580 --- /dev/null +++ b/web/modules/libraries/tests/example/example_3.php @@ -0,0 +1,12 @@ +<?php + +/** + * @file + * Test PHP file for Libraries loading. + */ + +/** + * Dummy function to see if this file was loaded. + */ +function _libraries_test_example_3() { +} diff --git a/web/modules/libraries/tests/example/example_4.css b/web/modules/libraries/tests/example/example_4.css new file mode 100644 index 0000000000000000000000000000000000000000..5030a614808ba266727b8f7a2ee55b935752aa8b --- /dev/null +++ b/web/modules/libraries/tests/example/example_4.css @@ -0,0 +1,11 @@ + +/** + * @file + * Test CSS file for Libraries loading. + * + * Color the 'libraries-test-css' div blue. See README.txt for more information. + */ + +.libraries-test-css { + color: blue; +} diff --git a/web/modules/libraries/tests/example/example_4.js b/web/modules/libraries/tests/example/example_4.js new file mode 100644 index 0000000000000000000000000000000000000000..ce5dc26e31fd6e22c721cb5cb378153553306c50 --- /dev/null +++ b/web/modules/libraries/tests/example/example_4.js @@ -0,0 +1,18 @@ + +/** + * @file + * Test JavaScript file for Libraries loading. + * + * Replace the text in the 'libraries-test-javascript' div. See README.txt for + * more information. + */ + +(function ($) { + +Drupal.behaviors.librariesTest = { + attach: function(context, settings) { + $('.libraries-test-javascript').text('If this text shows up, example_4.js was loaded successfully.') + } +}; + +})(jQuery); diff --git a/web/modules/libraries/tests/example/example_4.php b/web/modules/libraries/tests/example/example_4.php new file mode 100644 index 0000000000000000000000000000000000000000..b46507f540551ef2c07ac17eb7c871fcabf57e8d --- /dev/null +++ b/web/modules/libraries/tests/example/example_4.php @@ -0,0 +1,12 @@ +<?php + +/** + * @file + * Test PHP file for Libraries loading. + */ + +/** + * Dummy function to see if this file was loaded. + */ +function _libraries_test_example_4() { +} diff --git a/web/modules/libraries/tests/example/example_info_file.libraries.info.yml b/web/modules/libraries/tests/example/example_info_file.libraries.info.yml new file mode 100644 index 0000000000000000000000000000000000000000..529af045cf3d5cfce7fb74a9ce74dc98aaec9c88 --- /dev/null +++ b/web/modules/libraries/tests/example/example_info_file.libraries.info.yml @@ -0,0 +1,8 @@ +# This is an example info file of a library used for testing purposes. +name: Example info file + +# Information added by Drupal.org packaging script on 2018-01-27 +version: '8.x-3.0-alpha1' +core: '8.x' +project: 'libraries' +datestamp: 1517046488 diff --git a/web/modules/libraries/tests/libraries/test_php_file_library/test_php_file_library.php b/web/modules/libraries/tests/libraries/test_php_file_library/test_php_file_library.php new file mode 100644 index 0000000000000000000000000000000000000000..8ef69fb553bde2f4d725e3205feb9c62a012a810 --- /dev/null +++ b/web/modules/libraries/tests/libraries/test_php_file_library/test_php_file_library.php @@ -0,0 +1,16 @@ +<?php + +/** + * @file + * Test PHP file. + * + * This file is part of the 'test_php_file_library' test library. + * + * @see \Drupal\Tests\libraries\Kernel\ExternalLibrary\PhpFile\PhpFileLibraryTest + */ + +/** + * A test function to be able to test whether this file was loaded or not. + */ +function _libraries_test_php_function() { +} diff --git a/web/modules/libraries/tests/library_definitions/test_asset_library.json b/web/modules/libraries/tests/library_definitions/test_asset_library.json new file mode 100644 index 0000000000000000000000000000000000000000..aca82a39a796efb25bf2908d4ccfdaf67b029615 --- /dev/null +++ b/web/modules/libraries/tests/library_definitions/test_asset_library.json @@ -0,0 +1,18 @@ +{ + "type": "asset", + "version_detector": { + "id": "static", + "configuration": { + "version": "1.0.0" + } + }, + "remote_url": "http://example.com", + "css": { + "base": { + "example.css": {} + } + }, + "js": { + "example.js": {} + } +} diff --git a/web/modules/libraries/tests/library_definitions/test_asset_multiple_library.json b/web/modules/libraries/tests/library_definitions/test_asset_multiple_library.json new file mode 100644 index 0000000000000000000000000000000000000000..8bcff3c1c538aa964358593e41b5bdcdb5971ae8 --- /dev/null +++ b/web/modules/libraries/tests/library_definitions/test_asset_multiple_library.json @@ -0,0 +1,32 @@ +{ + "type": "asset_multiple", + "version_detector": { + "id": "static", + "configuration": { + "version": "1.0.0" + } + }, + "remote_url": "http://example.com", + "libraries": { + "first": { + "css": { + "base": { + "example.first.css": {} + } + }, + "js": { + "example.first.js": {} + } + }, + "second": { + "css": { + "base": { + "example.second.css": {} + } + }, + "js": { + "example.second.js": {} + } + } + } +} diff --git a/web/modules/libraries/tests/library_definitions/test_php_file_library.json b/web/modules/libraries/tests/library_definitions/test_php_file_library.json new file mode 100644 index 0000000000000000000000000000000000000000..61cb28c9c5f3242fae474328e94fdcb37cce7cb2 --- /dev/null +++ b/web/modules/libraries/tests/library_definitions/test_php_file_library.json @@ -0,0 +1,6 @@ +{ + "type": "php_file", + "files": [ + "test_php_file_library.php" + ] +} diff --git a/web/modules/libraries/tests/modules/libraries_test/libraries_test.css b/web/modules/libraries/tests/modules/libraries_test/libraries_test.css new file mode 100644 index 0000000000000000000000000000000000000000..350528129c69498a52f749ffb07f9c323fd3b850 --- /dev/null +++ b/web/modules/libraries/tests/modules/libraries_test/libraries_test.css @@ -0,0 +1,12 @@ + +/** + * @file + * Test CSS file for Libraries loading. + * + * Color the 'libraries-test-css' div purple. See README.txt for more + * information. + */ + +.libraries-test-css { + color: purple; +} diff --git a/web/modules/libraries/tests/modules/libraries_test/libraries_test.inc b/web/modules/libraries/tests/modules/libraries_test/libraries_test.inc new file mode 100644 index 0000000000000000000000000000000000000000..68e2d2eab37071cca089b18010f21b68e8495ac6 --- /dev/null +++ b/web/modules/libraries/tests/modules/libraries_test/libraries_test.inc @@ -0,0 +1,12 @@ +<?php + +/** + * @file + * Test PHP file for Libraries loading. + */ + +/** + * Dummy function to see if this file was loaded. + */ +function _libraries_test_integration_file() { +} diff --git a/web/modules/libraries/tests/modules/libraries_test/libraries_test.info.yml b/web/modules/libraries/tests/modules/libraries_test/libraries_test.info.yml new file mode 100644 index 0000000000000000000000000000000000000000..be54ba548ffeeb66d574293940fbcbdb06524972 --- /dev/null +++ b/web/modules/libraries/tests/modules/libraries_test/libraries_test.info.yml @@ -0,0 +1,16 @@ +name: Libraries test module +type: module +description: Tests library detection and loading. +# core: 8.x +dependencies: + - libraries +hidden: TRUE +library_dependencies: + - test_asset_library + - test_asset_multiple_library + +# Information added by Drupal.org packaging script on 2018-01-27 +version: '8.x-3.0-alpha1' +core: '8.x' +project: 'libraries' +datestamp: 1517046488 diff --git a/web/modules/libraries/tests/modules/libraries_test/libraries_test.js b/web/modules/libraries/tests/modules/libraries_test/libraries_test.js new file mode 100644 index 0000000000000000000000000000000000000000..25ac3ec7de2cdcb4ca0545ac7cdb88bb0bccaf20 --- /dev/null +++ b/web/modules/libraries/tests/modules/libraries_test/libraries_test.js @@ -0,0 +1,18 @@ + +/** + * @file + * Test JavaScript file for Libraries loading. + * + * Replace the text in the 'libraries-test-javascript' div. See README.txt for + * more information. + */ + +(function ($) { + +Drupal.behaviors.librariesTest = { + attach: function(context, settings) { + $('.libraries-test-javascript').text('If this text shows up, libraries_test.js was loaded successfully.') + } +}; + +})(jQuery); diff --git a/web/modules/libraries/tests/modules/libraries_test/libraries_test.module b/web/modules/libraries/tests/modules/libraries_test/libraries_test.module new file mode 100644 index 0000000000000000000000000000000000000000..1a30ebc1c3c78ffab668b0b0a848bb370a2307cd --- /dev/null +++ b/web/modules/libraries/tests/modules/libraries_test/libraries_test.module @@ -0,0 +1,497 @@ +<?php + +/** + * @file + * Tests the library detection and loading. + */ + +use Drupal\Component\Utility\SafeMarkup; + +/** + * Implements hook_libraries_info(). + */ +function libraries_test_libraries_info() { + // Test library detection. + $libraries['example_missing'] = array( + 'name' => 'Example missing', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/missing', + ); + $libraries['example_undetected_version'] = array( + 'name' => 'Example undetected version', + 'library path' => drupal_get_path('module', 'libraries') . '/tests', + 'version callback' => '_libraries_test_return_version', + 'version arguments' => array(FALSE), + ); + $libraries['example_unsupported_version'] = array( + 'name' => 'Example unsupported version', + 'library path' => drupal_get_path('module', 'libraries') . '/tests', + 'version callback' => '_libraries_test_return_version', + 'version arguments' => array('1'), + 'versions' => array( + '2' => array(), + ), + ); + + $libraries['example_supported_version'] = array( + 'name' => 'Example supported version', + 'library path' => drupal_get_path('module', 'libraries') . '/tests', + 'version callback' => '_libraries_test_return_version', + 'version arguments' => array('1'), + 'versions' => array( + '1' => array(), + ), + ); + + // Test the default version callback. + $libraries['example_default_version_callback'] = array( + 'name' => 'Example default version callback', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'version arguments' => array( + 'file' => 'README.txt', + // Version 1 + 'pattern' => '/Version (\d+)/', + 'lines' => 5, + ), + ); + + // Test a multiple-parameter version callback. + $libraries['example_multiple_parameter_version_callback'] = array( + 'name' => 'Example multiple parameter version callback', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + // Version 1 + 'version callback' => '_libraries_test_get_version', + 'version arguments' => array('README.txt', '/Version (\d+)/', 5), + ); + + // Test a top-level files property. + $libraries['example_files'] = array( + 'name' => 'Example files', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'version' => '1', + 'files' => array( + 'js' => array('example_1.js'), + 'css' => array('example_1.css'), + 'php' => array('example_1.php'), + ), + ); + + // Test loading of integration files. + // Normally added by the corresponding module via hook_libraries_info_alter(), + // these files should be automatically loaded when the library is loaded. + $libraries['example_integration_files'] = array( + 'name' => 'Example integration files', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'version' => '1', + 'integration files' => array( + 'libraries_test' => array( + 'js' => array('libraries_test.js'), + 'css' => array('libraries_test.css'), + 'php' => array('libraries_test.inc'), + ), + ), + ); + + // Test version overloading. + $libraries['example_versions'] = array( + 'name' => 'Example versions', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'version' => '2', + 'versions' => array( + '1' => array( + 'files' => array( + 'js' => array('example_1.js'), + 'css' => array('example_1.css'), + 'php' => array('example_1.php'), + ), + ), + '2' => array( + 'files' => array( + 'js' => array('example_2.js'), + 'css' => array('example_2.css'), + 'php' => array('example_2.php'), + ), + ), + ), + ); + + // Test variant detection. + $libraries['example_variant_missing'] = array( + 'name' => 'Example variant missing', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'version' => '1', + 'variants' => array( + 'example_variant' => array( + 'files' => array( + 'js' => array('example_3.js'), + 'css' => array('example_3.css'), + 'php' => array('example_3.php'), + ), + 'variant callback' => '_libraries_test_return_installed', + 'variant arguments' => array(FALSE), + ), + ), + ); + + $libraries['example_variant'] = array( + 'name' => 'Example variant', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'version' => '1', + 'variants' => array( + 'example_variant' => array( + 'files' => array( + 'js' => array('example_3.js'), + 'css' => array('example_3.css'), + 'php' => array('example_3.php'), + ), + 'variant callback' => '_libraries_test_return_installed', + 'variant arguments' => array(TRUE), + ), + ), + ); + + // Test correct behaviour with multiple versions and multiple variants. + $libraries['example_versions_and_variants'] = array( + 'name' => 'Example versions and variants', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'version' => '2', + 'versions' => array( + '1' => array( + 'variants' => array( + 'example_variant_1' => array( + 'files' => array( + 'js' => array('example_1.js'), + 'css' => array('example_1.css'), + 'php' => array('example_1.php'), + ), + 'variant callback' => '_libraries_test_return_installed', + 'variant arguments' => array(TRUE), + ), + 'example_variant_2' => array( + 'files' => array( + 'js' => array('example_2.js'), + 'css' => array('example_2.css'), + 'php' => array('example_2.php'), + ), + 'variant callback' => '_libraries_test_return_installed', + 'variant arguments' => array(TRUE), + ), + ), + ), + '2' => array( + 'variants' => array( + 'example_variant_1' => array( + 'files' => array( + 'js' => array('example_3.js'), + 'css' => array('example_3.css'), + 'php' => array('example_3.php'), + ), + 'variant callback' => '_libraries_test_return_installed', + 'variant arguments' => array(TRUE), + ), + 'example_variant_2' => array( + 'files' => array( + 'js' => array('example_4.js'), + 'css' => array('example_4.css'), + 'php' => array('example_4.php'), + ), + 'variant callback' => '_libraries_test_return_installed', + 'variant arguments' => array(TRUE), + ), + ), + ), + ), + ); + + // Test dependency loading. + // We add one file to each library to be able to verify if it was loaded with + // libraries_load(). + // This library acts as a dependency for the libraries below. + $libraries['example_dependency'] = array( + 'name' => 'Example dependency', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'version' => '1.1', + 'files' => array('js' => array('example_1.js')), + ); + $libraries['example_dependency_missing'] = array( + 'name' => 'Example dependency missing', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'version' => '1', + 'dependencies' => array('example_missing'), + 'files' => array('js' => array('example_1.js')), + ); + $libraries['example_dependency_incompatible'] = array( + 'name' => 'Example dependency incompatible', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'version' => '1', + 'dependencies' => array('example_dependency (>1.1)'), + 'files' => array('js' => array('example_1.js')), + ); + $libraries['example_dependency_compatible'] = array( + 'name' => 'Example dependency compatible', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'version' => '1', + 'dependencies' => array('example_dependency (>=1.1)'), + 'files' => array('js' => array('example_1.js')), + ); + + // Test the applying of callbacks. + $libraries['example_callback'] = array( + 'name' => 'Example callback', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'version' => '1', + 'versions' => array( + '1' => array( + 'variants' => array( + 'example_variant' => array( + // These keys are for testing purposes only. + 'info callback' => 'not applied', + 'pre-detect callback' => 'not applied', + 'post-detect callback' => 'not applied', + 'pre-load callback' => 'not applied', + 'post-load callback' => 'not applied', + ), + ), + // These keys are for testing purposes only. + 'info callback' => 'not applied', + 'pre-detect callback' => 'not applied', + 'post-detect callback' => 'not applied', + 'pre-load callback' => 'not applied', + 'post-load callback' => 'not applied', + ), + ), + 'variants' => array( + 'example_variant' => array( + // These keys are for testing purposes only. + 'info callback' => 'not applied', + 'pre-detect callback' => 'not applied', + 'post-detect callback' => 'not applied', + 'pre-load callback' => 'not applied', + 'post-load callback' => 'not applied', + ), + ), + 'callbacks' => array( + 'info' => array('_libraries_test_info_callback'), + 'pre-detect' => array('_libraries_test_pre_detect_callback'), + 'post-detect' => array('_libraries_test_post_detect_callback'), + 'pre-load' => array('_libraries_test_pre_load_callback'), + 'post-load' => array('_libraries_test_post_load_callback'), + ), + // These keys are for testing purposes only. + 'info callback' => 'not applied', + 'pre-detect callback' => 'not applied', + 'post-detect callback' => 'not applied', + 'pre-load callback' => 'not applied', + 'post-load callback' => 'not applied', + ); + + return $libraries; +} + +/** + * Implements hook_libraries_info_file_paths() + */ +function libraries_test_libraries_info_file_paths() { + return array(drupal_get_path('module', 'libraries') . '/tests/example'); +} + +/** + * Gets the version of an example library. + * + * Returns exactly the version string entered as the $version parameter. This + * function cannot be collapsed with _libraries_test_return_installed(), because + * of the different arguments that are passed automatically. + */ +function _libraries_test_return_version($library, $version) { + return $version; +} + +/** + * Gets the version information from an arbitrary library. + * + * Test function for a version callback with multiple arguments. This is an + * exact copy of libraries_get_version(), which uses a single $option argument, + * except for the fact that it uses multiple arguments. Since we support both + * type of version callbacks, detecting the version of a test library with this + * function ensures that the arguments are passed correctly. This function might + * be a useful reference for a custom version callback that uses multiple + * parameters. + * + * @param $library + * An associative array containing all information about the library. + * @param $file + * The filename to parse for the version, relative to the library path. For + * example: 'docs/changelog.txt'. + * @param pattern + * A string containing a regular expression (PCRE) to match the library + * version. For example: '/@version (\d+)\.(\d+)/'. + * @param lines + * (optional) The maximum number of lines to search the pattern in. Defaults + * to 20. + * @param cols + * (optional) The maximum number of characters per line to take into account. + * Defaults to 200. In case of minified or compressed files, this prevents + * reading the entire file into memory. + * + * @return + * A string containing the version of the library. + * + * @see libraries_get_version() + */ +function _libraries_test_get_version($library, $file, $pattern, $lines = 20, $cols = 200) { + + $file = DRUPAL_ROOT . '/' . $library['library path'] . '/' . $file; + if (!file_exists($file)) { + return; + } + $file = fopen($file, 'r'); + while ($lines && $line = fgets($file, $cols)) { + if (preg_match($pattern, $line, $version)) { + fclose($file); + return $version[1]; + } + $lines--; + } + fclose($file); +} + +/** + * Detects the variant of an example library. + * + * Returns exactly the value of $installed, either TRUE or FALSE. This function + * cannot be collapsed with _libraries_test_return_version(), because of the + * different arguments that are passed automatically. + */ +function _libraries_test_return_installed($library, $name, $installed) { + return $installed; +} + +/** + * Sets the 'info callback' key. + * + * This function is used as a test callback for the 'info' callback group. + * + * @see _libraries_test_callback() + */ +function _libraries_test_info_callback(&$library, $version, $variant) { + _libraries_test_callback($library, $version, $variant, 'info'); +} + +/** + * Sets the 'pre-detect callback' key. + * + * This function is used as a test callback for the 'pre-detect' callback group. + * + * @see _libraries_test_callback() + */ +function _libraries_test_pre_detect_callback(&$library, $version, $variant) { + _libraries_test_callback($library, $version, $variant, 'pre-detect'); +} + +/** + * Sets the 'post-detect callback' key. + * + * This function is used as a test callback for the 'post-detect callback group. + * + * @see _libraries_test_callback() + */ +function _libraries_test_post_detect_callback(&$library, $version, $variant) { + _libraries_test_callback($library, $version, $variant, 'post-detect'); +} + +/** + * Sets the 'pre-load callback' key. + * + * This function is used as a test callback for the 'pre-load' callback group. + * + * @see _libraries_test_callback() + */ +function _libraries_test_pre_load_callback(&$library, $version, $variant) { + _libraries_test_callback($library, $version, $variant, 'pre-load'); +} + +/** + * Sets the 'post-load callback' key. + * + * This function is used as a test callback for the 'post-load' callback group. + * + * @see _libraries_test_callback() + */ +function _libraries_test_post_load_callback(&$library, $version, $variant) { + _libraries_test_callback($library, $version, $variant, 'post-load'); +} + +/** + * Sets the '[group] callback' key, where [group] is prepare, detect, or load. + * + * This function is used as a test callback for the all callback groups. + * + * It sets the '[group] callback' (see above) key to 'applied ([part])' where + * [part] is either 'top-level', 'version x.y' (where x.y is the passed-in + * version string), 'variant example' (where example is the passed-in variant + * name), or 'version x.y, variant example' (see above), depending on the part + * of the library the passed-in library information belongs to. + * + * @param $library + * An array of library information, which may be version- or variant-specific. + * Passed by reference. + * @param $version + * The version the library information passed in $library belongs to, or NULL + * if the passed library information is not version-specific. + * @param $variant + * The variant the library information passed in $library belongs to, or NULL + * if the passed library information is not variant-specific. + */ +function _libraries_test_callback(&$library, $version, $variant, $group) { + $string = 'applied'; + if (isset($version) && isset($variant)) { + $string .= " (version $version, variant $variant)"; + } + elseif (isset($version)) { + $string .= " (version $version)"; + } + elseif (isset($variant)) { + $string .= " (variant $variant)"; + } + else { + $string .= ' (top-level)'; + } + $library["$group callback"] = $string; + + // The following is used to test caching of library information. + // Only set the message for the top-level library to prevent confusing, + // duplicate messages. + if (!isset($version) && !isset($variant) && \Drupal::state()->get('libraries_test.cache', FALSE)) { + drupal_set_message(SafeMarkup::set("The <em>$group</em> callback group was invoked.")); + } +} + +/** + * Implements hook_menu(). + */ +function libraries_test_menu() { + $items['libraries_test/files'] = array( + 'title' => 'Test files', + 'route_name' => 'libraries_test_files', + ); + $items['libraries_test/integration_files'] = array( + 'title' => 'Test integration files', + 'route_name' => 'libraries_test_integration_files', + ); + $items['libraries_test/versions'] = array( + 'title' => 'Test version loading', + 'route_name' => 'libraries_test_versions', + ); + $items['libraries_test/variant'] = array( + 'title' => 'Test variant loading', + 'route_name' => 'libraries_test_variant', + ); + $items['libraries_test/versions_and_variants'] = array( + 'title' => 'Test concurrent version and variant loading', + 'route_name' => 'libraries_test_versions_and_variants', + ); + $items['libraries_test/cache'] = array( + 'title' => 'Test caching of library information', + 'route_name' => 'libraries_test_cache', + ); + return $items; +} diff --git a/web/modules/libraries/tests/modules/libraries_test/libraries_test.routing.yml b/web/modules/libraries/tests/modules/libraries_test/libraries_test.routing.yml new file mode 100644 index 0000000000000000000000000000000000000000..a11f0bececf331cf93796d8a6031eaa6aff2c4e5 --- /dev/null +++ b/web/modules/libraries/tests/modules/libraries_test/libraries_test.routing.yml @@ -0,0 +1,36 @@ +libraries_test_files: + path: '/libraries_test/files' + defaults: + _controller: Drupal\libraries_test\Controller\ExampleController::files + requirements: + _access: 'TRUE' +libraries_test_integration_files: + path: '/libraries_test/integration_files' + defaults: + _controller: Drupal\libraries_test\Controller\ExampleController::integration + requirements: + _access: 'TRUE' +libraries_test_versions: + path: '/libraries_test/versions' + defaults: + _controller: Drupal\libraries_test\Controller\ExampleController::versions + requirements: + _access: 'TRUE' +libraries_test_variant: + path: '/libraries_test/variant' + defaults: + _controller: Drupal\libraries_test\Controller\ExampleController::variant + requirements: + _access: 'TRUE' +libraries_test_versions_and_variants: + path: '/libraries_test/versions_and_variants' + defaults: + _controller: Drupal\libraries_test\Controller\ExampleController::versionsAndVariants + requirements: + _access: 'TRUE' +libraries_test_cache: + path: '/libraries_test/cache' + defaults: + _controller: Drupal\libraries_test\Controller\ExampleController::cache + requirements: + _access: 'TRUE' diff --git a/web/modules/libraries/tests/modules/libraries_test/src/Controller/ExampleController.php b/web/modules/libraries/tests/modules/libraries_test/src/Controller/ExampleController.php new file mode 100644 index 0000000000000000000000000000000000000000..217ebf2f8ab4028aff545bd7560818355f602957 --- /dev/null +++ b/web/modules/libraries/tests/modules/libraries_test/src/Controller/ExampleController.php @@ -0,0 +1,94 @@ +<?php + +namespace Drupal\libraries_test\Controller; + +use Drupal\Core\DependencyInjection\ContainerInjectionInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +class ExampleController implements ContainerInjectionInterface { + + /** + * Injects BookManager Service. + */ + public static function create(ContainerInterface $container) { + return new static(); + } + + /** + * Loads a specified library (variant) for testing. + * + * JavaScript and CSS files can be checked directly by SimpleTest, so we only + * need to manually check for PHP files. We provide information about the loaded + * JavaScript and CSS files for easier debugging. See example/README.txt for + * more information. + */ + private function buildPage($library, $variant = NULL) { + libraries_load($library, $variant); + // JavaScript and CSS files can be checked directly by SimpleTest, so we only + // need to manually check for PHP files. + $output = ''; + + // For easer debugging of JS loading, a text is shown that the JavaScript will + // replace. + $output .= '<h2>JavaScript</h2>'; + $output .= '<div class="libraries-test-javascript">'; + $output .= 'If this text shows up, no JavaScript test file was loaded.'; + $output .= '</div>'; + + // For easier debugging of CSS loading, the loaded CSS files will color the + // following text. + $output .= '<h2>CSS</h2>'; + $output .= '<div class="libraries-test-css">'; + $output .= 'If one of the CSS test files has been loaded, this text will be colored:'; + $output .= '<ul>'; + // Do not reference the actual CSS files (i.e. including '.css'), because that + // breaks testing. + $output .= '<li>example_1: red</li>'; + $output .= '<li>example_2: green</li>'; + $output .= '<li>example_3: orange</li>'; + $output .= '<li>example_4: blue</li>'; + $output .= '<li>libraries_test: purple</li>'; + $output .= '</ul>'; + $output .= '</div>'; + + $output .= '<h2>PHP</h2>'; + $output .= '<div class="libraries-test-php">'; + $output .= 'The following is a list of all loaded test PHP files:'; + $output .= '<ul>'; + $files = get_included_files(); + foreach ($files as $file) { + if ((strpos($file, 'libraries/test') || strpos($file, 'libraries_test')) && !strpos($file, 'libraries_test.module') && !strpos($file, 'lib/Drupal/libraries_test')) { + $output .= '<li>' . str_replace(DRUPAL_ROOT . '/', '', $file) . '</li>'; + } + } + $output .= '</ul>'; + $output .= '</div>'; + + return ['#markup' => $output]; + } + + public function files() { + return $this->buildPage('example_files'); + } + + public function integration() { + return $this->buildPage('example_integration_files'); + } + + public function versions() { + return $this->buildPage('example_versions'); + } + + public function variant() { + return $this->buildPage('example_variant', 'example_variant'); + } + + public function versionsAndVariants() { + return $this->buildPage('example_versions_and_variants', 'example_variant_2'); + } + + public function cache() { + return $this->buildPage('example_callback'); + } + +} diff --git a/web/modules/libraries/tests/src/Functional/ExternalLibrary/Definition/DefinitionDiscoveryFactoryTest.php b/web/modules/libraries/tests/src/Functional/ExternalLibrary/Definition/DefinitionDiscoveryFactoryTest.php new file mode 100644 index 0000000000000000000000000000000000000000..14bf74e4f6ecfbbd5e1a7a693c4a88349af084be --- /dev/null +++ b/web/modules/libraries/tests/src/Functional/ExternalLibrary/Definition/DefinitionDiscoveryFactoryTest.php @@ -0,0 +1,118 @@ +<?php + +namespace Drupal\Tests\libraries\Functional\ExternalLibrary\Definition; + +use Drupal\Tests\BrowserTestBase; + +/** + * Tests that remote library definitions are found and downloaded. + * + * This is a browser test because Guzzle is not usable from a kernel test. + * + * @group libraries + * + * @todo Make this a kernel test when https://www.drupal.org/node/2571475 is in. + */ +class DefinitionDiscoveryFactoryTest extends BrowserTestBase { + + /** + * The 'libraries.settings' configuration object. + * + * @var \Drupal\Core\Config\Config + */ + protected $config; + + /** + * The path to the test library definitions. + * + * @var string + */ + protected $definitionPath; + + /** + * {@inheritdoc} + */ + public static $modules = ['libraries']; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + /** @var \Drupal\Core\Config\ConfigFactoryInterface $config_factory */ + $config_factory = $this->container->get('config.factory'); + $this->config = $config_factory->getEditable('libraries.settings'); + + // Set up the remote library definition URL to point to the local website. + /** @var \Drupal\Core\Extension\ModuleHandlerInterface $module_handler */ + $module_handler = $this->container->get('module_handler'); + $module_path = $module_handler->getModule('libraries')->getPath(); + $this->definitionPath = "$module_path/tests/library_definitions"; + } + + /** + * Tests that the discovery works according to the configuration. + */ + public function testDiscovery() { + $library_id = 'test_asset_library'; + $expected_definition = [ + 'type' => 'asset', + 'version_detector' => [ + 'id' => 'static', + 'configuration' => [ + 'version' => '1.0.0' + ], + ], + 'remote_url' => 'http://example.com', + 'css' => [ + 'base' => [ + 'example.css' => [], + ], + ], + 'js' => [ + 'example.js' => [], + ], + ]; + $discovery_service_id = 'libraries.definition.discovery'; + + // Test the local discovery with an incorrect path. + $this->config + ->set('definition.local.path', 'path/that/does/not/exist') + ->set('definition.remote.enable', FALSE) + ->save(); + $discovery = $this->container->get($discovery_service_id); + $this->assertFalse($discovery->hasDefinition($library_id)); + + // Test the local discovery with a proper path. + $this->config + ->set('definition.local.path', $this->definitionPath) + ->save(); + $discovery = $this->container->get($discovery_service_id); + $this->assertTrue($discovery->hasDefinition($library_id)); + + // Test a remote discovery with an incorrect path. + $definitions_directory = 'public://library-definitions'; + $this->config + ->set('definition.local.path', $definitions_directory) + ->set('definition.remote.enable', TRUE) + ->set('definition.remote.urls', ["$this->baseUrl/path/that/does/not/exist"]) + ->save(); + $discovery = $this->container->get($discovery_service_id); + $this->assertFalse($discovery->hasDefinition($library_id)); + + // Test a remote discovery with a proper path. + $this->config + ->set('definition.remote.urls', ["$this->baseUrl/$this->definitionPath"]) + ->save(); + /** @var \Drupal\libraries\ExternalLibrary\Definition\DefinitionDiscoveryInterface $discovery */ + $discovery = $this->container->get($discovery_service_id); + $definition_file = "$definitions_directory/$library_id.json"; + $this->assertFalse(file_exists($definition_file)); + $this->assertTrue($discovery->hasDefinition($library_id)); + $this->assertFalse(file_exists($definition_file)); + $this->assertEquals($discovery->getDefinition($library_id), $expected_definition); + $this->assertTrue(file_exists($definition_file)); + } + +} diff --git a/web/modules/libraries/tests/src/Kernel/ExternalLibrary/Asset/AssetLibraryTest.php b/web/modules/libraries/tests/src/Kernel/ExternalLibrary/Asset/AssetLibraryTest.php new file mode 100644 index 0000000000000000000000000000000000000000..cb2551e22fcd2132a1b8b1b8f267c246be1d67b8 --- /dev/null +++ b/web/modules/libraries/tests/src/Kernel/ExternalLibrary/Asset/AssetLibraryTest.php @@ -0,0 +1,114 @@ +<?php + +namespace Drupal\Tests\libraries\Kernel\ExternalLibrary\Asset; + +use Drupal\Tests\libraries\Kernel\ExternalLibrary\TestLibraryFilesStream; + +/** + * Tests that external asset libraries are registered as core asset libraries. + * + * @group libraries + */ +class AssetLibraryTest extends AssetLibraryTestBase { + + /** + * {@inheritdoc} + */ + protected function getLibraryTypeId() { + return 'asset'; + } + + /** + * Tests that attachable asset library info is correctly gathered. + */ + public function testAttachableAssetInfo() { + /** @var \Drupal\libraries\ExternalLibrary\Asset\AttachableAssetLibraryRegistrationInterface $library_type */ + $library_type = $this->getLibraryType(); + $library = $this->getLibrary(); + $expected = [ + 'test_asset_library' => [ + 'version' => '1.0.0', + 'css' => ['base' => ['http://example.com/example.css' => []]], + 'js' => ['http://example.com/example.js' => []], + 'dependencies' => [], + ], + ]; + $this->assertEquals($expected, $library_type->getAttachableAssetLibraries($library, $this->libraryManager)); + } + + /** + * Tests that a remote asset library is registered as a core asset library. + * + * @see \Drupal\libraries\Extension\Extension + * @see \Drupal\libraries\Extension\ExtensionHandler + * @see \Drupal\libraries\ExternalLibrary\Asset\AssetLibrary + * @see \Drupal\libraries\ExternalLibrary\Asset\AssetLibraryTrait + * @see \Drupal\libraries\ExternalLibrary\ExternalLibraryManager + * @see \Drupal\libraries\ExternalLibrary\ExternalLibraryTrait + * @see \Drupal\libraries\ExternalLibrary\Registry\ExternalLibraryRegistry + */ + public function testAssetLibraryRemote() { + $library = $this->coreLibraryDiscovery->getLibraryByName('libraries', 'test_asset_library'); + $expected = [ + 'version' => '1.0.0', + 'css' => [[ + 'weight' => -200, + 'group' => 0, + 'type' => 'external', + 'data' => 'http://example.com/example.css', + 'version' => '1.0.0', + ]], + 'js' => [[ + 'group' => -100, + 'type' => 'external', + 'data' => 'http://example.com/example.js', + 'version' => '1.0.0', + ]], + 'dependencies' => [], + 'license' => [ + 'name' => 'GNU-GPL-2.0-or-later', + 'url' => 'https://www.drupal.org/licensing/faq', + 'gpl-compatible' => TRUE, + ] + ]; + $this->assertEquals($expected, $library); + } + + /** + * Tests that a local asset library is registered as a core asset library. + */ + public function testAssetLibraryLocal() { + $this->container->set('stream_wrapper.asset_libraries', new TestLibraryFilesStream( + $this->container->get('module_handler'), + $this->container->get('string_translation'), + 'assets/vendor' + )); + $this->coreLibraryDiscovery->clearCachedDefinitions(); + $library = $this->coreLibraryDiscovery->getLibraryByName('libraries', 'test_asset_library'); + $expected = [ + 'version' => '1.0.0', + 'css' => [[ + 'weight' => -200, + 'group' => 0, + 'type' => 'file', + 'data' => $this->modulePath . '/tests/assets/vendor/test_asset_library/example.css', + 'version' => '1.0.0', + ]], + 'js' => [[ + 'group' => -100, + 'type' => 'file', + 'data' => $this->modulePath . '/tests/assets/vendor/test_asset_library/example.js', + 'version' => '1.0.0', + 'minified' => FALSE, + ]], + 'dependencies' => [], + 'license' => [ + 'name' => 'GNU-GPL-2.0-or-later', + 'url' => 'https://www.drupal.org/licensing/faq', + 'gpl-compatible' => TRUE, + ] + ]; + $this->assertEquals($expected, $library); + } + +} diff --git a/web/modules/libraries/tests/src/Kernel/ExternalLibrary/Asset/AssetLibraryTestBase.php b/web/modules/libraries/tests/src/Kernel/ExternalLibrary/Asset/AssetLibraryTestBase.php new file mode 100644 index 0000000000000000000000000000000000000000..4f835b72550fd0bf5909aeb5d0d6667b13af9978 --- /dev/null +++ b/web/modules/libraries/tests/src/Kernel/ExternalLibrary/Asset/AssetLibraryTestBase.php @@ -0,0 +1,36 @@ +<?php + +namespace Drupal\Tests\libraries\Kernel\ExternalLibrary\Asset; + +use Drupal\Tests\libraries\Kernel\LibraryTypeKernelTestBase; + +/** + * Provides a base test class for asset library type tests. + */ +abstract class AssetLibraryTestBase extends LibraryTypeKernelTestBase { + + /** + * {@inheritdoc} + * + * LibraryManager requires system_get_info() which is in system.module. + * + * @see \Drupal\libraries\ExternalLibrary\LibraryManager::getRequiredLibraryIds() + */ + public static $modules = ['system']; + + /** + * The Drupal core library discovery. + * + * @var \Drupal\Core\Asset\LibraryDiscoveryInterface + */ + protected $coreLibraryDiscovery; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + $this->coreLibraryDiscovery = $this->container->get('library.discovery'); + } + +} diff --git a/web/modules/libraries/tests/src/Kernel/ExternalLibrary/Asset/MultipleAssetLibraryTest.php b/web/modules/libraries/tests/src/Kernel/ExternalLibrary/Asset/MultipleAssetLibraryTest.php new file mode 100644 index 0000000000000000000000000000000000000000..4a2af5c3d7a7424a13dd1d30faa93c0d040be8fe --- /dev/null +++ b/web/modules/libraries/tests/src/Kernel/ExternalLibrary/Asset/MultipleAssetLibraryTest.php @@ -0,0 +1,172 @@ +<?php + +namespace Drupal\Tests\libraries\Kernel\ExternalLibrary\Asset; + +use Drupal\Tests\libraries\Kernel\ExternalLibrary\TestLibraryFilesStream; + +/** + * Tests that external asset libraries can register multiple core libraries. + * + * @group libraries + */ +class MultipleAssetLibraryTest extends AssetLibraryTestBase { + + /** + * {@inheritdoc} + */ + protected function getLibraryTypeId() { + return 'asset_multiple'; + } + + /** + * Tests that attachable asset library info is correctly gathered. + */ + public function testAttachableAssetInfo() { + /** @var \Drupal\libraries\ExternalLibrary\Asset\AttachableAssetLibraryRegistrationInterface $library_type */ + $library_type = $this->getLibraryType(); + $library = $this->getLibrary(); + $expected = [ + 'test_asset_multiple_library.first' => [ + 'version' => '1.0.0', + 'css' => ['base' => ['http://example.com/example.first.css' => []]], + 'js' => ['http://example.com/example.first.js' => []], + 'dependencies' => [], + ], + 'test_asset_multiple_library.second' => [ + 'version' => '1.0.0', + 'css' => ['base' => ['http://example.com/example.second.css' => []]], + 'js' => ['http://example.com/example.second.js' => []], + 'dependencies' => [], + ], + ]; + $this->assertEquals($expected, $library_type->getAttachableAssetLibraries($library, $this->libraryManager)); + } + + /** + * Tests that a remote asset library is registered as a core asset library. + * + * @see \Drupal\libraries\Extension\Extension + * @see \Drupal\libraries\Extension\ExtensionHandler + * @see \Drupal\libraries\ExternalLibrary\Asset\AssetLibrary + * @see \Drupal\libraries\ExternalLibrary\Asset\AssetLibraryTrait + * @see \Drupal\libraries\ExternalLibrary\ExternalLibraryManager + * @see \Drupal\libraries\ExternalLibrary\ExternalLibraryTrait + * @see \Drupal\libraries\ExternalLibrary\Registry\ExternalLibraryRegistry + */ + public function testAssetLibraryRemote() { + $library = $this->coreLibraryDiscovery->getLibraryByName('libraries', 'test_asset_multiple_library.first'); + $expected = [ + 'version' => '1.0.0', + 'css' => [[ + 'weight' => -200, + 'group' => 0, + 'type' => 'external', + 'data' => 'http://example.com/example.first.css', + 'version' => '1.0.0', + ]], + 'js' => [[ + 'group' => -100, + 'type' => 'external', + 'data' => 'http://example.com/example.first.js', + 'version' => '1.0.0', + ]], + 'dependencies' => [], + 'license' => [ + 'name' => 'GNU-GPL-2.0-or-later', + 'url' => 'https://www.drupal.org/licensing/faq', + 'gpl-compatible' => TRUE, + ] + ]; + $this->assertEquals($expected, $library); + + $library = $this->coreLibraryDiscovery->getLibraryByName('libraries', 'test_asset_multiple_library.second'); + $expected = [ + 'version' => '1.0.0', + 'css' => [[ + 'weight' => -200, + 'group' => 0, + 'type' => 'external', + 'data' => 'http://example.com/example.second.css', + 'version' => '1.0.0', + ]], + 'js' => [[ + 'group' => -100, + 'type' => 'external', + 'data' => 'http://example.com/example.second.js', + 'version' => '1.0.0', + ]], + 'dependencies' => [], + 'license' => [ + 'name' => 'GNU-GPL-2.0-or-later', + 'url' => 'https://www.drupal.org/licensing/faq', + 'gpl-compatible' => TRUE, + ] + ]; + $this->assertEquals($expected, $library); + } + + /** + * Tests that a local asset library is registered as a core asset library. + */ + public function testAssetLibraryLocal() { + $this->container->set('stream_wrapper.asset_libraries', new TestLibraryFilesStream( + $this->container->get('module_handler'), + $this->container->get('string_translation'), + 'assets/vendor' + )); + $this->coreLibraryDiscovery->clearCachedDefinitions(); + + $library = $this->coreLibraryDiscovery->getLibraryByName('libraries', 'test_asset_multiple_library.first'); + $expected = [ + 'version' => '1.0.0', + 'css' => [[ + 'weight' => -200, + 'group' => 0, + 'type' => 'file', + 'data' => $this->modulePath . '/tests/assets/vendor/test_asset_multiple_library/example.first.css', + 'version' => '1.0.0', + ]], + 'js' => [[ + 'group' => -100, + 'type' => 'file', + 'data' => $this->modulePath . '/tests/assets/vendor/test_asset_multiple_library/example.first.js', + 'version' => '1.0.0', + 'minified' => FALSE, + ]], + 'dependencies' => [], + 'license' => [ + 'name' => 'GNU-GPL-2.0-or-later', + 'url' => 'https://www.drupal.org/licensing/faq', + 'gpl-compatible' => TRUE, + ] + ]; + $this->assertEquals($expected, $library); + + $library = $this->coreLibraryDiscovery->getLibraryByName('libraries', 'test_asset_multiple_library.second'); + $expected = [ + 'version' => '1.0.0', + 'css' => [[ + 'weight' => -200, + 'group' => 0, + 'type' => 'file', + 'data' => $this->modulePath . '/tests/assets/vendor/test_asset_multiple_library/example.second.css', + 'version' => '1.0.0', + ]], + 'js' => [[ + 'group' => -100, + 'type' => 'file', + 'data' => $this->modulePath . '/tests/assets/vendor/test_asset_multiple_library/example.second.js', + 'version' => '1.0.0', + 'minified' => FALSE, + ]], + 'dependencies' => [], + 'license' => [ + 'name' => 'GNU-GPL-2.0-or-later', + 'url' => 'https://www.drupal.org/licensing/faq', + 'gpl-compatible' => TRUE, + ] + ]; + $this->assertEquals($expected, $library); + } + +} diff --git a/web/modules/libraries/tests/src/Kernel/ExternalLibrary/GlobalLocatorTest.php b/web/modules/libraries/tests/src/Kernel/ExternalLibrary/GlobalLocatorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..84eb86eb1e532f06e8ded013c6f9b6da93587d34 --- /dev/null +++ b/web/modules/libraries/tests/src/Kernel/ExternalLibrary/GlobalLocatorTest.php @@ -0,0 +1,53 @@ +<?php + +namespace Drupal\Tests\libraries\Kernel\ExternalLibrary; + +use Drupal\Tests\libraries\Kernel\ExternalLibrary\TestLibraryFilesStream; +use Drupal\Tests\libraries\Kernel\LibraryTypeKernelTestBase; + +/** + * Tests that a global locator can be properly used to load a libraries. + * + * @group libraries + */ +class GlobalLocatorTest extends LibraryTypeKernelTestBase { + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + // Assign our test stream (which points to the test php lib) to the asset + // scheme. This gives us a scheme to work with in the test that is not + // used to locate a php lib by default. + $this->container->set('stream_wrapper.asset_libraries', new TestLibraryFilesStream( + $this->container->get('module_handler'), + $this->container->get('string_translation'), + 'libraries' + )); + } + + /** + * {@inheritdoc} + */ + protected function getLibraryTypeId() { + return 'php_file'; + } + + /** + * Tests that the library is located via the global loactor. + */ + public function testGlobalLocator() { + // By default the library will not be locatable (control assertion) until we + // add the asset stream to the global loctors conf list. + $library = $this->getLibrary(); + $this->assertFalse($library->isInstalled()); + $config_factory = $this->container->get('config.factory'); + $config_factory->getEditable('libraries.settings') + ->set('global_locators', [['id' => 'uri', 'configuration' => ['uri' => 'asset://']]]) + ->save(); + $library = $this->getLibrary(); + $this->assertTrue($library->isInstalled()); + } + +} diff --git a/web/modules/libraries/tests/src/Kernel/ExternalLibrary/PhpFile/PhpFileLibraryTest.php b/web/modules/libraries/tests/src/Kernel/ExternalLibrary/PhpFile/PhpFileLibraryTest.php new file mode 100644 index 0000000000000000000000000000000000000000..10c1d2cfee45cc789d1632925badba861e15391a --- /dev/null +++ b/web/modules/libraries/tests/src/Kernel/ExternalLibrary/PhpFile/PhpFileLibraryTest.php @@ -0,0 +1,66 @@ +<?php + +namespace Drupal\Tests\libraries\Kernel\ExternalLibrary\PhpFile; + +use Drupal\Tests\libraries\Kernel\ExternalLibrary\TestLibraryFilesStream; +use Drupal\Tests\libraries\Kernel\LibraryTypeKernelTestBase; + +/** + * Tests that the external library manager properly loads PHP file libraries. + * + * @group libraries + */ +class PhpFileLibraryTest extends LibraryTypeKernelTestBase { + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->container->set('stream_wrapper.php_file_libraries', new TestLibraryFilesStream( + $this->container->get('module_handler'), + $this->container->get('string_translation'), + 'libraries' + )); + } + + /** + * {@inheritdoc} + */ + protected function getLibraryTypeId() { + return 'php_file'; + } + + /** + * Tests that the list of PHP files is correctly gathered. + */ + public function testPhpFileInfo() { + /** @var \Drupal\libraries\ExternalLibrary\PhpFile\PhpFileLibrary $library */ + $library = $this->getLibrary(); + $this->assertTrue($library->isInstalled()); + $library_path = $this->modulePath . '/tests/libraries/test_php_file_library'; + $this->assertEquals($library_path, $library->getLocalPath()); + $this->assertEquals(["$library_path/test_php_file_library.php"], $library->getPhpFiles()); + } + + /** + * Tests that the external library manager properly loads PHP files. + * + * @see \Drupal\libraries\ExternalLibrary\ExternalLibraryManager + * @see \Drupal\libraries\ExternalLibrary\ExternalLibraryTrait + * @see \Drupal\libraries\ExternalLibrary\PhpFile\PhpRequireLoader + */ + public function testFileLoading() { + $function_name = '_libraries_test_php_function'; + if (function_exists($function_name)) { + $this->markTestSkipped('Cannot test file inclusion if the file to be included has already been included prior.'); + return; + } + + $this->assertFalse(function_exists($function_name)); + $this->libraryManager->load('test_php_file_library'); + $this->assertTrue(function_exists($function_name)); + } + +} diff --git a/web/modules/libraries/tests/src/Kernel/ExternalLibrary/TestLibraryFilesStream.php b/web/modules/libraries/tests/src/Kernel/ExternalLibrary/TestLibraryFilesStream.php new file mode 100644 index 0000000000000000000000000000000000000000..ec20647ead3187b8a3cee8a35cd5dfc9d101adb4 --- /dev/null +++ b/web/modules/libraries/tests/src/Kernel/ExternalLibrary/TestLibraryFilesStream.php @@ -0,0 +1,80 @@ +<?php + +namespace Drupal\Tests\libraries\Kernel\ExternalLibrary; + +use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\StreamWrapper\LocalStream; +use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\Core\StringTranslation\TranslationInterface; +use Drupal\libraries\StreamWrapper\LocalHiddenStreamTrait; +use Drupal\libraries\StreamWrapper\PrivateStreamTrait; + +/** + * Provides a stream wrapper for accessing test library files. + */ +class TestLibraryFilesStream extends LocalStream { + + use LocalHiddenStreamTrait; + use PrivateStreamTrait; + use StringTranslationTrait; + + /** + * The module handler. + * + * @var \Drupal\Core\Extension\ModuleHandlerInterface + */ + protected $moduleHandler; + + /** + * The test directory. + * + * @var string + */ + protected $directory; + + /** + * Constructs a stream wrapper for test library files. + * + * Dependency injection is generally not possible to implement for stream + * wrappers, because stream wrappers are initialized before the container is + * booted, but this stream wrapper is only registered explicitly from tests + * so it is possible here. + * + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * The module handler. + * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation + * The string translation handler. + * @param string $directory + * The directory within the Libraries API's tests directory that is to be + * searched for test library files. + */ + public function __construct(ModuleHandlerInterface $module_handler, TranslationInterface $string_translation, $directory) { + $this->moduleHandler = $module_handler; + $this->directory = (string) $directory; + + $this->setStringTranslation($string_translation); + } + + /** + * {@inheritdoc} + */ + public function getName() { + $this->t('Test library files'); + } + + /** + * {@inheritdoc} + */ + public function getDescription() { + $this->t('Provides access to test library files.'); + } + + /** + * {@inheritdoc} + */ + public function getDirectoryPath() { + $module_path = $this->moduleHandler->getModule('libraries')->getPath(); + return $module_path . '/tests/' . $this->directory; + } + +} diff --git a/web/modules/libraries/tests/src/Kernel/LibraryTypeKernelTestBase.php b/web/modules/libraries/tests/src/Kernel/LibraryTypeKernelTestBase.php new file mode 100644 index 0000000000000000000000000000000000000000..8185794763c2cb88df2c3479805987cf2a02e68e --- /dev/null +++ b/web/modules/libraries/tests/src/Kernel/LibraryTypeKernelTestBase.php @@ -0,0 +1,171 @@ +<?php + +namespace Drupal\Tests\libraries\Kernel; + +use Drupal\Component\Plugin\Exception\PluginException; +use Drupal\KernelTests\KernelTestBase; +use Drupal\libraries\ExternalLibrary\Exception\LibraryDefinitionNotFoundException; +use Drupal\libraries\ExternalLibrary\Exception\LibraryTypeNotFoundException; +use Drupal\libraries\ExternalLibrary\LibraryInterface; +use Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface; + +/** + * Provides an improved version of the core kernel test base class. + */ +abstract class LibraryTypeKernelTestBase extends KernelTestBase { + + /** + * The external library manager. + * + * @var \Drupal\libraries\ExternalLibrary\LibraryManagerInterface + */ + protected $libraryManager; + + /** + * The library type factory. + * + * @var \Drupal\Component\Plugin\Factory\FactoryInterface + */ + protected $libraryTypeFactory; + + /** + * The absolute path to the Libraries API module. + * + * @var string + */ + protected $modulePath; + + /** + * {@inheritdoc} + */ + public static $modules = ['libraries', 'libraries_test']; + + /** + * Gets the ID of the library type that is being tested. + * + * @return string + */ + abstract protected function getLibraryTypeId(); + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + /** @var \Drupal\Core\Extension\ModuleHandlerInterface $module_handler */ + $module_handler = $this->container->get('module_handler'); + $this->modulePath = $module_handler->getModule('libraries')->getPath(); + + $this->installConfig('libraries'); + // Disable remote definition fetching and set the local definitions path to + // the module directory. + /** @var \Drupal\Core\Config\ConfigFactoryInterface $config_factory */ + $config_factory = $this->container->get('config.factory'); + $config_factory->getEditable('libraries.settings') + ->set('definition.local.path', "{$this->modulePath}/tests/library_definitions") + ->set('definition.remote.enable', FALSE) + ->save(); + + // LibrariesConfigSubscriber::onConfigSave() invalidates the container so + // that it is rebuilt on the next request. We need the container rebuilt + // immediately, however. + /** @var \Drupal\Core\DrupalKernelInterface $kernel */ + $kernel = $this->container->get('kernel'); + $this->container = $kernel->rebuildContainer(); + + $this->libraryManager = $this->container->get('libraries.manager'); + $this->libraryTypeFactory = $this->container->get('plugin.manager.libraries.library_type'); + } + + /** + * Tests that the library type can be instantiated. + */ + public function testLibraryType() { + $type_id = $this->getLibraryTypeId(); + try { + $this->libraryTypeFactory->createInstance($type_id); + $this->assertTrue(TRUE, "Library type '$type_id' can be instantiated."); + } + catch (PluginException $exception) { + $this->fail("Library type '$type_id' cannot be instantiated."); + } + } + + /** + * Tests that the test library can be instantiated. + */ + public function testLibrary() { + $type_id = $this->getLibraryTypeId(); + $id = $this->getLibraryId(); + try { + $library = $this->libraryManager->getLibrary($id); + $this->assertTrue(TRUE, "Test $type_id library can be instantiated."); + $this->assertInstanceOf($this->getLibraryType()->getLibraryClass(), $library); + $this->assertEquals($this->getLibraryId(), $library->getId()); + + } + catch (LibraryDefinitionNotFoundException $exception) { + $this->fail("Missing library definition for test $type_id library."); + } + catch (LibraryTypeNotFoundException $exception) { + $this->fail("Missing library type declaration for test $type_id library."); + } + } + + /** + * Returns the library type that is being tested. + * + * @return \Drupal\libraries\ExternalLibrary\Type\LibraryTypeInterface + * The test library type. + */ + protected function getLibraryType() { + try { + $library_type = $this->libraryTypeFactory->createInstance($this->getLibraryTypeId()); + } + catch (PluginException $exception) { + $library_type = $this->prophesize(LibraryTypeInterface::class)->reveal(); + } + finally { + return $library_type; + } + } + + /** + * Retuns the library ID of the library used in the test. + * + * Defaults to 'test_[library_type]_library', where [library_type] is the + * ID of the library type being tested. + * + * @return string + */ + protected function getLibraryId() { + $type_id = $this->getLibraryTypeId(); + return "test_{$type_id}_library"; + } + + /** + * Returns the test library for this library type. + * + * @return \Drupal\libraries\ExternalLibrary\LibraryInterface + * The test library. + */ + protected function getLibrary() { + try { + $library = $this->libraryManager->getLibrary($this->getLibraryId()); + } + catch (LibraryDefinitionNotFoundException $exception) { + $library = $this->prophesize(LibraryInterface::class)->reveal(); + } + catch (LibraryTypeNotFoundException $exception) { + $library = $this->prophesize(LibraryInterface::class)->reveal(); + } + catch (PluginException $exception) { + $library = $this->prophesize(LibraryInterface::class)->reveal(); + } + finally { + return $library; + } + } + +} diff --git a/web/modules/libraries/tests/src/Unit/Plugin/libraries/VersionDetector/LinePatternDetectorTest.php b/web/modules/libraries/tests/src/Unit/Plugin/libraries/VersionDetector/LinePatternDetectorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..5e4e34b93148f7b195e3bae014ac18db940870c5 --- /dev/null +++ b/web/modules/libraries/tests/src/Unit/Plugin/libraries/VersionDetector/LinePatternDetectorTest.php @@ -0,0 +1,197 @@ +<?php + +namespace Drupal\Tests\libraries\Unit\Plugin\libraries\VersionDetector; + +use Drupal\libraries\ExternalLibrary\Local\LocalLibraryInterface; +use Drupal\libraries\ExternalLibrary\Version\VersionedLibraryInterface; +use Drupal\libraries\Plugin\libraries\VersionDetector\LinePatternDetector; +use Drupal\Tests\UnitTestCase; +use org\bovigo\vfs\vfsStream; + +/** + * Tests the line pattern version detector. + * + * @group libraries + * + * @coversDefaultClass \Drupal\libraries\Plugin\libraries\VersionDetector\LinePatternDetector + */ +class LinePatternDetectorTest extends UnitTestCase { + + protected $libraryId = 'test_library'; + + /** + * Tests that version detection fails for a non-local library. + * + * @expectedException \Drupal\libraries\ExternalLibrary\Exception\UnknownLibraryVersionException + * + * @covers ::detectVersion + */ + public function testDetectVersionNonLocal() { + $library = $this->prophesize(VersionedLibraryInterface::class); + $detector = $this->setupDetector(); + $detector->detectVersion($library->reveal()); + } + + /** + * Tests that version detection fails for a missing file. + * + * @expectedException \Drupal\libraries\ExternalLibrary\Exception\UnknownLibraryVersionException + * + * @covers ::detectVersion + */ + public function testDetectVersionMissingFile() { + $library = $this->setupLibrary(); + + $detector = $this->setupDetector(['file' => 'CHANGELOG.txt']); + $detector->detectVersion($library->reveal()); + } + + /** + * Tests that version detection fails without a version in the file. + * + * @dataProvider providerTestDetectVersionNoVersion + * + * @covers ::detectVersion + */ + public function testDetectVersionNoVersion($configuration, $file_contents) { + $library = $this->setupLibrary(); + + $detector = $this->setupDetector($configuration); + $this->setupFile($configuration['file'], $file_contents); + + $library->setVersion()->shouldNotBeCalled(); + $detector->detectVersion($library->reveal()); + } + + /** + * @return array + */ + public function providerTestDetectVersionNoVersion() { + $test_cases = []; + + $configuration = [ + 'file' => 'CHANGELOG.txt', + 'pattern' => '/@version (\d+\.\d+\.\d+)/' + ]; + + $test_cases['empty_file'] = [$configuration, '']; + + $test_cases['no_version'] = [$configuration, <<<EOF +This is a file with +multiple lines that does +not contain a version. +EOF + ]; + + $configuration['lines'] = 3; + $test_cases['long_file'] = [$configuration, <<<EOF +This is a file that +contains the version after +the maximum number of lines +to test has been surpassed. + +@version 1.2.3 +EOF + ]; + + $configuration['columns'] = 10; + // @todo Document why this is necessary. + $configuration['lines'] = 2; + $test_cases['long_column'] = [$configuration, <<<EOF +This is a file that contains the version after +the maximum number of columns to test has been surpassed. @version 1.2.3 +EOF + ]; + + return $test_cases; + } + + /** + * Tests that version detection succeeds with a version in the file. + * + * @dataProvider providerTestDetectVersion + * + * @covers ::detectVersion + */ + public function testDetectVersion($configuration, $file_contents, $version) { + $library = $this->setupLibrary(); + + $detector = $this->setupDetector($configuration); + $this->setupFile($configuration['file'], $file_contents); + + $library->setVersion($version)->shouldBeCalled(); + $detector->detectVersion($library->reveal()); + } + + /** + * @return array + */ + public function providerTestDetectVersion() { + $test_cases = []; + + $configuration = [ + 'file' => 'CHANGELOG.txt', + 'pattern' => '/@version (\d+\.\d+\.\d+)/' + ]; + $version = '1.2.3'; + + $test_cases['version'] = [$configuration, <<<EOF +This a file with a version + +@version $version +EOF + , $version]; + + return $test_cases; + } + + /** + * Sets up the library prophecy and returns it. + * + * @return \Prophecy\Prophecy\ObjectProphecy + */ + protected function setupLibrary() { + $library = $this->prophesize(VersionedLibraryInterface::class); + $library->willImplement(LocalLibraryInterface::class); + $library->getId()->willReturn($this->libraryId); + $library->getLocalPath()->willReturn('libraries/' . $this->libraryId); + return $library; + } + + /** + * Sets up the version detector for testing and returns it. + * + * @param array $configuration + * The plugin configuration to set the version detector up with. + * + * @return \Drupal\libraries\Plugin\libraries\VersionDetector\LinePatternDetector + * The line pattern version detector to test. + */ + protected function setupDetector(array $configuration = []) { + $app_root = 'root'; + vfsStream::setup($app_root); + + $plugin_id = 'line_pattern'; + $plugin_definition = [ + 'id' => $plugin_id, + 'class' => LinePatternDetector::class, + 'provider' => 'libraries', + ]; + return new LinePatternDetector($configuration, $plugin_id, $plugin_definition, 'vfs://' . $app_root); + } + + /** + * @param $file + * @param $file_contents + */ + protected function setupFile($file, $file_contents) { + vfsStream::create([ + 'libraries' => [ + $this->libraryId => [ + $file => $file_contents, + ], + ], + ]); + } + +} diff --git a/web/themes/asc_bootstrap/assets/sass/components/_webforms.scss b/web/themes/asc_bootstrap/assets/sass/components/_webforms.scss index fc4df252d6655b523eb1d7548bca0ec6e4800d14..132a0b62017987d2f4ba1d9099c8356f29e079a6 100644 --- a/web/themes/asc_bootstrap/assets/sass/components/_webforms.scss +++ b/web/themes/asc_bootstrap/assets/sass/components/_webforms.scss @@ -7,9 +7,14 @@ .form-inline .control-label { width: 100%; + margin-bottom: 5px; } textarea.form-control { margin-bottom: 10px; } + +label { + width: 100%; +} diff --git a/web/themes/asc_bootstrap/assets/sass/layout/_default.scss b/web/themes/asc_bootstrap/assets/sass/layout/_default.scss index 8687e31ad2695d3b17cc2788b9698b6de7cdaa2d..1512b7e89aace4416dcdab4646aa00d4125e6ec5 100755 --- a/web/themes/asc_bootstrap/assets/sass/layout/_default.scss +++ b/web/themes/asc_bootstrap/assets/sass/layout/_default.scss @@ -218,6 +218,15 @@ h4 { h5, h6 { line-height: 15px; + text-transform: uppercase; +} + +h5 { + font-weight: 600; +} + +h6 { + font-weight: 500; } diff --git a/web/themes/asc_bootstrap/css/style.css b/web/themes/asc_bootstrap/css/style.css index 1e7bdb4ae6a62af0767090c6432ad6705275c91d..93670ab6e0ee509ba95a039d8c729d4c225d869b 100644 --- a/web/themes/asc_bootstrap/css/style.css +++ b/web/themes/asc_bootstrap/css/style.css @@ -9092,19 +9092,30 @@ h4 { /* line 219, ../assets/sass/layout/_default.scss */ h5, h6 { line-height: 15px; + text-transform: uppercase; +} + +/* line 224, ../assets/sass/layout/_default.scss */ +h5 { + font-weight: 600; } /* line 228, ../assets/sass/layout/_default.scss */ +h6 { + font-weight: 500; +} + +/* line 237, ../assets/sass/layout/_default.scss */ #contact-message-feedback-form { margin-bottom: 20px; } -/* line 237, ../assets/sass/layout/_default.scss */ +/* line 246, ../assets/sass/layout/_default.scss */ .user-login-form { margin-bottom: 40px; } -/* line 241, ../assets/sass/layout/_default.scss */ +/* line 250, ../assets/sass/layout/_default.scss */ #user-form { margin-bottom: 40px; } @@ -11753,13 +11764,19 @@ body { /* line 8, ../assets/sass/components/_webforms.scss */ .form-inline .control-label { width: 100%; + margin-bottom: 5px; } -/* line 13, ../assets/sass/components/_webforms.scss */ +/* line 14, ../assets/sass/components/_webforms.scss */ textarea.form-control { margin-bottom: 10px; } +/* line 18, ../assets/sass/components/_webforms.scss */ +label { + width: 100%; +} + /* line 9, ../assets/sass/components/_wysiwyg.scss */ .align-right .field--name-field-media-image { padding: 15px 0 15px 15px;