', $html );
58 | $this->assertStringContainsString( 'href="', $html );
59 | $this->assertStringContainsString( 'ValidImageThatDoesNotExist.png', $html );
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/tests/js/jquery.ui.mediasuggester.tests.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license GPL-2.0+
3 | */
4 | ( function () {
5 | 'use strict';
6 |
7 | /**
8 | * @return {jQuery}
9 | */
10 | var newTestSuggester = function( mockSearchResult ) {
11 | var options = {
12 | ajax: function( options ) {
13 | var response = { query: { search: mockSearchResult || [] } };
14 |
15 | // This uses the search results array as a spy, and appends _requestTerm
16 | response.query.search._requestTerm = options.data.srsearch;
17 |
18 | return $.Deferred().resolve( response ).promise();
19 | },
20 | apiUrl: 'can not be empty'
21 | };
22 |
23 | return $( '' )
24 | .addClass( 'test_suggester' )
25 | .appendTo( 'body' )
26 | .mediasuggester( options );
27 | };
28 |
29 | QUnit.module( 'jquery.ui.mediasuggester', {
30 | afterEach: function() {
31 | var $suggester = $( '.test_suggester' ),
32 | suggester = $suggester.data( 'mediasuggester' );
33 | if ( suggester ) {
34 | suggester.destroy();
35 | }
36 | $suggester.remove();
37 | }
38 | } );
39 |
40 | QUnit.test( 'Create', function( assert ) {
41 | var $suggester = newTestSuggester();
42 |
43 | assert.ok(
44 | $suggester.data( 'mediasuggester' ) instanceof $.ui.mediasuggester,
45 | 'Instantiated media suggester.'
46 | );
47 | } );
48 |
49 | QUnit.test( 'search integration', function( assert ) {
50 | var $suggester = newTestSuggester(),
51 | suggester = $suggester.data( 'mediasuggester' ),
52 | input = 'Bar',
53 | done = assert.async();
54 |
55 | $suggester.val( input );
56 | suggester.search().done( function( suggestions, term ) {
57 | assert.strictEqual( suggestions._requestTerm, 'Bar' );
58 | assert.strictEqual( term, input );
59 |
60 | done();
61 | } );
62 | } );
63 |
64 | QUnit.test( 'put matching file name on top of result list', function( assert ) {
65 | var $suggester = newTestSuggester( [
66 | { title: 'File:mockResult_a.jpg' },
67 | { title: 'File:mockResult_b.jpg' },
68 | { title: 'File:mockResult_c.jpg' }
69 | ] ),
70 | suggester = $suggester.data( 'mediasuggester' ),
71 | input = 'mockResult_b.jpg',
72 | done = assert.async();
73 |
74 | $suggester.val( input );
75 | suggester.search().done( function( suggestions, term ) {
76 | assert.strictEqual( suggestions[0].title, 'File:mockResult_b.jpg' );
77 | assert.strictEqual( suggestions[2].title, 'File:mockResult_c.jpg' );
78 | done();
79 | } );
80 | } );
81 |
82 | }() );
83 |
--------------------------------------------------------------------------------
/extension.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Wikibase Local Media",
3 | "type": "wikibase",
4 |
5 | "version": "2.0.0",
6 |
7 | "author": [
8 | "[https://www.EntropyWins.wtf/mediawiki Jeroen De Dauw]",
9 | "[https://professional.wiki/ Professional Wiki]"
10 | ],
11 |
12 | "url": "https://professional.wiki/en/extension/wikibase-local-media",
13 |
14 | "descriptionmsg": "wblm-desc",
15 |
16 | "license-name": "GPL-2.0-or-later",
17 |
18 | "requires": {
19 | "MediaWiki": ">= 1.40.0",
20 | "extensions": {
21 | "WikibaseRepository": "*"
22 | }
23 | },
24 |
25 | "MessagesDirs": {
26 | "WikibaseLocalMedia": [
27 | "i18n"
28 | ]
29 | },
30 |
31 | "AutoloadNamespaces": {
32 | "Wikibase\\LocalMedia\\": "src/",
33 | "Wikibase\\LocalMedia\\Tests\\": "tests/"
34 | },
35 |
36 | "Hooks": {
37 | "WikibaseRepoDataTypes": "Wikibase\\LocalMedia\\HookHandlers::onWikibaseRepoDataTypes",
38 | "WikibaseClientDataTypes": "Wikibase\\LocalMedia\\HookHandlers::onWikibaseClientDataTypes",
39 | "ResourceLoaderGetConfigVars": "Wikibase\\LocalMedia\\HookHandlers::onResourceLoaderGetConfigVars"
40 | },
41 |
42 | "config": {
43 | "WikibaseLocalMediaRemoteApiUrl": {
44 | "value": null,
45 | "description": "Optional wiki API URL, works in conjunction with $wgForeignFileRepos for retrieving images from any wiki"
46 | }
47 | },
48 |
49 | "ResourceFileModulePaths": {
50 | "localBasePath": "resources",
51 | "remoteExtPath": "WikibaseLocalMedia/resources"
52 | },
53 |
54 | "ResourceModules": {
55 | "jquery.ui.mediasuggester": {
56 | "scripts": [
57 | "jquery.ui.mediasuggester.js"
58 | ],
59 | "styles": [
60 | "jquery.ui.mediasuggester.css"
61 | ],
62 | "dependencies": [
63 | "jquery.ui.suggester",
64 | "jquery.ui",
65 | "util.highlightSubstring"
66 | ],
67 | "targets": [ "desktop", "mobile" ]
68 | },
69 | "jquery.valueview.experts.LocalMediaType": {
70 | "scripts": [
71 | "LocalMediaType.js"
72 | ],
73 | "dependencies": [
74 | "jquery.event.special.eachchange",
75 | "jquery.ui.mediasuggester",
76 | "jquery.valueview.experts.StringValue",
77 | "jquery.valueview.Expert"
78 | ],
79 | "targets": [ "desktop", "mobile" ]
80 | }
81 | },
82 |
83 | "QUnitTestModule": {
84 | "localBasePath": "tests/js",
85 | "remoteExtPath": "WikibaseLocalMedia/tests/js",
86 | "scripts": [
87 | "jquery.ui.mediasuggester.tests.js"
88 | ],
89 | "dependencies": [
90 | "jquery.ui.mediasuggester"
91 | ]
92 | },
93 |
94 | "manifest_version": 2
95 | }
96 |
--------------------------------------------------------------------------------
/phpstan-baseline.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | ignoreErrors:
3 | -
4 | message: '#^Access to undefined constant Wikibase\\Repo\\Rdf\\PropertyRdfBuilder\:\:OBJECT_PROPERTY\.$#'
5 | identifier: classConstant.notFound
6 | count: 1
7 | path: src/HookHandlers.php
8 |
9 | -
10 | message: '#^Method Wikibase\\LocalMedia\\HookHandlers\:\:onResourceLoaderGetConfigVars\(\) has parameter \$vars with no value type specified in iterable type array\.$#'
11 | identifier: missingType.iterableValue
12 | count: 1
13 | path: src/HookHandlers.php
14 |
15 | -
16 | message: '#^Method Wikibase\\LocalMedia\\HookHandlers\:\:onWikibaseClientDataTypes\(\) has parameter \$dataTypeDefinitions with no value type specified in iterable type array\.$#'
17 | identifier: missingType.iterableValue
18 | count: 1
19 | path: src/HookHandlers.php
20 |
21 | -
22 | message: '#^Method Wikibase\\LocalMedia\\HookHandlers\:\:onWikibaseRepoDataTypes\(\) has parameter \$dataTypeDefinitions with no value type specified in iterable type array\.$#'
23 | identifier: missingType.iterableValue
24 | count: 1
25 | path: src/HookHandlers.php
26 |
27 | -
28 | message: '#^Method Wikibase\\LocalMedia\\Services\\FormatterBuilder\:\:__construct\(\) has parameter \$thumbLimits with no value type specified in iterable type array\.$#'
29 | identifier: missingType.iterableValue
30 | count: 1
31 | path: src/Services/FormatterBuilder.php
32 |
33 | -
34 | message: '#^Parameter \#3 \$languageCode of class Wikibase\\LocalMedia\\Services\\InlineImageFormatter constructor expects string, mixed given\.$#'
35 | identifier: argument.type
36 | count: 1
37 | path: src/Services/FormatterBuilder.php
38 |
39 | -
40 | message: '#^Property Wikibase\\LocalMedia\\Services\\FormatterBuilder\:\:\$thumbLimits type has no value type specified in iterable type array\.$#'
41 | identifier: missingType.iterableValue
42 | count: 1
43 | path: src/Services/FormatterBuilder.php
44 |
45 | -
46 | message: '#^Instanceof between DataValues\\StringValue and DataValues\\StringValue will always evaluate to true\.$#'
47 | identifier: instanceof.alwaysTrue
48 | count: 1
49 | path: src/Services/ImageLinkFormatter.php
50 |
51 | -
52 | message: '#^Constructor of class Wikibase\\LocalMedia\\Services\\InlineImageFormatter has an unused parameter \$languageCode\.$#'
53 | identifier: constructor.unusedParameter
54 | count: 1
55 | path: src/Services/InlineImageFormatter.php
56 |
57 | -
58 | message: '#^Instanceof between DataValues\\StringValue and DataValues\\StringValue will always evaluate to true\.$#'
59 | identifier: instanceof.alwaysTrue
60 | count: 1
61 | path: src/Services/InlineImageFormatter.php
62 |
63 | -
64 | message: '#^Method Wikibase\\LocalMedia\\Services\\InlineImageFormatter\:\:__construct\(\) has parameter \$thumbLimits with no value type specified in iterable type array\.$#'
65 | identifier: missingType.iterableValue
66 | count: 1
67 | path: src/Services/InlineImageFormatter.php
68 |
69 | -
70 | message: '#^Property Wikibase\\LocalMedia\\Services\\InlineImageFormatter\:\:\$thumbLimits type has no value type specified in iterable type array\.$#'
71 | identifier: missingType.iterableValue
72 | count: 1
73 | path: src/Services/InlineImageFormatter.php
74 |
75 | -
76 | message: '#^Cannot call method getFullURL\(\) on MediaWiki\\Title\\Title\|null\.$#'
77 | identifier: method.nonObject
78 | count: 1
79 | path: src/Services/LocalMediaRdfBuilder.php
80 |
81 | -
82 | message: '#^Parameter \#1 \$text of method MediaWiki\\Title\\TitleFactory\:\:newFromText\(\) expects int\|string\|null, mixed given\.$#'
83 | identifier: argument.type
84 | count: 1
85 | path: src/Services/LocalMediaRdfBuilder.php
86 |
87 | -
88 | message: '#^Parameter \#2 \$thumbLimits of class Wikibase\\LocalMedia\\Services\\FormatterBuilder constructor expects array, mixed given\.$#'
89 | identifier: argument.type
90 | count: 1
91 | path: src/WikibaseLocalMedia.php
--------------------------------------------------------------------------------
/src/Services/InlineImageFormatter.php:
--------------------------------------------------------------------------------
1 | 120, 1 => 240, ...]
41 | * @param string $languageCode
42 | * @param ImageLinker $imageLinker
43 | * @param string $captionCssClass
44 | * @throws \MWException
45 | */
46 | public function __construct(
47 | ParserOptions $parserOptions,
48 | array $thumbLimits,
49 | string $languageCode,
50 | ImageLinker $imageLinker,
51 | string $captionCssClass
52 | ) {
53 | $this->language = MediaWikiServices::getInstance()->getContentLanguage();
54 | $this->parserOptions = $parserOptions;
55 | $this->thumbLimits = $thumbLimits;
56 | $this->imageLinker = $imageLinker;
57 | $this->captionCssClass = $captionCssClass;
58 | }
59 |
60 | /**
61 | * @see ValueFormatter::format
62 | *
63 | * Formats the given commons file name as an HTML image gallery.
64 | *
65 | * @param StringValue $value The commons file name
66 | *
67 | * @throws InvalidArgumentException
68 | * @return string HTML
69 | */
70 | public function format( $value ) {
71 | if ( !( $value instanceof StringValue ) ) {
72 | throw new InvalidArgumentException( 'Data value type mismatch. Expected a StringValue.' );
73 | }
74 |
75 | $fileName = $value->getValue();
76 | // We cannot use makeTitle because it does not secureAndSplit()
77 | $title = Title::makeTitleSafe( NS_FILE, $fileName );
78 | if ( $title === null ) {
79 | return htmlspecialchars( $fileName );
80 | }
81 |
82 | $transformOptions = [
83 | 'width' => $this->getThumbWidth( $this->parserOptions->getThumbSize() ),
84 | 'height' => 1000
85 | ];
86 |
87 | $repoGroup = MediaWikiServices::getInstance()->getRepoGroup();
88 |
89 | $file = $repoGroup->findFile( $fileName );
90 | if ( !$file instanceof File ) {
91 | return $this->getCaptionHtml( $title );
92 | }
93 | $thumb = $file->transform( $transformOptions );
94 | if ( !$thumb ) {
95 | return $this->getCaptionHtml( $title );
96 | }
97 |
98 | Linker::processResponsiveImages( $file, $thumb, $transformOptions );
99 |
100 | return $this->wrapThumb( $title, $thumb->toHtml() ) . $this->getCaptionHtml( $title, $file );
101 | }
102 |
103 | private function getThumbWidth( int $thumbSize ): int {
104 | return $this->thumbLimits[$thumbSize] ?? self::FALLBACK_THUMBNAIL_WIDTH;
105 | }
106 |
107 | private function wrapThumb( Title $title, string $thumbHtml ): string {
108 | $attributes = [
109 | 'class' => 'image',
110 | 'href' => $this->imageLinker->buildUrl( $title )
111 | ];
112 |
113 | return Html::rawElement(
114 | 'div',
115 | [ 'class' => 'thumb' ],
116 | Html::rawElement( 'a', $attributes, $thumbHtml )
117 | );
118 | }
119 |
120 | private function getCaptionHtml( Title $title, ?File $file = null ): string {
121 | $attributes = [
122 | 'href' => $this->imageLinker->buildUrl( $title )
123 | ];
124 | $innerHtml = Html::element( 'a', $attributes, $title->getText() );
125 |
126 | if ( $file ) {
127 | $innerHtml .= Html::element( 'br' ) . $this->getFileMetaHtml( $file );
128 | }
129 |
130 | return Html::rawElement(
131 | 'div',
132 | [ 'class' => $this->captionCssClass ],
133 | $innerHtml
134 | );
135 | }
136 |
137 | private function getFileMetaHtml( File $file ): string {
138 | return $this->language->semicolonList( [
139 | $file->getDimensionsString(),
140 | htmlspecialchars( $this->language->formatSize( (int)$file->getSize() ) )
141 | ] );
142 | }
143 |
144 | }
145 |
--------------------------------------------------------------------------------
/resources/jquery.ui.mediasuggester.js:
--------------------------------------------------------------------------------
1 | ( function () {
2 | 'use strict';
3 |
4 | /**
5 | * Media suggester.
6 | * Enhances an input box with suggestion functionality for local asset names.
7 | * (uses `util.highlightSubstring`)
8 | * @class jQuery.ui.mediasuggester
9 | * @extends jQuery.ui.suggester
10 | * @uses util
11 | * @license GNU GPL v2+
12 | *
13 | * @constructor
14 | */
15 | $.widget( 'ui.mediasuggester', $.ui.suggester, {
16 |
17 | /**
18 | * @see jQuery.ui.suggester.options
19 | */
20 | options: {
21 | ajax: $.ajax,
22 | apiUrl: null,
23 | indexPhpUrl: null,
24 | namespaceId: 6
25 | },
26 |
27 | /**
28 | * @inheritdoc
29 | * @protected
30 | */
31 | _create: function() {
32 | if ( !this.options.apiUrl ) {
33 | throw new Error( 'apiUrl option required' );
34 | }
35 |
36 | if ( !this.options.source ) {
37 | this.options.source = this._initDefaultSource();
38 | }
39 |
40 | $.ui.suggester.prototype._create.call( this );
41 |
42 | this.options.menu.element.addClass( 'ui-mediasuggester-list' );
43 | },
44 |
45 | /**
46 | * Initializes the default source pointing to the "query" API module of the local wiki.
47 | * @protected
48 | *
49 | * @return {Function}
50 | */
51 | _initDefaultSource: function() {
52 | var self = this;
53 |
54 | return function( term ) {
55 | var deferred = $.Deferred();
56 |
57 | self.options.ajax( {
58 | url: self.options.apiUrl,
59 | dataType: 'jsonp',
60 | data: {
61 | action: 'query',
62 | list: 'search',
63 | srsearch: term,
64 | srnamespace: self.options.namespaceId,
65 | srlimit: 10,
66 | format: 'json'
67 | },
68 | timeout: 8000
69 | } )
70 | .done( function( response ) {
71 | if ( response.query === undefined ) {
72 | return;
73 | }
74 | var sorted = self._prioritiseMatchingFilename( response.query.search, term );
75 |
76 | deferred.resolve( sorted, term );
77 | } )
78 | .fail( function( jqXHR, textStatus ) {
79 | // Since this is a JSONP request, this will always fail with a timeout...
80 | deferred.reject( textStatus );
81 | } );
82 |
83 | return deferred.promise();
84 | };
85 | },
86 |
87 | /**
88 | * Be smart on the media search results and put an exactly matching file name on top
89 | * @private
90 | *
91 | * @param {Array} resultList Results from the search API response
92 | * @param {string} term The user's search term
93 | *
94 | * @return {Array}
95 | */
96 | _prioritiseMatchingFilename: function( resultList, term ) {
97 | return resultList.sort( function( a, b ) {
98 | // use indexOf() in favour of startsWith() for browser compatibility
99 | if ( a.title.indexOf( this._getFileNamespace() + ':' + term ) === 0 ) {
100 | return -1;
101 | } else if ( b.title.indexOf( this._getFileNamespace() + ':' + term ) === 0 ) {
102 | return 1;
103 | } else {
104 | return 0;
105 | }
106 | } );
107 | },
108 |
109 | /**
110 | * @see jQuery.ui.suggester._createMenuItemFromSuggestion
111 | * @protected
112 | *
113 | * @param {Object} suggestion
114 | * @param {string} requestTerm
115 | * @return {jQuery.ui.ooMenu.Item}
116 | */
117 | _createMenuItemFromSuggestion: function( suggestion, requestTerm ) {
118 | suggestion = suggestion.title;
119 |
120 | var isFile = this._getFileNamespaceRegex().test( suggestion );
121 |
122 | if ( isFile ) {
123 | suggestion = suggestion.replace( this._getFileNamespaceRegex(), '' );
124 | }
125 |
126 | var label = util.highlightSubstring(
127 | requestTerm,
128 | suggestion,
129 | {
130 | caseSensitive: false,
131 | withinString: true
132 | }
133 | ),
134 | $label = $( '' )
135 | .attr( { dir: 'ltr', title: suggestion } )
136 | .append( label );
137 |
138 | if ( isFile ) {
139 | $label.prepend( this._createThumbnail( suggestion ) );
140 | }
141 |
142 | return new $.ui.ooMenu.Item( $label, suggestion );
143 | },
144 |
145 | /**
146 | * @private
147 | *
148 | * @param {string} fileName Must be a file name without the File: namespace.
149 | * @return {jQuery}
150 | */
151 | _createThumbnail: function( fileName ) {
152 | return $( '' )
153 | .attr( 'class', 'ui-mediasuggester-thumbnail' )
154 | .css( 'background-image', this._createBackgroundImage( fileName ) );
155 | },
156 |
157 | /**
158 | * @private
159 | *
160 | * @param {string} fileName Must be a file name without the File: namespace.
161 | * @return {string} CSS
162 | */
163 | _createBackgroundImage: function ( fileName ) {
164 | // Height alone is ignored, width must be set to something.
165 | // We accept to truncate 50% and only show the center 50% of the images area.
166 | return 'url("' + this.options.indexPhpUrl + '?title=Special:Filepath/'
167 | + encodeURIComponent( fileName )
168 | + '&width=100&height=50")';
169 | },
170 |
171 | /**
172 | * @private
173 | *
174 | * @returns {string}
175 | */
176 | _getFileNamespace: function () {
177 | return mw.config.get('wgFormattedNamespaces')[6];
178 | },
179 |
180 | /**
181 | * @private
182 | *
183 | * @returns {RegExp}
184 | */
185 | _getFileNamespaceRegex: function () {
186 | return new RegExp( '^' + this._getFileNamespace() + ':' );
187 | }
188 |
189 | } );
190 |
191 | }() );
192 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Wikibase Local Media
2 |
3 | [](https://github.com/ProfessionalWiki/WikibaseLocalMedia/actions?query=workflow%3ACI)
4 | [](https://packagist.org/packages/professional-wiki/wikibase-local-media)
5 | [](https://packagist.org/packages/professional-wiki/wikibase-local-media)
6 | [](LICENSE)
7 |
8 | MediaWiki extension that adds support for local media files to [Wikibase] via a new data type.
9 |
10 | [Professional Wiki] created and maintains Wikibase Local Media. We provide [Wikibase hosting], [Wikibase development], and [Wikibase consulting].
11 |
12 | [Rhizome] commissioned and funded the extension's initial development. Wikibase Local Media is an open-source project, and contributions are welcome!
13 |
14 | ## Demo and screenshot
15 |
16 |
17 |
18 |
19 |
20 |
21 | Also see [this animated gif](https://twitter.com/i/status/1286293710112731137).
22 |
23 | ## Platform requirements
24 |
25 | * [PHP] 7.4 or later
26 | * [MediaWiki] 1.40 or later
27 | * [Wikibase Repository]
28 |
29 | For more information on the different versions of this extension, see the [release notes](#release-notes).
30 |
31 | ## Installation
32 |
33 | First, install MediaWiki and Wikibase Repository.
34 |
35 | **Using Composer (option 1/2)**
36 |
37 | The recommended way to install Wikibase Local Media is using [Composer](https://getcomposer.org) with
38 | [MediaWiki's built-in support for Composer](https://professional.wiki/en/articles/installing-mediawiki-extensions-with-composer).
39 |
40 | On the commandline, go to your wiki's root directory. Then run these two commands:
41 |
42 | ```shell script
43 | COMPOSER=composer.local.json composer require --no-update professional-wiki/wikibase-local-media:*
44 | composer update professional-wiki/wikibase-local-media --no-dev -o
45 | ```
46 |
47 | **Manual download (option 2/2)**
48 |
49 | You can also install the extension via git clone or download. Place the `WikibaseLocalMedia` directory into `extensions`.
50 |
51 | **Enabling the extension**
52 |
53 | Then enable the extension by adding the following to the bottom of your wikis `LocalSettings.php` file:
54 |
55 | ```php
56 | wfLoadExtension( 'WikibaseLocalMedia' );
57 | ```
58 |
59 | You can verify the extension was enabled successfully by opening your wikis Special:Version page in your browser.
60 |
61 | ## PHP Configuration
62 |
63 | Configuration can be changed via [LocalSettings.php].
64 |
65 | ### Setting foreign file repo
66 |
67 | Optional wiki API URL, works in conjunction with `$wgForeignFileRepos` for retrieving images from any wiki
68 |
69 | Variable: `$wgWikibaseLocalMediaRemoteApiUrl`
70 |
71 | Default: `null`
72 |
73 | Example: `https://commons.wikimedia.org/w/api.php`
74 |
75 | ## Running the tests
76 |
77 | * PHP tests: `php tests/phpunit/phpunit.php extensions/WikibaseLocalMedia/tests/`
78 | * JS tests: `index.php?title=Special%3AJavaScriptTest&filter=jquery.ui.mediasuggester`
79 |
80 | ## Release notes
81 |
82 | ### Version 2.0.0
83 |
84 | Released on March 30, 2025
85 |
86 | * Raised the minimum MediaWiki version from 1.35 to 1.40
87 | * Added support for MediaWiki 1.41, 1.42, 1.43, and the development version of 1.44
88 | * Translation updates
89 |
90 | ### Version 1.1.0
91 |
92 | Released on February 16, 2025
93 |
94 | * [Added optional APU URL configuration variable](https://github.com/ProfessionalWiki/WikibaseLocalMedia/pull/37) `wgWikibaseLocalMediaRemoteApiUrl` (works together with `wgForeignFileRepos`)
95 | * Translation updates
96 |
97 | ### Version 1.0.4
98 |
99 | Released on October 9, 2024
100 |
101 | * Fixed support for non-English wikis
102 | * Fixed deprecation warning of MediaWiki 1.40 and later
103 | * Translation updates
104 |
105 | ### Version 1.0.3
106 |
107 | Released on March 30, 2023
108 |
109 | * Added support for MediaWiki and Wikibase 1.38 and 1.39
110 | * Translation updates
111 |
112 | ### Version 1.0.2
113 |
114 | Released on October 4th 2022
115 |
116 | * Added support for MediaWiki and Wikibase 1.37
117 | * Translation updates
118 |
119 | ### Version 1.0.1
120 |
121 | Released on March 17th 2021
122 |
123 | * Allowed installation with PHP 7.2.x
124 | * Translation updates
125 |
126 | ### Version 1.0.0
127 |
128 | Released on October 5th 2020
129 |
130 | * Added optional integration with Wikibase Client
131 | * Translation updates
132 |
133 | ### Version 0.2.1
134 |
135 | Released on September 28th 2020
136 |
137 | * Fixed support for PHP 7.3.x
138 |
139 | ### Version 0.2
140 |
141 | Released on September 26th 2020
142 |
143 | * Added support for MediaWiki/Wikibase 1.35
144 | * The extension is now listed in the Wikibase group on Special:Version
145 |
146 | ### Version 0.1
147 |
148 | Released on September 26th 2020
149 |
150 | * [Initial release] for MediaWiki/Wikibase 1.34
151 |
152 | [Professional Wiki]: https://professional.wiki
153 | [Wikibase]: https://professional.wiki/en/wikibase-wikidata-and-knowledge-graphs
154 | [Wikibase hosting]: https://professional.wiki/en/hosting/wikibase
155 | [Wikibase development]: https://professional.wiki/en/wikibase-software-development
156 | [Wikibase consulting]: https://wikibase.consulting/
157 | [Rhizome]: https://rhizome.org/
158 | [MediaWiki]: https://www.mediawiki.org
159 | [PHP]: https://www.php.net
160 | [Wikibase Repository]: https://www.mediawiki.org/wiki/Extension:Wikibase_Repository
161 | [LocalSettings.php]: https://www.mediawiki.org/wiki/Manual:LocalSettings.php
162 | [Initial release]: https://professional.wiki/en/news/wikibase-local-media
163 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | # Controls when the action will run. Triggers the workflow on push or pull request
4 | # events but only for the master branch
5 | on:
6 | push:
7 | branches: [ "*" ]
8 | pull_request:
9 | branches: [ "*" ]
10 |
11 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel
12 | jobs:
13 | test:
14 | name: "PHPUnit: MW ${{ matrix.mw }}, PHP ${{ matrix.php }}"
15 | continue-on-error: ${{ matrix.experimental }}
16 |
17 | strategy:
18 | fail-fast: false
19 | matrix:
20 | include:
21 | - mw: 'REL1_40'
22 | php: 7.4
23 | experimental: false
24 | - mw: 'REL1_41'
25 | php: 8.1
26 | experimental: false
27 | - mw: 'REL1_42'
28 | php: 8.2
29 | experimental: false
30 | - mw: 'REL1_43'
31 | php: 8.3
32 | experimental: false
33 | - mw: 'master'
34 | php: 8.4
35 | experimental: true
36 |
37 | runs-on: ubuntu-latest
38 |
39 | defaults:
40 | run:
41 | working-directory: mediawiki
42 |
43 | # Steps represent a sequence of tasks that will be executed as part of the job
44 | steps:
45 | - name: Setup PHP
46 | uses: shivammathur/setup-php@v2
47 | with:
48 | php-version: ${{ matrix.php }}
49 | extensions: mbstring, intl
50 | tools: composer:v2
51 |
52 | - name: Cache MediaWiki
53 | id: cache-mediawiki
54 | uses: actions/cache@v4
55 | with:
56 | path: |
57 | mediawiki
58 | !mediawiki/extensions/
59 | !mediawiki/vendor/
60 | key: mw_${{ matrix.mw }}-php${{ matrix.php }}_v1
61 |
62 | - name: Cache Composer cache
63 | uses: actions/cache@v4
64 | with:
65 | path: ~/.composer/cache
66 | key: composer-php${{ matrix.php }}
67 |
68 | - uses: actions/checkout@v4
69 | with:
70 | path: EarlyCopy
71 |
72 | - name: Install MediaWiki
73 | if: steps.cache-mediawiki.outputs.cache-hit != 'true'
74 | working-directory: ~
75 | run: bash EarlyCopy/.github/workflows/installMediaWiki.sh ${{ matrix.mw }} WikibaseLocalMedia
76 |
77 | - uses: actions/checkout@v4
78 | with:
79 | path: mediawiki/extensions/WikibaseLocalMedia
80 |
81 | - name: Composer update
82 | run: composer update
83 |
84 | - name: Run PHPUnit
85 | run: php tests/phpunit/phpunit.php extensions/WikibaseLocalMedia/tests/
86 |
87 | PHPStan:
88 | name: "PHPStan: MW ${{ matrix.mw }}, PHP ${{ matrix.php }}"
89 |
90 | strategy:
91 | matrix:
92 | include:
93 | - mw: 'REL1_43'
94 | php: '8.3'
95 |
96 | runs-on: ubuntu-latest
97 |
98 | defaults:
99 | run:
100 | working-directory: mediawiki
101 |
102 | steps:
103 | - name: Setup PHP
104 | uses: shivammathur/setup-php@v2
105 | with:
106 | php-version: ${{ matrix.php }}
107 | extensions: mbstring
108 | tools: composer, cs2pr
109 |
110 | - name: Cache MediaWiki
111 | id: cache-mediawiki
112 | uses: actions/cache@v4
113 | with:
114 | path: |
115 | mediawiki
116 | mediawiki/extensions/
117 | mediawiki/vendor/
118 | key: mw_${{ matrix.mw }}-php${{ matrix.php }}_v1
119 |
120 | - name: Cache Composer cache
121 | uses: actions/cache@v4
122 | with:
123 | path: ~/.composer/cache
124 | key: composer_static_analysis
125 |
126 | - uses: actions/checkout@v4
127 | with:
128 | path: EarlyCopy
129 |
130 | - name: Install MediaWiki
131 | if: steps.cache-mediawiki.outputs.cache-hit != 'true'
132 | working-directory: ~
133 | run: bash EarlyCopy/.github/workflows/installMediaWiki.sh ${{ matrix.mw }} WikibaseLocalMedia
134 |
135 | - uses: actions/checkout@v4
136 | with:
137 | path: mediawiki/extensions/WikibaseLocalMedia
138 |
139 | - name: Composer allow-plugins
140 | run: composer config --no-plugins allow-plugins.composer/installers true
141 |
142 | - run: composer update
143 |
144 | - name: Composer install
145 | run: cd extensions/WikibaseLocalMedia && composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader
146 |
147 | - name: PHPStan
148 | run: cd extensions/WikibaseLocalMedia && php vendor/bin/phpstan analyse --error-format=checkstyle --no-progress | cs2pr
149 |
150 | phpcs:
151 | name: "Code style: MW ${{ matrix.mw }}, PHP ${{ matrix.php }}"
152 |
153 | strategy:
154 | matrix:
155 | include:
156 | - mw: 'REL1_43'
157 | php: '8.3'
158 |
159 | runs-on: ubuntu-latest
160 |
161 | defaults:
162 | run:
163 | working-directory: mediawiki/extensions/WikibaseLocalMedia
164 |
165 | steps:
166 | - name: Setup PHP
167 | uses: shivammathur/setup-php@v2
168 | with:
169 | php-version: ${{ matrix.php }}
170 | extensions: mbstring, intl, php-ast
171 | tools: composer
172 |
173 | - name: Cache MediaWiki
174 | id: cache-mediawiki
175 | uses: actions/cache@v4
176 | with:
177 | path: |
178 | mediawiki
179 | !mediawiki/extensions/
180 | !mediawiki/vendor/
181 | key: mw_static_analysis
182 |
183 | - name: Cache Composer cache
184 | uses: actions/cache@v4
185 | with:
186 | path: ~/.composer/cache
187 | key: mw_${{ matrix.mw }}-php${{ matrix.php }}_v1
188 |
189 | - uses: actions/checkout@v4
190 | with:
191 | path: EarlyCopy
192 |
193 | - name: Install MediaWiki
194 | if: steps.cache-mediawiki.outputs.cache-hit != 'true'
195 | working-directory: ~
196 | run: bash EarlyCopy/.github/workflows/installMediaWiki.sh ${{ matrix.mw }} WikibaseLocalMedia
197 |
198 | - uses: actions/checkout@v4
199 | with:
200 | path: mediawiki/extensions/WikibaseLocalMedia
201 |
202 | - name: Composer install
203 | run: composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader
204 |
205 | - run: vendor/bin/phpcs -p -s
206 |
--------------------------------------------------------------------------------
/COPYING:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 |
294 | Copyright (C)
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | , 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
340 |
--------------------------------------------------------------------------------