diff --git a/.eslintrc.jsdoc.js b/.eslintrc.jsdoc.js new file mode 100644 index 00000000000..f16403159ec --- /dev/null +++ b/.eslintrc.jsdoc.js @@ -0,0 +1,115 @@ +/** + * TODO: Temporary file for rules. Merge this file into eslintrc.js + * after all the ignored patterns under themes/bootstrap5/js are removed. + */ +module.exports = { + plugins: ["jsdoc"], + ignorePatterns: [ + "themes/**/vendor/**", + "themes/**/node_modules/**", + "themes/bootstrap5/js/account_ajax.js", + "themes/bootstrap5/js/relais.js", + "themes/bootstrap5/js/search.js", + "themes/bootstrap5/js/requests.js", + "themes/bootstrap5/js/resultcount.js", + "themes/bootstrap5/js/map_tab_leaflet.js", + "themes/bootstrap5/js/check_item_statuses.js", + "themes/bootstrap5/js/combined-search.js", + "themes/bootstrap5/js/cart.js", + "themes/bootstrap5/js/pubdate_vis.js", + "themes/bootstrap5/js/map_selection_leaflet.js", + "themes/bootstrap5/js/common.js", + "themes/bootstrap5/js/bs3-compat.js", + "themes/bootstrap5/js/explain.js", + "themes/bootstrap5/js/list_item_selection.js", + "themes/bootstrap5/js/embedded_record.js", + "themes/bootstrap5/js/openurl.js", + "themes/bootstrap5/js/ill.js", + "themes/bootstrap5/js/record.js", + "themes/bootstrap5/js/doi.js", + "themes/bootstrap5/js/keep_alive.js", + "themes/bootstrap5/js/embedGBS.js", + "themes/bootstrap5/js/lib/ajax_request_queue.js", + "themes/bootstrap5/js/covers.js", + "themes/bootstrap5/js/trigger_print.js", + "themes/bootstrap5/js/visual_facets.js", + "themes/bootstrap5/js/preview.js", + "themes/bootstrap5/js/facets.js", + "themes/bootstrap5/js/searchbox_controls.js", + "themes/bootstrap5/js/lightbox.js", + "themes/bootstrap5/js/hierarchy_tree.js", + "themes/bootstrap5/js/cookie.js", + "themes/bootstrap5/js/record_versions.js", + "themes/bootstrap5/js/collection_record.js", + "themes/bootstrap5/js/sticky_elements.js", + "themes/bootstrap5/js/checkouts.js", + "themes/bootstrap5/js/advanced_search.js", + "themes/bootstrap5/js/check_save_statuses.js", + "themes/bootstrap5/js/hold.js", + "themes/bootstrap5/js/config.js", + "themes/bootstrap5/js/channels.js", + "themes/bootstrap5/js/truncate.js", + "themes/bootstrap3/**" + ], + extends: [], + env: { + "browser": true, + "es6": true, + "jquery": true + }, + rules: { + // Recommended + "jsdoc/check-access": 1, + "jsdoc/check-alignment": 1, + "jsdoc/check-param-names": 1, + "jsdoc/check-property-names": 1, + "jsdoc/check-tag-names": 1, + "jsdoc/check-types": 1, + "jsdoc/check-values": 1, + "jsdoc/empty-tags": 1, + "jsdoc/implements-on-classes": 1, + "jsdoc/multiline-blocks": 1, + "jsdoc/no-multi-asterisks": 1, + "jsdoc/no-undefined-types": 1, + "jsdoc/require-jsdoc": 1, + "jsdoc/require-param": 1, + "jsdoc/require-param-description": 1, + "jsdoc/require-param-name": 1, + "jsdoc/require-param-type": 1, + "jsdoc/require-property": 1, + "jsdoc/require-property-description": 1, + "jsdoc/require-property-name": 1, + "jsdoc/require-property-type": 1, + "jsdoc/require-returns": 1, + "jsdoc/require-returns-check": 1, + "jsdoc/require-returns-description": 1, + "jsdoc/require-returns-type": 1, + "jsdoc/require-yields": 1, + "jsdoc/require-yields-check": 1, + "jsdoc/tag-lines": 1, + "jsdoc/valid-types": 1 + // Disabled + //"jsdoc/check-examples": 1, + //"jsdoc/check-indentation": 1, + //"jsdoc/check-line-alignment": 1, + //"jsdoc/check-template-names": 1, + //"jsdoc/check-syntax": 1, + //"jsdoc/informative-docs": 1, + //"jsdoc/match-description": 1, + //"jsdoc/no-bad-blocks": 1, + //"jsdoc/no-blank-block-descriptions": 1, + //"jsdoc/no-defaults": 1, + //"jsdoc/no-missing-syntax": 1, + //"jsdoc/no-restricted-syntax": 1, + //"jsdoc/no-types": 1, + //"jsdoc/require-asterisk-prefix": 1, + //"jsdoc/require-description": 1, + //"jsdoc/require-description-complete-sentence": 1, + //"jsdoc/require-example": 1, + //"jsdoc/require-file-overview": 1, + //"jsdoc/require-hyphen-before-param-description": 1, + //"jsdoc/require-template": 1, + //"jsdoc/require-throws": 1, + //"jsdoc/sort-tags": 1, + } +}; diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f6c4c2bbd8c..02f24adf778 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -40,7 +40,7 @@ jobs: - name: Setup node if: ${{ matrix.phing_tasks == 'qa-console' }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: '20' diff --git a/.gitignore b/.gitignore index f083c8ac324..8c6a39fcde7 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,5 @@ themes/bootstrap5/scss/vendor/bootstrap local/DirLocations.ini local/config/vufind/*.ini local/config/vufind/*.json +local/config/vufind/*.key local/config/vufind/*.yaml diff --git a/Gruntfile.js b/Gruntfile.js index d83cebd39bb..ef19a18c628 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,5 +1,6 @@ module.exports = function(grunt) { const fs = require("fs"); + const os = require("node:os"); // Load dart-sass grunt.loadNpmTasks('grunt-dart-sass'); @@ -275,7 +276,7 @@ module.exports = function(grunt) { expand: true, cwd: path.join('themes', themeList[i], 'scss'), src: ['compiled.scss'], - dest: checkOnly ? null : path.join('themes', themeList[i], 'css'), + dest: path.join(checkOnly ? os.tmpdir() : 'themes', themeList[i], 'css'), ext: '.css' }] }; diff --git a/build.xml b/build.xml index 7638776d855..731e0f38f13 100644 --- a/build.xml +++ b/build.xml @@ -65,8 +65,10 @@ + + - + @@ -153,6 +155,7 @@ + @@ -274,6 +277,20 @@ + + + + + + + + + + + + + + @@ -730,11 +747,21 @@ + + + + + + + + + + @@ -755,11 +782,11 @@ <?php return [ - 'extends' => 'sandal', + 'extends' => 'sandal5', ]; - + <div class="js-status-test hidden"></div> @@ -1252,24 +1279,10 @@ ${git_status} - - - - - - - - - - - - - - - + @@ -1287,7 +1300,7 @@ ${git_status} - + @@ -1313,7 +1326,7 @@ ${git_status} - + @@ -1331,7 +1344,7 @@ ${git_status} - + @@ -1354,8 +1367,8 @@ ${git_status} - - + + @@ -1365,8 +1378,8 @@ ${git_status} - - + + @@ -1377,8 +1390,8 @@ ${git_status} - - + + diff --git a/composer.json b/composer.json index a1b5e01b3da..c12ad3a9936 100644 --- a/composer.json +++ b/composer.json @@ -32,8 +32,8 @@ "cap60552/php-sip2": "1.0.0", "colinmollenhour/credis": "1.16.1", "composer/package-versions-deprecated": "1.11.99.5", - "composer/semver": "3.4.2", - "endroid/qr-code": "5.0.9", + "composer/semver": "3.4.3", + "endroid/qr-code": "5.1.0", "guzzlehttp/guzzle": "7.9.2", "jaybizzle/crawler-detect": "^1.2", "laminas/laminas-cache": "3.12.2", @@ -46,23 +46,21 @@ "laminas/laminas-config": "3.9.0", "laminas/laminas-crypt": "3.11.0", "laminas/laminas-db": "2.20.0", - "laminas/laminas-diactoros": "3.3.1", + "laminas/laminas-diactoros": "3.5.0", "laminas/laminas-dom": "2.14.0", "laminas/laminas-escaper": "2.13.0", "laminas/laminas-eventmanager": "3.13.1", - "laminas/laminas-feed": "2.22.0", - "laminas/laminas-filter": "2.37.0", - "laminas/laminas-form": "3.20.1", - "laminas/laminas-http": "2.19.0", - "laminas/laminas-i18n": "2.28.0", - "laminas/laminas-loader": "2.10.0", + "laminas/laminas-feed": "2.23.0", + "laminas/laminas-form": "3.21.0", + "laminas/laminas-http": "2.20.0", + "laminas/laminas-i18n": "2.29.0", + "laminas/laminas-loader": "2.11.0", "laminas/laminas-log": "2.17.0", - "laminas/laminas-mail": "2.25.1", "laminas/laminas-modulemanager": "2.16.0", "laminas/laminas-mvc": "3.7.0", - "laminas/laminas-mvc-i18n": "1.8.0", + "laminas/laminas-mvc-i18n": "1.9.0", "laminas/laminas-mvc-plugin-flashmessenger": "1.10.1", - "laminas/laminas-paginator": "2.18.1", + "laminas/laminas-paginator": "2.19.0", "laminas/laminas-paginator-adapter-laminasdb": "1.4.0", "laminas/laminas-psr7bridge": "1.11.0", "laminas/laminas-recaptcha": "3.7.0", @@ -74,6 +72,7 @@ "laminas/laminas-validator": "2.55.0", "laminas/laminas-view": "2.27.0", "league/commonmark": "2.5.3", + "league/oauth2-client": "^2.7", "league/oauth2-server": "8.5.4", "lm-commons/lmc-rbac-mvc": "3.4.0", "matthiasmullie/minify": "1.3.73", @@ -81,16 +80,18 @@ "pear/http_request2": "2.6.0", "phing/phing": "3.0.0", "ppito/laminas-whoops": "2.2.0", + "ramsey/uuid": "^4.7", "scssphp/scssphp": "1.13.0", "serialssolutions/summon": "1.3.1", - "slm/locale": "1.1.0", + "slm/locale": "1.2.0", "steverhoades/oauth2-openid-connect-server": "2.6.1", "swagger-api/swagger-ui": "5.17.14", - "symfony/console": "6.4.11", + "symfony/console": "6.4.12", + "symfony/mailer": "6.4.12", "symfony/rate-limiter": "^6.4", "symfony/var-dumper": "6.4.11", - "symfony/yaml": "6.4.11", - "vstelmakh/url-highlight": "3.1.0", + "symfony/yaml": "6.4.12", + "vstelmakh/url-highlight": "3.1.1", "vufind-org/vufindcode": "1.2", "vufind-org/vufinddate": "1.2.0", "vufind-org/vufindharvest": "5.3.0", @@ -108,12 +109,12 @@ "firebase/php-jwt": "6.10.1", "friendsofphp/php-cs-fixer": "3.64.0", "phpmd/phpmd": "2.15.0", - "phpstan/phpstan": "1.12.2", + "phpstan/phpstan": "1.12.7", "phpunit/php-code-coverage": "10.1.16", "phpunit/phpcov": "^9.0", - "phpunit/phpunit": "10.5.32", + "phpunit/phpunit": "10.5.36", "pietercolpaert/hardf": "0.5.0", - "squizlabs/php_codesniffer": "3.10.2" + "squizlabs/php_codesniffer": "3.10.3" }, "extra": { "merge-plugin": { @@ -131,7 +132,7 @@ }, "scripts": { "fix": "phing fix-php", - "phing-install-dependencies": ["phing patch-dependencies", "phing installsolr installswaggerui"], + "phing-install-dependencies": ["phing installsolr installswaggerui"], "post-install-cmd": "@phing-install-dependencies", "post-update-cmd": "@phing-install-dependencies", "qa": "phing qa-console -Ddefaultconfigs=true", diff --git a/composer.lock b/composer.lock index d22a3b8f501..7f7305d5779 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "40bc33d412e92ab73871dcd09b1bf65f", + "content-hash": "566e3f40ebe7e97a8b64fc55d6a3eff0", "packages": [ { "name": "ahand/mobileesp", @@ -138,16 +138,16 @@ }, { "name": "bacon/bacon-qr-code", - "version": "v3.0.0", + "version": "v3.0.1", "source": { "type": "git", "url": "https://github.com/Bacon/BaconQrCode.git", - "reference": "510de6eca6248d77d31b339d62437cc995e2fb41" + "reference": "f9cc1f52b5a463062251d666761178dbdb6b544f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/510de6eca6248d77d31b339d62437cc995e2fb41", - "reference": "510de6eca6248d77d31b339d62437cc995e2fb41", + "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/f9cc1f52b5a463062251d666761178dbdb6b544f", + "reference": "f9cc1f52b5a463062251d666761178dbdb6b544f", "shasum": "" }, "require": { @@ -186,9 +186,69 @@ "homepage": "https://github.com/Bacon/BaconQrCode", "support": { "issues": "https://github.com/Bacon/BaconQrCode/issues", - "source": "https://github.com/Bacon/BaconQrCode/tree/v3.0.0" + "source": "https://github.com/Bacon/BaconQrCode/tree/v3.0.1" }, - "time": "2024-04-18T11:16:25+00:00" + "time": "2024-10-01T13:55:55+00:00" + }, + { + "name": "brick/math", + "version": "0.12.1", + "source": { + "type": "git", + "url": "https://github.com/brick/math.git", + "reference": "f510c0a40911935b77b86859eb5223d58d660df1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1", + "reference": "f510c0a40911935b77b86859eb5223d58d660df1", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^10.1", + "vimeo/psalm": "5.16.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Brick\\Math\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Arbitrary-precision arithmetic library", + "keywords": [ + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "bignumber", + "brick", + "decimal", + "integer", + "math", + "mathematics", + "rational" + ], + "support": { + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/0.12.1" + }, + "funding": [ + { + "url": "https://github.com/BenMorel", + "type": "github" + } + ], + "time": "2023-11-29T23:19:16+00:00" }, { "name": "brick/varexporter", @@ -480,24 +540,24 @@ }, { "name": "composer/semver", - "version": "3.4.2", + "version": "3.4.3", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6" + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/c51258e759afdb17f1fd1fe83bc12baaef6309d6", - "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6", + "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", "shasum": "" }, "require": { "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^1.4", - "symfony/phpunit-bridge": "^4.2 || ^5" + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" }, "type": "library", "extra": { @@ -541,7 +601,7 @@ "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.2" + "source": "https://github.com/composer/semver/tree/3.4.3" }, "funding": [ { @@ -557,7 +617,7 @@ "type": "tidelift" } ], - "time": "2024-07-12T11:35:52+00:00" + "time": "2024-09-19T14:15:21+00:00" }, { "name": "dasprid/enum", @@ -842,6 +902,83 @@ ], "time": "2024-05-22T20:47:39+00:00" }, + { + "name": "doctrine/lexer", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^5.21" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/3.0.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2024-02-05T11:56:58+00:00" + }, { "name": "doctrine/persistence", "version": "3.3.3", @@ -939,18 +1076,85 @@ ], "time": "2024-06-20T10:14:30+00:00" }, + { + "name": "egulias/email-validator", + "version": "4.0.2", + "source": { + "type": "git", + "url": "https://github.com/egulias/EmailValidator.git", + "reference": "ebaaf5be6c0286928352e054f2d5125608e5405e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/ebaaf5be6c0286928352e054f2d5125608e5405e", + "reference": "ebaaf5be6c0286928352e054f2d5125608e5405e", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^2.0 || ^3.0", + "php": ">=8.1", + "symfony/polyfill-intl-idn": "^1.26" + }, + "require-dev": { + "phpunit/phpunit": "^10.2", + "vimeo/psalm": "^5.12" + }, + "suggest": { + "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Egulias\\EmailValidator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eduardo Gulias Davis" + } + ], + "description": "A library for validating emails against several RFCs", + "homepage": "https://github.com/egulias/EmailValidator", + "keywords": [ + "email", + "emailvalidation", + "emailvalidator", + "validation", + "validator" + ], + "support": { + "issues": "https://github.com/egulias/EmailValidator/issues", + "source": "https://github.com/egulias/EmailValidator/tree/4.0.2" + }, + "funding": [ + { + "url": "https://github.com/egulias", + "type": "github" + } + ], + "time": "2023-10-06T06:47:41+00:00" + }, { "name": "endroid/qr-code", - "version": "5.0.9", + "version": "5.1.0", "source": { "type": "git", "url": "https://github.com/endroid/qr-code.git", - "reference": "3dcdfab4c9122874f3915d8bf80a43b9df11852d" + "reference": "393fec6c4cbdc1bd65570ac9d245704428010122" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/endroid/qr-code/zipball/3dcdfab4c9122874f3915d8bf80a43b9df11852d", - "reference": "3dcdfab4c9122874f3915d8bf80a43b9df11852d", + "url": "https://api.github.com/repos/endroid/qr-code/zipball/393fec6c4cbdc1bd65570ac9d245704428010122", + "reference": "393fec6c4cbdc1bd65570ac9d245704428010122", "shasum": "" }, "require": { @@ -1001,7 +1205,7 @@ ], "support": { "issues": "https://github.com/endroid/qr-code/issues", - "source": "https://github.com/endroid/qr-code/tree/5.0.9" + "source": "https://github.com/endroid/qr-code/tree/5.1.0" }, "funding": [ { @@ -1009,30 +1213,30 @@ "type": "github" } ], - "time": "2024-05-08T08:09:28+00:00" + "time": "2024-09-08T08:52:55+00:00" }, { "name": "filp/whoops", - "version": "2.15.4", + "version": "2.16.0", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546" + "reference": "befcdc0e5dce67252aa6322d82424be928214fa2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a139776fa3f5985a50b509f2a02ff0f709d2a546", - "reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546", + "url": "https://api.github.com/repos/filp/whoops/zipball/befcdc0e5dce67252aa6322d82424be928214fa2", + "reference": "befcdc0e5dce67252aa6322d82424be928214fa2", "shasum": "" }, "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", + "php": "^7.1 || ^8.0", "psr/log": "^1.0.1 || ^2.0 || ^3.0" }, "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^4.0 || ^5.0" }, "suggest": { "symfony/var-dumper": "Pretty print complex values better with var-dumper available", @@ -1072,7 +1276,7 @@ ], "support": { "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.15.4" + "source": "https://github.com/filp/whoops/tree/2.16.0" }, "funding": [ { @@ -1080,7 +1284,7 @@ "type": "github" } ], - "time": "2023-11-03T12:00:00+00:00" + "time": "2024-09-25T12:00:00+00:00" }, { "name": "guzzlehttp/guzzle", @@ -1210,16 +1414,16 @@ }, { "name": "guzzlehttp/promises", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8" + "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8", - "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8", + "url": "https://api.github.com/repos/guzzle/promises/zipball/f9c436286ab2892c7db7be8c8da4ef61ccf7b455", + "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455", "shasum": "" }, "require": { @@ -1273,7 +1477,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.0.3" + "source": "https://github.com/guzzle/promises/tree/2.0.4" }, "funding": [ { @@ -1289,7 +1493,7 @@ "type": "tidelift" } ], - "time": "2024-07-18T10:29:17+00:00" + "time": "2024-10-17T10:06:22+00:00" }, { "name": "guzzlehttp/psr7", @@ -1409,16 +1613,16 @@ }, { "name": "jaybizzle/crawler-detect", - "version": "v1.2.119", + "version": "v1.2.120", "source": { "type": "git", "url": "https://github.com/JayBizzle/Crawler-Detect.git", - "reference": "275002e22b0333c15a7c6792fdae5d5deefc9ef0" + "reference": "2b325bdce46bbb8a2e96dc740ad37c743c9d8617" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JayBizzle/Crawler-Detect/zipball/275002e22b0333c15a7c6792fdae5d5deefc9ef0", - "reference": "275002e22b0333c15a7c6792fdae5d5deefc9ef0", + "url": "https://api.github.com/repos/JayBizzle/Crawler-Detect/zipball/2b325bdce46bbb8a2e96dc740ad37c743c9d8617", + "reference": "2b325bdce46bbb8a2e96dc740ad37c743c9d8617", "shasum": "" }, "require": { @@ -1455,9 +1659,9 @@ ], "support": { "issues": "https://github.com/JayBizzle/Crawler-Detect/issues", - "source": "https://github.com/JayBizzle/Crawler-Detect/tree/v1.2.119" + "source": "https://github.com/JayBizzle/Crawler-Detect/tree/v1.2.120" }, - "time": "2024-06-07T07:58:43+00:00" + "time": "2024-09-15T14:31:21+00:00" }, { "name": "laminas/laminas-cache", @@ -2167,25 +2371,28 @@ }, { "name": "laminas/laminas-diactoros", - "version": "3.3.1", + "version": "3.5.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "74cfb9a7522ffd2a161d1ebe10db2fc2abb9df45" + "reference": "143a16306602ce56b8b092a7914fef03c37f9ed2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/74cfb9a7522ffd2a161d1ebe10db2fc2abb9df45", - "reference": "74cfb9a7522ffd2a161d1ebe10db2fc2abb9df45", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/143a16306602ce56b8b092a7914fef03c37f9ed2", + "reference": "143a16306602ce56b8b092a7914fef03c37f9ed2", "shasum": "" }, "require": { - "php": "~8.1.0 || ~8.2.0 || ~8.3.0", - "psr/http-factory": "^1.0.2", + "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0", + "psr/http-factory": "^1.1", "psr/http-message": "^1.1 || ^2.0" }, + "conflict": { + "amphp/amp": "<2.6.4" + }, "provide": { - "psr/http-factory-implementation": "^1.1 || ^2.0", + "psr/http-factory-implementation": "^1.0", "psr/http-message-implementation": "^1.1 || ^2.0" }, "require-dev": { @@ -2193,12 +2400,12 @@ "ext-dom": "*", "ext-gd": "*", "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.9.0", + "http-interop/http-factory-tests": "^2.2.0", "laminas/laminas-coding-standard": "~2.5.0", - "php-http/psr7-integration-tests": "^1.3", - "phpunit/phpunit": "^9.6.16", - "psalm/plugin-phpunit": "^0.18.4", - "vimeo/psalm": "^5.22.1" + "php-http/psr7-integration-tests": "^1.4.0", + "phpunit/phpunit": "^10.5.36", + "psalm/plugin-phpunit": "^0.19.0", + "vimeo/psalm": "^5.26.1" }, "type": "library", "extra": { @@ -2248,7 +2455,7 @@ "type": "community_bridge" } ], - "time": "2024-02-16T16:06:16+00:00" + "time": "2024-10-14T11:59:49+00:00" }, { "name": "laminas/laminas-dom", @@ -2446,16 +2653,16 @@ }, { "name": "laminas/laminas-feed", - "version": "2.22.0", + "version": "2.23.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-feed.git", - "reference": "669792b819fca7274698147ad7a2ecc1b0a9b141" + "reference": "23807e692b3174750b426143bd93572b71b6739a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-feed/zipball/669792b819fca7274698147ad7a2ecc1b0a9b141", - "reference": "669792b819fca7274698147ad7a2ecc1b0a9b141", + "url": "https://api.github.com/repos/laminas/laminas-feed/zipball/23807e692b3174750b426143bd93572b71b6739a", + "reference": "23807e692b3174750b426143bd93572b71b6739a", "shasum": "" }, "require": { @@ -2463,24 +2670,24 @@ "ext-libxml": "*", "laminas/laminas-escaper": "^2.9", "laminas/laminas-stdlib": "^3.6", - "php": "~8.1.0 || ~8.2.0 || ~8.3.0" + "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" }, "conflict": { "laminas/laminas-servicemanager": "<3.3", "zendframework/zend-feed": "*" }, "require-dev": { - "laminas/laminas-cache": "^2.13.2 || ^3.11", - "laminas/laminas-cache-storage-adapter-memory": "^1.1.0 || ^2.2", + "laminas/laminas-cache": "^2.13.2 || ^3.12", + "laminas/laminas-cache-storage-adapter-memory": "^1.1.0 || ^2.3", "laminas/laminas-coding-standard": "~2.5.0", "laminas/laminas-db": "^2.18", - "laminas/laminas-http": "^2.18", - "laminas/laminas-servicemanager": "^3.21.0", - "laminas/laminas-validator": "^2.38", - "phpunit/phpunit": "^10.3.1", - "psalm/plugin-phpunit": "^0.18.4", + "laminas/laminas-http": "^2.19", + "laminas/laminas-servicemanager": "^3.22.1", + "laminas/laminas-validator": "^2.46", + "phpunit/phpunit": "^10.5.5", + "psalm/plugin-phpunit": "^0.19.0", "psr/http-message": "^2.0", - "vimeo/psalm": "^5.14.1" + "vimeo/psalm": "^5.18.0" }, "suggest": { "laminas/laminas-cache": "Laminas\\Cache component, for optionally caching feeds between requests", @@ -2522,42 +2729,42 @@ "type": "community_bridge" } ], - "time": "2023-10-11T20:16:37+00:00" + "time": "2024-10-09T10:53:30+00:00" }, { "name": "laminas/laminas-filter", - "version": "2.37.0", + "version": "2.38.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-filter.git", - "reference": "27dda1e60547bc000b876e24808f47932df2f4ac" + "reference": "3037168c2db8af3088aca8826bdf7e249fd24ce3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-filter/zipball/27dda1e60547bc000b876e24808f47932df2f4ac", - "reference": "27dda1e60547bc000b876e24808f47932df2f4ac", + "url": "https://api.github.com/repos/laminas/laminas-filter/zipball/3037168c2db8af3088aca8826bdf7e249fd24ce3", + "reference": "3037168c2db8af3088aca8826bdf7e249fd24ce3", "shasum": "" }, "require": { "ext-mbstring": "*", "laminas/laminas-servicemanager": "^3.21.0", - "laminas/laminas-stdlib": "^3.13.0", - "php": "~8.1.0 || ~8.2.0 || ~8.3.0" + "laminas/laminas-stdlib": "^3.19.0", + "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" }, "conflict": { "laminas/laminas-validator": "<2.10.1", "zendframework/zend-filter": "*" }, "require-dev": { - "laminas/laminas-coding-standard": "~2.5.0", - "laminas/laminas-crypt": "^3.11", - "laminas/laminas-i18n": "^2.26.0", - "laminas/laminas-uri": "^2.11", + "laminas/laminas-coding-standard": "~3.0", + "laminas/laminas-crypt": "^3.12", + "laminas/laminas-i18n": "^2.28.1", + "laminas/laminas-uri": "^2.12", "pear/archive_tar": "^1.5.0", - "phpunit/phpunit": "^10.5.20", + "phpunit/phpunit": "^10.5.36", "psalm/plugin-phpunit": "^0.19.0", "psr/http-factory": "^1.1.0", - "vimeo/psalm": "^5.24.0" + "vimeo/psalm": "^5.26.1" }, "suggest": { "laminas/laminas-crypt": "Laminas\\Crypt component, for encryption filters", @@ -2601,27 +2808,27 @@ "type": "community_bridge" } ], - "time": "2024-08-12T09:23:23+00:00" + "time": "2024-10-17T20:43:05+00:00" }, { "name": "laminas/laminas-form", - "version": "3.20.1", + "version": "3.21.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-form.git", - "reference": "91ac71bd4862abf9c5e2ccc8f063f83b4999660b" + "reference": "653c869d10c361027ae6c660c991ec3e3f38ed65" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-form/zipball/91ac71bd4862abf9c5e2ccc8f063f83b4999660b", - "reference": "91ac71bd4862abf9c5e2ccc8f063f83b4999660b", + "url": "https://api.github.com/repos/laminas/laminas-form/zipball/653c869d10c361027ae6c660c991ec3e3f38ed65", + "reference": "653c869d10c361027ae6c660c991ec3e3f38ed65", "shasum": "" }, "require": { "laminas/laminas-hydrator": "^4.13.0", "laminas/laminas-inputfilter": "^2.24.0", "laminas/laminas-stdlib": "^3.16.1", - "php": "~8.1.0 || ~8.2.0 || ~8.3.0" + "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" }, "conflict": { "doctrine/annotations": "<1.14.0", @@ -2637,21 +2844,21 @@ "ext-intl": "*", "laminas/laminas-captcha": "^2.17", "laminas/laminas-coding-standard": "^2.5", - "laminas/laminas-db": "^2.18", + "laminas/laminas-db": "^2.20", "laminas/laminas-escaper": "^2.13", - "laminas/laminas-eventmanager": "^3.12", - "laminas/laminas-filter": "^2.33", - "laminas/laminas-i18n": "^2.24.1", - "laminas/laminas-modulemanager": "^2.15.0", + "laminas/laminas-eventmanager": "^3.13.1", + "laminas/laminas-filter": "^2.36", + "laminas/laminas-i18n": "^2.28.0", + "laminas/laminas-modulemanager": "^2.16.0", "laminas/laminas-recaptcha": "^3.7", "laminas/laminas-servicemanager": "^3.22.1", - "laminas/laminas-session": "^2.17", + "laminas/laminas-session": "^2.21", "laminas/laminas-text": "^2.11.0", - "laminas/laminas-validator": "^2.43", - "laminas/laminas-view": "^2.32", - "phpunit/phpunit": "^10.4.2", + "laminas/laminas-validator": "^2.64.1", + "laminas/laminas-view": "^2.35", + "phpunit/phpunit": "^10.5.29", "psalm/plugin-phpunit": "^0.19.0", - "vimeo/psalm": "^5.16" + "vimeo/psalm": "^5.25" }, "suggest": { "doctrine/annotations": "^1.14, required to use laminas-form annotations support", @@ -2698,20 +2905,20 @@ "type": "community_bridge" } ], - "time": "2024-08-22T08:29:18+00:00" + "time": "2024-10-09T08:28:30+00:00" }, { "name": "laminas/laminas-http", - "version": "2.19.0", + "version": "2.20.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-http.git", - "reference": "26dd6d1177e25d970058863c2afed12bb9dbff4d" + "reference": "a66bfb65c698aad6ee9f10df42cb5902f2c3dec8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-http/zipball/26dd6d1177e25d970058863c2afed12bb9dbff4d", - "reference": "26dd6d1177e25d970058863c2afed12bb9dbff4d", + "url": "https://api.github.com/repos/laminas/laminas-http/zipball/a66bfb65c698aad6ee9f10df42cb5902f2c3dec8", + "reference": "a66bfb65c698aad6ee9f10df42cb5902f2c3dec8", "shasum": "" }, "require": { @@ -2719,7 +2926,7 @@ "laminas/laminas-stdlib": "^3.6", "laminas/laminas-uri": "^2.11", "laminas/laminas-validator": "^2.15", - "php": "~8.1.0 || ~8.2.0 || ~8.3.0" + "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" }, "conflict": { "zendframework/zend-http": "*" @@ -2727,7 +2934,7 @@ "require-dev": { "ext-curl": "*", "laminas/laminas-coding-standard": "~2.4.0", - "phpunit/phpunit": "^9.5.25" + "phpunit/phpunit": "^9.6.21" }, "suggest": { "paragonie/certainty": "For automated management of cacert.pem" @@ -2763,7 +2970,7 @@ "type": "community_bridge" } ], - "time": "2023-11-02T16:27:41+00:00" + "time": "2024-10-18T07:35:59+00:00" }, { "name": "laminas/laminas-hydrator", @@ -2844,16 +3051,16 @@ }, { "name": "laminas/laminas-i18n", - "version": "2.28.0", + "version": "2.29.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-i18n.git", - "reference": "e1e312650232e5ef26c28ea08f3c4c18633f48c3" + "reference": "9aa7ef6073556e9b4cfd8d9a0cb8e41cd3883454" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-i18n/zipball/e1e312650232e5ef26c28ea08f3c4c18633f48c3", - "reference": "e1e312650232e5ef26c28ea08f3c4c18633f48c3", + "url": "https://api.github.com/repos/laminas/laminas-i18n/zipball/9aa7ef6073556e9b4cfd8d9a0cb8e41cd3883454", + "reference": "9aa7ef6073556e9b4cfd8d9a0cb8e41cd3883454", "shasum": "" }, "require": { @@ -2861,7 +3068,7 @@ "laminas/laminas-servicemanager": "^3.21.0", "laminas/laminas-stdlib": "^3.0", "laminas/laminas-translator": "^1.0", - "php": "~8.1.0 || ~8.2.0 || ~8.3.0" + "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" }, "conflict": { "laminas/laminas-view": "<2.20.0", @@ -2926,7 +3133,7 @@ "type": "community_bridge" } ], - "time": "2024-07-15T12:54:14+00:00" + "time": "2024-10-11T09:44:53+00:00" }, { "name": "laminas/laminas-inputfilter", @@ -3004,27 +3211,27 @@ }, { "name": "laminas/laminas-json", - "version": "3.6.0", + "version": "3.7.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-json.git", - "reference": "53ff787b20b77197f38680c737e8dfffa846b85b" + "reference": "1931b26ac677f418f39cd0af6d0740e8f4a67d18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-json/zipball/53ff787b20b77197f38680c737e8dfffa846b85b", - "reference": "53ff787b20b77197f38680c737e8dfffa846b85b", + "url": "https://api.github.com/repos/laminas/laminas-json/zipball/1931b26ac677f418f39cd0af6d0740e8f4a67d18", + "reference": "1931b26ac677f418f39cd0af6d0740e8f4a67d18", "shasum": "" }, "require": { - "php": "~8.1.0 || ~8.2.0 || ~8.3.0" + "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" }, "conflict": { "zendframework/zend-json": "*" }, "require-dev": { "laminas/laminas-coding-standard": "~2.4.0", - "laminas/laminas-stdlib": "^2.7.7 || ^3.8", + "laminas/laminas-stdlib": "^2.7.7 || ^3.19", "phpunit/phpunit": "^9.5.25" }, "suggest": { @@ -3061,24 +3268,24 @@ "type": "community_bridge" } ], - "time": "2023-10-18T09:54:55+00:00" + "time": "2024-10-25T09:02:25+00:00" }, { "name": "laminas/laminas-loader", - "version": "2.10.0", + "version": "2.11.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-loader.git", - "reference": "e6fe952304ef40ce45cd814751ab35d42afdad12" + "reference": "f2eedd3a6e774d965158fd11946bb1eba72e298c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-loader/zipball/e6fe952304ef40ce45cd814751ab35d42afdad12", - "reference": "e6fe952304ef40ce45cd814751ab35d42afdad12", + "url": "https://api.github.com/repos/laminas/laminas-loader/zipball/f2eedd3a6e774d965158fd11946bb1eba72e298c", + "reference": "f2eedd3a6e774d965158fd11946bb1eba72e298c", "shasum": "" }, "require": { - "php": "~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0" + "php": "~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" }, "conflict": { "zendframework/zend-loader": "*" @@ -3117,7 +3324,7 @@ "type": "community_bridge" } ], - "time": "2023-10-18T09:58:51+00:00" + "time": "2024-10-16T09:06:57+00:00" }, { "name": "laminas/laminas-log", @@ -3207,82 +3414,6 @@ ], "time": "2023-12-05T18:27:50+00:00" }, - { - "name": "laminas/laminas-mail", - "version": "2.25.1", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-mail.git", - "reference": "110e04497395123998220e244cceecb167cc6dda" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-mail/zipball/110e04497395123998220e244cceecb167cc6dda", - "reference": "110e04497395123998220e244cceecb167cc6dda", - "shasum": "" - }, - "require": { - "ext-iconv": "*", - "laminas/laminas-loader": "^2.9.0", - "laminas/laminas-mime": "^2.11.0", - "laminas/laminas-stdlib": "^3.17.0", - "laminas/laminas-validator": "^2.31.0", - "php": "~8.1.0 || ~8.2.0 || ~8.3.0", - "symfony/polyfill-intl-idn": "^1.27.0", - "symfony/polyfill-mbstring": "^1.27.0", - "webmozart/assert": "^1.11.0" - }, - "require-dev": { - "laminas/laminas-coding-standard": "~2.5.0", - "laminas/laminas-db": "^2.18", - "laminas/laminas-servicemanager": "^3.22.1", - "phpunit/phpunit": "^10.4.2", - "psalm/plugin-phpunit": "^0.18.4", - "symfony/process": "^6.3.4", - "vimeo/psalm": "^5.15" - }, - "suggest": { - "laminas/laminas-servicemanager": "^3.21 when using SMTP to deliver messages" - }, - "type": "library", - "extra": { - "laminas": { - "component": "Laminas\\Mail", - "config-provider": "Laminas\\Mail\\ConfigProvider" - } - }, - "autoload": { - "psr-4": { - "Laminas\\Mail\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "Provides generalized functionality to compose and send both text and MIME-compliant multipart e-mail messages", - "homepage": "https://laminas.dev", - "keywords": [ - "laminas", - "mail" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-mail/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-mail/issues", - "rss": "https://github.com/laminas/laminas-mail/releases.atom", - "source": "https://github.com/laminas/laminas-mail" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "abandoned": "symfony/mailer", - "time": "2023-11-02T10:32:34+00:00" - }, { "name": "laminas/laminas-math", "version": "3.7.0", @@ -3351,90 +3482,29 @@ "time": "2023-10-18T09:53:37+00:00" }, { - "name": "laminas/laminas-mime", - "version": "2.12.0", + "name": "laminas/laminas-modulemanager", + "version": "2.16.0", "source": { "type": "git", - "url": "https://github.com/laminas/laminas-mime.git", - "reference": "08cc544778829b7d68d27a097885bd6e7130135e" + "url": "https://github.com/laminas/laminas-modulemanager.git", + "reference": "8df7b237d75c04a1bc17b8f7d01eeb601cd7b7e3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-mime/zipball/08cc544778829b7d68d27a097885bd6e7130135e", - "reference": "08cc544778829b7d68d27a097885bd6e7130135e", + "url": "https://api.github.com/repos/laminas/laminas-modulemanager/zipball/8df7b237d75c04a1bc17b8f7d01eeb601cd7b7e3", + "reference": "8df7b237d75c04a1bc17b8f7d01eeb601cd7b7e3", "shasum": "" }, "require": { - "laminas/laminas-stdlib": "^2.7 || ^3.0", - "php": "~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0" + "brick/varexporter": "^0.3.2 || ^0.4 || ^0.5", + "laminas/laminas-config": "^3.7", + "laminas/laminas-eventmanager": "^3.4", + "laminas/laminas-stdlib": "^3.6", + "php": "~8.1.0 || ~8.2.0|| ~8.3.0", + "webimpress/safe-writer": "^1.0.2 || ^2.1" }, "conflict": { - "zendframework/zend-mime": "*" - }, - "require-dev": { - "laminas/laminas-coding-standard": "~2.4.0", - "laminas/laminas-mail": "^2.19.0", - "phpunit/phpunit": "~9.5.25" - }, - "suggest": { - "laminas/laminas-mail": "Laminas\\Mail component" - }, - "type": "library", - "autoload": { - "psr-4": { - "Laminas\\Mime\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "Create and parse MIME messages and parts", - "homepage": "https://laminas.dev", - "keywords": [ - "laminas", - "mime" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-mime/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-mime/issues", - "rss": "https://github.com/laminas/laminas-mime/releases.atom", - "source": "https://github.com/laminas/laminas-mime" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2023-11-02T16:47:19+00:00" - }, - { - "name": "laminas/laminas-modulemanager", - "version": "2.16.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-modulemanager.git", - "reference": "8df7b237d75c04a1bc17b8f7d01eeb601cd7b7e3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-modulemanager/zipball/8df7b237d75c04a1bc17b8f7d01eeb601cd7b7e3", - "reference": "8df7b237d75c04a1bc17b8f7d01eeb601cd7b7e3", - "shasum": "" - }, - "require": { - "brick/varexporter": "^0.3.2 || ^0.4 || ^0.5", - "laminas/laminas-config": "^3.7", - "laminas/laminas-eventmanager": "^3.4", - "laminas/laminas-stdlib": "^3.6", - "php": "~8.1.0 || ~8.2.0|| ~8.3.0", - "webimpress/safe-writer": "^1.0.2 || ^2.1" - }, - "conflict": { - "zendframework/zend-modulemanager": "*" + "zendframework/zend-modulemanager": "*" }, "require-dev": { "laminas/laminas-coding-standard": "^2.5", @@ -3566,16 +3636,16 @@ }, { "name": "laminas/laminas-mvc-i18n", - "version": "1.8.0", + "version": "1.9.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-mvc-i18n.git", - "reference": "669a00f36dd9fba8ec95516fe4afde7c4d8b8faa" + "reference": "433e71e949438239cce814536711914a37544c42" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-mvc-i18n/zipball/669a00f36dd9fba8ec95516fe4afde7c4d8b8faa", - "reference": "669a00f36dd9fba8ec95516fe4afde7c4d8b8faa", + "url": "https://api.github.com/repos/laminas/laminas-mvc-i18n/zipball/433e71e949438239cce814536711914a37544c42", + "reference": "433e71e949438239cce814536711914a37544c42", "shasum": "" }, "require": { @@ -3586,7 +3656,7 @@ "laminas/laminas-servicemanager": "^3.15.1", "laminas/laminas-stdlib": "^3.10.1", "laminas/laminas-validator": "^2.19.0", - "php": "~8.1.0 || ~8.2.0 || ~8.3.0" + "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" }, "conflict": { "laminas/laminas-mvc": "<3.0.0", @@ -3597,7 +3667,7 @@ "laminas/laminas-coding-standard": "~2.5.0", "phpspec/prophecy-phpunit": "^2.0.2", "phpunit/phpunit": "^9.6.13", - "psalm/plugin-phpunit": "^0.18.4", + "psalm/plugin-phpunit": "^0.19.0", "vimeo/psalm": "^5.15" }, "suggest": { @@ -3640,7 +3710,7 @@ "type": "community_bridge" } ], - "time": "2023-11-06T09:31:01+00:00" + "time": "2024-10-11T09:36:44+00:00" }, { "name": "laminas/laminas-mvc-plugin-flashmessenger", @@ -3713,37 +3783,37 @@ }, { "name": "laminas/laminas-paginator", - "version": "2.18.1", + "version": "2.19.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-paginator.git", - "reference": "6a03499a899fb8ba650594ddf4b4338d4235252a" + "reference": "d6339e7ad61491fd76b1490cf9f723d0348ce430" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-paginator/zipball/6a03499a899fb8ba650594ddf4b4338d4235252a", - "reference": "6a03499a899fb8ba650594ddf4b4338d4235252a", + "url": "https://api.github.com/repos/laminas/laminas-paginator/zipball/d6339e7ad61491fd76b1490cf9f723d0348ce430", + "reference": "d6339e7ad61491fd76b1490cf9f723d0348ce430", "shasum": "" }, "require": { "ext-json": "*", "laminas/laminas-stdlib": "^3.10.1", - "php": "~8.1.0 || ~8.2.0 || ~8.3.0" + "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" }, "conflict": { "zendframework/zend-paginator": "*" }, "require-dev": { - "laminas/laminas-cache": "^3.9", - "laminas/laminas-cache-storage-adapter-memory": "^2.2.0", - "laminas/laminas-coding-standard": "^2.4.0", - "laminas/laminas-config": "^3.8.0", - "laminas/laminas-filter": "^2.30", - "laminas/laminas-servicemanager": "^3.22", - "laminas/laminas-view": "^2.25", - "phpunit/phpunit": "^9.5.27", - "psalm/plugin-phpunit": "^0.18.4", - "vimeo/psalm": "^5.4" + "laminas/laminas-cache": "^3.12.2", + "laminas/laminas-cache-storage-adapter-memory": "^2.3.0", + "laminas/laminas-coding-standard": "^2.5.0", + "laminas/laminas-config": "^3.9.0", + "laminas/laminas-filter": "^2.37", + "laminas/laminas-servicemanager": "^3.22.1", + "laminas/laminas-view": "^2.35", + "phpunit/phpunit": "^10.5.30", + "psalm/plugin-phpunit": "^0.19.0", + "vimeo/psalm": "^5.25" }, "suggest": { "laminas/laminas-cache": "Laminas\\Cache component to support cache features", @@ -3788,7 +3858,7 @@ "type": "community_bridge" } ], - "time": "2024-01-11T11:00:36+00:00" + "time": "2024-10-16T13:10:19+00:00" }, { "name": "laminas/laminas-paginator-adapter-laminasdb", @@ -3980,33 +4050,33 @@ }, { "name": "laminas/laminas-router", - "version": "3.13.0", + "version": "3.14.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-router.git", - "reference": "04e14e757303787c83f79298dbd4483eebacfeb9" + "reference": "5e1f5ca7fe95200661b50235c891ed3eee02d3f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-router/zipball/04e14e757303787c83f79298dbd4483eebacfeb9", - "reference": "04e14e757303787c83f79298dbd4483eebacfeb9", + "url": "https://api.github.com/repos/laminas/laminas-router/zipball/5e1f5ca7fe95200661b50235c891ed3eee02d3f0", + "reference": "5e1f5ca7fe95200661b50235c891ed3eee02d3f0", "shasum": "" }, "require": { "laminas/laminas-http": "^2.15", "laminas/laminas-servicemanager": "^3.14.0", "laminas/laminas-stdlib": "^3.10.1", - "php": "~8.1.0 || ~8.2.0 || ~8.3.0" + "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" }, "conflict": { "zendframework/zend-router": "*" }, "require-dev": { "laminas/laminas-coding-standard": "~2.5.0", - "laminas/laminas-i18n": "^2.26.0", - "phpunit/phpunit": "^10.5.11", - "psalm/plugin-phpunit": "^0.18.4", - "vimeo/psalm": "^5.22.2" + "laminas/laminas-i18n": "^2.29.0", + "phpunit/phpunit": "^10.5.36", + "psalm/plugin-phpunit": "^0.19.0", + "vimeo/psalm": "^5.26.1" }, "suggest": { "laminas/laminas-i18n": "^2.15.0 if defining translatable HTTP path segments" @@ -4047,7 +4117,7 @@ "type": "community_bridge" } ], - "time": "2024-03-05T12:54:05+00:00" + "time": "2024-10-11T11:18:03+00:00" }, { "name": "laminas/laminas-serializer", @@ -4412,23 +4482,23 @@ }, { "name": "laminas/laminas-translator", - "version": "1.0.0", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-translator.git", - "reference": "86d176c01a96b0ef205192b776cb69e8d4ca06b1" + "reference": "12897e710e21413c1f93fc38fe9dead6b51c5218" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-translator/zipball/86d176c01a96b0ef205192b776cb69e8d4ca06b1", - "reference": "86d176c01a96b0ef205192b776cb69e8d4ca06b1", + "url": "https://api.github.com/repos/laminas/laminas-translator/zipball/12897e710e21413c1f93fc38fe9dead6b51c5218", + "reference": "12897e710e21413c1f93fc38fe9dead6b51c5218", "shasum": "" }, "require": { - "php": "~8.1.0 || ~8.2.0 || ~8.3.0" + "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" }, "require-dev": { - "laminas/laminas-coding-standard": "~2.5.0", + "laminas/laminas-coding-standard": "~3.0.0", "vimeo/psalm": "^5.24.0" }, "type": "library", @@ -4461,7 +4531,7 @@ "type": "community_bridge" } ], - "time": "2024-06-18T15:09:24+00:00" + "time": "2024-10-21T15:33:01+00:00" }, { "name": "laminas/laminas-uri", @@ -5086,16 +5156,16 @@ }, { "name": "league/flysystem", - "version": "3.28.0", + "version": "3.29.1", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c" + "reference": "edc1bb7c86fab0776c3287dbd19b5fa278347319" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c", - "reference": "e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/edc1bb7c86fab0776c3287dbd19b5fa278347319", + "reference": "edc1bb7c86fab0776c3287dbd19b5fa278347319", "shasum": "" }, "require": { @@ -5163,22 +5233,22 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.28.0" + "source": "https://github.com/thephpleague/flysystem/tree/3.29.1" }, - "time": "2024-05-22T10:09:12+00:00" + "time": "2024-10-08T08:58:34+00:00" }, { "name": "league/flysystem-local", - "version": "3.28.0", + "version": "3.29.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-local.git", - "reference": "13f22ea8be526ea58c2ddff9e158ef7c296e4f40" + "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/13f22ea8be526ea58c2ddff9e158ef7c296e4f40", - "reference": "13f22ea8be526ea58c2ddff9e158ef7c296e4f40", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/e0e8d52ce4b2ed154148453d321e97c8e931bd27", + "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27", "shasum": "" }, "require": { @@ -5212,22 +5282,22 @@ "local" ], "support": { - "source": "https://github.com/thephpleague/flysystem-local/tree/3.28.0" + "source": "https://github.com/thephpleague/flysystem-local/tree/3.29.0" }, - "time": "2024-05-06T20:05:52+00:00" + "time": "2024-08-09T21:24:39+00:00" }, { "name": "league/mime-type-detection", - "version": "1.15.0", + "version": "1.16.0", "source": { "type": "git", "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301" + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301", - "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9", + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9", "shasum": "" }, "require": { @@ -5258,7 +5328,7 @@ "description": "Mime-type detection for Flysystem", "support": { "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.15.0" + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.16.0" }, "funding": [ { @@ -5270,7 +5340,77 @@ "type": "tidelift" } ], - "time": "2024-01-28T23:22:08+00:00" + "time": "2024-09-21T08:32:55+00:00" + }, + { + "name": "league/oauth2-client", + "version": "2.7.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth2-client.git", + "reference": "160d6274b03562ebeb55ed18399281d8118b76c8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/160d6274b03562ebeb55ed18399281d8118b76c8", + "reference": "160d6274b03562ebeb55ed18399281d8118b76c8", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^6.0 || ^7.0", + "paragonie/random_compat": "^1 || ^2 || ^9.99", + "php": "^5.6 || ^7.0 || ^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.3.5", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpunit/phpunit": "^5.7 || ^6.0 || ^9.5", + "squizlabs/php_codesniffer": "^2.3 || ^3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\OAuth2\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Bilbie", + "email": "hello@alexbilbie.com", + "homepage": "http://www.alexbilbie.com", + "role": "Developer" + }, + { + "name": "Woody Gilk", + "homepage": "https://github.com/shadowhand", + "role": "Contributor" + } + ], + "description": "OAuth 2.0 Client Library", + "keywords": [ + "Authentication", + "SSO", + "authorization", + "identity", + "idp", + "oauth", + "oauth2", + "single sign on" + ], + "support": { + "issues": "https://github.com/thephpleague/oauth2-client/issues", + "source": "https://github.com/thephpleague/oauth2-client/tree/2.7.0" + }, + "time": "2023-04-16T18:19:15+00:00" }, { "name": "league/oauth2-server", @@ -6013,24 +6153,24 @@ }, { "name": "nette/schema", - "version": "v1.3.0", + "version": "v1.3.2", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "a6d3a6d1f545f01ef38e60f375d1cf1f4de98188" + "reference": "da801d52f0354f70a638673c4a0f04e16529431d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/a6d3a6d1f545f01ef38e60f375d1cf1f4de98188", - "reference": "a6d3a6d1f545f01ef38e60f375d1cf1f4de98188", + "url": "https://api.github.com/repos/nette/schema/zipball/da801d52f0354f70a638673c4a0f04e16529431d", + "reference": "da801d52f0354f70a638673c4a0f04e16529431d", "shasum": "" }, "require": { "nette/utils": "^4.0", - "php": "8.1 - 8.3" + "php": "8.1 - 8.4" }, "require-dev": { - "nette/tester": "^2.4", + "nette/tester": "^2.5.2", "phpstan/phpstan-nette": "^1.0", "tracy/tracy": "^2.8" }, @@ -6069,9 +6209,9 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.3.0" + "source": "https://github.com/nette/schema/tree/v1.3.2" }, - "time": "2023-12-11T11:54:22+00:00" + "time": "2024-10-06T23:10:23+00:00" }, { "name": "nette/utils", @@ -6161,16 +6301,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.1.0", + "version": "v5.3.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1" + "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1", - "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b", + "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b", "shasum": "" }, "require": { @@ -6213,9 +6353,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1" }, - "time": "2024-07-01T20:03:41+00:00" + "time": "2024-10-08T18:51:32+00:00" }, { "name": "opis/json-schema", @@ -7744,6 +7884,187 @@ }, "time": "2019-03-08T08:55:37+00:00" }, + { + "name": "ramsey/collection", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/ramsey/collection.git", + "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/collection/zipball/a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", + "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "captainhook/plugin-composer": "^5.3", + "ergebnis/composer-normalize": "^2.28.3", + "fakerphp/faker": "^1.21", + "hamcrest/hamcrest-php": "^2.0", + "jangregor/phpstan-prophecy": "^1.0", + "mockery/mockery": "^1.5", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpcsstandards/phpcsutils": "^1.0.0-rc1", + "phpspec/prophecy-phpunit": "^2.0", + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.9", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5", + "psalm/plugin-mockery": "^1.1", + "psalm/plugin-phpunit": "^0.18.4", + "ramsey/coding-standard": "^2.0.3", + "ramsey/conventional-commits": "^1.3", + "vimeo/psalm": "^5.4" + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Collection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "A PHP library for representing and manipulating collections.", + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" + ], + "support": { + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/2.0.0" + }, + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", + "type": "tidelift" + } + ], + "time": "2022-12-31T21:50:55+00:00" + }, + { + "name": "ramsey/uuid", + "version": "4.7.6", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "91039bc1faa45ba123c4328958e620d382ec7088" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/91039bc1faa45ba123c4328958e620d382ec7088", + "reference": "91039bc1faa45ba123c4328958e620d382ec7088", + "shasum": "" + }, + "require": { + "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12", + "ext-json": "*", + "php": "^8.0", + "ramsey/collection": "^1.2 || ^2.0" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "captainhook/captainhook": "^5.10", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "doctrine/annotations": "^1.8", + "ergebnis/composer-normalize": "^2.15", + "mockery/mockery": "^1.3", + "paragonie/random-lib": "^2", + "php-mock/php-mock": "^2.2", + "php-mock/php-mock-mockery": "^1.3", + "php-parallel-lint/php-parallel-lint": "^1.1", + "phpbench/phpbench": "^1.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^8.5 || ^9", + "ramsey/composer-repl": "^1.4", + "slevomat/coding-standard": "^8.4", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.9" + }, + "suggest": { + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "source": "https://github.com/ramsey/uuid/tree/4.7.6" + }, + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", + "type": "tidelift" + } + ], + "time": "2024-04-27T21:32:50+00:00" + }, { "name": "scssphp/scssphp", "version": "v1.13.0", @@ -7916,16 +8237,16 @@ }, { "name": "slm/locale", - "version": "1.1.0", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/basz/SlmLocale.git", - "reference": "42783f68a34d8afc3fdb048f40f3575559f5963e" + "reference": "1e0ac228c66b6ac0cb07e0097bcade5e813a2843" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/basz/SlmLocale/zipball/42783f68a34d8afc3fdb048f40f3575559f5963e", - "reference": "42783f68a34d8afc3fdb048f40f3575559f5963e", + "url": "https://api.github.com/repos/basz/SlmLocale/zipball/1e0ac228c66b6ac0cb07e0097bcade5e813a2843", + "reference": "1e0ac228c66b6ac0cb07e0097bcade5e813a2843", "shasum": "" }, "require": { @@ -7937,7 +8258,7 @@ "laminas/laminas-servicemanager": "^3.2", "laminas/laminas-stdlib": "^3.2.1", "laminas/laminas-view": "^2.9.0", - "php": "^7.2 || ~8.0.0 || ~8.1.0 || ~8.2.0" + "php": "^7.2 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.13.0", @@ -7977,9 +8298,9 @@ ], "support": { "issues": "https://github.com/basz/SlmLocale/issues", - "source": "https://github.com/basz/SlmLocale/tree/1.1.0" + "source": "https://github.com/basz/SlmLocale/tree/1.2.0" }, - "time": "2023-07-10T06:50:57+00:00" + "time": "2024-10-15T05:50:58+00:00" }, { "name": "steverhoades/oauth2-openid-connect-server", @@ -8089,52 +8410,347 @@ }, { "name": "symfony/console", - "version": "v6.4.11", + "version": "v6.4.12", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "72d080eb9edf80e36c19be61f72c98ed8273b765" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/72d080eb9edf80e36c19be61f72c98ed8273b765", + "reference": "72d080eb9edf80e36c19be61f72c98ed8273b765", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^5.4|^6.0|^7.0" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v6.4.12" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-20T08:15:52+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-04-18T09:32:20+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v6.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "8d7507f02b06e06815e56bb39aa0128e3806208b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/8d7507f02b06e06815e56bb39aa0128e3806208b", + "reference": "8d7507f02b06e06815e56bb39aa0128e3806208b", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/error-handler": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:49:08+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/8f93aec25d41b72493c6ddff14e916177c9efc50", + "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-04-18T09:32:20+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v6.4.12", "source": { "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "42686880adaacdad1835ee8fc2a9ec5b7bd63998" + "url": "https://github.com/symfony/filesystem.git", + "reference": "f810e3cbdf7fdc35983968523d09f349fa9ada12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/42686880adaacdad1835ee8fc2a9ec5b7bd63998", - "reference": "42686880adaacdad1835ee8fc2a9ec5b7bd63998", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/f810e3cbdf7fdc35983968523d09f349fa9ada12", + "reference": "f810e3cbdf7fdc35983968523d09f349fa9ada12", "shasum": "" }, "require": { "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^5.4|^6.0|^7.0" - }, - "conflict": { - "symfony/dependency-injection": "<5.4", - "symfony/dotenv": "<5.4", - "symfony/event-dispatcher": "<5.4", - "symfony/lock": "<5.4", - "symfony/process": "<5.4" - }, - "provide": { - "psr/log-implementation": "1.0|2.0|3.0" + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" }, "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/event-dispatcher": "^5.4|^6.0|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/lock": "^5.4|^6.0|^7.0", - "symfony/messenger": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0", - "symfony/stopwatch": "^5.4|^6.0|^7.0", - "symfony/var-dumper": "^5.4|^6.0|^7.0" + "symfony/process": "^5.4|^6.4|^7.0" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\Console\\": "" + "Symfony\\Component\\Filesystem\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -8154,16 +8770,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Eases the creation of beautiful and testable command line interfaces", + "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", - "keywords": [ - "cli", - "command-line", - "console", - "terminal" - ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.11" + "source": "https://github.com/symfony/filesystem/tree/v6.4.12" }, "funding": [ { @@ -8179,38 +8789,51 @@ "type": "tidelift" } ], - "time": "2024-08-15T22:48:29+00:00" + "time": "2024-09-16T16:01:33+00:00" }, { - "name": "symfony/deprecation-contracts", - "version": "v3.5.0", + "name": "symfony/mailer", + "version": "v6.4.12", "source": { "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" + "url": "https://github.com/symfony/mailer.git", + "reference": "b6a25408c569ae2366b3f663a4edad19420a9c26" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "url": "https://api.github.com/repos/symfony/mailer/zipball/b6a25408c569ae2366b3f663a4edad19420a9c26", + "reference": "b6a25408c569ae2366b3f663a4edad19420a9c26", "shasum": "" }, "require": { - "php": ">=8.1" + "egulias/email-validator": "^2.1.10|^3|^4", + "php": ">=8.1", + "psr/event-dispatcher": "^1", + "psr/log": "^1|^2|^3", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/mime": "^6.2|^7.0", + "symfony/service-contracts": "^2.5|^3" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } + "conflict": { + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<5.4", + "symfony/messenger": "<6.2", + "symfony/mime": "<6.2", + "symfony/twig-bridge": "<6.2.1" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/messenger": "^6.2|^7.0", + "symfony/twig-bridge": "^6.2|^7.0" }, + "type": "library", "autoload": { - "files": [ - "function.php" + "psr-4": { + "Symfony\\Component\\Mailer\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -8219,18 +8842,18 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "A generic function and convention to trigger deprecation notices", + "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/mailer/tree/v6.4.12" }, "funding": [ { @@ -8246,34 +8869,49 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-09-08T12:30:05+00:00" }, { - "name": "symfony/filesystem", - "version": "v6.4.9", + "name": "symfony/mime", + "version": "v6.4.12", "source": { "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "b51ef8059159330b74a4d52f68e671033c0fe463" + "url": "https://github.com/symfony/mime.git", + "reference": "abe16ee7790b16aa525877419deb0f113953f0e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/b51ef8059159330b74a4d52f68e671033c0fe463", - "reference": "b51ef8059159330b74a4d52f68e671033c0fe463", + "url": "https://api.github.com/repos/symfony/mime/zipball/abe16ee7790b16aa525877419deb0f113953f0e1", + "reference": "abe16ee7790b16aa525877419deb0f113953f0e1", "shasum": "" }, "require": { "php": ">=8.1", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.8" + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "egulias/email-validator": "~3.0.0", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/mailer": "<5.4", + "symfony/serializer": "<6.4.3|>7.0,<7.0.3" }, "require-dev": { - "symfony/process": "^5.4|^6.4|^7.0" + "egulias/email-validator": "^2.1.10|^3.1|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.4|^7.0", + "symfony/property-access": "^5.4|^6.0|^7.0", + "symfony/property-info": "^5.4|^6.0|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\Filesystem\\": "" + "Symfony\\Component\\Mime\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -8293,10 +8931,14 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides basic utilities for the filesystem", + "description": "Allows manipulating MIME messages", "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ], "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.4.9" + "source": "https://github.com/symfony/mime/tree/v6.4.12" }, "funding": [ { @@ -8312,7 +8954,7 @@ "type": "tidelift" } ], - "time": "2024-06-28T09:49:33+00:00" + "time": "2024-09-20T08:18:25+00:00" }, { "name": "symfony/options-resolver", @@ -9083,16 +9725,16 @@ }, { "name": "symfony/string", - "version": "v6.4.11", + "version": "v6.4.12", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "5bc3eb632cf9c8dbfd6529d89be9950d1518883b" + "reference": "f8a1ccebd0997e16112dfecfd74220b78e5b284b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/5bc3eb632cf9c8dbfd6529d89be9950d1518883b", - "reference": "5bc3eb632cf9c8dbfd6529d89be9950d1518883b", + "url": "https://api.github.com/repos/symfony/string/zipball/f8a1ccebd0997e16112dfecfd74220b78e5b284b", + "reference": "f8a1ccebd0997e16112dfecfd74220b78e5b284b", "shasum": "" }, "require": { @@ -9149,7 +9791,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.4.11" + "source": "https://github.com/symfony/string/tree/v6.4.12" }, "funding": [ { @@ -9165,7 +9807,7 @@ "type": "tidelift" } ], - "time": "2024-08-12T09:55:28+00:00" + "time": "2024-09-20T08:15:52+00:00" }, { "name": "symfony/var-dumper", @@ -9254,16 +9896,16 @@ }, { "name": "symfony/yaml", - "version": "v6.4.11", + "version": "v6.4.12", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "be37e7f13195e05ab84ca5269365591edd240335" + "reference": "762ee56b2649659380e0ef4d592d807bc17b7971" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/be37e7f13195e05ab84ca5269365591edd240335", - "reference": "be37e7f13195e05ab84ca5269365591edd240335", + "url": "https://api.github.com/repos/symfony/yaml/zipball/762ee56b2649659380e0ef4d592d807bc17b7971", + "reference": "762ee56b2649659380e0ef4d592d807bc17b7971", "shasum": "" }, "require": { @@ -9306,7 +9948,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v6.4.11" + "source": "https://github.com/symfony/yaml/tree/v6.4.12" }, "funding": [ { @@ -9322,20 +9964,20 @@ "type": "tidelift" } ], - "time": "2024-08-12T09:55:28+00:00" + "time": "2024-09-17T12:47:12+00:00" }, { "name": "vstelmakh/url-highlight", - "version": "v3.1.0", + "version": "v3.1.1", "source": { "type": "git", "url": "https://github.com/vstelmakh/url-highlight.git", - "reference": "354d04fe0239143cd3b64aa9131fc985f17460d6" + "reference": "24b1ecf30d2a682de834d1f15838657768886c45" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vstelmakh/url-highlight/zipball/354d04fe0239143cd3b64aa9131fc985f17460d6", - "reference": "354d04fe0239143cd3b64aa9131fc985f17460d6", + "url": "https://api.github.com/repos/vstelmakh/url-highlight/zipball/24b1ecf30d2a682de834d1f15838657768886c45", + "reference": "24b1ecf30d2a682de834d1f15838657768886c45", "shasum": "" }, "require": { @@ -9379,9 +10021,9 @@ ], "support": { "issues": "https://github.com/vstelmakh/url-highlight/issues", - "source": "https://github.com/vstelmakh/url-highlight/tree/v3.1.0" + "source": "https://github.com/vstelmakh/url-highlight/tree/v3.1.1" }, - "time": "2024-07-28T18:31:02+00:00" + "time": "2024-09-15T17:36:16+00:00" }, { "name": "vufind-org/vufind-marc", @@ -11029,16 +11671,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.12.2", + "version": "1.12.7", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "0ca1c7bb55fca8fe6448f16fff0f311ccec960a1" + "reference": "dc2b9976bd8b0f84ec9b0e50cc35378551de7af0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0ca1c7bb55fca8fe6448f16fff0f311ccec960a1", - "reference": "0ca1c7bb55fca8fe6448f16fff0f311ccec960a1", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dc2b9976bd8b0f84ec9b0e50cc35378551de7af0", + "reference": "dc2b9976bd8b0f84ec9b0e50cc35378551de7af0", "shasum": "" }, "require": { @@ -11083,7 +11725,7 @@ "type": "github" } ], - "time": "2024-09-05T16:09:28+00:00" + "time": "2024-10-18T11:12:07+00:00" }, { "name": "phpunit/php-code-coverage", @@ -11470,16 +12112,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.32", + "version": "10.5.36", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "f069f46840445d37a4e6f0de8c5879598f9c4327" + "reference": "aa0a8ce701ea7ee314b0dfaa8970dc94f3f8c870" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f069f46840445d37a4e6f0de8c5879598f9c4327", - "reference": "f069f46840445d37a4e6f0de8c5879598f9c4327", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/aa0a8ce701ea7ee314b0dfaa8970dc94f3f8c870", + "reference": "aa0a8ce701ea7ee314b0dfaa8970dc94f3f8c870", "shasum": "" }, "require": { @@ -11551,7 +12193,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.32" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.36" }, "funding": [ { @@ -11567,7 +12209,7 @@ "type": "tidelift" } ], - "time": "2024-09-04T13:33:39+00:00" + "time": "2024-10-08T15:36:51+00:00" }, { "name": "phrity/net-stream", @@ -11686,16 +12328,16 @@ }, { "name": "phrity/util-errorhandler", - "version": "1.1.0", + "version": "1.1.1", "source": { "type": "git", "url": "https://github.com/sirn-se/phrity-util-errorhandler.git", - "reference": "4016d9f9615a4c602f525b0542e4835e316a42e4" + "reference": "483228156e06673963902b1cc1e6bd9541ab4d5e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sirn-se/phrity-util-errorhandler/zipball/4016d9f9615a4c602f525b0542e4835e316a42e4", - "reference": "4016d9f9615a4c602f525b0542e4835e316a42e4", + "url": "https://api.github.com/repos/sirn-se/phrity-util-errorhandler/zipball/483228156e06673963902b1cc1e6bd9541ab4d5e", + "reference": "483228156e06673963902b1cc1e6bd9541ab4d5e", "shasum": "" }, "require": { @@ -11731,9 +12373,9 @@ ], "support": { "issues": "https://github.com/sirn-se/phrity-util-errorhandler/issues", - "source": "https://github.com/sirn-se/phrity-util-errorhandler/tree/1.1.0" + "source": "https://github.com/sirn-se/phrity-util-errorhandler/tree/1.1.1" }, - "time": "2024-03-05T19:32:14+00:00" + "time": "2024-09-12T06:49:16+00:00" }, { "name": "phrity/websocket", @@ -12564,16 +13206,16 @@ }, { "name": "sebastian/comparator", - "version": "5.0.2", + "version": "5.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "2d3e04c3b4c1e84a5e7382221ad8883c8fbc4f53" + "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2d3e04c3b4c1e84a5e7382221ad8883c8fbc4f53", - "reference": "2d3e04c3b4c1e84a5e7382221ad8883c8fbc4f53", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", + "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", "shasum": "" }, "require": { @@ -12584,7 +13226,7 @@ "sebastian/exporter": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^10.4" + "phpunit/phpunit": "^10.5" }, "type": "library", "extra": { @@ -12629,7 +13271,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.2" + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.3" }, "funding": [ { @@ -12637,7 +13279,7 @@ "type": "github" } ], - "time": "2024-08-12T06:03:08+00:00" + "time": "2024-10-18T14:56:07+00:00" }, { "name": "sebastian/complexity", @@ -13259,16 +13901,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.10.2", + "version": "3.10.3", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "86e5f5dd9a840c46810ebe5ff1885581c42a3017" + "reference": "62d32998e820bddc40f99f8251958aed187a5c9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/86e5f5dd9a840c46810ebe5ff1885581c42a3017", - "reference": "86e5f5dd9a840c46810ebe5ff1885581c42a3017", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/62d32998e820bddc40f99f8251958aed187a5c9c", + "reference": "62d32998e820bddc40f99f8251958aed187a5c9c", "shasum": "" }, "require": { @@ -13335,7 +13977,7 @@ "type": "open_collective" } ], - "time": "2024-07-21T23:26:44+00:00" + "time": "2024-09-18T10:38:58+00:00" }, { "name": "symfony/config", @@ -13479,16 +14121,16 @@ }, { "name": "symfony/dependency-injection", - "version": "v6.4.11", + "version": "v6.4.12", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "e93c8368dc9915c2fe12018ff22fcbbdd32c9a9e" + "reference": "cfb9d34a1cdd4911bc737a5358fd1cf8ebfb536e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/e93c8368dc9915c2fe12018ff22fcbbdd32c9a9e", - "reference": "e93c8368dc9915c2fe12018ff22fcbbdd32c9a9e", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/cfb9d34a1cdd4911bc737a5358fd1cf8ebfb536e", + "reference": "cfb9d34a1cdd4911bc737a5358fd1cf8ebfb536e", "shasum": "" }, "require": { @@ -13540,163 +14182,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v6.4.11" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-08-29T08:15:38+00:00" - }, - { - "name": "symfony/event-dispatcher", - "version": "v6.4.8", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "8d7507f02b06e06815e56bb39aa0128e3806208b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/8d7507f02b06e06815e56bb39aa0128e3806208b", - "reference": "8d7507f02b06e06815e56bb39aa0128e3806208b", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "symfony/event-dispatcher-contracts": "^2.5|^3" - }, - "conflict": { - "symfony/dependency-injection": "<5.4", - "symfony/service-contracts": "<2.5" - }, - "provide": { - "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "2.0|3.0" - }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/error-handler": "^5.4|^6.0|^7.0", - "symfony/expression-language": "^5.4|^6.0|^7.0", - "symfony/http-foundation": "^5.4|^6.0|^7.0", - "symfony/service-contracts": "^2.5|^3", - "symfony/stopwatch": "^5.4|^6.0|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\EventDispatcher\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.8" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-05-31T14:49:08+00:00" - }, - { - "name": "symfony/event-dispatcher-contracts", - "version": "v3.5.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/8f93aec25d41b72493c6ddff14e916177c9efc50", - "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "psr/event-dispatcher": "^1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\EventDispatcher\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to dispatching event", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/dependency-injection/tree/v6.4.12" }, "funding": [ { @@ -13712,7 +14198,7 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-09-20T08:18:25+00:00" }, { "name": "symfony/finder", @@ -13856,16 +14342,16 @@ }, { "name": "symfony/process", - "version": "v6.4.8", + "version": "v6.4.12", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "8d92dd79149f29e89ee0f480254db595f6a6a2c5" + "reference": "3f94e5f13ff58df371a7ead461b6e8068900fbb3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/8d92dd79149f29e89ee0f480254db595f6a6a2c5", - "reference": "8d92dd79149f29e89ee0f480254db595f6a6a2c5", + "url": "https://api.github.com/repos/symfony/process/zipball/3f94e5f13ff58df371a7ead461b6e8068900fbb3", + "reference": "3f94e5f13ff58df371a7ead461b6e8068900fbb3", "shasum": "" }, "require": { @@ -13897,7 +14383,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.4.8" + "source": "https://github.com/symfony/process/tree/v6.4.12" }, "funding": [ { @@ -13913,7 +14399,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-17T12:47:12+00:00" }, { "name": "symfony/stopwatch", @@ -14119,5 +14605,5 @@ "platform-overrides": { "php": "8.1" }, - "plugin-api-version": "2.2.0" + "plugin-api-version": "2.6.0" } diff --git a/config/application.config.php b/config/application.config.php index 9443e0b7a32..266ec770059 100644 --- a/config/application.config.php +++ b/config/application.config.php @@ -28,8 +28,13 @@ if (PHP_SAPI == 'cli' && APPLICATION_ENV !== 'testing') { $modules[] = 'VuFindConsole'; } -if (APPLICATION_ENV == 'development') { - $modules[] = 'WhoopsErrorHandler'; +if (APPLICATION_ENV === 'development' || APPLICATION_ENV === 'testing') { + if (!isset($_SERVER['HTTP_X_VUFIND_DISABLE_WHOOPS'])) { + $modules[] = 'WhoopsErrorHandler'; + } + $modules[] = 'VuFindDevTools'; +} +if (APPLICATION_ENV === 'development') { $modules[] = 'VuFindDevTools'; } if ($localModules = getenv('VUFIND_LOCAL_MODULES')) { diff --git a/config/vufind/Blender.ini b/config/vufind/Blender.ini index 15ca9d685ad..e7ab670ad88 100644 --- a/config/vufind/Blender.ini +++ b/config/vufind/Blender.ini @@ -234,6 +234,10 @@ exclude = blender_backend ; Do we want any facets to be collapsed by default? ;collapsedFacets = * +; Enable JS feature to select multiple facets without reloading the result page +; default : false (behaviour disabled) +;multiFacetsSelection = true + ; Most of these settings affect the way the [Advanced] facets are displayed; the ; translated_facets setting affects facets globally. [Advanced_Settings] diff --git a/config/vufind/BrowZine.ini b/config/vufind/BrowZine.ini index 6345c609aa0..dce09baa526 100644 --- a/config/vufind/BrowZine.ini +++ b/config/vufind/BrowZine.ini @@ -4,6 +4,8 @@ [General] ; BrowZine-issued access token for API access_token = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +; Use access_token_file to load the secret from another file instead of including it directly in this configuration. +;access_token_file = /path/to/secret ; The ID number of your library (found in your BrowZine public access URL) library_id = "yyy" diff --git a/config/vufind/Collection.ini b/config/vufind/Collection.ini index 7ff365bfa1a..a583fcedb54 100644 --- a/config/vufind/Collection.ini +++ b/config/vufind/Collection.ini @@ -56,6 +56,10 @@ showMoreInLightbox[*] = more ; Do we want any facets to be collapsed by default? ;collapsedFacets = * +; Enable JS feature to select multiple facets without reloading the result page +; default : false (behaviour disabled) +;multiFacetsSelection = true + ; These settings control which fields are available to sort on in the Collection ; module. [Sort] diff --git a/config/vufind/EDS.ini b/config/vufind/EDS.ini index 67fce1d8477..7ce74e326f6 100644 --- a/config/vufind/EDS.ini +++ b/config/vufind/EDS.ini @@ -197,6 +197,10 @@ top_rows = 2 ; And 2) the facet value counts will be wrong. ;orFacets = * +; Enable JS feature to select multiple facets without reloading the result page +; default : false (behaviour disabled) +;multiFacetsSelection = true + [Advanced_Facet_Settings] ; Any facets named in the list below will have their values run through the ; translation code; unlisted facets will displayed as-is without translation. For @@ -271,6 +275,8 @@ next_prev_navigation = false ip_auth = false user_name = "USERNAME" password = "PASSWORD" +; Use password_file to load the secret from another file instead of including it directly in this configuration. +;password_file = /path/to/secret profile = "PROFILE" organization_id = "VuFind from MyUniversity" diff --git a/config/vufind/EPF.ini b/config/vufind/EPF.ini index 5c55710efb3..ee5792267b7 100644 --- a/config/vufind/EPF.ini +++ b/config/vufind/EPF.ini @@ -91,6 +91,10 @@ top_rows = 2 ; VuFind displays match actual behavior. ;orFacets = * +; Enable JS feature to select multiple facets without reloading the result page +; default : false (behaviour disabled) +;multiFacetsSelection = true + ; Prevent specific facet values from being displayed to the user. ; Use facet field names as keys and untranslated facet values as values. [HideFacetValue] @@ -111,5 +115,7 @@ top_rows = 2 ip_auth = false user_name = [USERNAME] password = [PASSWORD] +; Use password_file to load the secret from another file instead of including it directly in this configuration. +;password_file = /path/to/secret profile = [PROFILE] organization_id = "VuFind from MyUniversity" diff --git a/config/vufind/Folio.ini b/config/vufind/Folio.ini index 65870d59cc0..84811c6a82a 100644 --- a/config/vufind/Folio.ini +++ b/config/vufind/Folio.ini @@ -6,6 +6,8 @@ base_url = https://localhost:9130 ; https://vufind.org/wiki/configuration:ils:folio username = diku_admin password = admin +; Use password_file to load the secret from another file instead of including it directly in this configuration. +;password_file = /path/to/secret tenant = diku ; If set to true, the driver will log all GET requests as debug messages; if false ; (the default), it will only log POSTs to reduce noise. @@ -121,16 +123,35 @@ extraHoldFields = requiredByDate:pickUpLocation ; requested item's effective location. Only applicable to item-level requests. ;limitPickupLocations = itemEffectiveLocation +; When the extra hold field requestGroup is used, the request group Delivery is offered +; whenever the FOLIO user's Request Preferences have delivery enabled. This setting +; overrides every user's FOLIO setting if it's necessary to globally disable delivery +; for backwards compatibility. +;allowDelivery = false + +; When the extra hold fields pickupLocation and requestGroup are used, and requestGroup +; is Delivery, the request form's list of "pickup locations" is actually a list of +; address types matching the addresses defined for the current user. In that context, +; this setting optionally further limits which FOLIO address types are offered. +;limitDeliveryAddressTypes[] = "Campus" + +; When the extra hold fields pickupLocation and requestGroup are used, and the user +; selects a request group, the pickup locations field label is adjusted to be more +; appropriate to that group. This setting defines translation keys used for the label. +; Disabling these settings stops customization of the pickup locations field label. +locationsLabelByRequestGroup["Delivery"] = "pick_up_location_label_delivery" +locationsLabelByRequestGroup["Hold Shelf"] = "pick_up_location_label_hold_shelf" + ; By default, a "Hold" type request is placed when an item is unavailable and a Page ; when an item is available. This setting overrides the default behavior for ; unavailable items and for all title-level requests. Legal values: "Page", "Hold" or ; "Recall" ;default_request = Hold -; It is possible that the default_request method will not work in every situation, -; e.g. if circulation rules prevent certain request types on certain item types or -; statuses; if you uncomment one or more of the values below, alternate request -; types will be attempted in the order listed if the initial request fails. +; While default_request (and standard driver behavior) determines the preferred request type, +; there are scenarios where the preferred request type is not possible (due to item statuses, +; circulation rules, etc.). This setting lists all request types that the user is allowed to +; place, in priority order from most preferred to least preferred. ;fallback_request_type[] = Page ;fallback_request_type[] = Hold ;fallback_request_type[] = Recall @@ -151,6 +172,11 @@ extraHoldFields = requiredByDate:pickUpLocation ; Regex mode example ;excludeHoldLocations[] = "/.*RESERVE.*/i" +; Hide the place hold/recall/page link when an item is currently loaned to a member +; of one of the following FOLIO patron groups (matching on patron group name). +; For example, block requests for items currently loaned out to other institutions. +;excludeHoldCurrentLoanPatronGroups[] = 'interlibrary_loan_patron' + ; When a request is cancelled through VuFind, use this cancellation reason ID. Most users ; will not have to change this ID unless they have replaced the cancellation reason ; reference data in mod-circulation-storage, or would prefer to use a cancellation @@ -186,12 +212,24 @@ in_transit[] = "Open - Awaiting delivery" ; retrieved from FOLIO will be retained. ;vufind_sort = "enumchron" -; If set to true and there are no items attached to a FOLIO holdings record, +; If set to true and there are no items attached to a FOLIO holdings record, ; VuFind will display the holdings summary, supplement, and indexes fields on the record ; holdings tab. ; Note: `hide_holdings[]` in config.ini can be used to suppress display of specific locations. ; show_holdings_no_items = true +; This section controls behavior on the "Checked Out Items" screen +[Loans] +; The size for a page of results; see also checked_out_page_size in config.ini, as +; the system will attempt to reconcile the global page size config there with the +; available options defined here. +page_size[] = 50 +; Sort options +;sort[dueDate/sort.ascending] = sort_due_date_asc +;sort[dueDate/sort.descending] = sort_due_date_desc +; Default sort (note: this must be defined in the sort[] options above to be applied!) +;default_sort=dueDate/sort.ascending + [CourseReserves] ; If set to true, the course number will be prefixed on the course name; if false, ; only the name will be displayed: diff --git a/config/vufind/OAuth2Server.yaml b/config/vufind/OAuth2Server.yaml index 87f65837197..67ca2da3009 100644 --- a/config/vufind/OAuth2Server.yaml +++ b/config/vufind/OAuth2Server.yaml @@ -59,6 +59,11 @@ Clients: # Note that a secret can only be used with confidential clients since public # ones have no way of using it securely. secret: "" + # By default a client can request all scopes. This setting can be used to limit + # the allowed scopes to a subset of all available ones. + #allowedScopes: + # - openid + # - email # Scope configuration. Keys are scope identifiers. Each identifier should include a # description (translation key) to be displayed to the user. The ils field should be diff --git a/config/vufind/Primo.ini b/config/vufind/Primo.ini index aab30300fa0..55de2b6d81c 100644 --- a/config/vufind/Primo.ini +++ b/config/vufind/Primo.ini @@ -190,6 +190,10 @@ top_rows = 2 ; Do we want to display cites/cited by links in search results (default is true)? ;display_citation_links = true +; Enable JS feature to select multiple facets without reloading the result page +; default : false (behaviour disabled) +;multiFacetsSelection = true + ; These settings affect the way the facets are displayed [Facet_Settings] facet_limit = 30 ; how many values should we show for each facet? diff --git a/config/vufind/RecordTabs.ini b/config/vufind/RecordTabs.ini index 1eef980c6c5..12918bcd5ab 100644 --- a/config/vufind/RecordTabs.ini +++ b/config/vufind/RecordTabs.ini @@ -133,14 +133,14 @@ tabs[Preview] = preview tabs[Details] = StaffViewArray defaultTab = null -[VuFind\RecordDriver\WorldCat] -tabs[Holdings] = HoldingsWorldCat +[VuFind\RecordDriver\WorldCat2] +tabs[Holdings] = HoldingsWorldCat2 tabs[Description] = Description tabs[TOC] = TOC tabs[UserComments] = UserComments tabs[Reviews] = Reviews tabs[Excerpt] = Excerpt -tabs[Details] = StaffViewMARC +tabs[Details] = StaffViewArray defaultTab = null ; TabScripts is a special section that defines any extra scripts that may be required diff --git a/config/vufind/Search2.ini b/config/vufind/Search2.ini index 95969ccb9eb..81a6a8b4674 100644 --- a/config/vufind/Search2.ini +++ b/config/vufind/Search2.ini @@ -267,6 +267,10 @@ top_rows = 2 ;sorted_by_index[] = building; ;sorted_by_index[] = institution; +; Enable JS feature to select multiple facets without reloading the result page +; default : false (behaviour disabled) +;multiFacetsSelection = true + [Advanced_Facets] callnumber-first = "Call Number" language = Language diff --git a/config/vufind/Summon.ini b/config/vufind/Summon.ini index 78884a03200..25490b8ef07 100644 --- a/config/vufind/Summon.ini +++ b/config/vufind/Summon.ini @@ -202,6 +202,10 @@ top_rows = 2 ; Do we want any facets to be collapsed by default? ;collapsedFacets = * +; Enable JS feature to select multiple facets without reloading the result page +; default : false (behaviour disabled) +;multiFacetsSelection = true + ; These settings affect the way the facets are displayed [Facet_Settings] ; By default, how many values should we show for each facet? diff --git a/config/vufind/WorldCat.ini b/config/vufind/WorldCat.ini deleted file mode 100644 index f17612e6b3f..00000000000 --- a/config/vufind/WorldCat.ini +++ /dev/null @@ -1,89 +0,0 @@ -; This section contains global settings affecting search behavior. -[General] -; This setting controls the default sort order of search results; the selected -; option should be one of the options present in the [Sorting] section below. -default_sort = relevance - -;default_noresults_recommend[] = SwitchTab - -; This section shows which search types will display in the basic search box at -; the top of WorldCat pages. The name of each setting below corresponds with one -; or more indices defined in the WorldCat API (multiple values are separated by -; colons). The value of each setting is the text to display on screen. All -; on-screen text will be run through the translator, so be sure to update language -; files if necessary. The order of these settings will be maintained in the -; drop-down list in the UI. -; -; For a complete list of legal values, see the SRU Explain page here: -; http://worldcat.org/webservices/catalog/ -[Basic_Searches] -srw.kw = "All Fields" -srw.ti:srw.se = Title -srw.au = Author -srw.su = Subject -srw.dd:srw.lc = "Call Number" -srw.bn:srw.in = "ISBN/ISSN" - -; This section defines which search options will be included on the advanced -; search screen. All the notes above [Basic_Searches] also apply here. -[Advanced_Searches] -srw.kw = adv_search_all -srw.ti:srw.se = adv_search_title -srw.au = adv_search_author -srw.su = adv_search_subject -srw.dd:srw.lc = adv_search_callnumber -srw.bn:srw.in = adv_search_isn -srw.pb = adv_search_publisher -srw.se = adv_search_series -srw.yr = adv_search_year - -; This section defines the sort options available on WorldCat search results. -; Values on the left of the equal sign are WorldCat API sort values. Values -; on the right of the equal sign are text that will be run through the -; translation module and displayed on screen. -[Sorting] -relevance = sort_relevance -Date,,0 = sort_year -Date = sort_year_asc -Author = sort_author -Title = sort_title - -; This section allows you to specify hidden sorting options. They can be used to create a -; whitelist of sort values using regular expressions. If you want to do this add regexes to -; the pattern[] array. All sort values that match at least one of these pattern are allowed -; in searches. But they will not be shown in the sort selection in the result list. -[HiddenSorting] -;pattern[] = .* ; E.g. uncomment this line to allow any value - -; This section controls the behavior of the WorldCatRecord module. See the -; [Record] section of the main config.ini for more detailed documentation. -[Record] -; Because the WorldCat database changes frequently, this feature sometimes -; behaves unpredictably; it is recommended that you leave it disabled: -next_prev_navigation = false - -related[] = "WorldCatSimilar" - -; This section controls what happens when a record title in a search result list -; is clicked. VuFind can either embed the full result directly in the list using -; AJAX or can display it at its own separate URL as a full HTML page. -; full - separate page (default) -; tabs - embedded using tabs (see record/tabs.phtml) -; accordion - embedded using an accordion (see record/accordion.phtml) -; NOTE: To turn this feature on for favorite lists, see the lists_view setting -; in the [Social] section of config.ini. -; NOTE: This feature is incompatible with SyndeticsPlus content; please use -; regular Syndetics if necessary. -[List] -view=full - -; This section contains additional settings to pass to the WorldCat connector -; code. -[Connector] -; Set this to the latitude,longitude position of your library to ensure that -; custom holding locations are retrieved from your region first. -;latLon = "40.0373881,-75.3442107" - -; When looking up holdings at other libraries, should we retrieve holdings for -; any record matching the FRBR group (true) or only for exact matches (false)? -;useFrbrGroupingForHoldings = false diff --git a/config/vufind/WorldCat2.ini b/config/vufind/WorldCat2.ini new file mode 100644 index 00000000000..643ce94ef02 --- /dev/null +++ b/config/vufind/WorldCat2.ini @@ -0,0 +1,171 @@ +; This section contains global settings affecting search behavior. +[General] +; This setting controls the default sort order of search results; the selected +; option should be one of the options present in the [Sorting] section below. +default_sort = relevance + +; This section controls the result limit options for search results. default_limit +; sets the default number of results per page. limit_options is a comma-separated +; list of numbers to be presented to the end-user. If only one limit is required, +; set default_limit and leave limit_options commented out. +; WARNING: The WorldCat API does not allow limits higher than 50. +default_limit = 20 +;limit_options = 10,20,50 + +; These are the default recommendations modules to use when no specific setting +; are found in the [TopRecommendations], [SideRecommendations] or +; [NoResultsRecommendations] sections below. +; See the comments above those sections for details on legal settings. You may +; repeat these lines to load multiple recommendations. +default_side_recommend[] = SideFacets:Facets:CheckboxFacets:WorldCat2 +;default_noresults_recommend[] = SwitchTab + +; The filters listed below will be applied to all new searches by default. Omit +; this setting to have no default filters applied. These differ from hidden +; filters because they are visible in the UI and may be removed by the user. +default_filters[] = "groupRelatedEditions:true" + +; Your holdings symbol (usually a three-letter code) - used for excluding your +; institution's holdings from the search results. +;exclude_code = XXX + +; OCLC may limit the length/complexity of search queries. This setting defines +; the threshold (in terms of distinct words) that makes a query too large and +; will lead to the user receiving an error message instead of search results. +terms_limit = 30 + +; The following sections can be used to associate specific recommendations +; modules with specific search types defined in the [Basic_Searches] section +; below. For all the details on how these sections work, see the comments above +; the equivalent sections of searches.ini. Recommendations work the same here +; as they do in the regular Search module. +[SideRecommendations] +; No search-specific settings by default -- add your own here. +[TopRecommendations] +; No search-specific settings by default -- add your own here. +[NoResultsRecommendations] +; No search-specific settings by default -- add your own here. + +; This section is reserved for special boolean facets. These are displayed +; as checkboxes. If the box is checked, the filter on the left side of the +; equal sign is applied. If the box is not checked, the filter is not applied. +; The value on the right side of the equal sign is the text to display to the +; user. It will be run through the translation code, so be sure to update the +; language files appropriately. +[CheckboxFacets] +; Important: the related editions/variant records settings are incompatible with +; each other; only one should be uncommented at a time. +groupRelatedEditions:true = "worldcat_group_related_editions" +;groupVariantRecords:true = "worldcat_group_variant_records" + +; The name of the index field is on the left +; The display name of the field is on the right +[Facets] +itemType = Format +language = Language +;database = Database +genre = Genre +datePublished = "adv_search_year" ; share year string w/advanced search page + +[FacetLabels] +labelSections[] = Facets +checkboxSections[] = CheckboxFacets + +[Advanced_Facet_Settings] +translated_facets[] = itemType:WorldCatFormats +translated_facets[] = language:ISO639-3 + +; This section shows which search types will display in the basic search box at +; the top of WorldCat v2 pages. The name of each setting below corresponds with one +; or more indices defined in the WorldCat API (multiple values are separated by +; colons). The value of each setting is the text to display on screen. All +; on-screen text will be run through the translator, so be sure to update language +; files if necessary. The order of these settings will be maintained in the +; drop-down list in the UI. +; +; For a complete list of legal values, see the Bibliographic record indexes documentation: +; https://help.oclc.org/Librarian_Toolbox/Searching_WorldCat_Indexes/Bibliographic_records/Bibliographic_record_indexes +[Basic_Searches] +kw = "All Fields" +ti:se = Title +au = Author +su = Subject +dd:lc = "Call Number" +bn:in = "ISBN/ISSN" + +; This section defines which search options will be included on the advanced +; search screen. All the notes above [Basic_Searches] also apply here. +[Advanced_Searches] +kw = adv_search_all +ti:se = adv_search_title +au = adv_search_author +su = adv_search_subject +dd:lc = adv_search_callnumber +bn:in = adv_search_isn +pb = adv_search_publisher +se = adv_search_series +yr = adv_search_year + +; This section defines the sort options available on WorldCat search results. +; Values on the left of the equal sign are WorldCat API sort values. Values +; on the right of the equal sign are text that will be run through the +; translation module and displayed on screen. +[Sorting] +bestMatch = sort_relevance +publicationDateDesc = sort_year +publicationDateAsc = "sort_year asc" +creator = sort_author +title = sort_title + +; This section allows you to specify hidden sorting options. They can be used to create a +; whitelist of sort values using regular expressions. If you want to do this add regexes to +; the pattern[] array. All sort values that match at least one of these pattern are allowed +; in searches. But they will not be shown in the sort selection in the result list. +[HiddenSorting] +;pattern[] = .* ; E.g. uncomment this line to allow any value + +; This section controls the behavior of the WorldCatRecord module. See the +; [Record] section of the main config.ini for more detailed documentation. +[Record] +; Because the WorldCat database changes frequently, this feature sometimes +; behaves unpredictably; it is recommended that you leave it disabled: +next_prev_navigation = false + +; Set to true to include URLs in records; many WorldCat URLs do not work, so they are disabled +; by default. +show_urls = false + +; This array controls which Related modules are used to display sidebars on the +; record view page. See config.ini's [Record] section for a list of available options. +related[] = "WorldCat2Similar" + +; This section controls what happens when a record title in a search result list +; is clicked. VuFind can either embed the full result directly in the list using +; AJAX or can display it at its own separate URL as a full HTML page. +; full - separate page (default) +; tabs - embedded using tabs (see record/tabs.phtml) +; accordion - embedded using an accordion (see record/accordion.phtml) +; NOTE: To turn this feature on for favorite lists, see the lists_view setting +; in the [Social] section of config.ini. +; NOTE: This feature is incompatible with SyndeticsPlus content; please use +; regular Syndetics if necessary. +[List] +view=full + +; This section contains additional settings to pass to the WorldCat connector code. +[Connector] +; The base URL for the WorldCat search API: +base_url = "https://americas.discovery.api.oclc.org/worldcat/search/v2" +; The authorize and access token URLs for OAuth2: +auth_url = "https://oauth.oclc.org/auth" +token_url = "https://oauth.oclc.org/token" +; Credentials obtained from https://platform.worldcat.org/wskey/ +wskey = "your-wskey-here" +secret = "your-secret-here" + +; This setting controls the behavior of the Holdings tab by sending additional +; parameters to Holdings API calls. See the OCLC documentation for a list of +; supported options: https://developer.api.oclc.org/wcv2#/Member%20General%20Holdings/find-bib-holdings +[Holdings] +; Uncomment to limit the number of libraries listed in the holdings tab: +;limit = 5 diff --git a/config/vufind/combined.ini b/config/vufind/combined.ini index b074ebb7ca2..2731d258d40 100644 --- a/config/vufind/combined.ini +++ b/config/vufind/combined.ini @@ -1,5 +1,5 @@ ; This file controls the "combined search" module. Each section is named for a -; search backend (e.g. "Solr", "Summon", "WorldCat", etc.). If you want to create +; search backend (e.g. "Solr", "Summon", "WorldCat2", etc.). If you want to create ; multiple columns using the same backend but different settings, you may add a ; colon and a suffix (e.g. "Solr:filter1", "Solr:filter2") to differentiate the ; sections. diff --git a/config/vufind/config.ini b/config/vufind/config.ini index 6dc6cfa4b75..0d2d33fa780 100644 --- a/config/vufind/config.ini +++ b/config/vufind/config.ini @@ -27,6 +27,10 @@ url = http://library.myuniversity.edu/vufind ; Set to true if VuFind is behind a reverse proxy (typically Apache with mod_proxy), ; make sure your reverse proxy sets the necessary headers. ;reverse_proxy = true +; Email address to use as the contact point for support requests. +; If you have defined default_from in the [Mail] section, you can set this to an +; empty string to hide the email address from error messages etc. (e.g. when all +; requests should be made via feedback forms). email = support@myuniversity.edu ; The title of your site, used in some messages and (by default) page tags. ; Including site title in page titles is recommended to improve browser tab @@ -39,13 +43,15 @@ titleSeparator = "::" ; This is the default theme for non-mobile devices (or all devices if mobile_theme ; is disabled below). Available standard themes: ; bootstrap3 = HTML5 theme using Bootstrap 3 + jQuery libraries, with minimal styling -; bootprint3 = bootstrap3 theme with more attractive default styling applied -; (named after the earlier, now-deprecated blueprint theme) -; sandal = bootstrap3 theme with a "flat" styling applied (a newer look -; than bootprint3). -; bootstrap5 = HTML5 theme using Bootstrap 5 with minimal styling (beta). -; sandal5 = Like sandal, but based on bootstrap5 (beta). -theme = sandal +; (deprecated; use bootstrap5 instead) +; bootprint3 = bootstrap3 theme with more opinionated default styling applied +; (deprecated; use bootstrap5 or sandal5 instead) +; sandal = bootstrap3 theme with a "flat" styling applied (deprecated; use +; sandal5 instead). +; bootstrap5 = HTML5 theme using Bootstrap 5 with minimal styling. +; sandal5 = An extension of bootstrap5 applying "flat" styling for a more +; opinionated design. +theme = sandal5 ; Uncomment the following line to use a different default theme for mobile devices. ; You may not wish to use this setting if you are using one of the Bootstrap-based @@ -182,7 +188,13 @@ allowSavedSearches = true ; features (such as display of hierarchies). nonJavascriptSupportEnabled = false ; Generator value to display in an HTML header <meta> tag: -generator = "VuFind 10.0" +generator = "VuFind 10.1" +; The following setting can be set to true in case extended HTML attribute +; escaping is needed. Default is false. Extended escaping is slower and +; results in larger HTML responses than normal escaping, so this setting +; should normally be left disabled, but must be enabled if there are unquoted +; HTML attributes in local templates. +;extendedHtmlAttributeEscaping = false ; This section allows you to configure the mechanism used for storing user ; sessions. Available types: File, Memcache, Database, Redis. @@ -312,6 +324,21 @@ driver = Sample ; library_cards setting (see below). allowUserLogin = true +; If true (default), any ILS credentials stored with the user are checked up-front +; when the user logs in to VuFind. If the credentials don't work, they're cleared, +; and the user will be prompted for new credentials when accessing a page that +; needs them. This setting is ignored if allowUserLogin is false. +checkILSCredentialsOnLogin = true + +; ILS data cache life time in seconds. The cache is used to avoid repeating +; requests too often (but often enough to not use stale data). +; Default is 60 seconds. Set to 0 to disable caching. +; * can be used to set the default for all cacheable functions (patronLogin, +; getProxiedUsers, getProxyingUsers or getPurchaseHistory): +;cacheLifeTime[*] = 60 +; Default cache life time can be overridden for any cacheable function: +;cacheLifeTime[getProxiedUsers] = 300 + ; loadNoILSOnFailure - Whether or not to load the NoILS driver if the main driver fails loadNoILSOnFailure = false @@ -695,27 +722,45 @@ search_enabled = false ; This section requires no changes for most installations; if your SMTP server ; requires authentication, you can fill in a username and password below. [Mail] +; For normal SMTP you can use the following settings: host = localhost port = 25 ;username = user ;password = pass -; The server name to report to the upstream mail server when sending mail. -;name = vufind.myuniversity.edu +; Use password_file to load the secret from another file instead of including it +; directly in this configuration. +;password_file = /path/to/secret ; If a login is required you can define which protocol to use for securing the ; connection. If no explicit protocol ('tls' or 'ssl') is configured, a protocol ; based on the configured port is chosen (587 -> tls, 487 -> ssl). ;secure = tls ; This setting enforces a limit (in seconds) on the lifetime of an SMTP -; connection, which can be useful when sending batches of emails, since it can -; help avoid errors caused by server timeouts. Comment out the setting to disable -; the limit. +; connection before the connection is checked, which can be useful when sending +; batches of emails, since it can help avoid errors caused by server timeouts. +; This is mapped to the ping_threshold option in Symfony Mailer. +; Comment out the setting to use the default ping threshold. +; See https://symfony.com/doc/current/mailer.html#other-options for more +; information. connection_time_limit = 60 +; The server name to report to the upstream mail server when sending mail. +;name = vufind.myuniversity.edu + +; To use any other transport than SMTP, you can use the dsn setting to define +; the connection string. +; See https://symfony.com/doc/current/mailer.html#transport-setup for more +; information on the DSN configuration. Also note that VuFind does not include the +; third-party transports out of box, but you can add them to composer.local.json. +;dsn = "native://default" + ; Uncomment this setting to disable outbound mail but simulate success; this ; is useful for interface testing but should never be used in production! ;testOnly = true ; Set to a file path writable by VuFind to log email messages into that file; ; primarily intended for testing/debugging purposes. ;message_log = /tmp/emails.log +; Message log format. Valid options are 'plain' for plain text (default) and +; 'serialized' for serialized Email objects. +;message_log_format = plain ; The action to email records and searches may be "enabled", "disabled" or "require_login" ; (default = "require_login") ; If set to "enabled", users can send anonymous emails; If set to "require_login", @@ -730,7 +775,8 @@ user_editable_subjects = false ; How many recipients is the user allowed to specify? (use 0 for no limit) maximum_recipients = 1 ; Populate the "from" field with this value if user_email_in_from is false and/or no -; user is logged in: +; user is logged in. If specified, this is also used as the "from" address for any +; messages sent by the system (such as password change notifications). ;default_from = "no-reply@myuniversity.edu" ; Should we hide the "from" field in email forms? If no from field is visible, emails ; will be sent based on user_email_in_from and default_from above, with the email @@ -771,13 +817,22 @@ url_shortener_key_type = md5 [Database] ; Connection string format is [platform]://[username]:[password]@[host]:[port]/[db] ; where: -; [platform] = database platform (mysql, oci8 or pgsql) +; [platform/driver] = database platform (mysql, oci8 or pgsql) ; [username] = username for connection ; [password] = password for connection (optional) ; [host] = host of database server ; [port] = port of database server (optional) ; [db] = database name +; For more granular configuration, comment out the database setting and use the individual parameters below. database = mysql://root@localhost/vufind +;database_driver = "mysql" +;database_username = "notroot" +;database_password = "password" +; database_password_file will be used in priority over database_password +;database_password_file = "/path/to/secret" +;database_host = "localhost" +;database_port = "3306" +;database_name = "vufind" ; Should SSL be enabled on connections? (Currently only supported for MySQL). ; IMPORTANT: when using Linux, if your database connection string above uses @@ -1322,15 +1377,6 @@ url = "https://api.booksite.com" ;apiId = myAccessId ;apiKey = mySecretKey -; This section must be filled in if you plan to use the optional WorldCat -; search module. Otherwise, it may be ignored. -;[WorldCat] -;Your WorldCat search API key -;apiKey = "long-search-api-key-goes-here" -;Your holdings symbol (usually a three-letter code) - used for excluding your -; institution's holdings from the search results. -;OCLCCode = MYCODE - ; This section must be filled in to use Relais functionality. When ; activated, this function will allow users to place ILL requests on unavailable ; items through the record holdings tab. @@ -1992,6 +2038,16 @@ callnumber_handler = false load_batch_wise = true load_observable_only = true +; If set to true, the item status display will include additional holdings text fields. +; This option will only take effect when show_full_status is also set to true. +include_holdings_text_fields = true + +; The set of additional holdings text fields shown in the item status display. If not +; set explicitly, the default is to show all fields defined in holdings_text_fields[]. +;displayed_holdings_text_fields[] = 'holdings_notes' +;displayed_holdings_text_fields[] = 'summary' + + ; This section controls the behavior of the Record module. [Record] ; Set this to true in order to enable "next" and "previous" links to navigate @@ -2062,7 +2118,7 @@ hide_holdings[] = "World Wide Web" ; MoreByAuthorSolr - Display books from the Solr index matching the current ; record's primary author. ; Similar - Similarity based on Solr lookup -; WorldCatSimilar - Similarity based on WorldCat lookup +; WorldCat2Similar - Similarity based on WorldCat v2 lookup related[] = "Similar" ;related[] = "MoreByAuthorSolr" @@ -2131,11 +2187,18 @@ includeSchemaOrgMetadata = true [AlphaBrowse] ; This setting controls how many headings are displayed on each page of results: page_size = 20 + ; How many headings to show before the match (or the spot where the match ; would have been found). Default is 0 for backwards compatibility. rows_before = 0 + +; For topic browse, controls the separator to place between terms when +; displaying topic headings. Defaults to " > " if not set. +;topic_browse_separator = " > " + ; highlight the match row (or spot where match would have been)? default false highlighting = false + ; AlphaBrowse results are not subject to dynamic filtering. If you have default ; filters defined in searches.ini, you most likely will want to disable them when ; users navigate from browse results to search results, to ensure that the result @@ -2144,12 +2207,14 @@ highlighting = false ; rather than NARROW search results, you will likely want to change this setting ; to false to avoid inconsistencies. bypass_default_filters = true + ; SEE ALSO: the General/includeAlphaBrowse setting in searchbox.ini, for including ; alphabrowse options in the main search drop-down options. ; This section controls the order and content of the browse type menu in the ; Alphabetic Browse module. The key is the browse index to use, the value is the ; string to display to the user (subject to translation). + [AlphaBrowse_Types] topic = "By Topic" author = "By Author" @@ -2247,6 +2312,8 @@ treeSearchLimit = 100 ;simpleContainerLinks = true ; If true, throw an exception if hierarchy parent and sequence data is out of sync. validateHierarchySequences = true +; Uncomment to hide tree record preview if display width is too narrow +;hide_preview_in_narrow_displays = true ; This section will be used to configure the feedback module. ; Set "tab_enabled" to true in order to enable the feedback module. @@ -2360,7 +2427,7 @@ validateHierarchySequences = true [SearchTabs] ;Solr = Catalog ;Summon = Summon -;WorldCat = WorldCat +;WorldCat2 = WorldCat ;Solr:filtered = "Catalog (Main Building Books)" ;EDS = "EBSCO Discovery Service" ;EIT = "EBSCO Integration Toolkit" @@ -2395,7 +2462,7 @@ validateHierarchySequences = true [SearchHistoryLabels] ;Solr = Catalog ;Summon = Summon -;WorldCat = WorldCat +;WorldCat2 = WorldCat ;SolrWeb = "Library Website" ;EDS = "EBSCO Discovery Service" @@ -2448,6 +2515,8 @@ validateHierarchySequences = true ; [Http] section, or your Captcha may not work. ;recaptcha_siteKey = "get your reCaptcha key at" ;recaptcha_secretKey = "https://www.google.com/recaptcha/admin/create" +; Use recaptcha_secretKey_file to load the secret from another file instead of including it directly in this configuration. +;recaptcha_secretKey_file = /path/to/secret ; Valid theme values: dark, light ;recaptcha_theme = light @@ -2511,7 +2580,7 @@ max_tag_length = 64 case_sensitive_tags = false ; If this setting is set to false, users will not be presented with a search ; drop-down or advanced search link when searching/viewing tags. This is recommended -; when using a multi-backend system (e.g. Solr + Summon + WorldCat). If set to +; when using a multi-backend system (e.g. Solr + Summon + WorldCat2). If set to ; true, the standard Solr search options and advanced search link will be shown ; in the tag screens; this is recommended when using a Solr-only configuration. show_solr_options_in_tag_search = false diff --git a/config/vufind/facets.ini b/config/vufind/facets.ini index 05d42b275e1..69a97dd655c 100644 --- a/config/vufind/facets.ini +++ b/config/vufind/facets.ini @@ -180,6 +180,10 @@ top_rows = 2 ;sorted_by_index[] = building; ;sorted_by_index[] = institution; +; Enable JS feature to select multiple facets without reloading the result page +; default : false (behaviour disabled) +;multiFacetsSelection = true + ; The author home screen has different facets [Author] topic_facet = "Related Subjects" diff --git a/config/vufind/reserves.ini b/config/vufind/reserves.ini index cddeb3a9238..d5540a6dffa 100644 --- a/config/vufind/reserves.ini +++ b/config/vufind/reserves.ini @@ -42,6 +42,11 @@ formatting_rule[*] = "phrase" [Autocomplete_Types] Reserves = "SolrReserves:AllFields:course,instructor,department" +[Advanced_Settings] +translated_facets[] = course_str:Reserves +translated_facets[] = department_str:Reserves +translated_facets[] = instructor_str:Reserves + [SearchCache] ;adapter = Memcached ;options[servers] = "localhost:11211,otherhost:11211" diff --git a/config/vufind/webcrawl.ini b/config/vufind/webcrawl.ini index ac6c9c59a42..8909d7977a5 100644 --- a/config/vufind/webcrawl.ini +++ b/config/vufind/webcrawl.ini @@ -6,4 +6,22 @@ url[] = http://library.myuniversity.edu/sitemap.xml [General] ; Uncomment the setting below to get more detailed output from the crawler: -;verbose = true \ No newline at end of file +;verbose = true + +[Cache] +; When set, the result of transforming sitemaps into Solr documents will +; be written to the specified directory in addition to being sent to Solr. +;transform_cache_dir = "/path/to/directory" + +; When set to true, the transform cache will be "write-only" -- crawling +; will always fully re-fetch all data, and the cache will only be populated +; for reference/backup purposes. +; When set to false (the default), the crawler will attempt to reuse existing +; transformed data from the cache as long as the cache update date is newer +; than any last modification specified in the sitemap lastmod date. If the +; sitemap does not specify a modification date, the data will always be +; considered to be expired. The crawler can be permitted to read expired data +; from the cache with the --use-expired-cache flag, which can be useful if you +; need to very quickly rebuild your index (e.g. after a Solr upgrade) and do +; not mind if some of the data is out of date. +;transform_cache_write_only = true diff --git a/config/vufind/website.ini b/config/vufind/website.ini index c423ef10f3b..b833dfdd7a4 100644 --- a/config/vufind/website.ini +++ b/config/vufind/website.ini @@ -52,6 +52,10 @@ facet_limit = 30 ;url = http://localhost:8983/solr ;default_core = website +; Enable JS feature to select multiple facets without reloading the result page +; default : false (behaviour disabled) +;multiFacetsSelection = true + ; This section controls default behavior of the Web Search API. Settings omitted ; here will be inherited from searches.ini; see that file for more details. [API] diff --git a/import/browse-indexing.jar b/import/browse-indexing.jar index 0fad614df73..6961817e0d9 100644 Binary files a/import/browse-indexing.jar and b/import/browse-indexing.jar differ diff --git a/import/index_java/src/org/vufind/index/DatabaseManager.java b/import/index_java/src/org/vufind/index/DatabaseManager.java index a1f9ac26a43..4173c7d0293 100644 --- a/import/index_java/src/org/vufind/index/DatabaseManager.java +++ b/import/index_java/src/org/vufind/index/DatabaseManager.java @@ -21,6 +21,10 @@ import org.apache.log4j.Logger; import org.solrmarc.tools.SolrMarcIndexerException; import java.sql.*; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.Path; /** * Database manager. @@ -70,49 +74,122 @@ private void connectToDatabase() if (vufindDatabase != null) { return; } + try { + connectToDatabaseUsingSplitConfig(); + } catch (Throwable e) { + logger.warn("Unable to connect to database using split config (" + e.getMessage() + ")"); + } + // If the split config allowed to setup the DB, do nothing further + if (vufindDatabase != null) { + return; + } + try { + connectToDatabaseUsingStringConfig(); + } catch (Throwable e) { + dieWithError("Unable to connect to VuFind database; " + e.getMessage()); + } + } + /** + * Connect to the VuFind database using the legacy PHP-style connection string in config.ini. + */ + private void connectToDatabaseUsingStringConfig() throws Throwable + { String dsn = ConfigManager.instance().getConfigSetting("config.ini", "Database", "database"); + if (dsn == null || dsn.isEmpty()) { + throw new Exception("Cannot find working database settings in config.ini"); + } - try { - // Parse key settings from the PHP-style DSN: - String username = ""; - String password = ""; - String classname = "invalid"; - String prefix = "invalid"; - String extraParams = ""; - if (dsn.substring(0, 8).equals("mysql://")) { - classname = "com.mysql.jdbc.Driver"; - prefix = "mysql"; - String useSsl = ConfigManager.instance().getBooleanConfigSetting("config.ini", "Database", "use_ssl", false) ? "true" : "false"; - extraParams = "?useSSL=" + useSsl; - if (useSsl != "false") { - String verifyCert = ConfigManager.instance().getBooleanConfigSetting("config.ini", "Database", "verify_server_certificate", false) ? "true" : "false"; - extraParams += "&verifyServerCertificate=" + verifyCert; - } - } else if (dsn.substring(0, 8).equals("pgsql://")) { - classname = "org.postgresql.Driver"; - prefix = "postgresql"; - } + // Parse key settings from the PHP-style DSN: + String platform = "invalid"; + if (dsn.substring(0, 8).equals("mysql://")) { + platform = "mysql"; + } else if (dsn.substring(0, 8).equals("pgsql://")) { + platform = "postgresql"; + } - Class.forName(classname).getDeclaredConstructor().newInstance(); - String[] parts = dsn.split("://"); + String host = ""; + String port = ""; + String name = ""; + String username = ""; + String password = ""; + String[] parts = dsn.split("://"); + if (parts.length > 1) { + parts = parts[1].split("@"); if (parts.length > 1) { - parts = parts[1].split("@"); + String[] pathParts = parts[1].split("/"); + name = pathParts[pathParts.length - 1]; + String[] hostParts = pathParts[0].split(":"); + host = hostParts[0]; + if (hostParts.length > 1) { + port = hostParts[1]; + } + parts = parts[0].split(":"); + username = parts[0]; if (parts.length > 1) { - dsn = prefix + "://" + parts[1]; - parts = parts[0].split(":"); - username = parts[0]; - if (parts.length > 1) { - password = parts[1]; - } + password = parts[1]; } } + } + connectToDatabaseUsingParams(platform, host, port, name, username, password); + } - // Connect to the database: - vufindDatabase = DriverManager.getConnection("jdbc:" + dsn + extraParams, username, password); - } catch (Throwable e) { - dieWithError("Unable to connect to VuFind database; " + e.getMessage()); + /** + * Connect to the VuFind database using the preferred granular config.ini settings. + */ + private void connectToDatabaseUsingSplitConfig() throws Throwable + { + String username = ConfigManager.instance().getConfigSetting("config.ini", "Database", "database_username"); + String password = ""; + String passwordFile = ConfigManager.instance().getConfigSetting("config.ini", "Database", "database_password_file"); + if (passwordFile != null && !passwordFile.isEmpty()) { + Path passwordFilePath = Paths.get(passwordFile); + password = Files.readString(passwordFilePath, Charset.defaultCharset()).trim(); + } + if (password.isEmpty()) { + password = ConfigManager.instance().getConfigSetting("config.ini", "Database", "database_password"); } + String host = ConfigManager.instance().getConfigSetting("config.ini", "Database", "database_host"); + String port = ConfigManager.instance().getConfigSetting("config.ini", "Database", "database_port"); + String name = ConfigManager.instance().getConfigSetting("config.ini", "Database", "database_name"); + String platform = ConfigManager.instance().getConfigSetting("config.ini", "Database", "database_driver"); + // If no platform is set, don't bother trying to connect: + if (platform != null && !platform.isEmpty()) { + connectToDatabaseUsingParams(platform, host, port, name, username, password); + } + } + + /** + * Connect to the VuFind database using provided values + */ + private void connectToDatabaseUsingParams(String platform, String host, String port, String name, String username, String password) throws Throwable + { + String classname = "invalid"; + String extraParams = ""; + String prefix = "invalid"; + if (platform.equals("mysql")) { + classname = "com.mysql.jdbc.Driver"; + prefix = "mysql"; + String useSsl = ConfigManager.instance().getBooleanConfigSetting("config.ini", "Database", "use_ssl", false) ? "true" : "false"; + extraParams = "?useSSL=" + useSsl; + if (useSsl != "false") { + String verifyCert = ConfigManager.instance().getBooleanConfigSetting("config.ini", "Database", "verify_server_certificate", false) ? "true" : "false"; + extraParams += "&verifyServerCertificate=" + verifyCert; + } + } else if (platform.equals("pgsql") || platform.equals("postgresql")) { + classname = "org.postgresql.Driver"; + prefix = "postgresql"; + } + + Class.forName(classname).getDeclaredConstructor().newInstance(); + String dsn = prefix + "://" + host; + if (!port.isEmpty()) { + dsn = dsn + ":" + port; + } + dsn = dsn + "/" + name; + + // Connect to the database: + vufindDatabase = DriverManager.getConnection("jdbc:" + dsn + extraParams, username, password); Runtime.getRuntime().addShutdownHook(new DatabaseManagerShutdownThread(this)); } diff --git a/import/index_java/src/org/vufind/index/FieldSpecTools.java b/import/index_java/src/org/vufind/index/FieldSpecTools.java index 1e780e9acb4..98b996663a0 100644 --- a/import/index_java/src/org/vufind/index/FieldSpecTools.java +++ b/import/index_java/src/org/vufind/index/FieldSpecTools.java @@ -32,6 +32,8 @@ import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; +import java.util.regex.Pattern; +import java.util.regex.Matcher; import java.util.Set; /** @@ -137,4 +139,35 @@ protected static final String getFieldData(DataField dataField, String subfieldC return result.length() > 0 ? result.toString() : null; } + + + private static Pattern unicodeEscape = Pattern.compile("(?i)\\\\u([0-9a-f]{4})"); + + /** + * Retrieves all subfields from a record, separated by a specified UTF-8 delimiter. + * + * This method takes a MARC record and a specification for the fields to extract, and returns + * a set of strings representing the subfields found, concatenated with a specified separator. + * The separator can include Unicode escape sequences, which will be converted to their corresponding + * characters. + * + * @param record the MARC record from which to extract subfields. + * @param fieldSpec a string specifying the fields and subfields to retrieve. + * @param separatorWithEscapes a string representing the delimiter to use between subfields, + * which can contain Unicode escape sequences in the format \\uXXXX. + * @return a set of strings with the concatenated subfields, separated by the given delimiter. + */ + public static Set<String> getAllSubfieldsUTF8Delimited(final Record record, String fieldSpec, String separatorWithEscapes) + { + StringBuilder separator = new StringBuilder(separatorWithEscapes); + Matcher m = unicodeEscape.matcher(separator); + + while (m.find()) { + int codepoint = Integer.parseInt(m.group(1), 16); + separator.replace(m.start(), m.end(), new String(Character.toChars(codepoint))); + m.reset(); + } + + return org.solrmarc.index.SolrIndexer.instance().getAllSubfields(record, fieldSpec, separator.toString()); + } } diff --git a/import/index_java/src/org/vufind/index/FormatCalculator.java b/import/index_java/src/org/vufind/index/FormatCalculator.java index 85203a00c6e..13aaee355ed 100644 --- a/import/index_java/src/org/vufind/index/FormatCalculator.java +++ b/import/index_java/src/org/vufind/index/FormatCalculator.java @@ -195,7 +195,13 @@ protected String getFormatFrom007(char formatCode, String formatString) { case 'd': char formatCode5 = formatString.length() > 4 ? formatString.charAt(4) : ' '; - return formatCode5 == 's' ? "BRDisc" : "VideoDisc"; + switch(formatCode5) { + case 's': + return "BRDisc"; + case 'g': + return "LaserDisc"; + } + return "VideoDisc"; case 'f': return "VideoCassette"; case 'r': diff --git a/import/marc.properties b/import/marc.properties index 41cbab91fe7..7600e95734f 100644 --- a/import/marc.properties +++ b/import/marc.properties @@ -92,6 +92,7 @@ callnumber-sort = custom, getLCSortable(099ab:090ab:050ab) callnumber-raw = 099ab:090ab:050ab topic = custom, getAllSubfields(600:610:611:630:650:653:656, " ") +topic_browse = custom, getAllSubfieldsUTF8Delimited(600:610:611:630:650:653:656, "\u2002") genre = custom, getAllSubfields(655, " ") geographic = custom, getAllSubfields(651, " ") era = custom, getAllSubfields(648, " ") diff --git a/import/translation_maps/callnumber_subject_map.properties b/import/translation_maps/callnumber_subject_map.properties index 7a172bd9296..1e5f3e29cc7 100644 --- a/import/translation_maps/callnumber_subject_map.properties +++ b/import/translation_maps/callnumber_subject_map.properties @@ -1,3 +1,4 @@ +A = A - General Works AC = AC - Collections and Collected Works AE = AE - Encyclopedias AG = AG - Dictionaries and Reference @@ -23,7 +24,7 @@ BS = BS - The Bible BT = BT - Doctrinal Theology BV = BV - Practical Theology BX = BX - Christian Denominations -C = C - General History +C = C - Historical Sciences CB = CB - History of Civilization CC = CC - Archaeology CD = CD - Diplomatics, Archives, Seals @@ -53,7 +54,7 @@ DR = DR - Balkan Peninsula DS = DS - Asia DT = DT - Africa DU = DU - Oceania (South Seas) -DX = DX - Gypsies +DX = DX - Romanies E = E - United States History F = F - General American History G = G - General Geography diff --git a/import/translation_maps/format_map.properties b/import/translation_maps/format_map.properties index aba6fd99b34..e0f02003d34 100644 --- a/import/translation_maps/format_map.properties +++ b/import/translation_maps/format_map.properties @@ -28,6 +28,7 @@ Image = Image InteractiveMultimedia = Software Journal = Journal Kit = Kit +LaserDisc = Laser Disc Manuscript = Manuscript Map = Map Microfilm = Microfilm diff --git a/index-alphabetic-browse.bat b/index-alphabetic-browse.bat index b150375e829..e50ae1e336a 100644 --- a/index-alphabetic-browse.bat +++ b/index-alphabetic-browse.bat @@ -86,7 +86,7 @@ mkdir "%index_dir%" rem These parameters should match the ones in solr/vufind/biblio/conf/solrconfig.xml - BrowseRequestHandler call %VUFIND_HOME%\index-alphabetic-browse.bat build_browse hierarchy hierarchy_browse call %VUFIND_HOME%\index-alphabetic-browse.bat build_browse title title_fullStr 1 "-Dbib_field_iterator=org.vufind.solr.indexing.StoredFieldIterator -Dsortfield=title_sort -Dvaluefield=title_fullStr -Dbrowse.normalizer=org.vufind.util.TitleNormalizer" -call %VUFIND_HOME%\index-alphabetic-browse.bat build_browse topic topic_browse +call %VUFIND_HOME%\index-alphabetic-browse.bat build_browse topic topic_browse 0 "-Dbrowse.normalizer=org.vufind.util.TopicNormalizer" call %VUFIND_HOME%\index-alphabetic-browse.bat build_browse author author_browse call %VUFIND_HOME%\index-alphabetic-browse.bat build_browse lcc callnumber-raw 1 "-Dbrowse.normalizer=org.vufind.util.LCCallNormalizer" call %VUFIND_HOME%\index-alphabetic-browse.bat build_browse dewey dewey-raw 1 "-Dbrowse.normalizer=org.vufind.util.DeweyCallNormalizer" diff --git a/index-alphabetic-browse.sh b/index-alphabetic-browse.sh index 5cdad59e2c8..e61c62ba496 100755 --- a/index-alphabetic-browse.sh +++ b/index-alphabetic-browse.sh @@ -131,7 +131,7 @@ function build_browse # These parameters should match the ones in solr/vufind/biblio/conf/solrconfig.xml - BrowseRequestHandler build_browse "hierarchy" "hierarchy_browse" build_browse "title" "title_fullStr" 1 "-Dbib_field_iterator=org.vufind.solr.indexing.StoredFieldIterator -Dsortfield=title_sort -Dvaluefield=title_fullStr -Dbrowse.normalizer=org.vufind.util.TitleNormalizer" -build_browse "topic" "topic_browse" +build_browse "topic" "topic_browse" 0 "-Dbrowse.normalizer=org.vufind.util.TopicNormalizer" build_browse "author" "author_browse" build_browse "lcc" "callnumber-raw" 1 "-Dbrowse.normalizer=org.vufind.util.LCCallNormalizer" build_browse "dewey" "dewey-raw" 1 "-Dbrowse.normalizer=org.vufind.util.DeweyCallNormalizer" diff --git a/languages/CookieConsent/eu.ini b/languages/CookieConsent/eu.ini new file mode 100644 index 00000000000..bc73869f229 --- /dev/null +++ b/languages/CookieConsent/eu.ini @@ -0,0 +1,33 @@ +Accept All Cookies = "Onartu cookie guztiak" +Accept Only Essential Cookies = "Onartu bakarrik funtsezko cookie-ak" +analytics_cookies_description_html = "Cookie hauekin bildutako analitikak gunearen garapenerako erabiltzen da." +analytics_cookies_title_html = "Cookies-i buruzko analitikak" +category_description_html = 'Webgunean erabiltzen diren cookieak zure helburuaren arabera kategorizatuta daude. Ikus aurrerago kategoria bakoitza deskribatzeko, baita onartzeko edo ukatzeko aukera ere <a href="%%siteRootAttr%%/Content/privacy" target="_blank">Privacy information for the site</a>.' +cookie_description_cart_html = "Liburu-poltsaren edukia gordetzeko erabiltzen da." +cookie_description_consent_html = "Bisitariek baimena eman duten edo webgunean erabilitako cookie kategorien erabilera baztertu duten informazioa gordetzeko erabilia." +cookie_description_language_html = "Uneko erabiltzailearen interfaze-hizkuntza gordetzeko erabiltzen da." +cookie_description_login_token_html = "Uneko erabiltzailearen saio-hasiera gogoratzeko token bat gordetzeko erabilia." +cookie_description_matomo_id_html = "Webguneen estatistiketarako erabiltzen da. Matomo-k jarrita, erabiltzailearen ID bakarra gordetzeko." +cookie_description_matomo_ref_html = "Webguneen estatistiketarako erabiltzen da. Matomok jarri du, arbitroaren IDa gordetzeko." +cookie_description_matomo_ses_html = "Webguneen estatistiketarako erabiltzen da. Matomok jarrita saioaren ID bakarra gordetzeko." +cookie_description_search_open_html = "Bilaketa-emaitzetan erregistro irekiak gordetzeko erabiltzen da" +cookie_description_secure_key_html = "Saioko informazioa zifratzeko erabiltzen da." +cookie_description_session_html = "Erabiltzailearen saio-egoera mantentzeko erabiltzen da." +cookie_description_sidefacet_html = "Hedatze/kolapso fazetario egoera mantentzeko erabilia." +cookie_description_ui_html = "Hautatutako gaia gordetzeko erabilia." +cookie_settings_html = "Cookie Baldintzak" +Description = "Deskribapena" +Domain = "Eremua" +essential_cookies_description_html = "Ezin da funtsezko cookieak erabiltzeko baimena kendu. Beharrezkoak dira guneak lan egin dezan eta gunea erabiltzeko erraztasuna hobetzeko." +essential_cookies_title_html = "Funtsezko Cookies" +Expiration = "Amaiera" +expiration_never = "Inouz ez" +expiration_session = "Saioaren amaiera" +expiration_unit_days = "%%expiration%% egun" +expiration_unit_months = "%%expiration%% hilabete" +Name = "Izena" +popup_description_html = 'Webgune honek funtsezko cookie-ak erabiltzen ditu bere funtzionamendu egokia ziurtatzeko, eta cookie-ak arakatzen ditu harekin nola elkarreragiten duen ulertzeko. <a href="#" data-cc="show-preferencesModal">Cookie baldintzak</a>.<br>{{revisionMessage}}' +popup_revision_message_html = "Zure baimena eskatzen da cookien kategoriak eguneratu direlako." +popup_title_html = "Web orriealdean erabilitako cookies-i buruzko informazioa" +Save Settings = "Gorde baldintzak" +third_party_html = "Hirugarren zatia" diff --git a/languages/DDC23/cs.ini b/languages/DDC23/cs.ini index d1ccef146ee..0fdb64d4892 100644 --- a/languages/DDC23/cs.ini +++ b/languages/DDC23/cs.ini @@ -1,6 +1,34 @@ +'000' = "Informatika, informace, obecné práce" +'001' = "Znalosti" +'002' = "Kniha" +'003' = "Systémy" +'004' = "Počítačová věda" +'005' = "Počítačové programování, programy, data, bezpečnost" +'006' = "Speciální počítačové metody" +'010' = "Bibliografie" +'011' = "Bibliografie a katalogy" +'023' = "Řízení lidských zdrojů" +'108' = "Skupiny lidí" +'109' = "Historie a souborná biografie" +'110' = "Metafyzika" +'111' = "Ontologie" +'114' = "Vesmír" +'115' = "Čas" '122' = "Příčinná souvislost" +'181' = "Východní filozofie" +'231' = "Bůh" +'232' = "Ježíš Kristus a jeho rodina" +'233' = "Lidstvo" +'408' = "Skupiny lidí" +'413' = "Slovníky" +'415' = "Gramatika" +'490' = "Ostatní jazyky" +'496' = "Africké jazyky" +'502' = "Různé" +'506' = "Organizace a řízení" '747' = "Výzdoba interiéru" '752' = "Barevný" '774' = "Holografie" '779' = "Fotografické snímky" '780' = "Hudba" +'940' = "Dějiny Evropy" diff --git a/languages/Exception/eu.ini b/languages/Exception/eu.ini new file mode 100644 index 00000000000..08e552e1e7c --- /dev/null +++ b/languages/Exception/eu.ini @@ -0,0 +1,5 @@ +access_denied_change_password = "Ezin da pasahitza aldatu." +access_denied_read_fines = "Ezin dira zure isunak ikusi." +access_denied_read_items = "Ezin dira ikusi eskatutako edo egiaztatutako elementuak." +access_denied_read_patron = "Ezin da zure erabiltzaile-profila ikusi." +access_denied_write_items = "Ezin da artikulurik eskatu edo berritu." diff --git a/languages/HoldingStatus/ar.ini b/languages/HoldingStatus/ar.ini index fcc9f1dd2e7..06e6bef0ffd 100644 --- a/languages/HoldingStatus/ar.ini +++ b/languages/HoldingStatus/ar.ini @@ -1,7 +1,10 @@ availability_uncertain = "غير مؤكد" copies_ordered_on_date = "تم طلب %%copies%% نسخة في %%date%%" copy_ordered_on_date = "تم طلب نسخة واحدة في %%date%%" +holding_no_items_availability_message = "مشاهدة التسجيلة الكاملة" service_available_presentation = "للاستخدام داخل المكتبة فقط" service_loan = "استعارة" service_presentation = "استخدام داخل المكتبة" services_available_html = "متاح لـ %%قائمة%%" +transit_to = "في النقل إلى %%location%%" +transit_to_date = "في النقل إلى %%location%%, تم الإرسال في %%date%%" diff --git a/languages/HoldingStatus/bn.ini b/languages/HoldingStatus/bn.ini index e21479d8edc..862b14da990 100644 --- a/languages/HoldingStatus/bn.ini +++ b/languages/HoldingStatus/bn.ini @@ -1,7 +1,10 @@ availability_uncertain = "অনিশ্চিত" copies_ordered_on_date = "%%copies%% কপি %%date%% তারিখে অর্ডার করা হয়েছে" copy_ordered_on_date = "1 কপি অর্ডার করা হয়েছে এই তারিখে %%date%%" +holding_no_items_availability_message = "সম্পূর্ণ রেকর্ড দেখুন" service_available_presentation = "গ্রন্থাগারে ব্যবহারের জন্য সংরক্ষিত" service_loan = "গ্রন্থাগারে সার্কুলেশন" service_presentation = "গ্রন্থাগারে ব্যবহার করুন" services_available_html = "গ্রন্থাগারে লভ্য পরিষেবা: %%list%%" +transit_to = "ট্রানজিটে রয়েছে %%location%%" +transit_to_date = "ট্রানজিটে রয়েছে %%location%%, %%date%% পাঠানো হয়েছে" diff --git a/languages/HoldingStatus/ca.ini b/languages/HoldingStatus/ca.ini index db70469ee6d..faed77d7d6e 100644 --- a/languages/HoldingStatus/ca.ini +++ b/languages/HoldingStatus/ca.ini @@ -1,7 +1,10 @@ availability_uncertain = "Incert" copies_ordered_on_date = "%%copies%% còpies demanades el %%date%%" copy_ordered_on_date = "1 còpia demanada el %%date%%" +holding_no_items_availability_message = "Consulta el registre complet" service_available_presentation = "Us en sala" service_loan = "Préstec" service_presentation = "Us en sala" services_available_html = "Disponible per %%list%%" +transit_to = "En trànsit cap a %%location%%" +transit_to_date = "En trànsit cap a %%location%%, enviat el %%date%%" diff --git a/languages/HoldingStatus/cs.ini b/languages/HoldingStatus/cs.ini index a2d84431d82..11d3ffb753b 100644 --- a/languages/HoldingStatus/cs.ini +++ b/languages/HoldingStatus/cs.ini @@ -1,7 +1,10 @@ availability_uncertain = "Neznámý" copies_ordered_on_date = "%%copies%% jednotek objednáno %%date%%" copy_ordered_on_date = "1 jednotka objednaná %%date%%" +holding_no_items_availability_message = "Zobrazit celý záznam" service_available_presentation = "Pouze ke studiu v knihovně" service_loan = "Vypůjčení" service_presentation = "Studiu v knihovně" services_available_html = "Dostupné k %%list%%" +transit_to = "Na cestě do %%location%%" +transit_to_date = "Na cestě do %%location%%, od %%date%%" diff --git a/languages/HoldingStatus/de.ini b/languages/HoldingStatus/de.ini index 238d6f08480..3cc9a7cf33d 100644 --- a/languages/HoldingStatus/de.ini +++ b/languages/HoldingStatus/de.ini @@ -1,6 +1,7 @@ availability_uncertain = "Unbestimmt" copies_ordered_on_date = "%%copies%% Kopien bestellt am %%date%%" copy_ordered_on_date = "1 Kopie bestellt am %%date%%" +holding_no_items_availability_message = "Siehe vollständiger Datensatz" service_available_presentation = "Nur Präsenznutzung" service_loan = "Ausleihexemplar" service_presentation = "Präsenzexemplar" diff --git a/languages/HoldingStatus/el.ini b/languages/HoldingStatus/el.ini index 1d462087830..a4aaf9431f6 100644 --- a/languages/HoldingStatus/el.ini +++ b/languages/HoldingStatus/el.ini @@ -1,7 +1,10 @@ availability_uncertain = "Αβέβαιη" copies_ordered_on_date = "%%copies%% αντίτυπα παραγγέλθηκαν στις %%date%%" copy_ordered_on_date = "1 αντίτυπο παραγγέλθηκε στις %%date%%" +holding_no_items_availability_message = "Εμφάνιση πλήρους εγγραφής" service_available_presentation = "Χρήση εντός της Βιβλιοθήκης αποκλειστικά" service_loan = "Δανεισμό" service_presentation = "Χρήση εντός της Βιβλιοθήκης" services_available_html = "Διαθέσιμο για %%list%%" +transit_to = "Σε μεταφορά προς %%location%%" +transit_to_date = "Σε μεταφορά προς %%location%%, απεστάλη στις %%date%%" diff --git a/languages/HoldingStatus/es.ini b/languages/HoldingStatus/es.ini index 2a2f4a736c6..f0ea385d9c8 100644 --- a/languages/HoldingStatus/es.ini +++ b/languages/HoldingStatus/es.ini @@ -1,7 +1,10 @@ availability_uncertain = "Incierto" copies_ordered_on_date = "%%copies%% copias pedidas el %%date%%" copy_ordered_on_date = "1 copia pedida en %%date%%" +holding_no_items_availability_message = "Ver registro completo" service_available_presentation = "Solo para uso en la Biblioteca" service_loan = "Préstamo" service_presentation = "En suso en la Biblioteca" services_available_html = "Disponible para %%list%%" +transit_to = "En tránsito hacia %%location%%" +transit_to_date = "En tránsito a %%location%%, enviado el %%date%%" diff --git a/languages/HoldingStatus/eu.ini b/languages/HoldingStatus/eu.ini index 419fc32efbd..ce4a6689471 100644 --- a/languages/HoldingStatus/eu.ini +++ b/languages/HoldingStatus/eu.ini @@ -1,4 +1,10 @@ +availability_uncertain = "Zalantzagarria" +copies_ordered_on_date = "%%copies%% kopia eskatu dira data honetan: %%date%%" +copy_ordered_on_date = "1 eskatu da data honetan %%date%%" +holding_no_items_availability_message = "Ikusi erregistro osoa" service_available_presentation = "Erabilgarri bakarrik liburutegian" service_loan = "Mailegua" service_presentation = "Liburutegian erabilita" services_available_html = "Erabilgarri %%list%%-etarako" +transit_to = "Iragaitzazkoa kokapena honetara: %%location%%" +transit_to_date = "Iragaitzazkoa kokapena honetara %%location%%, %%date%%-(e)an bidalita" diff --git a/languages/HoldingStatus/fi.ini b/languages/HoldingStatus/fi.ini index 0f41aed2a6c..3607493715e 100644 --- a/languages/HoldingStatus/fi.ini +++ b/languages/HoldingStatus/fi.ini @@ -1,6 +1,7 @@ availability_uncertain = "Epävarma" copies_ordered_on_date = "%%copies%% kappaletta tilattu %%date%%" copy_ordered_on_date = "%%copies%% kappale tilattu %%date%%" +holding_no_items_availability_message = "Katso koko tietue" service_available_presentation = "Käyttö vain kirjastossa" service_loan = "Lainaus" service_presentation = "Käyttö kirjastossa" diff --git a/languages/HoldingStatus/ga.ini b/languages/HoldingStatus/ga.ini index 192432b9f95..236bbfd06e2 100644 --- a/languages/HoldingStatus/ga.ini +++ b/languages/HoldingStatus/ga.ini @@ -1,7 +1,10 @@ availability_uncertain = "Éiginnte" copies_ordered_on_date = "%%copies%% cóip ordaithe ar an %%date%%" copy_ordered_on_date = "1 chóip ordaithe ar an %%date%%" +holding_no_items_availability_message = "Féach an taifead iomlán" service_available_presentation = "Gan bheith in úsáid ach ag an leabharlann amháin" service_loan = "Iasacht" service_presentation = "In úsáid ag an leabharlann" services_available_html = "Ar fáil do %%list%%" +transit_to = "Ar an tslí chuig %%location%%" +transit_to_date = "Ar an tslí chuig %%location%%, seolta ar %%date%%" diff --git a/languages/HoldingStatus/hi.ini b/languages/HoldingStatus/hi.ini index 6f65e9c44e3..b72c0640cd0 100644 --- a/languages/HoldingStatus/hi.ini +++ b/languages/HoldingStatus/hi.ini @@ -1,7 +1,10 @@ availability_uncertain = "अनिश्चित" copies_ordered_on_date = "%%copies%% कॉपी %%date%% को आर्डर की गई" copy_ordered_on_date = "1 कॉपी %%date%% को आदेशित की गई" +holding_no_items_availability_message = "पूरा रिकॉर्ड देखें" service_available_presentation = "केवल पुस्तकालय में उपयोग करने हेतु" service_loan = "उधार हेतु" service_presentation = "पुस्तकालय में उपयोग करने हेतु" services_available_html = "उपलब्ध है : %%list%%" +transit_to = "%%location%% पर ट्रांज़िट में" +transit_to_date = "%%location%% पर पारगमन में, %%date%% को भेजा गया" diff --git a/languages/HoldingStatus/hr.ini b/languages/HoldingStatus/hr.ini index 1424e27195d..9bfa8fd1024 100644 --- a/languages/HoldingStatus/hr.ini +++ b/languages/HoldingStatus/hr.ini @@ -2,7 +2,10 @@ availability_uncertain = "Neizvjesno" copies_ordered_on_date = "%%copies%% kopije naručene %%date%%" copy_ordered_on_date = "1 primjerak naručen %%date%%" +holding_no_items_availability_message = "Pogledaj cijeli zapis" service_available_presentation = "Korištenje samo u knjižnici" service_loan = "Posudba" service_presentation = "Korištenje u knjižnici" services_available_html = "Dostupno za %%list%%" +transit_to = "Na putu prema %%location%%" +transit_to_date = "Na putu prema %%location%%, poslano %%date%%" diff --git a/languages/HoldingStatus/hy.ini b/languages/HoldingStatus/hy.ini index cf81d72d225..27d879880a2 100644 --- a/languages/HoldingStatus/hy.ini +++ b/languages/HoldingStatus/hy.ini @@ -1,7 +1,10 @@ availability_uncertain = "Անորոշ" copies_ordered_on_date = "%%copies%% պատվիրված պատճենները %%date%%" copy_ordered_on_date = "Պատվիրված է 1 օրինակ %%date%%" +holding_no_items_availability_message = "Տե՛ս ամբողջական գրառումը" service_available_presentation = "Միայն գրադարանում օգտագործման համար" service_loan = "փոխառություն" service_presentation = "Գրադարանում օգտագործման համար" services_available_html = "Հասանելի է %%list%%" +transit_to = "Տեղափոխման մեջ է դեպի %%location%%" +transit_to_date = "Տեղափոխման մեջ է դեպի %%location%%, ուղարկված է %%date%%" diff --git a/languages/HoldingStatus/it.ini b/languages/HoldingStatus/it.ini index e09fed51955..e8df431e888 100644 --- a/languages/HoldingStatus/it.ini +++ b/languages/HoldingStatus/it.ini @@ -1,7 +1,10 @@ availability_uncertain = "Incerta" copies_ordered_on_date = "%%copies%% copie ordinate su %%date%%" copy_ordered_on_date = "1 copia ordinata su %%date%%" +holding_no_items_availability_message = "Vedi record completo" service_available_presentation = "Solo consultazione in biblioteca" service_loan = "Prestito" service_presentation = "Consultazione in biblioteca" services_available_html = "Disponibile per %%list%%" +transit_to = "In transito verso %%location%%" +transit_to_date = "In transito verso %%location%%, inviato in data %%date%%" diff --git a/languages/HoldingStatus/ja.ini b/languages/HoldingStatus/ja.ini index a681c5fdec6..a48ec01f629 100644 --- a/languages/HoldingStatus/ja.ini +++ b/languages/HoldingStatus/ja.ini @@ -1,7 +1,10 @@ availability_uncertain = "不明" copies_ordered_on_date = "%%date%% に指定した %%copies%% 件" copy_ordered_on_date = "%%date%% に指定した 1 件" +holding_no_items_availability_message = "詳細情報を参照してください" service_available_presentation = "図書館専用のみ" service_loan = "貸出" service_presentation = "図書館専用" services_available_html = "使用可能 %%list%%" +transit_to = "%%location%% に配送中" +transit_to_date = "%%location%% に配送中(%%date%% に配送)" diff --git a/languages/HoldingStatus/mn.ini b/languages/HoldingStatus/mn.ini index f667a3c35f4..8332f8d4433 100644 --- a/languages/HoldingStatus/mn.ini +++ b/languages/HoldingStatus/mn.ini @@ -1,7 +1,10 @@ availability_uncertain = "Тодорхойгүй" copies_ordered_on_date = "%%date%% %%copies%% захиалсан" copy_ordered_on_date = "%%date%% 1 хувийг захиалсан" +holding_no_items_availability_message = "Бүрэн бичлэгийг үзэх" service_available_presentation = "Зөвхөн номын санд ашиглах" service_loan = "Түрээслэх" service_presentation = "Зөвхөн номын сангийн хэрэгцээнд ашиглах" services_available_html = "%%list%% ашиглах боломжтой" +transit_to = "%%location%% руу шилжүүлэх байна" +transit_to_date = "%%location%% руу шилжүүлэж байна, %%date%% илгээсэн" diff --git a/languages/HoldingStatus/pt-br.ini b/languages/HoldingStatus/pt-br.ini index 2aa0b7379d1..2b1e250d849 100644 --- a/languages/HoldingStatus/pt-br.ini +++ b/languages/HoldingStatus/pt-br.ini @@ -1,7 +1,10 @@ availability_uncertain = "Incerto" copies_ordered_on_date = "%%copies%% cópias pedidas em %%date%%" copy_ordered_on_date = "1 cópia solicitada em %%date%%" +holding_no_items_availability_message = "Ver registro completo" service_available_presentation = "Uso somente na biblioteca" service_loan = "Empréstimo" service_presentation = "No uso da biblioteca" services_available_html = "Disponível para %%list%%" +transit_to = "Em trânsito para %%location%%" +transit_to_date = "Em trânsito para %%location%%, enviado em %%date%%" diff --git a/languages/HoldingStatus/ru.ini b/languages/HoldingStatus/ru.ini index 3afe9617022..0755b285d11 100644 --- a/languages/HoldingStatus/ru.ini +++ b/languages/HoldingStatus/ru.ini @@ -1,7 +1,10 @@ availability_uncertain = "Неопределенно" copies_ordered_on_date = "%%copies%% экземпляров заказано %%date%%" copy_ordered_on_date = "1 экземпляр заказан %%date%%" +holding_no_items_availability_message = "Посмотреть полную запись" service_available_presentation = "В библиотеке использовать только" service_loan = "Кредит" service_presentation = "В библиотеке использовать" services_available_html = "Доступно для %%list%%" +transit_to = "Находится в пути %%location%%" +transit_to_date = "Находится в пути %%location%%, отправленный %%date%%" diff --git a/languages/HoldingStatus/sv.ini b/languages/HoldingStatus/sv.ini index 10cb640960c..474e78a8b5e 100644 --- a/languages/HoldingStatus/sv.ini +++ b/languages/HoldingStatus/sv.ini @@ -1,6 +1,7 @@ availability_uncertain = "Osäker" copies_ordered_on_date = "%%copies%% exemplar beställt %%date%%" copy_ordered_on_date = "1 exemplar beställt %%date%%" +holding_no_items_availability_message = "Se fullständig post" service_available_presentation = "Endast i biblioteksbruk" service_loan = "Lån" service_presentation = "I biblioteksbruk" diff --git a/languages/HoldingStatus/tr.ini b/languages/HoldingStatus/tr.ini index c3ded031d10..7fb171e53c6 100644 --- a/languages/HoldingStatus/tr.ini +++ b/languages/HoldingStatus/tr.ini @@ -1,7 +1,10 @@ availability_uncertain = "Belirsiz" copies_ordered_on_date = "%%kopyalar%% şu tarihte sipariş edilen kopyalar %%date%%" copy_ordered_on_date = "yandaki tarihte 1 kopya sipariş edildi %%date%%" +holding_no_items_availability_message = "Tüm kaydı görüntüle" service_available_presentation = "Sadece Kütüphane İçi Kullanım" service_loan = "Ödünç" service_presentation = "Kütüphane içi kullanım" services_available_html = "%%listesi%% için kullanılabilir" +transit_to = "%%location%%'a aktarımda" +transit_to_date = "%%location%%'a aktarımda, %%date%% tarihinde gönderildi" diff --git a/languages/HoldingStatus/uk.ini b/languages/HoldingStatus/uk.ini index a7059dd61c6..7fa13cb3b12 100644 --- a/languages/HoldingStatus/uk.ini +++ b/languages/HoldingStatus/uk.ini @@ -1,7 +1,10 @@ availability_uncertain = "Невизначено" copies_ordered_on_date = "%%copies%% примірників замовлено на %%date%%" copy_ordered_on_date = "1 примірник замовлений на %%date%%" +holding_no_items_availability_message = "Переглянути повний запис" service_available_presentation = "Лише для використання в Бібліотеці" service_loan = "Видача" service_presentation = "Для використання в Бібліотеці" services_available_html = "Доступний для %%list%%" +transit_to = "У дорозі до %1:location%" +transit_to_date = "У дорозі до %location%, відправлено %date%" diff --git a/languages/ILSMessages/ar.ini b/languages/ILSMessages/ar.ini index d4bccb0b2de..e31798e04ed 100644 --- a/languages/ILSMessages/ar.ini +++ b/languages/ILSMessages/ar.ini @@ -2,6 +2,8 @@ available_for_pickup_notification = "لديك مواد متاحة للالتقا checkout_block = "لا يمكنك إعارة المزيد من المواد" electronic_resources_block = "لا يمكنك الوصول إلى الموارد الإلكترونية" hold_wrong_user_institution = "لا يمكنك حجز المواد من المكتبة المطلوبة." +ill_item_renewal_limit = "إعادة بحلول تاريخ الاستحقاق (تم بلوغ الحد الأقصى للتجديدات)." +ill_renewal_too_soon = "سيتم تجديد المادة تلقائياً (ما لم تكن مطلوبة بواسطة آخرين)" lost_card = "تم الإبلاغ عن ضياع بطاقتك" message_from_library = "لديك رسالة من مكتبتك" no_change_password_scope = "غير مسموح لك بتغيير كلمة المرور." @@ -16,6 +18,7 @@ no_update_patron_name_scope = "غير مسموح لك بتحديث الاسم ف no_update_patron_scope = "غير مسموح لك بتحديث بيانات المستفيد." no_write_items_scope = "غير مسموح لك بكتابة المواد." renewal_block = "لا يمكنك تجديد الإعارات" +renewal_recalled = "تم طلب الماة - تم تغيير تاريخ الاستحقاق - إعادة في تاريخ الاستحقاق" renewal_too_soon = "لا يمكن التجديد بعد" requests_blocked = "لا يمكنك وضع طلب على مادة المكتبة" special_circulation = "النسخة لها إعارة خاص" diff --git a/languages/ILSMessages/bn.ini b/languages/ILSMessages/bn.ini index 5180bbc444f..f708d1078c4 100644 --- a/languages/ILSMessages/bn.ini +++ b/languages/ILSMessages/bn.ini @@ -2,6 +2,8 @@ available_for_pickup_notification = "আপনার কাছে পিকআ checkout_block = "আপনি আরও প্রলেখ চেক আউট করতে পারবেন না" electronic_resources_block = "আপনি বৈদ্যুতিন প্রলেখ অ্যাক্সেস করতে পারবেন না" hold_wrong_user_institution = "আপনি অনুরোধ করা লাইব্রেরি থেকে সামগ্রীর জন্য হোল্ড রাখতে পারবেন না।" +ill_item_renewal_limit = "নির্ধারিত তারিখের মধ্যে ফিরে আসুন (সর্বোচ্চ রিনিউয়াল পৌঁছেছে) ।" +ill_renewal_too_soon = "আইটেমটি স্বয়ংক্রিয়ভাবে রিনিউ হবে (অন্যদের অনুরোধ ব্যতীত)" lost_card = "আপনার কার্ড হারিয়ে গেছে বলে রিপোর্ট করা হয়েছে" message_from_library = "আপনার লাইব্রেরি থেকে একটি বার্তা আছে" no_change_password_scope = "আপনার পাসওয়ার্ড পরিবর্তন করার অনুমতি নেই ।" @@ -16,6 +18,7 @@ no_update_patron_name_scope = "পৃষ্ঠপোষকের তথ্যে no_update_patron_scope = "আপনাকে পৃষ্ঠপোষকের ডেটা আপডেট করার অনুমতি নেই ।" no_write_items_scope = "আপনাকে আইটেম লেখার অনুমতি নেই ।" renewal_block = "আপনি চেকআউট নবীকরণ করতে পারবেন না" +renewal_recalled = "অনুরোধ করা আইটেম - নির্ধারিত তারিখ পরিবর্তিত - নির্ধারিত তারিখে ফেরত" renewal_too_soon = "এখনও রিনিউ করা যাচ্ছে না" requests_blocked = "আপনি লাইব্রেরির সামগ্রী পাওয়ার জন্য অনুরোধ করতে পারবেন না" special_circulation = "অনুলিপিটির বিশেষ প্রচলন রয়েছে" diff --git a/languages/ILSMessages/ca.ini b/languages/ILSMessages/ca.ini index 5a14c3a2a3d..a8b544543a2 100644 --- a/languages/ILSMessages/ca.ini +++ b/languages/ILSMessages/ca.ini @@ -2,6 +2,8 @@ available_for_pickup_notification = "Tens material disponible per recollir" checkout_block = "No pots treure més ítems" electronic_resources_block = "No pots accedir a recursos electrònics" hold_wrong_user_institution = "No pots reservar material de la biblioteca sol·licitada." +ill_item_renewal_limit = "Torna abans de la data de venciment (s'ha arribat al màxim de renovacions)." +ill_renewal_too_soon = "L'article es renovarà automàticament (tret que sigui sol·licitat per altres)." lost_card = "La teva targeta s'ha declarat perduda" message_from_library = "Tens un missatge de la teva biblioteca" no_change_password_scope = "No tens permís per canviar la contrasenya." @@ -16,6 +18,7 @@ no_update_patron_name_scope = "No tens permís per actualitzar el nom en la info no_update_patron_scope = "No tens permís per actualitzar les dades del patron." no_write_items_scope = "No tens permís per escriure ítems." renewal_block = "No pots renovar préstecs" +renewal_recalled = "Article sol·licitat - data de venciment modificada - torna abans de la data de venciment" renewal_too_soon = "No es pot renovar encara" requests_blocked = "No pots fer una petició de material de la biblioteca" special_circulation = "La còpia té circulació especial" diff --git a/languages/ILSMessages/cs.ini b/languages/ILSMessages/cs.ini index a470bfb638a..6fea81ac7f1 100644 --- a/languages/ILSMessages/cs.ini +++ b/languages/ILSMessages/cs.ini @@ -2,6 +2,8 @@ available_for_pickup_notification = "Vámi požadované dokumenty jsou připrave checkout_block = "Nemůžete si půjčovat dokumenty z knihovny" electronic_resources_block = "Nemůžete přistupovat k elektronickým zdrojům knihovny" hold_wrong_user_institution = "Rezervovat si můžete pouze v knihovnách, kde jste zaregistrovaní" +ill_item_renewal_limit = "Vraťte v původním termínu (již nelze prodloužit)." +ill_renewal_too_soon = "Jednotka se automaticky prodlouží (pokud o ni někdo nepožádá)" lost_card = "Váš průkaz je označen jako ztracený" message_from_library = "Máte zprávu z knihovny" no_change_password_scope = "Nemáte oprávnění pro změnu hesla." @@ -16,6 +18,7 @@ no_update_patron_name_scope = "Nemáte oprávnění pro změnu jména čtenáře no_update_patron_scope = "Nemáte oprávnění pro úpravu informací o čtenáři." no_write_items_scope = "Nemáte oprávnění pro vkládání položek." renewal_block = "Nemůžete si prodlužovat výpůjčky" +renewal_recalled = "Požadovaná položka - změna data vrácení - vráceno v poslední den" renewal_too_soon = "Zatím nelze prodloužit" requests_blocked = "Nemůžete vytvářet požadavky na dokumenty knihovny" special_circulation = "Kopie má zvláštní pravidla pro výpůjčky" diff --git a/languages/ILSMessages/de.ini b/languages/ILSMessages/de.ini index 0ef2e926328..3a1c466948e 100644 --- a/languages/ILSMessages/de.ini +++ b/languages/ILSMessages/de.ini @@ -2,6 +2,8 @@ available_for_pickup_notification = "Es stehen Medien für Sie zur Abholung bere checkout_block = "Sie können keine zusätzlichen Medien ausleihen." electronic_resources_block = "Sie haben keinen Zugriff auf elektronische Ressourcen" hold_wrong_user_institution = "Sie können keine Medien aus der gewünschten Bibliothek vormerken lassen." +ill_item_renewal_limit = "Bitte zum Fälligkeitsdatum abgeben (max. Anzahl an Verlängerungen erreicht)." +ill_renewal_too_soon = "Titel wird automatisch verlängert (sofern er nicht anderweitig vorbestellt wird)" lost_card = "Ihre Karte ist als verloren gemeldet" message_from_library = "Sie haben eine Mitteilung Ihrer Bibliothek" no_change_password_scope = "Sie sind nicht berechtigt Ihr Passwort zu ändern." @@ -16,6 +18,7 @@ no_update_patron_name_scope = "Sie sind nicht berechtigt Ihre Profilinformation no_update_patron_scope = "Sie sind nicht berechtigt Ihre Profilinformationen zu ändern." no_write_items_scope = "Sie sind nicht berechtigt Bestellungen aufzugeben." renewal_block = "Sie können keine Ausleihfristen verlängern" +renewal_recalled = "Titel wurde anderweitig vorbestellt: Fälligkeitsdatum aktualisiert - bitte zum Fälligkeitsdatum abgeben" renewal_too_soon = "Noch nicht verlängerbar" requests_blocked = "Sie können keine Bibliotheksmedien ausleihen" special_circulation = "Exemplar hat besondere Auflage" diff --git a/languages/ILSMessages/el.ini b/languages/ILSMessages/el.ini index 884bd9a49b1..3c03eb2a169 100644 --- a/languages/ILSMessages/el.ini +++ b/languages/ILSMessages/el.ini @@ -2,6 +2,8 @@ available_for_pickup_notification = "Έχετε διαθέσιμο υλικό π checkout_block = "Δεν μπορείτε να δανειστείτε περισσότερα τεκμήρια" electronic_resources_block = "Δεν έχετε πρόσβαση σε ηλεκτρονικές πηγές" hold_wrong_user_institution = "Δεν μπορείτε να ζητήσετε δέσμευση υλικού από αυτή τη βιβλιοθήκη." +ill_item_renewal_limit = "Επιστροφή μέχρι την ημερομηνία επιστροφής (ο μέγιστος αριθμός ανανεώσεων έχει συμπληρωθεί)." +ill_renewal_too_soon = "Το τεκμήριο θα ανανεωθεί αυτόματα (εκτός αν ζητηθεί από άλλους)" lost_card = "Η κάρτα σας έχει δηλωθεί ως χαμένη" message_from_library = "Έχετε κάποιο μήνυμα απο την βιβλιοθήκη σας" no_change_password_scope = "Δεν έχετε δικαίωμα να αλλάξετε κωδικό." @@ -16,6 +18,7 @@ no_update_patron_name_scope = "Δεν έχετε δικαίωμα να ενημ no_update_patron_scope = "Δεν έχετε δικαίωμα να ενημερώνετε δεδομένα χρηστών." no_write_items_scope = "Δεν έχετε δικαίωμα να γράφετε τεκμήρια." renewal_block = "Δεν μπορείτε να ανανεώσετε τα δανεισμένα τεκμήρια" +renewal_recalled = "Το τεκμήριο ζητήθηκε - η ημερομηνία επιστροφής άλλαξε - να επιστρέψει μέχρι την ημερομηνία επιστροφής" renewal_too_soon = "Δεν γίνεται ανανέωση ακόμα" requests_blocked = "Δεν μπορείτε να κάνετε αίτηση για υλικό της βιβλιοθήκης" special_circulation = "Το αντίτυπο έχει ειδικές ρυθμίσεις κυκλοφορίας" diff --git a/languages/ILSMessages/es.ini b/languages/ILSMessages/es.ini index f9c60bdc002..453ade9f89b 100644 --- a/languages/ILSMessages/es.ini +++ b/languages/ILSMessages/es.ini @@ -2,6 +2,8 @@ available_for_pickup_notification = "Tiene material disponible para recoger" checkout_block = "No puedes retirar más tems" electronic_resources_block = "No puede acceder a recursos electrónicos" hold_wrong_user_institution = "No puede reservar material de la biblioteca solicitada." +ill_item_renewal_limit = "Devolución en la fecha de vencimiento (máx. de renovaciones alcanzado)." +ill_renewal_too_soon = "El ítem se renovará automáticamente (a menos que otros lo soliciten)" lost_card = "Su tarjeta ha sido reportada como perdida" message_from_library = "Tiene un mensaje de su biblioteca" no_change_password_scope = "No se le permite cambiar la contraseña." @@ -16,6 +18,7 @@ no_update_patron_name_scope = "No se le permite actualizar el nombre en la infor no_update_patron_scope = "No se le permite actualizar los datos del usuario." no_write_items_scope = "No se le permite escribir ítems." renewal_block = "No se pueden renovar los préstamos" +renewal_recalled = "Item solicitado - fecha de entrega modificada - devolución en fecha de entrega" renewal_too_soon = "Aún no se puede renovar" requests_blocked = "No se puede realizar una solicitud sobre el material de la biblioteca" special_circulation = "La copia tiene circulación especial" diff --git a/languages/ILSMessages/eu.ini b/languages/ILSMessages/eu.ini new file mode 100644 index 00000000000..18f7ab37978 --- /dev/null +++ b/languages/ILSMessages/eu.ini @@ -0,0 +1,27 @@ +available_for_pickup_notification = "Jasotzeko materiala eskuragarri duzu" +checkout_block = "Ezin duzu mailegu gehiago egin" +electronic_resources_block = "Ezin duzu baliabide elektronikoetara sartu" +hold_wrong_user_institution = "Ezin da erreserbarik jarri eskatutako liburutegiko materialerako." +ill_item_renewal_limit = "Itzulketa mugaegunean (gehienez lortutako berritzeak)." +ill_renewal_too_soon = "Artikulua automatikoki berrituko da (beste batzuek eskatu ezean)" +lost_card = "Zure txartela galduta dago" +message_from_library = "Mezu berria duzu zure libutegiarengandik" +no_change_password_scope = "Ezin duzu pasahitza aldatu." +no_delete_notifications_scope = "Ezin dira ezabatu jakinarazpenak." +no_read_fees_scope = "Ezin dira tarifak irakurri." +no_read_items_scope = "Ezin dira artikuluak irakurri." +no_read_notifications_scope = "Ezin da jakinarazpenik irakurri." +no_read_patron_scope = "Ezin da erabiltzailea irakurri." +no_update_patron_address_scope = "Ezin da helbidea eguneratu erabiltzailearen informazioan." +no_update_patron_email_scope = "Ezin da helbide elektronikoa eguneratu erabiltzailearen informazioan." +no_update_patron_name_scope = "Ezin da izena eguneratu erabiltzailearen informazioan." +no_update_patron_scope = "Ezin da erabiltzailearen informazioa eguneratu." +no_write_items_scope = "Ezin duzu itemak idatzi." +renewal_block = "Ezin duzu maileguak berritu" +renewal_recalled = "Eskatutako artikulua - mugaeguna aldatuta - mugaeguna" +renewal_too_soon = "Oraindik ezin da berritu" +requests_blocked = "Ezin duzu eskaera bat liburutegiko materialean jarri" +special_circulation = "Kopiak zirkulazio berezia du" +storage_wrong_user_institution = "Ezin dituzu eskatutako liburutegiko materiala berreskuratzeko eskaerak jarri." +too_much_debt = "Too much unpaid debt" +will_auto_renew = "Item berritu da" diff --git a/languages/ILSMessages/fi.ini b/languages/ILSMessages/fi.ini index c707fa6575e..dce3c90d26d 100644 --- a/languages/ILSMessages/fi.ini +++ b/languages/ILSMessages/fi.ini @@ -2,6 +2,8 @@ available_for_pickup_notification = "Sinulla on aineistoa valmiina noudettavaksi checkout_block = "Et voi lainata enempää aineistoa" electronic_resources_block = "Sinulla ei ole pääsyä elektronisiin aineistoihin" hold_wrong_user_institution = "Et voi varata tämän kirjaston aineistoa." +ill_item_renewal_limit = "Palauta eräpäivään mennessä (ei uusimiskertoja jäljellä)." +ill_renewal_too_soon = "Laina uusitaan automaattisesti (jos ei ole varauksia)" lost_card = "Kirjastokorttisi on merkitty kadonneeksi" message_from_library = "Sinulle on viesti kirjastosta" no_change_password_scope = "Sinulla ei ole salasanan vaihto-oikeutta." @@ -16,6 +18,7 @@ no_update_patron_name_scope = "Sinulla ei ole asiakkaan nimen päivitysoikeutta. no_update_patron_scope = "Sinulla ei ole asiakastietojen päivitysoikeuksia." no_write_items_scope = "Sinulla ei ole tietojen muokkausoikeuksia." renewal_block = "Et voi uusia lainoja" +renewal_recalled = "Aineistoon on varauksia - eräpäivä muuttunut - palauta eräpäivään mennessä" renewal_too_soon = "Ei voida uusia vielä" requests_blocked = "Et voi tehdä varauksia" special_circulation = "Nide on nopeakierrossa" diff --git a/languages/ILSMessages/ga.ini b/languages/ILSMessages/ga.ini index c50c05b3323..726ac0f2d6d 100644 --- a/languages/ILSMessages/ga.ini +++ b/languages/ILSMessages/ga.ini @@ -2,6 +2,8 @@ available_for_pickup_notification = "Tá ábhar le bailiú agat" checkout_block = "Ní féidir leat tuilleadh míreanna a sheiceáil amach" electronic_resources_block = "Ní féidir leat rochtain a fháil ar acmhainní leictreonacha" hold_wrong_user_institution = "Ní féidir coinneáil a chuir ar ábhar ón leabharlann iarrtha." +ill_item_renewal_limit = "Tabhair ar ais faoin dáta dlite (uasmhéid athnuachaintí sroichte)." +ill_renewal_too_soon = "Athnuafar an mhír go huathoibríoch (mura n-iarrfar í ag daoine eile)" lost_card = "Tuairiscíodh go raibh do chárta ar iarraidh" message_from_library = "Tá teachtaireacht agat ó do leabharlann" no_change_password_scope = "Níl cead agat an pasfhocal a athrú." @@ -16,6 +18,7 @@ no_update_patron_name_scope = "Níl cead agat an t-ainm san eolas pátrúin a nu no_update_patron_scope = "Níl cead agat sonraí pátrúin a nuashonrú." no_write_items_scope = "Níl cead agat míreanna a scríobh." renewal_block = "Ní féidir leat seiceálacha amach a athnuachan" +renewal_recalled = "Tá an mhír á hiarraidh - dáta dlite athraithe - tabhair ar ais ar an dáta dlite" renewal_too_soon = "Ní féidir athnuachan a dhéanamh go fóill" requests_blocked = "Ní féidir leat iarratas a dhéanamh ar ábhar leabharlainne" special_circulation = "Tá scaipeadh speisialta ag an gcóip seo" diff --git a/languages/ILSMessages/hi.ini b/languages/ILSMessages/hi.ini index 705f0f13b58..5f5ca1c7e7e 100644 --- a/languages/ILSMessages/hi.ini +++ b/languages/ILSMessages/hi.ini @@ -2,6 +2,8 @@ available_for_pickup_notification = "आपके पास पिकअप क checkout_block = "आप अधिक डाक्यूमेंट उधार नहीं ले सकते" electronic_resources_block = "आप इलेक्ट्रॉनिक दस्तावेज़ तक नहीं पहुंच पाएंगे" hold_wrong_user_institution = "आप अनुरोधित पुस्तकालय से सामग्री के लिए होल्ड नहीं कर सकते।" +ill_item_renewal_limit = "नियत तारीख तक वापसी (अधिकतम नवीनीकरण तक पहुँच गया)।" +ill_renewal_too_soon = "आइटम अपने आप रिन्यू हो जाएगा (जब तक कि दूसरों द्वारा अनुरोध नहीं किया जाता)" lost_card = "आपके कार्ड के गुम होने की सूचना दी गई है" message_from_library = "आपके पास अपनी लाइब्रेरी से एक संदेश है" no_change_password_scope = "आपको पासवर्ड बदलने की अनुमति नहीं है।" @@ -16,6 +18,7 @@ no_update_patron_name_scope = "आपको सब्सक्राइबर no_update_patron_scope = "आपको सब्सक्राइबर का डेटा अपडेट करने की इजाज़त नहीं है." no_write_items_scope = "आपको आइटम लिखने की अनुमति नहीं है।" renewal_block = "आप चेकआउट का नवीनीकरण नहीं कर सकते" +renewal_recalled = "अनुरोध किया गया आइटम - नियत तारीख बदल गई - नियत तारीख को वापस करें" renewal_too_soon = "अभी रिन्यू नहीं किया जा सकता" requests_blocked = "आप पुस्तकालय सामग्री प्राप्त करने के लिए अनुरोध नहीं कर सकते" special_circulation = "कॉपी में स्पेशल सर्कुलेशन है" diff --git a/languages/ILSMessages/hr.ini b/languages/ILSMessages/hr.ini index 1d6ca78872d..9fb3150c7c1 100644 --- a/languages/ILSMessages/hr.ini +++ b/languages/ILSMessages/hr.ini @@ -2,6 +2,8 @@ available_for_pickup_notification = "Postoji građa koju možeš preuzeti" checkout_block = "Ne možeš posuditi više predmeta" electronic_resources_block = "Ne možeš pristupiti elektronskim resursima" hold_wrong_user_institution = "Ne možeš naručiti građu od odabrane knjižnice." +ill_item_renewal_limit = "Vrati do roka (maks. broj obnavljanja dosegnut)." +ill_renewal_too_soon = "Predmet će se automatski obnoviti (osim ako ga netko drugi ne zatraži)" lost_card = "Tvoja je kartica prijavljena kao izgubljena" message_from_library = "Imaš poruku od tvoje knjižnice" no_change_password_scope = "Nemaš prava za mijenjanje lozinke." @@ -16,6 +18,7 @@ no_update_patron_name_scope = "Nemaš prava za aktualiziranje imena pokrovitelja no_update_patron_scope = "Nemaš prava za aktualiziranje podataka pokrovitelja." no_write_items_scope = "Nemaš prava za pisanje predmeta." renewal_block = "Ne možeš produžiti posudbe" +renewal_recalled = "Predmet zatražen – datum roka promijenjen – vraćanje na rok" renewal_too_soon = "Još se ne može obnoviti" requests_blocked = "Ne možeš postaviti zahtjev za knjižničnu građu" special_circulation = "Primjerak ima posebna pravila posuđivanja/vraćanja" diff --git a/languages/ILSMessages/hy.ini b/languages/ILSMessages/hy.ini index 6c116ab447b..72ed81f8a24 100644 --- a/languages/ILSMessages/hy.ini +++ b/languages/ILSMessages/hy.ini @@ -2,6 +2,8 @@ available_for_pickup_notification = "Դուք ունեք նյութ վերցնե checkout_block = "Դուք չեք կարող ստանալ ավելի շատ նյութեր" electronic_resources_block = "Դուք չեք կարող մուտք գործել էլեկտրոնային պաշարներ" hold_wrong_user_institution = "Դուք չեք կարող պահումներ տեղադրել հայցվող գրադարանից նյութերի համար:" +ill_item_renewal_limit = "Վերադարձ մինչև վերջնաժամկետը (առավելագույնը երկարաձգվել է):" +ill_renewal_too_soon = "Նյութը ինքնաշխատ կթարմացվի (եթե ուրիշների կողմից պահանջված չէ)" lost_card = "Ձեր քարտը հաղորդվում է որպես կորած" message_from_library = "Դուք հաղորդագրություն ունեք ձեր գրադարանից" no_change_password_scope = "Դուք իրավունք չունեք փոխել գաղտնաբառը:" @@ -16,6 +18,7 @@ no_update_patron_name_scope = "Ձեզ չի թույլատրվում թարմաց no_update_patron_scope = "Ձեզ չի թույլատրվում թարմացնել հաճախորդի տվյալները:" no_write_items_scope = "Դուք իրավունք չունեք նյութեր գրել:" renewal_block = "Դուք չեք կարող թարմացնել դուրս տրումները" +renewal_recalled = "Պահանջվող նյութ - վերադարձի ժամկետը փոխվել է - վերադարձրեք ժամկետի ավարտին" renewal_too_soon = "Դեռևս հնարավոր չէ թարմացնել" requests_blocked = "Դուք չեք կարող հարցում կատարել գրադարանային նյութի մասին" special_circulation = "Պատճենը ունի հատուկ տացք" diff --git a/languages/ILSMessages/it.ini b/languages/ILSMessages/it.ini index a9941ebd5ad..e2fdba52b96 100644 --- a/languages/ILSMessages/it.ini +++ b/languages/ILSMessages/it.ini @@ -2,6 +2,8 @@ available_for_pickup_notification = "Non hai materiale disponibile per il ritiro checkout_block = "Non puoi verificare ulteriori titoli" electronic_resources_block = "Non puoi accedere a risorse elettroniche" hold_wrong_user_institution = "Non è possibile effettuare prenotazioni per il materiale della biblioteca richiesta." +ill_item_renewal_limit = "Restituisci entro la data di scadenza (numero massimo di rinnovi raggiunto)." +ill_renewal_too_soon = "L'articolo si rinnoverà automaticamente (a meno che non venga richiesto da altri)." lost_card = "La tua tessera è stata segnala come smarrita" message_from_library = "Hai un messaggio dalla tua biblioteca" no_change_password_scope = "Non ti è permesso cambiare la password." @@ -16,6 +18,7 @@ no_update_patron_name_scope = "Non ti è permesso aggiornare il nome nei dati ut no_update_patron_scope = "Non ti è permesso aggiornare i dati utente." no_write_items_scope = "Non ti è permesso modificare gli oggetti" renewal_block = "Non è possibile rinnovare le verifiche" +renewal_recalled = "Articolo richiesto - modifica della data di scadenza - restituzione alla data di scadenza" renewal_too_soon = "Non è ancora possibile rinnovare" requests_blocked = "Non è possibile effettuare una richiesta di materiale della biblioteca" special_circulation = "La copia ha una diffusione speciale" diff --git a/languages/ILSMessages/ja.ini b/languages/ILSMessages/ja.ini index 6f956d78e04..8d6dfdcdfb9 100644 --- a/languages/ILSMessages/ja.ini +++ b/languages/ILSMessages/ja.ini @@ -2,6 +2,8 @@ available_for_pickup_notification = "受取可能な予約資料があります" checkout_block = "これ以上、貸し出しはできません" electronic_resources_block = "電子資料にはアクセスできません" hold_wrong_user_institution = "指定した図書館では取り置きができません。" +ill_item_renewal_limit = "(最大更新回数に達しましたので)返却日までに返却してください" +ill_renewal_too_soon = "(別の人からの依頼がない限り)自動的に更新されます" lost_card = "紛失届のあった図書カードです" message_from_library = "所属図書館から連絡事項があります" no_change_password_scope = "パスワードを変更することはできません。" @@ -16,6 +18,7 @@ no_update_patron_name_scope = "利用者の名前を変更することはでき no_update_patron_scope = "利用者情報を変更することはできません。" no_write_items_scope = "アイテムを書き込むことはできません。" renewal_block = "貸出更新はできません" +renewal_recalled = "依頼資料 - 返却日が変更されました - 返却日までに返却してください" renewal_too_soon = "まだ更新できません" requests_blocked = "図書館資料の取り寄せはできません" special_circulation = "閲覧制限資料です" diff --git a/languages/ILSMessages/mn.ini b/languages/ILSMessages/mn.ini index d5a63b1d63a..a436adf79aa 100644 --- a/languages/ILSMessages/mn.ini +++ b/languages/ILSMessages/mn.ini @@ -2,6 +2,8 @@ available_for_pickup_notification = "Та хүлээн авах боломжто checkout_block = "Та илүү ихийг үйлчилгээгээр авах боломжгүй" electronic_resources_block = "Та цахим нөөцийг хандах боломжгүй" hold_wrong_user_institution = "Таны номын сангийн захиалсан материалыг захиалах боломжгүй." +ill_item_renewal_limit = "Буцаах хугацаа (дээд шинэчлэлт хүрсэн)." +ill_renewal_too_soon = "Хэрэв өөр хүн хүсэлт гаргаагүй бол зүйлийг автоматаар сунгагдана" lost_card = "Таны уншигчийн карт алдагдсан гэж мэдээлсэн" message_from_library = "Таньд номын сангаас зурвас ирсэн байна." no_change_password_scope = "Та нууц үг өөрчлөх боломжгүй." @@ -16,6 +18,7 @@ no_update_patron_name_scope = "Та уншигчийн мэдээлэлд нэр no_update_patron_scope = "Та өөрийн уншигчийн мэдээллийг өөрчлөх боломжгүй." no_write_items_scope = "Та материалуудад бичих боломжгүй." renewal_block = "Та үйлчилгээний материалуудыг сунгах боломжгүй" +renewal_recalled = "Зүйлийг хүсэлтээр буцаасан - буцаах хугацаа өөрчлөгдсөн" renewal_too_soon = "Одоохондоо шинэчлэх боломжгүй" requests_blocked = "Та номын сангийн материалд хүсэлт гаргах боломжгүй" special_circulation = "Хуулбар нь тусгай эргэлттэй" diff --git a/languages/ILSMessages/pt-br.ini b/languages/ILSMessages/pt-br.ini index b253394a415..0d6118573cc 100644 --- a/languages/ILSMessages/pt-br.ini +++ b/languages/ILSMessages/pt-br.ini @@ -2,6 +2,8 @@ available_for_pickup_notification = "Você tem material disponível para retirad checkout_block = "Você não pode verificar mais itens" electronic_resources_block = "Você não pode acessar recursos eletrônicos" hold_wrong_user_institution = "Não é possível reservar o item na biblioteca seelcionada." +ill_item_renewal_limit = "Devolver até a data de vencimento (máximo de renovações atingido)." +ill_renewal_too_soon = "O item será renovado automaticamente (a menos que solicitado por outros)" lost_card = "Seu cartão foi reportado como perdido" message_from_library = "Você tem uma mensagem de sua biblioteca" no_change_password_scope = "Não é permitido alterar a senha." @@ -16,6 +18,7 @@ no_update_patron_name_scope = "Você não tem permissão para atualizar o nome d no_update_patron_scope = "Você não tem permissão para atualizar os dados dos usuários." no_write_items_scope = "Você não tem permissão para escrever itens." renewal_block = "Você não pode renovar empréstimos" +renewal_recalled = "Item solicitado - data de vencimento alterada - devolver na data de vencimento" renewal_too_soon = "Não é possível renovar ainda" requests_blocked = "Você não pode fazer um pedido de material da biblioteca" special_circulation = "Cópia possui circulação especial" diff --git a/languages/ILSMessages/ru.ini b/languages/ILSMessages/ru.ini index b82aee6ab17..2a8144e0394 100644 --- a/languages/ILSMessages/ru.ini +++ b/languages/ILSMessages/ru.ini @@ -2,6 +2,8 @@ available_for_pickup_notification = "У вас есть материалы дл checkout_block = "Вы не можете получить дополнительные элементы" electronic_resources_block = "Вы не можете получить доступ к электронным ресурсам" hold_wrong_user_institution = "Вы не можете удерживать материалы из запрошенной библиотеки." +ill_item_renewal_limit = "Верните в установленный срок (максимальное количество продлений достигнуто)." +ill_renewal_too_soon = "Предмет будет автоматически продлеваться (если только его не попросят другие)" lost_card = "Ваша карта потеряна" message_from_library = "У вас есть сообщение из вашей библиотеки" no_change_password_scope = "Вам не разрешено менять пароль." @@ -16,6 +18,7 @@ no_update_patron_name_scope = "Вам не разрешено обновлять no_update_patron_scope = "Вам не разрешено обновлять данные патрона." no_write_items_scope = "Вам не разрешено писать элементы." renewal_block = "Вы не можете продлевать кассы" +renewal_recalled = "Запрошенное изделие - изменен срок исполнения - возврат в установленный срок" renewal_too_soon = "Пока невозможно продлить" requests_blocked = "Вы не можете отправить запрос на библиотечный материал" special_circulation = "Копия имеет особое распространение" diff --git a/languages/ILSMessages/sv.ini b/languages/ILSMessages/sv.ini index 2d251601078..2769899debf 100644 --- a/languages/ILSMessages/sv.ini +++ b/languages/ILSMessages/sv.ini @@ -2,6 +2,8 @@ available_for_pickup_notification = "Du har material tillgänglig för avhämtni checkout_block = "Du kan inte låna material" electronic_resources_block = "Du har inte tillgång till elektroniska material" hold_wrong_user_institution = "Du kan inte reservera material från det utvalda biblioteket." +ill_item_renewal_limit = "Återlämna på förfallodagen (förnyad max. antal gånger)." +ill_renewal_too_soon = "Exemplaret förnyas automatiskt (om ingen har reserverat det)" lost_card = "Ditt bibliotekskort har anmälts försvunnet" message_from_library = "Du har ett meddelande från biblioteket" no_change_password_scope = "Du har inte rättigheter att byta lösenord." @@ -16,6 +18,7 @@ no_update_patron_name_scope = "Du har inte rättigheter att uppdatera namn i kun no_update_patron_scope = "Du har inte rättigheter att uppdatera kundinformation." no_write_items_scope = "Du har inte rättigheter att skriva poster." renewal_block = "Du kan inte förnya material" +renewal_recalled = "Exemplaret har reserverats – förfallodagen ändrad – återlämna på förfallodagen" renewal_too_soon = "Kan ännu inte förnyas" requests_blocked = "Du kan inte reservera material" special_circulation = "Exemplaret är i snabbcirkulation" diff --git a/languages/ILSMessages/tr.ini b/languages/ILSMessages/tr.ini index 84ce489b52f..d89dffb05ad 100644 --- a/languages/ILSMessages/tr.ini +++ b/languages/ILSMessages/tr.ini @@ -2,6 +2,8 @@ available_for_pickup_notification = "Materyalleriniz teslim alınabilir" checkout_block = "Daha fazla materyal ödünç alamazsınız" electronic_resources_block = "Elektronik kaynaklara erişemezsiniz" hold_wrong_user_institution = "İstenen kütüphaneden materyal ayırtamazsınız." +ill_item_renewal_limit = "Son teslim tarihine kadar iade edin (maksimum yenileme sayısına ulaşıldı)." +ill_renewal_too_soon = "Materyalin süresi otomatik olarak uzatılacak (başkaları tarafından talep edilmediği sürece)" lost_card = "Kartınızın kayıp olduğu bildirildi" message_from_library = "Kütüphanenizden bir mesajınız var" no_change_password_scope = "Şifreyi değiştirmenize izin verilmiyor." @@ -16,6 +18,7 @@ no_update_patron_name_scope = "Kullanıcı bilgilerindeki adı güncellemenize i no_update_patron_scope = "Kullanıcı verilerini güncellemenize izin verilmiyor." no_write_items_scope = "Materyal yazmanıza izin verilmiyor." renewal_block = "Ödünç aldığınız materyallerin süresini uzatamazsınız" +renewal_recalled = "Materyal talep edildi - son tarih değiştirildi - son tarihte iade edildi" renewal_too_soon = "Henüz yenilenemiyor" requests_blocked = "Kütüphane materyali için istekte bulunamazsınız" special_circulation = "Kopyanın özel dolaşımı var" diff --git a/languages/ILSMessages/uk.ini b/languages/ILSMessages/uk.ini index 37d9fd211a7..6ee28093544 100644 --- a/languages/ILSMessages/uk.ini +++ b/languages/ILSMessages/uk.ini @@ -2,6 +2,8 @@ available_for_pickup_notification = "Ви маєте матеріал, дост checkout_block = "Вам не може бути видано більше примірників" electronic_resources_block = "У Вас немає доступу до електронних ресурсів" hold_wrong_user_institution = "Ви не можете розміщувати замовлення на матеріали у цій бібліотеці." +ill_item_renewal_limit = "Повернути до кінця терміну видачі (досягнута максимальна кількість подовжень)." +ill_renewal_too_soon = "Примірник буде автоматично подовжений (доки не буде замовлений іншими користувачами)" lost_card = "Ваш читацький квиток заявлений як втрачений" message_from_library = "Ви маєте повідомлення від Вашої Бібліотеки" no_change_password_scope = "Відсутній дозвіл на зміну пароля." @@ -16,6 +18,7 @@ no_update_patron_name_scope = "Відсутній дозвіл на оновле no_update_patron_scope = "Відсутній дозвіл на оновлення даних користувача." no_write_items_scope = "Відсутній дозвіл на запис примірників." renewal_block = "Відсутній дозвіл на подовження видач" +renewal_recalled = "Примірник замовлений – термін видачі змінений – поверніть до закінчення терміну видачі" renewal_too_soon = "Поки що не можна поновити" requests_blocked = "Відсутній дозвіл на розміщення замовлення на бібліотечний матеріал" special_circulation = "Примірник має спеціальні правила видачі" diff --git a/languages/ISO639-3/ca.ini b/languages/ISO639-3/ca.ini index 7d092e50491..198c1f4418d 100644 --- a/languages/ISO639-3/ca.ini +++ b/languages/ISO639-3/ca.ini @@ -2,6 +2,7 @@ ; a Creative Commons Attribution-ShareAlike (CC-BY-SA) 3.0 Unported license ; (http://creativecommons.org/licenses/by-sa/3.0/). _3F_3F_3F = "Desconegut" +_7C_7C_7C = "Cap intent de codificació" ___ = "No s'ha proporcionat informació" aaa = "Ghotuo" aab = "Alumu-Tesu" diff --git a/languages/ISO639-3/eu.ini b/languages/ISO639-3/eu.ini index f3bbc15e4a1..43d05ebe968 100644 --- a/languages/ISO639-3/eu.ini +++ b/languages/ISO639-3/eu.ini @@ -1,6 +1,9 @@ ; Some data in this file is derived from the Lexvo.org RDF dump, licensed under ; a Creative Commons Attribution-ShareAlike (CC-BY-SA) 3.0 Unported license ; (http://creativecommons.org/licenses/by-sa/3.0/). +_3F_3F_3F = "Ezezaguna" +_7C_7C_7C = "Kodifikatzeko ahaleginik ez" +___ = "Ez da informaziorik ematen" aae = "Arbëreshë hizkuntza" aar = "afarera" aas = "Aasax hizkuntza" diff --git a/languages/ISO639-3/hr.ini b/languages/ISO639-3/hr.ini index d7617f6c064..5cf540f9e8a 100644 --- a/languages/ISO639-3/hr.ini +++ b/languages/ISO639-3/hr.ini @@ -1,6 +1,9 @@ ; Some data in this file is derived from the Lexvo.org RDF dump, licensed under ; a Creative Commons Attribution-ShareAlike (CC-BY-SA) 3.0 Unported license ; (http://creativecommons.org/licenses/by-sa/3.0/). +_3F_3F_3F = "Nepoznato" +_7C_7C_7C = "Bez pokušaja kodiranja" +___ = "Bez informacija" aaa = "Ghotuo jezik" aab = "Alumu-Tesu jezik" aac = "Ari jezik" diff --git a/languages/ISO639-3/it.ini b/languages/ISO639-3/it.ini index 9e22403e88f..e75f71439b1 100644 --- a/languages/ISO639-3/it.ini +++ b/languages/ISO639-3/it.ini @@ -1341,6 +1341,7 @@ cky = "Cakfem-Mushere" ckz = "Lingua mista Cakchiquel-Quiché" cla = "Ron" clc = "Chilcotin" +cld = "Caldeo neo-aramaico" cle = "Chinantec, Lealao" clh = "Chilisso" cli = "Chakali" @@ -1526,6 +1527,7 @@ daa = "Dangaléat" dac = "Dambi" dad = "Marik" dae = "Duupa" +daf = "Dan" dag = "Dagbani" dah = "Gwahatike" dai = "Day" @@ -1661,6 +1663,7 @@ djf = "Djangun" dji = "Djinang" djj = "Djeebbana" djk = "Businenge Tongo" +djl = "Djiwarli" djm = "Dogon, Jamsay" djn = "Djauan" djo = "Jangkang" @@ -7143,6 +7146,7 @@ wil = "Wilawila" wim = "Wik-Mungkan" win = "Lingua winnebago" wir = "Wiraféd" +wit = "Wintu" wiu = "Wiru" wiv = "Vitu" wiy = "Wiyot" @@ -7674,6 +7678,7 @@ yit = "Lalu orientale" yiu = "Awu" yiv = "Nisu, Nord" yix = "Axi Yi" +yiy = "Yir Yoront" yiz = "Azhe" yka = "Yakan" ykg = "Yukaghir settentrionale" diff --git a/languages/ISO639-3/pt-br.ini b/languages/ISO639-3/pt-br.ini index 1831d6e0d89..a5bd089ba7e 100644 --- a/languages/ISO639-3/pt-br.ini +++ b/languages/ISO639-3/pt-br.ini @@ -901,6 +901,7 @@ bsw = "Baiso" bsx = "Yangkam" bsy = "Sabah Bisaya" bta = "Bata (Cameroon)" +btb = "Língua Beti" btc = "Bati (Cameroon)" btd = "Batak Dairi" bte = "Gamo-Ningi" @@ -909,6 +910,7 @@ btg = "Gagnoa Béte" bth = "Biatah Bidayuh" bti = "Burate" btj = "Bacan, Malaio" +btk = "Línguas Batak" btl = "Banda, Oriental" btm = "Batak Mandailing" btn = "Ratagnon" @@ -1046,6 +1048,7 @@ byt = "Berti" byv = "Medumba" byw = "Belhariya" byx = "Qaqet" +byy = "Buya" byz = "Banaro" bza = "Bandi" bzb = "Andio" @@ -1993,6 +1996,7 @@ gox = "Gobu" goy = "Goundo" goz = "Gozarkhani" gpa = "Gupa-Abawa" +gpe = "Pidgin Inglês Ganês" gpn = "Taiap" gqa = "Ga'anda" gqi = "Guiqiong" @@ -2007,6 +2011,7 @@ grg = "Madi, Gira" grh = "Gbiri-Niragu" gri = "Ghari" grj = "Grebo do Sul" +grk = "Grego" grm = "Kota Maring" gro = "Groma" grq = "Gorovu" @@ -2030,6 +2035,7 @@ gss = "Língua de Sinais Grega" gsw = "Alemão Suíço" gta = "Guató" gti = "Gbati-ri" +gtu = "Aghu-Tharnggala" gua = "Shiki" gub = "Guajajára" guc = "Wayuu" @@ -2077,6 +2083,7 @@ gwf = "Gowro" gwg = "Moo" gwi = "Gwichʼin" gwj = "ǀGwi" +gwm = "Awngthim" gwn = "Gwandara" gwr = "Gwere" gwt = "Gawar-Bati" diff --git a/languages/ISO639-3/uk.ini b/languages/ISO639-3/uk.ini index b691fc3763f..ee2732484b3 100644 --- a/languages/ISO639-3/uk.ini +++ b/languages/ISO639-3/uk.ini @@ -2,6 +2,8 @@ ; a Creative Commons Attribution-ShareAlike (CC-BY-SA) 3.0 Unported license ; (http://creativecommons.org/licenses/by-sa/3.0/). _3F_3F_3F = "Невідомо" +_7C_7C_7C = "Не було спроби закодувати" +___ = "Інформацію не надано" aaa = "Готуо" aab = "Алуму-тесу" aac = "Аріська (арі)" @@ -391,309 +393,802 @@ ash = "Абішира" asi = "Бурувай" asj = "Нсарі (саарі)" ask = "Ашкун" +asl = "Асілулу" asm = "Ассамська" +asn = "Асуріні, Сінгу (Шингу)" +aso = "Дано" asp = "Алжирська жестова мова" asq = "Австрійська жестова мова" +asr = "Асурі" +ass = "Іпуло (ассумбо)" ast = "Астурійська" asu = "Асуріні Токантінс" asv = "Асоа" +asw = "Жестова мова австралійських аборигенів" +asx = "Муратайяк" +asy = "Асмат, Яосакор" +asz = "Ас" +ata = "Пеле-Ата" atb = "Зайва (цзайва)" +atc = "Ацауака (ацвака)" +atd = "Ата Манобо" +ate = "Атембл" +atg = "Івбіє Північно-Окпела-Архе" ath = "Атабасканські мови" ati = "Аттіє" atj = "Атикамек" +atk = "Аті" +atl = "Інагта (гора Ірая)" +atm = "Ата" atn = "Аштіані" ato = "Атонг" +atp = "Атта, Пудтол" +atq = "Аралле-Табулахан" +atr = "Ваймірі-Атроарі" ats = "Гро-вантр" +att = "Атта, Памплона" +atu = "Котушка" atv = "Північноалтайська" atw = "Ацуґеві" atx = "Арутані" aty = "Анейтюмська" +atz = "Арта" +aua = "Асумбоа" +aub = "Алуґу" auc = "Ваорані" aud = "Анута" aue = "Гобабіс Кунг (Каукау, Кунг-Гобабіс)" +auf = "Арауан" +aug = "Агуна" +auh = "Ауші" aui = "Анукі" auj = "Авджіла" +auk = "Гейо" aul = "Аулуа" aum = "Асу (Нігерія)" +aun = "Молмо Оне" auo = "Ауйокава" +aup = "Макаям" auq = "Анус" +aur = "Аруек" aus = "Австралійські мови" aut = "Тубуаї" +auu = "Аує" +auw = "Аві" aux = "Аура" +auy = "Авіяана" auz = "Арабська, узбецька" ava = "Аварська" +avb = "Авау" avd = "Альвірі-Відарі" ave = "Авестійська" avi = "Авікам" +avk = "Котава" avl = "Бедуїнський діалект східного Єгипту та Леванту" +avm = "Ангкамутхі" avn = "Аватіме" +avo = "Агавотагерра" avs = "Ауширі" avt = "Ау" avu = "Авокая" +avv = "Ава-Кануейро" awa = "Авадхі" +awb = "Ава (Папуа-Нова Гвінея)" awc = "Чічіпу" +awd = "Аравакан" awe = "Аветі" +awg = "Ангутімрі" +awh = "Авбоно" +awi = "Екіом" awk = "Авабакал" +awm = "Аравум" awn = "Авнгі" +awo = "Авак" +awr = "Авера" +aws = "Авью, Південна" +awt = "Аравете" +awu = "Авью, Центральна" +awv = "Ав'ю, Джаір" +aww = "Авун" +awx = "Авара" +awy = "Авью, Едера" axb = "Абіпон" +axe = "Айерренге" axg = "Арара, мату-гроська" axk = "Яка (Центральноафриканська республіка)" +axl = "Аранда, Нижня Південна" axm = "Середньовірменська" +axx = "Харагуре" +aya = "Авар" +ayb = "Аїзо Гбе" +ayc = "Аймара, Південна" +ayd = "Аябадху" aye = "Аєре" +ayg = "Гіньянга" ayh = "Хадрамаутський діалект арабської" +ayi = "Лейіга" +ayk = "Акуку" ayl = "Лівійський діалект арабської" aym = "Аймара" ayn = "Північноєменський діалект арабської" ayo = "Айорео" ayp = "Арабська, північномесопотамський діалект" ayq = "Айі (Папуа-Нова Гвінея)" +ayr = "Аймара, Центральна" +ays = "Айта, Сорсогон" ayt = "Марівеленьо (магбікін, батаан айта, магбукун айта)" ayu = "Аю" +ayy = "Айта, Тайябас" +ayz = "Май Брат" +aza = "Ажа" azb = "Азербайджанська" +azc = "Уто-ацтекська" +azd = "Східна Дуранго Науатль" aze = "Азербайджанська" +azg = "Амузго, Сан-Педро-Амузгос" azj = "Азербайджанська" +azm = "Амузго, Іпалапа" +azn = "Науатль, Західний Дуранго" +azo = "Авінг (мбве'ві)" +azt = "Атта, Фейр" azz = "Сьєрра-пуебланський науатль" +baa = "Бабатана" +bab = "Байнук-Гунюньо" +bac = "Бадуї" bad = "Мови Банда" +bae = "Баре" +baf = "Нубака" bag = "Тукі (кі)" bah = "Багамська креольська" bai = "Мови Бамілеке" +baj = "Баракай" bak = "Башкирська" bal = "Балучі" bam = "Бамбара" ban = "Балійська" +bao = "Ваймаха" bap = "Бантава" +bar = "Баварська" bas = "Баса" bat = "Балтійські мови" +bau = "Бада" bav = "Венго (бабунго)" +baw = "Бамбілі-Бамбуї" bax = "Бамум" +bay = "Батулей" bba = "Бариба (баатонум)" +bbb = "Барай" bbc = "Тоба батак" +bbd = "Бау" bbe = "Бангба" +bbf = "Байбай" +bbg = "Барама" bbh = "Буган (боган, пакан, бугенг)" bbi = "Ромбі" bbj = "Гомала" +bbk = "Бабанки" bbl = "Бацбійська" +bbm = "Бабанго" bbn = "Унеапа" +bbo = "Бобо Мадаре, Північна" bbp = "Банда, західно-центральна" +bbq = "Бамалі" +bbr = "Ґірава" +bbs = "Бакпінка" +bbt = "Мбурку" bbu = "Кулунг (Нігерія)" +bbv = "Карнаї" +bbw = "Баба" +bbx = "Бубія" bby = "Бафанг" +bbz = "Бабалія креольська арабська" +bca = "Бай, Центральна" +bcb = "Байнук-Самік" +bcc = "Белуджі, південна" +bcd = "Бабар, Північна" +bce = "Баменям" +bcf = "Баму" +bcg = "Бага Бінарі" +bch = "Баріай" bci = "Бауле" +bcj = "Барді" +bck = "Бунаба" bcl = "Бікольська, центральна" +bcm = "Банноні" bcn = "Балійська (Нігерія)" +bco = "Калулі" +bcp = "Балі (Демократична Республіка Конго)" bcq = "Бенч" bcr = "Бабін-віцувітен" bcs = "Гумоно (когумоно)" +bct = "Бенді" bcu = "Авад Бінг (біліау)" +bcv = "Шу-Мінда-Най" bcw = "Бана" bcy = "Бакама" +bcz = "Байнук-Гуньяамоло" bda = "Байот" +bdb = "Басап" bdc = "Баудо Ембера" +bdd = "Бунама" bde = "Баде" +bdf = "Біаж" +bdg = "Бонггі" bdh = "Бака (тара бака), Судан" +bdi = "Бурун" bdj = "Бай" bdk = "Будуська" +bdl = "Баджау, індонезійська" +bdm = "Будума" bdn = "Балдему (мбазлам)" +bdo = "Мором" +bdp = "Бенде" bdq = "Бахнар" +bdr = "Баджау, Західне узбережжя" bds = "Бурунгі" +bdt = "Бокото" bdu = "Ороко" +bdv = "Бодо Парха" +bdw = "Багам" +bdx = "Будонг-Будонг" bdy = "Пантяланг (Бунджалунг)" +bdz = "Бадеші" bea = "Бівер (дане-заа)" +beb = "Бебеле" +bec = "Ісеве-Макі" +bed = "Бедоанас" +bee = "Б'янсі" +bef = "Бенабена" +beg = "Белайт" beh = "Берба (біалі)" +bei = "Бекаті" bej = "Беджа" +bek = "Бебелі" bel = "Білоруська" bem = "Бемба" ben = "Бенгальська" +beo = "Бімі" +bep = "Бесоа" +beq = "Бембе" ber = "Берберські мови" +bes = "Бесме" +bet = "Бете, Гіберуа" +beu = "Благар" +bev = "Бете, Далоа" bew = "Бетаві" bex = "Джур модо" +bey = "Белі (Папуа-Нова Гвінея)" bez = "Бена" bfa = "Барі" +bfb = "Барелі, Паурі" +bfc = "Бай, Північна" bfd = "Бафут" +bfe = "Бетаф" bff = "Бофі" +bfg = "Бусанг Каян" +bfh = "Блафе" bfi = "Британська жестова мова" bfj = "Фанджі (чууфіє)" bfk = "Бан-Хор, жестова мова" +bfl = "Банда-Нделе" +bfm = "Ммен" bfn = "Бунакська" +bfo = "Біріфор, Мальба" +bfp = "Беба" bfq = "Бадага" +bfr = "Базігар" +bfs = "Бай, Південна" bft = "Балті" +bfu = "Ґахрі" bfw = "Бонда (бондо)" +bfx = "Бантаянон" bfy = "Багхелі" +bfz = "Махасу Пахарі" +bga = "Гвамхі-Вурі" +bgb = "Бобонгко" bgc = "Хар'янві" +bgd = "Барелі, Ратві" +bge = "Баурія" bgf = "Бангандо" +bgg = "Бугун" +bgi = "Джанґан" +bgj = "Банголан" bgk = "Біт" +bgl = "Бо (Лаос)" bgm = "Мботені" +bgn = "Белуджі, західна" +bgo = "Бага Кога" +bgp = "Белуджі, східна" bgq = "Багрі" +bgr = "Бавм Чин" +bgs = "Тагабава" +bgt = "Буготу" bgu = "Мбонго" +bgv = "Варкай-Біпім" +bgw = "Бхатрі" bgx = "Балкано-гагаузька турецька" +bgy = "Бенггой" bgz = "Бангаї" +bha = "Бхарія" +bhb = "Бхілі" +bhc = "Біга" +bhd = "Бхадравахі" +bhe = "Бхая" bhf = "Одія" +bhg = "Бінандере" bhh = "Бухорі" +bhi = "Бхілалі" bhj = "Бахінг" bhk = "Албайська бікольська" +bhl = "Бімін" bhm = "Батхарі" bhn = "Бохтанська нео-арамейська" bho = "Бходжпурі" bhp = "Біма" +bhq = "Туканг Бесі південна" +bhr = "Бара малагасійська" +bhs = "Бувал" +bht = "Бхаттіялі" +bhu = "Бхунджіа" +bhv = "Бахау" bhw = "Біак" +bhx = "Бхалай" +bhy = "Бхеле" +bhz = "Бада (Індонезія)" bia = "Бадімайя" bib = "Бісса" +bic = "Бікару" bid = "Бідійо" +bie = "Бепур" bif = "Біафада" +big = "Біангай" bih = "Біхарі" bij = "Вагат-Я-Біджим-Легері" bik = "Бікольська" bil = "Біле" +bim = "Бімоба" bin = "Біні" bio = "Най (біака)" +bip = "Біла" biq = "Біпі" +bir = "Бісоріо" bis = "Біслама" +bit = "Беріномо" +biu = "Б'єте" +biv = "Біріфор, Південна" biw = "Кол (Камерун)" +bix = "Біджорі" +biy = "Бірхор" biz = "Балой" +bja = "Будза" +bjb = "Бангарла" +bjc = "Баріджі" +bje = "Бяо-Цзяо Мінь" bjf = "Єврейська новоарамейська мова Барзані" bjg = "Біджаго (бідого)" +bjh = "Бахінемо" bji = "Бурджі" bjj = "Каннауджі" +bjk = "Барок" +bjl = "Булу (Папуа Нова Гвінея)" +bjm = "Баджелані" bjn = "Банджарська" +bjo = "Банда, Середньо-південна" +bjp = "Фанамакет" +bjr = "Бінумарієн" bjs = "Баджан, креольська" +bjt = "Баланта-Гянджа" bju = "Бусуу" +bjv = "Беджонд" bjw = "Бакве" +bjx = "Банао Ітнег" +bjy = "Баялі" +bjz = "Баруга" bka = "Кіяк" +bkc = "Бака (Камерун)" +bkd = "Бінукід" +bkf = "Біке" +bkg = "Бурака" bkh = "Кого (Бакоко)" +bki = "Бакі" +bkj = "Панде" +bkk = "Брокскат" +bkl = "Берік" bkm = "Ком" +bkn = "Букітан" +bko = "Ква" +bkp = "Боко (Демократична Республіка Конго)" +bkq = "Бакайрі" bkr = "Бакумпай" +bks = "Північна Сорсоганон" +bkt = "Болокі" bku = "Бухідська" bkv = "Бекварра" +bkw = "Беквел" +bkx = "Байкено" bky = "Бокійська" +bkz = "Бунгку" bla = "Сіксіка" blb = "Білуа" blc = "Белла Кула (нуксалк, нухалк)" +bld = "Боланго" +ble = "Баланта-Кентохе" +blf = "Буол" +blg = "Балау" blh = "Куваа" +bli = "Боліа" +blj = "Болонган" +blk = "Карен, Пао" bll = "Білоксі" blm = "Джур-белі (Судан)" +bln = "Бікольська, Південний Катандуанес" blo = "Анійська" +blp = "Блабланга" +blq = "Балуан-Пам" +blr = "Бланг" +bls = "Балаесанг" blt = "Тай-дам" +blv = "Боло" +blw = "Балангао" blx = "Маг-інді айта" +bly = "Нотр" +blz = "Балантак" +bma = "Ламе" +bmb = "Бембе" +bmc = "Бієм" +bmd = "Бага Мандурі" +bme = "Лімасса" bmf = "Бом" +bmg = "Бамве" +bmh = "Кейн" bmi = "Багірмі" +bmj = "Боте-Маджі" +bmk = "Гаяві" +bml = "Бомболі" +bmm = "Малагасійська, північна бецимісарака" +bmn = "Біна (Папуа-Нова Гвінея)" +bmo = "Бамбаланг" +bmp = "Булгебі" +bmq = "Бому" bmr = "Муїнане" +bms = "Більма Канурі" +bmt = "Бяо Мон" +bmu = "Сомба-Сіаварі" +bmv = "Бум" bmw = "Бомвалі" +bmx = "Баймак" +bmy = "Бемба (Демократична Республіка Конго)" +bmz = "Бараму" +bna = "Бонерат" +bnb = "Букан" +bnc = "Бонток" +bnd = "Банда (Індонезія)" +bne = "Бінтауна" +bnf = "Масіванг" bng = "Бенга" bni = "Бангі (бобангі)" +bnj = "Східна Тавбуїд" +bnk = "Беребо" bnl = "Бун" +bnm = "Батанга" bnn = "Бунун" bno = "Бантоанон (асі)" +bnp = "Бола" bnq = "Бантик" +bnr = "Бутмас-Тур" +bns = "Бунделі" bnt = "Мови банту" +bnu = "Бентонг" +bnv = "Бенераф" +bnw = "Бісіс" +bnx = "Бангубангу" +bny = "Бінтулу" +bnz = "Бізен" boa = "Бора" bob = "Авір" bod = "Тибетська" boe = "Мундаблі" +bof = "Болон" +bog = "Жестова мова бамако" boh = "Бома" boi = "Барбареньо" +boj = "Анджам" bok = "Бонджо" +bol = "Боле" bom = "Бером" +bon = "Біне" +boo = "Бозо, Тємачеве" +bop = "Бонкіман" +boq = "Богая" +bor = "Бороро" bos = "Боснійська" bot = "Бонго" +bou = "Бондей" bov = "Тувулі (бовілі)" +bow = "Рема" +box = "Буаму" +boy = "Бодо (Центральноафриканська Республіка)" +boz = "Бозо, Тієяксо" +bpa = "Даакака" bpb = "Барбакойська" +bpd = "Банда-Банда" +bpg = "Бонгго" bph = "Ботліська" +bpi = "Багупі" +bpj = "Бінджі" +bpk = "Орове" +bpl = "Брум Перлінг Лаггер Піджин" +bpm = "Бійом" +bpn = "Дзао Мін" +bpo = "Анасі" bpp = "Кауре" +bpq = "Банда малайська" +bpr = "Блаан, Коронадал" +bps = "Блаан, Сарангані" bpt = "Барроу-пойнтська" +bpu = "Бонгу" +bpv = "Б'ян Марінд" +bpw = "Бо (Папуа Нова Гвінея)" +bpx = "Барелі, Паля" bpy = "Бішнупрія" +bpz = "Більба" +bqa = "Чумбулі" +bqb = "Багуса" bqc = "Боко" +bqd = "Бунг" +bqf = "Бага Калум" +bqg = "Баго-Кусунту" bqh = "Байма" bqi = "Бахтіарська" bqj = "Бандіал" +bqk = "Банда-Мбрес" +bql = "Білакура" +bqm = "Вумбоко" +bqn = "Болгарська жестова мова" bqo = "Бало" bqp = "Буса" +bqq = "Бірітай" +bqr = "Бурусу" +bqs = "Боснгун" +bqt = "Бамукумбіт" +bqu = "Богуру" +bqv = "Бегбере-Еджар" bqw = "Буру (Нігерія)" +bqx = "Баангі" bqy = "Бенкала, жестова мова" +bqz = "Бакака" bra = "Брадж" +brb = "Лаве" brc = "Бербісська креольська (голландська креольська)" +brd = "Барааму" bre = "Бретонська" +brf = "Бера" brg = "Бауре" brh = "Брагуї" +bri = "Мокпве" +brj = "Бієрія" +brk = "Біргід" +brl = "Бірва" brm = "Барамбу" +brn = "Борука" bro = "Броккатська" +brp = "Барапасі" +brq = "Брері" +brr = "Бірао" +brs = "Барас" brt = "Бітаре" +bru = "Бру, Східна" +brv = "Бру, Західна" +brw = "Белларі" brx = "Бодо" +bry = "Буруї" brz = "Біл Біл" bsa = "Абіномн" +bsb = "Бісая, Бруней" bsc = "Бассарі" +bse = "Вуші" bsf = "Баучі" bsg = "Південна башкарді (башагерді)" bsh = "Ката-варі, діалект" +bsi = "Бассоссі" +bsj = "Бангвінджі" bsk = "Бурушаскі" +bsl = "Баса-Гумна" +bsm = "Бусамі" bsn = "Барасана-едурія" +bso = "Бусо" +bsp = "Бага Сітему" bsq = "Басса" +bsr = "Басса-Контагора" bss = "Акус" bst = "Баскето" +bsu = "Бахонсуай" +bsv = "Бага Собане" bsw = "Байсо" bsx = "Янгкам" +bsy = "Бісая, Сабах" +bta = "Бата" btb = "Беті" +btc = "Баті (Камерун)" +btd = "Батак Дайрі" +bte = "Гамо-Нінгі" +btf = "Бірґіт" +btg = "Бете, Ґаньйоа" bth = "Біата" +bti = "Бурате" +btj = "Баканська малайська" btk = "Батакські мови" +btl = "Бхатола" btm = "Батак мандайлінг" +btn = "Ратаньйон" bto = "Бікольська, ринконада" +btp = "Будібуд" +btq = "Батек" +btr = "Баетора" +bts = "Батак Сімалунгун" +btt = "Бете-Бенді" btu = "Бату" +btv = "Батері" +btw = "Бутуанон" btx = "Батак Каро" bty = "Бобот" +btz = "Батак Алас-Клует" bua = "Бурятська" bub = "Буа" buc = "Буші (кібосі)" +bud = "Нтчам" bue = "Беотук" +buf = "Бушунг" bug = "Бугійська" +buh = "Буну, Юнуо" +bui = "Бонгілі" +buj = "Баса-Гурмана" buk = "Букава" bul = "Болгарська" bum = "Булу" bun = "Шербро" +buo = "Терей" +bup = "Бусоа" +buq = "Брем" +bus = "Бокобару" +but = "Бунґейн" +buu = "Буду" +buv = "Бун" +buw = "Бубі" bux = "Богом" buy = "Буллом Со" +buz = "Буквен" +bva = "Барейн" bvb = "Бубе (бохобе, бубе-бенга)" +bvc = "Баелеа" bvd = "Беґгу" +bve = "Берау малайська" +bvf = "Бур" bvg = "Бонкенг" bvh = "Буре" bvi = "Беланда Вірі" +bvj = "Баан" +bvk = "Букат" bvl = "Болівійська жестова мова" +bvm = "Бамунка" +bvn = "Буна" bvo = "Болго" +bvp = "Буманг" bvq = "Біррі" +bvr = "Бурарра" +bvt = "Баті (Індонезія)" +bvu = "Букіт малайська" bvv = "Баніва" bvw = "Бога" +bvx = "Діболе" +bvy = "Байбаянон" +bvz = "Баузі" +bwa = "Бвату" bwb = "Намосі-Наітасірі-Серуа" +bwc = "Бвайле" +bwd = "Бвайдока" +bwe = "Бве Карен" +bwf = "Боселева" +bwg = "Барве" bwh = "Бішуо" bwi = "Баніва" +bwj = "Бваму, Лаа Лаа" +bwk = "Баувакі" +bwl = "Бвела" +bwm = "Біват" +bwn = "Буну, Вунай" bwo = "Боро" +bwp = "Мандобо Бава" +bwq = "Бобо Мадаре, Південна" bwr = "Бура" +bws = "Бомбома" bwt = "Бафав-балонг" +bwu = "Булі (Гана)" +bww = "Бва" bwx = "Бу-Нао Буну" +bwy = "Бваму, Цві" bwz = "Бвісі" bxa = "Бауро (тайраха)" +bxb = "Беланда Бор" +bxc = "Моленге" +bxd = "Пела" bxe = "Онґота (бірале)" +bxf = "Білур" bxg = "Бангала" +bxh = "Бухуту" +bxi = "Пірлатапа" +bxj = "Баюнгу" bxk = "Лух'я" +bxl = "Джалкунан" +bxm = "Бурятська, Монголія" +bxn = "Бурдуна" +bxo = "Баріканчі" +bxp = "Бебіл" bxq = "Бееле" +bxr = "Бурятська, Росія" +bxs = "Бусам" +bxu = "Бурятська, Китай" +bxv = "Бераку" +bxw = "Банкагума" bxx = "Боро (Демократична республіка Конго)" +bxz = "Бінахарі" +bya = "Батак" byb = "Бік'я" byc = "Убахарська" byd = "Ньяду (беньяду)" +bye = "Пуйє" byf = "Бете" +byg = "Байго" +byh = "Бхуджел" +byi = "Бую" +byj = "Біна (Нігерія)" +byk = "Бяо" +byl = "Байоно" +bym = "Бідьяра" byn = "Блін" +byo = "Бійо" +byp = "Бумаджі" byq = "Басай" +byr = "Баруя" +bys = "Бурак" +byt = "Берті" byv = "Медумба" +byw = "Белгарія" +byx = "Бейнінг" +byy = "Буя" +byz = "Банаро" bza = "Банді" +bzb = "Андіо" +bzc = "Малагасійська, Південна Бецимісарака" bzd = "Брібрі (брибрі)" +bze = "Бозо, Дженаама" +bzf = "Бойкін" bzg = "Бабузька" +bzh = "Буанг, Мапос" +bzi = "Бісу" bzj = "Белізька креольська англійська" +bzk = "Креольська англійська, Нікарагуа" +bzl = "Боано (Сулавесі)" +bzm = "Болондо" +bzn = "Боано (Малуку)" +bzo = "Бозаба" +bzp = "Кемберано" +bzq = "Булі (Індонезія)" +bzr = "Бірі" bzs = "Бразильська жестова мова" bzt = "Бріфеніг (брітеніг, брітенік)" bzu = "Бурмесо" bzv = "Бебе (наамі)" bzw = "Баса (Нігерія)" +bzx = "Бозо, Келенґахо" bzy = "Обанліку" +bzz = "Евант" caa = "Чорті" cab = "Гаріфуна" cac = "Чудж (чухська)" cad = "Каддо" +cae = "Лаалаа" +caf = "Керіер, Південна" cag = "Нівакле" cah = "Кауарано" cai = "Центрально-американські індіанські мови" @@ -701,6 +1196,7 @@ caj = "Чане" cak = "Какчикель" cal = "Каролінська" cam = "Чемуї (тьямуї, вагап)" +can = "Чамбрі" cao = "Чакобо" cap = "Чіпая" caq = "Кар, нікобарська" @@ -713,618 +1209,1691 @@ caw = "Кальяуайя" cax = "Чикітано" cay = "Кайюга" caz = "Канічана" +cba = "Чібчан" +cbb = "Кабіярі" +cbc = "Карапана" +cbd = "Каріхона" +cbe = "Чіпіахес" +cbg = "Чіміла" cbh = "Кагва" cbi = "Чачі" +cbj = "Еде Кабе" cbk = "Чавакано" +cbl = "Буалхау Чін" cbn = "Ньях-кур" +cbo = "Ізора" cbr = "Кашібо-какатаїбо" cbs = "Південна кашинава" cbt = "Чаяуіта" cbu = "Кандоші" cbv = "Какуа" +cbw = "Кінабаліан" cby = "Карабайо" +cca = "Каука" ccc = "Чамікуро" ccd = "Кафундо" cce = "Чопі" +ccg = "Дака, Самба" cch = "Атсам" ccj = "Касанга (хааль)" +ccl = "Кутчі-суахілі" +ccm = "Креольська малайська, малаккська" +ccn = "Північнокавказька" +cco = "Чинантек, Комальтепек" ccp = "Чакма" ccr = "Какаопера" +ccs = "Південнокавказька" +cda = "Чоні" +cdc = "Чадська" +cdd = "Каддоан" +cde = "Ченчу" +cdf = "Чіру" +cdg = "Чамарі" +cdh = "Чамбеалі" +cdi = "Чодрі" +cdj = "Чурахі" +cdm = "Чепанг" +cdn = "Чаудангсі" cdo = "Східноміньська" cdr = "Сінда-Регі" +cds = "Чадська жестова мова" cdy = "Чадун (кам-суй)" +cdz = "Кода" +cea = "Чехаліс, Нижня" ceb = "Себуанська" +ceg = "Чамакоко" +cek = "Чин, Східний Хумі" cel = "Кельтські мови" +cen = "Цен" ces = "Чеська мова" cet = "Джалаа (кентум, кунтум)" cfa = "Дікака (діджим-бвілім)" cfd = "Кара" cfg = "Комо-карім" cfm = "Фалам Чін" +cga = "Чанґріва" +cgc = "Кагаянен" cgg = "Кіга" cgk = "Чоча-нгача (чочангачакха, цаманг)" cha = "Чаморро" chb = "Чібча" +chc = "Катавба" chd = "Високогірна оахака чонталь" che = "Чеченська" chf = "Чонтальська" chg = "Чагатайська" +chh = "Чінук" +chi = "Китайська" +chj = "Чинантек, Охотлан" chk = "Чуукська" chl = "Кауілья" chm = "Марійська" chn = "Чинук жаргон" cho = "Чоктавська" chp = "Чіпев’ян" +chq = "Чинантек, Кіотепек" chr = "Черокі" cht = "Чолонська" chu = "Церковнослов'янська" chv = "Чуваська" +chw = "Чувабу" +chx = "Шантьял" chy = "Шеєнська" +chz = "Чинантек, Озумасін" cia = "Чіа-чіа" +cib = "Чі Гбе" cic = "Чікасо" cid = "Чімаріко" cie = "Сінені" +cih = "Чиналі" +cik = "Читкулі Кіннаурі" cim = "Цимбрська" +cin = "Сінта Ларга" cip = "Ч'япанець" +cir = "Хамеа" ciw = "Чіппева" +ciy = "Чайма" +cja = "Чам, Західна" +cje = "Чру" +cjh = "Чехаліс, Верхня" cji = "Чамалинська" cjk = "Чокве" +cjm = "Чам, Східна" +cjn = "Ченапіан" cjo = "Ашенінка Пайональ" +cjp = "Кабекар" cjs = "Шорська" +cjv = "Чуаве" cjy = "Цзінська, китайська" ckb = "Сорані" +ckh = "Чак" ckl = "Сібак" +ckn = "Чін, Каанг" cko = "Ануфо" +ckq = "Каяксе" +ckr = "Кайрак" cks = "Тайо" ckt = "Чукотська" cku = "Коасаті" ckv = "Кавалан" +ckx = "Кака" +cky = "Чакфем-Мушере" +ckz = "Какчікель-кіче, змішана мова" cla = "Рон" clc = "Чілкотін" +cld = "Халдейська нео-арамейська" +cle = "Чинантек, Леалао" clh = "Чиліссо" +cli = "Чакалі" +clj = "Чін, Лайту" clk = "Іду-Мішмі" +cll = "Чала" clm = "Клаллам" +clo = "Чонталь, низовина Оахаки" +clt = "Чін, Лауту" +clu = "Калуянун" clw = "Чулимська" +cly = "Чатіно, Східне нагір'я" +cma = "Маа" cmc = "Чамські мови" +cme = "Церма" cmg = "Монгольська, класична" cmi = "Чамі-ембера" +cml = "Кампалагійська" +cmm = "Мічігамея" cmn = "Мандаринська" +cmo = "Центральний Мнонг" +cmr = "Чін, Мро-Хімі" cms = "Месапська" +cmt = "Камто" +cna = "Чангтанг" +cnb = "Чін, Чінбон" +cnc = "Коонг" cng = "Північний цян" cnh = "Чинська (хака, баунгше, лай)" cni = "Ашанінка" +cnk = "Чін, Хумі" +cnl = "Чинантек, Лалана" cno = "Кон" +cnr = "Чорногорська" +cns = "Асмат, Центральна" +cnt = "Чинантек, Тепетотутла" cnu = "Шенва" +cnw = "Чін, Нгаун" +cnx = "Корнуольська, Середня" +coa = "Малайська, Кокосові острови" cob = "Чикомусельтек" coc = "Кокопа" cod = "Кокама" +coe = "Корегуахе" cof = "Колорадо" +cog = "Чонг" +coh = "Чічоні-Чідзіхана-Чікаума" coj = "Кочімі" +cok = "Кора, Санта-Тереза" col = "Колумбійська венатчі" com = "Команчська" con = "Кофан" coo = "Комокс" cop = "Коптська" +coq = "Кокіль" cor = "Корнська" cos = "Корсиканська" cot = "Каквінте (какінте)" cou = "Вамей" +cov = "Цао Мяо" cow = "Коуліц" +cox = "Нанті" +coy = "Кояйма" coz = "Чочо" +cpa = "Чинантек, Палантла" +cpb = "Ашенінка, Укаялі-Юруа" cpc = "Ашенінка Апурукайалі" +cpe = "Креольська та піджин на основі англійської" +cpf = "Креольська та піджин на основі французької" cpg = "Каппадокійська" cpi = "Китайська піджин-англійська" +cpn = "Черепон" +cpo = "Кпіго" cpp = "Креольські та піджин, на основі португальської" cps = "Капіснон (капіценьо)" +cpu = "Ашенінка, Пічіс" cpx = "Пу-сіань мінь (путянь-сянью мінь)" +cpy = "Ашенінка, Південна Укаялі" +cqd = "Чуаньцяньдянь кластер Мяо" +cqu = "Чилійська кечуа" +cra = "Чара" +crb = "Карибська, острів" +crc = "Лонвольвол" crd = "Кьор-д'ален" cre = "Крі" +crf = "Караманта" crg = "Мічиф" crh = "Кримськотатарська" cri = "Форру" +crj = "Крі, Південно-східна" +crk = "Крі, рівнинна" +crl = "Крі, Північно-східна" +crm = "Крі, Муз" crn = "Кора, Ель Наяр" +cro = "Кроу" crp = "Креольські та піджин" +crq = "Чороте, Ійо'вуджва" +crr = "Алгонкін, Кароліна" crs = "Сейшельська креольська" +crt = "Чороте, Іоджа" crv = "Чаура (тутет)" +crw = "Чрау" +crx = "Керіер" cry = "Кіолі (корі)" +crz = "Крузеньо" +csa = "Чильтепек Чинантек" csb = "Кашубська" csc = "Каталонська жестова мова" +csd = "Жестова мова Чіангмай" +cse = "Чеська жестова мова" +csf = "Кубинська жестова мова" csg = "Чилійська жестова мова" +csh = "Ашо Чін" +csi = "Мівок, узбережжя" +csj = "Чін, Сонглай" csk = "Каса" csl = "Китайська жестова мова" +csm = "Центральна Сьєрра Мівок" csn = "Колумбійська жестова мова" +cso = "Чинантек, Сочіапам" csq = "Хорватська жестова мова" +csr = "Коста-риканська жестова мова" css = "Охлоне, південна" cst = "Північна охлоне" +csu = "Центральносуданська" +csv = "Чін, Сумту" +csw = "Свомпі Крі" +csy = "Чін, Сійін" csz = "Кусська (кусанська)" +cta = "Чатіно, Таталтепек" +ctc = "Четко" +ctd = "Чін, Тедім" +cte = "Чинантек, Тепінапа" ctg = "Читтагонгська" +cth = "Чін, Тайпхум" +ctl = "Чинантек, Тлакоаццінтепек" ctm = "Читімача" +ctn = "Хінтанге" cto = "Катіо" +ctp = "Чатіно, Західне нагір'я" +cts = "Бікол, Північний Катандуанес" +ctt = "Четті, Ваянад" ctu = "Чольська (чоль)" +ctz = "Чатіно, Сакатепек" cua = "Куа" +cub = "Кубео" +cuc = "Чинантек, Усіла" cug = "Чунг" +cuh = "Чука" cui = "Куйба (куйва)" +cuj = "Машко Піро" +cuk = "Куна, Сан-Блас" cul = "Куліна" +cum = "Кумерал" +cuo = "Куманагото" cup = "Купеньйо" +cuq = "Кун" +cur = "Чхулунг" cus = "Кушитські мови" +cut = "Куікатек, Теутіла" +cuu = "Тай Я" +cuv = "Кувок" +cuw = "Чуква" +cux = "Куікатек, Тепеусіла" +cvg = "Чуг" +cvn = "Чинантек, Вальє-Насьональ" +cwa = "Кабва" +cwb = "Майндо" +cwd = "Крі, Вудс" +cwe = "Квере" cwg = "Чек Вонг" cwt = "Кватай" +cya = "Чатіно, Нопала" cyb = "Каювава" cym = "Валлійська" cyo = "Куйонон" czh = "Китайська, діалект хуей" czk = "Єврейсько-слов'янська" +czn = "Чатіно, Зензонтепек" czo = "Центральний Мін, китайська (Мін Чжун)" +czt = "Чін, Зотунг" daa = "Дангалеат" +dac = "Дамбі" +dad = "Марік" +dae = "Дуупа" +daf = "Дан" dag = "Дагбані" +dah = "Гвахатіке" dai = "День" +daj = "Даджу, Дар Фур" dak = "Дакота" dal = "Дагало" dam = "Дамакава" dan = "Данська" +dao = "Чін, Даай" +daq = "Дандамі Марія" dar = "Даргінська" +das = "Дахо-Ду" +dau = "Даджу, Дар Сила" dav = "Таіта" +daw = "Дававенйо" +dax = "Дайі" day = "Мови землі даяків" +daz = "Дао" dba = "Бангері" dbb = "Дено" +dbd = "Дадія" +dbe = "Дабе" +dbf = "Едопі" +dbg = "Догон, Догул Дом" +dbi = "Дока" dbj = "Ідаан" dbl = "Дірбал" +dbm = "Дугурі" +dbn = "Дуріанкере" +dbo = "Дулбу" +dbp = "Дувай" +dbq = "Даба" dbr = "Дабарре" +dbt = "Бен Тей Догон" +dbu = "Бондум Догон" +dbv = "Дунгу" +dbw = "Банкан Тей Догон" +dby = "Дібіясо" dcc = "Деканська" dcr = "Негро-голландська" +dda = "Даді Даді" +ddd = "Донготоно" +dde = "Дундо" ddg = "Фаталуку" +ddi = "Гуденаф, Західна" +ddj = "Джару" ddn = "Денді (Бенін)" ddo = "Цезька" +ddr = "Дхудхуроа" +dds = "Догон, Донно Со" +ddw = "Давера-Давелур" dec = "Дагік (денгебу)" +ded = "Дедуа" +dee = "Девойн" +def = "Дезфулі" deg = "Дегема" +deh = "Дехварі" +dei = "Деміса" +dek = "Дек" del = "Делаварська" +dem = "Дем" den = "Слейв" +dep = "Делавер, піджин" +deq = "Денді (Центральноафриканська Республіка)" +der = "Деорі" +des = "Десано" deu = "Німецька" +dev = "Домунг" +dez = "Денгезе" +dga = "Дагааре, Південна" +dgb = "Буду Догон" +dgc = "Агта, Касігуран Думагат" +dgd = "Дагаарі Діула" +dge = "Дегенан" dgg = "Дога" dgh = "Дгведе" +dgi = "Дагара, Північна" +dgk = "Дагба" +dgl = "Андаанді" +dgn = "Дагоман" +dgo = "Догрі" dgr = "Догрибська" +dgs = "Догозо" +dgt = "Ндраг'нгіт" +dgu = "Дегару" +dgw = "Даунгваррунг" +dgx = "Догоро" +dgz = "Дага" +dhd = "Дхундарі" +dhg = "Дхангу" +dhi = "Дхімал" +dhl = "Дхаланджі" +dhm = "Земба" +dhn = "Дханкі" +dho = "Дходія" +dhr = "Дхаргарі" +dhs = "Дхайсо" +dhu = "Дхурга" dhv = "Деху" dhw = "Дханвар" +dhx = "Дхунгалу" +dia = "Діа" +dib = "Динка, Південно-Центральна" +dic = "Лакота Діда" +did = "Дідінґа" dif = "Діярі" dig = "Діго" +dih = "Куміай" +dii = "Дімбонг" +dij = "Дай" +dik = "Дінка, Південно-Західна" +dil = "Діллінг" dim = "Дайм (діма)" din = "Дінка" +dio = "Дібо" +dip = "Дінка, Північно-Східна" +diq = "Дімлі" +dir = "Дірім" dis = "Дімаса" +dit = "Дірарі" diu = "Діріку (гціріку, дціріку)" div = "Дівехі (мальдівська)" +diw = "Дінка, Північно-Західна" +dix = "Аветейська (Риф Діксона)" +diy = "Дьюве" +diz = "Дінг" +dja = "Джаджаваррунг" +djb = "Джинба" +djc = "Даджу, Дар Даджу" +djd = "Джамінджунг" dje = "Джерма" +djf = "Джанґун" +dji = "Джінанг" +djj = "Джиббана" djk = "Ндюка" +djl = "Дживарлі" +djm = "Догон, Джамсі" +djn = "Джауан" +djo = "Джангкан" +djr = "Джамбаррпуйнгу" +dju = "Капріман" +djw = "Джаві" +dka = "Дакпакха" +dkk = "Дакка" +dkr = "Куіджау" +dks = "Дінка, Південно-Східна" +dkx = "Мазагвей" dlg = "Долганська" +dlk = "Дахалик" dlm = "Далматинська" +dln = "Дарлонг" +dma = "Дума" +dmb = "Догон, Момбо" +dmc = "Дімір" +dmd = "Мадхі Мадхі" +dme = "Дугвор" +dmg = "Кінабатанган, Верхня" dmk = "Давуді" dml = "Дамелі" +dmm = "Дама" +dmn = "Манде" +dmo = "Кемедзунг" +dmr = "Дамар, Східна" +dms = "Дампелас" +dmu = "Дубу" +dmv = "Думпас" +dmw = "Мудбурра" +dmx = "Дема" +dmy = "Демта" +dna = "Дані, Верхня Велика Долина" +dnd = "Даонда" +dne = "Ндендеуле" dng = "Дунганська" +dni = "Дані, Нижня Велика Долина" dnj = "Дан" +dnk = "Денгка" +dnn = "Східна Дуун" +dnr = "Данару" +dnt = "Дані, Середні Велика Долина" +dnu = "Данау" +dnv = "Дану" dnw = "Західна дані (лані)" dny = "Дені" +doa = "Дом" +dob = "Добу" +doc = "Донг, Північна" +doe = "Доу" +dof = "Дому" +doh = "Донг" doi = "Догрі" +dok = "Дондо" +dol = "Досо" +don = "Тура (Папуа Нова Гвінея)" +doo = "Донго" dop = "Лукпа" +doq = "Домініканська жестова мова" dor = "Доріо" +dos = "Догосе" +dot = "Дасс" +dov = "Домбе" +dow = "Дояйо" dox = "Бусса" +doy = "Домпо" doz = "Дорзе" +dpp = "Папар" dra = "Дравідійські мови" +drb = "Даїр" drc = "Міндеріко" +drd = "Дармія" +dre = "Долпо" +drg = "Рунгус" +dri = "К'лела" +drl = "Паакантій" +drn = "Дамар, Західна" +dro = "Даро-Мату Меланау" +drq = "Дура" drr = "Дороро" drs = "Гедео" drt = "Дрентс" dru = "Рукай" +dry = "Дарай" dsb = "Нижньолужицька" dse = "Голландська жестова мова" dsh = "Даасанах" +dsi = "Діса" dsl = "Датська жестова мова" +dsn = "Дуснер" +dso = "Десія" dsq = "Тадаксахак" dta = "Дагурська" +dtb = "Кадазан, Лабук-Кінабатанган" dtd = "Нітінат" +dth = "Адитіннгітиг" +dti = "Ана Догон" +dtk = "Догон, Тене Кан" +dtm = "Догон, Томо Кан" +dto = "Догон, Томмо Со" dtp = "Центральний дусун" +dtr = "Лотуд" +dts = "Догон, Торо Со" +dtt = "Догон, Торо Тегу" +dtu = "Догон, Тебул Уре" +dty = "Дотялі" dua = "Дуала" +dub = "Дублі" +duc = "Дуна" +dud = "Хун-Сааре" +due = "Умірай Думагет Агта" +duf = "Друбея" +dug = "Чідурума" +duh = "Дунгра Бхіл" +dui = "Думун" +duj = "Дхувал" +duk = "Уяджитая" +dul = "Агта, острів Алабат" dum = "Середньонідерландська" +dun = "Дусун Дея" +duo = "Агта, Дупанінан" +dup = "Дуано" +duq = "Дусун Маланг" dur = "Діі" +dus = "Думі" duu = "Дулунська" +duv = "Дувле" +duw = "Дусун Віту" +dux = "Дуунгума" +duy = "Агта, Дікамай" +duz = "Дулі" +dva = "Дуау" +dwa = "Дірі" +dwr = "Давро" +dws = "World Speedwords Даттона" +dww = "Давава" +dya = "Дян" +dyb = "Дябердябер" +dyd = "Дюґун" +dyg = "Агта, Вілла Вісіоза" +dyi = "Джиміні Сенуфо" +dym = "Догон, Янда Дом" +dyn = "Дьянґаді" dyo = "Дьола-фоні" dyu = "Діула" dyy = "Джабугай (тяпукай)" +dza = "Тунцу" +dzd = "Даза" dze = "Джіварлі" dzg = "Дазага" +dzl = "Дзалаха" +dzn = "Дзандо" dzo = "Дзонґ-ке" +eaa = "Каренгапа" ebg = "Ебугу" +ebk = "Бонток, Східна" +ebo = "Теке-Ебо" ebr = "Ебріє (кама)" ebu = "Ембу" +ecr = "Етеокритська" ecs = "Еквадорська жестова мова" ecy = "Етеокіпрська" eee = "Е" +efa = "Ефай" +efe = "Ефе" efi = "Ефік" +ega = "Еґа" egl = "Емільянська" +ego = "Еггон" egy = "Давньоєгипетська" +ehu = "Ехуен" +eip = "Ейпомек" +eit = "Етієп" eiv = "Аскопан" +eja = "Еджамат" eka = "Екаджук" +ekc = "Східна Карнік" +eke = "Екіт" +ekg = "Екарі" +eki = "Екі" +ekk = "Естонська" +ekl = "Кол (Бангладеш)" +ekm = "Еліп" eko = "Коті" +ekp = "Екпейе" +ekr = "Ясе" +eky = "Східна Каях" +ele = "Елепі" +elh = "Ель Хугейрат" +eli = "Ндінг" +elk = "Елкей" ell = "Грецька" +elm = "Елеме" elo = "Ель-моло" +elu = "Елу" elx = "Еламський клинопис" ema = "Емай-юлеха-ора" +emb = "Ембалох" eme = "Емерілон (емерильон)" +emg = "Східна Меоханг" emi = "Муссау-Еміра" +emk = "Східна Манінкакан" emm = "Мамулік" +emn = "Еман" +emo = "Емок" +emp = "Ембера, Північна" ems = "Алутик" +emu = "Східна Мурія" +emw = "Емплавас" emx = "Ерромінчела" +emy = "Епіграфічна майя" ena = "Апалі (апал)" +enb = "Марквіта" +enc = "Ень" +end = "Енде" +enf = "Енецька, лісова" eng = "Англійська" +enh = "Енецька, тундрова" enm = "Середньоанглійська" +enn = "Енгенні" eno = "Енггано (анґгано)" enq = "Енга" +enr = "Емем" +enu = "Ену" +env = "Енван (штат Еду)" +enw = "Енван (штат Аква Ібом)" +eot = "Беті (Кот-д'Івуар)" epi = "Епі" epo = "Есперанто" +era = "Ераваллан" erg = "Сіє (сіе, ероманга)" +erh = "Ерува" +eri = "Огеа" erk = "Ефате, південна" +ero = "Горпа" +err = "Ерр (Нор)" ers = "Ерсу" +ert = "Ерітай" +erw = "Ерокванас" ese = "Есе-ехха" +esh = "Ештехарді" +esi = "Інупіатун, Північна Аляска" +esk = "Інупіатун, Північно-Західна Аляска" +esl = "Єгипетська жестова мова" +esm = "Есума" +esn = "Сальвадорська жестова мова" eso = "Естонська жестова мова" esq = "Есселен" ess = "Юїтська (центральносибірська юпікська)" est = "Естонська" esu = "Центральноаляскинська юпікська" +esx = "Екімосько-алеутський" +etb = "Етебі" etc = "Етчемін" +eth = "Ефіопська жестова мова" +etn = "Ітон (Вануату)" eto = "Етон (Камерун)" +etr = "Едоло" +ets = "Єхі" ett = "Етруська" etu = "Джагам (еджагхам, екой)" +etx = "Етен" etz = "Семімі (етна-бей)" eus = "Баскська" eve = "Евенська" +evh = "Увбіє" evn = "Евенкійська" ewe = "Еве" ewo = "Евондо" ext = "Естремадурська" eya = "Еякська" +eyo = "Кейо" +eza = "Езаа" +eze = "Узекве" faa = "Фасу" fab = "Аннобонська" +fad = "Ваґі" faf = "Фагані" +fag = "Фінонган" +fah = "Байса Фалі" +fai = "Файвол" faj = "Курсав (фаїта, кулсаб)" +fak = "Фанг (Камерун)" +fal = "Фалі, Південна" fam = "Фам" fan = "Фанґ" fao = "Фарерська" +fap = "Палор" +far = "Фаталека" fas = "Перська" fat = "Фанті" +fau = "Фаю" fax = "Фальська" +fay = "Даванський діалект" +faz = "Фарс, Північно-Західна" +fbl = "Бікол, Західний Олбей" fcs = "Квебекська жестова мова" +fer = "Фероґе" +ffi = "Фойя Фойя" +ffm = "Фулфулде, Маасіна" +fgr = "Фонґоро" fia = "Нобіїн (нобін)" fie = "Ф'єр" fij = "Фіджійська" fil = "Філіппінська" fin = "Фінська" fip = "Фіпа" +fir = "Фіран" fit = "Мянкіелі (торнедальська фінська)" fiu = "Фіно-угорські мови" +fiw = "Фіваґа" +fkk = "Кір'я-Конзел" fkv = "Фінська, квенська" +fla = "Каліспель-Пенд д'Орей" +flh = "Фоау" fli = "Фалі" +fll = "Фалі, Північна" +fln = "Мова Острова Фліндерс" flr = "Фулііру (кіфулііру)" +fly = "Цоцитаал" fmp = "Фефе" +fmu = "Далекосхідна Мурія" fng = "Фанагало" +fni = "Фаня" +fod = "Фудо" +foi = "Фой" +fom = "Фома" fon = "Фон" for = "Форей" fos = "Сірая" +fox = "Формозан" +fpe = "Креольська англійська, Фернандо По" +fqs = "Фас" fra = "Французька" frc = "Кажунський діалект французької мови" +frd = "Фордата" +fri = "Фризька" frk = "Франкська" frm = "Середньофранцузька" fro = "Давньофранцузька" +frp = "Арпітан" +frq = "Форак" frr = "Фризька північна" frs = "Фризька східна" frt = "Кіаі (фортсенал)" fry = "Західно-фризька" fse = "Фінська жестова мова" fsl = "Французька жестова мова" +fss = "Фінсько-шведська жестова мова" +fub = "Адамава Фулфулде" fuc = "Фульфульде (пулар, фулані)" fud = "Футуна, східна" +fue = "Боргу Фулфулде" +fuf = "Пулар" +fuh = "Фулфулде Західного Нігеру" +fui = "Багірмі Фулфулде" fuj = "Ко" ful = "Фула" +fum = "Фум" fun = "Фулніо (яте)" +fuq = "Фулфулде Центрально-східного Нігеру" fur = "Фріульська" fut = "Футуна-аніва" +fuu = "Фуру" +fuv = "Фулфулде, нігерійська" fuy = "Фуюг" +fvr = "Фур" +fwa = "Фвай (Поаї)" +fwe = "Фве" gaa = "Га" +gab = "Габрі" +gac = "Велико-Андаманські змішані мови" gad = "Гадданг" +gae = "Гуарекена" +gaf = "Генде" gag = "Гагаузька" gah = "Алекано (ґахуку)" +gai = "Борей" +gaj = "Гадсуп" +gak = "Гамконора" gal = "Галолі (галолен)" +gam = "Кандаво" gan = "Ґань" +gao = "Гантс" +gap = "Гал" +gaq = "Гата" +gar = "Галея" +gas = "Адівасі Гарасія" gat = "Кенаті" +gau = "Гадаба, Мудхілі" +gaw = "Нобоноб" gax = "Південна оромо (борана)" gay = "Гайо" +gaz = "Оромо" gba = "Гбайя" +gbb = "Кайтетьє" +gbd = "Караджері" +gbe = "Ніксек" +gbf = "Гайкунді" +gbg = "Гбанзірі" +gbh = "Дефі Гбе" +gbi = "Галела" gbj = "Бодо Гадаба" +gbk = "Гадді" +gbl = "Гаміт" gbm = "Ґархвалі" +gbn = "Мо'да" +gbo = "Гребо, Північна" +gbp = "Гбая-Боссангоа" +gbq = "Гбая-Бозум" +gbr = "Гбагі" +gbs = "Гбе, Гбесі" gbu = "Гаагуджу (какутю, какаду, гагуджу)" +gbv = "Гбану" +gbw = "Габі-Габі" +gbx = "Гбе, Східна Хвла" +gby = "Гбарі" gbz = "Дарі" +gcc = "Малі" +gcd = "Ганггаліда" +gce = "Галіце" +gcf = "Креольська французька, гваделупська" +gcl = "Креольська англійська, гренадська" +gcn = "Гайна" gcr = "Гвіанська креольська" gct = "Алеман колоньєро" +gda = "Гаде Лохар" +gdb = "Гадаба, Поттангі Оллар" +gdc = "Гугу Бадхун" gdd = "Гедаг" +gde = "Гуде" +gdf = "Гудуф-Гава" +gdg = "Ґа'данґ" +gdh = "Гаджераванг" +gdi = "Гунді" +gdj = "Гурджар" +gdk = "Гаданг" +gdl = "Діраша" gdm = "Лаал" +gdn = "Уманакайна" gdo = "Годоберинська" gdq = "Мехрі" +gdr = "Віпі" +gds = "Жестова мова Гандрук" +gdt = "Кунгардутий" +gdu = "Гуду" +gdx = "Годварі" +gea = "Герума" +geb = "Кіре" +gec = "Гболу Гребо" +ged = "Ґаде" +geg = "Генгл" geh = "Німецька, гуттеритська" +gei = "Гебе" gej = "Ґен" +gek = "Йівом" gel = "Ут-маїнська" gem = "Германські мови" +geq = "Джеме" +ges = "Гезер-Гором" gew = "Гера (герава, гере, равам)" gex = "Гарре" +gey = "Енья" gez = "Гєез" gfk = "Патпатар (гелік)" gft = "Гафат" +gfx = "! Сюнг, Дюна Мангетті" +gga = "Гао" +ggb = "Ґбі" +ggd = "Гугадж" +gge = "Гурагоне" +ggg = "Гургула" +ggk = "Кунгаракани" +ggl = "Ганглау" +ggm = "Гугу Міні" +ggn = "Східна Гурунг" +ggo = "Гонді, Південна" ggr = "Агу Тхарнгалу" +ggt = "Гітуа" +ggu = "Ґаґу" ggw = "Гогодала" gha = "Гадамес (гадамсі, гадамсіан)" +ghc = "Класична гельська" +ghe = "Гейл, Південна" +ghh = "Гейл, Північна" +ghk = "Геко Карен" +ghl = "Гульфан" +ghn = "Ганонга" gho = "Гомара" +ghr = "Гера" +ghs = "Гуху-Самане" +ght = "Куке" +gia = "Кія" +gib = "Ґібанава" gic = "Гейл" +gid = "Ґідар" gig = "Ґоарія" +gih = "Гітабул" gil = "Гільбертська" +gim = "Гімі (Східне нагір'я)" gin = "Гінухська" +gip = "Гімі (Західна Нова Британія)" +giq = "Зелений гелао" +gir = "Червоний гелао" +gis = "Гізіга, північна" git = "Гітсан (гітксан)" +giu = "Мулао" +giw = "Білий гелао" +gix = "Гіліма" +giy = "Гіюг" +giz = "Гізіга, південна" +gji = "Ґеджі" +gjk = "Качі Колі" +gjm = "Гундітджмара" gjn = "Гонджа" gju = "Гуджарі" +gka = "Гуя" +gke = "Ндай" gkn = "Гокана" +gko = "Кок-Нар" +gkp = "Гвінейська Кпелле" gla = "Шотландська гаельська" +glc = "Бон Гула" gld = "Нанайська" gle = "Ірландська" glg = "Галісійська" +glh = "Північно-Західна Пашаї" gli = "Гулігулі" +glj = "Гула Іро" glk = "Ґілакі" +gll = "Гарлалі" glo = "Галамбу" +glr = "Гларо-Твабо" +glu = "Гула (Чад)" glv = "Менська" +glw = "Главда" +gly = "Гуле" +gma = "Гамбера" +gmb = "Гула'алаа" +gmd = "Маґді" +gme = "Східногерманська" gmh = "Середньоверхньонімецька" gml = "Середньонижньонімецька" +gmm = "Гбая-Мбодомо" +gmn = "Гімніме" +gmq = "Північногерманська" +gmu = "Гумалу" +gmv = "Гамо" +gmw = "Західногерманська" +gmx = "Магома" gmy = "Мікенська" +gmz = "Мгболіжія" +gna = "Каанса" +gnb = "Ганґте" gnc = "Давньоканарська" gnd = "Зульго-гемзек" +gne = "Гананг" gng = "Нгангам" +gnh = "Лере" gni = "Гуніянді (куніянті)" +gnk = "//Гана" +gnl = "Ґанґулу" +gnm = "Гінуман" +gnn = "Гуматдж" +gno = "Гонді, Північна" +gnq = "Гана" +gnr = "Гуренг Гуренг" +gnt = "Гунтай" +gnu = "Гнау" gnw = "Гуарані, західна болівійська" +gnz = "Ганзі" goa = "Ґуро (квені)" gob = "Плеєро" +goc = "Горакор" +god = "Годіє" goe = "Гонгду" +gof = "Гофа" gog = "Гого" goh = "Давньоверхньонімецька" +goi = "Гобасі" +goj = "Гоулан" +gok = "Гоулі" gol = "Гола" +gom = "Гоан Конкані" gon = "Гонді" goo = "Гоне-дау" +gop = "Єретуар" +goq = "Горап" gor = "Горонтало" gos = "Гронінгенський діалект" got = "Готська" +gou = "Гавар" gow = "Горова" +gox = "Гобу" +goy = "Гундо" +goz = "Гозархані" +gpa = "Гупа-Абава" +gpe = "Ганська піджин англійська" gpn = "Таяп" gqa = "Га'анда (ганда)" +gqi = "Гуйцюн" +gqn = "Гуана (Бразилія)" +gqr = "Гор" +gqu = "Кау" +gra = "Гарасія, Раджпут" grb = "Гребо" grc = "Давньогрецька" +grd = "Гурунтум-Мбаару" +grg = "Маді" +grh = "Гбірі-Нірагу" gri = "Ґарі" +grj = "Гребо, Південна" grk = "Грецька" +grm = "Кота Маруду Талантанг" grn = "Гуарані" gro = "Ґрома" +grq = "Горову" grr = "Гурара (тазнатит, зенатья)" +grs = "Гресі" grt = "Гаро" gru = "Соддо" +grv = "Центральна Гребо" +grw = "Гведа" grx = "Гуріасо (муно)" +gry = "Барклайвіль Гребо" +grz = "Гурамалум" +gse = "Ганська жестова мова" +gsg = "Німецька жестова мова" gsl = "Гусилай" gsm = "Гватемальська жестова мова" +gsn = "Гусан" +gso = "Гбайя, Південно-західна" +gsp = "Васембо" gss = "Грецька жестова мова" gsw = "Німецька швейцарська" gta = "Гуато" +gti = "Гбаті-рі" +gtu = "Агу-Тарнггала" +gua = "Шикі" +gub = "Гуахахара" guc = "Ваюу (гуахіро)" +gud = "Діда, Йокобуе" +gue = "Гурінджі" +guf = "Гупапуйнгу" gug = "Гуарані" guh = "Гуахібо" +gui = "Східноболівійська гуарані" guj = "Гуджараті" guk = "Гумуз" gul = "Креольська англійська, Сі Айленд" +gum = "Гуамбіано" gun = "Мб'я гуарані" guo = "Гуаяберо" gup = "Кунвіньку (гунвінггу)" +guq = "Аче" gur = "Фарефаре" +gus = "Гвінейська жестова мова" gut = "Малеку-джаїка" +guu = "Яномаме" +guv = "Гей" +guw = "Гун" gux = "Гурманче" guz = "Ікігусії" +gva = "Гуана (Парагвай)" gvc = "Гуанано (піратапуйо)" +gve = "Дювет" gvf = "Голін" +gvj = "Гуаха" +gvl = "Гулай" +gvm = "Гурмана" +gvn = "Куку-Яланджі" +gvo = "Жипаранська гавіану" +gvp = "Гавіану, Пара" +gvr = "Гурунг, Західна" +gvs = "Гумавана" +gvy = "Гуяні" +gwa = "Мбато" +gwb = "Гва" gwc = "Гаврі (каламі)" gwd = "Гаввада (Але)" +gwe = "Ґвено" +gwf = "Говро" +gwg = "Моо" gwi = "Кучін" gwj = "Лгана-цгві" +gwm = "Аунгтім" gwn = "Гвандара" gwr = "Гвере (лугвере)" gwt = "Ґавар-баті (нарсаті)" +gwu = "Гуваму" +gww = "Квіні" +gwx = "Гуа" +gxx = "Ве Південна" +gya = "Гбая, північно-західна" +gyb = "Гарус" +gyd = "Каярділд" +gye = "Г'єм" +gyf = "Гунгабула" +gyg = "Гбаї" +gyi = "Г'єле" +gyl = "Гаїл" gym = "Нгабере (Гуаймі)" gyn = "Гаянська креольська англійська" +gyr = "Гуараю" +gyy = "Гуня" +gza = "Ганза" +gzi = "Газі" +gzn = "Ґане" haa = "Хен (хань)" +hab = "Ханойська жестова мова" hac = "Гуарані" +had = "Хатам" +hae = "Східна Оромо" +haf = "Хайфонська жестова мова" +hag = "Ханга" +hah = "Хахон" hai = "Хайда" haj = "Хаджонг" hak = "Хаккаська" +hal = "Халанг" +ham = "Хева" +han = "Хангаза" +hao = "Хако" +hap = "Хупла" +haq = "Ха" har = "Харарі" has = "Хаісла (хайсла)" hat = "Гаїтянська" hau = "Хауса" +hav = "Хаву" haw = "Гавайська" +hax = "Хайда, Південна" hay = "Хая" haz = "Хазара" +hba = "Хамба" +hbb = "Хуба" hbn = "Хейбан (ебанг, абул)" hbo = "Давньоєврейська" hbs = "Сербохорватська" +hbu = "Хабу" +hca = "Андаманська креольська хінді" hch = "Уічоль (Віксаріка)" +hdn = "Хайда, Північна" hds = "Гондураська жестова мова" hdy = "Хадія" +hea = "Мяо, Північний Цяньдун" heb = "Іврит" +hed = "Херде" +heg = "Хелонг" heh = "Хехе (кіхехе)" +hei = "Хейлцук" +hem = "Хемба" her = "Гереро" +hgm = "Хай//ом" +hgw = "Хайгвай" +hhi = "Хойя Хойя" +hhr = "Керак" +hhy = "Хояхоя" +hia = "Ламанг" hib = "Гібіто" hid = "Хідаца" hif = "Фіджійська гінді" hig = "Камве" +hih = "Памосу" +hii = "Гіндурі" +hij = "Хіджук" +hik = "Сейт-Кайтету" hil = "Хілігайнон" him = "Хімачалі" hin = "Гінді" +hio = "Цоа" hir = "Імаріма" hit = "Хеттська" hiw = "Хів" hix = "Хішкар’яна" +hji = "Хаджі" +hka = "Кахе" hke = "Хунде" +hkk = "Хунджара-Кайна Ке" +hks = "Хен Конг Сау Юе" +hla = "Халіа" +hlb = "Халбі" +hld = "Халанг Доан" +hle = "Хлерсу" +hlt = "Чін, Мату" hlu = "Лувійські ієрогліфи" +hma = "Хмонг, Південна Машан" +hmb = "Хумбурі Сенні Сонгхай" +hmc = "Центральна Хуейшуй Хмонг" hmd = "А-Хмао" +hme = "Східна Хуейшуй Хмонг" +hmf = "Хмонг Дон" +hmg = "Хмонг, південно-західна Гуйян" +hmh = "Хмонг, південно-західна Хуейшуй" +hmi = "Хмонг, північна Хуейшуй" +hmj = "Ге" +hmk = "Маек" +hml = "Хмонг, Луопохе" +hmm = "Центральна Машан Хмонг" hmn = "Хмонг" hmo = "Гірі-моту" +hmp = "Хмонг, Північна Машан" +hmq = "Східна Цяньдун Мяо" hmr = "Хмар" +hms = "Мяо, Південна Цяньдун" +hmt = "Хамтай" +hmu = "Хамап" +hmv = "Хмонг До" +hmw = "Хмонг, Західна Машан" +hmx = "Хмонг-М'єн" +hmy = "Хмонг, Південна Гуйян" +hmz = "Хмонг Шуа" hna = "Міна (Камерун)" +hnd = "Гіндко, Південна" hne = "Чхаттісгархі" +hnh = "//Ані" +hni = "Хані" +hnj = "Хмонг Нджуа" +hnn = "Хануну" +hno = "Гіндко, Північна" hns = "Карибська гіндустані" +hnu = "Хунг" hoa = "Гоава" +hob = "Марі (провінція Маданг)" hoc = "Хо" +hod = "Холма" +hoe = "Хором" hoh = "Хобіот" hoi = "Голікачук (холікачук)" +hoj = "Хадоті" +hok = "Хокан" +hol = "Холу" hom = "Хома" +hoo = "Голохоло" hop = "Хопі" +hor = "Хоро" +hos = "Жестова мова міста Хошимін" +hot = "Хоте" +hov = "Ховонган" +how = "Хоні" +hoy = "Холія" +hoz = "Хозо" +hpo = "Хпон" hps = "Гавайська жестова мова" +hra = "Хрангкхол" +hrc = "Нівер Міл" +hre = "Гре" +hrk = "Харуку" +hrm = "Мяо, Хорнед" +hro = "Хароі" +hrp = "Нхіррпі" +hrt = "Гертевін" +hru = "Хрусо" hrv = "Хорватська" +hrw = "Варвар Фені" hrx = "Ріограндський хунсрюкський діалект" +hrz = "Харзані" hsb = "Верхньолужицька" +hsh = "Угорська жестова мова" hsl = "Жестова мова хауса" hsn = "Сянська" hss = "Харсусі" +hti = "Хоті" hto = "Миниканська уїтотська" hts = "Хадза" +htu = "Хіту" +htx = "Хетська, середня" hub = "Уамбіса" +huc = "=/Хуа" +hud = "Хуаулу" +hue = "Сан-Франциско Дель Мар Хуаве" +huf = "Хумене" hug = "Уачипаері" huh = "Вільїче" hui = "Хулі" +huj = "Хмонг, Північний Гуйян" +huk = "Хулунг" +hul = "Хула" +hum = "Хунгана" hun = "Угорська" +huo = "Ху" hup = "Хупа" huq = "Цатська (цат)" hur = "Халкомелем" hus = "Уастекська" +hut = "Хумла" huu = "Муруйська уїтотська" +huv = "Хуаве, Сан-Матео-дель-Мар" +huw = "Хукуміна" hux = "Ніподе (нюподе-уїтотська)" huy = "Хулаула (трансзабська єврейська нео-арамейська)" huz = "Гунзибська" hvc = "Мова культури гаїтянського вуду (лангай, лангадж)" +hve = "Хуаве, Сан-Діонісіо-дель-Мар" +hvk = "Хавеке" +hvn = "Сабу" +hvv = "Хуаве, Санта-Марія-дель-Мар" +hwa = "Ване" hwc = "Гавайський піджин (гавайська креольська англійська)" hwo = "Хвана" hya = "Хя" hye = "Вірменська" iai = "Іааї" +ian = "Ятмул" +iap = "Япама" iar = "Пурарі" iba = "Ібанська" ibb = "Ібібіо" ibd = "Івайджа (іваджа)" ibe = "Акпес" ibg = "Ібанаґ" +ibl = "Ібалой" ibm = "Агої (ібамі)" +ibn = "Ібіно" ibo = "Ігбо" +ibr = "Ібуоро" +ibu = "Ібу" +iby = "Ібані" +ica = "Еде Іка" +ich = "Етківан" icl = "Ісландська жестова мова" icr = "Креольська, Сан-Андрес-Провіденсія" +ida = "Ідахо-Ісуха-Тірікі" +idb = "Індо-португальська" +idc = "Аджія" +idd = "Еде Ідака" +ide = "Ідере" +idi = "Іді" ido = "Ідо" +idr = "Індрі" +ids = "Ідеса" +idt = "Ідате" +idu = "Ідома" +ifa = "Амганад Іфугао" +ifb = "Аянган Іфугао" ife = "Іфе" +iff = "Іфо" +ifk = "Іфугао, Тувалі" +ifm = "Теке-Фууму" +ifu = "Іфугао, Майояо" +ify = "Каллахан, Келі-І" igb = "Ебіра" +ige = "Ігеде" +igg = "Ігана" igl = "Ігала" +igm = "Канггапе" +ign = "Ігнасіано" +igo = "Ісебе" igs = "Глоса" +igw = "Ігве" +ihb = "Піджин на основі Іха" +ihi = "Ігієвбе" +ihp = "Іха" +ihw = "Бідхавал" iii = "Сичуань Йї" +iin = "Тхіін" +iir = "Індоіранська" +ijc = "Ізон" +ije = "Бісені" +ijj = "Еде Ідже" +ijn = "Калабарі" ijo = "Ізонські мови" +ijs = "Іджо, південно-східна" +ike = "Східноканадський Інуктитут" +iki = "Іко" ikk = "Іка" +ikl = "Ікулу" +iko = "Олулумо-Іком" +ikp = "Ікпеші" +ikr = "Ікаранґгал" +ikt = "Інуіннактун" iku = "Інуктитут" +ikv = "Іку-Гора-Анква" ikw = "Ікверре" ikx = "Ік" +ikz = "Ікідзу" +ila = "Іль Апе" +ilb = "Іла" ile = "Окциденталь" +ilg = "Гаріг-Ілгар" ili = "Ілі-тюркська" ilk = "Бугкалот (ілонгот)" +ill = "Іранун" ilo = "Ілоко" ils = "Міжнародний жест (джестуно)" +ilu = "Ілі'уун" +ilv = "Ілуе" +ilw = "Талур" +ima = "Мала Маласар" +ime = "Імерагуен" +imi = "Анамгура" iml = "Мілук" +imn = "Імонда" +imo = "Імбонгу" +imr = "Імроінг" ims = "Марси" +imy = "Мілян" ina = "Інтерлінгва" +inb = "Інга" inc = "Індійські мови" ind = "Індонезійська" ine = "Індоєвропейські мови" ing = "Дег-хітан" inh = "Інгуська" +inj = "Інга, джунглі" inl = "Індонезійська жестова мова" inm = "Мінейська" +inn = "Ісінай" +ino = "Іноке-Яте" inp = "Іньяпарі" +ins = "Індійська жестова мова" +int = "Бірманські діалекти" +inz = "Інесеньо" ior = "Інор" +iou = "Тума-Іруму" iow = "Чівере" +ipi = "Іпілі" ipk = "Інупіак" +ipo = "Іпіко" iqu = "Ікіто" +iqw = "Ікво" ira = "Іранські мови" +ire = "Іресім" +irh = "Іраруту" +iri = "Ірігве" irk = "Іраку" irn = "Іранче" iro = "Ірокезькі мови" irr = "Ір" iru = "Ірула" +irx = "Камберау" +iry = "Ірая" +isa = "Ісабі" isc = "Ісконауа (іскобакебо)" +isd = "Існаг" ise = "Італійська жестова мова" isg = "Ірландська жестова мова" ish = "Есан" isi = "Нкем-нкум (ісібірі)" isk = "Ішкашимська" isl = "Ісландська" +ism = "Масімасі" +isn = "Ісанзу" iso = "Ісоко" isr = "Ізраїльська жестова мова" ist = "Істророманська" +isu = "Ісу (округ Менчум)" ita = "Італійська мова" +itb = "Бінонган Ітнег" +itc = "Італійські мови" +ite = "Ітене" +iti = "Інлаод Ітнег" itk = "Іудейсько-італійська" itl = "Ітельменська" +itm = "Іту Мбон Узо" ito = "Ітонама" itr = "Ітері" +its = "Ісекірі" +itt = "Ітнег, Маенг" +itv = "Ітавіт" +itw = "Іто" +itx = "Ітік" +ity = "Ітнег, Моядан" itz = "Іца" +ium = "Іу М'єн" +ivb = "Ібатан" +ivv = "Іватан" +iwk = "І-Вак" iwm = "Івам" +iwo = "Івур" +iws = "Івам, Сепік" ixc = "Іскатекська" ixl = "Іксіль" +iya = "Іяю" +iyo = "Месака" +iyx = "Яка (Конго)" izh = "Іжорська" +izi = "Ізі-Езаа-Ікво-Мгбо" +izr = "Ізере" +izz = "Ізії" jaa = "Жамамадí" jab = "Хіам" +jac = "Джакалтеко" +jad = "Джаханка" jae = "Ябем (джабем)" jaf = "Джара (джера)" jah = "Джа Хат" +jaj = "Зазао" +jak = "Якун" +jal = "Ялахатан" jam = "Патуа" +jan = "Джандай" jao = "Яньюва" +jaq = "Якай" +jas = "Яванська, новокаледонська" jat = "Джакаті" +jau = "Яур" jav = "Яванська" +jax = "Джамбі малайська" +jay = "Ян-нхангу" +jaz = "Джаве" jbe = "Єврейсько-берберські діалекти" +jbi = "Баджірі" +jbj = "Арандай" +jbk = "Барікева" jbn = "Нафусі" jbo = "Ложбан" +jbr = "Йофотек-Бромня" jbt = "Ябуті (жабуті, кіпіу, джеоромічі)" +jbu = "Джукун Такум" +jbw = "Явіджибая" +jcs = "Сільська жестова мова Ямайки" jct = "Кримчацька" +jda = "Джад" +jdg = "Джадгалі" jdt = "Гірсько-єврейська (юдео-тат, джухурі)" +jeb = "Джеберо" +jee = "Джерунг" +jeg = "Дженг" +jeh = "Джех" +jei = "Єй" +jek = "Джері Куо" +jel = "Єльмек" jen = "Дза (джен)" +jer = "Джере" +jet = "Манем" +jeu = "Йонкор Бурматагіль" +jgb = "Нгбі" jge = "Єврейсько-грузинська" +jgk = "Гвак" +jgo = "Нгомба" +jhi = "Джехай" +jhs = "Жестова мова Джанкот" +jia = "Джина" +jib = "Джибу" jic = "Тольська" +jid = "Бу" +jie = "Джилбе" +jig = "Джингілі" +jih = "Шанчжай" jii = "Джийдду (джііду)" +jil = "Джілім" +jim = "Джимі (Камерун)" jio = "Цзямао" +jiq = "Гуаньіньцяо" +jit = "Джіта" +jiu = "Джінуо, Юле" jiv = "Шуар" +jiy = "Буюань Цзінюо" +jjr = "Банкал" +jkm = "Карен, Мобва" +jko = "Кубо" +jkp = "Карен, Паку" jkr = "Коро (Індія)" +jku = "Лабір" jle = "Нгіле" +jls = "Ямайська жестова мова" +jma = "Діма" jmb = "Зумбун" jmc = "Мачаме" +jmd = "Ямдена" +jmi = "Джимі (Нігерія)" +jml = "Джумлі" +jmn = "Макурі Нага" +jmr = "Камара" +jms = "Маші (Нігерія)" +jmw = "Мувасе" +jmx = "Мікстек, Західна Хустлахуака" +jna = "Джангшунг" +jnd = "Джандавра" +jng = "Янгман" +jni = "Джанджі" jnj = "Ємса" +jnl = "Рават" +jns = "Джаунсарі" +job = "Джоба" +jod = "Воєнака" +jor = "Жора" +jos = "Йорданська жестова мова" +jow = "Джовулу" jpa = "Палестинська іудейсько-арамейська" jpn = "Японська" jpr = "Іудео-перська" jqr = "Джакару" jra = "Джарай" jrb = "Іудео-арабська" +jrr = "Джиру" +jrt = "Джорто" +jru = "Япрерія" jsl = "Японська жестова мова" +jua = "Джума" +jub = "Ванну" juc = "Чжурчженьська" +jud = "Вородугу" +juh = "Хоне" +jui = "Нгаджурі" +juk = "Вапан" +jul = "Джирел" +jum = "Джумджум" jun = "Джуанг" +juo = "Джиба" +jup = "Хупде" +jur = "Юруна" +jus = "Жестова мова Джумла" jut = "Ютландський (ютський) діалект" +juu = "Джу" +juw = "Вафа" +juy = "Джурай" jvd = "Джавіндо" +jvn = "Карибська яванська" jwi = "Джвіра-пепеса (гвіра)" jya = "Гьялронг" jye = "Єврейсько-йеменський діалект" +jyy = "Джайя" kaa = "Каракалпацька" kab = "Кабільська" kac = "Качін" +kad = "Адара" +kae = "Кетангалан" +kaf = "Кацо" +kag = "Каджаман" +kah = "Кара (Центральноафриканська Республіка)" +kai = "Карекаре" kaj = "Йю" +kak = "Каяпа Каллахан" kal = "Гренландська" kam = "Камба" kan = "Каннада" +kao = "Кассонке" kap = "Бежтинська" kaq = "Капанауа" kar = "Каренські мови" @@ -1333,132 +2902,420 @@ kat = "Грузинська" kau = "Канурі" kav = "Катукіна" kaw = "Каві" +kax = "Као" kay = "Камаюра" kaz = "Казахська" +kba = "Каларко" +kbb = "Кашуйана" kbc = "Кадівеу (кадіве)" kbd = "Кабардинська" +kbe = "Канджу" kbf = "Какаууа (какаухуа)" +kbg = "Хамба" kbh = "Камса" +kbi = "Каптіау" +kbj = "Карі" +kbk = "Трав’яний койарі" kbl = "Канембу" +kbm = "Іваль" kbn = "Каре (Центральноафриканська республіка)" +kbo = "Келіко" kbp = "Кабіє" +kbq = "Камано" +kbr = "Кафа" +kbs = "Канде" +kbt = "Абаді" +kbu = "Кабутра" +kbv = "Дера (Індонезія)" +kbw = "Кайеп" +kbx = "Ап Ма" +kby = "Канурі, Манга" +kbz = "Духва" kca = "Хантийська" +kcb = "Кавача" +kcc = "Любіла" +kcd = "Канум, Нгкалмпв" +kce = "Кайві" kcf = "Укаан" kcg = "Тіап" +kch = "Воно" +kci = "Камантан" kcj = "Кобіана" kck = "Каланга" +kcl = "Кала" kcm = "Гула (Центральноафриканська Республіка)" kcn = "Нубійська" +kco = "Кіналакна" +kcp = "Канґа" +kcq = "Камо" +kcr = "Катла" +kcs = "Коноем" +kct = "Каян" +kcu = "Камі (Танзанія)" +kcv = "Кете" +kcw = "Кабварі" +kcx = "Качама-Ганджуле" kcy = "Коранджі" +kcz = "Кононго" +kda = "Ворімі" +kdc = "Куту" kdd = "Янкуньтятяра (янкунітьятджара)" kde = "Маконде" +kdf = "Мамусі" +kdg = "Себа" kdh = "Тем (котоколі)" +kdi = "Кумам" kdj = "Карамоджонг" +kdk = "Квеньї" +kdl = "Цікімба" +kdm = "Кагома" +kdn = "Кунда" +kdo = "Кордофанська" +kdp = "Канінгдон-Ніндем" kdq = "Коч" kdr = "Караїмська" kdt = "Куй (суай)" +kdu = "Кадару" +kdw = "Конерав" +kdx = "Кам" +kdy = "Кедер" +kdz = "Кваджа" kea = "Кабувердіану" keb = "Келе" +kec = "Кейга" +ked = "Кереве" +kee = "Східна Керес" +kef = "Кпессі" +keg = "Тезе" +keh = "Кеак" kei = "Кей" +kej = "Кадар" kek = "Кекчі" +kel = "Кела" kem = "Кемак" +ken = "Кеньян" +keo = "Каква" +kep = "Кайкаді" +keq = "Камар" ker = "Кера" +kes = "Кугбо" ket = "Кетська" keu = "Акебу (кебу)" +kev = "Каніккаран" +kew = "Кева, Західна" +kex = "Карнатака Конкані" +key = "Купія (балмікі)" +kez = "Кукеле" kfa = "Кодава" kfb = "Коламі (північно-західна коламі)" +kfc = "Конда-Дора" +kfd = "Корага, Корра" kfe = "Кота (Індія)" kff = "Коя" +kfg = "Кудія" +kfh = "Курічія" +kfi = "Каннада Курумба" kfj = "Ман Мет" kfk = "Кіннаурі" +kfl = "Кунг" +kfm = "Хунсарі" +kfn = "Кук" kfo = "Коро" +kfp = "Корва" kfq = "Корку" kfr = "Кутчі (качхі)" +kfs = "Біласпурі" +kft = "Канджарі" +kfu = "Каткарі" +kfv = "Курмукар" +kfw = "Харам Нага" +kfx = "Кулу Пахарі" kfy = "Кумаоні" kfz = "Коромфе" +kga = "Кояга" +kgb = "Каве" +kgc = "Кассенг" +kgd = "Катаанський діалект" kge = "Комерінг" +kgf = "Кубе" kgg = "Кусунда" kgi = "Селангорська жестова мова" +kgj = "Гамале Кхам" +kgk = "Кайва" +kgl = "Кунггарі" +kgm = "Каріпуна" +kgn = "Карінгані" +kgo = "Кронго" kgp = "Кайнганг (каінанг)" +kgq = "Каморо" kgr = "Абун" kgs = "Гумбайнггір (кумпайнгкір)" +kgt = "Сомієв" +kgu = "Кобол" +kgv = "Карас (Каламанг)" +kgw = "Карон Дорі" +kgx = "Камару" +kgy = "К'єрунг" kha = "Кхасі" khb = "Ли (тай-ли, тай-люе, тай-ле)" +khc = "Туканг Бесі Північна" +khd = "Баді Канум" +khe = "Коровай" +khf = "Кхуен" khg = "Кхамська тибетська" +khh = "Кеху" khi = "Койсанські мови" +khj = "Кутурмі" khk = "Монгольська" +khl = "Лусі" khm = "Кхмерська" +khn = "Кхандеші" kho = "Хотаносакська" khp = "Капорі" khq = "Койра чіїні" +khr = "Кхарія" +khs = "Касуа" +kht = "Кхамті" +khu = "Нкхумбі" khv = "Хваршинська" khw = "Кховар (читралі)" +khx = "Кану" +khy = "Келе (Демократична Республіка Конго)" +khz = "Кеапара" kia = "Кім" kib = "Коаліб" +kic = "Кікапу" +kid = "Кошин" +kie = "Кібет" +kif = "Східна Парбат Кхам" +kig = "Кімаама" +kih = "Кілмері" kii = "Кіцай" +kij = "Кілівіла" kik = "Кікуйю" +kil = "Карія" kim = "Кім" kin = "Кінаруанда" kio = "Кайова" +kip = "Кхам, Шеші" +kiq = "Косадле" kir = "Киргизька" +kis = "Кіс" +kit = "Агоб" +kiu = "Кирманки" +kiv = "Кімбу" +kiw = "Ківаї, північно-східна" +kix = "Хямніунган Нага" +kiy = "Кірікірі" +kiz = "Кісі" +kja = "Млап" kjb = "Канхобальська" +kjc = "Прибережна Конджо" +kjd = "Ківаї, Південна" +kje = "Кісар" +kjf = "Халадж" kjg = "Кхму" kjh = "Фуюйсько-киргизька" +kji = "Забана" kjj = "Хіналузька" +kjk = "Високогірна Конджо" +kjl = "Кхам, Західний Парбат" +kjm = "Кханг" +kjn = "Куньчжен" +kjo = "Харіджан Кіннаурі" +kjp = "Карен, Пво Східна" +kjq = "Керес, Західна" +kjr = "Куруду" +kjs = "Східна Кева" +kjt = "Карен, Фре Пво" kju = "Кашая" +kjx = "Рамопа" +kjy = "Ераве" kjz = "Бумтанг" +kka = "Каканда" +kkb = "Кверіса" +kkc = "Одооде" +kkd = "Кінуку" +kke = "Какабе" +kkf = "Калактанг Монпа" +kkg = "Калінга, долина Мабака" kkh = "Кхун (тайкхун)" +kki = "Кагулу" kkj = "Како" kkk = "Кокота" +kkl = "Косарек Єль" +kkm = "Кіонг" +kkn = "Кон Кеу" +kko = "Карко" +kkp = "Гугубера" +kkq = "Кайку" +kkr = "Кір-Балар" +kks = "Гііво (кірфі)" +kkt = "Коі" +kku = "Тумі" +kkv = "Кангіанська" +kkw = "Теке-Кукуя" +kkx = "Кохін" kky = "Ґуґу Ґімітірр (Ґуґу Їмідхірр, Гуґуїміджір)" kkz = "Каска" kla = "Кламатська (кламат-модоцька)" klb = "Кіліва" +klc = "Колбіла" kld = "Гаміларай (каміларойська)" +kle = "Кулунг (Непал)" +klf = "Кендедже" +klg = "Тагакауло" +klh = "Велікі" +kli = "Калумпанг" klj = "Халадж, тюркська" +klk = "Коно (Нігерія)" +kll = "Каган Калаган" +klm = "Мігум" kln = "Каленжин" +klo = "Кап'я" +klp = "Камаса" +klq = "Руму" +klr = "Халінг" kls = "Калаша" +klt = "Нукна" +klu = "Клао" +klv = "Маскелін (кулівіу)" +klw = "Лінду" +klx = "Колувава" +kly = "Калао" +klz = "Кабола" +kma = "Конні" kmb = "Кімбунду" +kmc = "Донг, Південна" +kmd = "Калінга, Маджукаянг" +kme = "Баколе" +kmf = "Каре (Папуа Нова Гвінея)" kmg = "Кате" +kmh = "Калам" +kmi = "Камі (Нігерія)" kmj = "Кумарбхаг-пахаріа" +kmk = "Калінга, Лімос" +kml = "Калінга, Танудан" +kmm = "Ком (Індія)" kmn = "Автув" +kmo = "Квома" +kmp = "Гімме" kmq = "Квама" kmr = "Курманджі (північнокурдська)" +kms = "Камасау" +kmt = "Кемтуйк" +kmu = "Каніте" kmv = "Французька креольська, каріпуна" +kmw = "Комо (Демократична Республіка Конго)" +kmx = "Вабода" +kmy = "Кома" kmz = "Хорасанська тюркська" +kna = "Дера (Нігерія)" +knb = "Калінга, Лубуаган" +knc = "Центральна Канурі" +knd = "Конда" kne = "Канканай" knf = "Манканья" +kng = "Кунґо" +kni = "Кануфі" knj = "Канхобальська" +knk = "Куранко" +knl = "Кенінджал" knm = "Канамарі" knn = "Конкані" kno = "Коно" +knp = "Кванджа" +knq = "Кінтак" +knr = "Канінгра" +kns = "Кенсіу" +knt = "Катукіна, Паноан" +knu = "Коно (Гвінея)" +knv = "Табо" +knw = "Екока! Кунг" +knx = "Кендаян" +kny = "Каньок" +knz = "Каламсе" +koa = "Кономала" +koc = "Кпаті" +kod = "Коді" +koe = "Качіпо-Балезі" +kof = "Кубі" +kog = "Когі" +koh = "Койо" koi = "Комі-перм'яцька" +koj = "Сара Дунджо" kok = "Конголезька" kol = "Кол (Папуа-Нова Гвінея)" kom = "Комі" kon = "Конголезька" koo = "Конзо (лхуконзо)" +kop = "Ваубе" koq = "Кота (Габон)" kor = "Корейська" kos = "Косрае" +kot = "Лагван" +kou = "Коке" +kov = "Куду-Камо" +kow = "Кугама" kox = "Кохіма" koy = "Коюкон" +koz = "Корак" +kpa = "Кутто" +kpb = "Курумба, Муллу" +kpc = "Курріпако" +kpd = "Коба" kpe = "Кпеллє" +kpf = "Комба" kpg = "Капінгамарангі" +kph = "Кпланг" +kpi = "Кофей" kpj = "Караджа (каража)" +kpk = "Кпан" +kpl = "Кпала" kpm = "Кохо (K'Ho)" +kpn = "Кепкіріват" kpo = "Кпосо (ікпосо)" +kpq = "Корупун-Села" +kpr = "Корафе-Єга" kps = "Техіт" kpt = "Каратинська" +kpu = "Кафоа" kpv = "Комі-зирянська" kpw = "Кобон" +kpx = "Коялі, Гора" kpy = "Коряцька" +kpz = "Купсабіни" +kqa = "Мам" +kqb = "Ковай" +kqc = "Дорому-Кокі" kqd = "Християнська нео-арамейська, Кой-Санджак" +kqe = "Калаган" +kqf = "Какабай" +kqg = "Кхе" +kqh = "Кісанкаса" +kqi = "Койтабу" +kqj = "Короміра" +kqk = "Гбе, Котафон" +kql = "Кінеле" +kqm = "Хіса" kqn = "Каонде" +kqo = "Східна Кран" +kqp = "Кімре" +kqq = "Кренак" +kqr = "Кімараганг" +kqs = "Кіссі, Північна" +kqt = "Кадазан, річка Кліас" kqu = "Кві" +kqv = "Околод" +kqw = "Кандас" +kqx = "Мсер" +kqy = "Курете" kqz = "Корана" +kra = "Кумхалі" krb = "Каркінська (каркін)" krc = "Карачаєво-балкарська" +krd = "Кайруї-Мідікі" +kre = "Панара" krf = "Коро (Вануату)" krh = "Курама (т'курмі, акурмі)" kri = "Аку, діалект" @@ -1466,138 +3323,523 @@ krj = "Кінарайська (карай-а)" krk = "Керецька" krl = "Карельська" krm = "Кірім (кім, крім, бом)" +krn = "Сапо" kro = "Кру" +krp = "Коропська" +krr = "Кру'нг 2" +krs = "Гбая (Судан)" +krt = "Канурі, Тумарі" kru = "Курукх" +krv = "Кавет" +krw = "Кран, Західна" krx = "Карон" kry = "Кризька" +krz = "Канум, Сота" +ksa = "Шува-Замані" ksb = "Шамбала" +ksc = "Калінга, Південна" ksd = "Толаї (куануа, туна)" +kse = "Куні" ksf = "Бафіа" +ksg = "Кусаге" ksh = "Ріпуарські діалекти" ksi = "Ісака (Кріса)" +ksj = "Уаре" ksk = "Канса" +ksl = "Кумалу" +ksm = "Кумба" +ksn = "Касігуранін" kso = "Кофа" +ksp = "Каба" +ksq = "Кваамі" +ksr = "Боронг" +kss = "Кісі, Південна" +kst = "Віньє" +ksu = "Хам’янг" +ksv = "Кусу" ksw = "Каренська" +ksx = "Кеданг" +ksy = "Харія Тар" +ksz = "Кодаку" +kta = "Катуа" ktb = "Камбаата" +ktc = "Холок" +ktd = "Коката" +kte = "Нубрі" +ktf = "Квамі" +ktg = "Калкатунгу" +kth = "Каранга" +kti = "Мую, Північна" +ktj = "Крумен, Плапо" +ktk = "Канієт" ktl = "Короші" +ktm = "Курті" ktn = "Карітіана" kto = "Куот (панарас)" +ktp = "Кадуо" +ktq = "Катабага" +ktr = "Кота Маруду Тінагас" +kts = "Мую, Південна" +ktt = "Кетум" +ktu = "Кітуба (Демократична Республіка Конго)" +ktv = "Східна Кату" +ktw = "Като" +ktx = "Кашарарі" +kty = "Канго (округ Бас-Уеле)" ktz = "Жуц'оан" kua = "Кунама" +kub = "Кутеп" +kuc = "Квінсу" kud = "Аухелава" kue = "Куман" +kuf = "Кату, Західна" +kug = "Купа" +kuh = "Куші" +kui = "Куйкуро-Калапало" +kuj = "Курія" +kuk = "Кепо" +kul = "Кулере" kum = "Кумицька" kun = "Кунама" +kuo = "Кумукіо" +kup = "Кунімайпа" +kuq = "Каріпуна" kur = "Курдська" kus = "Кусааль" kut = "Кутенаї" kuu = "Верхньокускоквімська" +kuv = "Кур" +kuw = "Кпагуа" +kux = "Кукатжа" +kuy = "Кууку-Яу" kuz = "Кунса" kva = "Багвалинська" +kvb = "Кубу" kvc = "Кове" +kvd = "Куї (Індонезія)" +kve = "Калабакан" +kvf = "Кабалаї" +kvg = "Куні-Боазі" +kvh = "Комодо" +kvi = "Кванг" kvj = "Псікі (псікя, капсікі)" kvk = "Корейська жестова мова" +kvl = "Каяв" +kvm = "Кендем" +kvn = "Прикордонна куна" +kvo = "Добель" +kvp = "Компане" +kvq = "Геба Карен" +kvr = "Керінсі" +kvs = "Кунггара" +kvt = "Карен, Лахта" +kvu = "Карен, Інбау" +kvv = "Кольська" +kvw = "Версинг" kvx = "Паркарі Колі" +kvy = "Карен, Інтале" +kvz = "Цаукамбо (цакамбо)" kwa = "Дав (дау)" kwb = "Ква" +kwc = "Ліквала" +kwd = "Квайо" +kwe = "Кверба" +kwf = "Кварайська" +kwg = "Сара Каба Деме" +kwh = "Ковіаї" kwi = "Ава-піт (ава-куайкер)" kwj = "Кванга" kwk = "Кваквала (квакіутль)" +kwl = "Коф'яр" kwm = "Квамбі (отшиквамбі) діалект" kwn = "Кванґалі (РуКванґалі)" +kwo = "Квомтарі" +kwp = "Квадія" +kwq = "Квак" +kwr = "Квер" +kws = "Квесе" +kwt = "Квестен" +kwu = "Квакум" +kwv = "Сара Каба Наа" +kww = "Квінті" +kwx = "Хірвар" +kwy = "Конго, Сан-Сальвадор" kwz = "Кваді" +kxa = "Кайріру" kxb = "Кробу" kxc = "Консо" kxd = "Брунейська малайська" +kxe = "Какіхум" +kxf = "Карен, Мануманау" kxh = "Каро (Ефіопія)" +kxi = "Кенінгау Мурут" +kxj = "Кулфа" +kxk = "Карен, Заєїн" +kxl = "Курукс, непальська" +kxm = "Кхмерська, північна" +kxn = "Кановіт-Танджонг Меланау" kxo = "Капішана" +kxp = "Колі, Вадіяра" +kxq = "Канум, Смярки" kxr = "Коро (Папуа-Нова Гвінея)" kxs = "Канцьзя (канцзя)" +kxt = "Койват" kxu = "Куї (Індія)" +kxv = "Куві" +kxw = "Конаї" +kxx = "Лікуба" +kxy = "Кайонг" +kxz = "Керево" +kya = "Квая" +kyb = "Бутбут Калінга" +kyc = "К'яка" +kyd = "Карей" +kye = "Краче" kyf = "Куйя" +kyg = "Кеягана" kyh = "Карук (карок)" kyi = "Кіпут" +kyj = "Карао" +kyk = "Камайо" kyl = "Калапуйя" +kym = "Кпатілі" +kyn = "Бінукіднон, Північна" +kyo = "Келон" +kyp = "Канг" +kyq = "Кенга" +kyr = "Куруая" +kys = "Барам Каян" +kyt = "Каягар" +kyu = "Каях, західна" +kyv = "Кайорт" +kyw = "Кудмалі" +kyx = "Рапуазі" +kyy = "Камбайра" +kyz = "Каябі" +kza = "Караборо, Західна" +kzb = "Кайбобо" +kzc = "Бондуку Куланго" +kzd = "Кадай" +kze = "Косена" +kzf = "Да'а Кайлі" +kzg = "Кікай" +kzi = "Келабіт" kzj = "Прибережний кадазан" kzk = "Казукуру" kzl = "Каєлі" +kzm = "Кайс" +kzn = "Кокола" +kzo = "Канінгі" +kzp = "Кайдіпанг" +kzq = "Кайке" +kzr = "Каранг" +kzs = "Дусун, Сугут" +kzt = "Дусун, Тамбунан" +kzu = "Каюпулау" +kzv = "Ком'яндарет" kzw = "Шукуру" +kzx = "Камарська" +kzy = "Канго (район Чопо)" +kzz = "Калабра" +laa = "Південна Субанен" +lab = "Лінійна A" lac = "Лакандонська" lad = "Ладіно" +lae = "Паттані" laf = "Лафофа (тегем-аміра)" lag = "Лангі" lah = "Ланда" +lai = "Ламбія" laj = "Ланґо (Уганда)" +lak = "Лака (Нігерія)" +lal = "Лалія" lam = "Ламба" +lan = "Лару" lao = "Лаоська" +lap = "Лака" laq = "Кабяо" +lar = "Ларте" +las = "Лама (Того)" lat = "Латинська" +lau = "Лаба" lav = "Латиська" +law = "Лаудже" +lax = "Тіва" +lay = "Лама (М'янма)" laz = "Арібватса (лае, лахе)" +lba = "Луі" +lbb = "Лейбл" lbc = "Лаккія (лаккя)" lbe = "Лакська" +lbf = "Тінані" +lbg = "Лаопанг" +lbi = "Ла'бі" lbj = "Ладакська" +lbk = "Бонток, Центральна" +lbl = "Бікол, Лібон" +lbm = "Лодхі" +lbn = "Ламет" +lbo = "Лавен" +lbq = "Вампар" +lbr = "Лохорунг" +lbs = "Лівійська жестова мова" +lbt = "Лачі" +lbu = "Лабу" +lbv = "Лаватбура-Ламусонг" +lbw = "Толакі" lbx = "Лаванган" +lby = "Ламу-Ламу" +lbz = "Ларділ" +lcc = "Легеньєм" +lcd = "Лола" +lce = "Лонконг" +lcf = "Лубу" +lch = "Лучазі" lcl = "Лісела" +lcm = "Тунгаг" +lcp = "Лава, Західна" +lcq = "Лугу" +lcs = "Лісабата-Нуньялі" +lda = "Кла-Дан" +ldb = "Ідун" +ldd = "Лурі" +ldg = "Леніма" +ldh = "Ламджа-Денгса-Тола" +ldi = "Лаарі" +ldj = "Леморо" +ldk = "Лілау" +ldl = "Каан" ldm = "Ландома" ldn = "Лаадан" +ldo = "Лоо" +ldp = "Тсо" +ldq = "Луфу" +lea = "Лега-Шабунда" +leb = "Лала-Біса" lec = "Леко (рік'а)" led = "Ленду" +lee = "Леле" lef = "Лелемі (лефана)" +leg = "Ленгва" +leh = "Лендже" +lei = "Леміо" +lej = "Ленгола" +lek = "Лейпон" lel = "Леле" +lem = "Номанде" len = "Ленка" +leo = "Леті (Камерун)" lep = "Лепча" +leq = "Лембена" +ler = "Ленкау" +les = "Лесе" +let = "Аміо-Гелімі" +leu = "Кара (Папуа Нова Гвінея)" +lev = "Ламма" +lew = "Кайлі, Ледо" +lex = "Луанг" +ley = "Лемоланг" lez = "Лезгинська" +lfa = "Лефа" lfn = "Лінгва франка нова" +lga = "Лунга" lgb = "Лагу" +lgg = "Лугбара" +lgh = "Лагхуу" +lgi = "Ленгілу" +lgk = "Лінгарак" lgl = "Вала" +lgm = "Лега-Мвенга" lgn = "Опуо (т'апо)" +lgq = "Логба" +lgr = "Ленґо" +lgt = "Пахі" +lgu = "Лонггу" +lgz = "Лігенза" lha = "Лаха" +lhh = "Лаха (Індонезія)" +lhi = "Лаху Ши" +lhl = "Лахул Лохар" +lhm = "Лхомі" +lhn = "Лаханан" +lhp = "Лхокпу" lhs = "Млахсо" lht = "Ло-тога" +lhu = "Лаху" +lia = "Західно-центральна Лімба" +lib = "Лікум" +lic = "Хлайська" +lid = "Ньїндру" +lie = "Лікіла" lif = "Лімбу" +lig = "Лігбі" lih = "Ліхір" +lii = "Лінгхім" lij = "Монегаська" +lik = "Ліка" lil = "Ліллует" lim = "Лімбурзька" lin = "Лінґала" +lio = "Лікі" lip = "Лікпе (секпеле)" liq = "Лібідо" +lir = "Англійська, ліберійська" lis = "Лісу" lit = "Литовська" +liu = "Логорик" liv = "Лівська" +liw = "Кол" +lix = "Ліабуку" +liy = "Банда-Бамбарі" +liz = "Лібінза" +lja = "Голпа" +lje = "Рампі" +lji = "Лайоло" +ljl = "Ліо" ljp = "Лампунг Апі" +ljw = "Йірандалі" +ljx = "Юру" +lka = "Лакалей" +lkb = "Кабрас" +lkc = "Куконг" +lkd = "Лаконде" +lke = "Кеній" +lkh = "Лакха" lki = "Лекі" +lkj = "Ремун" +lkl = "Лаеко-Лібуат" +lkm = "Калаамая" lkn = "Лакон" +lko = "Хайо" +lkr = "Пері" +lks = "Кіса" lkt = "Лакота" +lku = "Кунгкарі" +lky = "Локоя" +lla = "Лала-Роба" +llb = "Лоло" +llc = "Леле (Гвінея)" lld = "Ладинська" +lle = "Леле" +llf = "Мова відлюдників" +llg = "Лоле" +llh = "Ламу" +lli = "Теке-Лаалі" +llj = "Ладжі Ладжі" +llk = "Лелак" +lll = "Лілау" +llm = "Ласаліму" +lln = "Леле (Чад)" +llo = "Хлор" llp = "Ефате, північна" +llq = "Лолак" lls = "Литовська жестова мова" llu = "Лау" llx = "Лауан" +lma = "Східна Лімба" +lmb = "Мерей" +lmc = "Лімілнган" lmd = "Лумун" +lme = "Певе" +lmf = "Лембата, Південна" lmg = "Ламогай" +lmh = "Ламбічхонг" +lmi = "Ломбі" +lmj = "Лембата, Західна" +lmk = "Ламканг" lml = "Рага" +lmm = "Ламам" lmn = "Ламбаді" lmo = "Ломбардська" +lmp = "Лімбум" +lmq = "Ламатука" +lmr = "Ламалера" +lmu = "Ламену" lmv = "Ломаївіті" lmw = "Мова озера Мівок" +lmx = "Лаймбу" +lmy = "Ламбойя" +lmz = "Ламбі" +lna = "Лангбаше" +lnb = "Мбаланху" lnd = "Лун Баванг" lng = "Лангобардська" +lnh = "Ланох" +lni = "Даантанай" +lnj = "Лінгітхай" +lnl = "Банда, Південно-Центральна" +lnm = "Лангам" +lnn = "Лоредіакаркар" +lno = "Ланго (Судан)" +lns = "Ламнсо'" +lnu = "Лонгуда" +lnw = "Ланіма" +lnz = "Лонзо" +loa = "Лолода" lob = "Лобі" +loc = "Інонхан" +loe = "Салуан" lof = "Логол (лукха)" +log = "Лого" +loh = "Нарім" +loi = "Лома (Кот-д'Івуар)" +loj = "Лу" +lok = "Локо" lol = "Монго" +lom = "Лома (Ліберія)" lon = "Ломве, Малаві" +loo = "Ломбо" +lop = "Лопа" +loq = "Лобала" +lor = "Лорхон (теен)" +los = "Лоніу" lot = "Отухо" lou = "Креольська французька, Луїзіана" +lov = "Лопі" +low = "Тампіас Лобу" +lox = "Лоун" +loy = "Локе" loz = "Лозі" +lpa = "Лелепа" +lpe = "Лепкі" +lpn = "Лонг Пхурі Нага" +lpo = "Ліпо" +lpx = "Лопіт" lra = "Лара (луру, бакаті)" +lrc = "Північна Лурі" lre = "Лаврентійська" lrg = "Ларакія" +lri = "Марачі" +lrk = "Лоаркі" +lrl = "Ларестанські діалекти" +lrm = "Марама" +lrn = "Лоранг" +lro = "Ларо" +lrr = "Південний Ямпху" +lrt = "Ларантука малайська" +lrv = "Лареват" +lrz = "Лемеріг" +lsa = "Ласгерді" lsd = "Єврейський неоарамейський діалект Захо" +lse = "Лусенго" +lsg = "Ліонська жестова мова" +lsh = "Ліш" +lsi = "Лаші" +lsl = "Латвійська жестова мова" +lsm = "Олусамія" +lso = "Лаоська жестова мова" +lsp = "Панамська жестова мова" +lsr = "Аруоп" +lss = "Ласі" +lst = "Жестова мова Тринідаду і Тобаго" +lsy = "Маврикійська жестова мова" ltc = "Середньокитайська" ltg = "Латгальська" lti = "Леті (Індонезія)" +ltn = "Латунде" +lto = "Олутсотсо" +lts = "Лутачоні" +ltu = "Лату" ltz = "Люксембурзька мова" lua = "Луба-лулуа" lub = "Луба-катанга" @@ -1607,17 +3849,45 @@ lue = "Лувале" luf = "Лауа" lug = "Луганда" lui = "Луїсеньо" +luj = "Луна" +luk = "Лунанаха" +lul = "Олу'бо" +lum = "Луімбі" lun = "Лунда" luo = "Луо" +lup = "Лумбу" luq = "Лукумі" +lur = "Лаура" lus = "Лушей" lut = "Лушуцид" +luu = "Лумба-Якха" luv = "Луваті" +luw = "Луо (Камерун)" luy = "Луія" +luz = "Лурі, Південна" +lva = "Маку'а" lvk = "Лавукалеве" lvs = "Латиська" +lvu = "Левука" +lwa = "Лвалу" +lwe = "Лево Еленг" +lwg = "Олуванга" +lwh = "Лачі, Білий" +lwl = "Східна Лава" +lwm = "Лаомська" +lwo = "Луво" +lwt = "Левотобі" +lwu = "Лаву" +lww = "Лево" +lya = "Лаякха" +lyg = "Лінгнгам" +lyn = "Луяна" lzh = "Класична китайська" +lzl = "Ліцліц" +lzn = "Лейнонг Нага" lzz = "Лазська" +maa = "Мазатек, Сан-Херонімо-Текоатль" +mab = "Мікстек, Ютандучі" mad = "Мадурська" mae = "Баркул (Бо-Рукул)" maf = "Мафа" @@ -1630,383 +3900,1320 @@ mal = "Малаялам" mam = "Мам" man = "Мандінго" map = "Австронезійські мови" +maq = "Чикіуітлан Мазатек" mar = "Маратхі" mas = "Масаї" +mat = "Матлацінка, Сан-Франциско" +mau = "Уаутла Мазатек" +mav = "Сатере-Маве" maw = "Мампрулі" +max = "Малайська, Північно-Молукканська" +maz = "Центральна Мазахуа" +mba = "Хігаонон" +mbb = "Манобо, Західний Букіднон" mbc = "Макуші" +mbd = "Дібабавон Манобо" mbe = "Молала" +mbf = "Баба малайська" +mbh = "Мангсенг" +mbi = "Іліанен Манобо" +mbj = "Надеб" +mbk = "Малол" mbl = "Максакалі" mbm = "Лембаама" mbn = "Макагуан" mbo = "Мбо (Камерун)" mbp = "Малайська" +mbq = "Майзін" mbr = "Нукак" +mbs = "Манобо, Сарангані" +mbt = "Манобо, Матігсалуг" +mbu = "Мбула-Бвазза" +mbv = "Мбулунгіш" +mbw = "Марінг" +mbx = "Марі (провінція Східний Сепік)" +mby = "Мемоні" +mbz = "Амолтепек Мікстек" mca = "Мака" +mcb = "Мачігуенга" +mcc = "Бітур" +mcd = "Шаранауа" +mce = "Ітундухія Мікстек" mcf = "Матсес" +mcg = "Мапойо" +mch = "Макірітарі (Є'куана)" +mci = "Месем" +mcj = "Мваніп" +mck = "Мбунда" +mcl = "Макагуахе" mcm = "Малайсько-португальська креольська" mcn = "Маса (масана)" +mco = "Коатлан Міксе" mcp = "Макаа" +mcq = "Есе" +mcr = "Меня" +mcs = "Мамбаї" +mct = "Менгіса" +mcu = "Камерун Мамбіла" +mcv = "Мінанібай" +mcw = "Мава (Чад)" +mcx = "Мпіемо" +mcy = "Південна Ватут" +mcz = "Маван" +mda = "Мада (Нігерія)" +mdb = "Морігі" +mdc = "Мале (Папуа Нова Гвінея)" mdd = "Мбум" mde = "Маба" mdf = "Мокшанська" +mdg = "Массалат" mdh = "Магінданао" +mdi = "Мамву" +mdj = "Мангбету" +mdk = "Мангбуту" +mdl = "Мальтійська жестова мова" +mdm = "Майого" mdn = "Мбаті" +mdp = "Мбала" +mdq = "Мболе" mdr = "Мандарська" mds = "Марія (Папуа Нова Гвінея)" +mdt = "Мбере" +mdu = "Мбоко" +mdv = "Мікстек, Санта-Люсія Монтеверде" mdw = "Мбосі" mdx = "Дізін" +mdy = "Мале (Ефіопія)" +mdz = "Суруї-ду-Пара" +mea = "Менка" +meb = "Ікобі" +mec = "Мара" +med = "Мелпа" +mee = "Менген" +mef = "Мегам" +meg = "Меа" +meh = "Мікстек, південно-західний Тлаксіако" +mei = "Мідоб" +mej = "Мейя" +mek = "Мекео" +mel = "Центральна Меланау" +mem = "Мангала" men = "Менде" +meo = "Кедахська малайськ" mep = "Мірівунг (міріуунг)" +meq = "Мерей" mer = "Меру" +mes = "Масмадже" +met = "Мато" meu = "Моту" +mev = "Манн" +mew = "Маака" mey = "Хасанія" mez = "Меноміні" mfa = "Малайська келантан-паттані" +mfb = "Бангка" mfc = "Мба" +mfd = "Менданкве-Нквен" mfe = "Маврикійська креольська" +mff = "Накі" +mfg = "Міксіфор" +mfh = "Матал" +mfi = "Вандала" +mfj = "Мефеле" mfk = "Мофу, північна" mfl = "Путай (маргі вест)" +mfm = "Маргі Південна" +mfn = "Крос-рівер Мбембе" +mfo = "Мбе" +mfp = "Малайська Макассар" +mfq = "Моба" +mfr = "Марітіель" mfs = "Мексиканська жестова мова" +mft = "Мокеранг" +mfu = "Мбвела" mfv = "Манджак" +mfw = "Мулаха" mfx = "Мело" mfy = "Майо" +mfz = "Мабаан" mga = "Середньоірландська" +mgb = "Марарит" +mgc = "Морокодо" +mgd = "Мору" +mge = "Манго" +mgf = "Маклев" +mgg = "Мпумпонг" mgh = "Макува-меето" +mgi = "Джілі (ліджілі, лід'їлі)" +mgj = "Абурені" +mgk = "Мавес" mgl = "Ідне (малеу-кіленге)" +mgm = "Мамбае" mgn = "Мбангі" +mgo = "Метаʼ" +mgp = "Східна Магар" +mgq = "Маліла" +mgr = "Мамбве-Лунгу" +mgs = "Манда (Танзанія)" +mgt = "Монгольська" mgu = "Майлу (магі)" +mgv = "Матенго" mgw = "Матумбі" +mgy = "Мбунга" +mgz = "Мбугве" +mha = "Манда (Індія)" +mhb = "Махонгве" mhc = "Мочо" +mhd = "Мбугу" mhe = "Мах мері (бесісі)" +mhf = "Мамаа" +mhg = "Маргу" +mhh = "Маской Піджин" mhi = "Ма'ді" mhj = "Могольська" +mhk = "Мунгака" +mhl = "Мауваке" +mhm = "Махува-Моніга" mhn = "Мокенська" +mho = "Маші (Замбія)" +mhp = "Балійська малайська" mhq = "Мандан" +mhr = "Східна марійська" mhs = "Буру (Індонезія)" +mht = "Мандахуака" +mhu = "Даранг Денг" mhw = "Мбукушу (тхімбукушу)" +mhx = "Лхаово" mhy = "Мааньян" mhz = "Мор (острів Інішмор)" mia = "Маямі-іллінойс" +mib = "Мікстек Ататлахука" mic = "Мікмак" +mid = "Мандейська" +mie = "Мікстек, Окотепек" +mif = "Мофу-Гудур" +mig = "Мікстек, Сан-Мігель-ель-Гранде" +mih = "Чаюко Мікстек" +mii = "Чигмекатітлан Мікстек" mij = "Мунгбам (абар)" mik = "Мікасукі (хітчіті-мікасукі)" +mil = "Мікстек, Пеньолес" +mim = "Алакатлацала Мікстек" min = "Мінангкабау" +mio = "Мікстек, Пінотепа Насьональ" +mip = "Апаско-Апоала Мікстек" miq = "Міскіто" +mir = "Істмуська міхе" mis = "Інші мови" +mit = "Mixtec, Південна Пуебла" +miu = "Какалостепек Мікстек" +miw = "Акоє" +mix = "Мікстек, Мікстепек" +miy = "Аютла Мікстек" +miz = "Коацоспан Мікстек" +mjc = "Mixtec, Сан-Хуан, Колорадо" mjd = "Майду, північно-східна" +mje = "Мускум" mjg = "Монгорська (ту)" +mjh = "Мвера (Ньяса)" mji = "Кіммун" +mjj = "Мавак" +mjk = "Матукар" +mjl = "Мандеалі" +mjm = "Медебур" +mjn = "Ма (Папуа Нова Гвінея)" +mjo = "Маланкураван" +mjp = "Малапандарам" +mjq = "Маларійська" +mjr = "Малаведа" mjs = "Мішип (чіп)" mjt = "Сауріа Пахарія" +mju = "Манна-Дора" +mjv = "Маннан" mjw = "Карбі" +mjx = "Махалі" mjy = "Могіканська (махікан)" +mjz = "Маджі" mka = "Мбре" +mkb = "Мал Пахарія" +mkc = "Силіпут" mkd = "Македонська" +mke = "Мавчі" +mkf = "Мія" +mkg = "Мак (Китай)" mkh = "Мон-кхмерські мови" mki = "Дхаткі" mkj = "Мокільська (мокілезе)" +mkk = "Байп" +mkl = "Моколе" +mkm = "Моклен" +mkn = "Купанг Малайська" +mko = "Мінганг Досо" +mkp = "Мойкоді (дорірі)" +mkq = "Бей Мівок" +mkr = "Малас" +mks = "Мікстек, Силакайоапан" +mkt = "Вамале" +mku = "Коньянка Манінка" mkv = "Мавеа" +mkw = "Кітуба (Конго)" +mkx = "Кінамігінг Манобо" mky = "Таба (східно-макіанська)" mkz = "Макасае" mla = "Тамамбо" +mlb = "Мбуле" +mlc = "Цао Лан" +mld = "Малахел" +mle = "Манамбу" +mlf = "Мал" mlg = "Малагасійська" +mlh = "Мапе" +mli = "Малімпунг" +mlj = "Мілту" +mlk = "Ілвана" +mll = "Малуа Бей" mlm = "Мулам" +mln = "Маланго" mlo = "Мломп (гуломпаай)" +mlp = "Баргам" +mlq = "Манінкакан, Західний" +mlr = "Ваме" +mls = "Масаліт" mlt = "Мальтійська" +mlu = "То'абайта" mlv = "Мотлав" +mlw = "Молоко" +mlx = "Малфаксал" +mlz = "Малайнон" +mma = "Мама" +mmb = "Моміна" +mmc = "Мазахуа, штат Мічоакан" +mmd = "Маонан" +mme = "Мае" mmf = "Мундат" +mmg = "Амбрим, Північна" +mmh = "Мехінаку" +mmi = "Мусар" +mmj = "Маджвар" +mmk = "Муха-Дора" +mml = "Ман Мет" +mmm = "Майі" +mmn = "Маманва" +mmo = "Буанг, Мангга" mmp = "Сіаві (мусанська)" +mmq = "Мусак" +mmr = "Мяо, Західний Сянсі" +mmt = "Малаламай" +mmu = "Ммаала" +mmv = "Міріті" +mmw = "Емае" +mmx = "Мадак" mmy = "Мігаама" +mmz = "Мабаале" +mna = "Мбула" +mnb = "Муна" mnc = "Манчжурська" +mnd = "Монде" +mne = "Наба" +mnf = "Мундані" +mng = "Східний Мнонг" mnh = "Моно (Демократична Республіка Конго)" mni = "Маніпурі" mnj = "Мунджі" mnk = "Мандінка" +mnl = "Тіале" +mnm = "Мапена" +mnn = "Мнонг, Південна" mno = "Манобо" mnp = "Китайська, північноміньська (мінь бей)" +mnq = "Мінрік" mnr = "Моно (США)" mns = "Мансійська" +mnt = "Майкулан" mnu = "Мер" +mnv = "Реннелл-Беллона" mnw = "Монська" mnx = "Манікіон" +mny = "Маньява" +mnz = "Моні" +moa = "Мван" moc = "Мокові" mod = "Мобіліанський (мобільський) жаргон" moe = "Монтаньє (інну-аймун)" mog = "Монгондоу (болаанг монгондоу)" moh = "Магавк" +moi = "Мбой" +moj = "Монзомбо" +mok = "Морорі" mol = "Молдовська" mom = "Мангу (хоротега)" mon = "Монгольська" +moo = "Моном" mop = "Мопан" +moq = "Мор (півострів Бомберай)" mor = "Моро" mos = "Моссі" mot = "Барі" +mou = "Могум" mov = "Мохаве" +mow = "Moi (Конго)" +mox = "Моліма" +moy = "Шеккачо" +moz = "Гергіко" +mpa = "Мпото" +mpb = "Муллукмуллук" +mpc = "Мангараї" +mpd = "Машінере" +mpe = "Маджанг" mpg = "Марба" +mph = "Маунг" +mpi = "Мпаде" +mpj = "Марту Вангка" +mpk = "Мбара (Чад)" +mpl = "Середня Ватут" +mpm = "Мікстек, Йосондуа" +mpn = "Міндірі" +mpo = "Міу" +mpp = "Мігабак" +mpq = "Матіс" +mpr = "Вангуну" +mps = "Дадібі" +mpt = "Міан" +mpu = "Макурап" +mpv = "Мунгкіп" +mpw = "Мапідіан" +mpx = "Місіма-Панаеаті" +mpy = "Мапія" +mpz = "Mpi" +mqa = "Маба (Індонезія)" +mqb = "Мбуко" +mqc = "Манголе" +mqe = "Матепі" +mqf = "Момуна" +mqg = "Кота Бангун Кутай Малайська" +mqh = "Мікстек, Тлазоялтепек" +mqi = "Марірі" +mqj = "Мамаса" +mqk = "Манобо, Раджа Кабунсуван" mql = "Мбеліме (ніенде)" +mqm = "Маркізька, Південна" +mqn = "Моронене" +mqo = "Модоле" +mqp = "Маніпа" +mqq = "Мінокок" +mqr = "Мандер" mqs = "Макіанська, західна" +mqt = "Мок" mqu = "Мандарі" +mqv = "Мосімо" +mqw = "Мурупі" +mqx = "Мамуджу" +mqy = "Мангарай" +mqz = "Пано" mra = "Млабрі" +mrb = "Маріно" mrc = "Марікопа" +mrd = "Магар, Західна" mre = "Жестова мова Мартас-Він'ярд" mrf = "Елсенг" mrg = "Мішинг" mrh = "Чін, Мара" mri = "Маорі" mrj = "Гірськомарійська" +mrk = "Хмвавеке" +mrl = "Мортлокська" mrm = "Мверлап" +mrn = "Чеке Холо" +mro = "Мру" +mrp = "Моруас" +mrq = "Маркізька, Північниа" mrr = "Марія (мадія), Індія" +mrs = "Марагус" +mrt = "Маргі Центральна" +mru = "Моно (Камерун)" mrv = "Мангареванська (мангарева)" mrw = "Маранао" +mrx = "Дінеор" +mry = "Мандая" +mrz = "Маринд" msa = "Малайська" msb = "Масбатеньо (мінасбате)" +msc = "Манінка, Санкаран" +msd = "Жестова мова юкатекських майя" +mse = "Мусей" +msf = "Меквей" +msg = "Морейд" +msh = "Малагасійська, Масікоро" +msi = "Малайська, Сабах" +msj = "Ма (Демократична Республіка Конго)" +msk = "Мансака" msl = "Молоф" +msm = "Агусан Манобо" +msn = "Вурес" +mso = "Момбум" +msp = "Маріцауа" +msq = "Каак" msr = "Монгольська жестова мова" +mss = "Масела, Західна" +msu = "Мусом" +msv = "Маслам" msw = "Суа (мансоанка, кунанте)" +msx = "Моресада" +msy = "Аруаму" +msz = "Момаре" +mta = "Котабато Манобо" +mtb = "Аньїн Морофо" +mtc = "Муніт" +mtd = "Муаланг" mte = "Моно-алу" +mtf = "Мурік (Папуа Нова Гвінея)" +mtg = "Уна" +mth = "Мунггуі" +mti = "Майва (Папуа Нова Гвінея)" +mtj = "Москона" +mtk = "Мбе'" +mtl = "Монтол" mtm = "Маторська" mtn = "Матагальпа" +mto = "Міксе, Тотонтепек" +mtp = "Ноктен" mtq = "Мионгська" mtr = "Меварі" mts = "Йора" mtt = "Мота" +mtu = "Мікстек, Тутутепек" +mtv = "Асаро'о" +mtw = "Бінукіднон, Південна" +mtx = "Мікстек, Тідаа" +mty = "Набі" mua = "Мунданг" +mub = "Мубі" +muc = "Аджумбу" mud = "Алеутсько-мідновська" mue = "Медіа-ленгуа" +mug = "Мусгу" +muh = "Мюндю" +mui = "Мусі" +muj = "Мабіре" +muk = "Мугом" mul = "Декілька мов" +mum = "Майвала" mun = "Мови мунда" +muo = "Ніонг" mup = "Раджастханська" +muq = "Східна Сянсі Мяо" mur = "Мурле" mus = "Крикська" +mut = "Західна Мурія" muu = "Яаку" +muv = "Мутуван" +mux = "Бо-Унг" +muy = "Муянг" muz = "Мурсі" +mva = "Манам" +mvb = "Маттоле" +mvd = "Мамбору" +mve = "Марварі (Пакистан)" +mvf = "Монгольська, периферійна" +mvg = "Мікстек, Юкуаньє" +mvh = "Мулгі" mvi = "Міякоа" +mvk = "Мекмек" +mvl = "Мбара" +mvm = "Мунья (муя)" +mvn = "Мінавеха" mvo = "Марово" +mvp = "Дурі" +mvq = "Моере" +mvr = "Марау" +mvs = "Массеп" +mvt = "Мпотоворо" +mvu = "Марфа" +mvv = "Мурут, Тагал" +mvw = "Мачінга" +mvx = "Меосвар" +mvy = "Індо-Кохістані" +mvz = "Мескан" +mwa = "Мватебу" +mwb = "Джувал" mwc = "Аре" +mwd = "Мудбура" +mwe = "Мвера" mwf = "Муррін-пата" +mwg = "Айклеп" +mwh = "Мук-Арія" mwi = "Нінде (лабо)" +mwj = "Маліго" +mwk = "Кіта Манінкакан" mwl = "Мірандська" +mwm = "Сар" +mwn = "Ньямванга" +mwo = "Центральна Маево" mwp = "Кала-лагав-я" +mwq = "Чін, Мюн" mwr = "Марварі" +mws = "Мвімбі-Мутхамбі" +mwt = "Мокен" +mwu = "Мітту" mwv = "Ментавайська" +mww = "Хмонг До" +mwx = "Медіак" +mwy = "Мосіро" +mwz = "Моінгі" +mxa = "Mixtec, Північно-Західна Оахака" +mxb = "Мікстек, Тезоатлан" +mxc = "Маньїка" +mxd = "Моданг" +mxe = "Меле-Філа" +mxf = "Малгбе" +mxg = "Мбангала" +mxh = "Мвуба" mxi = "Мосарабська" +mxj = "Денг, Геман" +mxk = "Монумбо" +mxl = "Гбе, Максі" +mxm = "Мерамера" +mxn = "Moi (Індонезія)" +mxo = "Мбове" +mxp = "Міксе, Тлауітолтепек" +mxq = "Джукіла Міксе" +mxr = "Мурік (Малайзія)" +mxs = "Уїтепек Мікстек" +mxt = "Джамільтепек Мікстек" +mxu = "Мада (Камерун)" +mxv = "Метлатонок Мікстек" +mxw = "Намо" +mxx = "Махоу" +mxy = "Мікстек, південно-східний Ночіштлан" +mxz = "Центральна Масела" mya = "Бірманська" +myb = "Мбай" +myc = "Маєка" +myd = "Марамба" mye = "Миін" myf = "Бамбасі" +myg = "Манта" myh = "Мака" +myi = "Міна (Індія)" +myj = "Мангаят" myk = "Мамара сенуфо" +myl = "Мома" +mym = "Ме'ен" myn = "Маянські мови" myo = "Анфілло" myp = "Пірахан" +myq = "Лісова Манінка" myr = "Муніче" mys = "Месмес" myu = "Мундуруку" myv = "Ерзянська" myw = "Муюв" myx = "Масаба (гісу)" +myy = "Макуна" +myz = "Мандейська класична" +mza = "Мікстек, Санта-Марія-Сакатепек" mzb = "Мзаб (тумзабт)" +mzc = "Мадагаскарська жестова мова" +mzd = "Малімба" +mze = "Морава" mzg = "Монастирська жестова мова" mzh = "Гуіфней" +mzi = "Іксатлан Мазатек" +mzj = "Манья" +mzk = "Мамбіла, Нігерія" +mzl = "Мазатлан Мікс" mzm = "Мумуйє" mzn = "Мазандеранська" +mzo = "Матіпухі" mzp = "Мовіма" +mzq = "Морі Атас" +mzr = "Марубо" mzs = "Маканська" +mzt = "Мінтіл" +mzu = "Інапанг" +mzv = "Манза" mzw = "Дег" +mzx = "Маваяна" +mzy = "Мозамбіцька жестова мова" +mzz = "Майадому" +naa = "Намла" nab = "Намбіквара, південна" +nac = "Нарак" +nad = "Ніджадалі" +nae = "Нака'ела" +naf = "Набак" nag = "Наґамська креольська" nah = "Науатль" nai = "Мови північноамериканських індіанців" naj = "Налу" +nak = "Наканай" nal = "Налік" nam = "Нганьгі" nan = "Південноміньська" +nao = "Нааба" nap = "Палі" naq = "Нама" +nar = "Ігута" nas = "Наасіоі" +nat = "Хунгворо" nau = "Науру" nav = "Навахо" +naw = "Навурі" +nax = "Накві" +nay = "Нарріньєрі" +naz = "Коатепек Науатль" +nba = "Ньємба" nbb = "Ндое" +nbc = "Чанг Нага" +nbd = "Нгбінда" +nbe = "Коньяк Нага" nbf = "Насі" +nbg = "Нагарчал" nbh = "Нгамо" +nbi = "Мао Нага" +nbj = "Нгарінман" +nbk = "Наке" nbl = "Південна ндебелє" +nbm = "Нгбака Ма'бо" +nbn = "Курі" +nbo = "Нкуколі" nbp = "Ннам" +nbq = "Нґгем" +nbr = "Нумана-Нунку-Гбанту-Нумбу" +nbs = "Намібійська жестова мова" +nbt = "На" +nbu = "Нага, Ронгмей" +nbv = "Нгамамбо" +nbw = "Нгбанді, Південна" +nbx = "Нгура" +nby = "Нінгера" +nca = "Ійо" +ncb = "Центральна Нікобарська" +ncc = "Понам" +ncd = "Начерінг" nce = "Яле" +ncf = "Ноці" ncg = "Нісгаа" nch = "Уастекський науатль, центральний" +nci = "Класична Науатль" +ncj = "Науатль, Північна Пуебла" +nck = "Накара" +ncl = "Мічоакан Науатль" +ncm = "Намбо" +ncn = "Науна" +nco = "Сібе" +ncp = "Ндактуп" +ncr = "Нкане" ncs = "Нікарагуанська жестова мова" +nct = "Чоте Нага" +ncu = "Чумбурунг" +ncx = "Центральна Пуебла Науатль" ncz = "Натчез" +nda = "Ндаса" +ndb = "Кенсвей Нсей" ndc = "Ндау" ndd = "Нде-нселе-нта" nde = "Північна ндебеле" +ndf = "Надрувіан" +ndg = "Нденгереко" +ndh = "Ндалі" +ndi = "Чамба Леко" +ndj = "Ндамба" +ndk = "Ндака" +ndl = "Ндоло" +ndm = "Ндам" +ndn = "Нгунді" ndo = "Ндонга" +ndp = "Ндо" +ndq = "Ндомбе" ndr = "Ндоро (ндула)" nds = "Нижньонімецька" +ndt = "Ндунга" +ndu = "Дугун" ndv = "Ндут" +ndw = "Ндобо" +ndx = "Ндуга" +ndy = "Лутос" +ndz = "Ндоґо" +nea = "Східна Нгада" +neb = "Тура (Кот-д'Івуар)" nec = "Недебанг" +ned = "Нде-Гбіте" +nee = "Нелемва-Ніксумвак" +nef = "Нефамесе" neg = "Негідальська" +neh = "Ньєнха" +nei = "Новохетська" +nej = "Неко" +nek = "Неку" +nem = "Немі" +nen = "Ненгоне" +neo = "На-Мео" nep = "Непальська мова" +neq = "Міксе, Північно-Центральна" +ner = "Яхадіан" +nes = "Бхоті Кіннаурі" net = "Нете" neu = "Нео" +nev = "Ньяхеун" new = "Неварська" +nex = "Неме" +ney = "Нейо" nez = "Не-персе" +nfa = "Дхао" +nfd = "Ахвай" nfl = "Айво" nfr = "Нафаанра (нафанан)" +nfu = "Мфумте" nga = "Нгбака" +ngb = "Нгбанді, Північна" ngc = "Нгомбе (Демократична республіка Конго)" +ngd = "Нгандо" +nge = "Нгемба" +ngf = "Транс-Нова Гвінея" +ngg = "Нгбака Манза" ngh = "Нц'у" +ngi = "Нгізім" +ngj = "Нгі" +ngk = "Далабон" +ngl = "Ломве" +ngm = "Нгатікеська креольська" +ngn = "Нгво" +ngo = "Нгоні" ngp = "Нгулу" +ngq = "Нгореме" ngr = "Нангку (енгдеву)" +ngs = "Гвоко" +ngt = "Нгек" +ngu = "Герреро Науатль" +ngv = "Нагумі" +ngw = "Нгваба" +ngx = "Нгвах'ї" +ngy = "Тібеа" +ngz = "Нгунгвель" nha = "Нханда" +nhb = "Бенг" +nhc = "Науатль, Табаско" nhd = "Чиріпа гуарані (Ава Гуарані)" nhe = "Уастекський науатль, східний" +nhf = "Нхувала" +nhg = "Науатль, Тетельсінго" +nhh = "Нахарі" +nhi = "Науатль, Сакатлан-Ауакатлан-Тепецінтла" +nhk = "Істмус Науатль" +nhm = "Морелос Науатль" +nhn = "Центральна Науатль" nho = "Такуу" +nhp = "Істмус-Пахапан Науатль" +nhq = "Уаскалека Науатль" nhr = "Наро" +nht = "Науатль, Ометепек" +nhu = "Нооне (ноні)" +nhv = "Науатль, Темаскальтепек" +nhw = "Науатль, Західна Уастека" nhx = "Істмус мекаяпанська науатль" +nhy = "Науатль, Північна Оахака" +nhz = "Науатль, Санта-Марія-ла-Альта" nia = "Ніаська" +nib = "Накаме" nic = "Нігеро-кордофанські мови" +nid = "Нганді" nie = "Ніеллімська" +nif = "Нек" +nig = "Нгалакан" +nih = "Ньїха (Танзанія)" +nii = "Нії" nij = "Нгаджу" +nik = "Нікобарська, Південна" +nil = "Ніла" nim = "Ірамба (ніламба)" +nin = "Нінзо" nio = "Нганасанська" +niq = "Нанді" +nir = "Німборан" +nis = "Німі" +nit = "Коламі, Південно-Східна" niu = "Ніуе" niv = "Нівхська (гіляцька)" +niw = "Німо" +nix = "Гема" +niy = "Нгіті" +niz = "Нінгіл" +nja = "Нзаньї" +njb = "Нага, Нокте" +njd = "Ндонде Хамба" njh = "Лотха нага" +nji = "Гуданджі" +njj = "Нджен" +njl = "Нджалгулгуле" njm = "Ангамі Нага" +njn = "Лянгмай Нага" njo = "Ао нага" +njr = "Нджереп" +njs = "Ніса" +njt = "Ндюка-Тріо Піджин" +nju = "Нгаджунмайя" +njx = "Куньї" +njy = "Ньєм" +njz = "Нііші" +nka = "Нкойя" +nkb = "Хойбу Нага" +nkc = "Нконго" +nkd = "Койренг" +nke = "Ндуке" +nkf = "Інпуї Нага" +nkg = "Некгіні" +nkh = "Хежа Нага" +nki = "Нага, Тхангал" +nkj = "Накаї" +nkk = "Нокуку" +nkm = "Намат" +nkn = "Нкангала" +nko = "Нконья" +nkp = "Ніуатопутапу" +nkq = "Нкамі" nkr = "Нукуоро" +nks = "Асмат, Північна" +nkt = "Нїїка" +nku = "Боуна Куланго" +nkv = "Ніїка (Малаві та Замбія)" nkw = "Нкуту" +nkx = "Нкороо" +nkz = "Нкарі" +nla = "Нгомбале" +nlc = "Налка" nld = "Нідерландська" +nle = "Східна Ньяла" nlg = "Гела" +nli = "Грангалі" +nlj = "Ніялі" +nlk = "Нінія Ялі" nll = "Нігалі" +nlo = "Нгул" +nlq = "Лао Нага" +nlr = "Нгарла" +nlu = "Нчумбулу" +nlv = "Науатль, Орізаба" +nlw = "Валангама" +nlx = "Нахалі" +nly = "Ньямал" +nlz = "Нальоґо" +nma = "Марам Нага" nmb = "Біг Намбас" +nmc = "Нгам" +nmd = "Ндуму" +nme = "Мзіме Нага" +nmf = "Нага, Тангхул (Індія)" nmg = "Квазіо" +nmh = "Монсанг Нага" +nmi = "Ньям" +nmj = "Нгомбе (Центральноафриканська Республіка)" nmk = "Намакура (макура, намакір)" +nml = "Ндемлі" +nmm = "Манангба" nmn = "К'хонг" +nmo = "Мойон Нага" nmp = "Німанбурру" +nmq = "Намбья" +nmr = "Німбарі" +nms = "Летембой" +nmt = "Намонуїто" nmu = "Майду, північно-східна" +nmv = "Нгаміні" +nmw = "Німоа" +nmx = "Нама (Папуа Нова Гвінея)" +nmy = "Намуї" +nmz = "Навдм" +nna = "Ньянгумарта" nnb = "Нанде" +nnc = "Нансере" +nnd = "Амбае, Західна" +nne = "Нгандйєра" +nnf = "Нгаїнг" +nng = "Марінг Нага" nnh = "Нгємбун" +nni = "Північна Нуаулу" +nnj = "Ньянгатом" +nnk = "Нанкіна" +nnl = "Нага, Північна Ренгма" +nnm = "Намія" +nnn = "Нґете" nno = "Нюношк (мова)" +nnp = "Нага, Ванчо" +nnq = "Нгіндо" +nnr = "Нарунгга" +nns = "Нінґ'є" nnt = "Нантикокська" +nnu = "Дванг" +nnv = "Нугуну (Австралія)" +nnw = "Нуні, Південна" +nnx = "Нгонг" +nny = "Ньянгга" +nnz = "Нда'нда'" noa = "Воун-меу" nob = "Букмол" +noc = "Нук" nod = "Північнотайська (кам муанг)" noe = "Німаді" +nof = "Номане" nog = "Ногайська" +noh = "Ному" +noi = "Ноірі" +noj = "Нонуя" nok = "Нуксак (ноуксак)" +nol = "Номлакі" +nom = "Нокаман" non = "Давньоскандинавська" nop = "Нумангганг (Мангганг)" +noq = "Нгонго" nor = "Норвезька" +nos = "Східна Нісу" not = "Номаціґенґа (маціґенка)" +nou = "Еваге-Ноту" nov = "Новіаль" +now = "Ньямбо" +noy = "Ной (лоо)" noz = "Найї" +npa = "Нар Фу" +npb = "Нупбіха" +npg = "Нага, Поньо-Гонванг" +nph = "Нага, Пхом" npi = "Непальська" +npl = "Науатль, південно-східна Пуебла" +npn = "Мондрополон" +npo = "Нага, Почурі" +nps = "Ніпсан" +npu = "Нага, Пуймей" +npy = "Напу" +nqg = "Наго, Південна" +nqk = "Еде Наго, Кура" nqm = "Ндом" +nqn = "Нен" nqo = "Нко" +nqq = "К'ян-Кар'ява Нага" +nqy = "Акьяунг Арі Нага" +nra = "Нгом" nrb = "Нара (нера, бареа)" nrc = "Норська" +nre = "Нага, Південна Ренгма" +nrg = "Наранго" +nri = "Чокрі Нага" +nrl = "Нгарлума" nrm = "Наром (наромська)" nrn = "Норн" nrp = "Північнопіценська" +nrr = "Нора" nrt = "Калапуянська, північна" +nru = "Наруа" nrx = "Нгурмбур" +nrz = "Лала" +nsa = "Нага, Сангтам" +nsc = "Нші" +nsd = "Нісу, Південна" nse = "Нсенга (сенга)" +nsf = "Нісу, Північно-Західна" nsg = "Нгаса (онгамо)" +nsh = "Нгоші" +nsi = "Нігерійська жестова мова" +nsk = "Наскапі" +nsl = "Норвезька жестова мова" +nsm = "Нага, Суми" +nsn = "Нехан" nso = "Сото північна" nsp = "Непальська жестова мова" +nsq = "Мівок, Північна Сьєрра" nsr = "Приморська жестова мова" +nss = "Налі" +nst = "Нага, Тасе" +nsu = "Науатль, Сьєрра-Негра" +nsv = "Нісу, Південно-Західна" +nsw = "Навут" +nsx = "Нсонго" +nsy = "Насал" nsz = "Нісенан" +nte = "Натембо" +ntg = "Нгантангарра" +nti = "Натіоро" ntj = "Нганятьяра" +ntk = "Ікома-Ната-Ісенье" ntm = "Натені" +nto = "Нтомба" +ntp = "Північна Тепеуан" +ntr = "Дело" +nts = "Натагаймас" +ntu = "Натугу (натгу)" +ntw = "Нотуей" +ntx = "Нага, Тангхул (М'янма)" +nty = "Мантсі" +ntz = "Натанзі" +nua = "Юага" nub = "Нубійські мови" +nuc = "Нукуїні" +nud = "Нгала" +nue = "Нгунду" +nuf = "Нусу" +nug = "Нунгалі" +nuh = "Ндунда" nui = "Нгумбі" +nuj = "Ніольський діалект" nuk = "Нутка (Нуу-ча-нульт)" +nul = "Нуса Лаут" num = "Ніуафооу" nun = "Анунг" nuo = "Нґуен" nup = "Нупе" +nuq = "Нукуману" +nur = "Нукурія" nus = "Нуер" nut = "Нунг" +nuu = "Нгбунду" +nuv = "Північна Нуні" +nuw = "Нгулуван" +nux = "Мехек" nuy = "Нунггубую (вубуй)" +nuz = "Науатль, Тламакасапа" +nvh = "Насарійська" +nvm = "Наміє" +nvo = "Нйокон" +nwa = "Наватінехена" +nwb = "Ньябва" nwc = "Неварі класична" nwe = "Нгве" +nwg = "Нгаявунг" +nwi = "Південно-західна Танна" +nwm = "Ньямуса-Моло" +nwo = "Науо" +nwr = "Навару" +nwx = "Середній Невар" +nwy = "Ноттуей-Мегеррін" +nxa = "Науете" +nxd = "Нгандо (Демократична Республіка Конго)" +nxe = "Наге" nxg = "Нґадха" +nxi = "Нінді" +nxk = "Кокі Нага" +nxl = "Нуаулу, південна" +nxm = "Нумідійська" +nxn = "Нгавун" +nxq = "Наксі" +nxr = "Нінгерум" +nxu = "Нарау" +nxx = "Нафрі" nya = "Ньянджа" +nyb = "Ньянгбо" +nyc = "Ньянга-лі" +nyd = "Ніольський діалект" +nye = "Ньєнґо" +nyf = "Гір'яма" +nyg = "Ньїнду" +nyh = "Ньїгіна" +nyi = "Ама (Судан)" +nyj = "Ньянга" +nyk = "Ньянека" +nyl = "Ньєу" nym = "Ньямвезі" nyn = "Ньянколе" nyo = "Ньоро" +nyp = "Ньянг'і" +nyq = "Найіні" +nyr = "Ньїха (Малаві)" +nys = "Ньюнга" +nyt = "Ньявейгі" +nyu = "Ньюнгве" nyv = "Нюлнюльська (ньюльньюльська)" +nyw = "Мова Тай Йо" +nyx = "Нганьяйвана" nyy = "Нгонде" nza = "Мбембе (тігонг)" +nzb = "Нджебі" nzi = "Нзіма" +nzk = "Нзакара" +nzm = "Нага, Земе" nzs = "Новозеландська мова жестів" +nzu = "Теке-Нзіку" +nzy = "Нзакамбай" +nzz = "Догон, Нанга Дама" oaa = "Орокська (ульта, уйльта)" oac = "Орочська" +oar = "Давньоарамейська (до 700 р. до н. е.)" +oav = "Аварська, стара" +obi = "Обіспеньо" +obk = "Бонток, Південна" +obl = "Обло" obm = "Моавітська" +obo = "Манобо, Обо" +obr = "Бірманська, стара" +obt = "Бретонська, стара" +obu = "Обулом" oca = "Окаїна (окайна)" och = "Китайська, давня" oci = "Окситанська" +oco = "Корніш, Старий" +ocu = "Атзінго Матлацінка" +oda = "Одут" +odk = "Од" odt = "Старонідерландська" +odu = "Одуал" ofo = "Офо" ofs = "Старофризька" ofu = "Футоп (ефутоп)" +ogb = "Огбія" +ogc = "Огба" +oge = "Грузинська, стара" +ogg = "Огбоголо" +ogo = "Кхана" +ogu = "Огбронуагум" +oht = "Хеттська, стара" ohu = "Угорська, давня" oia = "Ойрата (войрата)" +oin = "Інебу Оне" +ojb = "Північно-західна Оджибва" +ojc = "Центральна Оджибва" +ojg = "Східна Оджибва" oji = "Оджибве" ojp = "Класична японська" +ojs = "Оджибва, Северн" +ojv = "Онтонг Ява" ojw = "Оджибве, західна" oka = "Оканаган" +okb = "Окобо" okd = "Окодія (акіта)" +oke = "Окпе (Південно-Західне Едо)" +okg = "Коко Бабангк" +okh = "Кореш-е Ростам" +oki = "Окієк" okj = "Джувої (око-джувой)" +okk = "Квамтім Оне" +okl = "Кентська жестова мова, стара" +okm = "Корейська, середня (10-16 ст.)" +okn = "Окі-Но-Ерабу" oko = "Корейська, давня (3-9 століття)" +okr = "Кіріке" +oks = "Око-Ені-Осаєн" oku = "Оку" +okv = "Орокайва" +okx = "Окпе (північно-західне Едо)" +ola = "Валунгґе" +old = "Мочі" +ole = "Олеха" +olk = "Олкол" +olm = "Олома" olo = "Ліввіки" +olr = "Олрат" oma = "Омаха-понка" omb = "Амбае, східна" omc = "Мочіка" +ome = "Омехес" +omg = "Омагуа" +omi = "Омі" +omk = "Омок" +oml = "Омбо" +omn = "Мінойська" +omo = "Утармбунг" +omp = "Маніпурі, стара" +omq = "Отомангейська" +omr = "Маратхі, стара" +omt = "Омотік" omu = "Омурано" +omv = "Омотська" +omw = "Південна Тайрора" +omx = "Мон, стара" ona = "Она" onb = "Бе (он бе, во лімгао)" one = "Онейда" +ong = "Оло" +oni = "Онін" +onj = "Онджоб" +onk = "Каборе Оне" +onn = "Онобасулу" ono = "Онондага" +onp = "Сартанг" +onr = "Північна Оне" +ons = "Оно" +ont = "Онтену" +onu = "Унуа" onw = "Давньонубійська" +onx = "Піджин на основі Онін" ood = "Тохоно Оодхам" +oog = "Онг" oon = "Онге" oor = "Урламс, креольська" +oos = "Староосетинська" +opa = "Окпамгері" +opk = "Копкака" opm = "Оксапмін" +opo = "Опао" opt = "Опата" opy = "Офайє" ora = "Ороха" orc = "Орма" +ore = "Орехон" +org = "Орінг" orh = "Орокен" ori = "Орія" orm = "Орома" orn = "Канацька (канак, оранг-канак)" oro = "Ороколо" orr = "Орума" +ors = "Оранґ Селетар" +ort = "Адівасі Орія" oru = "Ормурі" orv = "Давньоруська" orw = "Оро-він" +orx = "Оро" +ory = "Одія" +orz = "Орму" osa = "Осейдж" osc = "Оскійська" +osi = "Осінгський діалект" +oso = "Ососо" osp = "Староіспанська" oss = "Осетинська" +ost = "Осату" +osu = "Оне, Південна" osx = "Старосаксонська" ota = "Османська" +otb = "Старотибетська" +otd = "От Данум" +ote = "Мескіталь Отомі" oti = "Оті" otk = "Старотурецька" +otl = "Отомі, Тілапа" otm = "Отомі східного нагір'я" +otn = "Отомі, Тенанго" oto = "Отомські мови" +otq = "Отомі, Керетаро" otr = "Оторо" +ots = "Естадо де Мехіко Отомі" +ott = "Отомі, Темоая" +otu = "Отуке" +otw = "Оттавський діалект" +otx = "Отомі, Техатепек" +oty = "Старотамільська" +otz = "Ікстенко Отомі" oua = "Уарглі (теггаргрент)" +oub = "Гліо-Убі" +oue = "Оуне" +oui = "Староуйгурська" +oum = "Оума" oun = "!О!унг" +owi = "Овініга" owl = "Давньоваллійська" +oyb = "Оі" oyd = "Ойда" +oym = "Ваямпі" +oyy = "Оя'оя" ozm = "Нзіме (кунзіме)" paa = "Папуаські мови" +pab = "Паресіс" pac = "Пако" pad = "Паумарі" +pae = "Пагібете" +paf = "Паранават" pag = "Панґасінанська" +pah = "Тенхарім" +pai = "Пе" +pak = "Паракана" pal = "Пехлеві" pam = "Пампанґо" pan = "Панджабі" @@ -2014,259 +5221,722 @@ pao = "Північна паюте" pap = "Пап'яменто" paq = "Пар’я" par = "Тимбіша (панамінт)" +pas = "Папасена" +pat = "Папіталай" pau = "Палауська" pav = "Пакаасновос" paw = "Пауні" +pax = "Панкараре" +pay = "Печ" +paz = "Панкарару" pbb = "Паес" pbc = "Патамона" +pbe = "Мезонтла Пополока" +pbf = "Койотепек Пополока" +pbg = "Параухано" pbh = "Панаре" +pbi = "Парква" +pbl = "Мак (Нігерія)" +pbn = "Кпасам" pbo = "Папель (ойум)" pbp = "Бадьяра" pbr = "Пангва" +pbs = "Центральна Паме" +pbt = "Пушту, південна" +pbu = "Пушту" +pbv = "Пнар" pby = "П'ю" +pca = "Пополока, Санта-Інес Ауатемпан" +pcb = "Пеар" pcc = "Буйєйська" pcd = "Пікардійська" +pce = "Палаунг, Рухінг" +pcf = "Паліян" +pcg = "Панія" +pch = "Пардхан" +pci = "Дурува" +pcj = "Паренга" +pck = "Чін, Пайте" +pcl = "Пардхі" pcm = "Нігерійський піджин" +pcn = "Піті" pcp = "Чакобо" +pcr = "Пананг" +pcw = "П'япун" +pda = "Анам" pdc = "Пенсильвансько-німецький діалект" +pdi = "Па Ді" +pdn = "Федан" +pdo = "Падое" pdt = "Німецько-платський діалект" +pdu = "Каян" +pea = "Індонезійська, Перанакан" peb = "Східне помо" +ped = "Мала (Папуа Нова Гвінея)" +pee = "Тадже" pef = "Помо, північно-східний" +peg = "Пенго" peh = "Бонанська (баоанська)" pei = "Чічімека-хонас" pej = "Помо, північний" +pek = "Пенчал" +pel = "Пекал" +pem = "Пхенде" peo = "Давньоперська" +pep = "Кунджа" peq = "Помо, південний" pes = "Перська (фарсі)" pev = "Пемонська" pex = "Петатс" pey = "Петьо" +pez = "Східна Пенан" +pfa = "Паафанг" +pfe = "Пеере" pfl = "Пфальцький діалект німецької" pga = "Південносуданський арабський піджин" +pgg = "Пангвалі" +pgi = "Пагі" pgk = "Ререп (панкуму, тісман)" pgl = "Первісна ірландська" +pgn = "Паелігнійська" +pgs = "Пангсенг" +pgu = "Пагу" pha = "Пахнг" +phd = "Пхудагі" +phg = "Фуонг" +phh = "Пхукха" phi = "Філіпінські мови" +phk = "Пхаке" phl = "Пхалура" +phm = "Пхімбі" phn = "Фінікійська" +pho = "Пхуной" +phq = "Пхана" phr = "Пахарі-потварі" +pht = "Пху Тай" phu = "Пхуанська" +phv = "Пахлавані" +phw = "Пхангдувалі" pia = "Піма баджо (нижньопіманська)" pib = "Йіне" +pic = "Пінджі" +pid = "Піароа" +pie = "Піро" pif = "Пінгелапеська" pig = "Пісабо (пісагуа, пісауа)" pih = "Піткернська" +pii = "Піні" pij = "Піхао" pil = "Йом (пілапіла)" pim = "Повхатан" +pin = "Піаме" pio = "П'япоко (піапоко)" +pip = "Перо" +pir = "Піратапуйо" pis = "Піджин Соломонових Островів" +pit = "Пітта Пітта" piu = "Пінтубі-лурітья" piv = "Пілені" +piw = "Пімбве" +pix = "Піу" +piy = "Пія-Квончі" +piz = "Пійе" +pjt = "Пітжантятьяра" +pka = "Джайністський пракрит" +pkb = "Кіпфокомо" +pkc = "Пекче" +pkg = "Пак-Тонг" +pkh = "Панкху" +pkn = "Паканха" +pko = "Покот" pkp = "Пукапукан" +pkr = "Аттападі Курумба" +pks = "Пакистанська жестова мова" +pkt = "Маленг" +pku = "Паку" +pla = "Міані" +plb = "Полономбаук" +plc = "Центральна Палавано" pld = "Поларі" ple = "Палуе" +plf = "Центральна малайсько-полінезійська" plg = "Пілага" +plh = "Паулохі" pli = "Палі" +plj = "Полчі" +plk = "Кохістані Шина" +pll = "Палаунг, Шве" pln = "Паленкеро (паленке)" +plo = "Олута Пополуца" +plp = "Палпа" plq = "Палайська" plr = "Палака" +pls = "Пополока, Сан-Маркос Тлалькоялко" +plt = "Малагасійська, Плоскогір'я" plu = "Палікур" +plv = "Палавано, південно-західна" +plw = "Брукс-Пойнт Палавано" +ply = "Болью" +plz = "Палуан" pma = "Паамська (паама)" pmb = "Памбія" +pmc = "Палумата" +pmd = "Палланганмідданг" +pme = "Пваамей" +pmf = "Памона" pmh = "Махараштрі" +pmi = "Північна пумі" +pmj = "Пумі, Південна" +pmk = "Памліко" pml = "Лінгва франка" pmm = "Помо (помоанська)" +pmn = "Пам" +pmo = "Пом" +pmq = "Північна Паме" +pmr = "Пейнамар" pms = "П'ємонтська" pmt = "Туамотуан (Паумоту)" +pmu = "Мірпур панджабі" +pmw = "Мівок, Рівнини" +pmx = "Нага, Пумей" +pmy = "Малайська, папуаська" +pmz = "Паме, Південна" +pna = "Пунан Бах-Біау" pnb = "Пенджабська, західна" +pnc = "Панней" +pne = "Пенан, західна" png = "Понгу (рін)" pnh = "Пенрінська (тонгарева)" +pni = "Аохенг" +pnj = "Пінджаруп" pnk = "Паунака (пауна)" +pnm = "Пунан Бату 1" pnn = "Хагахай (пінай)" +pno = "Панобо" +pnp = "Панкана" +pnq = "Пана (Буркіна-Фасо)" +pnr = "Панім" +pns = "Поносакан" pnt = "Понтійська" +pnu = "Буну, Джіоннай" +pnv = "Пінігура" +pnw = "Панитийма" +pnx = "Фонг-Княнг" +pny = "Піньїнь" +pnz = "Пана (Центральноафриканська Республіка)" poc = "Покомам" +pod = "Понарес" +poe = "Пополока, Сан-Хуан-Ацінго" +pof = "Поке" pog = "Потігуара" poh = "Покомчі" +poi = "Високогірна Пополука" +pok = "Поканга" pol = "Польська" pom = "Помо, південносхідна" pon = "Понапе" poo = "Помо, центральна" +pop = "Пвапва" +poq = "Пополука, Тексістепек" por = "Португальська" +pos = "Пополука, Саюла" pot = "Потаватомі" pov = "Кіріол (кріоуло), Верхня Гвінея" +pow = "Пополока, Сан-Феліпе Отлальтепек" pox = "Полабська" poy = "Погоро (поголо)" +poz = "Малайсько-полінезійська" +ppa = "Пао" +ppe = "Папі" ppi = "Пайпай" +ppk = "Ума" ppl = "Науат (піпіль)" +ppm = "Папума" +ppn = "Папапана" +ppo = "Фолопа" +ppp = "Пеленде" +ppq = "Пей" +ppr = "Піру" +pps = "Пополока, Сан-Луїс Темалакаюка" +ppt = "Паре" ppu = "Папора" +pqa = "Па'а" +pqe = "Східна Малайсько-Полінезійська" pqm = "Малісіт-Пассамакводді" +pqw = "Західна малайсько-полінезійська" pra = "Пракрити" +prb = "Луа'" prc = "Парачі" +prd = "Парсі-Дарі" pre = "Принсипійська говірка" +prf = "Паранан" prg = "Пруcська" +prh = "Пороханон" pri = "Паїсі" +prk = "Параук" prl = "Перуанська жестова мова" prm = "Кібірі (пороме)" prn = "Прасун (васі-варі, прасуні)" pro = "Давньопровансальська" +prp = "Парсі" prq = "Ашенінка перене" +prr = "Пурі" +prs = "Дарі" +prt = "Пхай" +pru = "Пурагі" +prw = "Паравен" +prx = "Пурік" +pry = "Прай" prz = "Жестова мова острова Провіденс" +psa = "Асуе Ав'ю" +psc = "Перська жестова мова" psd = "Жестова мова північноамериканських індіанців" +pse = "Центральна малайська" psg = "Жестова мова Пенанг" +psh = "Пашаї, південно-західна" +psi = "Пашаї, південно-східна" +psl = "Пуерториканська жестова мова" +psm = "Паузерна" +psn = "Панасуан" pso = "Польська жестова мова" psp = "Філіппінська жестова мова" +psq = "Пасі" psr = "Португальська жестова мова" pss = "Каулонг (пасісмануа)" +pst = "Центральна пушту" psu = "Пракрит, шаурасені" +psw = "Порт Сандвіч" +psy = "Піскатавей" +pta = "Пай Тавітера" +pth = "Патаксо Га-Ха-Ха-Хе" +pti = "Пінтііні" +ptn = "Патані" +pto = "Зо'е" +ptp = "Патеп" +ptr = "П'ямаціна" +ptt = "Енреканг" +ptu = "Бамбам" +ptv = "Порт Вато" +ptw = "Пентлач" +pty = "Патія" +pua = "Пурепеча, Західне нагір'я" +pub = "Пурум" +puc = "Пунан Мерап" +pud = "Пунан Апут" pue = "Пуельче" +puf = "Пунан Мерах" +pug = "Фуї" pui = "Пуйнаве" +puj = "Пунан Тубу" +puk = "Пу Ко" pum = "Пума" +puo = "Пуок" +pup = "Пулабу" puq = "Пукіна" +pur = "Пурубора" pus = "Пушту" +put = "Путох" puu = "Іпуну (ісіра)" +puw = "Пулуватесе" +pux = "Пуаре" +puy = "Пурісіменьо" +puz = "Нага, Пурум" +pwa = "Павайя" +pwb = "Панава" +pwg = "Гапапайва" +pwi = "Патвін" +pwm = "Молбог" pwn = "Пайванська" +pwo = "Карен, Пво Вестерн" +pwr = "Поварі" +pww = "Карен, Пво Північна" +pxm = "Міксе, Кецальтепек" +pye = "Крумен, Пай" +pym = "Фьям" +pyn = "Поянава" +pys = "Ленгва де Сеньяс дель Парагвай" pyu = "Пуюма (пінуюмаян)" pyx = "П'ю (М'янма)" +pyy = "П'єн" +pzn = "Нага, Пара" qot = "Сахаптін" +qua = "Куапау" +qub = "Уаллага Уануко Кечуа" quc = "Кіче" +qud = "Кальдерон Хайленд Кічуа" que = "Кечуа" quf = "Ламбаекенська кечуа" +qug = "Чимборасо Хайленд Кічуа" quh = "Південно-болівійська кечуа" qui = "Квілет (кілеут)" quk = "Чачапояська кечуа" +qul = "Північноболівійська кечуа" qum = "Сипакапенська" +qun = "Квіно" +qup = "Кечуа, Південна Пастаса" +quq = "Квінкі" +qur = "Кечуа, Янауанка Паско" qus = "Аргентинська кечуа" quv = "Сакапултек" +quw = "Кічуа, низовина Тена" +qux = "Кечуа, Яуйос" quy = "Аякучанська кечуа" quz = "Кечуа" +qva = "Амбо-Паско Кечуа" qvc = "Кахамарканська кечуа" +qve = "Східний Апурімак Кечуа" +qvh = "Уамалієс-Дос-де-Майо Уануко Кечуа" +qvi = "Імбабура Хайленд Кічуа" +qvj = "Лоха Хайленд Кічуа" +qvl = "Кахатамбо Північна Ліма Кечуа" +qvm = "Маргос-Яровілка-Лаурікоча Кечуа" +qvn = "Північний Хунін Кечуа" +qvo = "Кечуа низини Напо" qvp = "Пакараоська кечуа" qvs = "Ламаська кечуа" +qvw = "Уайлла Ванка Кечуа" +qvy = "Чойо" +qvz = "Північна Пастаса Кічуа" +qwa = "Коронго Анкаш Кечуа" +qwc = "Класична кечуа" +qwe = "Кечуанська" +qwh = "Хуайлас Анкаш Кечуа" qwm = "Половецька" +qws = "Кечуа, Сіхуас Анкаш" +qwt = "Квалхіоква-Тлацканай" +qxa = "Чикіан Анкаш Кечуа" +qxc = "Чінча кечуа" +qxh = "Панао Уануко Кечуа" +qxl = "Кічуа, нагір'я Саласака" +qxn = "Північна Кончуко Анкаш Кечуа" +qxo = "Кечуа, Південна Кончуко Анкаш" qxp = "Кечуа Куско-Кольяо" qxq = "Кашкайська" +qxr = "Каньяр Хайленд Кічуа" +qxs = "Цян, Південна" +qxt = "Кечуа, Санта-Ана-де-Тусі Паско" +qxu = "Арекіпа - Уніон Кечуа" +qxw = "Джауджа Ванка Кечуа" qya = "Квенья" qyp = "Квіріпі" +raa = "Дунгмалі" rab = "Чамлінг" +rac = "Расава" +rad = "Раде" +raf = "Меоханг, Західна" +rag = "Логуолі" +rah = "Рабха" +rai = "Рамоаайна" raj = "Раджастханська" +rak = "Тулу-Бохуай" +ral = "Ралте" ram = "Канела" +ran = "Ріантана" +rao = "Рао" rap = "Рапануйська" +raq = "Саам" rar = "Раротонга" +ras = "Тегалі" +rat = "Разаджерді" +rau = "Раут" +rav = "Сампанг" +raw = "Раванг" +rax = "Ранг" ray = "Рапа" +raz = "Рахамбуу" +rbb = "Палаунг, Румай" +rbk = "Бонток, Північна" +rbl = "Бікол, Мірая" +rbp = "Барабабараба" rcf = "Реюньйонська креольська" +rdb = "Рудбарі" +rea = "Рерау" +reb = "Рембонг" +ree = "Реджанг Каян" +reg = "Кара" +rei = "Релі" rej = "Реджанг" rel = "Ренділле" +rem = "Ремо" +ren = "Ренгао" +rer = "Рер Баре" res = "Реше" +ret = "Ретта" rey = "Рейесано (чірігуа)" +rga = "Рорія" rge = "Романо-грецька" +rgk = "Рангкас" rgn = "Романьоль" +rgr = "Резігаро" +rgs = "Роглай, Південна" +rgu = "Рінггоу" rhg = "Рогінджа" +rhp = "Яхан" +ria = "Ріанг (Індія)" +rie = "Рієн" rif = "Таріфітська берберська (ріфська)" +ril = "Ріанг (М'янма)" rim = "Туру (ньятуру, кіньятуру)" +rin = "Нунгу" +rir = "Рібун" +rit = "Рітарунго" +riu = "Ріунг" +rjg = "Раджонг" +rji = "Раджі" +rjs = "Раджбанші" +rka = "Краол" rkb = "Рікбакца" rkh = "Ракаханга-маніхікі" +rki = "Ракхайн" rkm = "Марка" rkt = "Камта" +rkw = "Араквал" +rma = "Рама" +rmb = "Рембарунга" +rmc = "Карпатська ромська" +rmd = "Данська, Мандрівнича" rme = "Англо-романська" rmf = "Фінська кало" rmg = "Норвежська ромська" +rmh = "Муркім" rmi = "Ломаврен" +rmk = "Ромкун" +rml = "Балтійська ромська" rmm = "Рома (романг)" rmn = "Балканоромська" rmo = "Сінті" +rmp = "Ремпі" rmq = "Кало" +rms = "Румунська жестова мова" rmt = "Домарі" rmu = "Скандинавсько-циганська" +rmv = "Романова" rmw = "Валлійсько-циганська" +rmx = "Ромам" rmy = "Волоський (влашський) діалект циганської" +rmz = "Марма" +rna = "Руна" +rnd = "Руунд" rng = "Ронга" +rnl = "Ранглонг" +rnn = "Рун" +rnp = "Ронгпо" +rnr = "Нарі Нарі" +rnw = "Рунгва" roa = "Романські мови" +rob = "Tae'" +roc = "Каджія Роглай" +rod = "Рого" +roe = "Ронджі" rof = "Ромбо" +rog = "Північна Роглай" roh = "Ретороманська" rol = "Ромбломанон (бісая/бінісая нга ромбломанон)" rom = "Циганська" ron = "Румунська/Молодовська" +roo = "Ротокас" rop = "Австралійський кріоль" +ror = "Ронгга" +rou = "Рунга" +row = "Дела-Оенале" +rpn = "Репанбітіп" +rpt = "Рептінг" +rri = "Ріріо" rro = "Вайма" +rrt = "Аррітіннгіт" +rsb = "Романо-сербська" +rsi = "Реннелльська жестова мова" rsl = "Російська жестова мова" +rtc = "Чін, Рунгту" +rth = "Ратахан" rtm = "Ротуманська" +rtw = "Ратхаві" +rub = "Гунгу" +ruc = "Руулі" rue = "Русинська" ruf = "Лугуру" rug = "Ровіана" +ruh = "Руга" +rui = "Руфіджі" +ruk = "Че" run = "Кірундійська" ruo = "Істрорумунська" rup = "Арумунська" ruq = "Мегленорумунська" rus = "Російська" rut = "Рутульська" +ruu = "Ланас Лобу" +ruy = "Мала (Нігерія)" +ruz = "Рума" +rwa = "Раво" rwk = "Рва" rwm = "Амба (Уганда)" +rwo = "Рава" +rwr = "Марварі (Індія)" +rxd = "Нгарді" +rxw = "Карувалі" ryn = "Північно-осімський діалект" rys = "Яеяма" ryu = "Центральноокінавська" +saa = "Саба" +sab = "Буглере" +sac = "Месквакі" sad = "Сандаве" +sae = "Сабане" +saf = "Сафаліба" sag = "Санго" sah = "Якутська" sai = "Мови південноамериканських індіанців" +saj = "Саху" +sak = "Саке" sal = "Саліські мови" sam = "Самаритянська арамейська" san = "Санскрит" +sao = "Саусе" +sap = "Санапана" saq = "Самбуру" +sar = "Саравека" sas = "Сасакська" sat = "Сантальська" +sau = "Салеман" sav = "Сафене (сафі, саафі-саафі)" saw = "Саві" sax = "Са (саа)" +say = "Сая" saz = "Саураштра" sba = "Нгамбай" +sbb = "Сімбо" sbc = "Келе (геле)" +sbd = "Само, Південна" +sbe = "Саліба" sbf = "Шабо" +sbg = "Сегет" +sbh = "Сорі-Харенган" +sbi = "Сеті" +sbj = "Сурбахал" +sbk = "Сафва" +sbl = "Ботолан Самбал" +sbm = "Сагала" +sbn = "Бхіл, Сіндхі" +sbo = "Сабум" sbp = "Сангу" +sbq = "Сілейбі" +sbr = "Сембакунг Мурут" +sbs = "Субія" +sbt = "Кімкі" +sbu = "Бхоті, Стод" sbv = "Сабіни" +sbw = "Сімба" +sbx = "Себеруанг" +sby = "Солі" +sbz = "Сара Каба" scb = "Тьит" sce = "Дунсянська (широнгол-монгольська, санта, саньта)" +scf = "Креольська французька, Сан-Мігель" scg = "Санггау" +sch = "Сакачеп" +sci = "Креольська малайська, шрі-ланкійська" +sck = "Садрі" scl = "Шина" scn = "Сицилійська" sco = "Шотландська германська" +scp = "Хеламбу Шерпа" +scq = "Са'оч" +scs = "Північна Слейві" +scu = "Шумчо" +scv = "Шені" +scw = "Ша" scx = "Сікулійська" +sda = "Тораджа-Са'дан" sdb = "Шабакі" sdc = "Сассарська, Сардинія" +sde = "Сурубу" +sdf = "Сарлі" +sdg = "Саві" sdh = "Південнокурдська" +sdj = "Суунді" +sdk = "Сос Кунді" sdl = "Саудівська жестова мова" +sdm = "Семанданг" sdn = "Галлурський діалект, Сардинія" +sdo = "Бідаюх, Букар-Садунг" +sdp = "Шердукпен" +sdr = "Ораон Садрі" sds = "Сенед" sdt = "Єврейсько-провансальський діалект" +sdu = "Саруду" +sdv = "Східносуданська" +sdx = "Меланау, Сібу" sdz = "Саланда" sea = "Семаї" +seb = "Сенуфо, Шемпір" +sec = "Сешелт" +sed = "Седанг" see = "Сенека" +sef = "Себаара Сенуфо" +seg = "Сегеджу" seh = "Сена" sei = "Сері" +sej = "Сене" +sek = "Секані" sel = "Селькупська" sem = "Семітські мови" +sen = "Нанеріге Сенуфо" +seo = "Суармін" +sep = "Сенуфо, Сусіте" +seq = "Сенуфо, Сенара" ser = "Серрано" ses = "Койраборо сені" set = "Сентані (буяка)" +seu = "Серуї-Лаут" +sev = "Ньярафоло Сенуфо" +sew = "Мова Затоки Сева" sey = "Секойя (секоя)" +sez = "Чін, Сентханг" +sfb = "Французька бельгійська жестова мова" +sfe = "Східна Субанен" +sfm = "Гха-му" sfs = "Південноафриканська жестова мова" +sfw = "Сехві" sga = "Давньоірландська" +sgb = "Айта, Маг-анці" sgc = "Кіпсігі (кіпсігіс)" sgd = "Сурігаонон" +sge = "Сегай" +sgg = "Швейцарсько-німецька жестова мова" sgh = "Шугнанська" +sgi = "Суга" +sgj = "Сургуджія" +sgk = "Санконг" +sgm = "Сінга" sgn = "Жестові мови" +sgo = "Сонга" +sgp = "Сінгпхо" +sgr = "Сангісарі" sgs = "Жмудська" +sgt = "Брокпейк" +sgu = "Салас" sgw = "Себат-бет гураге" +sgx = "Жестова мова Сьєрра-Леоне" sgy = "Сангліцька" sgz = "Сурсурунга" +sha = "Шалл-Зволл" shb = "Нінам (янам)" +shc = "Зонде" +shd = "Кундал Шахі" she = "Шеко" shg = "Шуа" shh = "Шошоні" shi = "Тачеліт" +shj = "Шатт" shk = "Шилук" +shl = "Шенду" +shm = "Шахруді" shn = "Шанська" +sho = "Шанга" shp = "Шипібо-конібо" +shq = "Сала" +shr = "Ші" shs = "Шусвап" sht = "Шаста" shu = "Чадійська арабська" @@ -2276,283 +5946,808 @@ shx = "Ше" shy = "Шавія" shz = "Сенуфо, с'єнара" sia = "Аккала-саамська" +sib = "Себоп" sid = "Сідамо" +sie = "Сімаа" sif = "Сіаму (семе)" +sig = "Паасаал" +sih = "Зіре (Сіше)" sii = "Шомпенська" sij = "Нумбамі" +sik = "Сікіана" +sil = "Тумулунг Сісаала" +sim = "Менде (Папуа Нова Гвінея)" sin = "Сингальська" sio = "Сіуанські мови" sip = "Сіккімська (бхутія)" +siq = "Соня" +sir = "Сірі" sis = "Саюсло" sit = "Сино-тибетські мови" +siu = "Сінаген" +siv = "Сумаріуп" +siw = "Сівай" +six = "Сумау" siy = "Сівенді" siz = "Сиванська" +sja = "Епена" +sjb = "Саджау Басап" sjd = "Кільдинська саамська" sje = "Піт-саамська (ар'єплозька саамська)" sjg = "Сунґор" sjk = "Кемі-саамська" +sjl = "Міджі" +sjm = "Мапун" sjn = "Синдарин (синдарська, сіроельфійська)" sjo = "Сібе" +sjp = "Сурджапурі" +sjr = "Сіар-Лак" sjs = "Сенхаджа" sjt = "Терська саамська" sju = "Уме саамі" sjw = "Шоні" +ska = "Скагіт" skb = "Сак" +skc = "Ма Манда" skd = "Південна Сьєрра-Мівок" +ske = "Секе (Вануату)" +skf = "Сакірабіа" +skg = "Малагасійська, Сакалава" skh = "Сігулайська" ski = "Сікка" +skj = "Секе (Непал)" +skk = "Сок" +skm = "Кутонг" +skn = "Колібуган Субанон" +sko = "Секо Тенгах" +skp = "Секапан" +skq = "Сінінкере" +skr = "Сераіки" +sks = "Майя" skt = "Саката" sku = "Сакао" +skv = "Скоу" skw = "Скепі" +skx = "Секо Паданг" +sky = "Сікаяна" +skz = "Секар" sla = "Слов'янські мови" slc = "Саліба" +sld = "Сіссала" +sle = "Шолага" +slf = "Швейцарсько-італійська жестова мова" +slg = "Селунгай Мурут" +slh = "Саліш, Південний Пьюджет-Саунд" sli = "Сілезький діалект німецької" +slj = "Салума" slk = "Словацька" +sll = "Солт_Юї" +slm = "Пангутаран Сама" sln = "Салінанська" +slp = "Ламахолот" slq = "Салчукська (сельджукська)" slr = "Саларська" sls = "Сінгапурська жестова мова" +slt = "Сіла" +slu = "Селару" slv = "Словенська" +slw = "Сіалум" +slx = "Салампасу" +sly = "Селаяр" +slz = "Ма'йя" sma = "Саамська південна" +smb = "Сімбарі" +smc = "Сом" +smd = "Сама" sme = "Північносаамська" +smf = "Оуве" +smg = "Сімбалі" +smh = "Самей" smi = "Саамські мови" smj = "Саамська луле" smk = "Болінао (бінуболінао)" +sml = "Центральна Сама" +smm = "Мусаса" smn = "Саамська інарі" smo = "Самоанська" +smp = "Самаритянська" +smq = "Само" smr = "Сімалурська (сімеулу)" sms = "Саамська скольт" +smt = "Сімте" +smu = "Сомрай" +smv = "Самведі" smw = "Сумбава (сумбаваресе)" +smx = "Самба" +smy = "Семнані" +smz = "Сімеку" sna = "Шона" +snb = "Себуяу" +snc = "Сінаугоро" snd = "Сіндхі" +sne = "Бау Бідаюх" snf = "Ноон" +sng = "Санга (Демократична Республіка Конго)" +snh = "Шінабо" +sni = "Сенсі" +snj = "Ріверен Санго" snk = "Сонінке" +snl = "Сангіл" +snm = "Ма'ді, Південна" +snn = "Сіона" +sno = "Снохоміш" +snp = "Сіане" snq = "Сангу (Габон)" +snr = "Сіхан" +sns = "Нахавак" +snu = "Сенггі" +snv = "Са'бан" snw = "Сантрокофі (селее)" +snx = "Сам" +sny = "Санійо-Хієве" +snz = "Синсауру" +soa = "Тай Сонг" sob = "Собей" +soc = "Со (Демократична Республіка Конго)" +sod = "Сонгура" +soe = "Сонгомено" sog = "Согдійська" soh = "Ака" +soi = "Сонха" +soj = "Сої" +sok = "Сокоро" +sol = "Солос" som = "Сомалійська мова" son = "Сонгайські мови" +soo = "Сонго" sop = "Сонге" +soq = "Канасі" sor = "Сомрай" sos = "Сембла" sot = "Сесото" sou = "Південнотайська" sov = "Сонсорольська" +sow = "Сованда" +sox = "Сво" +soy = "Мійобе (соруба)" +soz = "Темі" spa = "Іспанська" +spb = "Сепа (Індонезія)" spc = "Сапе" +spd = "Саеп" +spe = "Сепа" +spg = "Сіан" +spi = "Сапоні" +spk = "Сенго" +spl = "Селепет" +spm = "Акукем" +spo = "Спокане" +spp = "Сенуфо, Суп'їре" +spq = "Лорето-Укаялі іспанська" +spr = "Сапаруа" +sps = "Сапоса" +spt = "Бхоті, Спіті" +spu = "Сапуанська" +spv = "Кослі" spx = "Південнопіценська" +spy = "Сабаот" +sqa = "Шама-Самбуга" +sqh = "Шау" sqi = "Албанська" +sqk = "Албанська жестова мова" +sqm = "Сума" sqn = "Саскуеганнок" +sqo = "Сорхей" +sqq = "Соу" sqr = "Сицилійська арабська" sqs = "Жестова мова Шрі-Ланки" sqt = "Сокотрійська (сокотрі)" squ = "Сквоміш (скоміш)" +sra = "Саруга" srb = "Сора" src = "Логудорський діалект" srd = "Сардинська" sre = "Сара" +srf = "Нафі" +srg = "Сулод" srh = "Сарикольська" +sri = "Сіріано" +srk = "Серудунг Мурут" srl = "Ісірава" srm = "Сарамакканська" srn = "Сранан-тонго" sro = "Кампіданська сардинська" srp = "Сербська" +srq = "Сіріоно" srr = "Серер" srs = "Сарсі" +srt = "Саурі" +sru = "Суруі" +srv = "Південний Сорсоганон" +srw = "Серуа" +srx = "Сірмаурі" +sry = "Сера" +srz = "Шахмірзаді" ssa = "Ніло-сахарські мови" +ssb = "Сама, Південна" +ssc = "Суба-Сімбіті" +ssd = "Сирої" +sse = "Балангінгі" ssf = "Тхао" ssg = "Сеймат" ssh = "Арабська, шиххі" +ssi = "Сансі" +ssj = "Саусі" +ssk = "Сунам" +ssl = "Сісаала, Західна" +ssm = "Семнам" +ssn = "Ваата" +sso = "Сіссано" ssp = "Іспанська жестова мова" +ssq = "Со'а" +ssr = "Швейцарсько-французька жестова мова" sss = "Со" +sst = "Сінасіна" +ssu = "Сусуамі" +ssv = "Нген (Шарк Бей)" ssw = "Сваті" +ssx = "Самберігі" ssy = "Сахо" +ssz = "Сенгсенг" +sta = "Сеттла" +stb = "Північна Субанен" std = "Сентінельська" +ste = "Ліана-Сеті" +stf = "Сета" +stg = "Трієнг" sth = "Шелта (ґаммон, кант)" +sti = "Було Стіенг" +stj = "Матья Само" +stk = "Араммба" stl = "Стеллінгверфський діалект" +stm = "Сетаман" stn = "Ова" +sto = "Стоні" +stp = "Південно-східна Тепехуан" stq = "Затерландська фризька" str = "Саліш північної протоки (північний стрейтс, нортерн-стрейтс)" sts = "Шумашті" +stt = "Будех Стіенг" +stu = "Самтао" +stv = "Сілт'е" +stw = "Сатавалезе" sua = "Сулка" +sub = "Суку" +suc = "Субанон, західна" +sue = "Суена" +sug = "Суганга" sui = "Сукі" +suj = "Шубі" suk = "Сукума" sun = "Сунданська" +suq = "Сурі" +sur = "Мвагавул" sus = "Сусу" sut = "Субтіаба" +suv = "Пуроїк" +suw = "Сумбва" sux = "Шумерська" +suy = "Суйя" suz = "Сунвар" sva = "Сванська" +svb = "Улау-Суайн" +svc = "Креольська англійська, вінкентійська" +sve = "Серілі" +svk = "Словацька жестова мова" +svm = "Славомолісано" svr = "Савара" svs = "Савосаво" +svx = "Скальвіан" swa = "Суахілі" swb = "Коморська" swc = "Конгійське суахілі" swe = "Шведська" +swf = "Сере" swg = "Швабський діалект" swh = "Суахілі" swi = "Шуйська" +swj = "Сіра" +swk = "Малаві Сена" swl = "Шведська жестова мова" +swm = "Самоса" swn = "Сокна" +swo = "Шаненава" +swp = "Суау" +swq = "Шарва" +swr = "Саверу" +sws = "Селувасан" +swt = "Савіла" +swu = "Сувава" swv = "Шекхаваті" +sww = "Сова" swx = "Суруаха" swy = "Саруа" +sxb = "Суба" sxc = "Сікани" +sxe = "Сігу" +sxg = "Шисін" sxk = "Калапуянська, південна" sxl = "Селонська" +sxm = "Самре" sxn = "Сангірська" sxo = "Соротаптична" sxr = "Саароа (лхаалуа)" +sxs = "Сасару" sxu = "Верхньосаксонська" +sxw = "Гбе, Саксве" +sya = "Сіанг" +syb = "Центральна Субанен" syc = "Сирійська класична" +syd = "Самоїдська" +syi = "Секі" syk = "Сукур" syl = "Силхетська" +sym = "Майя Само" +syn = "Сенайя" +syo = "Суой" syr = "Сирійська" sys = "Сіньяр" +syw = "Кагате" syy = "Бедуїнська жестова мова Аль-Саїд" sza = "Семелай" +szb = "Нгалум" +szc = "Семак Бері" +szd = "Серу" +sze = "Сезе" +szg = "Сенгеле" szl = "Сілезька" +szn = "Сула" +szp = "Суабо" +szv = "Ісу (округ Фако)" szw = "Саваї (савай)" +taa = "Нижня Танана" tab = "Табасаранська" +tac = "Мова низовини Тарахумара" +tad = "Таусе" +tae = "Таріана" +taf = "Тапірапе" tag = "Тагой" tah = "Таїтянська" tai = "Тайські мови" +taj = "Східна Таманг" +tak = "Тала" +tal = "Тал" tam = "Тамільська" +tan = "Тангале" tao = "Ямі (тао)" +tap = "Таабва" +taq = "Тамашек" tar = "Тараумара, центральна" tas = "Тай Бой (в’єтнамська піджин-французька)" tat = "Татарська" tau = "Верхня танана" +tav = "Татуйо" +taw = "Тай" +tax = "Тамкі" tay = "Атаял" +taz = "Точо" tba = "Айкана" +tbb = "Тапеба" +tbc = "Такія" +tbd = "Какі Ае" +tbe = "Танімбілі" tbf = "Мандара (табар)" +tbg = "Північна Тайрора" +tbh = "Туравал" +tbi = "Гаам" +tbj = "Тянг" +tbk = "Каламіан Тагбанва" +tbl = "Тболі" +tbm = "Тагбу" +tbn = "Барро Негро Тунебо" +tbo = "Тавала" +tbp = "Таворта (дібруд)" +tbq = "Тибето-Бірманська" +tbr = "Тумтум" +tbs = "Тангуат" +tbt = "Тембо (Кітембо)" tbu = "Тубар" +tbv = "Тобо" +tbw = "Тагбанва" +tbx = "Капін" +tby = "Табару" tbz = "Таммарі" tca = "Тікуна" tcb = "Танакросс" +tcc = "Датуга" +tcd = "Тафі" +tce = "Південна Тутчон" +tcf = "Малінальтепек Ме'фаа" +tcg = "Тамагаріо" +tch = "Креольська англійська мови Теркс і Кайкос" +tci = "Вара" +tck = "Читчеге" +tcl = "Таман (М'янма)" tcm = "Танах Мерах" +tcn = "Тічуронг" +tco = "Таунґьо" +tcp = "Чін, Тавр" +tcq = "Каї (кай, таорі-кей)" +tcs = "Креольська, Торресова протока" tct = "Тень" +tcu = "Південно-східна Тарахумара" +tcw = "Текпатлан Тотонак" tcx = "Тода" tcy = "Тулу" +tcz = "Тадо Чін" +tda = "Тагдал" +tdb = "Панчпарганія" +tdc = "Ембера-Тадо" tdd = "Тай Нуеа (Тай Нюа)" +tde = "Догон, Тираніге Діга" +tdf = "Таліенг" +tdg = "Таманг, Західна" tdh = "Тхулунґ" +tdi = "Томадіно" +tdj = "Таджіо" +tdk = "Тамбас" +tdl = "Сур" +tdn = "Тондано" +tdo = "Теме" +tdq = "Тіта" +tdr = "Тодрах" +tds = "Дутай" +tdt = "Тетун Ділі" +tdu = "Дусун, Темпасук" +tdv = "Торо" +tdx = "Малагасійська, Тандрой-Махафалі" +tdy = "Тадьяван" +tea = "Теміар" +teb = "Тетете" +tec = "Терік" +ted = "Крумен, Тепо" +tee = "Уеуетла Тепехуа" +tef = "Тереса" +teg = "Теке-Теге" teh = "Теуелче" +tei = "Торрічеллі" +tek = "Ібалі Теке" tel = "Телугу" tem = "Темне" ten = "Тама (Колумбія)" teo = "Тесо" tep = "Тепекано" +teq = "Темейн" ter = "Терено" +tes = "Тенґґер" tet = "Тетум" +teu = "Соо" +tev = "Теор" tew = "Тева" +tex = "Теннет" +tey = "Туліші" +tfi = "Гбе, Тофін" tfn = "Танаіна (денаіна)" +tfo = "Тефаро" +tfr = "Терібе" tft = "Терната" +tga = "Сагалла" +tgb = "Тобілунг" +tgc = "Тігак" +tgd = "Цагу" +tge = "Східна Горкха Таманг" +tgf = "Чалі" +tgg = "Тангга" +tgh = "Креольська англійська, тобагонська" +tgi = "Лавунуя (піва)" +tgj = "Тагін" tgk = "Таджицька" tgl = "Тагалог" +tgn = "Тандаганон" +tgo = "Судест" tgp = "Тангоа" +tgq = "Трінг" +tgr = "Таренг" +tgs = "Нуме" +tgt = "Центральна Тагбанва" +tgu = "Тангу" tgv = "Тінгі-бото" tgw = "Тагвана (тагбана)" tgx = "Тагіш" +tgy = "Тогойо" +tgz = "Тагалака" tha = "Тайська" +thc = "Тай Ханг Тонг" +thd = "Куук таайорре" +the = "Чітванія Тару" +thf = "Тхангмі" +thh = "Північна Тарахумара" +thi = "Тай Лонг" +thk = "Кітарака" +thl = "Дангаура Тару" thm = "Тхавинг (ахеу)" +thn = "Тхачанадан" thp = "Томпсон" +thq = "Кочіла Тару" +thr = "Рана Тару" +ths = "Тхакалі" tht = "Талтан (тахлтан)" +thu = "Турі" thv = "Тамахак (північнотуарегська)" +thw = "Тудам" +thx = "Тхе" +thy = "Тха" thz = "Аїр (кель аїр, таірт, таярт)" +tia = "Тамазігхт, Тідікельт" +tic = "Тіра" +tid = "Тідонг" tif = "Тіфаль" tig = "Тигре" +tih = "Тімугон Мурут" +tii = "Тієне (тенде)" +tij = "Тілунг" +tik = "Тікар" +til = "Тілламук" +tim = "Тімбе" tin = "Тиндинська" +tio = "Теоп" +tip = "Тримуріс" +tiq = "Тьєфо" tir = "Тигрінья" +tis = "Ітнег, Масадііт" tit = "Тінігуа" +tiu = "Адасен" tiv = "Тів" tiw = "Тіві" +tix = "Південна Тива" +tiy = "Тірурай" +tiz = "Тай Хунцзін" +tja = "Таджуасон" +tjg = "Тунджунг" +tji = "Північна Туцзя" +tjl = "Тай Лейн" tjm = "Тімукуа (тімуква)" +tjn = "Тонджон" +tjo = "Уед-Райт Бербер" +tjs = "Південна Туцзя" +tju = "Журруру" +tjw = "Джабвуррунґ" tka = "Трука" +tkb = "Букса" tkd = "Токодеде (тукудеде)" +tke = "Такване" +tkf = "Тукуманфед" +tkg = "Малагасійська, Тесака" tkl = "Токелау" tkm = "Такелма (такельма)" +tkn = "Току-Но-Шіма" tkp = "Тікопія" tkq = "Тее (тай)" tkr = "Цахурська" tks = "Такестані (такістані, таті південний)" +tkt = "Каторія Тару" tku = "Верхньонекаханська тотонакська" tkw = "Теану" +tkx = "Танґко" +tkz = "Такуа" +tla = "Південно-західна Тепеуан" tlb = "Тобело" tlc = "Йекуатланська тотонакська" +tld = "Талауд" tlf = "Телефоль" tlg = "Тофамна" tlh = "Клінгонська" tli = "Тлінгіт" +tlj = "Талінга-Бвісі" +tlk = "Талокі" tll = "Тетела (сунгу)" tlm = "Толомако" +tln = "Талондо'" +tlo = "Талоді" +tlp = "Філомена Мата-Коауітлан Тотонак" +tlq = "Тай Лой" +tlr = "Талісе" +tls = "Тамботало" +tlt = "Телуті" +tlu = "Тулеху" +tlv = "Таліабу" tlx = "Кхехек" tly = "Талиська" +tma = "Тама (Чад)" +tmb = "Авава" tmc = "Тумак" tmd = "Харуаї" tme = "Тремембе" +tmf = "Тоба-Маской" +tmg = "Португіс (Тернатеньо)" tmh = "Тамашек" +tmi = "Тутуба" tmj = "Самарокена" +tmk = "Північно-західна Таманг" +tml = "Читак (каунак)" +tmm = "Тай Тхань" +tmn = "Таман (Індонезія)" +tmo = "Темок" +tmp = "Тай Мене" +tmq = "Тумлео" tmr = "Вавилонська іудейсько-арамейська, (прибл. 200-1200 н.е.)" +tms = "Тіма" +tmt = "Тасмате" +tmu = "Іау (туру)" +tmv = "Тембо (Мотембо)" tmw = "Темуанська" +tmy = "Тамі" +tmz = "Таманаку" tna = "Такана" +tnb = "Тунебо, західна" +tnc = "Танімука-Ретуара" +tnd = "Ангостурас Тунебо" +tne = "Каллахан, Тінок" +tng = "Тобанга" +tnh = "Майані" +tni = "Тандія" +tnk = "Квамера" +tnl = "Ленакель" +tnm = "Табла" +tnn = "Північна Танна" +tno = "Торомоно" tnp = "Уайтсендс (східна танна)" tnq = "Таїно" tnr = "Бедик" +tns = "Теніс" +tnt = "Тонтембоан" +tnu = "Тай Кханг" tnv = "Танчанья" +tnw = "Тонсаванг" +tnx = "Танема" +tny = "Тонгве" tnz = "Тонга" tob = "Тоба" +toc = "Коютла Тотонак" +tod = "Тома" +toe = "Томедес" +tof = "Гізра" tog = "Ньяса тонга" +toh = "Гітонга" toi = "Тонганська" toj = "Тохолабальська" +tok = "Токі Пона" +tol = "Толова" +tom = "Томбулу" ton = "Тонганська" +too = "Тотонак, Сікотепек де Хуарес" top = "Папантла тотонак" toq = "Топоса" +tor = "Банда, Тогбо-Вара" tos = "С'єрра-тотонакська" +tou = "Тхо" +tov = "Таромі, Верхня" tow = "Джемез" tox = "Тобійська" +toy = "Топойо" +toz = "То" +tpa = "Таупота" +tpc = "Азою Ме'пхаа" +tpe = "Тіппера" +tpf = "Тарпія" +tpg = "Кула" tpi = "Ток-пісін" tpj = "Тапієте" +tpk = "Тупінікін" +tpl = "Ме'пхаа, Тлакоапа" +tpm = "Тампулма" +tpn = "Тупінамба" +tpo = "Тай Пао" +tpp = "Пісафлорес Тепехуа" +tpq = "Тукпа" +tpr = "Тупарі" +tpt = "Тепехуа, Тлачічілко" +tpu = "Тампуан" tpv = "Танапаг" +tpw = "Тупі" tpx = "Акатепекський діалект" tpy = "Трумай" +tpz = "Тінпуц" +tqb = "Тембе" +tql = "Лехалі" +tqm = "Турумса" +tqn = "Теніно" +tqo = "Тоаріпі" +tqp = "Томоіп" +tqq = "Тунні" tqr = "Торона" +tqt = "Тотонак, Західна" tqu = "Тоуо" tqw = "Тонкава" +tra = "Тірахі" +trb = "Теребу" +trc = "Копала Трікі" +trd = "Турі" +tre = "Східна Таранган" +trf = "Креольська англійська, тринідадська" trg = "Єврейський нео-арамейський діалект Урмія" +trh = "Турака" tri = "Тірійо" +trj = "Торам" +trk = "Тюркська" trl = "Шотландський кант" trm = "Трегамі (гамбірі)" +trn = "Трінітаріо" +tro = "Нага, Тарао" trp = "Кокборок" +trq = "Сан-Мартін Ітуньосо Трікі" trr = "Тауширо (пінче, пінчі)" +trs = "Чікауаштла Трікі" trt = "Тунгаре" +tru = "Сурайт" trv = "Тароко" trw = "Торвалі" +trx = "Бідайя, Трінггус-Сембаан" +try = "Турунг" +trz = "Тора" +tsa = "Цаангі" tsb = "Цамай" tsc = "Тсва" tsd = "Цаконська" +tse = "Туніська жестова мова" +tsf = "Південно-західна Таманг" tsg = "Таусуг" +tsh = "Цуван" tsi = "Цимшиан" tsj = "Тшангла (цангла)" +tsk = "Цеку" tsl = "Цюн-Лао (Лао Бок)" +tsm = "Тюрк Ісарет Ділі" tsn = "Сетсвана" tso = "Тсонґа" +tsp = "Північна Туссан" tsq = "Тайська жестова мова" +tsr = "Акей" tss = "Тайванська жестова мова" +tst = "Сонгвей Кііні, Тонді" tsu = "Цоу (цзоу)" tsv = "Цого" +tsw = "Шінгіні" +tsx = "Мубамі" +tsy = "Тебульська жестова мова" +tsz = "Пурепеча" tta = "Тутело" +ttb = "Гаа" ttc = "Тектітек (теко)" +ttd = "Тауаде" +tte = "Бванабвана" +ttf = "Туотомб" +ttg = "Тутонг" +tth = "Та'ой, верхня" +tti = "Тобаті" +ttj = "Тооро" +ttk = "Тоторо" +ttl = "Тотела" +ttm = "Північна Тутчоне" +ttn = "Товей" +tto = "Нижня Та'ойх" +ttp = "Томбелала" ttq = "Тауллеммет (тавеллеммет)" ttr = "Тера" tts = "Ісанська" ttt = "Татська" +ttu = "Торау" +ttv = "Титан" +ttw = "Лонг Ват" +tty = "Сікарітай" +ttz = "Цум" +tua = "Віарумус" tub = "Тубатулабал (тюбатулабаль)" +tuc = "Муту" tud = "Туша" tue = "Туюка" +tuf = "Центральна Тунебо" +tug = "Тунія" +tuh = "Тауліл" tui = "Тупурі" +tuj = "Тугутіл" tuk = "Туркменська" +tul = "Тула" tum = "Тумбука" tun = "Туніка" tuo = "Тукано" @@ -2561,155 +6756,530 @@ tuq = "Тедага (теда)" tur = "Турецька мова" tus = "Тускарора (скаруре)" tut = "Алтайські мови" +tuu = "Тутутні" tuv = "Туркана" +tuw = "Тунгус" +tux = "Туксинава" tuy = "Туген" tuz = "Турка (курама)" +tva = "Вагуа" +tvd = "Цуваді" +tve = "Теун" +tvk = "Амбрим, Південно-східна" tvl = "Тувалу" +tvm = "Тела-Масбуар" +tvn = "Тавоян" tvo = "Тідоре" +tvs = "Тавета" +tvt = "Нага, Тутса" +tvu = "Тунен" +tvw = "Седоа" +tvy = "Бідау креольська португальська" +twa = "Твана" +twb = "Тавбуїд, західна" +twc = "Тешенава" twd = "Твентський діалект" +twe = "Тева (Індонезія)" +twf = "Північна Тива" +twg = "Теревенг" twh = "Тай Дон (Тай Кхао, Білий Тай)" twi = "Чві" +twl = "Тавара" twm = "Таванг Монпа" +twn = "Твенді" +two = "Цвапонг" +twp = "Ере" twq = "Тасавак" +twr = "Південно-західна Тарахумара" +twt = "Турівара" +twu = "Терману" +tww = "Туварі" +twx = "Теве" +twy = "Тавоян" +txa = "Томбонуо" +txb = "Тохарська Б" +txc = "Цецаут" +txe = "Тотолі" txg = "Тангут" txh = "Фракійська" +txi = "Ікпенг" +txm = "Томіні" +txn = "Таранган, Західна" +txo = "Тото" +txq = "Тій" txr = "Тартессійська" +txs = "Тонсеа" +txt = "Цитак" +txu = "Каяпо" +txx = "Татана" +txy = "Малагасійська, Таносі" tya = "Тауя" +tye = "К'янга" +tyh = "О'ду" +tyi = "Теке-Цаайі" +tyj = "Тай-До" +tyl = "Ту Лао" +tyn = "Комбай" +typ = "Тайпан" +tyr = "Тай Даенг" tys = "Сапа" +tyt = "Тай Так" +tyu = "Куа" tyv = "Тувинська" +tyx = "Теке-Тіє" tyz = "Тхо" tza = "Танзанійська жестова мова" tzh = "Цельталь" tzj = "Цутухільська" +tzl = "Талоссан" tzm = "Центральномароканська тамазіт" +tzn = "Тугун" tzo = "Цоциль" +tzx = "Табріак" uam = "Уамуе" +uan = "Куан" +uar = "Тайрума" +uba = "Убанг" +ubi = "Убі" +ubl = "Бікол, Бухі'нон" +ubr = "Убір" +ubu = "Умбу-Унгу" uby = "Убихська" +uda = "Уда" ude = "Удегейська" +udg = "Мудуга" udi = "Удінська" +udj = "Уджір" +udl = "Вузлам" udm = "Удмуртська" udu = "Удук" +ues = "Кіоко" +ufi = "Уфімська" uga = "Угаритська" +ugb = "Куку-Угбанх" +uge = "Угеле" +ugn = "Угандійська жестова мова" +ugo = "Угонг" +ugy = "Уругвайська жестова мова" +uha = "Ухамі" +uhn = "Дамал" uig = "Уйгурська" +uis = "Уісай" +uiv = "Уіве" +uji = "Танджиджилі" +uka = "Кабурі" +ukg = "Укурігума" +ukh = "Ухведжо" +ukl = "Українська жестова мова" +ukp = "Укпе-Байобірі" +ukq = "Уква" ukr = "Українська" uks = "Мова жестів каапор" +uku = "Укуе" ukw = "Уквуані-Або-Ндоні" +uky = "Куук-Як" +ula = "Фунгва" +ulb = "Улуквумі" ulc = "Ульцька" +ule = "Луле" ulf = "Уску (афра)" uli = "Улітіанська (улітійська)" ulk = "Меріам" +ull = "Уллатан" +ulm = "Улуманда" uln = "Унзердойч (рабаульська креольська німецька)" +ulu = "Ума' Ланг" +ulw = "Ульва" +uma = "Уматілла" umb = "Умбунду" +umc = "Марруцинська" +umd = "Умбіндхаму" +umg = "Умбуйгаму" +umi = "Укіт" +umm = "Умон" +umn = "Мак'ян Нага" +umo = "Умотіна" +ump = "Умпіла" umr = "Умпукала" +ums = "Пендау" umu = "Мансі" +una = "Північна Ватут" und = "Невідома мова" +une = "Унеме" +ung = "Нгаріньїн" +unk = "Енавене-Наве" unm = "Унамі" +unn = "Курнай" +unr = "Мундарі" +unu = "Унубахе" +unx = "Мунда" +unz = "Кайлі, Унде" +uok = "Уоха" +upi = "Умеда" upv = "Північно-східна малакула (Уріпів-Вала-Рано-Атчин)" ura = "Ураріна" +urb = "Каапор" +urc = "Унінгангк" urd = "Урду" ure = "Уру" +urf = "Урадхі" +urg = "Урігіна" urh = "Ургобо" +uri = "Урім" +urj = "Уральська" +urk = "Урак Лавой" +url = "Уралі" urm = "Урапмін" +urn = "Уруангнірин" uro = "Ура (Папуа-Нова Гвінея)" +urp = "Уру-Па-Ін" +urr = "Льойоп" +urt = "Урат" +uru = "Урумі" urv = "Уруава" +urw = "Соп" +urx = "Урімо" +ury = "Оря" +urz = "Уру-Еу-Вау-Вау" +usa = "Усаруфа" +ush = "Ушодзі" +usi = "Усуї" +usk = "Усаґаде" usp = "Успантек" +usu = "Уя" +uta = "Отанк" ute = "Юте" +utp = "Амба (Соломонові острови)" utr = "Етуло" +utu = "Уту" uum = "Урумська" uun = "Кулон-Пазе" +uur = "Ура" uuu = "Пуман (У)" uve = "Західна увеа" +uvh = "Урі" +uvl = "Лоте" +uwa = "Діалект Гугу Уван" +uya = "Доко-Уянга" uzb = "Узбецька" uzn = "Узбецька" uzs = "Південно-узбецька" +vaa = "Ваагрі ​​Булі" vae = "Вале" +vaf = "Вафсі" +vag = "Вагла" vah = "Вархаді діалект" vai = "Ваї" +vaj = "Васекела Бушмен" +val = "Вехес" vam = "Ванімо" +van = "Валман" vao = "Вао" +vap = "Вайфей" var = "Уаріхіо (гуарихіо)" +vas = "Васаві" +vau = "Ванума" +vav = "Варлі" +vay = "Вайю" +vbb = "Бабар, Південно-східна" +vbk = "Бонток, Південно-Західна" vec = "Венеційська" ved = "Ведда" vel = "Велувська" +vem = "Вемго-Мабас" ven = "Венда (мова)" veo = "Вентуреньо" vep = "Вепська" +ver = "Мом Джанго" +vgr = "Вагрі" vgt = "Фламандська жестова мова" +vic = "Креольська англійська, Віргінські острови" +vid = "Відунда" vie = "В'єтнамська" +vif = "Вілі" vig = "Віемо" vil = "Вілела" +vin = "Вінца" +vis = "Вішаван" +vit = "Віті" +viv = "Ідуна" +vka = "Каріярра" +vki = "Ія-Зуба" +vkj = "Куджарге" +vkk = "Каур" +vkl = "Кулісусу" +vkm = "Камакан" +vko = "Кодеоха" vkp = "Португальська креольська, корлай" +vkt = "Малайська, Тенггаронг Кутай" +vku = "Куррама" +vlp = "Валпей" vls = "Фламандська" vma = "Мартутуніра" vmb = "Мбабарам (барбарам)" +vmc = "Мікстек Хустлауака" +vmd = "Корага, Муду" +vme = "Східна Масела" vmf = "Майнфранкські діалекти" vmg = "Лунгалунга" +vmh = "Марагей" +vmi = "Міва" +vmj = "Ікстайутла Мікстек" vmk = "Макуа" +vml = "Малгана" +vmm = "Мілтатонго, Мікстек" +vmp = "Мазатек, Соялтепек" +vmq = "Мікстек, Соялтепек" +vmr = "Мареньє" +vms = "Моксела" +vmu = "Мулуриді" +vmv = "Майду, Долина" vmw = "Макува" +vmx = "Мікстек, Тамазола" +vmy = "Аяутла Мазатек" +vmz = "Мазатек, Масатлан" +vnk = "Ловоно" +vnm = "Неве'ей (вінмавіс)" +vnp = "Вунапу" vol = "Волапюк" +vor = "Воро" vot = "Водська" +vra = "Вераʼа (ватрата)" vro = "Виро" +vrs = "Варісі" +vrt = "Банам Бей" +vsi = "Молдовська жестова мова" vsl = "Венесуельська жестова мова" vsv = "Валенсійська жестова мова" +vto = "Віту" +vum = "Вумбу" vun = "Вуньо" +vut = "Вуте" +vwa = "Ава (Китай)" +waa = "Валла-Вала" +wab = "Ваб" +wac = "Васко-Вішрам" +wad = "Вандамен" wae = "Валзерська" +waf = "Вакона" +wag = "Ваема" +wah = "Ватубела" +wai = "Варес" +waj = "Ваффа" wak = "Вакаські мови" wal = "Валамо" wam = "Массачусетська (вампаноаг)" +wan = "Ван" wao = "Ваппо" +wap = "Вапішана" +waq = "Вагіман" war = "Варайська" was = "Вашо" +wat = "Канінува" +wau = "Ваура" +wav = "Вака" +waw = "Вайвай" +wax = "Маранґіс" +way = "Ваяна" +waz = "Вампур" wba = "Варао" +wbb = "Вабо" +wbe = "Варітай" +wbf = "Вара" +wbh = "Ванда" +wbi = "Вванджі" wbj = "Алагва" wbk = "Вайгалі" wbl = "Ваханська" +wbm = "Ва" wbp = "Варлпірі (вальбірі)" +wbq = "Ваддар" +wbr = "Вагді" +wbt = "Ванмен" +wbv = "Ваджаррі" +wbw = "Вой" +wca = "Яномама" wci = "Ваці, гбе" +wdd = "Ванджі" wdg = "Вадагінам" +wdj = "Ваджигіні" +wdk = "Вадікалі" +wdu = "Ваджигу" +wdy = "Ваджабангаї" +wea = "Вевав" +wec = "Ве західна" +wed = "Ведау" +weg = "Вергайя" +weh = "Вех" +wei = "Кіунум" +wem = "Гбе, Веме" wen = "Лужицькі мови" +weo = "Вемале" wep = "Вестфальська" +wer = "Вері" wes = "Камерунський піджин" wet = "Перай" +weu = "Чін, Равнгту" +wew = "Веєва" +wfg = "Яфі" +wga = "Вагайя" +wgb = "Ваґаваґа" +wgg = "Вангангуру" +wgi = "Вахґі" +wgo = "Вайгео" +wgu = "Вірангу" +wgy = "Варргамай" +wha = "Манусела" +whg = "Північна Вахгі" +whk = "Кенія, Вахау" +whu = "Каян, Вахау" +wib = "Південна Туссіан" wic = "Вічіта (уїчіта)" +wie = "Вік-Епа" +wif = "Вік-Кеянган" +wig = "Вік-Нгатхана" +wih = "Вікі-Меанга" +wii = "Мінідієн" +wij = "Вік-Іянх" +wik = "Вікалкан" +wil = "Вілавіла" wim = "Вік-мунгкан" win = "Віннебаго (хо-чанк)" +wir = "Вірафед" +wit = "Вінту" wiu = "Віру (віту)" wiv = "Віту (мудуапа)" wiy = "Війот (соулатлук)" +wja = "Ваджа" +wji = "Варджі" wka = "Квадза" +wkb = "Кумбаран" +wkd = "Мо" +wkl = "Каланаді" +wku = "Кундуваді" +wkw = "Вакавака" +wky = "Вангкаютюру" +wla = "Валіо" +wlc = "Коморська, Мвалі" +wle = "Волане" +wlg = "Кунбарланг" +wli = "Вайолі" +wlk = "Вайлакі" +wll = "Валі (Судан)" wlm = "Середньоваллійська" wln = "Валлонська" wlo = "Воліо" +wlr = "Вайлапа" wls = "Волліська" +wlu = "Вулівулі" +wlv = "Вехос" +wlw = "Валак" +wlx = "Валі" +wly = "Валінг" +wma = "Мава (Нігерія)" wmb = "Вамбая" +wmc = "Вама" wmd = "Мамаїнде" +wme = "Вамбуле" +wmh = "Ваймаа" +wmi = "Вамін" +wmm = "Майва (Індонезія)" +wmn = "Ваамванг" +wmo = "Вом (Папуа Нова Гвінея)" wms = "Вамбон" wmt = "Вальмаджарі" wmw = "Мвані" +wmx = "Вомо" +wnb = "Ванамбре" +wnc = "Вантоат" +wnd = "Вандаранг" +wne = "Ванечі" +wng = "Ванггом" +wni = "Коморська, Ндзвані" wnk = "Ванукака" +wnm = "Вангамала" +wnn = "Вунумара" +wno = "Вано" +wnp = "Ванап" +wnu = "Усан" wnw = "Вінту" +wny = "Ваньї" +woa = "Тіараті" +wob = "Ве Північна" +woc = "Вогео" +wod = "Волані" woe = "Волеанська" +wof = "Гамбійська Волоф" wog = "Вогамусін" +woi = "Каманг" +wok = "Лонгто" wol = "Волоф" +wom = "Вом (Нігерія)" +won = "Вонго" +woo = "Маномбай" +wor = "Ворія" +wos = "Ханга Хунді" +wow = "Вавонії" +woy = "Вейто" wpc = "Мако" wra = "Варапу" +wrb = "Варлувара" +wrd = "Вардуджі" wrg = "Варронго (варунгу)" +wrh = "Вірадхурі" +wri = "Варіянгга" +wrk = "Гаррва" +wrl = "Варлманпа" wrm = "Варумунгу" wrn = "Верні (варнанг)" +wro = "Воррора" +wrp = "Варопен" +wrr = "Вардаман" wrs = "Варіс (вальса)" +wru = "Вару" +wrv = "Варуна" +wrw = "Гугу Варра" +wrx = "Вае Рана" +wry = "Мерварі" +wrz = "Варай (Австралія)" +wsa = "Варемборі" +wsi = "Вусі" +wsk = "Васкія" +wsr = "Овенія" +wss = "Васа" +wsu = "Васу" +wsv = "Вотапурі-Катаркалай" wtf = "Ватива" +wth = "Ватавуррунг" wti = "Берта" +wtk = "Ватакатауї" wtm = "Меваті" +wtw = "Воту" +wua = "Вікнгенчера" +wub = "Вунамбал" +wud = "Вуду" wuh = "Вутун (утун)" +wul = "Силімо" +wum = "Вумбву" +wun = "Бунгу" +wur = "Вурругу" wut = "Вутунг" wuu = "Уська" wuv = "Вувулу-Ауа" +wux = "Вулна" +wuy = "Вауяй" +wwa = "Ваама" +wwb = "Вакабунга" wwo = "Дориг" wwr = "Варрва" www = "Вава" wxa = "Васян" +wxw = "Варданді" wya = "Віандот" +wyb = "Вангаайбуван-Нгіямбаа" +wyi = "Воівуррунг" wym = "Вілямівська" +wyr = "Вайоро" wyy = "Західна фіджійська" xaa = "Андалузька арабська" xab = "Самбе" @@ -2717,16 +7287,36 @@ xac = "Качарі" xad = "Адаі (адайська)" xae = "Еквська" xag = "Агванська" +xai = "Каїмбе" xal = "Калмицька" +xam = "Ц'хам" xan = "Хамтанга" +xao = "Кхао" +xap = "Апалачі" xaq = "Аквітанська" +xar = "Карамі" +xas = "Камас" xat = "Катавіксі (катавіші)" +xau = "Каувера" xav = "Шаванте (ксаванте)" xaw = "Каваїсу" +xay = "Каян Махакам" +xba = "Камба (Бразилія)" +xbb = "Бурдекін, Нижня" xbc = "Бактрійська" +xbd = "Біндал" +xbe = "Бігамбал" +xbg = "Бунгандиджі" +xbi = "Комбіо" +xbj = "Біррпайі" +xbm = "Бретонська, середня" +xbn = "Кенабой" xbo = "Булгарська" +xbp = "Біббульман" xbr = "Камбера" xbw = "Камбіва" +xbx = "Кабіксі" +xby = "Батяла" xcb = "Кумбрійська" xcc = "Камунська" xce = "Кельтіберська" @@ -2737,178 +7327,678 @@ xcm = "Комекрудо" xcn = "Котонаме" xco = "Хорезмійська" xcr = "Карійська" +xct = "Класична тибетська" xcu = "Куршська" +xcv = "Чувашська" xcw = "Коауїлтеко" xcy = "Каюсе" +xda = "Даркіньюнг" xdc = "Дакська" +xdk = "Дхарук" xdm = "Едомітська" +xdy = "Даяк, малайська" xeb = "Еблаїтська" +xed = "Хді" xeg = "Батва (зегві)" +xel = "Кело" +xem = "Кембаян" +xep = "Епі-Ольмек" +xer = "Шеренте" +xes = "Кесавай" +xet = "Шета" +xeu = "Кеору-Ахіа" xfa = "Фаліська" xga = "Галатська" +xgb = "Гбін" +xgd = "Гуданг" xgf = "Тонгва (габріеліно)" +xgg = "Горенг" +xgi = "Гарінгбал" xgl = "Галіндська" +xgm = "Гувінмал" xgr = "Гарза" +xgu = "Унггумі" +xgw = "Гува" +xha = "Харамі" xhc = "Гунська" +xhd = "Хадрамі" xhe = "Кхетрані" xho = "Коса" +xhr = "Герніканська" xht = "Хаттська" xhu = "Хуритська" +xhv = "Кхуа" +xia = "Сяндао" xib = "Іберійська" xii = "Ксірі (гріква, кейп-готтентот)" xil = "Іллірійська" xin = "Сінка (шинканська)" +xip = "Ксіпінава" +xir = "Ксіріана" xiv = "Мова долини Інду" xiy = "Шипая" +xjb = "Мінджунгбал" +xjt = "Джайтматанг" +xka = "Калкоті" +xkb = "Наго, Північна" +xkc = "Хоінський діалект" +xkd = "Каян, Мендалам" +xke = "Керехо" +xkf = "Кхенкха" +xkg = "Каґоро" xkh = "Карахав`яна" xki = "Кенійська жестова мова" +xkj = "Каджалі" +xkk = "Како'" +xkl = "Мейнстрім Кенійська" +xkn = "Каян, Річка Каян" +xko = "Кіорр" +xkp = "Кабатей" +xkq = "Короні" +xkr = "Ксакріаба" +xks = "Кумбеваха" +xkt = "Кантосі" +xku = "Каамба" +xkv = "Кгалагаді" +xkw = "Кембра" +xkx = "Кароре" +xky = "Ума Ласан" +xkz = "Куртоха" xla = "Камула" +xlb = "Луп Б" xlc = "Лікійська" xld = "Лідійська" xle = "Лемноська стела" xlg = "Лігурська" xli = "Лібурнська" +xln = "Аланська" +xlo = "Луп А" xlp = "Лепонтійська" xls = "Лузітанська" +xlu = "Клинопис лувійський" xly = "Елімська" +xma = "Мушунгулу" +xmb = "Мбонга" +xmc = "Махува-Марревоне" +xmd = "Мбудум" xme = "Мідійська" xmf = "Мегрельська" +xmg = "Менгака" +xmh = "Куку-Мумінх" +xmj = "Маджера" xmk = "Давньомакедонська" xml = "Малайзійська жестова мова" xmm = "Малайська манадо" +xmn = "Маніхейська середньоперська" +xmo = "Мореребі" +xmp = "Куку-Му'ін" +xmq = "Куку-Мангк" xmr = "Мероїтська" +xms = "Марокканська жестова мова" +xmt = "Матбат" +xmu = "Каму" +xmv = "Антанкарана малагасійська" +xmw = "Малагасійська, Ціміхети" +xmx = "Маден" +xmy = "Маягудуна" +xmz = "Морі Бава" xna = "Північноаравійська, давня" xnb = "Канаканаву (канаканабська)" +xnd = "На-Дене" xng = "Середньомонгольська" +xnh = "Куанхуа" +xni = "Нґаріґу" +xnk = "Нганакарті" +xnn = "Канканай, Північна" xno = "Англо-нормандська" xnr = "Кангрі" +xns = "Канаші" +xnt = "Наррагансетт" +xnu = "Нукунул" +xny = "Ньіяпарлі" +xnz = "Кензі" +xoc = "О'чі'чі'" +xod = "Кокода" xog = "Сога" +xoi = "Комінімунг" +xok = "Ксокленг" xom = "Комо (Судан)" xon = "Конкомба" +xoo = "Шукуру" +xop = "Копар" +xor = "Корубо" +xow = "Ковакі" +xpa = "Піррія" xpc = "Печенізька" +xpe = "Кпеллє, Ліберія" xpg = "Фригійська" xpi = "Піктська" +xpj = "Мпалітжанх" +xpk = "Куліна Пано" xpm = "Пумпокольська" +xpn = "Капінава" xpo = "Почутек" +xpp = "Пуйо-Пекче" xpq = "Мохеган-пекот" xpr = "Парфянська" xps = "Пісідійська" +xpt = "Пунтхамара" xpu = "Пунічна" +xpy = "Пуйо" +xqa = "Караханід" xqt = "Катабанська" +xra = "Крахо" +xrb = "Східна Караборо" +xrd = "Гундунгурра" +xre = "Крейе" +xrg = "Мінанг" +xri = "Крікаті-Тімбіра" +xrm = "Армазік" xrn = "Аринська" +xrq = "Карранга" xrr = "Ретська" +xrt = "Аранама-Таміке" +xru = "Марріамму" +xrw = "Карава" xsa = "Сабайська (сабейська)" xsb = "Самбал" xsc = "Скіфська" xsd = "Сідетська" xse = "Семпан (нарарапі)" +xsh = "Шаманґ" xsi = "Сіо" +xsj = "Субі" +xsl = "Слейві, Південна" xsm = "Касем (касена)" +xsn = "Санга (Нігерія)" xso = "Солано" +xsp = "Сілопі" +xsq = "Махува-Сака" xsr = "Шерпа" xss = "Асанська" xsu = "Санума" xsv = "Ятвязька" xsy = "Сайсіят" +xta = "Алькозаука Мікстек" +xtb = "Чазумба Мікстек" +xtc = "Катча-Кадуглі-Мірі" +xtd = "Мікстек, Дьюхі Тілантонго" +xte = "Кетенгбан" +xtg = "Галльська, Трансальпійська" +xth = "Йіта Йіта" +xti = "Мікстек, Сінікауа" +xtj = "Мікстек, Сан-Хуан-Тейта" +xtl = "Мікстек, Тіялтепек" +xtm = "Магдалена Пеньяско Мікстек" +xtn = "Мікстек, Північний Тлаксіако" +xto = "Тохаріан А" +xtp = "Мікстек, Сан-Мігель-П'єдрас" +xtq = "Тумшукезе" +xtr = "Рання Трипурі" +xts = "Мікстек, Сіндихуї" +xtt = "Мікстек, Такахуа" +xtu = "Мікстек, Куямекалько" +xtv = "Тава" +xtw = "Таванде" +xty = "Мікстек, Йолоксочитль" +xtz = "Тасманійська" +xua = "Алу Курумба" +xub = "Бетта Курумба" +xud = "Уміїда" xug = "Куніґамі (північна окінавська)" +xuj = "Дженну Курумба" +xul = "Нгунавал" xum = "Умбрійська" +xun = "Унггарранггу" +xuo = "Куо" +xup = "Умпква, Верхня" xur = "Урартський клинопис" +xut = "Кутхант" +xuu = "Kxoe" xve = "Венетська" xvi = "Камвірі діалект" xvn = "Вандальська" xvo = "Вольська" xvs = "Вестинська" xwa = "Куаза (коайя)" +xwc = "Воккон" +xwd = "Ваді-Ваді" xwe = "Фла-фера" +xwg = "Квегу" +xwj = "Ваджук" +xwk = "Вангкумара" +xwl = "Гбе, Західна Хвла" +xwo = "Ойрат, рукописний" +xwr = "Кверба Мамберамо" +xwt = "Вотджобалук" +xww = "Вемба Вемба" +xxb = "Боро (Гана)" +xxk = "Кео" +xxm = "Мінкін" +xxr = "Коропо" +xxt = "Тамбора" +xya = "Яйгір" +xyb = "Янджибара" +xyj = "Майї-Япі" +xyk = "Майї-Кулан" +xyl = "Ялакалоре" +xyt = "Майї-Тхакурті" +xyy = "Йорта Йорта" xzh = "Чжанчжун" xzm = "Земгальська" +xzp = "Стародавня Сапотек" yaa = "Ямінава" +yab = "Юхуп" +yac = "Ялі, Пасс Веллі" yad = "Ягуа" yae = "Яруро (пуме)" yaf = "Яка (Демократична Республіка Конго)" yag = "Ямана" yah = "Язгулямська" yai = "Ягнобська" +yaj = "Банда-Янгере" +yak = "Якама" yal = "Ялунга (дьялонке, ялонке, діалонке)" yam = "Ямба" +yan = "Маянгна" yao = "Яо" yap = "Яп" yaq = "Якай" yar = "Ябарана" +yas = "Нугуну (Камерун)" +yat = "Ямбета" yau = "Ювана" yav = "Янгбен" +yaw = "Явалапіті" +yax = "Яума діалект" yay = "Акунакуна (гвуне, агвагвуне)" +yaz = "Локаа" +yba = "Яла" ybb = "Ємба" ybe = "Сариг-югурська" ybh = "Якха" +ybi = "Ямпху" +ybj = "Хаша" +ybk = "Боха" +ybl = "Юкубен" +ybm = "Ябен" +ybn = "Ябаана" +ybo = "Ябонг" +ybx = "Явійо" +yby = "Явеюха" +ych = "Чесу" +ycl = "Лолопо" +ycn = "Юкуна" +ycp = "Чеп'я" +yda = "Янда" +ydd = "Ідиш" +yde = "Янгум Дей" ydg = "Їдга" +ydk = "Йойдік" +yds = "Жестова мова їдиш" +yea = "Равула" +yec = "Єніче" yee = "Їмас" yei = "Єй" yej = "Єврейсько-грецький діалект" +yel = "Єла" +yer = "Тарок" +'yes' = "Н`янкпа" +yet = "Йєтфа" yeu = "Єрукала" +yev = "Япунда" +yey = "Йейі" +yga = "Малянгапа" +ygi = "Йіньінгаї" +ygl = "Янгум Гель" +ygm = "Ягомі" +ygp = "Гепо" +ygr = "Ягарія" +ygu = "Югул" +ygw = "Ягвоя" +yha = "Баха Буянг" yhd = "Єврейсько-іракська арабська" +yhl = "Хлефо Фова" +yia = "Йінгарда" yid = "Їдиш" +yif = "Аче" +yig = "Насу, Вуса" +yih = "Західна ідиш" yii = "Їдіні" yij = "Йінджібарді" +yik = "Донгшанба Лало" +yil = "Йінджіланджі" +yim = "Нага, Йімчунгру" +yin = "Їньчія" +yip = "Фоло" +yiq = "Мікі" +yir = "Авью, Північна" +yis = "Їс" +yit = "Східна Лалу" +yiu = "Аву" +yiv = "Нісу, Північна" +yix = "Аксі Ї" +yiy = "Йір Йоронт" +yiz = "Аже" +yka = "Якан" ykg = "Північноюкагірська" +yki = "Йоке" +ykk = "Якайкеке" +ykl = "Хлула" +ykm = "Кап" +ykn = "Куа-нсі" +yko = "Яса" +ykr = "Єкора" +ykt = "Кату" +yku = "Куамасі" +yky = "Якома" +yla = "Яул" +ylb = "Ялеба" yle = "Йеле" +ylg = "Єлогу" +yli = "Анггурук Ялі" +yll = "Їл" +ylm = "Лімі" +yln = "Буянг, Лангнян" +ylo = "Налуо Ї" +ylr = "Яларннга" +ylu = "Арібваунг" +yly = "Ньялаю" +ymb = "Ямбес" +ymc = "Муджі, Південна" +ymd = "Муда" yme = "Ямео" +ymg = "Ямонгері" +ymh = "Мілі" +ymi = "Модзі" +ymk = "Макве" +yml = "Іамалеле" +ymm = "Маай" +ymn = "Сунум" +ymo = "Янгум Мон" +ymp = "Ямап" +ymq = "Муджі, Кіла" +ymr = "Маласар" yms = "Мізійська" +ymt = "Матор-Тайгі-Карагас" +ymx = "Муджі, Північна" +ymz = "Музі" +yna = "Алуо" +ynd = "Яндрувандха" +yne = "Ланьє" +yng = "Янго" +ynh = "Янгхо" ynk = "Наукан" +ynl = "Янгулам" +ynn = "Яхі діалект" +yno = "Йонг" +ynq = "Єнданг" +yns = "Янсі" +ynu = "Яхуна" +yob = "Йоба" +yog = "Йогад" yoi = "Йонагуні" yok = "Йокутська" yol = "Йола" +yom = "Йомбе" +yon = "Йонгком" yor = "Йоруба" +yos = "Йос" +yot = "Йотті" +yox = "Йорон" +yoy = "Йой" +ypa = "Фала" +ypb = "Лабо Фова" +ypg = "Фола" +yph = "Фуфа" ypk = "Юпікські мови" +ypm = "Пхума" +ypn = "Ані Пхова" +ypo = "Ало Фола" +ypp = "Фупа" +ypz = "Фуза" +yra = "Єракай" +yrb = "Яреба" +yre = "Яуре" yri = "Ярі" yrk = "Ненецька" yrl = "Ньєнґату" +yrm = "Їррк-Мел" +yrn = "Йеронг" +yrs = "Ярсун" +yrw = "Яравата" +yry = "Ярлуянді" +ysc = "Яська" +ysd = "Саматао" +ysg = "Сонага" +ysl = "Югославська жестова мова" ysn = "Сані" +yso = "Нісі (Китай)" +ysp = "Лолопо, Південна" ysr = "Сіренік юпік (сіренікська)" +yss = "Єсан-Майо" +ysy = "Сані" +yta = "Талу" +ytl = "Тангланг" +ytp = "Тофо" +ytw = "Яут Вам" +yty = "Ятай" yua = "Майя, юкатанська" yub = "Югамбал (югумбіл)" yuc = "Ючі" yud = "Єврейсько-триполітанська арабська" yue = "Кантонська" yuf = "Хавасупай-Валапай-Явапай" +yug = "Юг" +yui = "Юруті" yuj = "Каркар-юрі" yuk = "Юкі" +yul = "Юлу" yum = "Кечан (квацаан)" +yun = "Бена (Нігерія)" yup = "Юкпа" +yuq = "Юкі" yur = "Юрок" +yut = "Йопно" yuu = "Югська" +yuw = "Яу (провінція Моробе)" yux = "Південноюкагірська" yuy = "Східно-югурська" yuz = "Юракаре" +yva = "Ява" yvt = "Явітеро" +ywa = "Калу" +ywg = "Іньхавангка" +ywl = "Лалу, західна" +ywn = "Яванава" +ywq = "Вудінг-Луцюань І" +ywr = "Явуру" +ywt = "Центральна Лало" +ywu = "Насу, Вуменг" +yww = "Явараварга" +yxa = "Майявалі" +yxg = "Ягара" +yxl = "Ярдліяварра" +yxm = "Іньвум" +yxu = "Юю" +yxy = "Ябула Ябула" +yyr = "Йір Йоронт" +yyu = "Яу (провінція Сандаун)" +yyz = "Аїзі" +yzg = "Буянг, Ема" +yzk = "Зохуо" +zaa = "Сьєрра-де-Хуарес Сапотек" +zab = "Сан-Хуан Гелавіа Сапотек" +zac = "Окотлан Сапотек" +zad = "Кахонос Сапотек" +zae = "Ярені Сапотек" +zaf = "Айокеско Сапотек" zag = "Загава" zah = "Зангвал" zai = "Істмуська сапотекська" +zaj = "Зарамо" +zak = "Занакі" +zal = "Заузу" +zam = "Міауатлан Сапотек" +zao = "Озолотепек Сапотек" zap = "Сапотекська" +zaq = "Алоапам Сапотек" +zar = "Рінкон Сапотек" +zas = "Санто-Домінго Альбаррадас Сапотек" +zat = "Табаа Сапотек" +zau = "Зангскарі" +zav = "Яцачі Сапотек" +zaw = "Мітла Сапотек" +zax = "Хадані Сапотек" zay = "Зайсе-Зергулла" +zaz = "Зарі" +zbc = "Бераван, Центральна" +zbe = "Бераван, Східна" zbl = "Блісса" +zbt = "Батуї" +zbw = "Бераван, Західна" +zca = "Коатекас Альтас Сапотек" +zch = "Центральна Хуншуйхе Чжуан" +zdj = "Коморська, Нгазіджа" zea = "Зеландська" +zeg = "Зенаг" +zeh = "Східна Хуншуйхе Чжуан" zen = "Зенага" +zga = "Кінґа" +zgb = "Гуйбей Чжуан" +zgh = "Стандартна марокканська тамазігхт" +zgm = "Мінц Чжуан" +zgn = "Гуйбянь Чжуан" +zgr = "Маґорі" zha = "Чжуан" zhb = "Чжаба" +zhd = "Дай Чжуан" +zhi = "Жире" +zhn = "Нонг Чжуан" zho = "Китайська" +zhw = "Чжоа" zia = "Зія" +zib = "Зімбабвійська жестова мова" zik = "Зімакані" zil = "Зіало" +zim = "Месме" +zin = "Зінза" +zir = "Зірія" +ziw = "Зігула" +ziz = "Зізілівакан" +zka = "Каімбулава" +zkb = "Койбал" +zkd = "Каду" zkg = "Когурьо" +zkh = "Хорезмська" zkk = "Каранкава" +zkn = "Канан" zko = "Коттська" +zkp = "Кайнганг, Сан-Паулу" zkr = "Закрінг" zkt = "Китань (кіданьська, кітань)" +zku = "Каурна" +zkv = "Кревиньян" zkz = "Хозарська" +zle = "Східнослов'янська" +zlj = "Люцзян Чжуан" +zlm = "Малайська" +zln = "Ляньшань Чжуан" +zlq = "Люцянь Чжуан" +zls = "Південнослов'янська" +zlw = "Західнослов'янська" +zma = "Манда (Австралія)" +zmb = "Зімба" +zmc = "Маргані" +zmd = "Марідан" +zme = "Мангерр" +zmf = "Мфіну" +zmg = "Марті Ке" +zmh = "Маколкол" +zmi = "Негері Сембілан Малайська" +zmj = "Маріджабін" +zmk = "Манданданьї" +zml = "Маднґеле" +zmm = "Маріманінджі" +zmn = "Мбанґве" +zmo = "Моло" +zmp = "Мпуоно" +zmq = "Мітуку" +zmr = "Маранунггу" +zms = "Мбеса" +zmt = "Марінгарр" +zmu = "Муруварі" +zmv = "Мбаріман-Гудхінма" +zmw = "Мбо (Демократична Республіка Конго)" +zmx = "Бомітаба" +zmy = "Марієді" +zmz = "Мбанджа" +zna = "Зан Гула" znd = "Занде-мови" zne = "Занде" +zng = "Манг" +znk = "Манангкарі" zns = "Мантсі (мангас, маас)" +zoc = "Копайнала Зоке" +zoh = "Чімалапа Зоке" +zom = "Цзоу" +zoo = "Асунсьйон Мікстепек Сапотек" +zoq = "Аяпа Зоке" +zor = "Район Зоке" +zos = "Франциско Леон Зоке" +zpa = "Лачігуїрі Сапотек" +zpb = "Яутепек Сапотек" +zpc = "Чоапан Сапотек" +zpd = "Південно-східна Ікстлан Сапотек" +zpe = "Петапа Сапотек" +zpf = "Сан-Педро Кіатоні Сапотек" +zpg = "Гевеа де Гумбольдт Сапотек" +zph = "Тотомачапан Сапотек" +zpi = "Санта-Марія Кіеголані Сапотек" +zpj = "Квіакузас Сапотек" +zpk = "Тлаколуліта Сапотек" +zpl = "Лачісіо Сапотек" +zpm = "Мікстепек Сапотек" +zpn = "Санта-Інес Яцекі Сапотек" +zpo = "Аматлан Сапотек" +zpp = "Ель Альто Сапотек" +zpq = "Сапотек, Зоогочо" +zpr = "Сантьяго Ксаніка Сапотек" +zps = "Коатлан Сапотек" +zpt = "Сан-Вісенте-Коатлан Сапотек" +zpu = "Ялалаг Сапотек" +zpv = "Чичікапан Сапотек" +zpw = "Заніза Сапотек" +zpx = "Сан-Бальтазар Локсіча Сапотек" +zpy = "Мазальтепек Сапотек" +zpz = "Тексмелукан Сапотек" +zqe = "Цюбей Чжуан" +zra = "Кара (Корея)" +zrg = "Мірган" +zrn = "Зеренкель" zro = "Сапаро" zrp = "Зарфатська" zrs = "Майрасі (фараньяо, каніран)" +zsa = "Сарасіра" +zsk = "Каскеан" +zsl = "Замбійська жестова мова" zsm = "Малайська" +zsr = "Південна Рінкон Сапотек" +zsu = "Сукурум" +zte = "Елотепек Сапотек" +ztg = "Сапотек, ханагіянська" +ztl = "Лапагуія-Гуівіні Сапотек" +ztm = "Сан-Агустін Мікстепек Сапотек" +ztn = "Санта-Катаріна Альбаррадас Сапотек" +ztp = "Локсіха Сапотек" +ztq = "Кіокітані-К'єрі Сапотек" +zts = "Тількіапан Сапотек" +ztt = "Техалапан Сапотек" +ztu = "Гуїла Сапотек" +ztx = "Заахіла Сапотек" +zty = "Яті Сапотек" zua = "Зім (чаарі)" +zuh = "Токано" zul = "Зулу" zum = "Кумзарі" zun = "Зуньї" @@ -2916,4 +8006,9 @@ zuy = "Зумая" zwa = "Зайська" zxx = "Немає мовного змісту" zyb = "Чжуанська" +zyg = "Ян Чжуан" +zyj = "Юцзян Чжуан" +zyn = "Юннань Чжуан" +zyp = "Чін, Зіфе" zza = "Зазакі" +zzj = "Чжуан, Цзоцзян" diff --git a/languages/IndexFieldDescription/eu.ini b/languages/IndexFieldDescription/eu.ini new file mode 100644 index 00000000000..e1fee4acef2 --- /dev/null +++ b/languages/IndexFieldDescription/eu.ini @@ -0,0 +1,4 @@ +allfields_unstemmed = "eremu guztiak eremutik kopiatuta" +fulltext_unstemmed = "testu osoa eremutik kopiatuta" +title_alt = "ordezko titulu(ak)" +unknown = "eremuaren izena ezezaguna da" diff --git a/languages/KeyboardLayout/es.ini b/languages/KeyboardLayout/es.ini new file mode 100644 index 00000000000..fb03f1c7a2b --- /dev/null +++ b/languages/KeyboardLayout/es.ini @@ -0,0 +1,4 @@ +brazilian = "Brasileño" +nigerian = "Nigeriano" +russianOld = "Ruso (Antiguo)" +urduStandard = "Urdu (estándar)" diff --git a/languages/KeyboardLayout/eu.ini b/languages/KeyboardLayout/eu.ini new file mode 100644 index 00000000000..445640e579a --- /dev/null +++ b/languages/KeyboardLayout/eu.ini @@ -0,0 +1,4 @@ +brazilian = "Brasildarra" +nigerian = "Nigerianoa" +russianOld = "Errusiarra (Zaharra)" +urduStandard = "Urdu (Estandarizatua)" diff --git a/languages/KeyboardLayout/se.ini b/languages/KeyboardLayout/se.ini new file mode 100644 index 00000000000..8fa00c798d8 --- /dev/null +++ b/languages/KeyboardLayout/se.ini @@ -0,0 +1,4 @@ +brazilian = "Brasilgiella" +nigerian = "Nigeriagiella" +russianOld = "Ruoššagiella (boares)" +urduStandard = "Urdugiella (standárda)" diff --git a/languages/Reserves/en.ini b/languages/Reserves/en.ini new file mode 100644 index 00000000000..453f273b194 --- /dev/null +++ b/languages/Reserves/en.ini @@ -0,0 +1,3 @@ +no_course_listed = "No Course Listed" +no_department_listed = "No Department Listed" +no_instructor_listed = "No Instructor Listed" diff --git a/languages/ServiceType/el.ini b/languages/ServiceType/el.ini index 201eebc5210..a789ef59c6e 100644 --- a/languages/ServiceType/el.ini +++ b/languages/ServiceType/el.ini @@ -1,4 +1,4 @@ -getDOI = "link σε DOI" -getFullTxt = "link σε ηλεκτρονικό αντίτυπο" -getHolding = "link σε τοπική πληροφορία έντυπου τεκμηρίου" -getWebService = "link σε web service" +getDOI = "σύνδεσμος σε DOI" +getFullTxt = "σύνδεσμος σε ηλεκτρονικό αντίτυπο" +getHolding = "σύνδεσμος προς τοπική πληροφορία έντυπου τεκμηρίου" +getWebService = "σύνδεσμος προς διαδικτυακή υπηρεσία" diff --git a/languages/ServiceType/eu.ini b/languages/ServiceType/eu.ini new file mode 100644 index 00000000000..216dd4d736f --- /dev/null +++ b/languages/ServiceType/eu.ini @@ -0,0 +1,4 @@ +getDOI = "DOI-rako esteka" +getFullTxt = "funtsa elektronikorako esteka" +getHolding = "Inprimaketa-atxikipenari buruzko tokiko informaziorako esteka" +getWebService = "Web zerbitzurako esteka" diff --git a/languages/WorldCatFormats/aliases.ini b/languages/WorldCatFormats/aliases.ini new file mode 100644 index 00000000000..3f38329e4e0 --- /dev/null +++ b/languages/WorldCatFormats/aliases.ini @@ -0,0 +1,15 @@ +archv = "default::Archival Material" +artchap = "default::Article" +book = "default::Book" +compfile = "default::Electronic" +image = "default::Image" +jrnl = "default::Journal" +kit = "default::Kit" +map = "default::Map" +msscr = "default::Manuscript" +music = "default::Musical Score" +news = "default::Newspaper" +object = "default::Physical Object" +snd = "default::Sound" +video = "default::Video" +web = "default::Website" diff --git a/languages/WorldCatFormats/ar.ini b/languages/WorldCatFormats/ar.ini new file mode 100644 index 00000000000..80718e1ac73 --- /dev/null +++ b/languages/WorldCatFormats/ar.ini @@ -0,0 +1,5 @@ +audiobook = "كتاب صوتي" +game = "لعبة" +intmm = "وسائط متعددة تفاعلية" +toy = "لعبة أطفال" +vis = "مرئي" diff --git a/languages/WorldCatFormats/bn.ini b/languages/WorldCatFormats/bn.ini new file mode 100644 index 00000000000..ceaf3cd5720 --- /dev/null +++ b/languages/WorldCatFormats/bn.ini @@ -0,0 +1,5 @@ +audiobook = "অডিওবুক" +game = "গেম" +intmm = "ইন্টারেক্টিভ মাল্টিমিডিয়া" +toy = "খেলনা" +vis = "ভিজুয়াল" diff --git a/languages/WorldCatFormats/ca.ini b/languages/WorldCatFormats/ca.ini new file mode 100644 index 00000000000..6b93098d84e --- /dev/null +++ b/languages/WorldCatFormats/ca.ini @@ -0,0 +1,5 @@ +audiobook = "Audiollibre" +game = "Joc" +intmm = "Multimèdia interactiva" +toy = "Joguina" +vis = "Visual" diff --git a/languages/WorldCatFormats/cs.ini b/languages/WorldCatFormats/cs.ini new file mode 100644 index 00000000000..fe62ab758a5 --- /dev/null +++ b/languages/WorldCatFormats/cs.ini @@ -0,0 +1,5 @@ +audiobook = "Audiokniha" +game = "Hra" +intmm = "Interaktivní multimédia" +toy = "Hračka" +vis = "Vizuální" diff --git a/languages/WorldCatFormats/de.ini b/languages/WorldCatFormats/de.ini new file mode 100644 index 00000000000..eeb900cd222 --- /dev/null +++ b/languages/WorldCatFormats/de.ini @@ -0,0 +1,5 @@ +audiobook = "Hörbuch" +game = "Spiel" +intmm = "Multimedia: interaktiv" +toy = "Spielzeug" +vis = "Bildobjekt" diff --git a/languages/WorldCatFormats/el.ini b/languages/WorldCatFormats/el.ini new file mode 100644 index 00000000000..908b7f7c916 --- /dev/null +++ b/languages/WorldCatFormats/el.ini @@ -0,0 +1,5 @@ +audiobook = "Ηχητικό Βιβλίο" +game = "Παιχνίδι" +intmm = "Διαδραστικά Πολυμέσα" +toy = "Παιδικό Παιχνίδι" +vis = "Οπτικό Υλικό" diff --git a/languages/WorldCatFormats/en.ini b/languages/WorldCatFormats/en.ini new file mode 100644 index 00000000000..9d13623da45 --- /dev/null +++ b/languages/WorldCatFormats/en.ini @@ -0,0 +1,5 @@ +audiobook = "Audiobook" +game = "Game" +intmm = "Interactive Multimedia" +toy = "Toy" +vis = "Visual" diff --git a/languages/WorldCatFormats/es.ini b/languages/WorldCatFormats/es.ini new file mode 100644 index 00000000000..1c58f9450d2 --- /dev/null +++ b/languages/WorldCatFormats/es.ini @@ -0,0 +1,5 @@ +audiobook = "Audiolibro" +game = "Juego" +intmm = "Multimedia interactiva" +toy = "Juguete" +vis = "Visual" diff --git a/languages/WorldCatFormats/eu.ini b/languages/WorldCatFormats/eu.ini new file mode 100644 index 00000000000..d9bcf394bac --- /dev/null +++ b/languages/WorldCatFormats/eu.ini @@ -0,0 +1,5 @@ +audiobook = "Audioliburua" +game = "Jokua" +intmm = "Multimedia interaktiboa" +toy = "Toy" +vis = "Bisual" diff --git a/languages/WorldCatFormats/fi.ini b/languages/WorldCatFormats/fi.ini new file mode 100644 index 00000000000..c5511f1306c --- /dev/null +++ b/languages/WorldCatFormats/fi.ini @@ -0,0 +1,5 @@ +audiobook = "Äänikirja" +game = "Peli" +intmm = "Interaktiivinen multimedia" +toy = "Lelu" +vis = "Visuaalinen" diff --git a/languages/WorldCatFormats/ga.ini b/languages/WorldCatFormats/ga.ini new file mode 100644 index 00000000000..a59532c04e9 --- /dev/null +++ b/languages/WorldCatFormats/ga.ini @@ -0,0 +1,5 @@ +audiobook = "Closleabhar" +game = "Cluiche" +intmm = "Ilmheán Idirghníomhach" +toy = "Bréagán" +vis = "Físeach" diff --git a/languages/WorldCatFormats/hi.ini b/languages/WorldCatFormats/hi.ini new file mode 100644 index 00000000000..038be4a8e37 --- /dev/null +++ b/languages/WorldCatFormats/hi.ini @@ -0,0 +1,5 @@ +audiobook = "ऑडियोबुक" +game = "खेल" +intmm = "इंटरएक्टिव मल्टीमीडिया" +toy = "खिलौना / khilona" +vis = "दृश्य (Visual) " diff --git a/languages/WorldCatFormats/hr.ini b/languages/WorldCatFormats/hr.ini new file mode 100644 index 00000000000..e8de680ebfc --- /dev/null +++ b/languages/WorldCatFormats/hr.ini @@ -0,0 +1,5 @@ +audiobook = "Audio knjiga" +game = "Igra" +intmm = "Interaktivna multimedija" +toy = "Igračka" +vis = "Vizualna građa" diff --git a/languages/WorldCatFormats/hy.ini b/languages/WorldCatFormats/hy.ini new file mode 100644 index 00000000000..2868580a08c --- /dev/null +++ b/languages/WorldCatFormats/hy.ini @@ -0,0 +1,5 @@ +audiobook = "Աուդիո գիրք" +game = "Խաղ" +intmm = "Ինտերակտիվ մուլտիմեդիա" +toy = "Խաղալիք" +vis = "Տեսողական" diff --git a/languages/WorldCatFormats/it.ini b/languages/WorldCatFormats/it.ini new file mode 100644 index 00000000000..bf29964dae2 --- /dev/null +++ b/languages/WorldCatFormats/it.ini @@ -0,0 +1,5 @@ +audiobook = "Audiolibro" +game = "Gioco" +intmm = "Multimedia interattivo" +toy = "Giocattolo" +vis = "Visivo" diff --git a/languages/WorldCatFormats/ja.ini b/languages/WorldCatFormats/ja.ini new file mode 100644 index 00000000000..dbb8c712465 --- /dev/null +++ b/languages/WorldCatFormats/ja.ini @@ -0,0 +1,5 @@ +audiobook = "オーディオブック" +game = "ゲーム" +intmm = "インタラクティブ・マルチメディア" +toy = "おもちゃ" +vis = "視覚資料" diff --git a/languages/WorldCatFormats/mn.ini b/languages/WorldCatFormats/mn.ini new file mode 100644 index 00000000000..ec230a6bb3e --- /dev/null +++ b/languages/WorldCatFormats/mn.ini @@ -0,0 +1,5 @@ +audiobook = "Аудио ном" +game = "Тоглоом" +intmm = "Интерактив мультимедиа" +toy = "Тоглоом" +vis = "Дүрслэл" diff --git a/languages/WorldCatFormats/pt-br.ini b/languages/WorldCatFormats/pt-br.ini new file mode 100644 index 00000000000..2e83c0bfadc --- /dev/null +++ b/languages/WorldCatFormats/pt-br.ini @@ -0,0 +1,5 @@ +audiobook = "Audiolivro" +game = "Jogo" +intmm = "Multimídia Interativa" +toy = "Brinquedo" +vis = "Visual" diff --git a/languages/WorldCatFormats/ru.ini b/languages/WorldCatFormats/ru.ini new file mode 100644 index 00000000000..a49397ff209 --- /dev/null +++ b/languages/WorldCatFormats/ru.ini @@ -0,0 +1,5 @@ +audiobook = "Аудиокнига" +game = "Игра" +intmm = "Интерактивные мультимедиа" +toy = "Игрушка" +vis = "Визуальный" diff --git a/languages/WorldCatFormats/sv.ini b/languages/WorldCatFormats/sv.ini new file mode 100644 index 00000000000..530f0e06903 --- /dev/null +++ b/languages/WorldCatFormats/sv.ini @@ -0,0 +1,5 @@ +audiobook = "Ljudbok" +game = "Spel" +intmm = "Interaktiv multimedia" +toy = "Leksak" +vis = "Visuell" diff --git a/languages/WorldCatFormats/tr.ini b/languages/WorldCatFormats/tr.ini new file mode 100644 index 00000000000..e0a7c75e885 --- /dev/null +++ b/languages/WorldCatFormats/tr.ini @@ -0,0 +1,5 @@ +audiobook = "Sesli Kitap" +game = "Oyun" +intmm = "Etkileşimli Multimedya" +toy = "Oyuncak" +vis = "Görsel" diff --git a/languages/WorldCatFormats/uk.ini b/languages/WorldCatFormats/uk.ini new file mode 100644 index 00000000000..a93a5418246 --- /dev/null +++ b/languages/WorldCatFormats/uk.ini @@ -0,0 +1,5 @@ +audiobook = "Аудіокнига" +game = "Гра" +intmm = "Інтерактивні мультимедіа" +toy = "Той" +vis = "Візуальний" diff --git a/languages/ar.ini b/languages/ar.ini index 8351193c4df..cdf28915767 100644 --- a/languages/ar.ini +++ b/languages/ar.ini @@ -77,6 +77,7 @@ APA Citation = "APA استشهاد" APA Edition Citation = "توثيق جمعية علم النفس الأمريكية APA (الطبعة السابعة)" applied_filter = "منقح مطبّق:" applied_filters = "المنقحات المطبقة:" +Apply filters = "تطبيق المنقحات" Archival Material = "مواد أرشيفية" Article = "مقال" Ask a Librarian = "إسأل أخصائي مكتبات" @@ -430,6 +431,7 @@ explain_coord = "* %%coord%% (ضبط عدد المطابقات مقارنة با explain_difference_score = "اختلاف عن الدرجة الأعلى" explain_disabled = "تم إلغاء تنشيط الشرح لـ %%searchClassId%%" explain_for_search = "شرح للبحث" +explain_function_query_label = "وظيفة" explain_modified_value = "منتج لـ %%relevanceValue%% (قيمة الصلة)" explain_modifier = "مع المعدل %%modifier%%" explain_record_score = "درجة التسجيلة" @@ -437,6 +439,7 @@ explain_relevance = "معرف التسجيلة: %%recordId%% تم العثور explain_relevance_score = "درجة الصلة" explain_result_list_chart_title = "الدرجة: %%score%%" explain_result_list_hint = "درجة الصلة. انقر لرؤية شرح مفصل." +explain_show_raw = "عرض الشرح الخام" explain_sum = "مجموع" explain_top_relevance = "صلة النتيجة الأعلى" Export = "تصدير" @@ -621,6 +624,7 @@ hold_edit_title = "تغيير معلومات الحجز" hold_empty_selection = "لم يتم تحديد حجوزات" hold_error_age_restricted = "لا يمكن وضع الحجز بسبب قيد العمر على المادة." hold_error_blocked = "لا يوجد لديك امتيازات كافية لوضع حجز على هذه المادة" +hold_error_current_loan_patron_group = "لا يمكن طلب هذه المادة في الوقت الحالي." hold_error_fail = "لقد فشل طلبك. يرجى الإتصال بمكتب الإعارة للمزيد من المساعدة" hold_error_item_not_holdable = "لا يمكن طلب هذه المادة." hold_error_not_holdable = "لا يمكن طلب هذه المادة." @@ -668,6 +672,7 @@ ill_request_available = "متاح للالتقاط" ill_request_cancel = "الغاء طلب الاستعارة بين المكتبات" ill_request_cancel_all = "الغاء طلبات الاستعارة بين المكتبات" ill_request_cancel_fail = "لم يتم إلغاء طليك. يرجى الاتصال بمكتب الإعارة للمزيد من المساعدة" +ill_request_cancel_fail_items = "تعذر إلغاء %%count%% طلباً" ill_request_cancel_selected = "إلغاء طلبات الاستعارة بين المكتبات المحددة" ill_request_cancel_success = "تم إلغاء طلبك بنجاح" ill_request_cancel_success_items = "%%count%% تم إلغاء الطلب بنجاح" @@ -840,6 +845,7 @@ More options = "المزيد من الخيارات" More Summon results = "استدعاء المزيد من النتائج…" More Topics = "المزيد من الموضوعات" more_authors_abbrev = "وآخرون" +more_by_author = "أيضاً بواسطة %%name%%" more_ellipsis = "المزيد…" more_info_toggle = "عرض/إخفاء المزيد من المعلومات" more_options_ellipsis = "المزيد من الخيارات…" @@ -886,6 +892,7 @@ no_email_address = "عنوان البريد الإلكتروني مفقود." no_items_selected = "لم يتم تحديد مواد" no_proxied_user = "لا يوجد مستخدم وكيل (اطلب لنفسك)" nohit_active_filters = "أحد منقحات الواجهة أو أكثر تم تطبيقه على هذا البحث . إذا قمت بحذف المنقحات، فقد تستعيد المزيد من النتائج." +nohit_busy = "النظام مشغول حالياً ولا يمكنه الاستجابة. يرجى إعادة المحاولة مرة أخرى لاحقاً." nohit_change_tab = 'لقد كنت تبحث في تبويب "%%activeTab%%" . يمكنك العثور على شيء ما في أحد التبويبات الأخرى :' nohit_filters = "المنقحات المطبقة حاليا على هذا البحث :" nohit_heading = "لا توجد نتائج!" @@ -1202,6 +1209,7 @@ renew_all = "تجديد كل المواد" renew_determine_fail = "لم نستطع تحديد ما إذا كان بالإمكان تجديد مادتك. يرجى الاتصال بأحد الموظفين." renew_empty_selection = "لم يتم تحديد أي مواد" renew_error = "لم نستطع تجديد مادتك (موادك) - يرجى الاتصال بأحد الموظفين" +renew_error_summary = "تعذر تجديد {count, plural, =1 {1 مادة} other {# مواد}} نتيجة وجود أخطاء." renew_fail = "لا يمكن تجديد هذه المادة" renew_item = "تجديد المادة" renew_item_due = "المادة مستحقة خلال ال 24 ساعة القادمة" @@ -1214,6 +1222,7 @@ renew_item_requested = "تم طلب هذه المادة بواسطة مستخد renew_select_box = "تجديد المادة" renew_selected = "تجديد المواد المحددة" renew_success = "تم التجديد بنجاح" +renew_success_summary = "تم تجديد {count, plural, =1 {1 مادة} other {# مادة}}بنجاح." Renewed = "تم تجديده" Request full text = "نص الطلب الكامل" request_in_transit = "في النقل إلى موقع الالتقاط" @@ -1285,6 +1294,7 @@ seconds_abbrev = "s" see all = "انظر الكل" See also = "انظر أيضا" see_all_ellipsis = "انظر الكل…" +Select multiple filters = "حدد عدة منقحات" Select this record = "تحديد هذه التسجيلة" Select your carrier = "تحديد ناقلك" select_all = "تحديد كافة الإدخالات" @@ -1345,6 +1355,8 @@ sort_due_date_desc = "تاريخ الاستحقاق (الأحدث أولًا)" sort_relevance = "الصلة" sort_return_date_asc = "تاريخ الإعادة (الأقدم أولًا)" sort_return_date_desc = "تاريخ الإعادة (الأحدث أولًا)" +sort_saved = "تاريخ الحفظ (الأحدث أولاً)" +sort_saved_asc = "تاريخ الحفظ (الأقدم أولاً)" sort_title = "العنوان" sort_year = "التاريخ تنازليا" sort_year_asc = "التاريخ تصاعديا" @@ -1353,6 +1365,7 @@ Source Title = "اسم المصدر" spell_expand_alt = "توسيع البحث" spell_suggest = "بدائل البحث" Staff View = "عرض للأخصائي" +standalone_record_link = "تسجيلة مستقلة" Start a new Advanced Search = "بدء بحث متقدم جديد" Start a new Basic Search = "بدء بحث أساسي جديد" Start Page = "صفحة البدء" @@ -1365,6 +1378,7 @@ storage_retrieval_request_available = "متاح للالتقاط" storage_retrieval_request_cancel = "إلغاء طلبات استرجاع التخزين" storage_retrieval_request_cancel_all = "إلغاء كل طلبات استرجاع التخزين" storage_retrieval_request_cancel_fail = "لم يتم الغاء طلبك. يرجى الاتصال بمكتب الإعارة للمزيد من المساعدة" +storage_retrieval_request_cancel_fail_items = "تعذر إلغاء %%count%% طلباً" storage_retrieval_request_cancel_selected = "إلغاء طلبات استرجاع المخزون المحددة" storage_retrieval_request_cancel_success = "تم الغاء طلبك بنجاح" storage_retrieval_request_cancel_success_items = "%%count%% تم إلغاء الطلب بنجاح" @@ -1451,6 +1465,7 @@ toggle_dropdown = "تبديل القائمة المنسدلة" Too Many Email Recipients = "متلقي البريد الالكتروني أكثر من اللازم" too_many_favorites = "هذه القائمة كبيرة جدا لا يمكن عرضها كلها مرة واحدة. حاول إعادة ترتيب مفضلاتك إلى المزيد من القوائم أو تحديد استخدام الوسوم." too_many_new_items = "يوجد الكثير جدا من المواد لعرضها في قائمة واحدة. حاول تحديد بحثك" +too_many_query_terms = "يرجى تبسيط استعلامك؛ فهو يحتوي على مصطلحات أكثر (%%terms%%) من الحد الذي يسمح به النظام (%%maxTerms%%)." too_many_reserves = "يوجد الكثير جدا من الاحتياطي الأكاديمي لعرضه في قائمة واحدة. حاول تحديد بحثك." top_facet_label = "%%label%% من بحثك." Topic = "موضوع" @@ -1531,6 +1546,8 @@ What am I looking at = "ما الذي أنظر إليه؟" widen_prefix = "حاول توسيع بحثك إلى" wiki_link = "مقدم من ويكيبيديا" with filters = "بالمنقحات" +worldcat_group_related_editions = "تجميع طبعات ذات صلة" +worldcat_group_variant_records = "تجميع تسجيلات متغيرة" Year of Publication = "سنة النشر" You do not have any fines = "لا توجد لديك أي غرامات" You do not have any holds or recalls placed = "لا يوجد لديك أي حجوزات أو استدعاءات" diff --git a/languages/bn.ini b/languages/bn.ini index d42dde4e09e..c2afaa0c872 100644 --- a/languages/bn.ini +++ b/languages/bn.ini @@ -78,6 +78,7 @@ APA Citation = "APA সাইটেশন" APA Edition Citation = "APA (7 ম সংস্করণ) উদ্ধৃতি" applied_filter = "ব্যবহারিক ফিল্টার:" applied_filters = "ফলিত ফিল্টার:" +Apply filters = "ফিল্টারগুলি প্রয়োগ করুন" Archival Material = "সংরক্ষণাগার উপাদান।" Article = "প্রবন্ধ" Ask a Librarian = "গ্রন্থাগারিককে জিজ্ঞাসা করুন" @@ -431,6 +432,7 @@ explain_coord = "*%%coord%%(অনুসন্ধানের তুলনায explain_difference_score = "শীর্ষ স্কোরের পার্থক্য" explain_disabled = "%%searchClassId%% এর জন্য ব্যাখ্যা নিষ্ক্রিয় করা হয়েছে" explain_for_search = "অনুসন্ধানের জন্য ব্যাখ্যা" +explain_function_query_label = "ফাংশন" explain_modified_value = "%%relevanceValue%% এর পণ্য (প্রাসঙ্গিক মান)" explain_modifier = "%%modifier%% এর সংশোধক সহ" explain_record_score = "রেকর্ড স্কোর" @@ -438,6 +440,7 @@ explain_relevance = "রেকর্ড আইডি: %%recordId%% %%relevanceVa explain_relevance_score = "প্রাসঙ্গিক স্কোর" explain_result_list_chart_title = "স্কোরঃ %%score%%" explain_result_list_hint = "প্রাসঙ্গিক স্কোর । বিস্তারিত ব্যাখ্যা দেখতে ক্লিক করুন ।" +explain_show_raw = "মোটের ওপর ব্যাখ্যা দেখান" explain_sum = "যোগ" explain_top_relevance = "শীর্ষ ফলাফল প্রাসঙ্গিকতা" Export = "রপ্তানি করুন" @@ -622,6 +625,7 @@ hold_edit_title = "হোল্ড তথ্য পরিবর্তন কর hold_empty_selection = "কোন হোল্ড নির্বাচন করে রাখা নেই" hold_error_age_restricted = "বয়স সম্পর্কিত সীমাবদ্ধতার কারণে হোল্ড স্থাপন করা যাবে না।" hold_error_blocked = "এই উপাদানটি হোল্ড করে রাখার আপনার যথেষ্ট অধিকার নেই" +hold_error_current_loan_patron_group = "এই আইটেমটি বর্তমানে অনুরোধ করা যাবে না ।" hold_error_fail = "আপনার অনুরোধ পূরণ করতে ব্যর্থ। আরও সহায়তার জন্য সঞ্চারন ডেস্কে যোগাযোগ করুন" hold_error_item_not_holdable = "এই আইটেমটি অনুরোধ করা যাবে না।" hold_error_not_holdable = "এই উপাদান অনুরোধ করা যাবে না।" @@ -669,6 +673,7 @@ ill_request_available = "পিকআপের জন্য" ill_request_cancel = "আন্তঃগ্রন্থাগার ঋণ অনুরোধ বাতিল করা হয়েছে" ill_request_cancel_all = "সমস্ত আন্তঃগ্রন্থাগার ঋণ অনুরোধ বাতিল করা হয়েছে" ill_request_cancel_fail = "আপনার অনুরোধ বাতিল করা হয় নি। আরও সহায়তার জন্য সঞ্চারন ডেস্কে যোগাযোগ করুন" +ill_request_cancel_fail_items = "%%count%% রিকোয়েস্ট(গুলি) বাতিল করা যায়নি" ill_request_cancel_selected = "নির্বাচিত আন্তঃগ্রন্থাগার ঋণের অনুরোধ বাতিল" ill_request_cancel_success = "আপনার অনুরোধটি সম্পূর্ন বাতিল করা হয়েছে" ill_request_cancel_success_items = "%%count%% আপনার অনুরোধগুলো সম্পূর্নরূপে বাতিল করা হয়েছে" @@ -841,6 +846,7 @@ More options = "আরও অপশন" More Summon results = "আরও Summon ফলাফল…" More Topics = "আরও বিষয় দেখুন" more_authors_abbrev = "অন্যান্য" +more_by_author = "এছাড়াও %%name%%" more_ellipsis = "আরও…" more_info_toggle = "প্রদর্শন করুন/আরও তথ্য আড়াল করুন" more_options_ellipsis = "আরও অপশন…" @@ -887,6 +893,7 @@ no_email_address = "ই-মেল ঠিকানা অনুপস্থিত no_items_selected = "কোন উপাদান নির্বাচিত হয় নি" no_proxied_user = "কোন প্রক্সি ব্যবহারকারী নেই (নিজের জন্য অনুরোধ করুন)" nohit_active_filters = "এক বা একাধিক ফিল্টার এই অনুসন্ধানটির সঙ্গে যুক্ত আছে। প্রলেখ সংখ্যা বেশি পেতে ফিল্টার অপসারণ করুন।" +nohit_busy = "এই মুহূর্তে কোনও উত্তর দেওয়ার জন্য সিস্টেমটি খুব ব্যস্ত । অনুগ্রহ করে পরে আবার চেষ্টা করুন ।" nohit_change_tab = 'আপনার অনুসন্ধানটি "%%activeTab%%" ট্যাবে সীমিত আছে। প্রলেখ সংখ্যা বেশি পেতে অন্যান্য ট্যাব ব্যবহার করুন।' nohit_filters = "এই অনুসন্ধানের জন্য ফিল্টার প্রয়োগ করুন:" nohit_heading = "ফলাফল নেই!" @@ -1203,6 +1210,7 @@ renew_all = "সকল উপাদান নবীকরন করুন" renew_determine_fail = "আমরা নির্ধারণ করতে পারিনি যে আপনার উপাদানটি নবায়ন করা যাবে। কর্মীদের সাথে যোগাযোগ করুন" renew_empty_selection = "কোনো উপাদান নির্বাচন করা হয় নি" renew_error = "আমরা আপনার উপাদানটি পুনর্নবীকরণ করতে পারিনি - কর্মীদের সাথে যোগাযোগ করুন" +renew_error_summary = "ত্রুটির কারণে {count, plural, =1 {1 আইটেম} other {# আইটেমগুলি}} রিনিউ করতে অক্ষম ।" renew_fail = "এই উপাদানটি পুনর্নবীকরণ করা যাবে না" renew_item = "উপাদান নবীকরণ" renew_item_due = "উপাদানটি আগামী 24 ঘন্টার মধ্যে ফেরত দিতে হবে" @@ -1215,6 +1223,7 @@ renew_item_requested = "এই উপাদানটি একজন ব্য renew_select_box = "উপাদান নবীকরণ" renew_selected = "নির্বাচিত উপাদানগুলি নবীকরণ" renew_success = "পুনর্নবীকরণ সফল" +renew_success_summary = "সফলভাবে {count, plural, =1 {1 আইটেম} other {# আইটেমগুলি}} পুনর্নবীকরণ করা হয়েছে ।" Renewed = "পুনর্নবীকরণ" Request full text = "পূর্ণ পাঠের জন্য অনুরোধ" request_in_transit = "পিকাপ স্থানের মধ্যে" @@ -1286,6 +1295,7 @@ seconds_abbrev = "সেকেন্ড" see all = "সবগুলি দেখুন" See also = "আরও দেখুন" see_all_ellipsis = "সবগুলি দেখুন…" +Select multiple filters = "একাধিক ফিল্টার নির্বাচন করুন" Select this record = "নথিটি নির্বাচন করুন" Select your carrier = "আপনার ক্যারিয়ার নির্বাচন করুন" select_all = "সমস্ত এন্ট্রি নির্বাচন করুন" @@ -1346,6 +1356,8 @@ sort_due_date_desc = "প্রলেখ প্রত্যাবর্তনে sort_relevance = "প্রাসঙ্গিকতা" sort_return_date_asc = "প্রলেখ ফেরত দেওয়ার তারিখ (পুরাতন তারিখ আগে)" sort_return_date_desc = "প্রলেখ ফেরত দেওয়ার তারিখ (নতুন তারিখ আগে)" +sort_saved = "তারিখ সংরক্ষণ করুন (নতুনতম প্রথম)" +sort_saved_asc = "তারিখ সংরক্ষণ করুন (প্রাচীনতম প্রথম)" sort_title = "আখ্যা" sort_year = "তারিখ অধোগামী অনুযায়ী সাজান" sort_year_asc = "তারিখ ঊর্ধ্বগামী অনুযায়ী সাজান" @@ -1354,6 +1366,7 @@ Source Title = "সম্পদ আখ্যা" spell_expand_alt = "অনুসন্ধান সম্প্রসারণ করুন" spell_suggest = "বিকল্প অনুসন্ধান করুন" Staff View = "স্টাফেদের বিবরণ দেখুন" +standalone_record_link = "স্বতন্ত্র রেকর্ড" Start a new Advanced Search = "একটি নতুন বিস্তৃত অনুসন্ধান শুরু করুন" Start a new Basic Search = "একটি নতুন মৌলিক অনুসন্ধান শুরু করুন" Start Page = "পৃষ্ঠা আরম্ভ" @@ -1366,6 +1379,7 @@ storage_retrieval_request_available = "পিকআপের জন্য উপ storage_retrieval_request_cancel = "সঞ্চিত তথ্যোদ্ধারের অণুরোধটি বাতিল হয়েছে" storage_retrieval_request_cancel_all = "সমস্ত সঞ্চিত তথ্যোদ্ধার অণুরোধগুলি বাতিল হয়েছে" storage_retrieval_request_cancel_fail = "আপনার অনুরোধ বাতিল করা হয় নি। আরও সহায়তার জন্য সঞ্চারন ডেস্কে যোগাযোগ করুন" +storage_retrieval_request_cancel_fail_items = "%%count%% রিকোয়েস্ট(গুলি) বাতিল করা যায়নি" storage_retrieval_request_cancel_selected = "নির্বাচিত সঞ্চিত তথ্যোদ্ধার অণুরোধ বাতিল করা হয়েছে" storage_retrieval_request_cancel_success = "আপনার অনুরোধ সম্পূর্নরূপে বাতিল করা হয়েছে" storage_retrieval_request_cancel_success_items = "%%count%% অনুরোধগুলি সম্পূর্নরূপে বাতিল করা হয়েছে" @@ -1452,6 +1466,7 @@ toggle_dropdown = "ড্রপডাউন টগল করুন" Too Many Email Recipients = "অত্যধিক ইমেল প্রাপক" too_many_favorites = "এই তালিকাটি একবারে সমস্ত কিছু প্রদর্শনের জন্য অত্যন্ত বড়। আরো তালিকার মধ্যে আপনার পছন্দতালিকাগুলি সাজান বা ট্যাগগুলির ব্যবহার সীমিত করার চেষ্টা করুন।" too_many_new_items = "একটি একক তালিকায় অনেকগুলি নতুন উপাদান প্রদর্শন করা হয়েছে। আপনার অনুসন্ধান সীমিত করার চেষ্টা করুন" +too_many_query_terms = "অনুগ্রহ করে আপনার অনুসন্ধানটি আরো সহজ করুন; এতে সিস্টেমের সীমার (%%maxTerms%%) চেয়ে অতিরিক্ত শব্দ (%%terms%%) রয়েছে ।" too_many_reserves = "একটি একক তালিকায় অনেকগুলি কোর্স মজুদ করে প্রদর্শন করা হয়েছে। আপনার অনুসন্ধান সীমিত করার চেষ্টা করুন" top_facet_label = "%%label%% আপনার সার্চের মধ্যে" Topic = "বিষয়" @@ -1532,6 +1547,8 @@ What am I looking at = "এখানে আমি কি খুঁজছি?" widen_prefix = "আপনার সন্ধানকে প্রসারের চেষ্টা করুন" wiki_link = "উইকিপিডিয়া দ্বারা উপলব্ধ" with filters = "ফিল্টার দিয়ে" +worldcat_group_related_editions = "গ্রুপ সম্পর্কিত সংস্করণ" +worldcat_group_variant_records = "গ্রুপ ভেরিয়েন্ট রেকর্ড" Year of Publication = "প্রকাশনার বছর" You do not have any fines = "আপনার কোন জরিমানা নেই" You do not have any holds or recalls placed = "আপনি কোন হোল্ড রাখেন নি বা তলব স্থাপন করেননি" diff --git a/languages/ca.ini b/languages/ca.ini index 71fd2e6f737..6c7dcc8ce08 100644 --- a/languages/ca.ini +++ b/languages/ca.ini @@ -88,6 +88,7 @@ APA Citation = "Cita APA" APA Edition Citation = "Cita APA (7th ed.)" applied_filter = "Filtre aplicat:" applied_filters = "Filtres actius:" +Apply filters = "Aplica filtres" Archival Material = "Material d'arxiu" Article = "Article" Ask a Librarian = "Pregunteu al bibliotecari" @@ -441,6 +442,7 @@ explain_coord = "* %%coord%% (ajust per nombre de coincidències en comparació explain_difference_score = "diferència amb la puntuació més alta" explain_disabled = "L'explicació està desactivada per a %%searchClassId%%" explain_for_search = "Explicació per a la cerca" +explain_function_query_label = "Funció" explain_modified_value = "Producte de %%relevanceValue%% (valor de rellevància)" explain_modifier = "amb un modificador de %%modifier%%" explain_record_score = "puntuació del registre" @@ -448,6 +450,7 @@ explain_relevance = "Id del registre: %%recordId%% trobat amb un valor de rellev explain_relevance_score = "Puntuació de rellevància" explain_result_list_chart_title = "Puntuació: %%score%%" explain_result_list_hint = "Puntuació de rellevància. Fes clic per veure una explicació detallada." +explain_show_raw = "Mostra explicació en brut" explain_sum = "suma" explain_top_relevance = "Rellevància del resultat superior" Export = "Exportar" @@ -632,6 +635,7 @@ hold_edit_title = "Canviar informació de reserva" hold_empty_selection = "No hi ha reserves seleccionades" hold_error_age_restricted = "No es pot reservar el material per restriccions d'erat." hold_error_blocked = "NO teniu permisos suficients per fer una reserva d’aquest ítem" +hold_error_current_loan_patron_group = "Aquest article no es pot sol·licitar actualment." hold_error_fail = "Ha fallat la vostra petició. Contacteu amb el taulell de préstec per sol·licitar ajuda" hold_error_item_not_holdable = "Aquest exemplars no es pot demanar." hold_error_not_holdable = "Aquest material no es pot demanar." @@ -679,6 +683,7 @@ ill_request_available = "Disponible per recollir" ill_request_cancel = "Cancela la petició de prèstec interbibliotecari" ill_request_cancel_all = "Cancelar totes les peticions de prèstec interbibliotecari" ill_request_cancel_fail = "La petició no es pot cancel·lar. Si us plau, contacteu amb el taulell de préstec." +ill_request_cancel_fail_items = "%%count%% sol·licitud(es) no s'ha(n) pogut cancel·lar" ill_request_cancel_selected = "Cancelar les peticions seleccionades de prèstec interbibliotecari" ill_request_cancel_success = "La vostra petició s’ha cancel·lat correctament" ill_request_cancel_success_items = "%%count%% petició(ns) s’han cancel·lat correctament" @@ -851,6 +856,7 @@ More options = "Més opcions" More Summon results = "Més resultats de Summon…" More Topics = "Més matèries" more_authors_abbrev = "et al." +more_by_author = "També de %%name%%" more_ellipsis = "més…" more_info_toggle = "Veure/amagar més informació." more_options_ellipsis = "Més opcions…" @@ -897,6 +903,7 @@ no_email_address = "Falta l'adreça de correu electrònic." no_items_selected = "No hi ha ítems seleccionats" no_proxied_user = "No usuari delegat (sol·licitud per a tu mateix)" nohit_active_filters = "Heu aplicat una o més facetes a aquesta cerca. Si elimineu algun filtre obtindreu més resultats.." +nohit_busy = "El sistema està massa ocupat per oferir una resposta ara mateix. Si us plau, torna-ho a intentar més tard." nohit_change_tab = 'Has estar cercana a "%%activeTab%%". Jauría de cerca en altera pestañees per obtener más resultaste:' nohit_filters = "Filtres aplicats a aquesta cerca:" nohit_heading = "No s'han trobat resultats!" @@ -1213,6 +1220,7 @@ renew_all = "Renovar tots els ítems" renew_determine_fail = "No podem determinar si l’ítem es pot renovar. Contacteu amb algú del personal." renew_empty_selection = "No hi ha ítems seleccionats" renew_error = "No podem renovar el(s) ítem(s) - Contacteu amb algú del personal" +renew_error_summary = "No s'ha pogut renovar {count, plural, =1 {1 article} other {# articles}} a causa d'errors." renew_fail = "Aquest ítem no es pot renovar" renew_item = "Renovar ítem" renew_item_due = "Venciment de l’ítem en les properes 24 hores" @@ -1225,6 +1233,7 @@ renew_item_requested = "Aquest ítem ha estat sol·licitat per un altre usuari" renew_select_box = "Renovar ítem" renew_selected = "Renovar ítems seleccionats" renew_success = "Renovació correcte" +renew_success_summary = "S'ha renovat correctament {count, plural, =1 {1 article} other {# articles}}." Renewed = "Renovat" Request full text = "Demanar el text complet" request_in_transit = "En trànsit cap a la biblioteca de recollida" @@ -1296,6 +1305,7 @@ seconds_abbrev = "sec" see all = "veure tots" See also = "Vegeu també" see_all_ellipsis = "veure tots…" +Select multiple filters = "Selecciona diversos filtres" Select this record = "Seleccionar aquest register" Select your carrier = "Seleccioneu transport" select_all = "Selecciona totes les entrades" @@ -1356,6 +1366,8 @@ sort_due_date_desc = "Data de venciment (més propera primer)" sort_relevance = "Rellevància" sort_return_date_asc = "Data de retorn (més antiga primer)" sort_return_date_desc = "Data de retorn (més propera primer)" +sort_saved = "Data de desament (més recent primer)" +sort_saved_asc = "Data de desament (més antic primer)" sort_title = "Títol" sort_year = "Data Descendent" sort_year_asc = "Data Ascendent" @@ -1364,6 +1376,7 @@ Source Title = "Títol font" spell_expand_alt = "Ampliar cerca" spell_suggest = "Alternatives de cerca" Staff View = "Visualització del personal" +standalone_record_link = "Enllaç a registre independent" Start a new Advanced Search = "Començar una nova cerca avançada" Start a new Basic Search = "Començar una nova cerca bàsica" Start Page = "Pàgina d’inici" @@ -1376,6 +1389,7 @@ storage_retrieval_request_available = "Disponible per recollir" storage_retrieval_request_cancel = "Cancelar peticions al magatzem" storage_retrieval_request_cancel_all = "Cancelar totes les peticions al magatzem" storage_retrieval_request_cancel_fail = "La petició no es pot cancel·lar. Si us plau, contacteu amb el taulell de préstec." +storage_retrieval_request_cancel_fail_items = "%%count%% sol·licitud(es) no s'ha(n) pogut cancel·lar" storage_retrieval_request_cancel_selected = "Cancelar les peticions seleccionades al magatzem" storage_retrieval_request_cancel_success = "La vostra petició s’ha cancel·lat correctament" storage_retrieval_request_cancel_success_items = "%%count%% petició(ns) s’han cancel·lat correctament" @@ -1462,6 +1476,7 @@ toggle_dropdown = "Alternar desplegable" Too Many Email Recipients = "Massa destinataris de correus-e" too_many_favorites = "Aquesta llista és massa llarga per visualitzar-la de cop. Intenteu reconfigurar els favorits en més llistes o limitar l’ús d’etiquetes." too_many_new_items = "Hi ha massa ítems nous per visualitzar-los en una única llista. Intenteu limitar la cerca." +too_many_query_terms = "Simplifica la teva consulta; conté més termes (%%terms%%) del límit del sistema (%%maxTerms%%)." too_many_reserves = "Hi ha massa bibliografia recomanada per visualitzar-la en una única llista. Intenteu limitar la cerca." top_facet_label = "%%label%% dins de la cerca" Topic = "Tema" @@ -1542,6 +1557,8 @@ What am I looking at = "Què estic cercant?" widen_prefix = "Amplia la teva cerca a" wiki_link = "Proporcionat per Wikipedia" with filters = "amb filtres" +worldcat_group_related_editions = "Agrupa edicions relacionades" +worldcat_group_variant_records = "Agrupa registres variants" Year of Publication = "Any de publicació" You do not have any fines = "No teniu cap sanció" You do not have any holds or recalls placed = "No teniu cap reserva o reclamació" diff --git a/languages/cs.ini b/languages/cs.ini index 671504343cf..e414575f854 100644 --- a/languages/cs.ini +++ b/languages/cs.ini @@ -75,6 +75,7 @@ APA Citation = "Citace podle APA" APA Edition Citation = "Citace podle APA (7th ed.)" applied_filter = "Použitý filtr:" applied_filters = "Použité filtry:" +Apply filters = "Použít filtry" Archival Material = "Archivní materiály" Article = "Článek" Ask a Librarian = "Zeptejte se knihovníka" @@ -428,6 +429,7 @@ explain_coord = "* %%coord%% (úprava podle počtu shod ve srovnání s vyhledá explain_difference_score = "rozdíl oproti nejlepšímu skóre" explain_disabled = "Funkce vysvětlení vyhledávání je deaktivována pro %%searchClassId%%" explain_for_search = "Vysvětlení vyhledávání" +explain_function_query_label = "Funkce" explain_modified_value = "Součin %%relevanceValue%% (hodnota relevance)" explain_modifier = "s modifikátorem %%modifier%%" explain_record_score = "skóre záznamu" @@ -435,6 +437,7 @@ explain_relevance = "Id záznamu: %%recordId%% nalezeno s hodnotou relevance %%r explain_relevance_score = "Skóre relevance" explain_result_list_chart_title = "Skóre: %%score%%" explain_result_list_hint = "Skóre relevance. Kliknutím zobrazíte podrobné vysvětlení." +explain_show_raw = "Zobrazit nezpracované vysvětlení" explain_sum = "součet" explain_top_relevance = "Relevance nejlepších výsledků" Export = "Exportovat" @@ -619,6 +622,7 @@ hold_edit_title = "Upravit rezervace" hold_empty_selection = "Nebyly vybrány žádné požadavky" hold_error_age_restricted = "Požadavek nemohl být vytvořen, u tohoto dokumentu je věkové omezení." hold_error_blocked = "Nemáte dostatečné oprávnění k zadání požadavku na tento dokument." +hold_error_current_loan_patron_group = "Tuto položku nyní není možné vyžádat." hold_error_fail = "Váš požadavek selhal. Kontaktujte pracovníky knihovny pro další informace." hold_error_item_not_holdable = "Na tuto jednotku nelze vytvořit požadavek." hold_error_not_holdable = "Na tento dokument nelze vytvořit požadavek." @@ -666,6 +670,7 @@ ill_request_available = "Připraveno k vyzvednutí" ill_request_cancel = "Zrušit požadavek na meziknihovní výpůjčku" ill_request_cancel_all = "Zrušit všechny požadavky na meziknihovní výpůjčky" ill_request_cancel_fail = "Váš požadavek se nepodařilo zrušit. Pro další informace kontaktujte pracovníky knihovny." +ill_request_cancel_fail_items = "%%count%% požadavek/ků nebylo možné zrušit" ill_request_cancel_selected = "Zrušit vybrané požadavky na meziknihovní výpůjčky" ill_request_cancel_success = "Váš požadavek byl úspěšně zrušen." ill_request_cancel_success_items = "Bylo zrušeno %%count%% požadavků" @@ -838,6 +843,7 @@ More options = "Více možností" More Summon results = "Více výsledků hledání ze systému Summon…" More Topics = "Více témat" more_authors_abbrev = "a další" +more_by_author = "Také od %%name%%" more_ellipsis = "více…" more_info_toggle = "Zobrazit/skrýt podrobnosti." more_options_ellipsis = "Více možností…" @@ -884,6 +890,7 @@ no_email_address = "Chybí e-mailová adresa." no_items_selected = "Nic nebylo vybráno" no_proxied_user = "Žádný uživatel (vyžádejte si pro sebe)" nohit_active_filters = "Použili jste funkci upřesnění výsledků. Pokud tato upřesnění odstraníte, můžete získat více výsledků." +nohit_busy = "Systém je příliš zaneprázdněn na to, aby mohl poskytnout odpověď. Zkuste to prosím později." nohit_change_tab = 'Hledali jste v sekci "%%activeTab%%". Zkuste také další sekce, třeba budete úspěšní tam:' nohit_filters = "Filtry použité pro toto hledání:" nohit_heading = "Žádné výsledky!" @@ -1200,6 +1207,7 @@ renew_all = "Prodloužit všechny výpůjčky" renew_determine_fail = "Systém nemůže zjistit je-li možné prodloužit Vaše výpůjčky. Obraťte se na knihovníky s žádostí o pomoc. Děkujeme." renew_empty_selection = "Nic nebylo vybráno" renew_error = "Nelze prodloužit Vaše výpůjčky - obraťte se na knihovníky s žádostí o pomoc. Děkujeme." +renew_error_summary = "Kvůli chybě se nepodařilo prodloužit {count, plural, =1 {1 jednotku} other {# jednotek}}." renew_fail = "Výpůjčku nelze prodloužit" renew_item = "Prodloužit výpůjčku" renew_item_due = "Jednotky s termínem vrácení v následujících 24 hodinách" @@ -1212,6 +1220,7 @@ renew_item_requested = "Tuto položku již požaduje jiný uživatel" renew_select_box = "Prodloužit výpůjčku" renew_selected = "Prodloužit vybrané" renew_success = "Prodloužení úspěšné" +renew_success_summary = "Podařilo se úspěšně prodloužit {count, plural, =1 {1 jednotku} other {# jednotek}}." Renewed = "Počet prodloužení" Request full text = "Vyžádat plný text" request_in_transit = "Na cestě do místa vyzvednutí" @@ -1283,6 +1292,7 @@ seconds_abbrev = " s." see all = "Zobrazit vše" See also = "Viz též" see_all_ellipsis = "Zobrazit vše…" +Select multiple filters = "Výběr více filtrů" Select this record = "Vybrat tento záznam" Select your carrier = "Vyberte Vašeho operátora" select_all = "Vyberte všechny položky" @@ -1343,6 +1353,8 @@ sort_due_date_desc = "Termínu pro vrácení sestupně" sort_relevance = "Relevance" sort_return_date_asc = "Data vrácení vzestupně" sort_return_date_desc = "Data vrácení sestupně" +sort_saved = "Datum uložení (od nejnovějších)" +sort_saved_asc = "Datum uložení (od nejstarších)" sort_title = "Název" sort_year = "Podle data sestupně" sort_year_asc = "Podle data vzestupně" @@ -1351,6 +1363,7 @@ Source Title = "Název zdroje" spell_expand_alt = "Rozšířit vyhledávání" spell_suggest = "Alternativní vyhledávání" Staff View = "UNIMARC/MARC" +standalone_record_link = "Samostatný záznam" Start a new Advanced Search = "Začít pokročilé vyhledávání" Start a new Basic Search = "Začít základní vyhledávání" Start Page = "Začít na straně" @@ -1363,6 +1376,7 @@ storage_retrieval_request_available = "Připraveno k vyzvednutí" storage_retrieval_request_cancel = "Zrušit požadavek na vyzvednutí ze skladu" storage_retrieval_request_cancel_all = "Zrušit všechny požadavky na vyzvednutí ze skladu" storage_retrieval_request_cancel_fail = "Váš požadavek se nepodařilo zrušit. Kontaktujte pracovníky knihovny pro další informace." +storage_retrieval_request_cancel_fail_items = "%%count%% požadavek/ků nebylo možné zrušit" storage_retrieval_request_cancel_selected = "Zrušit vybrané požadavky na vyzvednutí ze skladu" storage_retrieval_request_cancel_success = "Váš požadavek byl úspěšně zrušen." storage_retrieval_request_cancel_success_items = "Bylo zrušeno %%count%% požadavků" @@ -1449,6 +1463,7 @@ toggle_dropdown = "Přepnout rozbalovací nabídku" Too Many Email Recipients = "Bylo vloženo příliš mnoho příjemců zprávy" too_many_favorites = "Tento seznam obsahuje příliš mnoho položek. Zkuste rozdělit oblíbené položky do více seznamů, případně omezte počet tagů." too_many_new_items = "V jednom seznamu nelze zobrazit tolik položek. Zkuste upřesnit parametry Vašeho hledání." +too_many_query_terms = "Zjednodušte prosím svůj dotaz; obsahuje více termínů (%%terms%%), než je limit systému (%%maxTerms%%)." too_many_reserves = "V jednom seznamu nelze zobrazit tolik kurzů. Zkuste upřesnit parametry Vašeho hledání." top_facet_label = "%%label%% ve výsledcích tohoto hledání:" Topic = "Téma" @@ -1529,6 +1544,8 @@ What am I looking at = "Co to znamená?" widen_prefix = "Zkuste rozšířit hledání na" wiki_link = "Získáno z Wikipedie" with filters = "S omezeními" +worldcat_group_related_editions = "Skupina souvisejících vydání" +worldcat_group_variant_records = "Skupina podobných záznamů" Year of Publication = "Rok vydání" You do not have any fines = "Nemáte žádné upomínky" You do not have any holds or recalls placed = "Nemáte žádné rezervace ani objednávky" diff --git a/languages/de.ini b/languages/de.ini index 7163a9c15ec..1ef6ea8e147 100644 --- a/languages/de.ini +++ b/languages/de.ini @@ -76,6 +76,7 @@ APA Citation = "APA Zitierstil" APA Edition Citation = "APA-Zitierstil (7. Ausg.)" applied_filter = "Aktivierter Filter:" applied_filters = "Aktivierte Filter:" +Apply filters = "Filter anwenden" Archival Material = "Archivmaterial" Article = "Artikel" Ask a Librarian = "Fachauskunft der Bibliothek" @@ -622,6 +623,7 @@ hold_edit_title = "Bezeichnung der Reservation ändern" hold_empty_selection = "Keine Bestellungen ausgewählt" hold_error_age_restricted = "Aufgrund einer Altersbeschränkung für die Nutzung dieses Titels kann keine Bestellung erfolgen." hold_error_blocked = "Sie haben keine ausreichende Berechtigung, um diese Medium zu bestellen" +hold_error_current_loan_patron_group = "Titel aktuell nicht bestellbar." hold_error_fail = "Ihre Bestellung ist fehlgeschlagen. Bitten Sie Ihre Bibliothek um Unterstützung" hold_error_item_not_holdable = "Dieser Titel kann nicht ausgeliehen werden." hold_error_not_holdable = "Diese Medienart kann nicht ausgeliehen werden." @@ -669,6 +671,7 @@ ill_request_available = "Abholbereit" ill_request_cancel = "Fernleihanfrage widerrufen" ill_request_cancel_all = "Alle Fernleihanfragen widerrufen" ill_request_cancel_fail = "Ihre Bestellung konnte nicht annulliert werden. Bitten Sie Ihre Bibliothek um Unterstützung" +ill_request_cancel_fail_items = "%%count%% Bestellung(en) konnten nicht storniert werden." ill_request_cancel_selected = "Ausgewählte Fernleihanfragen widerrufen" ill_request_cancel_success = "Ihre Bestellung wurde erfolgreich annulliert" ill_request_cancel_success_items = "%%count%% Bestellung(en) wurden erfolgreich annulliert" @@ -841,6 +844,7 @@ More options = "Weitere Optionen" More Summon results = "Mehr Zitate …" More Topics = "Weitere Themen" more_authors_abbrev = "et al." +more_by_author = "Auch von %%name%%" more_ellipsis = "mehr …" more_info_toggle = "Mehr/weniger anzeigen." more_options_ellipsis = "Weitere Optionen …" @@ -887,6 +891,7 @@ no_email_address = "E-Mail-Adresse fehlt." no_items_selected = "Sie haben nichts ausgewählt" no_proxied_user = "Keine delegierte Anfrage (Anfrage für sich selbst)" nohit_active_filters = "Es wurden Suchfilter/Facetten verwendet. Ohne Filter kann die Ergebnisanzahl möglicherweise erhöht werden." +nohit_busy = "System ist aktuell zu stark ausgelastet, um ein Ergebnis anzuzeigen. Versuchen Sie es bitte später noch einmal." nohit_change_tab = "Sie haben im Reiter %%activeTab%% gesucht. Möglicherweise finden Sie etwas in einem anderen Reiter:" nohit_filters = "Verwendete Suchfilter." nohit_heading = "Keine Ergebnisse!" @@ -1203,6 +1208,7 @@ renew_all = "Alle Ausleihfristen verlängern" renew_determine_fail = "Es konnte nicht herausgefunden werden, ob die Ausleihfrist verlängert werden kann. Bitte kontaktieren Sie Ihre Bibliothek." renew_empty_selection = "Sie haben nichts ausgewählt" renew_error = "Die Ausleihfrist(en) konnten nicht verlängert werden - Bitte kontaktieren Sie Ihre Bibliothek" +renew_error_summary = "Kann wegen eines Fehlers nicht verlängert werden: {count, plural, =1 {1 Titel} other {# Titel}}." renew_fail = "Die Ausleihfrist konnte nicht verlängert werden" renew_item = "Ausleihfrist verlängern" renew_item_due = "Rückgabe innerhalb von 24 Stunden fällig" @@ -1215,6 +1221,7 @@ renew_item_requested = "Dieses Medium wurde von einem anderen Benutzer vorgemerk renew_select_box = "Ausleihfrist verlängern" renew_selected = "Fristverlängerung der ausgewählten Ausleihen" renew_success = "Verlängerung erfolgreich" +renew_success_summary = "Verlängerung erfolgreich {count, plural, =1 {1 Titel} other {# Titel}}." Renewed = "Verlängert" Request full text = "Volltext bestellen" request_in_transit = "Unterwegs zum Abholort" @@ -1286,6 +1293,7 @@ seconds_abbrev = "s" see all = "Alle anzeigen" See also = "Siehe auch" see_all_ellipsis = "Alle anzeigen …" +Select multiple filters = "Mehrere Filter auswählen" Select this record = "Datensatz auswählen" Select your carrier = "Wählen Sie Ihren Telefonanbieter aus" select_all = "Alle Einträge auswählen" @@ -1356,6 +1364,7 @@ Source Title = "Name der Quelle" spell_expand_alt = "Erweiterte Suche" spell_suggest = "Andere Suchmöglichkeiten" Staff View = "Internformat" +standalone_record_link = "Datensatz separat aufrufen" Start a new Advanced Search = "Neue erweiterte Suche starten" Start a new Basic Search = "Neue einfache Suche starten" Start Page = "Erste Seite" @@ -1368,6 +1377,7 @@ storage_retrieval_request_available = "Abholbereit" storage_retrieval_request_cancel = "Magazinbestellungen widerrufen" storage_retrieval_request_cancel_all = "Alle Magazinbestellungen widerrufen" storage_retrieval_request_cancel_fail = "Ihre Bestellung konnte nicht annulliert werden. Bitte kontaktieren Sie Ihre Bibliothek für weitere Hilfe" +storage_retrieval_request_cancel_fail_items = "%%count%% Bestellung(en) konnten nicht storniert werden" storage_retrieval_request_cancel_selected = "Ausgewählte Magazinbestellungen widerrufen" storage_retrieval_request_cancel_success = "Ihre Bestellung wurde erfolgreich annulliert" storage_retrieval_request_cancel_success_items = "%%count%% Bestellung(en) wurden erfolgreich annulliert" @@ -1454,6 +1464,7 @@ toggle_dropdown = "Untermenü aufklappen" Too Many Email Recipients = "Zu viele E-Mail-Adressen" too_many_favorites = "Diese Liste ist zu groß, um auf einmal angezeigt zu werden. Versuchen Sie, Ihre Favoriten in weitere Listen zu unterteilen oder mittels Tags einzuschränken." too_many_new_items = "Es gibt zu viele Einträge, um in einer einzigen Liste angezeigt zu werden. Versuchen Sie, Ihre Suche weiter einzuschränken." +too_many_query_terms = "Bitte vereinfachen Sie Ihre Suchanfrage - sie enthält mehr Begriffe (%%terms%%) als das System erlaubt (%%maxTerms%%)." too_many_reserves = "Es gibt zu viele Vormerkungen, um in einer einzigen Liste angezeigt zu werden. Versuchen Sie, Ihre Suche weiter einzuschränken." top_facet_label = "%%label%% innerhalb Ihrer Suche." Topic = "Thema" @@ -1534,6 +1545,8 @@ What am I looking at = "Wie funktioniert das?" widen_prefix = "Versuchen Sie die Suche auszuweiten nach" wiki_link = "Veröffentlicht in Wikipedia" with filters = "mit Suchfiltern" +worldcat_group_related_editions = "Ähnliche Ausgaben gruppieren" +worldcat_group_variant_records = "Ähnliche Datensätze gruppieren" Year of Publication = "Erscheinungsjahr" You do not have any fines = "Es sind keine Gebühren auf Ihrem Bibliothekskonto" You do not have any holds or recalls placed = "Es liegen keine Vormerkungen oder Bestellungen vor" diff --git a/languages/el.ini b/languages/el.ini index e0691971ecc..e149e1f0631 100644 --- a/languages/el.ini +++ b/languages/el.ini @@ -77,6 +77,7 @@ APA Citation = "Παραπομπή APA" APA Edition Citation = "Παραπομπή σε μορφή APA (7η εκδ.)" applied_filter = "Εφαρμοσμένο φίλτρο:" applied_filters = "Φίλτρα που έχουν εφαρμοστεί:" +Apply filters = "Εφαρμογή φίλτρων" Archival Material = "Αρχειακό Υλικό" Article = "Άρθρο" Ask a Librarian = "Ερώτηση σε βιβλιοθηκονόμο" @@ -430,6 +431,7 @@ explain_coord = "* %%coord%% (προσαρμόστε για αριθμό ταυ explain_difference_score = "Διαφορά με την κορυφαία βαθμολογία" explain_disabled = "Η επεξήγηση είναι απενεργοποιημένη για το %%searchClassId%%" explain_for_search = "Επεξήγηση αναζήτησης" +explain_function_query_label = "Λειτουργία" explain_modified_value = "Προιόν του %%relevanceValue%% (τιμή συνάφειας)" explain_modifier = "με έναν τροποποιητή %%modifier%%" explain_record_score = "βαθμολογία εγγραφής" @@ -437,6 +439,7 @@ explain_relevance = "ID εγγραφής: %%recordId%% βρέθηκε με τι explain_relevance_score = "Βαθμολογία Συνάφειας" explain_result_list_chart_title = "Βαθμολογία: %%score%%" explain_result_list_hint = "Βαθμολογία συνάφειας. Κάντε κλικ για να δείτε την αναλυτική περιγραφή." +explain_show_raw = "Εμφάνιση ακατέργαστης επεξήγησης" explain_sum = "άθροισμα" explain_top_relevance = "Συνάφεια Κορυφαίων Αποτελεσμάτων" Export = "Εξαγωγή" @@ -621,6 +624,7 @@ hold_edit_title = "Αλλαγή Πληροφορίας Κράτησης" hold_empty_selection = "Δεν επιλέχτηκαν κρατήσεις" hold_error_age_restricted = "Δεν μπορεί να γίνει κράτηση στο τεκμήριο λόγω ηλικιακών περιορισμών." hold_error_blocked = "Δεν έχετε δικαίωμα να υποβάλετε αίτημα κράτησης για αυτό το τεκμήριο" +hold_error_current_loan_patron_group = "Αυτό το τεκμήριο δεν μπορεί να ζητηθεί επί του παρόντος." hold_error_fail = "Το αίτημά σας απέτυχε. Απευθυνθείτε στην εξυπηρέτηση για βοήθεια" hold_error_item_not_holdable = "Δεν μπορεί να γίνει αίτηση για αυτό το τεκμήριο." hold_error_not_holdable = "Δεν μπορεί να γίνει αίτηση για αυτό το υλικό." @@ -668,6 +672,7 @@ ill_request_available = "Διαθέσιμο" ill_request_cancel = "Ακύρωση αιτήματος διαδανεισμού" ill_request_cancel_all = "Ακύρωση όλων των αιτημάτων διαδανεισμού" ill_request_cancel_fail = "Το αίτημά σας δεν ακυρώθηκε. Απευθυνθείτε στην εξυπηρέτηση για βοήθεια" +ill_request_cancel_fail_items = "%%count%% αιτήματα δεν μπόρεσαν να ακυρωθούν." ill_request_cancel_selected = "Ακύρωση των επιλεγμένων αιτημάτων διαδανεισμού" ill_request_cancel_success = "Το αίτημά σας ακυρώθηκε με επιτυχία" ill_request_cancel_success_items = "%%count%% Τα αιτήματά σας ακυρώθηκαν με επιτυχία" @@ -840,6 +845,7 @@ More options = "Περισσότερες επιλογές" More Summon results = "Περισσότερα αποτελέσματα (Summon)…" More Topics = "Περισσότερα θέματα" more_authors_abbrev = "κ.ά." +more_by_author = "Περισσότερα από %%name%%" more_ellipsis = "περισσότερα…" more_info_toggle = "Εμφάνιση/απόκρυψη περισσότερων πληροφοριών." more_options_ellipsis = "Περισσότερες επιλογές…" @@ -886,6 +892,7 @@ no_email_address = "Λείπει η διεύθυνση email." no_items_selected = "Δεν επιλέχθηκαν τεκμήρια" no_proxied_user = "(αίτημα για εσάς)" nohit_active_filters = "Ένα ή περισσότερα φίλτρα έχουν εφαρμοστεί σε αυτή την αναζήτηση. Αν τα φίλτρα αναζήτησης αφαιρεθούν, θα μπορέσετε να ανακτήσετε περισσότερα αποτελέσματα." +nohit_busy = "Το σύστημα είναι πολύ απασχολημένο για να δώσει απάντηση αυτή τη στιγμή. Παρακαλώ προσπαθήστε ξανά αργότερα." nohit_change_tab = 'Κάνετε αναζήτηση στην καρτέλα "%%activeTab%%". Μπορεί να βρείτε σχετικά αποτελέσματα σε μια από τις άλλες καρτέλες:' nohit_filters = "Ενεργά φίλτρα αναζήτησης:" nohit_heading = "Κανένα αποτέλεσμα!" @@ -1202,6 +1209,7 @@ renew_all = "Ανανέωση όλων των τεκμηρίων" renew_determine_fail = "Δεν μπορεί να βεβαιωθεί η ανανέωση του τεκμηρίου. Απευθυνθείτε στην εξυπηρέτηση για βοήθεια." renew_empty_selection = "Δεν έχουν επιλεγεί τεκμήρια" renew_error = "Δεν είναι δυνατή η ανανέωση του τεκμηρίου. Απευθυνθείτε στην εξυπηρέτηση για βοήθεια" +renew_error_summary = "Αδυναμία ανανέωσης {count, plural, =1 {1 τεκμηρίου} other {# τεκμηρίων}} λόγω σφαλμάτων." renew_fail = "Η ανανέωση του τεκμηρίου απέτυχε" renew_item = "Ανανέωση τεκμηρίου" renew_item_due = "Το τεκμήριο θα πρέπει να επιστραφεί μέσα στο επόμενο 24ωρο" @@ -1214,6 +1222,7 @@ renew_item_requested = "Το τεκμήριο αυτό έχει ζητηθεί renew_select_box = "Ανανέωση τεκμηρίου" renew_selected = "Ανανέωση επιλεγμένων τεκμηρίων" renew_success = "Η ανανέωση του τεκμηρίου πέτυχε" +renew_success_summary = "Επιτυχής ανανέωση {count, plural, =1 {1 τεκμηρίου} other {# τεκμηρίων}}." Renewed = "Ανανεώθηκε" Request full text = "Αίτημα για πλήρες κείμενο" request_in_transit = "Σε μεταφορά προς την τοποθεσία παραλαβής" @@ -1285,6 +1294,7 @@ seconds_abbrev = "δλ" see all = "Προβολή όλων" See also = "Δείτε επίσης" see_all_ellipsis = "Προβολή όλων…" +Select multiple filters = "Επιλογή πολλαπλών φίλτρων" Select this record = "Επιλογή αυτής της εγγραφής" Select your carrier = "Επιλογή παρόχου" select_all = "Επιλογή όλων των εγγραφών" @@ -1345,6 +1355,8 @@ sort_due_date_desc = "Ημ/νια επιστροφής (φθίνουσα)" sort_relevance = "Με σχετικότητα" sort_return_date_asc = "Επιστράφηκε (αύξουσα)" sort_return_date_desc = "Επιστράφηκε (φθίνουσα)" +sort_saved = "Ημερομηνία Αποθήκευσης (νεότερη πρώτα)" +sort_saved_asc = "Ημερομηνία Αποθήκευσης (παλαιότερη πρώτα)" sort_title = "Με Τίτλο" sort_year = "Με Ημερομηνία (φθιν.)" sort_year_asc = "Με Ημερομηνία (αυξ.)" @@ -1353,6 +1365,7 @@ Source Title = "Τίτλος πηγής" spell_expand_alt = "Επέκταση αναζήτησης" spell_suggest = "Εναλλακτικές αναζητήσεις" Staff View = "Λεπτομερής προβολή" +standalone_record_link = "Μεμονωμένη Εγγραφή" Start a new Advanced Search = "Ξεκινήστε νέα σύνθετη αναζήτηση" Start a new Basic Search = "Ξεκινήστε νέα αναζήτηση" Start Page = "Αρχική" @@ -1365,6 +1378,7 @@ storage_retrieval_request_available = "Διαθέσιμο" storage_retrieval_request_cancel = "Ακύρωση αιτήματος ανάκτησης από αποθήκη" storage_retrieval_request_cancel_all = "Ακύρωση όλων των αιτημάτων ανάκτησης από αποθήκη" storage_retrieval_request_cancel_fail = "Το αίτημά σας δεν ακυρώθηκε. Απευθυνθείτε στην εξυπηρέτηση για βοήθεια" +storage_retrieval_request_cancel_fail_items = "%%count%% αιτήματα δεν μπόρεσαν να ακυρωθούν" storage_retrieval_request_cancel_selected = "Ακύρωση των επιλεγμένων αιτημάτων ανάκτησης από αποθήκη" storage_retrieval_request_cancel_success = "Το αίτημά σας ακυρώθηκε με επιτυχία" storage_retrieval_request_cancel_success_items = "%%count%% Τα αιτήματά σας ακυρώθηκαν με επιτυχία" @@ -1451,6 +1465,7 @@ toggle_dropdown = "Εμφάνιση/απόκρυψη αναπτυσσόμενη Too Many Email Recipients = "Πάρα πολλοί παραλήπτες email" too_many_favorites = "Η λίστα είναι πολύ μεγάλη για να προβληθεί. κάντε ανακατανομή των αγαπημένων σας σε περισσότερες λίστες ή βάλτε περιορισμούς χρησιμοποιώντας ετικέτες." too_many_new_items = "Τα τεκμήρια είναι πάρα πολλά για να εμφανιστούν σε μια λίστα. Περιορίστε την αναζήτησή σας." +too_many_query_terms = "Παρακαλώ απλοποιήστε το ερώτημά σας. Περιέχει περισσότερους όρους (%%terms%%) από το όριο του συστήματος (%%maxTerms%%)." too_many_reserves = "Τα δεσμευμένα τεκμήρια είναι πάρα πολλά για να εμφανιστούν σε μια λίστα. Περιορίστε την αναζήτησή σας." top_facet_label = "%%label%% σχετικά με την αναζήτησή σας." Topic = "Θέμα" @@ -1531,6 +1546,8 @@ What am I looking at = "Τι είναι αυτό που βλέπω;" widen_prefix = "Διευρύνετε την έρευνά σας σε" wiki_link = "Παρέχεται από τη Wikipedia" with filters = "με φίλτρα" +worldcat_group_related_editions = "Ομαδοποίηση Σχετιζόμενων Εκδόσεων" +worldcat_group_variant_records = "Ομαδοποίηση Group Variant Records" Year of Publication = "Έτος έκδοσης" You do not have any fines = "Δεν έχετε πρόστιμα" You do not have any holds or recalls placed = "Δεν έχετε αιτήματα κράτησης ή ανάκλησης" diff --git a/languages/en.ini b/languages/en.ini index 56ec648c798..e82696e28f5 100644 --- a/languages/en.ini +++ b/languages/en.ini @@ -76,6 +76,7 @@ APA Citation = "APA Citation" APA Edition Citation = "APA (7th ed.) Citation" applied_filter = "Applied Filter:" applied_filters = "Applied Filters:" +Apply filters = "Apply filters" Archival Material = "Archival Material" Article = "Article" Ask a Librarian = "Ask a Librarian" @@ -208,6 +209,7 @@ Change Password = "Change Password" change_email_disabled = "You are not allowed to change your email address at this time" change_email_verification_reminder = "Submitting this form will send an email to the new address; you will have to click on a link in the email before the change will take effect." change_notification_email_message = "A request was just made to change your email address at %%library%%. If you did not initiate this request, you may wish to log in at %%url%% and confirm the integrity of your account. Please contact support at %%email%% if you have questions or concerns." +change_notification_email_message_no_contact_email = "A request was just made to change your email address at %%library%%. If you did not initiate this request, you may wish to log in at %%url%% and confirm the integrity of your account. Please contact support if you have questions or concerns." change_notification_email_subject = "Account Email Change Notification" channel_add_more = "Add more channels like this" channel_browse = "Browse more records" @@ -280,6 +282,7 @@ confirm_storage_retrieval_request_cancel_selected_text = "Do you wish to cancel Connecting of library cards is not supported = "Connecting of library cards is not supported" consortial_vufind_recommend_heading = "Borrow from a Partner Library" consortial_vufind_recommend_intro_html = '<a href="%%url%%" target="_blank">%%result_count%% result(s)</a> found at partner libraries.' +Content Advice = "Content Advice" Contents = "Contents" Contributing Source = "Contributing Source" Contributors = "Contributors" @@ -622,6 +625,7 @@ hold_edit_title = "Change Hold Information" hold_empty_selection = "No holds were selected" hold_error_age_restricted = "A hold cannot be placed due to age restriction on the material." hold_error_blocked = "You do not have sufficient privileges to place a hold on this item" +hold_error_current_loan_patron_group = "This item cannot currently be requested." hold_error_fail = "Your request failed. Please contact the circulation desk for further assistance" hold_error_item_not_holdable = "This item cannot be requested." hold_error_not_holdable = "This material cannot be requested." @@ -748,6 +752,7 @@ Keyword Filter = "Keyword Filter" Kit = "Kit" Language = "Language" large = "Large" +Laser Disc = "Laser Disc" Last = "Last" Last Login = "Last Login" Last Modified = "Last Modified" @@ -884,11 +889,15 @@ No Preference = "No Preference" No reviews were found for this record = "No reviews were found for this record" No saved logins = "No saved logins" No Tags = "No Tags" +no_course_listed = "No Course Listed" +no_department_listed = "No Department Listed" no_description = "Description not available." no_email_address = "Email address missing." +no_instructor_listed = "No Instructor Listed" no_items_selected = "No Items were Selected" no_proxied_user = "No proxied user (request for yourself)" nohit_active_filters = "One or more facet filters have been applied to this search. If you remove filters, you may retrieve more results." +nohit_busy = "The system is too busy to provide a response right now. Please try again later." nohit_change_tab = 'You have been searching in the "%%activeTab%%" tab. You may find something in one of the other tabs:' nohit_filters = "Filters currently applied to this search:" nohit_heading = "No Results!" @@ -1086,6 +1095,8 @@ Photo = "Photo" Physical Description = "Physical Description" Physical Object = "Physical Object" pick_up_location = "Pickup Location" +pick_up_location_label_delivery = "Delivery Address" +pick_up_location_label_hold_shelf = "Pickup Location" Place a Hold = "Place a Hold" Place of birth = "Place of birth" Place of death = "Place of death" @@ -1221,6 +1232,8 @@ renew_success = "Renewal Successful" renew_success_summary = "Successfully renewed {count, plural, =1 {1 item} other {# items}}." Renewed = "Renewed" Request full text = "Request full text" +request_group_fulfillment_method_delivery = "Delivery" +request_group_fulfillment_method_hold_shelf = "Pick up" request_in_transit = "In Transit to Pickup Location" request_place_text = "Place a Request" request_submit_text = "Submit Request" @@ -1245,6 +1258,7 @@ results_citing_title_note = "Note that this may not be a complete list of citati results_citing_title_search_link = "Items that cite this one" Resumption Token = "Resumption Token" Return Date = "Return Date" +Review = "Review" Review by = "Review by" Reviews = "Reviews" Save = "Save" @@ -1290,6 +1304,7 @@ seconds_abbrev = "s" see all = "see all" See also = "See also" see_all_ellipsis = "see all…" +Select multiple filters = "Select multiple filters" Select this record = "Select this record" Select your carrier = "Select your carrier" select_all = "Select all entries" @@ -1460,6 +1475,7 @@ toggle_dropdown = "Toggle Dropdown" Too Many Email Recipients = "Too Many Email Recipients" too_many_favorites = "This list is too large to display all at once. Try rearranging your saved items into more lists or limiting using tags." too_many_new_items = "There are too many new items to display in a single list. Try limiting your search." +too_many_query_terms = "Please simplify your query; it contains more terms (%%terms%%) than the system limit (%%maxTerms%%)." too_many_reserves = "There are too many course reserves to display in a single list. Try limiting your search." top_facet_label = "%%label%% within your search." Topic = "Topic" @@ -1540,13 +1556,15 @@ What am I looking at = "What am I looking at?" widen_prefix = "Try widening your search to" wiki_link = "Provided by Wikipedia" with filters = "with filters" +worldcat_group_related_editions = "Group Related Editions" +worldcat_group_variant_records = "Group Variant Records" Year of Publication = "Year of Publication" You do not have any fines = "You do not have any fines" You do not have any holds or recalls placed = "You do not have any holds or recalls placed" You do not have any interlibrary loan requests placed = "You do not have any interlibrary loan requests placed" You do not have any items checked out = "You do not have any items checked out" You do not have any library cards = "You do not have any library cards" -You do not have any saved resources = "You do not have any saved resources. Perform a search and use the Save to List button to save items." +You do not have any saved resources = "You do not have any saved items. Perform a search and use the Save to List button to save items." You do not have any storage retrieval requests placed = "You do not have any storage retrieval requests placed" You must be logged in first = "You must be logged in first" Your Account = "Your Account" diff --git a/languages/es.ini b/languages/es.ini index 09ff9e07dc4..de7bc745670 100644 --- a/languages/es.ini +++ b/languages/es.ini @@ -77,6 +77,7 @@ APA Citation = "Cita APA" APA Edition Citation = "Cita APA (7a ed.)" applied_filter = "Filtro Aplicado:" applied_filters = "Filtros aplicados:" +Apply filters = "Aplicar filtros" Archival Material = "Material de Archivo" Article = "Artículo" Ask a Librarian = "Consulte a un Bibliotecario" @@ -430,6 +431,7 @@ explain_coord = "* %%coord%% (ajustar según el número de coincidencias en comp explain_difference_score = "diferencia con respecto a la puntuación más alta" explain_disabled = "La explicación está desactivada para %%searchClassId%%" explain_for_search = "Explicación de la búsqueda" +explain_function_query_label = "Función" explain_modified_value = "Producto de %%relevanceValue%% (valor de relevancia)" explain_modifier = "con un modificador de %%modifier%%" explain_record_score = "puntuación récord" @@ -437,6 +439,7 @@ explain_relevance = "Id. de registro: %%recordId%% encontrado con un valor de re explain_relevance_score = "Puntuación de relevancia" explain_result_list_chart_title = "Puntaje: %%score%%" explain_result_list_hint = 'Puntuación de relevancia. Haga clic para ver una explicación detallada".' +explain_show_raw = "Mostrar explicación en bruto" explain_sum = "total" explain_top_relevance = "Máxima relevancia de los resultados" Export = "Exportar" @@ -621,6 +624,7 @@ hold_edit_title = "Cambiar información de reserva" hold_empty_selection = "ninguna reserva fue seleccionada" hold_error_age_restricted = "No se puede realizar una reserva debido a la restricción de edad en el material." hold_error_blocked = "No cuenta con privilegios para reservar el elemento" +hold_error_current_loan_patron_group = "Este artículo no se puede solicitar actualmente." hold_error_fail = "Su solicitud ha fallado. Contacte con el personal del mostrador para ayuda" hold_error_item_not_holdable = "Este ítem no puede ser solicitado." hold_error_not_holdable = "Este material no puede ser solicitado." @@ -668,6 +672,7 @@ ill_request_available = "Disponible para llevar" ill_request_cancel = "Cancelar solicitud de préstamo interbibliotecario" ill_request_cancel_all = "Cancelar todas las solicitudes de préstamo interbibliotecario" ill_request_cancel_fail = "Su solicitud no ha sido cancelada, por favor contactar con el mostrador de circulación para más ayuda" +ill_request_cancel_fail_items = "%%count%% No se pudieron cancelar las solicitudes" ill_request_cancel_selected = "Cancelar las solicitudes de préstamo interbibliotecario seleccionadas" ill_request_cancel_success = "Su solicitud ha sido cancelada" ill_request_cancel_success_items = "%%count%% Su solicitud fue cancelada" @@ -840,6 +845,7 @@ More options = "Más opciones" More Summon results = "Más resultados solicitados…" More Topics = "Más Temas" more_authors_abbrev = "et al." +more_by_author = "También por %%name%%" more_ellipsis = "más…" more_info_toggle = "Mostra/ocultar más información" more_options_ellipsis = "Más opciones…" @@ -886,6 +892,7 @@ no_email_address = "Falta dirección de correo electrónico." no_items_selected = "No se han seleccionado elementos" no_proxied_user = "Usuario no apoderado (solicitar por ti mismo)" nohit_active_filters = "Uno o más filtros de faceta se han aplicado a esta búsqueda. Si remueve filtros, es posible recuperar más resultados." +nohit_busy = 'El sistema está demasiado ocupado para brindar una respuesta en este momento. Inténtelo nuevamente más tarde".' nohit_change_tab = "Ha estado buscando en %%activeTab%% tab. Podría encontrar algo en uno de los otros tabs:" nohit_filters = "Filtros aplicados a esta búsqueda" nohit_heading = "¡Sin Resultados!" @@ -1202,6 +1209,7 @@ renew_all = "Renovar todos los elementos" renew_determine_fail = "No podemos determinar si el elemento puede ser renovado, contactar con un miembro del equipo" renew_empty_selection = "Ningún elemento seleccionado" renew_error = "No podemos renovar los elementos – Contactar con un miembro del equipo" +renew_error_summary = "No se puede renovar {count, plural, =1 {1 ítem} other {# ítems}} debido a errores." renew_fail = "Este elemento no puede ser renovado" renew_item = "Renovar elemento" renew_item_due = "Vence en 24 horas" @@ -1214,6 +1222,7 @@ renew_item_requested = "Este elemento ha sido solicitado por otro usuario" renew_select_box = "Renovar elemento" renew_selected = "Renovar elementos seleccionados" renew_success = "Renovación realizada exitosamente" +renew_success_summary = "Renovación exitosa {count, plural, =1 {1 ítem} other {# ítems}}." Renewed = "Renovado" Request full text = "Solicitar texto completo" request_in_transit = "En Tránsito para recoger en la biblioteca" @@ -1285,6 +1294,7 @@ seconds_abbrev = "s" see all = "ver todos" See also = "Ver también" see_all_ellipsis = "ver todos…" +Select multiple filters = "Seleccionar varios filtros" Select this record = "Seleccione este registro" Select your carrier = "Seleccione su compañía" select_all = "Seleccionar todas las entradas" @@ -1345,6 +1355,8 @@ sort_due_date_desc = "Fecha de vencimiento (la más reciente primero)" sort_relevance = "Relevancia" sort_return_date_asc = "Fecha de devolución (el más antiguo primero)" sort_return_date_desc = "Fecha de devolución (el más reciente primero)" +sort_saved = "Guardar fecha (la más reciente primero)" +sort_saved_asc = "Guardar fecha (la más antigua primero)" sort_title = "Título" sort_year = "Fecha Descendente" sort_year_asc = "Fecha Ascendente" @@ -1353,6 +1365,7 @@ Source Title = "Fuente del Título" spell_expand_alt = "Expander búsqueda" spell_suggest = "Buscar alternativas" Staff View = "Vista Equipo" +standalone_record_link = "Registro independiente" Start a new Advanced Search = "Iniciar nueva Búsqueda Avanzada" Start a new Basic Search = "Iniciar nueva Búsqueda Básica" Start Page = "Página de inicio" @@ -1365,6 +1378,7 @@ storage_retrieval_request_available = "Disponible para llevar" storage_retrieval_request_cancel = "Cancelar solicitudes de recuperación de almacenamiento" storage_retrieval_request_cancel_all = "Cancelar todas las solicitudes de recuperación de almacenamiento" storage_retrieval_request_cancel_fail = "Su solicitud no ha sido cancelada, por favor contactar con el mostrador de circulación para más ayuda" +storage_retrieval_request_cancel_fail_items = "%%count%%No se pudieron cancelar las solicitudes" storage_retrieval_request_cancel_selected = "Cancelar las solicitudes de recuperación de almacenamiento seleccionadas" storage_retrieval_request_cancel_success = "Su solicitud ha sido cancelada" storage_retrieval_request_cancel_success_items = "%%count%% Su solicitud fue cancelada" @@ -1451,6 +1465,7 @@ toggle_dropdown = "Menú desplegable" Too Many Email Recipients = "Demasiados destinatarios de correo electrónico" too_many_favorites = "Lista muy larga para desplegar. Intente con sus favoritos en más de una lista o delimitelos con etiquetas." too_many_new_items = "Demasiados nuevos elementos para desplegar en una sola lista. Intente limitando su búsqueda." +too_many_query_terms = "Por favor simplifique su consulta; contiene más términos (%%terms%%) que el límite del sistema (%%maxTerms%%)." too_many_reserves = "Demasiadas reservas de cursos para desplegar en una sola lista. Intente limitando su búsqueda." top_facet_label = "%%label%% dentro de su búsqueda." Topic = "Tópico" @@ -1531,6 +1546,8 @@ What am I looking at = "¿Qué estoy viendo?" widen_prefix = "Intente ampliando su búsqueda a" wiki_link = "proporcionado por Wikipedia" with filters = "con filtros" +worldcat_group_related_editions = "Ediciones relacionadas con el grupo" +worldcat_group_variant_records = "Registros de variantes de grupo" Year of Publication = "Año de Publicación" You do not have any fines = "Usted no tiene multas" You do not have any holds or recalls placed = "No tiene reservas ni reclamos" diff --git a/languages/eu.ini b/languages/eu.ini index 0bf281d3280..2500f8246cb 100644 --- a/languages/eu.ini +++ b/languages/eu.ini @@ -631,10 +631,13 @@ account_block_options_missing = "Hautaketa batzuk ezabatu dira zure kontuak zerb account_checkouts_due = "Ejemplares vencidos pronto" account_checkouts_overdue = "Ejemplares vencidos" account_has_alerts = "Tu cuenta tiene alertas" +account_menu_label = "Kontuari buruzko informazio eta aukerak" account_normal_checkouts = "Mailegatutako aleak" account_requests_available = "Eramateko eskuragarri" account_requests_in_transit = "Iragaitzazkoa: jasotzeko kokapena" +account_requests_other = "Beste estatu bat" Add a Library Card = "Gehitu liburutegirako txartela" +Add a Library Card using login = "Erantsi bazkide txartel bat login instituzionala erabiltzen" Add a Note = "Ohar bat erantsi" Add Tag = "Etiketa erantsi" Add Tags = "Etiketak erantsi" @@ -653,6 +656,7 @@ add_tag_error = "Errorea: etiketak ezin izan dira gorde" add_tag_note = "Hutsuneek etiketak banantzen dituzte. Komatxoak erabili etiketek hitz bat baino gehiago badute." add_tag_success = "Etiketak gordeak" add_to_favorites_html = "Erantsi <em>%%title%%</em> Gehitu gogokoetan" +Additional data = "Datu gehigarriak" Address = "Helbidea" adv_search_all = "Eremu guztiak" adv_search_author = "Egilea" @@ -676,18 +680,25 @@ advSearchError_notFound = "Ez da aurkitu" ajax_load_interrupted = "Carga interrumpida" ajaxview_label_information = "Argibidea" ajaxview_label_tools = "Tresnak" +alert_email_address = "Programatutako alertaren emaitzak helbide elektroniko honetara bidaliko dira:" All = "Guztiak" All Fields = "Eremu guztiak" All Pages Loaded = "Orri guztiak kargatuta" All Text = "Testu osoa" alphabrowse_matches = "Emaitzak" alphabrowselink_html = 'Bilatu %%index%% hasiera honekin <a href="%%url%%">%%from%%</a>.' +Always ask me = "Galde egidazu beti" An error has occurred = "Errore bat gertatu da" An error occurred during execution; please try again later. = "Errorea izan zen egikaritu zen bitartean; mesedez saiatu berriro." AND = "ETA" +and = "eta" anonymous_tags = "Etiketa anonimoak" APA Citation = "APA aipamena" +APA Edition Citation = "APA (7th ed.) Zitazioa" applied_filter = "Iragazkia aplikatuta:" +applied_filters = "Aplikatutako iragazkiak:" +Apply filters = "Aplikatu iragazkiak" +Archival Material = "Artxiboko materiala" Article = "Artikulua" Ask a Librarian = "Galdetu liburuzainari" Associated country = "Herri elkartua" @@ -697,8 +708,12 @@ authentication_error_admin = "Momentu honetan ezin dugu zure saioa ireki. Mesede authentication_error_blank = "Sisteman sartzeko informazioa ezin da hutsik utzi." authentication_error_creation_blocked = "Ez duzu kontua sortzeko baimenik" authentication_error_denied = "Ezin zara sartu." +authentication_error_email_not_verified_html = 'Zure helbide elektronikoa oraindik ez da egiaztatu. Mesedez, egiaztatu zure spam iragazkia. Beharrezkoa bada, <a href="%%url%%">egiaztapen-mezua birbidal dezakegu</a>.' +authentication_error_expired = "Autentifikazio-eskaera iraungi egin da" +authentication_error_in_progress = "Dagoeneko autentifikazio-eskaera izapidetzen ari da. Mesedez, saiatu berriro geroago, hasi behar baduzu." authentication_error_invalid = "Datuak ez dira zuzenak." authentication_error_loggedout = "Orain deskonektatuta zaude." +authentication_error_session_ip_mismatch = "Autentifikazio-eskaera beste saio batekin eta IP helbide batekin hasi zen. Sarbidea ukatzen da." authentication_error_technical = "Errore teknikoa. Mesedez, beranduago saiatu." Author = "Egilea" Author Browse = "Egilea arakatu" @@ -726,6 +741,7 @@ Be the first to leave a comment = "Izan zaitez lehena ohar bat uzten" Be the first to tag this record = "Izan zaitez lehena erregistro honi etiketa jartzen" Bibliographic Details = "Xehetasun bibliografikoak" Bibliography = "Bibliografia" +blender_backend_error_message = "%%error%% -- %%label%%" Blu-ray Disc = "Blu-ray Diskoa" Book = "Liburua" Book Bag = "Liburu-poltsa" @@ -739,9 +755,14 @@ bookbag_full = "Completo" bookbag_full_msg = "Su Mochila está Llena" bookbag_is_empty = "Su Mochila está Vacía" Bookmark = "Sare sozialak" +bookplate_label = "%%title%% exlibris" Books = "Liburuak" Borrowing Location = "Maileguaren kokapena" +bound_with_heading_multiple_callnumbers = "Edukiak sailkapen/signatura honetarako: [%%callnumber%%]" +bound_with_heading_one_callnumber = "Edukiak" +bound_with_intro = "Hurrengo lan guztiak aurreko sailkapen/signaturan aurkitu dira" Braille = "Braille" +Breadcrumbs = "Ogi-apurren bidea" Brief View = "Bista laburra" Browse = "Arakatu" Browse Alphabetically = "Bilaketa alfabetikoki" @@ -764,6 +785,7 @@ bulk_error_missing = "Algún dato falta. Su solicitud no fue exitosa" bulk_export = "Exportar Seleccionados" bulk_export_not_supported = "Los registros seleccionados no soportan exportación masiva." bulk_fail = "Sentitzen dugu errorea. Mesedez, berriro saiatu." +bulk_limit_exceeded = "Item kopurua %%count%% aukeraketa muga %%limit%% gainditu du, mesedez, saiatu gutxiagorekin" bulk_noitems_advice = "Ez da inongo kopiarik aukeratu. Mesedez, berriro saiatu." bulk_print = "Imprimir los Seleccionados" bulk_save = "Guardar Seleccionados" @@ -784,11 +806,17 @@ By Recent = "Berrienen artean" By Region = "Herrialdearen araberan" By Title = "Izenburuaren arabera" By Topic = "Gaiaren arabera" +cached_record_warning = "Erregistroa ez dago erabilgarri bilaketa-indizean. Azken bertsioa bistaratzen da." Call Number = "Sailkapena" callnumber_abbrev = "Sailkapen-zenbakia" Cannot find record = "Erregistroa ez da aurkitu" Cannot find similar records = "Ez da antzeko erregistrorik aurkitu" +cannot set = "Ezin da ezarrit" +captcha_label_input = "Mesedez, sartu ikusten duzuena:" +captcha_label_multiple = "Aukeratu zure CAPTCHA gogokoena:" +captcha_label_single = "CAPTCHA:" captcha_not_passed = "CAPTCHA ez da gainditu" +captcha_technical_difficulties = "CAPTCHAk zailtasun teknikoengatik huts egin zuen" Cassette = "Kasetea" cat_establish_account = "Zure kontuaren profila ezartzeko, sartu ondorengoak:" cat_password_abbrev = "Pasahitza" @@ -797,7 +825,12 @@ Catalog Login = "Katalogorako sarbidea" Catalog Results = "Katalogoaren emaitzak" catalog_login_desc = "Idatzi zure datuak liburutegiko katalogora sartzeko." CD = "CDa" +Change Email Address = "Aldatu helbide elektronikoa" Change Password = "Aldatu pasahitza" +change_email_disabled = "Une honetan, ezin duzu zure helbide elektronikoa aldatu" +change_email_verification_reminder = "Formulario hau bidaltzeak posta elektroniko bat bidaliko du helbide berrira; posta elektronikoko esteka batean klik egin beharko duzu aldaketak eragina izan baino lehen." +change_notification_email_message = "Eskaera bidali da %%library%%-ra. Eskaera hau hasi ez baduzu, baliteke %%url%%-n saioa hasi eta zure kontuaren osotasuna baieztatu nahi izatea. Kezka edo galderarik baduzu, mesedez jarri harremanetan gurekin: %%email%% " +change_notification_email_subject = "Posta elektronikoaren aldatzearen jakinarazpena" channel_add_more = "Gehitu honen bezalako kanal gehiago" channel_browse = "Esploratu erregistro gehiago" channel_expand = "Bilatu lotutako kanalak" @@ -806,11 +839,13 @@ channel_search = "Erakutsi aleak bilaketaren emaitzetan" channel_searchbox_label = "Bilatu kanal gehiago:" Check Hold = "Comprobar Reserva" Check Recall = "Comprobar Solicitud" +check_profile = "Erabiltzailearen informazioa egiaztatu" Checked Out = "Maileguan" Checked Out Items = "Mailegatutako aleak" Checkedout = "Maileguan" Checkout Date = "Mailegu-Data" Chicago Citation = "Chicago Style aipamena" +Chicago Edition Citation = "Chicago Style (17th ed.) Zitazioa" child_record_count = "%%count%% erregistroak" child_records = "Edukiak/piezak" Choose a Category to Begin Browsing = "Aukeratu kategoria bat bilatzen hasteko" @@ -821,10 +856,12 @@ citation_issue_abbrev = "zk." citation_multipage_abbrev = "orr." citation_singlepage_abbrev = "orr." citation_volume_abbrev = "Lib." +Citations = "Zitazioak" Cite this = "Erreferentzia bihurtu" City = "Hiria" Clear = "Garbitu" clear_feedback_filter = "Ezabatu iragazkia" +clear_selection = "Ezabatu hautaketa (%%count%%)" clear_tag_filter = "Ezabatu iragazkia" close = "Itxi" Code = "Kodea" @@ -835,14 +872,17 @@ collection_disambiguation = "Bilduma gehiegi aurkitu dira" collection_empty = "Inongo item-ik ez dago ikusteko." collection_view_record = "Ikusi erregistroa" Collections = "Bildumak" +combined_jump_links_intro = "Saltatu emaitzetara:" comment_anonymous_user = "Anonimoa" comment_error_load = "Errorea: ezin dugu iruzkina erakutsi" comment_error_save = "Errorea: ezin dugu iruzkina gorde" Comments = "Iruzkinak" Company/Entity = "Konpainia/Entitatea" +Conference Proceeding = "Konferentzia-aktak" Configuration = "Konfigurazioa" confirm_delete = "Ziur zaude honako hau ezabatu nahi duzula?" confirm_delete_brief = "Ezabatu item-a?" +confirm_delete_feedback = "Ezabatu Feedback" confirm_delete_library_card_brief = "Liburutegirako txartela ezabatu?" confirm_delete_library_card_text = "Ziur zaude liburutegirako txartel hau ezabatzeaz?" confirm_delete_list_brief = "Ezabatu zerrenda?" @@ -855,27 +895,43 @@ confirm_hold_cancel_selected_text = "Aukeratutako erreserba guztiak ezabatu nahi confirm_ill_request_cancel_all_text = "Indarrean dauden zure liburutegi arteko mailegu guztiak ezeztatu nahi dituzu?" confirm_ill_request_cancel_selected_text = "Aukeratutako liburutegi arteko maileguak ezeztatu nahi dituzu?" confirm_new_password = "Baieztatu pasahitza berria" +confirm_renew_all_text = "Elementu guztiak berritu nahi dituzu?" +confirm_renew_selected_text = "Hautatutako elementuak berritu nahi dituzu?" confirm_storage_retrieval_request_cancel_all_text = "Zure biltegiratzea berreskuratzeko eskaera guztiak ezeztatu nahi dituzu?" confirm_storage_retrieval_request_cancel_selected_text = "Aukeratutako biltegiratzea berreskuratzeko eskaerak ezeztatu nahi dituzu?" +Connecting of library cards is not supported = "Connecting of library cards is not supported" +consortial_vufind_recommend_heading = "Eskatu mailegua bazkide den liburutegi batengandik" +consortial_vufind_recommend_intro_html = '<a href="%%url%%" target="_blank">%%result_count%% emaitza(k)</a> aurkitu dira bazkide diren liburutegietan.' Contents = "Edukia" Contributing Source = "Ekarpen-iturria" Contributors = "Egileak" +Cookie Settings = "Cookie baldintzak" Coordinates = "Coordenadas" Copies = "Kopiak" Copy = "Alea" +copy_to_clipboard_button_label = "Kopiatu" +copy_to_clipboard_failure_message = "Kopiatzeak huts egin du" +copy_to_clipboard_success_message = "Ondo kopiatu da" Copyright = "Copyright" Corporate Author = "Erakunde egilea" Corporate Authors = "Egile korporatiboa" +could_not_process_feedback = "Ezin izan da zure feedback prozesatu. Mesedez, saiatu berriro geroago." Country = "Herria" Course = "Ikastaroa" Course Reserves = "Erreserba egin ezazu" course_reserves_empty_list = "Ikastaroen erreserbak ez datoz bat." Cover Image = "Azala" +cover_source_label = "Azala-ren jatorria" Create a List = "Zerrenda sortu" Create New Account = "Kontu berria sortu" Create New Password = "Sortu pasahitza berria" Created = "Sortua" +csrf_validation_failed = "Eskaera ezin izan da prozesatu. Mesedez, saiatu berriro." +Data Set = "Datu multzoa" Database = "Datu-basea" +databases_recommend_heading = "Datubaseak" +databases_recommend_intro = "Funtzionaltasuna hobetzeko, bilatu zuzenean datu-base egokietan." +databases_recommend_link_to_all = "Datu baseen zerrenda guztia" Date = "Data" Date of birth = "Jaiotze data" Date of death = "Heriotza data" @@ -885,8 +941,10 @@ date_month_placeholder = "M" date_to = "a" date_year_placeholder = "A" Debug Information = "Arazketa-informazioa" +deep_paging_failure = "Bilaketa emaitzen orrialdea ez dago erabilgarri; birbideratu izan zara %%page%% orrialdera" default_list_title = "Nire gogokoenak" del_search = "Ezabatu" +del_search_num = "Ezabatu Bilaketa-talde %%num%%" Delete = "Ezabatu" delete_account_confirm = "Seguru zaude zure kontua ezabatu nahi duzula?" delete_account_description_html = "Gordetako bilaketak eta gogokoen zerrendak ezabatuko dira. Oraindik Kontu berri bat sor dezakezu geroago nahi baduzu." @@ -918,15 +976,18 @@ draw_searchbox_end = "Sagua askatu marraztea bukatzeko." draw_searchbox_start = "Klik egin hemen eta arrastatu laukizuzen bat marrazteko." Due = "Noiz arte" Due Date = "Itzulketa data" +dumb_captcha_prompt = "Mesedez, sartu aurreko testua alderantziz:" DVD = "DVD" eBook = "eBook" Edit = "Editatu" +edit = "editatu" Edit Library Card = "Editatu Liburutegirako Txartela" Edit this Advanced Search = "Bilaketa editatu" edit_list = "Zerrenda editatu" edit_list_fail = "Sentitzen dugu, ez zaude baimenduta editatzeko" edit_list_success = "Lista actualizada" Edition = "Edizioa" +EDS Results = "EDS Emaitzak" eds_expander_fulltext = "Bilatu artikuluetako testu osoan ere" eds_expander_relatedsubjects = "Aplikatu gai baliokideak" eds_expander_thesaurus = "Erabili lotutako hitzak" @@ -949,8 +1010,14 @@ Email address is invalid = "Helbide elektroniko baliogabea" Email Record = "Erregistroa posta elektronikoz bidali" Email this = "Bidali" Email this Search = "Emaitzak posta elektronikoz bidali" +email_change_pending_html = 'Mezu elektroniko bat zain daukazu %%pending%% egoera aldatuta. Mesedez, egin klik helbide horretara bidalitako egiaztapen-mezu elektronikoaren estekan, aldaketa osatzeko. Beharrezkoa bada, <a href="%%url%%">egiaztapen-mezua birbidal dezakegu</a>.' email_failure = "Errorea mezua bidaltzean" email_link = "Lotura" +email_login_desc = "Erabili esteka hau saioa hasteko. Saioa hasi ez baduzu, ez egin kasurik mezu honi segurtasunez. Kontuan izan estekak denbora mugatu baterako bakarrik balio duela eta helbide elektronikoa sartzeko erabili zenuen gailuarekin bakarrik balio duela." +email_login_link = "Esteka login-erako: <%%url%%>" +email_login_link_sent = "Zure helbide elektronikora sartzeko esteka bidali dugu. Une batzuk beharko dituzu lotura iristeko. Laster esteka jasotzen ez baduzu, mesedez, berrikusi zure spam-iragazkia ere." +email_login_requested = "Zure helbide elektronikoarekin saioa hasteko eskatu da %%title%% izenburuan." +email_login_subject = "Login izenburu honetarako %%title%%" email_maximum_recipients_note = "Gehienez %%max%% jasotzaile baimentzen dira." email_multiple_recipients_note = "Jasotzaile batzuk erants ditzakezu komaz bereiztuak." email_selected_favorites = "Aukeratutakoak bidali" @@ -959,15 +1026,42 @@ email_subject = "Gaia" email_success = "Mezua bidali da" Empty = "Hutsa" Empty Book Bag = "Liburu Saskia hutsik dago" +empty_search_disallowed = "Ezin da kontsulta hutsik egin bilaketa-helburuarekin" Enable Auto Config = "Gaitu Autokonfig." End Page = "Orriaren bukaera" +eol_ellipsis = "…" +epf_recommendations = "Argitalpenaren emaitzak" +epf_recommendations_more = "Argitalpenaren emaitza gehiago" +ePub Full Text = "ePub Testu Osoa" Era = "Garaia" +error_accessing_full_text = "Errore bat gertatu da zuk eskatutako testu osoa berreskuratzean." +error_creating_marc_xml = "Errorea bat gertatu da MARC formateatzean" error_inconsistent_parameters = "Un error ha ocurrido. Parámetros inconsistentes detectados." error_page_parameter_list_heading = "Eskaeraren parametroak" +error_too_many_requests = "Eskaera gehiegi" +error_too_many_requests_retry_after = "Eskaera gehiegi. Probatu berriro %%seconds%% segundo igaro ondoren." Exception = "Salbuespena" Excerpt = "Testu" exclude_facet = "[baztertu]" exclude_newspapers = "Aldizkarien artikuluak kendu" +explain_boost = "* %%boost%% (boost)" +explain_boost_description = "(boost) = %%boost_description%%" +explain_compared = "Garrantzia, goiko emaitzaren garrantziarekin alderatuta" +explain_coord = "* %%coord%% (bilaketarekin alderatuta, kointzidentzia-kopuruaren arabera doitu)" +explain_difference_score = "balio handienarekiko aldea" +explain_disabled = "azalpena desaktibatuta dago bilaketa honetarako: %%searchClassId%%" +explain_for_search = "Bilaketa honetarako azalpena" +explain_function_query_label = "Funtsio" +explain_modified_value = "%%relevanceValue%% -ko produktua(adierazgarritasun-balio)" +explain_modifier = "aldarazle honekin %%modifier%%" +explain_record_score = "errekor puntuazioa" +explain_relevance = "Erregistroaren Id %%recordId%% adierazgarritasun-balio honekin aurkitu da: %%relevanceValue%%" +explain_relevance_score = "Adierazgarritasun-baliozko puntuazioa" +explain_result_list_chart_title = "Puntuazioa: %%score%%" +explain_result_list_hint = "Adierazgarritasun-baliozko puntuazioa. Egin klik azalpen xehatua ikusteko." +explain_show_raw = "Berrikusi gabeko azalpena erakutsi" +explain_sum = "sum" +explain_top_relevance = "Emaitza altuenen adierazgarritasun-balioa" Export = "Esportatu" Export Favorites = "Exportar Favoritos" Export Items = "Esportatu Item-ak" @@ -985,8 +1079,29 @@ export_send = "Hasi esportatzen hona%%service%%" export_success = "Esportazioa burutua" export_to = "Nora %%target%%" export_unsupported_format = "Esportazio formatu hori ezin da erabili." +external_auth_access_heading = "Sartzeko eskaera" +external_auth_access_login_message = "Kanpoko aplikazio bat zure datuetarako sarbidea eskatzen ari da. Hasi saioa eskaera berrikusteko eta baimentzeko." +external_auth_allow_access = "Sarbidea eman" +external_auth_deny_access = "Sarbidea ukatu" external_auth_heading = "Baimendutako materialerako sarbidea" +external_auth_ils_prompt = "Eskatutako informazioaren zati bat liburutegiko katalogoko profiletik berreskuratzen da." external_auth_login_message = "Sartu zure erabiltzailearen izena eta pasahitza baimendutako materiala ikusteko" +external_auth_prompt_html = "sarbide-eskubide hauek eskatzen ditu:" +external_auth_scope_address = "Irakurri zure helbidea" +external_auth_scope_age = "Irakurri zure adina" +external_auth_scope_birthdate = "Irakurri zure jaiotze data" +external_auth_scope_block_status = "Egiaztatu zure kontuak blokerik baduen" +external_auth_scope_cat_id = "Irakurri liburutegiko erabiltzailearen identifikatzailea" +external_auth_scope_email = "Irakurri zure helbide elektronikoa" +external_auth_scope_library_user_id = "Irakurri hash bakarra, zure liburutegiko erabiltzailearen identifikatzailearen oinarrituta" +external_auth_scope_locale = "Irakurri zure indarrean dagoen hizkuntza" +external_auth_scope_name = "Irarkurri zure izena" +external_auth_scope_openid = "Irakurri zure erabiltzailearen identifikatzailea" +external_auth_scope_phone = "Irakurri zure telefono zenbakia" +external_auth_scope_profile = "Irakurri zure oinarrizko profilaren informazioa (nombre, idioma, jaiotze data)" +external_auth_scope_unique_id = "Irakurri zure identifikatzaile bakarra" +external_auth_scope_username = "Irakurri zure erabiltzailearen izena" +external_auth_scopes_none = "ez dago" external_auth_unauthorized = "Ez duzu baimendutako materiala ikusteko baimena" external_auth_unauthorized_desc = "Zure sartzeko metodoa ez du baimendutako materiala ikusteko baimenik. Mesedez, irten eta sartu berriro beste metodo bat erabiltzen." facet_list_empty = "No hay datos disponibles" @@ -1007,15 +1122,30 @@ fav_list_delete_cancel = "Zerrenda hori ez da ezabatu." fav_list_delete_fail = "Errorea gertatu da. Ez dugu zure zerrenda ezabatu." Fee = "Kuota" Feedback = "Feedback" +Feedback Management = "Feedback Kudeaketa" +feedback_delete_failure = "Feedback ezabaketak huts egin du" +feedback_delete_filter = "Iragazki hau erabiltzen ari zara - Formularioaren izena: %%formname%%, Orrialdearen URL: %%siteurl%%, Egoera: %%status%%" +feedback_delete_success = "%%count%% feedback-en erantzunak ezabatu dira." +feedback_delete_warning = "Kontuz! Ezabatuko dituzu %%count%% feedback mezuak" feedback_email = "Posta elektronikoa" +feedback_filter_empty = "Ez dago feedback-ik iragazki honetarako" feedback_help_label = "Laguntza behar al duzu?" feedback_name = "Feedback-Izena" feedback_response = "Eskerrik asko zure feedback bidaltzeagatik." +feedback_status_answered = "Erantzun da" +feedback_status_closed = "Itxita" +feedback_status_in progress = "Martxan dago" +feedback_status_open = "Ireki" +feedback_status_pending = "Egiteke" +feedback_status_update_failure = "Feedback egoeraren eguneratzeak huts egin du" +feedback_status_update_success = "Feedback egoeraren eguneratzea ondo egin da" feedback_title = "Bidali zure feedback!" Field of activity = "Jarduera-eremua" File Description = "Fitxategi deskribapena" Filter = "Iragazkia" +Filter Collection = "Iragazki bilduma" filter_tags = "Iragazteko etiketak" +filter_toggle_entries = "%%count%% iragazki" filter_wildcard = "Edozein" Find = "Bilatu" Find New Items = "Berriak bilatu" @@ -1028,12 +1158,16 @@ fine_limit_patron = "Ezin duzu gehiago berritu, mugara iritsi zara" Fines = "Multas" First = "Lehengoa" First Name = "Izena" +First Search Result = "Lehenengo bilaketaren emaitza" fix_metadata = "Bai, konpondu metadata; Itxarongo dut" footer_header_find_more = "Gehiago bilatu" footer_header_need_help = "Laguntza behar al duzu?" footer_header_search_options = "Bilaketa aukerak" for search = "bilaketa honetara" +Forget All = "Ahaztu guztia" +Forget Login = "Ahaztu Login" Forgot Password = "Pasahitza ahaztuta" +Form name = "Formularioaren izena" Format = "Formatua" From = "Nork" Full description = "Deskribapen osoa" @@ -1053,6 +1187,7 @@ Go to Standard View = "Ikuspegi arruntara joan" go_to_list = "Joan zerrendara" google_map_cluster = "Grupo" google_map_cluster_points = "Cluster Puntuak" +Government Document = "Gobernuaren Dokumentua" Grid = "Sareta" Group = "Taldea" group_AND = "ETA" @@ -1073,6 +1208,8 @@ history_delete = "Ezabatu historia" history_delete_link = "Lotura ezabatu" history_empty_search = "Hustu" history_limits = "Mugatzaileak" +history_login_html = 'Mesedez, <a href="%%url%%">log in</a> egin zure gordetako bilaketak ikusteko.' +history_no_saved_searches = "Ez duzu gordetako bilaketarik." history_no_searches = "Ez dago bilaketarik" history_purge = "Ezabatu" history_recent_searches = "Azken bilaketak" @@ -1080,6 +1217,7 @@ history_results = "Emaitzak" history_save = "Bilaketa gorde" history_save_link = "Gorde" history_saved_searches = "Gordetako bilaketak" +history_schedule = "Alerta egutegia" history_search = "Bilaketa" history_time = "Ordua" hold_available = "Eramateko eskuragarri" @@ -1087,15 +1225,38 @@ hold_available_until = "Disponible para recoger hasta %%date%%" hold_cancel = "Erreserba ezeztatu" hold_cancel_all = "Erreserba guztiak ezabatu" hold_cancel_fail = "Zure eskaera ezeztatua izan da. Mesedez, mostradoretik pasa." +hold_cancel_fail_items = "%%count%% eskaera ezin izan dira ezeztatu" hold_cancel_selected = "Aukeratutako erreserbak ezeztatu" hold_cancel_success = "Zure eskaera ezeztatua izan da" hold_cancel_success_items = "%%count%% Zure eskaera ezeztatua izan da" hold_date_invalid = "baliodun data sartu" hold_date_past = "data sartu" +hold_edit_conflicting_pickup_locations = "Hautatutako erreserbek jasotzeko kokapena ezartzeko aukera ezberdinak ditu. Editatu erreserba bat jasotzeko kokapena aldatzeko." +hold_edit_failed_items = "Eguneratzeak huts egin du %%count%% erreserbatan" +hold_edit_frozen = "Izozte-egoera" +hold_edit_frozen_set = "Izoztu (aldi baterako eten)" +hold_edit_frozen_through = "Izoztu honetaz zehar" +hold_edit_frozen_unset = "Bertsioa (jarraitu)" +hold_edit_no_change = "Aldaketarik ez" +hold_edit_selected = "Editatu Hautatutako Erreserbak" +hold_edit_success_items = "%%count%% erreserba eguneratuta" +hold_edit_title = "Aldatu erreserbaren informazioa" hold_empty_selection = "ez da erreserbarik aukeratu" +hold_error_age_restricted = "Erreserba ezin da egin, materialarekin lotutako adin-murriztapen baitu." hold_error_blocked = "Ezin duzu dokumentu hori erreserbatu" +hold_error_current_loan_patron_group = "Une honetan, item hau ezin da eskatu." hold_error_fail = "Zure eskaeran errorea egon da. Mesedez mostradoretik pasa lagundu zaitzaten" +hold_error_item_not_holdable = "Item hau ezin da eskatu." +hold_error_not_holdable = "Material hau ezin da eskatu." +hold_error_on_shelf_blocked = "Apalategiaren erreserbak ez daude erabilgarri." +hold_error_too_many_holds = "Erreserba ezin da egin erreserba-kopuru maximora iritsi baita." +hold_error_update_blocked_status = "Aldaketa batzuk edo guztiak eragozten dituen egoeran dago erreserba." +hold_error_update_failed = "Erreserba ezin da eguneratu." hold_expires = "Amaitua" +hold_frozen = "Izoztuta (aldi baterako etenda)" +hold_frozen_through = "Izoztuta (aldi baterako etenda) %%date%% data honetara arte" +hold_frozen_through_date_invalid = "Mesedez, sartu baliozko izozteko data" +hold_in_process = "Prozesuan" hold_invalid_pickup = "Kokapena ez da baliagarria. Berriz saiatu." hold_invalid_request_group = "Erreserbaren eskaera-taldea baliogabea da. Mesedez, saiatu berriro" hold_items_available = "Ezin duzu erreserbarik egin, ale erabilgarririk dagoelako." @@ -1104,11 +1265,18 @@ hold_place = "Eskaera egin" hold_place_fail_missing = "Zure eskaeran errorea gertatu da. Mostradoretik pasa lagundu zaitzaten." hold_place_success_html = 'Zure eskaera burutu da. <a href="%%url%%">Zure erreserbak</a>.' hold_profile_html = 'Erreserba eta gogoratze informaziorako, mesedez ezarri zure <a href="%%url%%">Liburutegiko Katalogorako Profila</a>.' +hold_proxied_by = "Nork eskatuta" +hold_proxied_for = "Nori eskatuta" hold_queue_position = "Ilara" hold_record_already_on_loan = "Dokumentua dagoeneko dago zuri mailegatuta" hold_request_group = "Eskatu duena" hold_requested_group = "Eskatu zuena" hold_required_by = "Jadanik ez da beharrezkoa" +hold_required_by_date_before_start_date = "Mesedez, sartu beharrezkoa data hasiera-data baino geroago" +hold_required_by_date_invalid = "Mesedez, sartu baliozko Nork eskatutakoa-data" +hold_required_by_optional = "Gero ez da behar (aukerakoa)" +hold_start_date = "Hasiera-data" +hold_start_date_invalid = "Mesedez, sartu baliozko hasiera-data" hold_success = "Eskaera baimendua" Holdings = "Aleari buruzko argibideak" Holdings at Other Libraries = "Beste liburutegietan" @@ -1124,6 +1292,7 @@ ill_request_available = "Eramateko eskuragarri" ill_request_cancel = "Ezeztatu liburutegi arteko mailegua egiteko eskaera" ill_request_cancel_all = "Ezeztatu liburutegi arteko mailegua egiteko eskaera guztiak" ill_request_cancel_fail = "Zure eskaera ezeztatua izan da. Mesedez, mostradoretik pasa." +ill_request_cancel_fail_items = "%%count%% eskaera ezin izan dira ezeztatu" ill_request_cancel_selected = "Ezeztatu aukeratutako liburutegi arteko mailegua egiteko eskaerak" ill_request_cancel_success = "Zure eskaera ezeztatua izan da" ill_request_cancel_success_items = "%%count%% Zure eskaera ezeztatua izan da" @@ -1158,6 +1327,7 @@ ils_offline_login_message = "Zure kontuaren xehetasunei buruzko informazioa ez d ils_offline_status = "Mantentze lanak direla eta, gure Liburutegia Kudeatzeko Sistema ez dago erabilgarri." ils_offline_title = "Sistema mantentze lanetan ari da" ils_transaction_history_disabled = "Mailegu-Historia ez dago erabiltzeko moduan liburutegi aktiboko txartelerako." +Image = "Irudia" Import Record = "Erregistroa hartu" in = "non" In This Collection = "Bilduma honetan" @@ -1172,6 +1342,7 @@ institutional_login_desc = "Idatzi zure kanpusaren sarean sartzeko erabiltzailea Instructor = "Instructor" Interlibrary Loan Requests = "Liburutegi arteko maileguaren eskaerak" Internet = "Internet" +interval_captcha_not_passed = "Ekintza hau %%delay%% segundo barru baino ezin da egin." Invalid Patron Login = "Login de Usuario Inválido" Invalid phone number. = "Telefono zenbaki baliogabea" Invalid Recipient Email Address = "Hartzailearen helbide elektronikoa ez da zuzena" @@ -1201,10 +1372,17 @@ Kit = "Kit" Language = "Hizkuntza" large = "Handia" Last = "Azkena" +Last Login = "Azken Login" Last Modified = "Azken aldaketa" Last Name = "Abizena" +Last Search Result = "Azken Bilaketa Emaitza" less = "gutxiago" less_ellipsis = "gutxiago..." +libguides_profile_suggestion_heading = "Gai-liburuzain batekin hitz egin" +libguides_recommendations = "Ikerketa Gidaren emaitzak" +libguides_recommendations_more = "Ikerketa Gidaren emaitza gehiago" +libguidesaz_recommendations = "Datubasearen emaitzak" +libguidesaz_recommendations_more = "Datubasearen emaitzak gehiago" libphonenumber_invalid = "Telefono zenbakia ez da zuzena" libphonenumber_invalidcountry = "Herriko aurrezenbakia ez da zuzena" libphonenumber_invalidregion = "Herrialdeko aurrezenbakia ez da zuzena:" @@ -1228,7 +1406,9 @@ Library Web Search = "Liburutegiaren web bilaketa" library_card_edit_password_placeholder = "Pasahitza berria" lightbox_error = "Errorea: ezin da kargatu" Limit To = "Bilaketa zehaztu" +Link to full results = "Emaitza guztiak ikusteko emaitzak" link_text_need_help = "Laguntza behar al duzu?" +Linked Full Text = "Lotutako Testu Osoa" List = "Zerrenda" List Tags = "Etiketa zerrena" list_access_denied = "Zerrenda hau ikusteko baimenik ez" @@ -1237,12 +1417,19 @@ load_tag_error = "Errorea: etiketa ezin da kargatu." Loading = "Lanean" loading_ellipsis = "Lanean..." Loan History = "Mailegu-Historia" +loan_history_all_purged = "Zure mailegu historioa ezabatu da" +loan_history_confirm_purge_all = "Zure mailegu-historiako mailegu guztiak ezabatu nahi dituzu?" +loan_history_confirm_purge_selected = "Zure mailegu-historiako hautatutako maileguak ezabatu nahi dituzu?" loan_history_empty = "ez duzu mailegurik mailegu-historian." +loan_history_purge_all = "Ezabatu Historia" +loan_history_purge_selected = "Ezabatu hautatutakoa" +loan_history_selected_purged = "Hautatutako maileguak zure mailegu historiatik ezabatu dira" Local Login = "Tokiko sarbidea" local_login_desc = "Idatzi hemen sartzeko zure erabiltzailearen izena eta pasahitza." Located = "Kokapena" Location = "Kokapena" Log Out = "Irten" +logged_in = "Saioa hasi duzu." Login = "Bazkideak" Login for full access = "Sartu zure identifikazio datuak sartze osoa izateko" login_disabled = "Login ez dago erabilgarri momentu honetan." @@ -1251,6 +1438,7 @@ Logout = "Irten" Main Author = "Egile nagusia" Main Authors = "Egile Nagusiak" Major Categories = "Kategoria nagusiak" +Manage Scheduled Alerts = "Kudeatu alerta programatuak" Manage Tags = "Kudeatu etiketak" Manuscript = "Eskuizkribua" Map = "Mapa" @@ -1265,18 +1453,23 @@ Message From Sender = "Igorlearen mezua" Metadata Prefix = "Metadata Aurrizki" Microfilm = "Mikrofilma" MLA Citation = "MLA aipamena" +MLA Edition Citation = "MLA (9th ed.) Zitazioa" Mobile Number = "Telefono Mugikorra" mobile_link = "Badirudi mugikorretik zaudela konektatuta, mugikorretarako bista gaitu?" +mobile_toggle_navigation_text = "Aldatu nabigazioa" Monograph Title = "Monografiaren izenburua" more = "Gehiago" More catalog results = "Emaitza gehiago" +More EDS results = "EDS emaitza gehiago…" More options = "Aukera gehiago" More Summon results = "Eskatutako emaitza gehiago..." More Topics = "Gai gehiago" more_authors_abbrev = "et al." +more_by_author = "%%name%% izenez ere bai" more_ellipsis = "Gehiago..." more_info_toggle = "Erakutsi/Ezkutatu info. gehiago." more_options_ellipsis = "Aukera gehiago..." +more_results_ellipsis = "Emaitza gehiago…" more_topics = "%%count%% gai gehiago" Most Recent Received Issues = "Oharra" Multiple Call Numbers = "Klasifikazio zenbaki bat baino gehiago" @@ -1292,25 +1485,34 @@ New Item Feed = "Dokumentu berriak" New Item Search = "Berriak" New Item Search Results = "Dokumentu berri baten bilaketaren emaitza" New Items = "Berriak liburutegietan" +New results found for search = "Emaitza gehiago aurkitu dira bilaketa honetarako" New Title = "Izenburu berria" +new_email_success = "Zure helbide elektronikoa ongi aldatu da" new_password = "Pasahitza berria" new_password_success = "Zure pasahitza aldatu da" +new_results_heading = "%%count%% emaitza berri" new_user_welcome_subject = "Zure berri kontua %%library%%" new_user_welcome_text = "Ongi etorria %%library%%. Kontu berri bat ireki da %%firstname%% %%lastname%%. zure erabiltzailea da %%username%%. Mesedez Konfiguratu orrialde honetan pasahitza: %%url%%" Newspaper = "Egunkaria" Next = "Hurrengoa" +Next Search Result = "Hurrengo bilaketa emaitza" No citations are available for this record = "Erregistro honetarako ez dago oharrik" No Cover Image = "Azalaren irudirik gabe" No dependency problems found = "Ez dira menpekotasun-arazorik aurkitu" No excerpts were found for this record. = "Dokumentu honetarako ez dago laburpenik" No library account = "Ez dago liburutegi-konturik" No new item information is currently available. = "Ez dago elementu berriei buruzko informaziorik" +No pickup locations available = "Ez dago erabilgarri dagoen jasotzeko kokapenik" No Preference = "Berdin dio" No reviews were found for this record = "Erregistro honek ez du iruzkinik" +No saved logins = "Ez dago gordetako saiorik" No Tags = "Etiketarik gabe" no_description = "Deskribapenik ez dago." +no_email_address = "Helbide elektronikoa ez da aurkitu." no_items_selected = "Ez duzu dokumenturik aukeratu" +no_proxied_user = "Ez dago proxy erabiltzailerik (egin eskaera zure izenean)" nohit_active_filters = "Fazeta iragazki bat edo gehiago erabili dira bilaketa honetarako. Iragazkirik ezabatu nahi baduzu, emaitza gehiago izango dituzu." +nohit_busy = "Sistema oso lanpetuta dago une honetan erantzuna emateko. Mesedez, proba ezazu geroago." nohit_change_tab = 'Bilatu duzu "%%activeTab%%"-en. Nahi baduzu, bila dezakezu beste batzuetan:' nohit_filters = "Iragazkirik gabe" nohit_heading = "Ez dago emaitzarik" @@ -1360,6 +1562,90 @@ number_decimal_point = "," number_thousands_separator = "." OAI Server = "OAI Zerbitzaria" Occupation = "Lanbidea" +od_account_noaccess = "Liburutegi honek ez du OverDrive edukietarako sarbiderik." +od_account_problem = "Arazo bat dago zure kontuarekin. %%message%%" +od_admin_menu = "OverDrive API" +od_audiobook-mp3 = "MP3 audio-liburua" +od_audiobook-overdrive = "OverDrive Entzuteko audio-liburua" +od_avail_avail = "Erabilgarri:" +od_avail_holds = "Erreserbak:" +od_avail_total = "Kopiak, guztira:" +od_but_cancel_hold = "Ezeztatu erreserba hau" +od_but_checkout = "OverDrive-n bidezko mailegua" +od_but_checkout_s = "Mailegua" +od_but_edit_hold = "Editatu erreserba" +od_but_edit_hold_conf = "Baieztatu eguneraketa" +od_but_gettitle = "Deskargatu eduki hau" +od_but_gettitle_s = "Deskargatu" +od_but_hold = "Egin erreserba OverDrive-n bidez" +od_but_hold_s = "Erreserbatu" +od_but_return = "Bueltatu titulu hau" +od_but_susp_hold = "Eten erreserba" +od_cancel_hold = "Ezeztatu OverDrive Erreserba" +od_cancel_hold_confirm = "Ziur zaude erreserba hau ezabatu nahi duzula?" +od_checkout = "OverDrive Mailegua" +od_code_connection_failed = "OverDrive-rako konexioak huts egin du. Arazoa jarraitzen badu, jarri harremanetan zure liburutegiarekin." +od_code_contentnotavail = "Eduki hau ez dago zure eremurako erabilgarri" +od_code_login_for_avail = "Login egin erabilgarritasunerako" +od_code_resource_not_found = "Titulua ez da aurkitu" +od_content = "OverDrive Edukia" +od_copies_available = "%%copies%% kopia erabilgarri" +od_dl_formats = "Deskargatzeko onartutako formatuak" +od_docheckout_failure = "Titulu hau ezin da mailegatu." +od_docheckout_success = "Titulu hau mailegatu da zuretzat. Iraungitze-data: %%expireDate%%." +od_early_return = "OverDrive Itzulera Goiztiar" +od_ebook-epub-adobe = "Adobe EPUB eBook" +od_ebook-epub-open = "EPUB eBook Irekia" +od_ebook-kindle = "Kindle Liburua" +od_ebook-mediado = "MediaDo Irakurketa-eBook" +od_ebook-overdrive = "OverDrive Irakurketa-eBook" +od_ebook-pdf-adobe = "Adobe PDF eBook" +od_ebook-pdf-open = "PDF eBook Irekia" +od_edit_hold_email = "Editatu erreserbarako helbide elektronikoa" +od_expires_on = "Titulu hau data honetan iraungiko da: %%due_date%%." +od_get_title = "OverDrive Deskarga" +od_gettitle_failure = "Baliteke titulu hau ezin deskargatu izatea." +od_help_linktext = "OverDrive Laguntza" +od_history = "OverDrive Historia" +od_hold = "OverDrive Erreserba" +od_hold_cancel_failure = "Erreserba ezeztatzeko eskaerak huts egin du." +od_hold_cancel_success = "Erreserba ongi ezeztatu da." +od_hold_email = "Erreserba-jakinarazpena egiteko helbide elektronikoa: %%holdEmailAddress%%." +od_hold_now_avail = "Erreserba hau mailegurako erabilgarri dago. Mailegua egun honetan iraungiko da %%expireDate%%." +od_hold_now_avail_s = "Mailegurako erabilgarri - Egun honetan iraungiko da %%expireDate%%" +od_hold_place_failure = "Erreserbaren eskaerak huts egin du." +od_hold_place_success = "Titulu hau erreserbatu da. Erreserben ilaran duzun posizioa hau da: %%holdListPosition%%." +od_hold_placed_on = "Erreserba egun honetan egin da: %%holdPlacedDate%%." +od_hold_queue = "Erreserben ilaran duzun posizioa %%holdPosition%% / %%numberOfHolds%% da" +od_hold_queue_s = "Posizioa: %%holdPosition%% / %%numberOfHolds%%" +od_hold_redelivery = "Erreserba %%days%% egunez eten da." +od_hold_redelivery_s = "%%days%% egunez eten da" +od_hold_susp_indef = "Erreserba hau mugagabe eten da." +od_hold_susp_indef_s = "Mugagabe etenda" +od_hold_update_failure = "Erreserba eguneratzeko eskaerak huts egin du. " +od_hold_update_success = "Erreserba ongi eguneratu da." +od_holds = "OverDrive Erreserbak" +od_info_unavail = "Informazio hau ez dago erabilgarri une honetan." +od_is_checkedout = "Baliabide hau maileguan duzu. Itzultze data: %%due_date%%." +od_is_on_hold = "Baliabide hau erreserbatuta daukazu." +od_loans = "OverDrive Maileguak" +od_mag_issue_ischeckedout = "maileguan" +od_magazine-overdrive = "Aldizkari digitala" +od_mycontent_help = 'Izenburu horiek deskargatzeko informazioa eta laguntza lortzeko, kontsultatu <a href="%%url%%">OverDrive Help</a>.' +od_none_found = "Ez dago aurkitutako titulurik." +od_resource_page = "OverDrive Baliabide orria" +od_return_confirm = "Ziur zaude titulu hau itzuli nahi duzula?" +od_return_failure = "Titulu hau ezin da itzuli." +od_return_success = "titulu hau itzuli da." +od_susp_after = "Entregatu %%days%% egun igaro ondoren" +od_susp_asap = "Entregatu ahalik eta lasterren" +od_susp_hold = "Erreserba eten" +od_susp_hold_confirm = "Zenbat denboraz eten nahiko zenuke erreserba hori?" +od_susp_hold_edit = "Editatu entrega-data" +od_susp_ind = "Mugagabe etenda" +od_unlimited = "Mugagabe" +od_video-streaming = "Streaming bideo fitxategi" +od_waiting = "{holds, plural, =1 {1 pertsona} other {# pertsona}} itxaroten" of_num_results = "#%%position%% -- %%total%% emaitzak" old_password = "Aurreko pasahitza" On Reserve = "Erreserbatua" @@ -1379,10 +1665,22 @@ Other Authors = "Beste egile batzuk" Other Editions = "Beste argitalpen batzuk" Other Libraries = "Beste liburutegi batzuk" Other Sources = "Beste baliabide batzuk" +other_versions_link = "Erakutsi beste bertsioak (%%count%%)" +other_versions_search_link = "Erakutsi bertsio guztiak (%%count%%)" +other_versions_title = "Beste bertsio batzuk (%%count%%)" Page not found. = "Orrialdea ez da aurkitu." +page_first = "Lehenengo orria" +page_last = "Azken orria" +page_next = "Hurrengo orria" +page_num = "Orria %%page%%" +page_prev = "Aurreko orrira" +page_reload_on_deselect_hint = "Orria berriz kargatuko da iragazki bat ezabatzean." +page_reload_on_select_hint = "Orria berriz kargatuko da iragazki bat hautatzen edo baztertzen denean." +pagination_label = "Orrikatzea" Password = "Erabiltzaile-zenbakia edo pasahitza" Password Again = "Idatzi berriz pasahitza" Password cannot be blank = "Pasahitza ezin da bete gabe utzi" +password_error_auth_old = "Lehen erabilitako pasahitza ez da baliozkoa" password_error_invalid = "Pasahitza berria ez da zuzena(ad. karaktereak ez dira zuzenak)" password_error_not_unique = "Pasahitza ez da aldatu" password_maximum_length = "Pasahitzarako gehienezko luzapena da %%maxlength%% karaktere" @@ -1392,11 +1690,21 @@ password_only_numeric = "Bakarrik zenbakiak" Passwords do not match = "Pasahitza ez da egokia" past_days = "{range, plural, =1 {Atzo} other {Azken # Egunak}}" patron_account_expires = "Amaitua" +patron_status_address_missing = "Zure helbidea falta da." +patron_status_card_blocked = "Liburutegiko txartel hau mailegurako blokeatuta dago." +patron_status_card_expired = "Zure liburutegiko txartela iraungi egin da." +patron_status_debarred_overdue = "Itzuli gabeko artikuluak dituzu atzeratuta." +patron_status_debt_limit_reached = "%%blockCount%% isunetan dituzu. Blokeatzeko muga hau da: %%blockLimit%%." +patron_status_guarantees_debt_limit_reached = "Zure bermeek %%blockCount%% isunetan dituzte. Blokeatzeko muga hau da: %%blockLimit%%." +patron_status_maximum_requests = "Eskaera aktiboen gehienezko kopurura iritsi zara (%%blockCount%%)." PDF Full Text = "PDF testu osoa" peer_reviewed = "Pareek aztertuta" peer_reviewed_limit = "Limitar a árticulos de revistas revisadas" +permanent_link = "Lotura iraunkorra" permission_denied = "Ekintza edo web-orririk eskatu duzu, baina ez duzu horretarako baimenik" permission_denied_title = "Baimena ukatua" +persistent_login_warning_email_message = "Zure kontuarekin lotutako ezohiko jarduera bat detektatu zen. Zure segurtasuna babesteko, gordetako login guztiak baliogabetu dira, eta saioa berriro hasteko eskatuko zaizu. Jakinarazpen hau zerbitzura itzulita aktibatu ez baduzu, zure kontu-jarduera berrikustea gomendatzen dizugu, ekintza ezezagunik ez dagoela ziurtatzeko." +persistent_login_warning_email_subject = "%%title%%: login egiteko saio baliogabea" Phone Number = "Telefonoa" Photo = "Argazkia" Physical Description = "Deskribapen fisikoa" @@ -1410,12 +1718,16 @@ Please check back soon = "Zuzena den egiaztatu" Please contact the Library Reference Department for assistance = "Jar zaitez liburutegiarekin harremanetan" Please enable JavaScript. = "Mesedez aktibatu JavaScript." Please upgrade your browser. = "Mesedez, eguneratu zure nabigatzailea" +Postcard = "Posta-txartel" +Poster = "Poster" Preferences = "Lehentasunak" Preferred Library = "Hobetsitako liburutegia" +preferred_library_default = "Aukera onena erabilgarri" Prev = "Aurrekoa" prev_ellipsis = "Aurrekoa..." Preview = "Aurrebista" Preview from = "Nondiko aurrebista" +Previous Search Result = "Aurreko Bilaketaren Emaitza" Previous Title = "Aurreko izenburua" Print = "Imprimir" Private = "Pribatua" @@ -1424,6 +1736,12 @@ Profile = "Profila" profile_update = "Eskatutakoaren arabera aktualizatu da" pronounced = "Ebakitua" Provider = "Hornitzailea" +proxied_user = "Egin eskaera proxy erabiltzaile batentzat" +proxy_hold_place_success_html = 'Zure proxy eskaera ongi egin da. <a href="%%url%%">Zure erreserbak</a>.' +proxy_list_by = "Erabiltzaile hauek zure ordezkariak dira:" +proxy_list_description = "Erabiltzaile baten ordezkariek baimena dute haren izenean ekintza batzuk egiteko." +proxy_list_for = "Zu zara ordezkaria erabiltzaile hauetarako:" +proxy_list_heading = "Ordezkariak" Public = "Publikoa" public_list_indicator = "Publikoa" Publication = "Argitalpena" @@ -1443,11 +1761,35 @@ query time = "Bilaketaren denbora" random_recommendation_title = "Zure emaitzeen araberako gomendioak" Range = "Maila" Range slider = "Rango de Deslizamiento" +rating_10 = "½ izar" +rating_100 = "5 izar" +rating_20 = "1 izar" +rating_30 = "1½ izar" +rating_40 = "2 izar" +rating_50 = "2½ izar" +rating_60 = "3 izar" +rating_70 = "3½ izar" +rating_80 = "4 izar" +rating_90 = "4½ izar" +rating_add_or_update = "Erantsi edo eguneratu balioztatzea (rating)" +rating_add_success = "Rating gordeta" +rating_breakdown_group_title = "%%from%%-tik %%to%%-ra" +rating_breakdown_percentage = "%%percentage%%%" +rating_disabled = "Rating desaktibatuta" +rating_heading = "‌" +rating_none = "Ez da oraindik baloratu" +rating_prompt = "Zure Rating" +rating_remove = "Ezabatu Rating" +rating_summary = "%%count%% ratings" +rating_summary_single = "1 rating" +rating_summary_unrated = "Erantsi lehenengo rating-a" Read the full review online... = "Azterketa osoa linean irakurri" Recall This = "Jakinarazi" recently_returned_channel_title = "Oraintsu itzulita" +recommend_links_text = "Probatu ere egin dezakezu:" Record Citations = "Dokumentuaren aipamena" Record Count = "Erregistro-zenbatzea" +Record in the Search Index = "Erregistroa bilaketa-indizean" Record Type = "Erregistro mota" Recover Account = "Berrezkuratu kontua" recovery_by_email = "Berreskuratu posta elektronikoren bidez" @@ -1464,6 +1806,7 @@ recovery_title = "Pasahitza berreskuratzea" recovery_too_soon = "Eskaera gehiegi, mesedez saiatu berriro geroago" recovery_user_not_found = "Ezin dugu aurkitu zure kontua" rectangle_center_message = "Hau laukizuzen nabarmenduaren erdigunea da" +Reference Material = "Erreferentzia-materiala" Refine Results = "Findu emaitzak" Region = "Eskualdea" relais_available = "Artikulu hau liburutegien arteko maileguaren bidez libre dago. Eskatzea gustatuko litzaizuke?" @@ -1477,12 +1820,16 @@ relais_success_message = "erabiltzailearen identifikatzaileko eskaera #%%id%% So Related Author = "Antzeko egilea" Related Items = "Antzeko dokumentuak" Related Subjects = "Antzeko gaiak" +Relevance = "Egokitasuna" +remember_me = "Gogoratu nire login hurrengo %%days%% egunez" +Remove filter = "Ezabatu iragazkia" Remove Filters = "Iragazkiak kendu" Remove from Book Bag = "Eliminar de la Mochila" renew_all = "Dokumentu guztiak berritu" renew_determine_fail = "Ezin da berritzea bermatu. Mesedez, liburutegiarekin harremanetan jarri" renew_empty_selection = "Ez da dokumenturik aukeratu" renew_error = "Ezin da dokumentu hau berritu. Mesedez, liburutegiarekin harremanetan jarri" +renew_error_summary = "Ezin dira berritu {count, plural, =1 {1 elementu} other {# elementu}} hauetako erroreak daudelako." renew_fail = "Ezin da dokumentu hau berritu" renew_item = "Dokumentua berritu" renew_item_due = "Epemuga 24 ordutan" @@ -1495,6 +1842,7 @@ renew_item_requested = "Dokumentu hau beste erabiltzaile batek eskatu du" renew_select_box = "Dokumentua berritu" renew_selected = "Aukeratutako dokumentuak berritu" renew_success = "Berriztapena egin da" +renew_success_summary = "Ongi berritu dira: {count, plural, =1 {1 elementu} other {# elementu}} hauetako." Renewed = "Berrituta" Request full text = "Eskaera: testu osoa" request_in_transit = "Iragaitzazkoa: jasotzeko kokapena" @@ -1504,12 +1852,21 @@ Requests = "Eskaerak" Reserves = "Erreserbak" Reserves Search = "Erresenben bilaketa" Reserves Search Results = "Signaturaren araberako bilaketaren emaitza" +reset_filters_button = "Iragazkiak berrezartzea" result_checkbox_label = "Aukera ezazu emaitza-zenbakia%%number%%" result_count = "%%count%% emaitzak" Results = "Emaitzak" results = "emaitzak" Results for = "Emaitzak" Results per page = "Orren araberako emaitza" +results_cited_by_title = 'Aipatutako emaitzak "%%title%%"' +results_cited_by_title_link_html = 'Aipatutako emaitzak <a href="%%url%%">%%title%%</a>' +results_cited_by_title_note = "Kontuan izan hau agian ez dela zitazioen zerrenda osoa." +results_cited_by_title_search_link = "Item honek aipatutako itemak" +results_citing_title = 'Emaitzak titulu hau aipatzen: "%%title%%"' +results_citing_title_link_html = 'Emaitzak titulu hau aipatzen: <a href="%%url%%">%%title%%</a>' +results_citing_title_note = "Kontuan izan hau agian ez dela zitazioen zerrenda osoa." +results_citing_title_search_link = "Item honek aipatutako itemak" Resumption Token = "Berrekitzeko Token-a" Return Date = "Itzultzeko data" Review by = "Berraztertu" @@ -1519,7 +1876,15 @@ Save Comment = "Iruzkina gorde" save_search = "Bilaketa gorde" save_search_remove = "Ezabatu" Saved in = "Gorde" +Saved Logins = "Gordetako Login-ak" saved_items = "Gogokoenak" +saved_login_actions = "Ekintzak" +saved_login_platform_and_browser = "Plataforma / Nabigatzailea" +schedule_daily = "Egunero" +schedule_explanation = "Jaso emaitza berriei buruzko alerta-mezuak, bilaketarako." +schedule_none = "Hauetako bat ere ezz" +schedule_weekly = "Astero" +Scheduled Alert Results = "Alerta programatuaren emaitzak" scholarly_limit = "Mugatu artikuluak gaiaren arabera" Scroll to Load More = "Erabili korritze-barra gehiago kargatzeko" Search = "Bilatu" @@ -1530,10 +1895,13 @@ Search Home = "Inicio de Búsqueda" Search Mode = "Bilaketa modua" Search Results = "Bilaketaren emaitzak" search results of = "Bilaketaren emaitzak" +Search sidebar = "Bilatu alboko barran" Search Tips = "Bilaketa egiteko aholkuak" Search Tools = "Bilaketa egiteko lanabesak" Search Type = "Bilaketa Mota" +Search within collection = "Bilatu bilduman" search_AND = "ETA" +search_backend_partial_failure = "Emaitza partzialak. Bilaketak hurrengo datuen jatorrietan huts egin du: %%sources%%" search_groups = "taldeak" search_match = "Operatzaileak" search_NOT = "BAINA EZ" @@ -1541,13 +1909,23 @@ search_OR = "EDO" search_save_success = "Búsqueda guardada con éxito" search_terms = "Términos de búsqueda" search_unsave_success = "Búsqueda guardada ha sido eliminada con éxito" +searchform_reset_button = "Ezabatu koltsulta" seconds_abbrev = "s" see all = "Guztiak ikusi" See also = "Ikus ere" see_all_ellipsis = "Guztiak ikusi..." +Select multiple filters = "Aukeratu Hautatu iragazki ugari" Select this record = "Aukeratu erregistro hau" Select your carrier = "Aukera ezazu zure hornitzailea" +select_all = "Aukeratu guztiak" select_all_on_page = "Orrialdea aukeratu" +select_item = "Aukeratu item bat ekintza gehiagorako" +select_item_checked_out_renew = "Aukeratu item bat eguneratzeko" +select_item_hold_cancel = "Aukeratu item bat erreserbak ezeztatzeko" +select_item_ill_request_cancel = "Aukeratu item bat liburutegien arteko maileguen eskaerak ezeztatzeko" +select_item_purge = "Aukeratu item bat purgatzeko" +select_item_storage_retrieval_request_cancel = "Aukeratu item bat biltegi berreskuratzeko eskaerak ezeztatzeko" +select_page_cart = "Aukeratu motxila dauden sarrera guztiak" select_pickup_location = "Aukeratu jasotzeko kokapena" select_request_group = "Aukeratu eskatze taldea" Selected = "Aukeratuta" @@ -1558,6 +1936,9 @@ Sensor Image = "Zentzumen irudia" Serial = "Aldizkako argitalpena" Series = "Saila" Set = "Aukera" +shortlink_redirect = "Hemen bideratuko zaitugu %%delay%% segundotan..." +Show = "Erakutsi" +show_filters_html = "Erakutsi iragazkiak (%%count%%)" showing_items_html = "Erakusten <strong>%%start%% - %%end%%</strong> Item-ak" showing_items_of_html = "Erakusten <strong>%%start%% - %%end%%</strong> -- <strong>%%total%%</strong> Item-ak" showing_results_for_html = "Erakusten <strong>%%start%% - %%end%%</strong> emaitzak bilaketa honetara '<strong>%%lookfor%%</strong>'" @@ -1567,11 +1948,13 @@ showing_results_of_html = "Erakusten <strong>%%start%% - %%end%%</strong> emaitz sidebar_close = "Itxi albokobarra" sidebar_expand = "Ireki albokobarra" Similar Items = "Antzeko izenburuak" +Site URL = "Orrialdearen URL" Skip to content = "Joan edukira" skip_confirm = "Pauso hau sahiestu nahi duzula ziur zaude?" skip_fix_metadata = "Ez konpondu metadata momentu honetan." skip_step = "Sahiestu pauso hau" Slide = "Diapositiba" +slider_label = "Diapositibak titulu honetarako: %%title%%" sms_failure = "Errorea: mezua ezin da bidali." sms_phone_number = "10 digitu dituen zenbaki telefonoa" sms_sending = "Mezua bidaltzen..." @@ -1592,6 +1975,8 @@ sort_due_date_desc = "Epemuga (berriena lehenik)" sort_relevance = "Garrantzia" sort_return_date_asc = "Itzultzeko data (zaharrena lehenik)" sort_return_date_desc = "Itzultzeko data (berriena lehenik)" +sort_saved = "Gorde data (berriena lehenengoa)" +sort_saved_asc = "Gorde data (zaharrena lehenengoa)" sort_title = "Izenburua" sort_year = "Berrienatik atzera" sort_year_asc = "Zaharrenetik aurrera" @@ -1600,17 +1985,20 @@ Source Title = "Baliabidearen titulua" spell_expand_alt = "Hedatu" spell_suggest = "Iradokizunak" Staff View = "MARC erregistroa" +standalone_record_link = "Erregistro beregaina" Start a new Advanced Search = "Bilaketa berria" Start a new Basic Search = "Bilaketa arrunta" Start Page = "Etxeko orrialdea" starting from = "-tik hasten" Status = "Egoera" +status_transit = "Iragaitzazkoak" status_unknown_message = "Egoera zuzenean ez dago erabilgarri" Storage Retrieval Requests = "Bilteratze berreskuratzeko eskaera" storage_retrieval_request_available = "Eramateko eskuragarri" storage_retrieval_request_cancel = "Bilteratze berreskuratzea ezeztatu" storage_retrieval_request_cancel_all = "Ezeztatu bilteratze berreskuratze guztiak" storage_retrieval_request_cancel_fail = "Zure eskaera ezeztatua izan da. Mesedez, mostradoretik pasa." +storage_retrieval_request_cancel_fail_items = "%%count%% eskaera ezin dira ezeztatu" storage_retrieval_request_cancel_selected = "Ezeztatu aukeratutako bilteratze berreskuratzeak" storage_retrieval_request_cancel_success = "Zure eskaera ezeztatua izan da" storage_retrieval_request_cancel_success_items = "%%count%% Zure eskaera ezeztatua izan da" @@ -1659,6 +2047,7 @@ switchquery_truncatechar = "Moztu zure bilaketa kontsulta emaitza gehiago izatek switchquery_unwantedbools = "Y, O eta NO hitzak idatzi behar dituzu; erabili komatxoak" switchquery_unwantedquotes = "Komatxoak ezabatzeak bilaketa zabalagoa onartuko du" switchquery_wildcard = "Zernahitarako karaktereak erabil daitezke, hitz-aldaerak aurkitzeko" +Synonym = "Sinonimoa" System Unavailable = "Sistema ez dago eskuragarri" Table of Contents = "Aurkibidea" Table of Contents unavailable = "Edukinen taula ez dago eskuragarri" @@ -1669,9 +2058,11 @@ tag_delete_warning = "Kontuz! Ezabatuko duzu %count% etiketa" tag_filter_empty = "Ez dago etiketarik iragazki honetarako" Tags = "Etiketak" tags_deleted = "%count% etiketa ezabatu dira" +temporary_search_tab_title = "Bilaketa pertsonalizatua" test_fail = "Huts egin zuen" test_fix = "Konponduta" test_ok = "Ados" +Text = "Testua" Text this = "SMS" That email address is already used = "Helbide elektroniko hau jadanik erabilia dago" That username is already taken = "Erabiltzailearen izena jadanik erabilia dago" @@ -1679,6 +2070,7 @@ The record you selected is not part of any of your lists. = "Zuk aukeratu duzun The record you selected is not part of the selected list. = "Aukeratu duzun erregistroa ez dago zerrendan" The system is currently unavailable due to system maintenance = "Mantentze lana dela-eta sistema ez dago eskuragarri." Theme = "Gaia" +Thesis = "Tesis" This email was sent from = "Posta elektroniko hau bidali du" This field is required = "Eremu hau behar da" This item is already part of the following list/lists = "Dokumentu hau ondoko zerrendetako baten parte da dagoeneko" @@ -1689,9 +2081,11 @@ Title View = "Izenburuaren bista" title_hold_place = "Egin eskaera izenbururako" title_wrapper = "%%pageTitle%% %%titleSeparator%% %%siteTitle%%" To = "Nori" +toggle_dropdown = "Palanka zabalgarria" Too Many Email Recipients = "Jasotzaile gehiegi" too_many_favorites = "Lista muy larga para desplegar. Intente con sus favoritos en más de una lista o delimitar con etiquetas." too_many_new_items = "Demasiados nuevos ítemes para desplegar en una sola lista. Intente limitando su búsqueda." +too_many_query_terms = "Mesedez, sinplifikatu zure kontsulta, sistemaren mugak baino termino gehiago ditu: (%%terms%%)/(%%maxTerms%%)." too_many_reserves = "Demasiadas reservas de cursos para desplegar en una sola lista. Intente limitando su búsqueda." top_facet_label = "%%label%% bilaketarako iradokizunak" Topic = "Gaia" @@ -1710,20 +2104,39 @@ unique_tags = "Etiketa bakarrak" University Library = "Liburutegi unibertsitarioa" Unknown = "Ezezaguna" unrecognized_facet_label = "Beste batzuk" +unsubscribe_confirmation = "Posta elektronikoko harpidetza ezeztatu nahi duzu??" +unsubscribe_description = "Etorkizunean ez duzue mezu hori jaso nahi? Harpidetza ezeztatu esteka hau erabiliz" +unsubscribe_successful = "Harpidetza ezeztatuta" +Updated = "Eguneratu da" Upgrade VuFind = "Eguneratu VuFind" upgrade_description = "VuFind aurreko bertsio baterako eguneratzen ari bazara, karga dezakezu zure aurreko konfigurazioa tresna hau erabiliz." URL = "URL" Use for = "Erabili" Use instead = "Honen ordez erabili" User Account = "Erabiltzaile" +User Agent = "Erabiltzaile-agentea" Username = "Izen eta 2 abizen" Username cannot be blank = "Erabiltzaile izena ezin da bete gabe utzi" Username is already in use in another library card = "Erabiltzaile izena beste txartel batean dago erabilita" +username_error_invalid = "Erabiltzaile-izena ez da baliozkoa (e.g. karaktere baliogabeak ditu)" +username_maximum_length = "Erabiltzaile-izenaren gehienezko luzera da %%maxlength%% karatere" +username_minimum_length = "Erabiltzaile-izenaren gutxienezko luzera da %%minlength%% karatere" username_only_alphanumeric = "Pasahitzak bakarrik A-Z onartzen du" +username_only_letters_numbers_and_basic_punctuation = "Letra, zenbaki eta puntuazio-karaktere arruntak bakarrik" username_only_numeric = "Bakarrik zenbakiak" +verification_done = "Zure helbide elektronikoa behar bezala egiaztatu da." +verification_email_change_sent = "Helbide elektronikoak egiaztatzeko jarraibideak bidali dira helbide elektroniko berrira. Helbidea egiaztatu behar duzu aldaketak eragina izan aurretik." +verification_email_notification = "Eskaera bakarra egin zen zure kontuaren helbide elektronikoa egiaztatzeko %%library%%." +verification_email_sent = "Helbide elektronikoak egiaztatzeko jarraibideak bidali dira kontu honetan erregistratutako helbide elektronikora." +verification_email_subject = "Posta elektronikoaren egiaztapena" +verification_email_url_pretext = "Zure helbide elektronikoa egiazta dezakezu esteka honen bidez: <%%url%%>." +verification_too_soon = "Zure posta elektronikoak balidazioa behar du. Duela gutxi, posta elektroniko bat bidali zen zure helbide elektroniko erregistratura. Jaso ez baduzu, itxaron minutu batzuk eta saiatu berriro." +verification_user_not_found = "Ezin izan dugu zure kontua aurkitu" +Versions = "Bertsioak" VHS = "VHS" Video = "Bideoa" Video Clips = "Video Clips" +Video Game = "Bideo jokua" Videos = "Bideoak" View Book Bag = "Ver Mochila" View Complete Issue = "Ver el problema completo" @@ -1733,11 +2146,14 @@ View in EDS = "EDS-n ikusita" View online: Full view Book Preview from the Hathi Trust = "Ikuspegia linean: Hathi Trust-etik liburuaren ikuspegi osoa" View Record = "Ikusi Erregistroa" View Records = "Dokumentuak ikusi" +View Retraction Notice = "Ikusi atzera egiteko abisua" View this record in EBSCOhost = "Ikusi hau EBSCOhost-en" view_already_selected = "%%current%% Aukeratutakoak ikusi" +view_in_opac = "Ikusi katalogoan" visual_facet_parent = "Nork" Volume = "Bolumena" Volume Holdings = "Liburukiak" +VuFind Administration - Feedback Management = "VuFind kudeaketa - Feedback kudeaketa" VuFind Configuration = "VuFind-en konfigurazioa" vufind_upgrade_fail = "VuFind ezin da eguneratu momentu honetan" Warning: These citations may not always be 100% accurate = "Kontuz: berrikusi erreferentzia hauek erabili aurretik" @@ -1745,10 +2161,13 @@ wcterms_broader = "Luzatutako gaiak" wcterms_exact = "Antzeko gaiak" wcterms_narrower = "Materias Reducidas" Web = "Web" +Website = "Web orrialdea" What am I looking at = "Zer ikusten ari naiz?" widen_prefix = "Saia zaitez zure bilaketa zabaltzen honekin" wiki_link = "Wikipediatik informazioa lortu" with filters = "iragazkiekin" +worldcat_group_related_editions = "Taldearekin lotutako edizioak" +worldcat_group_variant_records = "Taldeko aldaeren erregistroak" Year of Publication = "Argitaratze-urtea" You do not have any fines = "Ez daukazu isunik" You do not have any holds or recalls placed = "Ez daukazu erreserbarik" diff --git a/languages/fi.ini b/languages/fi.ini index 4a633e2cb0a..3b2ee6dbfb7 100644 --- a/languages/fi.ini +++ b/languages/fi.ini @@ -75,6 +75,7 @@ APA Citation = "APA-viite" APA Edition Citation = "APA-viite (7. p.)" applied_filter = "Käytetty suodatus:" applied_filters = "Käytetyt suodattimet:" +Apply filters = "Käytä" Archival Material = "Arkistomateriaali" Article = "Artikkeli" Ask a Librarian = "Kysy kirjastosta" @@ -207,6 +208,7 @@ Change Password = "Vaihda salasana" change_email_disabled = "Sähköpostiosoitteen vaihtaminen ei ole mahdollista" change_email_verification_reminder = "Lähetettyäsi tämän lomakkeen saat sähköpostiisi vahvistusviestin. Sinun tulee klikata viestin linkkiä, jotta uusi sähköpostiosoite tulee voimaan." change_notification_email_message = "Sähköpostiosoitteen muutospyyntö on tehty palvelussa %%library%%. Jos et tehnyt muutospyyntöä itse, suosittelemme varmistamaan tilisi osoitteessa %%url%%. Ota yhteyttä tukeen sähköpostiosoitteella %%email%%, jos sinulla on kysyttävää tai aihetta huoleen." +change_notification_email_message_no_contact_email = "Sähköpostiosoitteen muutospyyntö on tehty palvelussa %%library%%. Jos et tehnyt muutospyyntöä itse, suosittelemme varmistamaan tilisi osoitteessa %%url%%. Ota yhteyttä tukeen, jos sinulla on kysyttävää tai aihetta huoleen." change_notification_email_subject = "Tilin sähköpostiosoitteen muutospyyntö" channel_add_more = "Lisää tämänkaltaisia kanavia" channel_browse = "Näytä lisää tuloksia" @@ -428,6 +430,7 @@ explain_coord = "* %%coord%% (säätö osumien lukumäärään suhteessa hakuun) explain_difference_score = "Ero huippupisteisiin" explain_disabled = "Relevanssin kuvaus ei ole käytössä lähteelle %%searchClassId%%" explain_for_search = "Haun relevanssin kuvaus" +explain_function_query_label = "Funktio" explain_modified_value = "Tulos: %%relevanceValue%% (relevanssiarvo)" explain_modifier = "muokkaajalla %%modifier%%" explain_record_score = "Tietueen pisteet" @@ -435,6 +438,7 @@ explain_relevance = "Tietue %%recordId%% löytyi relevanssipisteillä %%relevanc explain_relevance_score = "Relevanssipisteet" explain_result_list_chart_title = "Pisteet: %%score%%" explain_result_list_hint = "Relevanssipisteet. Napsauta nähdäksesi yksityiskohtaisen selityksen." +explain_show_raw = "Näytä raakaversio" explain_sum = "summa" explain_top_relevance = "Parhaan tuloksen osuvuus" Export = "Vienti" @@ -619,6 +623,7 @@ hold_edit_title = "Varaustietojen muokkaus" hold_empty_selection = "Yhtään varausta ei valittu" hold_error_age_restricted = "Varausta ei voi tehdä aineiston ikärajan takia." hold_error_blocked = "Varaaminen ei ole mahdollista, koska olet lainauskiellossa." +hold_error_current_loan_patron_group = "Et voi varata tätä aineistoa." hold_error_fail = "Pyyntö epäonnistui. Ota yhteyttä kirjaston asiakaspalveluun." hold_error_item_not_holdable = "Nide ei ole varattavissa." hold_error_not_holdable = "Tätä aineistoa ei voi varata." @@ -666,6 +671,7 @@ ill_request_available = "Noudettavissa" ill_request_cancel = "Peru tilaus" ill_request_cancel_all = "Peru kaikki tilaukset" ill_request_cancel_fail = "Tilaustasi ei peruttu. Ota yhteyttä asiakaspalveluun." +ill_request_cancel_fail_items = "%%count%% pyyntöä ei voitu perua" ill_request_cancel_selected = "Peru valitut tilaukset" ill_request_cancel_success = "Tilaus peruttu" ill_request_cancel_success_items = "%%count%% tilaus(ta) peruttu" @@ -838,6 +844,7 @@ More options = "Lisää vaihtoehtoja" More Summon results = "Lisää Summon-tuloksia…" More Topics = "Lisää aiheita" more_authors_abbrev = "et al." +more_by_author = "Lisää tekijältä %%name%%" more_ellipsis = "lisää…" more_info_toggle = "Näytä/piilota lisätiedot." more_options_ellipsis = "Lisää vaihtoehtoja…" @@ -884,6 +891,7 @@ no_email_address = "Sähköpostiosoite puuttuu." no_items_selected = "Ei tietueita valittuna" no_proxied_user = "Ei välitystä (varaus omaan käyttöön)" nohit_active_filters = "Tässä haussa on käytetty suodattimia. Poistamalla suodattimet voit saada enemmän tuloksia." +nohit_busy = "Järjestelmän kuormitustaso on liian korkea juuri nyt. Yritä myöhemmin uudelleen." nohit_change_tab = 'Haku tehtiin välilehdellä "%%activeTab%%". Voit löytää jotain muilta välilehdiltä:' nohit_filters = "Tässä haussa käytetyt suodattimet:" nohit_heading = "Ei tuloksia!" @@ -1200,6 +1208,7 @@ renew_all = "Uusi kaikki lainat" renew_determine_fail = "Ei tietoa uusintaoikeudesta. Ota yhteyttä kirjaston asiakaspalveluun." renew_empty_selection = "Yhtään lainaa ei valittu" renew_error = "Lainojasi ei voitu uusia. Ota yhteyttä kirjaston asiakaspalveluun." +renew_error_summary = "{count, plural, =1 {Yhden} other {# lainan}} uusiminen epäonnistui." renew_fail = "Lainaa ei voitu uusia" renew_item = "Uusi laina" renew_item_due = "Laina-aikaa jäljellä alle vuorokausi" @@ -1212,6 +1221,7 @@ renew_item_requested = "Varattu toiselle asiakkaalle" renew_select_box = "Uusi laina" renew_selected = "Uusi valitut lainat" renew_success = "Uusiminen onnistui" +renew_success_summary = "{count, plural, =1 {Yksi} other {# laina}} uusittu." Renewed = "Uusittu" Request full text = "Pyydä kokotekstiä" request_in_transit = "Matkalla noutopaikkaan" @@ -1283,6 +1293,7 @@ seconds_abbrev = "s" see all = "näytä kaikki" See also = "Katso myös" see_all_ellipsis = "näytä kaikki…" +Select multiple filters = "Valitse useita suodattimia" Select this record = "Valitse tämä tietue" Select your carrier = "Valitse operaattori" select_all = "Valitse kaikki" @@ -1343,6 +1354,8 @@ sort_due_date_desc = "Eräpäivä (uusin ensin)" sort_relevance = "Relevanssi" sort_return_date_asc = "Palautuspäivä (vanhin ensin)" sort_return_date_desc = "Palautuspäivä (uusin ensin)" +sort_saved = "Lisäysjärjestys (uusin ensin)" +sort_saved_asc = "Lisäysjärjestys (vanhin ensin)" sort_title = "Nimeke" sort_year = "Aika (uusimmat ensin)" sort_year_asc = "Aika (vanhimmat ensin)" @@ -1351,6 +1364,7 @@ Source Title = "Lähdenimeke" spell_expand_alt = "Laajenna hakua" spell_suggest = "Tarkoititko" Staff View = "Henkilökuntanäyttö" +standalone_record_link = "Yksittäinen tietue" Start a new Advanced Search = "Aloita uusi tarkennettu haku" Start a new Basic Search = "Aloita uusi perushaku" Start Page = "Ensimmäinen sivu" @@ -1363,6 +1377,7 @@ storage_retrieval_request_available = "Noudettavissa" storage_retrieval_request_cancel = "Peru tilaus" storage_retrieval_request_cancel_all = "Peru kaikki tilaukset" storage_retrieval_request_cancel_fail = "Tilaustasi ei peruttu. Ota yhteyttä asiakaspalveluun." +storage_retrieval_request_cancel_fail_items = "%%count%% pyyntöä ei voitu perua" storage_retrieval_request_cancel_selected = "Peru valitut tilaukset" storage_retrieval_request_cancel_success = "Tilaus peruttu" storage_retrieval_request_cancel_success_items = "%%count%% tilaus(ta) peruttu" @@ -1449,6 +1464,7 @@ toggle_dropdown = "Näytä tai piilota alasvetovalikko" Too Many Email Recipients = "Liian monta sähköpostin vastaanottajaa" too_many_favorites = "Listaa ei voida näyttää yhdellä kertaa sen koon vuoksi. Jaa suosikkisi useammalle listalle tai rajaa hakua käyttämällä tageja." too_many_new_items = "Kaikkia uutuuksia ei voida näyttää yhdellä listalla. Kokeile haun rajaamista." +too_many_query_terms = "Yksinkertaista hakua. Haku sisältää enemmän ehtoja (%%terms%%) kuin järjestelmä sallii (%%maxTerms%%)." too_many_reserves = "Kaikkia kurssikirjoja ei voida näyttää yhdellä listalla. Kokeile haun rajaamista." top_facet_label = "%%label%%" Topic = "Aihe" @@ -1529,13 +1545,15 @@ What am I looking at = "Mitä tämä on?" widen_prefix = "Kokeile laajentaa hakua:" wiki_link = "Wikipedian tuottama" with filters = "suodattimilla" +worldcat_group_related_editions = "Ryhmittele liittyvät painokset" +worldcat_group_variant_records = "Ryhmittele vastaavat tietueet" Year of Publication = "Julkaisuvuosi" You do not have any fines = "Ei maksamattomia maksuja" You do not have any holds or recalls placed = "Ei voimassaolevia varauksia" You do not have any interlibrary loan requests placed = "Ei voimassaolevia kaukolainatilauksia" You do not have any items checked out = "Ei lainoja" You do not have any library cards = "Ei kirjastokortteja" -You do not have any saved resources = "Ei tallennettuja tietueita" +You do not have any saved resources = "Ei tallennettuja suosikkeja" You do not have any storage retrieval requests placed = "Ei voimassaolevia varastotilauksia" You must be logged in first = "Kirjaudu sisään ensin" Your Account = "Oma tili" diff --git a/languages/fr.ini b/languages/fr.ini index 5cc3f45893f..f39e454ce91 100644 --- a/languages/fr.ini +++ b/languages/fr.ini @@ -76,6 +76,7 @@ APA Citation = "Style de citation APA" APA Edition Citation = "Style de citation APA (7e éd.)" applied_filter = "Filtre appliqué :" applied_filters = "Filtres appliqués :" +Apply filters = "Appliquer les filtres" Archival Material = "Documents d'archives" Article = "Article" Ask a Librarian = "Contacter un bibliothécaire" @@ -272,6 +273,7 @@ confirm_renew_selected_text = "Voulez-vous prolonger les emprunts sélectionnés confirm_storage_retrieval_request_cancel_all_text = "Voulez-vous annuler toutes vos demandes de communication en magasin ?" confirm_storage_retrieval_request_cancel_selected_text = "Voulez-vous annuler les demandes de communication en magasin sélectionnées ?" Connecting of library cards is not supported = "Il n'est pas possible de connecter une carte de bibliothèque." +Content Advice = "Avis sur le Contenu" Contents = "Contenu" Contributing Source = "Sources" Contributors = "autres auteurs" @@ -1164,6 +1166,7 @@ Results for = "Résultats de" Results per page = "Résultats par page" Resumption Token = "Jeton (Resumption Token)" Return Date = "Date de retour" +Review = "Critique" Review by = "Recension de" Reviews = "Recensions" Save = "Sauvegarder" @@ -1205,6 +1208,7 @@ seconds_abbrev = "s" see all = "voir tous les" See also = "Voir aussi" see_all_ellipsis = "voir tous les …" +Select multiple filters = "Sélectionner plusieurs filtres" Select this record = "Sélectionner cette notice" Select your carrier = "Choisissez votre fournisseur de téléphonie" select_all_on_page = "Choisir tous les éléments de la page" diff --git a/languages/ga.ini b/languages/ga.ini index 093e96ccc48..be68d55aef1 100644 --- a/languages/ga.ini +++ b/languages/ga.ini @@ -76,6 +76,7 @@ APA Citation = "Lua APA" APA Edition Citation = "Lua APA (7ú heag.)" applied_filter = "Cuireadh scagaire i bhfeidhm:" applied_filters = "Scagairí i bhfeidhm:" +Apply filters = "Cuir scagairí i bhfeidhm" Archival Material = "Ábhar Cartlainne" Article = "Alt" Ask a Librarian = "Cuir ceist ar leabharlannaí" @@ -429,6 +430,7 @@ explain_coord = "* %%coord%% (coigeartaigh líon na meaitseanna i gcomparáid le explain_difference_score = "difríocht leis an scór is fearr" explain_disabled = "Tá an míniú díghníomhachtaithe le haghaidh %%searchClassId%%" explain_for_search = "Eochair eolais don chuardach" +explain_function_query_label = "Feidhm" explain_modified_value = "Táirge den %%relevanceValue%% (luach ábharthachta)" explain_modifier = "le mionathraitheoir de %%modifier%%" explain_record_score = "scór taifid" @@ -436,6 +438,7 @@ explain_relevance = "ID an taifid: %%recordId%% found with a relevance value of explain_relevance_score = "Scór de réir Ábharthachta" explain_result_list_chart_title = "Scór: %%score%%" explain_result_list_hint = "De réir ábharthachta. Cliceáil chun míniú mionsonraithe a fheiceáil." +explain_show_raw = "Taispeáin bunmhíniú" explain_sum = "iomlán" explain_top_relevance = "Ábharthacht an Toraidh Is Fearr" Export = "Easpórtáil" @@ -620,6 +623,7 @@ hold_edit_title = "Athraigh faisnéis choinneála" hold_empty_selection = "Níor roghnaíodh aon choinneálacha" hold_error_age_restricted = "Ní féidir coinneáil a chur ar an mír seo toisc go mbaineann srian aoise leis an ábhar." hold_error_blocked = "Níl do dhóthain pribhléidí agat le coinneáil a chur ar an mír seo" +hold_error_current_loan_patron_group = "Ní féidir an mhír seo a iarraidh." hold_error_fail = "Theip ar d’iarratas. Déan teagmháil leis an gcuntar riartha le tuilleadh cúnaimh a fháil" hold_error_item_not_holdable = "Ní féidir an mhír seo a iarraidh." hold_error_not_holdable = "Ní féidir an t-ábhar seo a iarraidh." @@ -667,6 +671,7 @@ ill_request_available = "Ar fáil le bailiú" ill_request_cancel = "Cuir an t-iarratas ar iasacht idirleabharlainne ar ceal" ill_request_cancel_all = "Cuir gach iarratas ar iasacht idirleabharlainne ar ceal" ill_request_cancel_fail = "Níor cuireadh d’iarratas ar ceal. Téigh i dteagmháil leis an deasc cúrsaíochta chun tuilleadh cabhrach a fháil" +ill_request_cancel_fail_items = "Níor éirigh linn %%count%% iarratas a chealú" ill_request_cancel_selected = "Cuir na hiarratais roghnaithe ar iasacht idirleabharlainne ar ceal" ill_request_cancel_success = "D’éirigh leat d’iarratas a chur ar ceal" ill_request_cancel_success_items = "%%count%% iarratas curtha ar ceal go rathúil" @@ -839,6 +844,7 @@ More options = "Tuilleadh roghanna" More Summon results = "Tuilleadh torthaí Summon…" More Topics = "Tuilleadh topaicí" more_authors_abbrev = "et al." +more_by_author = "De réir %%name%% chomh maith" more_ellipsis = "níos mó…" more_info_toggle = "Taispeáin/folaigh tuilleadh faisnéise." more_options_ellipsis = "Tuilleadh roghanna…" @@ -885,6 +891,7 @@ no_email_address = "Seoladh r-phoist ar iarraidh." no_items_selected = "Níor roghnaíodh aon mhíreanna." no_proxied_user = "Cosc ar neas-úsáideoirí (iarr tú féin é)" nohit_active_filters = "Cuireadh gnéscagaire amháin nó níos mó i bhfeidhm ar an gcuardach seo. Má bhaineann tú na scagairí, seans go bhfaighidh tú tuilleadh torthaí." +nohit_busy = "Tá an córas róghnóthach faoi láthair le freagra a thabhairt. Bain triail eile as níos déanaí." nohit_change_tab = "Tá tú ag cuardach faoin táb: %%activeTab%%. Seans go bhfaighfeá toradh faoi cheann de na táib eile:" nohit_filters = "Bain triail as do scagaire/scagairí a bhaint:" nohit_heading = "Níor aimsíodh aon torthaí" @@ -1201,6 +1208,7 @@ renew_all = "Athnuaigh gach mír" renew_determine_fail = "Ní raibh muid in ann a dhéanamh amach an féidir do mhír a athnuachan. Téigh i dteagmháil le ball foirne." renew_empty_selection = "Níor roghnaíodh aon mhíreanna." renew_error = "Ní raibh muid in ann do mhír/do chuid míreanna a athnuachan - Déan teagmháil le ball foirne" +renew_error_summary = "Níor éirigh linn {count, plural, =1 {1 mír} other {# mír}} a athnuachan de bharr earráidí." renew_fail = "Níorbh fhéidir an mhír seo a athnuachan" renew_item = "Athnuaigh an mhír" renew_item_due = "Mír dlite laistigh de 24 uair an chloig" @@ -1213,6 +1221,7 @@ renew_item_requested = "Tá an mhír seo iarrtha ag úsáideoir eile" renew_select_box = "Athnuaigh an mhír" renew_selected = "Athnuaigh na míreanna atá roghnaithe" renew_success = "D’éirigh leis an athnuachan" +renew_success_summary = "Athnuaite go rathúil {count, plural, =1 {1 mír} other {# mír}}." Renewed = "Athnuaite" Request full text = "Iarr rochtain iomlán" request_in_transit = "Ar an mbealach chuig an suíomh bailithe" @@ -1284,6 +1293,7 @@ seconds_abbrev = "s" see all = "breathnaigh ar gach rud" See also = "Moltaí cuardaigh" see_all_ellipsis = "breathnaigh ar gach rud…" +Select multiple filters = "Roghnaigh ilscagairí" Select this record = "Roghnaigh an taifead seo" Select your carrier = "Roghnaigh d’iompróir" select_all = "Roghnaigh gach rud" @@ -1344,6 +1354,8 @@ sort_due_date_desc = "Dáta dlite (is nuaí ar dtús)" sort_relevance = "De réir ábharthachta" sort_return_date_asc = "Dáta tabhairt ar ais (is sine ar dtús)" sort_return_date_desc = "Dáta tabhairt ar ais (is nuaí ar dtús)" +sort_saved = "Sábháil an Dáta (an ceann is déanaí ar dtús)" +sort_saved_asc = "Sábháil an Dáta (an ceann is sine ar dtús)" sort_title = "Teideal" sort_year = "Is nuaí ar dtús" sort_year_asc = "Is sine ar dtús" @@ -1352,6 +1364,7 @@ Source Title = "Teideal na foinse" spell_expand_alt = "Leathnaigh an cuardach" spell_suggest = "Cuardaigh le haghaidh roghanna malartacha" Staff View = "Amharc foirne" +standalone_record_link = "Taifead Aonair" Start a new Advanced Search = "Tosaigh cuardach casta nua" Start a new Basic Search = "Tosaigh cuardach bunúsach nua" Start Page = "Leathanach tosaigh" @@ -1364,6 +1377,7 @@ storage_retrieval_request_available = "Ar fáil le bailiú" storage_retrieval_request_cancel = "Cuir an t-iarratas aisghabhála ó stóras ar ceal" storage_retrieval_request_cancel_all = "Cuir gach iarratas aisghabhála ó stóras ar ceal" storage_retrieval_request_cancel_fail = "Níor cuireadh d’iarratas ar ceal. Téigh i dteagmháil leis an deasc cúrsaíochta chun tuilleadh cabhrach a fháil" +storage_retrieval_request_cancel_fail_items = "Níor éirigh linn %%count%% iarratas a chealú" storage_retrieval_request_cancel_selected = "Cuir na hiarratais aisghabhála ó stóras atá roghnaithe ar ceal" storage_retrieval_request_cancel_success = "D’éirigh leat d’iarratas a chur ar ceal" storage_retrieval_request_cancel_success_items = "%%count%% iarratas curtha ar ceal go rathúil" @@ -1450,6 +1464,7 @@ toggle_dropdown = "Scoránaigh an roghchlár anuas" Too Many Email Recipients = "An iomarca faighteoirí r-phoist" too_many_favorites = "Ní féidir an liosta seo a thaispeáint ina iomláine de bharr go bhfuil sé rómhór Bain triail as do chuid ceanán a eagrú ina dtuilleadh liostaí nó teorainn a chur leis an líon clibeanna atá á n-úsáid." too_many_new_items = "Tá an iomarca míreanna nua ann le taispeáint in aon liosta amháin. Bain triail as teorainn a chur le do chuardach" +too_many_query_terms = "Simpligh do cheist; tá níos mó téarmaí ann (%%terms%%) ná uasmhéid an chórais (%%maxTerms%%)." too_many_reserves = "Tá an iomarca áirithintí cúrsa ann le taispeáint in aon liosta amháin. Bain triail as teorainn a chur le do chuardach" top_facet_label = "%%label%% laistigh de do chuardach." Topic = "Topaic" @@ -1530,6 +1545,8 @@ What am I looking at = "Céard air a bhfuil mé ag breathnú?" widen_prefix = "Bain triail as do chuardach a leathnú go" wiki_link = "Arna chur ar fáil ag Vicipéid" with filters = "le scagairí" +worldcat_group_related_editions = "Eagráin Ghrúpabhunaithe" +worldcat_group_variant_records = "Taifid Ghrúpa-Athraithigh" Year of Publication = "Bliain a fhoilsithe" You do not have any fines = "Níl aon fhíneálacha agat" You do not have any holds or recalls placed = "Níl aon choinneálacha ná aisghairmeacha curtha agat" diff --git a/languages/hi.ini b/languages/hi.ini index 14a1299d4fa..1ade5f09543 100644 --- a/languages/hi.ini +++ b/languages/hi.ini @@ -78,6 +78,7 @@ APA Citation = "एपीए उद्धरण" APA Edition Citation = "APA (7 वां संस्करण) प्रशस्ति पत्र" applied_filter = "फ़िल्टर प्रयुक्त किया गया :" applied_filters = "लागू फिल्टर:" +Apply filters = "सारे फ़िल्टर्स लागू करें" Archival Material = "अभिलेखीय सामग्री" Article = "लेख" Ask a Librarian = "सहयता के लिए संपर्क करें" @@ -431,6 +432,7 @@ explain_coord = "* %%coord%% (खोज की तुलना में मि explain_difference_score = "शीर्ष स्कोर से अंतर" explain_disabled = "%%searchClassId%% के लिए स्पष्टीकरण निष्क्रिय है" explain_for_search = "खोज के लिए स्पष्टीकरण" +explain_function_query_label = "फंक्शन" explain_modified_value = "%%relevanceValue%% (प्रासंगिकता मान) का गुणनफल" explain_modifier = "%%modifier%% संशोधक के साथ" explain_record_score = "रिकॉर्ड स्कोर" @@ -438,6 +440,7 @@ explain_relevance = "रिकॉर्ड आईडी: %%recordId%%को %%re explain_relevance_score = "प्रासंगिकता स्कोर" explain_result_list_chart_title = "अंकः %%score%%" explain_result_list_hint = "प्रासंगिकता अंक। विस्तृत व्याख्या देखने के लिए क्लिक करें।" +explain_show_raw = "कच्चा स्पष्टीकरण दिखाएँ" explain_sum = "जोड़" explain_top_relevance = "शीर्ष परिणाम प्रासंगिकता" Export = "निर्यात" @@ -622,6 +625,7 @@ hold_edit_title = "होल्ड जानकारी बदलें" hold_empty_selection = "कोई होल्ड नहीं चुना गया था" hold_error_age_restricted = "सामग्री की आयु सीमाबद्धता के कारण होल्ड नहीं लगाया जा सकता।" hold_error_blocked = "इस आइटम पर पकड़ रखने के लिए आपके पास पर्याप्त विशेषाधिकार नहीं हैं" +hold_error_current_loan_patron_group = "इस आइटम का फ़िलहाल अनुरोध नहीं किया जा सकता।" hold_error_fail = "आपका अनुरोध विफल रहा। अधिक सहायता के लिए कृपया संचलन डेस्क से संपर्क करें" hold_error_item_not_holdable = "इस आइटम का अनुरोध नहीं किया जा सकता है" hold_error_not_holdable = "इस सामग्री का अनुरोध नहीं किया जा सकता है" @@ -669,6 +673,7 @@ ill_request_available = "उठाने के लिए उपलब्ध" ill_request_cancel = "ऋण अनुरोध रद्द करें" ill_request_cancel_all = "सभी अंतःक्रिया ऋण अनुरोध रद्द करें" ill_request_cancel_fail = "आपका अनुरोध रद्द नहीं किया गया था अधिक सहायता के लिए कृपया संचलन डेस्क से संपर्क करें" +ill_request_cancel_fail_items = "%%count%% अनुरोध रद्द नहीं किया जा सका" ill_request_cancel_selected = "चयनित अंतरपुस्तकालयी ऋण अनुरोध रद्द करें" ill_request_cancel_success = "आपका अनुरोध सफलतापूर्वक रद्द कर दिया गया था" ill_request_cancel_success_items = "%%count%% अनुरोध (ओं) को सफलतापूर्वक रद्द कर दिया गया" @@ -841,6 +846,7 @@ More options = "अधिक विकल्प" More Summon results = "अधिक समन परिणाम…" More Topics = "अधिक विषय" more_authors_abbrev = "और अन्य" +more_by_author = "इसके अलावा %%name%% द्वारा" more_ellipsis = "अधिक…" more_info_toggle = "अधिक जानकारी दिखाएं / छिपाएँ" more_options_ellipsis = "अधिक विकल्प …" @@ -887,6 +893,7 @@ no_email_address = "ईमेल पता लुप्त है।" no_items_selected = "कोई आइटम नहीं चुना गया था" no_proxied_user = "कोई प्रॉक्सी उपयोगकर्ता नहीं (स्वयं के लिए अनुरोध)" nohit_active_filters = "इस खोज में एक या अधिक पहलू फ़िल्टर लागू किए गए हैं। यदि आप फ़िल्टर हटाते हैं, तो आप अधिक परिणाम प्राप्त कर सकते हैं" +nohit_busy = "सिस्टम अभी जवाब देने में बहुत व्यस्त है। कृपया बाद में फिर से कोशिश करें।" nohit_change_tab = "आप में खोज रहे हैं %%activeTab%% टैब। आपको अन्य टैब में से कुछ मिल सकता है:" nohit_filters = "फ़िलहाल इस खोज पर फ़िल्‍टर किए गए हैं:" nohit_heading = "कोई परिणाम नहीं !" @@ -1203,6 +1210,7 @@ renew_all = "सभी आइटम नवीनीकृत करें" renew_determine_fail = "यदि आपका आइटम नवीनीकृत किया जा सकता है तो हम यह निर्धारित करने में असमर्थ थे। कृपया कर्मचारियों के एक सदस्य से संपर्क करें" renew_empty_selection = "कोई आइटम नहीं चुना गया था" renew_error = "हम आपके आइटम / वस्तु को नवीनीकृत करने में असमर्थ थे - कृपया कर्मचारियों के एक सदस्य से संपर्क करें" +renew_error_summary = "त्रुटियों के कारण {count, plural, =1 {1 आइटम} other {# आइटम}} को नवीनीकृत करने में असमर्थ।" renew_fail = "इस आइटम को नवीनीकृत नहीं किया जा सका" renew_item = "नवीनीकृत वस्तु" renew_item_due = "अगले 24 घंटों के भीतर आइटम देय" @@ -1215,6 +1223,7 @@ renew_item_requested = "यह मद किसी अन्य उपयोग renew_select_box = "नवीनीकृत मद" renew_selected = "चयनित मद नवीनीकृत करें" renew_success = "नवीनीकरण सफल" +renew_success_summary = "{count, plural, =1 {1 आइटम} other {# आइटम}} को सफलतापूर्वक नवीनीकृत किया गया।" Renewed = "नवीकृत" Request full text = "पूर्ण पाठ का अनुरोध करें" request_in_transit = "स्थानान्तरण में संग्रह स्थान पर" @@ -1286,6 +1295,7 @@ seconds_abbrev = "सेकंड" see all = "सभी देखें" See also = "यह सभी देखें" see_all_ellipsis = "सभी देखें…" +Select multiple filters = "कई फ़िल्टर चुनें" Select this record = "इस रिकॉर्ड का चयन करें" Select your carrier = "अपने कैरियर का चयन करें" select_all = "सभी प्रविष्टियों को चुनें" @@ -1346,6 +1356,8 @@ sort_due_date_desc = "नियत तिथि (सबसे पहले न sort_relevance = "प्रासंगिकता" sort_return_date_asc = "वापसी की तारीख (सबसे पुरानी पहली)" sort_return_date_desc = "वापसी की तारीख (पहले नई)" +sort_saved = "तारीख सेव करें (सबसे पहले सबसे नया)" +sort_saved_asc = "तारीख सेव करें (सबसे पुराना पहला)" sort_title = "शीर्षक" sort_year = "तिथि अवरोही में" sort_year_asc = "तिथि आरोही में" @@ -1354,6 +1366,7 @@ Source Title = "स्रोत शीर्षक" spell_expand_alt = "खोज का विस्तार करें" spell_suggest = "विकल्प खोजें" Staff View = "स्टाफ के लिए" +standalone_record_link = "स्टैंडअलोन रिकॉर्ड" Start a new Advanced Search = "एक नई उन्नत खोज शुरू करें" Start a new Basic Search = "एक नया मूलभूत खोज शुरू करें" Start Page = "पृष्ठ प्रारंभ करें" @@ -1366,6 +1379,7 @@ storage_retrieval_request_available = "संग्रह के लिए उ storage_retrieval_request_cancel = "संग्रहण पुनर्प्राप्ति अनुरोध रद्द करें" storage_retrieval_request_cancel_all = "सभी संग्रहण नियंत्रण क्षेत्र रद्द करें को भेजें" storage_retrieval_request_cancel_fail = "आपका अनुरोध रद्द नहीं किया गया था अधिक सहायता के लिए कृपया संचलन डेस्क से संपर्क करें" +storage_retrieval_request_cancel_fail_items = "%%count%% अनुरोध रद्द नहीं किया जा सका" storage_retrieval_request_cancel_selected = "चयनित संग्रहण पुनर्प्राप्ति अनुरोध रद्द करें" storage_retrieval_request_cancel_success = "आपका अनुरोध सफलतापूर्वक रद्द कर दिया गया था" storage_retrieval_request_cancel_success_items = "%%count%% अनुरोध (ओं) को सफलतापूर्वक रद्द कर दिया गया" @@ -1452,6 +1466,7 @@ toggle_dropdown = "ड्रॉपडाउन मेनू टॉगल कर Too Many Email Recipients = "बहुत सारे ईमेल प्राप्तकर्ता" too_many_favorites = "यह सूची एक साथ सभी को प्रदर्शित करने के लिए बहुत बड़ी है। अपने पसंदीदा को अधिक सूचियों में पुन: व्यवस्थित करने या टैग का उपयोग करने की कोशिश करें" too_many_new_items = "एकल सूची में प्रदर्शित करने के लिए बहुत सी नई वस्तुएँ हैं। अपनी खोज को सीमित करने का प्रयास करें" +too_many_query_terms = "कृपया अपनी खोज आसान बनाएं; इसमें सिस्टम सीमा (%%maxTerms%%) से अधिक शब्द (%%terms%%) शामिल हैं।" too_many_reserves = "एकल सूची में प्रदर्शित करने के लिए बहुत सारे कोर्स रिजर्व हैं। अपनी खोज को सीमित करने का प्रयास करें" top_facet_label = "%%label%% खोज निहित" Topic = "विषय" @@ -1532,6 +1547,8 @@ What am I looking at = "मेरी नज़र किस पर है ?" widen_prefix = "अपनी खोज को विस्तृत करने का प्रयास करें" wiki_link = "विकिपीडिया द्वारा प्रदान किया गया" with filters = "फ़िल्टर के साथ" +worldcat_group_related_editions = "समूह से संबंधित संस्करण" +worldcat_group_variant_records = "ग्रुप वेरिएंट रिकॉर्ड्स" Year of Publication = "प्रकाशन का वर्ष" You do not have any fines = "आपके पास कोई जुर्माना नहीं है" You do not have any holds or recalls placed = "आपके पास कोई होल्ड या रिकॉल नहीं है" diff --git a/languages/hr.ini b/languages/hr.ini index 868c49c9ce9..68d54f97dae 100644 --- a/languages/hr.ini +++ b/languages/hr.ini @@ -77,6 +77,7 @@ APA Citation = "APA način citiranja" APA Edition Citation = "APA način citiranja (7. izdanje)" applied_filter = "Primijenjeni filtar:" applied_filters = "Primijenjeni filtri:" +Apply filters = "Primijeni filtre" Archival Material = "Arhivska građa" Article = "Članak" Ask a Librarian = "Upitaj knjižničara" @@ -438,6 +439,7 @@ explain_relevance = "ID zapisa: %%recordId%% pronađen s vrijednošću relevantn explain_relevance_score = "Ocjena relevantnosti" explain_result_list_chart_title = "Ocjena: %%score%%" explain_result_list_hint = "Ocjena relevantnosti. Klikni za prikaz detaljnog objašnjenja." +explain_show_raw = "Prikaži neobrađeno objašnjenje" explain_sum = "zbroj" explain_top_relevance = "Relevantnost najboljeg rezultata" Export = "Izvezi" @@ -622,6 +624,7 @@ hold_edit_title = "Promijeni podatke narudžbe" hold_empty_selection = "Nije odabrana nijedna narudžba" hold_error_age_restricted = "Ne može se naručiti zbog dobnog ograničenja građe." hold_error_blocked = "Nemaš dozvole za postavljanje narudžbe za ovaj predmet" +hold_error_current_loan_patron_group = "Ovaj se predmet trenutačno ne može zatražiti." hold_error_fail = "Tvoj zahtjev nije uspio. Zatraži pomoć na informacijskom pultu" hold_error_item_not_holdable = "Ovaj se predmet ne može zatražiti." hold_error_not_holdable = "Ova se građa ne može zatražiti." @@ -669,6 +672,7 @@ ill_request_available = "Posudba je spremna za preuzimanje" ill_request_cancel = "Otkaži zahtjev za međuknjižničnu posudbu" ill_request_cancel_all = "Otkaži sve zahtjeve za međuknjižničnu posudbu" ill_request_cancel_fail = "Tvoj zahtjev nije otkazan. Zatraži pomoć na informacijskom pultu" +ill_request_cancel_fail_items = "Nije bilo moguće otkazati %%count%% zahtjev(a)" ill_request_cancel_selected = "Otkaži odabrane zahtjeve za međuknjižničnu posudbu" ill_request_cancel_success = "Tvoj je zahtjev je uspješno otkazan" ill_request_cancel_success_items = "Broj uspješno otkazanih zahtjeva: %%count%%" @@ -841,6 +845,7 @@ More options = "Daljnje opcije" More Summon results = "Daljnji Summon rezultati…" More Topics = "Daljnje teme" more_authors_abbrev = "i dr." +more_by_author = "Također po %%name%%" more_ellipsis = "više…" more_info_toggle = "Prikaži/sakrij daljnje informacije." more_options_ellipsis = "Daljnje opcije…" @@ -887,6 +892,7 @@ no_email_address = "Nedostaje e-mail adresa." no_items_selected = "Nije odabran nijedan predmet" no_proxied_user = "Bez posrednika (zatraži za sebe)" nohit_active_filters = "Jedna ili više faseta filtra je primijenjena na ovu pretragu. Ako ukloniš filtre, vjerojatno ćeš dobiti više rezultata." +nohit_busy = "Sustav je prezauzet i ne može odgovoriti. Pokušaj kasnije ponovo." nohit_change_tab = 'Pretražuješ karticu "%%activeTab%%". Možda ćeš u ostalim karticama također nešto pronaći:' nohit_filters = "Trenutačno primijenjen filtri za ovu pretragu:" nohit_heading = "Bez rezultata!" @@ -1203,6 +1209,7 @@ renew_all = "Produži rok za sve posudbe" renew_determine_fail = "Nismo mogli ustanoviti je li se rok tvoje posudbe može produžiti. Kontaktiraj djelatnike knjižnice." renew_empty_selection = "Nije odabran nijedan predmet" renew_error = "Nismo uspjeli produžiti rok tvoje posudbe – kontaktiraj djelatnike knjižnice" +renew_error_summary = "Nije moguće obnoviti {count, plural, =1 {1 predmet} few {# predmeta} other {# predmeta}} zbog grešaka." renew_fail = "Nije bilo moguće produžiti rok posudbe" renew_item = "Produži rok posudbe" renew_item_due = "Rok posudbe dospijeva za 24 sata" @@ -1215,6 +1222,7 @@ renew_item_requested = "Ovaj predmet je zatražio jedan drugi korisnik" renew_select_box = "Produži rok posudbe" renew_selected = "Produži rok posudbe odabranih predmeta" renew_success = "Rok posudbe je uspješno produžen" +renew_success_summary = "{count, plural, =1 {Uspješno obnovljen 1 predmet} few {Uspješno obnovljena # predmeta} other {Uspješno obnovljeno # predmeta}}." Renewed = "Produženo" Request full text = "Zatraži cijeli tekst" request_in_transit = "Na putu prema lokaciji za preuzimanje posudbe" @@ -1286,6 +1294,7 @@ seconds_abbrev = "s" see all = "pogledaj sve" See also = "Također pogledaj" see_all_ellipsis = "pogledaj sve…" +Select multiple filters = "Odaberi više filtara" Select this record = "Odaberi ovaj zapis" Select your carrier = "Odaberi prenositelja" select_all = "Odaberi sve unose" @@ -1347,6 +1356,7 @@ sort_relevance = "Relevantnost" sort_return_date_asc = "Datum vraćanja posudbe (najprije najstarije)" sort_return_date_desc = "Datum vraćanja posudbe (najprije najnovije)" sort_saved = "Datum spremanja (najprije najnovija)" +sort_saved_asc = "Datum spremanja (najprije najstarija)" sort_title = "Naslov" sort_year = "Datum uzlazno" sort_year_asc = "Datum silazno" @@ -1368,6 +1378,7 @@ storage_retrieval_request_available = "Posudba je spremna za preuzimanje" storage_retrieval_request_cancel = "Otkaži zahtjev za pronalaženje u spremištu" storage_retrieval_request_cancel_all = "Otkaži sve zahtjeve za pronalaženjem u spremištu" storage_retrieval_request_cancel_fail = "Tvoj zahtjev nije otkazan. Zatraži pomoć na informacijskom pultu" +storage_retrieval_request_cancel_fail_items = "Nije bilo moguće otkazati %%count%% zahtjev(a)" storage_retrieval_request_cancel_selected = "Otkaži odabrane zahtjeve za pronalaženje u spremištu" storage_retrieval_request_cancel_success = "Tvoj je zahtjev uspješno otkazan" storage_retrieval_request_cancel_success_items = "Broj uspješno otkazanih zahtjeva: %%count%%" @@ -1454,6 +1465,7 @@ toggle_dropdown = "Uključi/isključi rasklopiv izbornik" Too Many Email Recipients = "Previše primaoca e-maila" too_many_favorites = "Ovaj popis je prevelik za zajednički prikaz. Pokušaj presložiti favorite u višestruke popise ili ograničiti upotrebu oznaka." too_many_new_items = "Postoji previše novih predmeta za prikaz u jednom popisu. Pokušaj ograničiti pretragu." +too_many_query_terms = "Pojednostavi upit; upit sadrži više pojmova (%%terms%%) od maksimalno dozvoljenog broja (%%maxTerms%%)." too_many_reserves = "Previše je rezervacija tečajeva za prikaz u jednom popisu. Pokušaj ograničiti pretragu" top_facet_label = "%%label%% unutar tvoje pretrage." Topic = "Tema" @@ -1534,6 +1546,8 @@ What am I looking at = "Što gledam?" widen_prefix = "Pokušaj proširiti pretragu na" wiki_link = "Omogućuje Wikipedia" with filters = "s filtrima" +worldcat_group_related_editions = "Grupiraj povezana izdanja" +worldcat_group_variant_records = "Grupiraj varijante zapisa" Year of Publication = "Godina izdanja" You do not have any fines = "Nemaš zakasnina" You do not have any holds or recalls placed = "Nemaš postavljenih narudžbi ili rezervacija" diff --git a/languages/hy.ini b/languages/hy.ini index 87be90fb0cc..4a224a71a85 100644 --- a/languages/hy.ini +++ b/languages/hy.ini @@ -76,6 +76,7 @@ APA Citation = "APA մեջբերում" APA Edition Citation = "APA (7th ed.) մեջբերում" applied_filter = "Կիրառված զտիչ:" applied_filters = "Կիրառված զտիչներ:" +Apply filters = "Կիրառեք զտիչները" Archival Material = "Արխիվային նյութ" Article = "Հոդված" Ask a Librarian = "Հարցրեք գրադարանավարին" @@ -429,6 +430,7 @@ explain_coord = "* %%coord%% (հարմարեցնել համընկնումներ explain_difference_score = "տարբերություն մինչև բարձրագույն միավորը" explain_disabled = "Բացատրելը անջատված է %%searchClassId%% համար" explain_for_search = "Բացատրություն որոնման համար" +explain_function_query_label = "Գործառույթներ" explain_modified_value = "%%relevanceValue%% արտադրանք (համապատասխանության արժեք)" explain_modifier = "%%modifier%% փոփոխիչով" explain_record_score = "գրառման միավոր" @@ -436,6 +438,7 @@ explain_relevance = "Գրառման Id: %%recordId%% գտնվել է %%relevance explain_relevance_score = "Համապատասխանության միավոր" explain_result_list_chart_title = "Միավոր՝ %%score%%" explain_result_list_hint = "Համապատասխանության միավոր։ Սեղմեք՝ մանրամասն բացատրությունը տեսնելու համար։" +explain_show_raw = "Ցույց տալ չմշակված բացատրությունը" explain_sum = "sum" explain_top_relevance = "Լավագույն արդյունքի համապատասխանությունը" Export = "Արտահանել" @@ -620,6 +623,7 @@ hold_edit_title = "Փոխել պահման տեղեկատվությունը" hold_empty_selection = "Պահումներ չեն ընտրվել" hold_error_age_restricted = "Պահումը չի կարող տեղադրվել նյութի տարիքային սահմանափակման պատճառով:" hold_error_blocked = "Դուք չունեք բավարար արտոնություններ պահում դնելու այս նյութի" +hold_error_current_loan_patron_group = "Այս նյութը ներկայումս չի կարող պահանջվել:" hold_error_fail = "Ձեր հարցումը ձախողվեց: Խնդրում ենք կապվել տացքի գրասենյակի հետ հետագա օգնության համար" hold_error_item_not_holdable = "Այս տարրը չի կարող պահանջվել:" hold_error_not_holdable = "Այս նյութը չի կարող պահանջվել:" @@ -667,6 +671,7 @@ ill_request_available = "Հասանելի է վերցնելու համար" ill_request_cancel = "Չեղարկել միջգրադարանային բաժնույթի հարցումը" ill_request_cancel_all = "Չեղարկել միջգրադարանային բաժնույթի բոլոր հարցումները" ill_request_cancel_fail = "Ձեր հարցումը չի չեղարկվել: Խնդրում ենք կապվել տացքի գրասենյակի հետ հետագա օգնության համար" +ill_request_cancel_fail_items = "%%count%% հարցում(ներ)ը չեղարկվել չի հաջողվել" ill_request_cancel_selected = "Չեղարկեք միջգրադարանային բաժնույթի ընտրված հարցումները" ill_request_cancel_success = "Ձեր հարցումը հաջողությամբ չեղարկվել է" ill_request_cancel_success_items = "%%count%% հարցում(ներ)ը հաջողությամբ չեղարկվել են" @@ -839,6 +844,7 @@ More options = "Ավելի ընտրանքներ" More Summon results = "Ավելի Summon արդյունքներ…" More Topics = "Ավելի թեմաներ" more_authors_abbrev = "և այլն" +more_by_author = "Նաև %%name%%" more_ellipsis = "ավելին…" more_info_toggle = "Ցույց տալ/թաքցնել ավելի շատ տեղեկություններ:" more_options_ellipsis = "Ավելի ընտրանքներ…" @@ -885,6 +891,7 @@ no_email_address = "Էլ․ փոստի հասցեն բացակայում է:" no_items_selected = "Ոչ մի նյութ չի ընտրվել" no_proxied_user = "Վստահված օգտատեր չկա (խնդրեք ինքներդ ձեզ համար)" nohit_active_filters = "Այս որոնման համար կիրառվել են մեկ կամ մի քանի զտիչներ: Եթե հեռացնեք զտիչները, կարող եք ավելի շատ արդյունքներ ստանալ:" +nohit_busy = "Պատասխանելու համար համակարգը այս պահին չափազանց զբաղված է: Խնդրում ենք փորձել ավելի ուշ:" nohit_change_tab = 'Դուք փնտրում եք "%%activeTab%%" ներդիրում։ Դուք կարող եք ինչ-որ բան գտնել մյուս ներդիրներից մեկում.' nohit_filters = "Զտիչները ներկայումս կիրառվում են այս որոնման համար." nohit_heading = "Ոչ մի արդյունք:" @@ -1201,6 +1208,7 @@ renew_all = "Թարմացրեք բոլոր նյութերը" renew_determine_fail = "Մենք չկարողացանք որոշել, թե արդյոք ձեր նյութը կարող է թարմացվել: Խնդրում ենք կապվել աշխատակազմի անդամի հետ:" renew_empty_selection = "Ոչ մի նյութ չի ընտրվել" renew_error = "Մենք չկարողացանք թարմացնել ձեր նյութերը։ Խնդրում ենք կապվել աշխատակազմի անդամի հետ" +renew_error_summary = "Հնարավոր չէ թարմացնել {count, plural, =1 {1 item} other {# items}} սխալների պատճառով:" renew_fail = "Այս նյութը չհաջողվեց թարմացնել" renew_item = "Թարմացնել նյութը" renew_item_due = "Նյութը հասանելի կլինի առաջիկա 24 ժամվա ընթացքում" @@ -1213,6 +1221,7 @@ renew_item_requested = "Այս նյութը պահանջվել է մեկ այլ renew_select_box = "Թարմացնել նյութը" renew_selected = "Թարմացնել ընտրված նյութերը" renew_success = "Թարմացումը հաջողվեց" +renew_success_summary = "Հաջողությամբ թարմացվեց {count, plural, =1 {1 item} other {# items}}." Renewed = "Թարմացված է" Request full text = "Պահանջեք ամբողջական տեքստ" request_in_transit = "Տարանցիկ դեպի ստացման վայր" @@ -1284,6 +1293,7 @@ seconds_abbrev = "s" see all = "տես բոլորը" See also = "Տես նաև" see_all_ellipsis = "տես բոլորը…" +Select multiple filters = "Ընտրեք մի քանի զտիչներ" Select this record = "Ընտրեք այս գրառումը" Select your carrier = "Ընտրեք ձեր օպերատորը" select_all = "Ընտրեք բոլոր մուտքերը" @@ -1344,6 +1354,8 @@ sort_due_date_desc = "Վերադարձի ամսաթիվ (ամենաթարմը ս sort_relevance = "Համապատասխանություն" sort_return_date_asc = "Վերադարձի ամսաթիվ (ամենահինը սկզբից)" sort_return_date_desc = "Վերադարձի ամսաթիվ (ամենաթարմը սկզբից)" +sort_saved = "Պահպանել ամսաթիվը (նախ ամենանորը)" +sort_saved_asc = "Պահպանել ամսաթիվը (նախ ամենահինը)" sort_title = "Վերնագիր" sort_year = "Ամսաթիվը նվազող" sort_year_asc = "Ամսաթիվը Աճող" @@ -1352,6 +1364,7 @@ Source Title = "Աղբյուրի անվանումը" spell_expand_alt = "Ընդլայնել Որոնումը" spell_suggest = "Որոնել այլընտրանքներ" Staff View = "Աշխատակազմի տեսք" +standalone_record_link = "Միայնակ գրառում" Start a new Advanced Search = "Սկսեք նոր Ընդլայնված որոնում" Start a new Basic Search = "Սկսեք նոր Հիմնական որոնում" Start Page = "Սկզբի էջ" @@ -1364,6 +1377,7 @@ storage_retrieval_request_available = "Հասանելի է վերցնելու հ storage_retrieval_request_cancel = "Չեղարկել պահեստի առբերման հարցումը" storage_retrieval_request_cancel_all = "Չեղարկել պահեստի առբերման բոլոր հարցումները" storage_retrieval_request_cancel_fail = "Ձեր հարցումը չի չեղարկվել: Խնդրում ենք կապվել տացքակետի հետ հետագա օգնության համար" +storage_retrieval_request_cancel_fail_items = "%%count%% հարցում(ներ)ը չեղարկվել չի հաջողվել" storage_retrieval_request_cancel_selected = "Չեղարկել ընտրված պահեստի առբերման հարցումները" storage_retrieval_request_cancel_success = "Ձեր հարցումը հաջողությամբ չեղարկվել է" storage_retrieval_request_cancel_success_items = "%%count%% հարցում(ներ)ը հաջողությամբ չեղարկվել են" @@ -1450,6 +1464,7 @@ toggle_dropdown = "Միացնել բացվող պատուհանը" Too Many Email Recipients = "Չափից շատ էլ․ փոստի ստացողներ" too_many_favorites = "Այս ցուցակը չափազանց մեծ է միանգամից ցուցադրելու համար: Փորձեք վերադասավորել ձեր պահպանված նյութերը ավելի շատ ցուցակների մեջ կամ սահմանափակել պիտակների օգտագործումը:" too_many_new_items = "Չափազանց շատ նոր նյութեր կան մեկ ցուցակում ցուցադրելու համար: Փորձեք սահմանափակել ձեր որոնումը:" +too_many_query_terms = "Խնդրում ենք պարզեցնել ձեր հարցումը; այն պարունակում է ավելի շատ տերմիններ(%%terms%%) քան համակարգի սահմանափակումն է՝ (%%maxTerms%%):" too_many_reserves = "Դասընթացների ռեզերվները չափազանց շատ են մեկ ցուցակում ցուցադրելու համար: Փորձեք սահմանափակել ձեր որոնումը:" top_facet_label = "%%label%% ձեր որոնման շրջանակներում:" Topic = "Խորագիր" @@ -1530,6 +1545,8 @@ What am I looking at = "Ինչի՞ն եմ նայում։" widen_prefix = "Փորձեք ընդլայնել ձեր որոնումը դեպի" wiki_link = "Տրամադրված է Վիքիպեդիայի կողմից" with filters = "զտիչներով" +worldcat_group_related_editions = "Խմբի հետ կապված հրատարակություններ" +worldcat_group_variant_records = "Խմբային տարբերակի գրառումներ" Year of Publication = "Հրատարակման տարի" You do not have any fines = "Դուք տուգանքներ չունեք" You do not have any holds or recalls placed = "Դուք չունեք պահումներ կամ հետկանչեր տեղադրված" diff --git a/languages/it.ini b/languages/it.ini index b34b33a1a6d..b01c351a4ca 100644 --- a/languages/it.ini +++ b/languages/it.ini @@ -76,6 +76,7 @@ APA Citation = "Citazione APA" APA Edition Citation = "Citazione Stile APA (7a Edizione)" applied_filter = "Filtro applicato:" applied_filters = "filtri attivi:" +Apply filters = "Applica i filtri" Archival Material = "Materiali d'archivio" Article = "Articolo" Ask a Librarian = "Chiedi al bibliotecario" @@ -429,6 +430,7 @@ explain_coord = "* %%coord%% (rispetto alla ricerca, regola in base al numero di explain_difference_score = "differenza rispetto al punteggio massimo" explain_disabled = "Explain è disattivato per %%searchClassId%%" explain_for_search = "Spiegazione per la ricerca" +explain_function_query_label = "Funzione" explain_modified_value = "Prodotto di %%relevanceValue%% (valore di rilevanza)" explain_modifier = "con un modificatore di %%modifier%%" explain_record_score = "registra punteggio" @@ -436,6 +438,7 @@ explain_relevance = "Id del record: %%recordId%% trovato con un fattore di rilev explain_relevance_score = "Punteggio di rilevanza" explain_result_list_chart_title = "Punteggio: %%score%%" explain_result_list_hint = "Punteggio di rilevanza. Clicca per vedere la spiegazione dettagliata." +explain_show_raw = "Mostra spiegazione non elaborata" explain_sum = "somma" explain_top_relevance = "Pertinenza del risultato principale" Export = "Esporta" @@ -620,6 +623,7 @@ hold_edit_title = "Cambia informazioni prestito" hold_empty_selection = "Nessuna prenotazione selezionata" hold_error_age_restricted = "Un prestito espletato a causa di restrizioni d'età sul materiale." hold_error_blocked = "Non hai le autorizzazioni per fare una prenotazione. Contatta la biblioteca per chiarimenti." +hold_error_current_loan_patron_group = "Questo elemento non può essere richiesto al momento." hold_error_fail = "Richiesta fallita. Contatta la biblioteca per chiarimenti." hold_error_item_not_holdable = "Questo articolo non può essere richiesto." hold_error_not_holdable = "Questo materiale non può essere richiesto." @@ -667,6 +671,7 @@ ill_request_available = "Pronto per il ritiro" ill_request_cancel = "Cancella richiesta di prestito interbibliotecario" ill_request_cancel_all = "Cancella tutte le richieste di prestito interbibliotecario" ill_request_cancel_fail = "La tua richiesta non è stata cancellata. Contatta il servizio di prestito per assistenza." +ill_request_cancel_fail_items = "%%count%% richiesta/e non può/possono essere annullata/e" ill_request_cancel_selected = "Cancella le richieste di prestito interbibliotecario selezionate" ill_request_cancel_success = "La tua richiesta è stata cancellata con successo" ill_request_cancel_success_items = "%%count%% richiesta(e) cancellata(e) con successo" @@ -839,6 +844,7 @@ More options = "Approfondisci" More Summon results = "Più risultati (da Summon)..." More Topics = "Altri argomenti" more_authors_abbrev = "et al." +more_by_author = "Anche per %%name%%" more_ellipsis = "espandi..." more_info_toggle = "Mostra/nascondi più info." more_options_ellipsis = "Approfondisci..." @@ -885,6 +891,7 @@ no_email_address = "Indirizzo E-mail mancante." no_items_selected = "Non hai selezionato alcun elemento" no_proxied_user = "nessun utente delegato (richiesta per sé)" nohit_active_filters = "Hai applicato uno o più filtri a questa ricerca. Se li rimuovi, otterrai un maggior numero di risultati." +nohit_busy = "Il sistema è troppo occupato per fornire una risposta al momento. Per favore, riprova più tardi." nohit_change_tab = 'Stai cercando in "%%activeTab%%". Potresti trovare ulteriori risultati cercando in:' nohit_filters = "Filtri applicati attualmente per questa ricerca:" nohit_heading = "Nessun risultato!" @@ -1201,6 +1208,7 @@ renew_all = "Rinnova tutto" renew_determine_fail = "Non è possibile capire se la richiesta sia stata rinnovata. Contatta la biblioteca per chiarimenti." renew_empty_selection = "Nessun oggetto selezionato" renew_error = "Non possiamo effettuare il rinnovo. Contatta la biblioteca per chiarimenti." +renew_error_summary = "Impossibile rinnovare {count, plural, =1 {1 articolo} other {# articoli}} a causa di errori." renew_fail = "Richiesta non rinnovabile" renew_item = "Rinnova" renew_item_due = "Da restituire entro le prossime 24 ore" @@ -1213,6 +1221,7 @@ renew_item_requested = "Richiesto da un altro utente" renew_select_box = "Rinnova" renew_selected = "Rinnova i selezionati" renew_success = "Rinnovo effettuato" +renew_success_summary = "Rinnovato con successo {count, plural, =1 {1 articolo} other {# articoli}}." Renewed = "Rinnovato" Request full text = "Richiedi full text" request_in_transit = "In transito verso il punto di ritiro" @@ -1284,6 +1293,7 @@ seconds_abbrev = "s" see all = "vedi tutto" See also = "Vedi anche" see_all_ellipsis = "vedi tutto..." +Select multiple filters = "Seleziona più filtri" Select this record = "Seleziona questo record" Select your carrier = "Seleziona il tuo supporto" select_all = "Seleziona tutte le voci" @@ -1344,6 +1354,8 @@ sort_due_date_desc = "Data scadenza prestito (nuova prima)" sort_relevance = "Rilevanza" sort_return_date_asc = "Data rientro (più vecchia prima)" sort_return_date_desc = "Data rientro (nuova prima)" +sort_saved = "Data di salvataggio (dal più recente al più vecchio)" +sort_saved_asc = "Data di salvataggio (dal più vecchio al più recente)" sort_title = "Titolo" sort_year = "Data (discendente)" sort_year_asc = "Data (ascendente)" @@ -1352,6 +1364,7 @@ Source Title = "Titolo della fonte" spell_expand_alt = "Espandi la ricerca" spell_suggest = "Cerca anche" Staff View = "MARC21" +standalone_record_link = "Record autonomo" Start a new Advanced Search = "Inizia una nuova ricerca avanzata" Start a new Basic Search = "Inizia una nuova ricerca base" Start Page = "Pagina iniziale" @@ -1364,6 +1377,7 @@ storage_retrieval_request_available = "Pronto per il ritiro" storage_retrieval_request_cancel = "Cancella richiesta di volumi da magazzino" storage_retrieval_request_cancel_all = "Cancella tutte le richieste di volumi da magazzino" storage_retrieval_request_cancel_fail = "La tua richiesta non è stata cancellata. Contatta il servizio di prestito per assistenza." +storage_retrieval_request_cancel_fail_items = "%%count%% richiesta/e non può/possono essere annullata/e" storage_retrieval_request_cancel_selected = "Cancella le richieste di volumi da magazzino selezionate" storage_retrieval_request_cancel_success = "La tua richiesta è stata cancellata con successo" storage_retrieval_request_cancel_success_items = "%%count%% richiesta(e) cancellata con successo" @@ -1450,6 +1464,7 @@ toggle_dropdown = "Alterna il menu a tendina" Too Many Email Recipients = "Ci sono troppi indirizzi email." too_many_favorites = "Questa lista è troppo lunga per essere visualizzata. Prova a disporre i tuoi preferiti in più liste o a limitarli usando i tag." too_many_new_items = "Ci sono troppe nuove risorse per visualizzarle tutte. Prova a limitare la ricerca." +too_many_query_terms = "Per favore, semplifica la tua ricerca; contiene più termini %%terms%% del limite consentito dal sistema %%maxTerms%%." too_many_reserves = "Ci sono troppe risorse riservate per visualizzarle tutte. Prova a limitare la ricerca." top_facet_label = "%%label%% all'interno della tua ricerca." Topic = "Soggetto" @@ -1530,6 +1545,8 @@ What am I looking at = "Che cosa sto cercando?" widen_prefix = "Prova anche ad ampliare la ricerca" wiki_link = "da Wikipedia" with filters = "con filtri" +worldcat_group_related_editions = "Raggruppa le edizioni correlate" +worldcat_group_variant_records = "Raggruppa i record varianti" Year of Publication = "Anno di pubblicazione" You do not have any fines = "Non hai alcuna sanzione" You do not have any holds or recalls placed = "Non hai prenotazioni o richieste in atto" diff --git a/languages/ja.ini b/languages/ja.ini index c1237fc18bc..c26c1e77134 100644 --- a/languages/ja.ini +++ b/languages/ja.ini @@ -77,6 +77,7 @@ APA Citation = "APA引用形式" APA Edition Citation = "APA(7版)引用形式" applied_filter = "適用されたフィルター:" applied_filters = "適用フィルター:" +Apply filters = "フィルターを適用" Archival Material = "アーカイブ資料" Article = "論文" Ask a Librarian = "図書館員に聞く" @@ -430,6 +431,7 @@ explain_coord = "* %%coord%% (検索と比較してマッチ数を調整)" explain_difference_score = "上位スコアとの差異" explain_disabled = "%%searchClassId%%ではExplain機能は無効です" explain_for_search = "検索の説明" +explain_function_query_label = "ファンクション" explain_modified_value = "%%relevanceValue%% (関連値)の積" explain_modifier = "modifier %%modifier%%を適用" explain_record_score = "レコードスコア" @@ -437,6 +439,7 @@ explain_relevance = "レコードId: 関連値%%relevanceValue%%の%%recordId%% explain_relevance_score = "関連値スコア" explain_result_list_chart_title = "スコア: %%score%%" explain_result_list_hint = "関連値スコア。詳細な説明はクリックしてください。" +explain_show_raw = "加工前のexplanationを表示" explain_sum = "合計" explain_top_relevance = "検索上位の関連値" Export = "エクスポート" @@ -621,6 +624,7 @@ hold_edit_title = "予約情報の変更" hold_empty_selection = "資料が予約されていません" hold_error_age_restricted = "年齢制限のため、この本の取り置きはできません。" hold_error_blocked = "この資料を予約する権限がありません。詳細は閲覧係にご相談ください。" +hold_error_current_loan_patron_group = "この資料は現在取り置き依頼ができません。" hold_error_fail = "予約できませんでした。閲覧係にご連絡ください。" hold_error_item_not_holdable = "この資料は取り置きできません。" hold_error_not_holdable = "この資料は取り置きできません。" @@ -668,6 +672,7 @@ ill_request_available = "予約可能" ill_request_cancel = "ILLリクエストのキャンセル" ill_request_cancel_all = "すべてのILLリクエストのキャンセル" ill_request_cancel_fail = "予約の取消ができませんでした。閲覧係にご連絡ください。" +ill_request_cancel_fail_items = "%%count%%件の依頼がキャンセルできませんでした" ill_request_cancel_selected = "選択したILLリクエストのキャンセル" ill_request_cancel_success = "資料の予約を取り消しました" ill_request_cancel_success_items = "%%count%% 資料の予約を取り消しました" @@ -840,6 +845,7 @@ More options = "オプションを追加" More Summon results = "Summon検索結果をさらに表示…" More Topics = "さらなるトピック" more_authors_abbrev = "等" +more_by_author = "%%name%%による" more_ellipsis = "もっと見る…" more_info_toggle = "詳細情報の表示/非表示" more_options_ellipsis = "オプションを追加…" @@ -886,6 +892,7 @@ no_email_address = "メールアドレスが指定されていません。" no_items_selected = "アイテムが選択されていません" no_proxied_user = "代理ユーザではない(自分用にリクエスト)" nohit_active_filters = "一つ以上のファッセット・フイルターがこの検索に適用されます.もしフィルターを削除すると, より多くの結果が得られるかもしれません。" +nohit_busy = "現在、システムが混雑しているため応答できません。少し時間をおいて再度実行してください。" nohit_change_tab = "%%activeTab%%を検索しています。他のタブのひとつで何か見つかるかも知れません。" nohit_filters = "現在この検索に適用されているフィルター:" nohit_heading = "該当資料なし" @@ -1202,6 +1209,7 @@ renew_all = "全資料の貸出更新" renew_determine_fail = "資料の貸出更新が可能かどうか判断できませんでした。閲覧係にご連絡ください。" renew_empty_selection = "資料が選択されていません" renew_error = "更新できませんでした。閲覧係にご連絡ください。" +renew_error_summary = "エラーのため {count, plural, =1 {1 件} other {# 件}}の更新ができませんでした。" renew_fail = "更新できませんでした" renew_item = "貸出更新" renew_item_due = "返却期限前日" @@ -1214,6 +1222,7 @@ renew_item_requested = "この資料は他の利用者に予約されていま renew_select_box = "貸出更新" renew_selected = "選択資料を貸出更新" renew_success = "更新されました" +renew_success_summary = "{count, plural, =1 {1 件} other {# 件}}、更新しました。" Renewed = "更新" Request full text = "全文入手のリクエスト" request_in_transit = "引取場所に運送中" @@ -1285,6 +1294,7 @@ seconds_abbrev = "秒" see all = "すべて見る" See also = "をも見よ" see_all_ellipsis = "すべて見る…" +Select multiple filters = "フィルターの選択" Select this record = "このレコードを選択" Select your carrier = "携帯会社を選択してください" select_all = "すべての項目を選択" @@ -1345,6 +1355,8 @@ sort_due_date_desc = "返却期限日(降順)" sort_relevance = "適合順" sort_return_date_asc = "返却日(昇順)" sort_return_date_desc = "返却日(降順)" +sort_saved = "保存データ(降順)" +sort_saved_asc = "保存データ(昇順)" sort_title = "タイトル順" sort_year = "出版年降順" sort_year_asc = "出版年昇順" @@ -1353,6 +1365,7 @@ Source Title = "情報源タイトル" spell_expand_alt = "検索語の拡大" spell_suggest = "提案スペルによる検索" Staff View = "MARC表示" +standalone_record_link = "レコード単独" Start a new Advanced Search = "詳細検索をやり直す" Start a new Basic Search = "基本検索をやり直す" Start Page = "開始ページ" @@ -1365,6 +1378,7 @@ storage_retrieval_request_available = "予約可能" storage_retrieval_request_cancel = "所蔵検索リクエストのキャンセル" storage_retrieval_request_cancel_all = "すべての所蔵検索リクエストのキャンセル" storage_retrieval_request_cancel_fail = "予約の取消ができませんでした。閲覧係にご連絡ください。" +storage_retrieval_request_cancel_fail_items = "%%count%% 件、キャンセルできませんでした" storage_retrieval_request_cancel_selected = "選択した所蔵検索リクエストのキャンセル" storage_retrieval_request_cancel_success = "資料の予約を取り消しました" storage_retrieval_request_cancel_success_items = "%%count%% 資料の予約を取り消しました" @@ -1451,6 +1465,7 @@ toggle_dropdown = "ドロップダウンをトグル" Too Many Email Recipients = "メール受信者が多すぎます" too_many_favorites = "お気に入り項目が多すぎて一度に表示できません。リストを増やして再配置するか、タグを使って制限してください。" too_many_new_items = "新規資料が多すぎて一つのリストに表示できません。検索条件を追加してください。" +too_many_query_terms = "検索項目を絞ってください。指定された項目数 (%%terms%%) がシステムの最大項目数 (%%maxTerms%%) を超えています。" too_many_reserves = "講義資料が多すぎて一つのリストに表示できません。検索条件を追加してください。" top_facet_label = "%%label%% ..." Topic = "トピック" @@ -1531,6 +1546,8 @@ What am I looking at = "現在の検索対象" widen_prefix = "検索範囲を拡大" wiki_link = "Wikipediaによる" with filters = "フィルター適用" +worldcat_group_related_editions = "グループ関連エディション" +worldcat_group_variant_records = "グループ異版レコード" Year of Publication = "出版年" You do not have any fines = "延滞金はありません" You do not have any holds or recalls placed = "予約または返却請求された資料はありません" diff --git a/languages/mn.ini b/languages/mn.ini index a6a2db9f9e8..591ca02c8b3 100644 --- a/languages/mn.ini +++ b/languages/mn.ini @@ -74,6 +74,7 @@ APA Citation = "APA Баримт" APA Edition Citation = "APA-ийн эшлэл(7 дахь хэвлэлт)" applied_filter = "Ашигласан шүүлтүүр" applied_filters = "Ашигласан шүүлтүүрүүд:" +Apply filters = "Шүүлтүүрүүдийг хэрэглэх" Archival Material = "Архивын материал" Article = "Өгүүллэг" Ask a Librarian = "Номын санчаас асуух" @@ -427,6 +428,7 @@ explain_coord = "* %%coord%% (хайлттай харьцуулахад тохи explain_difference_score = "дээд онооны ялгаа" explain_disabled = "%%searchClassId%%-д тайлбарлахыг идэвхгүй болгосон" explain_for_search = "Хайлтын тайлбар" +explain_function_query_label = "Үүрэг" explain_modified_value = "%%relevanceValue%% бүтээгдэхүүн (хамааралтай үнэ цэнэ)" explain_modifier = "%%modifier%% хувиргагчтай" explain_record_score = "дээд оноо" @@ -434,6 +436,7 @@ explain_relevance = "Бичлэгийн дугаар: %%relevanceValue%% хам explain_relevance_score = "Холбогдох оноо" explain_result_list_chart_title = "Оноо: %% оноо%%" explain_result_list_hint = "Холбогдох оноо. Дэлгэрэнгүй тайлбарыг харахын тулд товшино уу." +explain_show_raw = "Тайлбарыг түүхий хэлбэрээр үзүүлэх" explain_sum = "нийлбэр" explain_top_relevance = "Шилдэг үр дүнгийн хамаарал" Export = "Экспорт" @@ -618,6 +621,7 @@ hold_edit_title = "Захилгын мэдээлэл өөрчлөх" hold_empty_selection = "Ямар ч түр хойшлуулалтыг сонгосонгүй" hold_error_age_restricted = "Материалын насжилтын хязгаарлалтын улмаас түр хойшлуулалт хийх боломжгүй." hold_error_blocked = "Танд энэ зүйлийг түр хойшлуулах хангалттай эрх байхгүй" +hold_error_current_loan_patron_group = "Энэ зүйлийг одоогоор хүсэх боломжгүй байна." hold_error_fail = "Таны хүсэлт амжилтгүй боллоо. Цаашдын туслалцаа авахын тулд ном түрээслэх ширээтэй холбоо барина уу" hold_error_item_not_holdable = "Энэ зүйл дээр хүсэлт гаргах боломжгүй." hold_error_not_holdable = "Энэ материал дээр хүсэлт гаргах боломжгүй." @@ -665,6 +669,7 @@ ill_request_available = "Авахад бэлэн байгаа" ill_request_cancel = "Номын сан хоорондын түрээсийн хүсэлтийг цуцлах" ill_request_cancel_all = "Номын сан хоорондын бүх түрээсийн хүсэлтийг цуцлах" ill_request_cancel_fail = "Таны хүсэлтийг цуцлаагүй байна. Цаашдын туслалцаа авахын тулд ном түрээслэх ширээтэй холбоо барина уу" +ill_request_cancel_fail_items = "%%count%% хүсэлтийг цуцлах боломжгүй байна" ill_request_cancel_selected = "Номын сан хоорондын сонгосон түрээсийн хүсэлтийг цуцлах" ill_request_cancel_success = "Таны хүсэлтийг амжилттай цуцлав" ill_request_cancel_success_items = "%%count%% хүсэлт(үүд)-ийг амжилттай цуцаллаа" @@ -837,6 +842,7 @@ More options = "Бусад сонголтууд" More Summon results = "Бусад дуудлагын үр дүн" More Topics = "Бусад сэдвүүд" more_authors_abbrev = "зэрэг" +more_by_author = "Мөн %%name%%-с" more_ellipsis = "илүү их..." more_info_toggle = "Дэлгэрэнгүй мэдээллийг харуулах/нуух" more_options_ellipsis = "Бусад сонголтууд..." @@ -883,6 +889,7 @@ no_email_address = "Цахим шуудангийн хаяг алга." no_items_selected = "Ямар ч зүйл сонгоогүй байна" no_proxied_user = "Ямарч прохи хэрэглэгч байхгүй (өөртөө хүсэлт гаргасан)" nohit_active_filters = "Энэ хайлтанд нэг буюу хэд хэдэн нүүрний шүүлтүүрийг ашигласан болно. Шүүлтүүрийг устгавал илүү их үр дүнг олж авах боломжтой." +nohit_busy = "Систем одоогоор хэт ачаалалтай байна. Дахин оролдоно уу." nohit_change_tab = 'Та "%%activeTab%%" таб дээр хайлт хийж байна. Та бусад табуудын аль нэг дээр ямар нэгэн зүйл олж магадгүй:' nohit_filters = "Энэ хайлтанд одоогоор ашиглаж буй шүүлтүүрүүд:" nohit_heading = "Үр дүн алга!" @@ -1199,6 +1206,7 @@ renew_all = "Бүх зүйлийг шинэчлэх" renew_determine_fail = "Таны зүйлийг шинэчлэх боломжтой эсэхийг бид тодорхойлж чадсангүй. Ажилтнуудын гишүүнтэй холбоо барина уу." renew_empty_selection = "Ямар ч зүйлийг сонгосонгүй" renew_error = "Бид таны зүйл (үүд)-г шинэчлэх боломжгүй байсан - Ажилтнуудын гишүүнтэй холбоо барина уу" +renew_error_summary = "{count, plural, =1 {1 зүйл} other {# зүйлүүд}}-ийг алдааны улмаас сунгалт хийж чадсангүй." renew_fail = "Энэ зүйлийг шинэчлэх боломжгүй байна" renew_item = "Зүйлийг шинэчлэх" renew_item_due = "Дараагийн 24 цагийн дотор хугацаа дуусах зүйл" @@ -1211,6 +1219,7 @@ renew_item_requested = "Энэ зүйл дээр өөр хэрэглэгч хү renew_select_box = "Зүйлийг шинэчлэх" renew_selected = "Сонгосон зүйлийг шинэчлэх" renew_success = "Шинэчлэлт амжилттай боллоо" +renew_success_summary = "{count, plural, =1 {1 зүйл} other {# зүйлүүд}}-ийг амжилттай сунгалт хийгдсэн." Renewed = "Шинэчилсэн" Request full text = "Бүрэн текстийн хүсэлт гаргах" request_in_transit = "Авах байршил руу шилжиж байна" @@ -1282,6 +1291,7 @@ seconds_abbrev = "s" see all = "бүгдийг харах" See also = "Мөн үзнэ үү" see_all_ellipsis = "бүгдийг харах..." +Select multiple filters = "Олон шүүлтүүр сонгох" Select this record = "Энэ бүртгэлийг сонгох" Select your carrier = "Оператор компаниа сонгох" select_all = "Бүх оруулгыг сонгох" @@ -1342,6 +1352,8 @@ sort_due_date_desc = "Дуусах огноо (шинэ эхэндээ)" sort_relevance = "Хамааралтай байдал" sort_return_date_asc = "Буцаах огноо (хуучин эхэндээ)" sort_return_date_desc = "Буцаах огноо (шинэ эхэндээ)" +sort_saved = "Хадгалах огноо (хамгийн шинэ)" +sort_saved_asc = "Хадгалах огноо (хамгийн хуучин)" sort_title = "Гарчиг" sort_year = "Огноо буурахаар" sort_year_asc = "Огноо өсөхөөр" @@ -1350,6 +1362,7 @@ Source Title = "Эх сурвалжийн гарчиг" spell_expand_alt = "Хайлтыг өргөжүүлэх" spell_suggest = "Хайлтын өөр хувилбарууд" Staff View = "Ажилтнуудыг харах" +standalone_record_link = "Тусдаа бичлэг" Start a new Advanced Search = "Шинэ Ахисан түвшний хайлт эхлүүлэх" Start a new Basic Search = "Шинэ Үндсэн хайлт эхлүүлэх" Start Page = "Хуудас эхлүүлэх" @@ -1362,6 +1375,7 @@ storage_retrieval_request_available = "Хүлээн авах боломжтой" storage_retrieval_request_cancel = "Хадгалалтыг буцаан татах хүсэлтийг цуцлах" storage_retrieval_request_cancel_all = "Хадгалалтыг буцаан татах бүх хүсэлтийг цуцлах" storage_retrieval_request_cancel_fail = "Таны хүсэлтийг цуцлаагүй. Цаашдын туслалцаа авахын тулд түрээслэх ширээтэй холбоо барина уу" +storage_retrieval_request_cancel_fail_items = "%%count%% хүсэлтийг цуцлах боломжгүй байна" storage_retrieval_request_cancel_selected = "Сонгосон хадгалалт буцаан татах хүсэлтийг цуцлах" storage_retrieval_request_cancel_success = "Таны хүсэлтийг амжилттай цуцлав" storage_retrieval_request_cancel_success_items = "%%count%% хүсэлт(үүд)-ийг амжилттай цуцлав" @@ -1448,6 +1462,7 @@ toggle_dropdown = "Дээш доош гүйгэх" Too Many Email Recipients = "Хэт олон цахим шуудан хүлээн авагч" too_many_favorites = "Энэ жагсаалтыг нэг дор харуулахад хэтэрхий том байна. Өөрийн дуртай зүйлээ илүү олон жагсаалтад багтааж, хаяг ашиглан хязгаарлаж үзнэ үү." too_many_new_items = "Нэг жагсаалтад харуулахад хэт олон шинэ зүйл байна. Хайлтаа хязгаарлаж үзээрэй." +too_many_query_terms = "Таны хүсэлт хэтэрхий олон үг агуулсан байна (%%terms%%), системийн хязгаар (%%maxTerms%%)-аас хэтэрсэн байна." too_many_reserves = "Нэг жагсаалтад харуулахад хэт олон тооны нөөц байна. Хайлтаа хязгаарлаж үзээрэй." top_facet_label = "Таны хайлт доторх %%label%%." Topic = "Сэдэв" @@ -1528,6 +1543,8 @@ What am I looking at = "Би юу харж байна вэ?" widen_prefix = "Хайлтаа руу өргөжүүлээд үзээрэй" wiki_link = "Wikipedia-аас ирүүлсэн" with filters = "шүүлтүүртэй" +worldcat_group_related_editions = "Холбогдох хэвлэлүүдийг бүлэглэх" +worldcat_group_variant_records = "Өөр хувилбаруудыг бүлэглэх" Year of Publication = "Хэвлэсэн он" You do not have any fines = "Танд ямар ч торгууль байхгүй байна" You do not have any holds or recalls placed = "Танд түр хойшлуулсан эсвэл буцааж татсан зүйл байхгүй байна" diff --git a/languages/pt-br.ini b/languages/pt-br.ini index b14a26719e6..953ce06382a 100644 --- a/languages/pt-br.ini +++ b/languages/pt-br.ini @@ -76,6 +76,7 @@ APA Citation = "Citação norma APA" APA Edition Citation = "Citação APA (7ª ed.)" applied_filter = "Filtro Aplicado:" applied_filters = "Filtros aplicados:" +Apply filters = "Aplicar filtros" Archival Material = "Material de Arquivo" Article = "Artigo" Ask a Librarian = "Serviço de Referência" @@ -429,6 +430,7 @@ explain_coord = "* %%coord%% (ajuste pelo número de correspondências em relaç explain_difference_score = "diferença para a pontuação máxima" explain_disabled = "Explicação desativada para %%searchClassId%%" explain_for_search = "Explicação para pesquisa" +explain_function_query_label = "Função" explain_modified_value = "Produto de %%relevanceValue%% (valor de relevância)" explain_modifier = "com um modificador de %%modifier%%" explain_record_score = "pontuação do registro" @@ -436,6 +438,7 @@ explain_relevance = "ID do Registro: %%recordId%% encontrado com um valor de rel explain_relevance_score = "Pontuação de Relevância" explain_result_list_chart_title = "Pontuação: %%score%%" explain_result_list_hint = "Pontuação de relevância. Clique para ver a explicação detalhada." +explain_show_raw = "Mostrar explicação completa" explain_sum = "soma" explain_top_relevance = "Relevância do Resultado Principal" Export = "Exportar" @@ -620,6 +623,7 @@ hold_edit_title = "Alterar informações de reservas" hold_empty_selection = "Não selecionou nenhuma reserva" hold_error_age_restricted = "Uma reserva não pode ser realizada devido à restrição de idade no material." hold_error_blocked = "Os seus privilégios não lhe permitem realizar uma reserva sobre este item" +hold_error_current_loan_patron_group = "Este item não pode ser solicitado no momento." hold_error_fail = "O seu pedido não foi bem-sucedido; por favor, contate o Balcão de Atendimento para ajuda adicional" hold_error_item_not_holdable = "Este item não pode ser solicitado." hold_error_not_holdable = "Este material não pode ser solicitado." @@ -667,6 +671,7 @@ ill_request_available = "Disponível para levantamento" ill_request_cancel = "Cancelar pedido de débito interbiblioteca" ill_request_cancel_all = "Cancelar todos os pedidos de débito interbiblioteca" ill_request_cancel_fail = "A sua reserva não foi cancelada; por favor, contate o Balcão de Atendimento para ajuda adicional" +ill_request_cancel_fail_items = "%%count%% solicitação(ões) não puderam ser canceladas" ill_request_cancel_selected = "Cancelar as requisições de débito interbibliotecas selecionadas" ill_request_cancel_success = "A sua reserva foi cancelada com sucesso" ill_request_cancel_success_items = "%%count%% reserva(s) foram canceladas com sucesso" @@ -839,6 +844,7 @@ More options = "Mais opções" More Summon results = "Mais resultados Summon…" More Topics = "Mais tópicos" more_authors_abbrev = "et al." +more_by_author = "Também por %%name%%" more_ellipsis = "Mais…" more_info_toggle = "Mostrar / ocultar mais informações." more_options_ellipsis = "Mais opções…" @@ -885,6 +891,7 @@ no_email_address = "Endereço de e-mail ausente." no_items_selected = "Nenhum item selecionado" no_proxied_user = "Nenhum usuário proxy (pedido para si mesmo)" nohit_active_filters = "Uma ou mais filtros de facetas têm sido aplicados a esta busca. Se você remover os filtros, você pode recuperar mais resultados" +nohit_busy = "O sistema está muito ocupado para fornecer uma resposta agora. Por favor, tente novamente mais tarde." nohit_change_tab = 'Você tem buscado na "%%activeTab%%" aba. Você pode encontrar algo em uma das outras abas:' nohit_filters = "Filtros aplicados a esta busca:" nohit_heading = "Nenhum registro encontrado!" @@ -1201,6 +1208,7 @@ renew_all = "Renovar todos os itens" renew_determine_fail = "Não foi possível determinar se o empréstimo pode ser renovado; por favor, contate o Balcão de Atendimento mais próximo." renew_empty_selection = "Não selecionou nenhum exemplar" renew_error = "Não foi possível renovar os seus empréstimos; por favor, contate o Balcão de Atendimento mais próximo." +renew_error_summary = "Não foi possível renovar {count, plural, =1 {1 item} other {# itens}} devido a erros." renew_fail = "O empréstimo deste exemplar não pode ser renovado" renew_item = "Renovar empréstimo" renew_item_due = "Prazo de devolução termina nas próximas 24 horas" @@ -1213,6 +1221,7 @@ renew_item_requested = "Este exemplar foi reservado por outro usuário" renew_select_box = "Renovar empréstimo" renew_selected = "Renovar os empréstimos selecionados" renew_success = "Empréstimo(s) renovados com sucesso" +renew_success_summary = "Renovado com sucesso {count, plural, =1 {1 item} other {# itens}}." Renewed = "Renovado" Request full text = "Pedir texto completo" request_in_transit = "Em trânsito para Coletar Localização" @@ -1284,6 +1293,7 @@ seconds_abbrev = "s" see all = "Ver todos" See also = "Ver também" see_all_ellipsis = "Ver todos…" +Select multiple filters = "Selecionar vários filtros" Select this record = "Selecione este registro" Select your carrier = "Selecione a sua operadora" select_all = "Selecionar todas as entradas" @@ -1344,6 +1354,8 @@ sort_due_date_desc = "Data de Vencimento (o mais recente primeiro)" sort_relevance = "Relevância" sort_return_date_asc = "Data de Devolução (o mais antigo primeiro)" sort_return_date_desc = "Data de Devolução (o mais recente primeiro)" +sort_saved = "Salvar data (mais recente primeiro)" +sort_saved_asc = "Salvar data (mais antigo primeiro)" sort_title = "Título" sort_year = "Data Descendente" sort_year_asc = "Data Ascendente" @@ -1352,6 +1364,7 @@ Source Title = "Título Fonte" spell_expand_alt = "Expandir a Busca" spell_suggest = "Buscas alternativas" Staff View = "Registro fonte" +standalone_record_link = "Registro Independente" Start a new Advanced Search = "Iniciar uma nova Busca Avançada" Start a new Basic Search = "Iniciar uma nova Busca Básica" Start Page = "Página inicial" @@ -1364,6 +1377,7 @@ storage_retrieval_request_available = "Disponível para levantamento" storage_retrieval_request_cancel = "Cancelar Pedido de Recuperação Armazenado" storage_retrieval_request_cancel_all = "Cancelar todos os Pedidos de Recuperação Armazenado" storage_retrieval_request_cancel_fail = "A sua reserva não foi cancelada; por favor, contate o Balcão de Atendimento para ajuda adicional" +storage_retrieval_request_cancel_fail_items = "%%count%% solicitação(ões) não puderam ser canceladas" storage_retrieval_request_cancel_selected = "Cancelar Pedido de Recuperação Armazenado" storage_retrieval_request_cancel_success = "A sua reserva foi cancelada com sucesso" storage_retrieval_request_cancel_success_items = "%%count%% reserva(s) foram canceladas com sucesso" @@ -1450,6 +1464,7 @@ toggle_dropdown = "Alternar lista suspensa" Too Many Email Recipients = "Excesso de e-mails destinatário" too_many_favorites = "Essa lista é muito grande para mostrar tudo de uma vez. Tente reorganizar seus favoritos em mais listas ou limite o uso de tags." too_many_new_items = "Há muitos novos registros para mostrar numa única lista. Tente limitar a sua busca." +too_many_query_terms = "Por favor, simplifique sua consulta; ela contém mais termos (%%terms%%) do que o limite do sistema (%%maxTerms%%)." too_many_reserves = "Há demasiadas obras recomendadas para mostrar numa única lista. Tente limitar a sua busca." top_facet_label = "%%label%% dentro de sua busca." Topic = "Assunto" @@ -1530,6 +1545,8 @@ What am I looking at = "O que eu estou olhando?" widen_prefix = "Tente ampliar a sua busca a" wiki_link = "Fornecido pela Wikipedia" with filters = "com os filtros" +worldcat_group_related_editions = "Agrupar Edições Relacionadas" +worldcat_group_variant_records = "Agrupar Registros Variantes" Year of Publication = "Ano de Publicação" You do not have any fines = "Você não tem quaisquer multas" You do not have any holds or recalls placed = "Você não tem qualquer reserva ou pedidos de devolução" diff --git a/languages/ru.ini b/languages/ru.ini index 83370e82b0c..91015f712fd 100644 --- a/languages/ru.ini +++ b/languages/ru.ini @@ -93,6 +93,7 @@ APA Citation = "APA Цитирование" APA Edition Citation = "Цитирование APA (7-е изд.)" applied_filter = "Примененный фильтр:" applied_filters = "Применяемые фильтры:" +Apply filters = "Применить фильтры" Archival Material = "Архивный материал" Article = "Статья" Ask a Librarian = "Обратитесь к библиотекарю" @@ -446,6 +447,7 @@ explain_coord = "* %%coord%% (скорректировать количеств explain_difference_score = "разница с лучшим результатом" explain_disabled = "Объяснение деактивировано для %%searchClassId%%" explain_for_search = "Пояснение к поиску" +explain_function_query_label = "Функция" explain_modified_value = "Произведение %%relevanceValue%% (значение релевантности)" explain_modifier = "с модификатором %%modifier%%" explain_record_score = "Показатель записи" @@ -453,6 +455,7 @@ explain_relevance = "Идентификатор записи: %%recordId%% на explain_relevance_score = "Показатель релевантности" explain_result_list_chart_title = "Оценка: %%score%%" explain_result_list_hint = "Показатель релевантности. Нажмите, чтобы просмотреть подробное объяснение." +explain_show_raw = "Показать необработанное объяснение" explain_sum = "сумма" explain_top_relevance = "Наибольшая релевантность результатов" Export = "Экспорт" @@ -637,6 +640,7 @@ hold_edit_title = "Изменить информацию об удержании hold_empty_selection = "Не выбрано задолженностей." hold_error_age_restricted = "Задержка невозможна из-за возрастных ограничений на материал" hold_error_blocked = "Отсутствуют достаточные права для установки задолженности на этот документ" +hold_error_current_loan_patron_group = "В настоящее время этот элемент не может быть запрошен." hold_error_fail = "Сбой Вашего запроса Обратитесь за помощью в circulation desk." hold_error_item_not_holdable = "Этот предмет не может быть запрошен." hold_error_not_holdable = "Этот материал не может быть запрошен." @@ -684,6 +688,7 @@ ill_request_available = "Доступна ячейка подхвата" ill_request_cancel = "Отмена межбиблиотечного запроса" ill_request_cancel_all = "Отмена всех межбиблиотечных запросов" ill_request_cancel_fail = "Ваш запрос не отменен. Обратитесь за помощью к библиотекарям." +ill_request_cancel_fail_items = "%%count%% запросов не могут быть отменены" ill_request_cancel_selected = "Отмена выбранных межбиблиотечных запросов" ill_request_cancel_success = "Ваш запрос успешно отменен." ill_request_cancel_success_items = "%%count%% Запросы успешно отменены." @@ -856,6 +861,7 @@ More options = "Дополнительные возможности" More Summon results = "Подробный просмотр по Summon..." More Topics = "Дополнительные темы" more_authors_abbrev = "и др." +more_by_author = "Также %%name%%" more_ellipsis = "больше..." more_info_toggle = "Показ/скрытие дополнительной информации." more_options_ellipsis = "Дополнительные возможности..." @@ -902,6 +908,7 @@ no_email_address = "Отсутствует адрес электронной п no_items_selected = "Не выбраны метки." no_proxied_user = "Нет проксируемого пользователя (запрос для себя)" nohit_active_filters = "Один или более facet-фильтров были применены к этому поиску. Если удалить фильтры, можно выбрать еще результаты." +nohit_busy = "Система слишком занята, чтобы предоставить ответ прямо сейчас. Пожалуйста, повторите попытку позже." nohit_change_tab = 'Вы искали в "%%activeTab%%" таблице. Вы можете искать в одной из следующих таблиц:' nohit_filters = "Сейчас применимы фильтры для этого поиска:" nohit_heading = "Нет результатов!" @@ -1218,6 +1225,7 @@ renew_all = "Обновить все документы" renew_determine_fail = "Невозможно определить, был ли обновлен Ваш документ. Обратитесь за помощью к библиотекарю." renew_empty_selection = "Не выбраны документы" renew_error = "Невозможно обновить Ваши документв- Обратитесь за помощью к библиотекрю." +renew_error_summary = "Не удалось обновить {count, plural, =1 {1 элемент} other {# элементов}} из-за ошибок." renew_fail = "Невозможно обновить Ваш документ" renew_item = "Обновить документ" renew_item_due = "Документ в течении следующих 24 часов" @@ -1230,6 +1238,7 @@ renew_item_requested = "Невозможно запросить этот док renew_select_box = "Обновить документ" renew_selected = "Обновить ввыбранные документы" renew_success = "Успешно выполнено обновление" +renew_success_summary = "Успешно обновлено {count, plural, =1 {1 элемент} other {# элементов}}." Renewed = "Переобновление" Request full text = "Запрос полного текста" request_in_transit = "Транзит к выбранному месту назначения" @@ -1301,6 +1310,7 @@ seconds_abbrev = "сек." see all = "см. все" See also = "См. также" see_all_ellipsis = "см. все..." +Select multiple filters = "Выберите несколько фильтров" Select this record = "Выбрать эту запись" Select your carrier = "Выбрать носитель" select_all = "Выбрать все записи" @@ -1361,6 +1371,8 @@ sort_due_date_desc = "Срок выполнения (сначала самые sort_relevance = "Релевантность" sort_return_date_asc = "Дата возврата (сначала самые старые)" sort_return_date_desc = "Дата возврата (сначала самые новые)" +sort_saved = "Сохранить дату (сначала самые новые)" +sort_saved_asc = "Сохранить дату (сначала самая старая)" sort_title = "Заглавие" sort_year = "Нижняя дата" sort_year_asc = "Верхняя дата" @@ -1369,6 +1381,7 @@ Source Title = "Заголовок источника" spell_expand_alt = "Расширить поиск" spell_suggest = "Альтернативы поиска" Staff View = "Marc-запись" +standalone_record_link = "Отдельная запись" Start a new Advanced Search = "Начать новый расширенный поиск" Start a new Basic Search = "Начать новый основной поиск" Start Page = "Первая страница" @@ -1381,6 +1394,7 @@ storage_retrieval_request_available = "Доступна ячейка подхв storage_retrieval_request_cancel = "Отменить запросы выборки из хранилища" storage_retrieval_request_cancel_all = "Отмена ВСЕХ запросов выборки из хранилища" storage_retrieval_request_cancel_fail = "Ваш запрос не отменен. Обратитесь за помощью к библиотекарям." +storage_retrieval_request_cancel_fail_items = "%%count%% запрос(ы) не удалось отменить" storage_retrieval_request_cancel_selected = "Отмена выбранных запросов выборки из хранилища" storage_retrieval_request_cancel_success = "Ваш запрос успешно отменен." storage_retrieval_request_cancel_success_items = "%%count%% Запросы успешно отменены." @@ -1467,6 +1481,7 @@ toggle_dropdown = "Переключить раскрывающийся спис Too Many Email Recipients = "Слишком много получателей E-Mail" too_many_favorites = "Список слишком велик для отображения целиком на экране. Превратите свои Избранные в несколько списков или ограничьте использование меток." too_many_new_items = "Слишком много документов для отображения в одном списке. Ограничьте свой поиск." +too_many_query_terms = "Пожалуйста, упростите запрос; он содержит больше терминов (%%terms%%) чем ограничено системой. (%%maxTerms%%)." too_many_reserves = "Слишком много резервов курса для отображения в одном списке. Ограничьте свой поиск." top_facet_label = "%%label%% внутри своего поиска." Topic = "Тема" @@ -1547,6 +1562,8 @@ What am I looking at = "Что я ищу?" widen_prefix = "Попытайтесь расширить свой поиск" wiki_link = "Предоставлено Wikipedia" with filters = "с фильтрами" +worldcat_group_related_editions = "Связанные с группой издания" +worldcat_group_variant_records = "Вариант записи группы" Year of Publication = "Дата издания" You do not have any fines = "У Вас нет штафов" You do not have any holds or recalls placed = "У пользователя не должно быть никаких задолженностей или напоминаний" diff --git a/languages/se.ini b/languages/se.ini index 9eb982ded02..9ca55ee3c22 100644 --- a/languages/se.ini +++ b/languages/se.ini @@ -66,6 +66,7 @@ alphabrowse_matches = "Bohtosat" alphabrowselink_html = 'Bláđe %%index%%- indeavssa nu ahte álggát sajis <a href="%%url%%">%%from%%</a>.' Always ask me = "Jeara álo" An error has occurred = "Dáhpáhuvai meattáhus" +An error occurred during execution; please try again later. = "Feaila doaimma čađahettiin. Iskka ođđasit maŋŋelis." AND = "JUO" and = "juo" anonymous_tags = "Dovdameahttumiid fáddágilkorat" @@ -73,6 +74,7 @@ APA Citation = "APA-čujuhus" APA Edition Citation = "APA-čujuhus (7. p.)" applied_filter = "Geavahuvvon sillen" applied_filters = "Geavahuvvon filtarat:" +Apply filters = "Geavat" Archival Material = "Arkiivamateriála" Article = "Artihkal" Ask a Librarian = "Jeara girjerájus" @@ -409,9 +411,12 @@ epf_recommendations = "Publikašuvdnaávžžuhusat" epf_recommendations_more = "Lasi publikašuvdnaávžžuhusat" ePub Full Text = "ePub-ollesdeaksta" Era = "Fáttá áigi" +error_accessing_full_text = "Ollesteavstta ozus dáhpáhuvai feaila." error_creating_marc_xml = "MARC-merkoša hábmemis dáhpáhuvai feaila" error_inconsistent_parameters = "Dáhpáhuvai meattáhus. Paramehterat leat mannan ruossalassii." error_page_parameter_list_heading = "Bivdaga paramehterat" +error_too_many_requests = "Menddo máŋga bivdaga" +error_too_many_requests_retry_after = "Menddo máŋga bivdaga. Iskka ođđasit %%seconds%% sekundda geažis." Exception = "Spiehkastat" Excerpt = "Oassi" exclude_facet = "[guođe eret]" @@ -423,12 +428,15 @@ explain_coord = "* %%coord%% (deaivamiid heiveheapmi lohkomearrái go veardidit explain_difference_score = "Earru allačuoggáide" explain_disabled = "Relevánssa govvádus ii leat geavahusas gáldui %%searchClassId%%" explain_for_search = "Ozu relevánssa govvádus" +explain_function_query_label = "Funkšuvdna" explain_modified_value = "Boađus: %%relevanceValue%% (relevánsaárvvoštallan)" explain_modifier = "hábmejeaddjis %%modifier%%" explain_record_score = "Merkoša čuoggát" +explain_relevance = "Mearkkuš %%recordId%% gávdnui relevánsačuoggáiguin %%relevanceValue%%" explain_relevance_score = "Relevánsačuoggát" explain_result_list_chart_title = "Čuoggát: %%score%%" explain_result_list_hint = "Relevánsačuoggát. Spoahkkal vai oainnat detáljalaš čilgehusa." +explain_show_raw = "Čájet roavis veršuvnna" explain_sum = "submi" explain_top_relevance = "Buoremus bohtosa deaivilvuohta" Export = "Doalvvo" @@ -460,6 +468,7 @@ external_auth_scope_address = "Oaidnit du čujuhusa" external_auth_scope_age = "Oaidnit du agi" external_auth_scope_birthdate = "Oaidnit du riegádanbeaivvi" external_auth_scope_block_status = "Dárkkistit, leago kontu luoikkahangildosis" +external_auth_scope_cat_id = "Oaidnit siskkáldas geavaheaddjidovddaldaga girjerádjovuogádagas" external_auth_scope_email = "Oaidnit du šleađgapoastačujuhusa" external_auth_scope_library_user_id = "Oaidnit du girjeráju geavaheaddjidovddaldagas ráhkaduvvon spesifiserejuvvon bagadasa" external_auth_scope_locale = "Oaidnit man giela geavahat aktiivvalaččat" @@ -468,6 +477,7 @@ external_auth_scope_openid = "Oaidnit du geavahandovddaldaga" external_auth_scope_phone = "Oaidnit du telefonnummira" external_auth_scope_profile = "Oaidnit du profiilla vuođđodieđuid (namma, giella, riegádanáigi)" external_auth_scope_unique_id = "Oaidnit du individuála dovddaldaga" +external_auth_scope_username = "Oaidnit geavaheaddjinama" external_auth_scopes_none = "ii maidige" external_auth_unauthorized = "Dus ii leat geavahanriekti lisensierejuvvon materiálii" external_auth_unauthorized_desc = "Du geavahan čálihuvvanvugiin ii beasa lisensierejuvvon materiálii. Čálihuva vuohččan olggos ja geavat de eará čálihuvvanvuogi." @@ -611,6 +621,7 @@ hold_edit_title = "Várrendieđuid hábmen" hold_empty_selection = "Oktage várren ii válljejuvvon" hold_error_age_restricted = "Várrema ii sáhte dahkat materiála ahkeráji dihte." hold_error_blocked = "Várren ii leat vejolaš." +hold_error_current_loan_patron_group = "It sáhte várret válljejuvvon materiála." hold_error_fail = "Bivdda ii lihkostuvvan. Váldde oktavuođa girjeráju áššehasbálvalussii." hold_error_item_not_holdable = "Njađđosa ii sáhte várret." hold_error_not_holdable = "Dán materiála ii sáhte várret." @@ -794,6 +805,7 @@ local_login_desc = "Biepma geavaheaddjidovddaldaga ja beassansáni, maid duddjoj Located = "Sajádat" Location = "Sajádat" Log Out = "Čálihuva olggos" +logged_in = "Leat čálihan sisa." Login = "Čálihuva" Login for full access = "Čálihuva sisa vai beasat buot materiála ollái." login_disabled = "Sisačáliheapmi ii leat anus." @@ -829,6 +841,7 @@ More options = "Čájet eará gálduid (SFX)" More Summon results = "Lasit Summon-bohtosiid" More Topics = "Lasit fáttaid" more_authors_abbrev = "et al." +more_by_author = "Lasi dahkkis %%name%%" more_ellipsis = "lasi…" more_info_toggle = "Čájet/čiega lassidieđuid." more_options_ellipsis = "Lasi molssaeavttut…" @@ -875,6 +888,7 @@ no_email_address = "Šleađgaboastačujuhus váilu." no_items_selected = "Merkošat eai leat válljejuvvon" no_proxied_user = "Ii gaskkusteami (dušše iežas geavahussii)" nohit_active_filters = "Dán ohcamis leat geavahuvvon filterat. Filteriid eret sihkkumiin sáhtát oažžut eanet bohtosiid." +nohit_busy = "Geavahanvuogádat ii vástit juste dál. Iskka ođđasit maŋŋelis." nohit_change_tab = "Ohcu dahkkojuvvui gaskabláđiin %%activeTab%%. Sáhtát gávdnat juoidá eará gaskabláđiin:" nohit_filters = "Dán ohcamis geavahuvvon filterat:" nohit_heading = "Eai bohtosat!" @@ -926,6 +940,7 @@ OAI Server = "OAI-server" Occupation = "Ámmát" od_account_noaccess = "Dán girjerájus ii leat beassan OverDrive-sisdollui" od_account_problem = "Du konttus lea čuolbma. %%message%%" +od_admin_menu = "OverDrive API" od_audiobook-mp3 = "MP3-jietnagirji" od_audiobook-overdrive = "OverDrive Guldal jietnagirjji" od_avail_avail = "Oažžunsajis:" @@ -934,18 +949,23 @@ od_avail_total = "Stuhkat oktiibuot :" od_but_cancel_hold = "Šluhtte várrema" od_but_checkout = "Luoikkat OverDrives" od_but_checkout_s = "Luoikkat" +od_but_edit_hold = "Hábme várrehusa" +od_but_edit_hold_conf = "Nanne beaivádeami" od_but_gettitle = "Ládde dán sisdoalu" od_but_gettitle_s = "Ládde" od_but_hold = "Várre OverDrives" od_but_hold_s = "Várre" od_but_return = "Máhcat luoikkaheami" +od_but_susp_hold = "Jiekŋut várrehusa" od_cancel_hold = "Šluhtte OverDrive-várrema" +od_cancel_hold_confirm = "Háliidatgo sihkkarit sihkkut dán várrehusa?" od_checkout = "OverDrive-luoikkaheapmi" od_code_connection_failed = "Oktavuohta OverDrivei ii lihkostuvvan. Juos dilli joatkahuvvá, váldde oktavuođa girjerádjui." od_code_contentnotavail = "Dát sisdoallu ii lea oažžunsajis guovllustat." od_code_login_for_avail = "Čálihuva sisa vai oainnát oažžasuvvama" od_code_resource_not_found = "Namahus ii gávdnon" od_content = "OverDrive-sisdoallu" +od_copies_available = "%%copies%% stuhka oažžumis" od_dl_formats = "Dorjojuvvon láddenhámit" od_docheckout_failure = "Namahusa luoikkaheapmi ii lihkostuvvan." od_docheckout_success = "Namahus lea luoikkahuvvon dutnje. Luoikkaheapmi nohká %%expireDate%%" @@ -957,6 +977,7 @@ od_ebook-mediado = "MediaDo Reader -šleađgagirji" od_ebook-overdrive = "OverDrive Read -šleađgagirji" od_ebook-pdf-adobe = "Adobe PDF -šleađgagirji" od_ebook-pdf-open = "Open PDF -šleađgagirji" +od_edit_hold_email = "Hábme várrehusa šleađgapoastta" od_expires_on = "Namahus boarásnuvvá %%due_date%%." od_get_title = "OverDrive-ládden" od_gettitle_failure = "Namahus ii sáhttán láddejuvvot." @@ -967,20 +988,40 @@ od_hold_cancel_failure = "Várrema šluhtten ii lihkostuvvan." od_hold_cancel_success = "Várren lea šluhttejuvvon." od_hold_email = "Šleađgaboastačujuhus várrenilmmuhussii: %%holdEmailAddress%%." od_hold_now_avail = "Dán várrema sáhttá dál luoikkahit. Luoikkaheapmi boarásnuvvá %%expireDate%%." +od_hold_now_avail_s = "Dát várrehus lea dál luoikkahanláhkai. Luoikkaheapmi boarásmuvvá %%expireDate%%." od_hold_place_failure = "Várrenbivdda ii lihkostuvvan." od_hold_place_success = "Dus lea várren dán girjái. Du báiki várrenráiddus lea %%holdListPosition%%" od_hold_placed_on = "Várren dahkkojuvvon %%holdPlacedDate%%." od_hold_queue = "Sajádat várrenráiddus lea %%holdPosition%% / %%numberOfHolds%%." +od_hold_queue_s = "Sajádat: %%holdPosition%% / %%numberOfHolds%%" +od_hold_redelivery = "Dát várrehus lea jiekŋuduvvon %%days%% beaivvi." +od_hold_redelivery_s = "Jiekŋuduvvon %%days%% beaivvi." +od_hold_susp_indef = "Dát várrehus lea jiekŋuduvvon doaisttážii." +od_hold_susp_indef_s = "Jiekŋuduvvon doaisttážii" +od_hold_update_failure = "Várrehusa beaivádanbivdda eahpelihkostuvai." +od_hold_update_success = "Várrehusa beaivádeapmi lihkostuvai." od_holds = "OverDrive-várremat" od_info_unavail = "Dát diehtu ii leat aiddo dál oažžumis." od_is_checkedout = "Dus lea dát girji luoikasis. Dan earrebeaivi lea %%due_date%%." od_is_on_hold = "Dus lea dát girji luoikasis." od_loans = "OverDrive-luoikkaheamit" +od_mag_issue_ischeckedout = "Luoikkahuvvon" +od_magazine-overdrive = "Digitála bláđđi" od_mycontent_help = 'Dieđuid oažžuma dihte dáid girjjiid láddemis ja geavaheamis, geahča <a href="%%url%%">OverDrive Help</a>.' od_none_found = "Girjjit eai gávdnon." +od_resource_page = "Overdrive-resursasiidu" +od_return_confirm = "Háliidatgo sihkkarit máhcahit dán stuhka?" od_return_failure = "Girjji ii sáhttán máhcahit." od_return_success = "Girji lea máhcahuvvon." +od_susp_after = "Doaimmaheapmi %%days%% beaivvi maŋŋá" +od_susp_asap = "Doaimmaheapmi nu johtilit go vejolaš" +od_susp_hold = "Jiekŋut várrehusa" +od_susp_hold_confirm = "Man guhkes áigái háliidat jiekŋudit várrehusa?" +od_susp_hold_edit = "Hábme doaimmahanbeaivvi" +od_susp_ind = "Jiekŋut doaisttážii" +od_unlimited = "Ráddjekeahtes" od_video-streaming = "neahttarávdnjenvideo" +od_waiting = "{holds, plural, =1 {1 olmmoš} other {# olbmo}} vuordimin" of_num_results = "%%position%% / %%total%%" old_password = "Boares beassansátni / PIN*" On Reserve = "Gursagirjeanus" @@ -1164,6 +1205,7 @@ renew_all = "Ođasnuhte visot luoikkahemiid" renew_determine_fail = "Ii diehtu ođasnuhttinrievttis. Váldde oktavuođa girjeráju áššehasbálvalussii." renew_empty_selection = "Oktage luoikkaheapmi ii válljejuvvon" renew_error = "Du luoikkaheamit eai sáhttán ođasnuhttojuvvot. Váldde oktavuođa girjeráju áššehasbálvalussii." +renew_error_summary = "{count, plural, =1 {Ovtta luoikkaheami} other {# luoikkaheami}} ođasmahttin eahpelihkostuvai." renew_fail = "Luoikkaheapmi ii sáhttán ođasnuhttojuvvot" renew_item = "Ođđa luoikkaheapmi" renew_item_due = "Luoikkahanáigi báhcán vuollái jándor" @@ -1176,6 +1218,7 @@ renew_item_requested = "Várrejuvvon nuppi áššehassii" renew_select_box = "Ođasnuhte luoikkaheami" renew_selected = "Ođasnuhte válljejuvvon luoikamiid" renew_success = "Ođasnuvvan lihkostuvai" +renew_success_summary = "Successfully renewed {count, plural, =1 {Okta luoikkaheapmi} other {# luoikkaheami}} ođasmahttojuvvon." Renewed = "Ođasnuhtton" Request full text = "Siđa ollesdeavstta" request_in_transit = "Mátkkis viežžanbáikái" @@ -1192,8 +1235,12 @@ Results = "Bohtosat" results = "bohtosis" Results for = "Bohtosat ohcui" Results per page = "Bohtosat siiddus" +results_cited_by_title = 'Gáldu "%%title%%" čujuhan' +results_cited_by_title_link_html = 'Gáldu <a href="%%url%%">%%title%%</a> čujuhan' results_cited_by_title_note = "Dát logahallan ii vealttakeahttá leat dievaslaš listu čujuhemiin." results_cited_by_title_search_link = "Dás čujuhuvvon" +results_citing_title = 'Gáldui "%%title%%" čujuheaddji' +results_citing_title_link_html = 'Gáldui <a href="%%url%%">%%title%%</a> čujuheaddji' results_citing_title_note = "Dát logahallan ii vealttakeahttá leat dievaslaš listu čujuhemiin." results_citing_title_search_link = "Dása čujuheaddji" Resumption Token = "Resumption Token" @@ -1224,6 +1271,7 @@ Search Home = "Ozu álggahansiidu" Search Mode = "Ohcanvuohki" Search Results = "Ohcanbohtosat" search results of = "oktiibuot" +Search sidebar = "Ozu siidofállu" Search Tips = "Ohcanráva" Search Tools = "Ohcanreaiddut" Search Type = "Ozu tiipa" @@ -1242,6 +1290,7 @@ seconds_abbrev = "s" see all = "čájet visot" See also = "Geahča maid" see_all_ellipsis = "čájet buot…" +Select multiple filters = "Vállje máŋggaid filtariid" Select this record = "Vállje dán merkoša" Select your carrier = "Vállje operáhtora" select_all = "Vállje buot" @@ -1310,6 +1359,7 @@ Source Title = "Gáldonamahus" spell_expand_alt = "Viiddit ozu" spell_suggest = "Dárkkuhitgo" Staff View = "Bargiidšearbma" +standalone_record_link = "Ovttaskas mearkkuš" Start a new Advanced Search = "Álggat ođđa aiddostahtton ozu" Start a new Basic Search = "Álggat ođđa vuođđoozu" Start Page = "Vuosttaš siidu" @@ -1322,6 +1372,7 @@ storage_retrieval_request_available = "Viežžamis" storage_retrieval_request_cancel = "Šluhtte diŋgojumi" storage_retrieval_request_cancel_all = "Šluhtte buot diŋgojumiid" storage_retrieval_request_cancel_fail = "Du diŋgojupmi ii šluhttejuvvon. Váldde oktavuođa áššehasbálvalussii." +storage_retrieval_request_cancel_fail_items = "%%count%% bivdaga ii sáhtán šluhttet" storage_retrieval_request_cancel_selected = "Šluhtte válljejuvvon diŋgojumiid" storage_retrieval_request_cancel_success = "Diŋgojupmi šluhttejuvvon" storage_retrieval_request_cancel_success_items = "%%count%% diŋgojumiin šluhttejuvvon" @@ -1370,6 +1421,7 @@ switchquery_truncatechar = "Oanit ohcama oažžut lasi bohtosiid" switchquery_unwantedbools = "Sánit AND, OR ja NOT sáhttet moivet ohcama; geahččal lasihit aisttonmearkkaid ohcansániid birra" switchquery_unwantedquotes = "Sáhtát oažžut eanet bohtosiid go sihkut aisttonmearkkaid" switchquery_wildcard = "Joker-mearkkaid geavahemiin sáhtát gávdnat earálágán sátnehámiid" +Synonym = "Synonyma" System Unavailable = "Vuogádat ii leat oažžumis" Table of Contents = "Sisdoallologahallan" Table of Contents unavailable = "Sisdoallologahallan ii leat oažžumis" @@ -1407,6 +1459,7 @@ toggle_dropdown = "Čájet dahje čiega fálu" Too Many Email Recipients = "Liiggás máŋga šleađgaboastta vuostáváldi" too_many_favorites = "Listu ii sáhte čájehuvvot hávális dan sturrodaga dihte. Juoge iežat oiddoha eanet listtuide dehe ráddje ozu nu ahte geavahat fáddágilkoriid." too_many_new_items = "Buot ođahiid ii sáhte čájehit ovtta listtus. Geahččal ráddjet ohcama." +too_many_query_terms = "Geahpet ohcaneavttuid. Ohcu sisdoallá eanet eavttuid (%%terms%%) go geavahanvuogádaga badjerádji (%%maxTerms%%)." too_many_reserves = "Buot gursagirjjiid ii sáhte čájehit ovtta listtus. Geahččal ráddjet ohcama." top_facet_label = "%%label%%" Topic = "Fáddá" @@ -1487,6 +1540,8 @@ What am I looking at = "Mii dát lea?" widen_prefix = "Geahččal viiddidit ohcama:" wiki_link = "Wikipedia buvttadan" with filters = "filteriiguin" +worldcat_group_related_editions = "Klassifisere laktáseaddji prentehusaid" +worldcat_group_variant_records = "Klassifisere vástideaddji merkošiid" Year of Publication = "Almmustuhttinjahki" You do not have any fines = "Eai mávssekeahtes mávssut" You do not have any holds or recalls placed = "Eai gustojeaddji várremat" diff --git a/languages/sv.ini b/languages/sv.ini index 29b433a9799..8a169c725e5 100644 --- a/languages/sv.ini +++ b/languages/sv.ini @@ -37,7 +37,7 @@ add_to_favorites_html = "Lägg till <em>%%title%%</em> till favoriter" Additional data = "Övrig information" Address = "Adress" adv_search_all = "Alla fält" -adv_search_author = "Upphovsman" +adv_search_author = "Skapare" adv_search_callnumber = "Signum" adv_search_filters = "Begränsningar i bruk" adv_search_isn = "ISBN/ISSN" @@ -75,6 +75,7 @@ APA Citation = "APA-referens" APA Edition Citation = "APA-referens (7:e uppl.)" applied_filter = "Filter i bruk:" applied_filters = "Aktiva filter:" +Apply filters = "Använd" Archival Material = "Arkivmaterial" Article = "Artikel" Ask a Librarian = "Fråga biblioteket" @@ -92,14 +93,14 @@ authentication_error_invalid = "Användarnamn/lösenord stämmer inte. Försök authentication_error_loggedout = "Du har loggat ut." authentication_error_session_ip_mismatch = "Inloggningsbegäran inleddes med en annan session och IP-adress. Ingen tillgång till systemet." authentication_error_technical = "Inloggningen misslyckades. Försök igen efter en stund." -Author = "Upphovsman" -Author Browse = "Bläddra bland upphovsmän" -Author Notes = "Anmärkningar om upphovsman" -Author Results for = "Upphovsmän hittade med sökningen" -Author Search Results = "Sökresultat med upphovsmän" +Author = "Skapare" +Author Browse = "Bläddra bland skapare" +Author Notes = "Anmärkningar om skapare" +Author Results for = "Skapare hittade med sökningen" +Author Search Results = "Sökresultat med skapare" Authority File = "Auktoritetskälla" Authors = "Författarna" -Authors Related to Your Search = "Upphovsmän relaterade till sökningen" +Authors Related to Your Search = "Skapare relaterade till sökningen" Auto configuration is currently disabled = "Auto configuration is currently disabled" auto_configure_description = "If this is a new installation, you may be able to fix the error using VuFind's Auto Configure tool." auto_configure_disabled = "Auto configuration is disabled." @@ -143,12 +144,12 @@ Breadcrumbs = "Synlig sökväg" Brief View = "Kort vy" Browse = "Bläddra" Browse Alphabetically = "Bläddra alfabetiskt" -Browse for Authors = "Bläddra bland upphovsmän" +Browse for Authors = "Bläddra bland skapare" Browse Home = "Browse Home" Browse the Catalog = "Bläddra i katalogen" Browse the Collection = "Bläddra i samlingen" Browse the Collection Alphabetically = "Bläddra i samlingen alfabetiskt" -browse_author = "Upphovsman" +browse_author = "Skapare" browse_dewey = "Klass (Dewey)" browse_format = "Format" browse_lcc = "Klass (LC)" @@ -171,7 +172,7 @@ bulk_save_success = "Posterna sparades." By = "Av" by = "av" By Alphabetical = "I alfabetisk ordning" -By Author = "Enligt upphovsman" +By Author = "Enligt skapare" By Call Number = "Enligt signum" By Course = "Enligt kurs" By Department = "Enligt avdelning" @@ -207,6 +208,7 @@ Change Password = "Ändra lösenord" change_email_disabled = "Det är inte möjligt att byta e-postadress" change_email_verification_reminder = "Du vill få ett meddelande till den nya adressen. Klicka på länken i meddelandet för att konfirmera ändringen." change_notification_email_message = "Begäran för att byta e-postadressen har gjorts med %%library%%. Om du inte gjort denna begäran, rekommenderar vi att du loggar in på %%url%% och kollar ditt konto. Vänligen kontakta biblioteket via %%email%% om du har några frågor." +change_notification_email_message_no_contact_email = "Begäran för att byta e-postadressen har gjorts med %%library%%. Om du inte gjort denna begäran, rekommenderar vi att du loggar in på %%url%% och kollar ditt konto. Vänligen kontakta biblioteket om du har några frågor." change_notification_email_subject = "Verifiering av din e-postadress" channel_add_more = "Mer liknande kanaler" channel_browse = "Bläddra fler poster" @@ -281,7 +283,7 @@ consortial_vufind_recommend_heading = "Låna från ett partnerbibliotek" consortial_vufind_recommend_intro_html = '<a href="%%url%%" target="_blank">%%result_count%% resultat</a> hittades på partnerbibliotek.' Contents = "Innehåll" Contributing Source = "Bidragande källa" -Contributors = "Övriga upphovsmän" +Contributors = "Övriga skapare" Cookie Settings = "Kakinställningar" Coordinates = "Koordinater" Copies = "Exemplar" @@ -290,8 +292,8 @@ copy_to_clipboard_button_label = "Kopiera till urklipp" copy_to_clipboard_failure_message = "Kopiering till urklipp misslyckades" copy_to_clipboard_success_message = "Kopierad till urklipp" Copyright = "Copyright" -Corporate Author = "Institutionell upphovsman" -Corporate Authors = "Institutionella upphovsmän" +Corporate Author = "Institutionell skapare" +Corporate Authors = "Institutionella skapare" could_not_process_feedback = "Begäran misslyckades. Försök på nytt efter en stund." Country = "Land" Course = "Kurs" @@ -428,6 +430,7 @@ explain_coord = "* %%coord%% (justera för antal träffar jämfört med sökning explain_difference_score = "skillnad mot högsta poäng" explain_disabled = "Explain är inaktiverat för %%searchClassId%%" explain_for_search = "Förklaring till sökning" +explain_function_query_label = "Funktion" explain_modified_value = "Produkt av %%relevanceValue%% (relevansvärde)" explain_modifier = "med en modifiering av %%modifiering%%" explain_record_score = "Poäng" @@ -435,6 +438,7 @@ explain_relevance = "Post %%recordId%% hittades med relevansvärdet %%relevanceV explain_relevance_score = "Relevanspoäng" explain_result_list_chart_title = "Poäng: %%score%%" explain_result_list_hint = "Relevanspoäng. Klicka för att se en detaljerad förklaring." +explain_show_raw = "Visa råversion" explain_sum = "summa" explain_top_relevance = "Relevans för toppresultat" Export = "Exportera" @@ -619,6 +623,7 @@ hold_edit_title = "Ändra reserveringsinformation" hold_empty_selection = "Inga reserveringar valda" hold_error_age_restricted = "Reservering är inte möjlig eftersom materialet har en åldersgräns." hold_error_blocked = "Reservering är inte möjlig, eftersom du har låneförbud eller redan har en likvärdig reservering." +hold_error_current_loan_patron_group = "Detta exemplar kan inte reserveras." hold_error_fail = "Begäran misslyckades. Kontakta kundtjänst." hold_error_item_not_holdable = "Exempeln kan inte reserveras." hold_error_not_holdable = "Denna material kan inte reserveras." @@ -666,6 +671,7 @@ ill_request_available = "Kan avhämtas" ill_request_cancel = "Annullera beställningen" ill_request_cancel_all = "Annullera alla beställningar" ill_request_cancel_fail = "Din beställning annullerades inte. Kontakta kundtjänst." +ill_request_cancel_fail_items = "%%count%% beställning(ar) kunde inte annulleras" ill_request_cancel_selected = "Annullera valda beställningar" ill_request_cancel_success = "Beställningen har annullerats" ill_request_cancel_success_items = "%%count%% beställning(ar) har annullerats" @@ -808,8 +814,8 @@ Login for full access = "Logga in för full Login for full tillgång." login_disabled = "Inloggning är inte i bruk" login_target = "Bibliotek" Logout = "Logga ut" -Main Author = "Huvudupphovsman" -Main Authors = "Huvudupphovsmän" +Main Author = "Huvudskapare" +Main Authors = "Huvudskapare" Major Categories = "Huvudkategorier" Manage Scheduled Alerts = "Hantera sökbevakningar" Manage Tags = "Hantera Tags" @@ -838,6 +844,7 @@ More options = "Fler alternativ" More Summon results = "Fler Summon-resultat…" More Topics = "Mer temor" more_authors_abbrev = "et al." +more_by_author = "Fler titlar av %%name%%" more_ellipsis = "mer…" more_info_toggle = "Visa/dölj mer information." more_options_ellipsis = "Fler alternativ…" @@ -884,6 +891,7 @@ no_email_address = "E-postadress saknas." no_items_selected = "Inga valda poster." no_proxied_user = "Ingen proxyanvändare (begära för dig själv)" nohit_active_filters = "Filtren i bruk i denna sökning. Om du tar bort filtren, kan du få fler resultat." +nohit_busy = "Systemet är överbelastat just nu. Försök igen senare." nohit_change_tab = 'Du har sökt i fliken "%%activeTab%%". Du kan hitta något i en av de andra flikar:' nohit_filters = "Filtren för denna sökning:" nohit_heading = "Inga resultat!" @@ -1032,7 +1040,7 @@ OR = "ELLER" or create a new list = "eller skapa en ny lista" original = "Original" Other associated place = "Annan associerad plats" -Other Authors = "Övriga upphovsmän" +Other Authors = "Övriga skapare" Other Editions = "Andra upplagor" Other Libraries = "Andra bibliotek" Other Sources = "Andra källor" @@ -1188,7 +1196,7 @@ relais_requesting = "Skickar beställningen..." relais_search = "Fjärrlånsökning" relais_success_label = "Bekräftelse:" relais_success_message = "Beställning med id #%%id%% har skapats. Du kommer att få ett bekräftelse per e-post." -Related Author = "Relaterad upphovsman" +Related Author = "Relaterad skapare" Related Items = "Relaterade poster" Related Subjects = "Relaterade ämnen" Relevance = "Relevans" @@ -1200,6 +1208,7 @@ renew_all = "Förnya alla lån" renew_determine_fail = "Ingen information om rätt att förnya. Kontakta kundtjänst." renew_empty_selection = "Inga valda lån" renew_error = "Dina lån kunde inte förnyas. Kontakta kundtjänst." +renew_error_summary = "{count, plural, =1 {Ett lån} other {# lån}} kunde inte förnyas." renew_fail = "Lånet kunde inte förnyas" renew_item = "Förnya lånet" renew_item_due = "Lånet förfaller inom ett dygn." @@ -1212,6 +1221,7 @@ renew_item_requested = "En annan kund har reserverat denna." renew_select_box = "Fönya lånet" renew_selected = "Förnya valda lån" renew_success = "Förnyandet lyckades" +renew_success_summary = "{count, plural, =1 {Ett lån} other {# lån}} har förnyats." Renewed = "Förnyat" Request full text = "Begär fulltext" request_in_transit = "På väg till avhämtninsplatsen" @@ -1283,6 +1293,7 @@ seconds_abbrev = "s" see all = "Visa alla" See also = "Se också" see_all_ellipsis = "Visa alla…" +Select multiple filters = "Välj flera filter" Select this record = "Välj denna post" Select your carrier = "Välj operatör" select_all = "Välj alla poster" @@ -1331,7 +1342,7 @@ Software = "Datorprogram" Sorry, but the help you requested is unavailable in your language. = "Denna hjälptext finns inte på svenska" Sort = "Sortera" sort_alphabetic = "Alfabetiskt" -sort_author = "Upphovsman" +sort_author = "Skapare" sort_author_author = "Alfabetiskt" sort_author_relevance = "Relevans" sort_callnumber = "Signum" @@ -1343,6 +1354,8 @@ sort_due_date_desc = "Förfallodag (nyast först)" sort_relevance = "Relevans" sort_return_date_asc = "Returneringsdag (äldst först)" sort_return_date_desc = "Returneringsdag (nyast först)" +sort_saved = "Sparad (nyaste först)" +sort_saved_asc = "Sparad (äldsta först)" sort_title = "Titel" sort_year = "Tid (nyaste först)" sort_year_asc = "Tid (äldsta först)" @@ -1351,6 +1364,7 @@ Source Title = "Källa titel" spell_expand_alt = "Utvidga sökningen" spell_suggest = "Menade du" Staff View = "Katalogiseringsuppgifter" +standalone_record_link = "Enskild post" Start a new Advanced Search = "Gör en ny avancerad sökning" Start a new Basic Search = "Gör en ny enkel sökning" Start Page = "Förstasidan" @@ -1363,6 +1377,7 @@ storage_retrieval_request_available = "Kan avhämtas" storage_retrieval_request_cancel = "Annullera beställningen" storage_retrieval_request_cancel_all = "Annullera alla beställningar" storage_retrieval_request_cancel_fail = "Din beställning annullerades inte. Kontakta kundtjänst." +storage_retrieval_request_cancel_fail_items = "%%count%% beställning(ar) kunde inte annulleras" storage_retrieval_request_cancel_selected = "Annullera valda beställningar" storage_retrieval_request_cancel_success = "Beställningen har annullerats" storage_retrieval_request_cancel_success_items = "%%count%% beställning(ar) har annullerats" @@ -1449,6 +1464,7 @@ toggle_dropdown = "Växla rullgardinsmenyn" Too Many Email Recipients = "För många e-postmottagare" too_many_favorites = "Hela listan kan inte visas på en gång p.g.a. dess storlek. Dela upp dina favoriter på flera listor eller begränsa sökningen genom att använda taggar." too_many_new_items = "Alla nyheter kan inte visas på en lista. Pröva att begränsa sökningen." +too_many_query_terms = "Förenkla sökningen. Den innehåller fler termer (%%terms%%) än systemet tillåter (%%maxTerms%%)." too_many_reserves = "Alla kursböcker kan inte visas på en lista. Pröva att begränsa sökningen." top_facet_label = "%%label%%" Topic = "Tema" @@ -1529,13 +1545,15 @@ What am I looking at = "Vad tittar jag på?" widen_prefix = "Pröva att utvidga sökningen:" wiki_link = "Levererad av Wikipedia" with filters = "med filter" +worldcat_group_related_editions = "Gruppera relaterade upplagor" +worldcat_group_variant_records = "Gruppera motsvarande poster" Year of Publication = "Utgivningsår" You do not have any fines = "Du har inga obetalda avgifter" You do not have any holds or recalls placed = "Du har inga reserveringar i kraft" You do not have any interlibrary loan requests placed = "Du har inga fjärrlånbeställningar i kraft" You do not have any items checked out = "Du har inga lån" You do not have any library cards = "Du har inga bibliotekskort" -You do not have any saved resources = "Du har inga sparade resurser" +You do not have any saved resources = "Du har inte sparat några favoriter" You do not have any storage retrieval requests placed = "Du har inga magasinsbeställningar i kraft" You must be logged in first = "Du måste logga in först" Your Account = "Mitt konto" diff --git a/languages/tr.ini b/languages/tr.ini index 79c92065b07..8314264b610 100644 --- a/languages/tr.ini +++ b/languages/tr.ini @@ -130,6 +130,7 @@ APA Citation = "APA Alıntı" APA Edition Citation = "APA (7. basım) Alıntı" applied_filter = "Uygulanmış Filtre:" applied_filters = "Uygulanan Filtreler:" +Apply filters = "Filtreleri uygula" Archival Material = "Arşiv Materyali" Article = "Makale" Ask a Librarian = "Kütüphaneciye Sor" @@ -483,6 +484,7 @@ explain_coord = "* %%coord%% (aramaya kıyasla eşleşme sayısına göre ayarla explain_difference_score = "en yüksek puana göre fark" explain_disabled = "%%searchClassId%% için açıklama devre dışı bırakıldı" explain_for_search = "Arama açıklaması" +explain_function_query_label = "İşlev" explain_modified_value = "%%relevanceValue%% ürünü (alaka değeri)" explain_modifier = "%%modifier%% değiştiricisiyle" explain_record_score = "puan rekoru" @@ -490,6 +492,7 @@ explain_relevance = "Kayıt Kimliği: %%relevanceValue%% alaka değeriyle %%reco explain_relevance_score = "Uyum Puanı" explain_result_list_chart_title = "Puan: %%score%%" explain_result_list_hint = "Uyum puanı. Ayrıntılı açıklamayı görmek için tıklayın." +explain_show_raw = "Ham açıklamayı göster" explain_sum = "toplam" explain_top_relevance = "En Yüksek Sonuç Alaka Düzeyi" Export = "İhraç Et" @@ -674,6 +677,7 @@ hold_edit_title = "Ayırtma Bilgilerini Değiştir" hold_empty_selection = "Hiç bir kayıt seçilmedi" hold_error_age_restricted = "Materyal üzerindeki yaş kısıtlaması nedeniyle ayırtma yapılamaz." hold_error_blocked = "Bu materyali ödünç alma hakkınız yok. Lütfen detayli bilgi için ödünç verme masasına başvurunuz." +hold_error_current_loan_patron_group = "Bu materyal şu anda talep edilemiyor." hold_error_fail = "İsteğiniz başarısız. Lütfen detayli bilgi için ödünç verme masasına başvurunuz." hold_error_item_not_holdable = "Bu ürün talep edilemez." hold_error_not_holdable = "Bu materyal talep edilemez." @@ -721,6 +725,7 @@ ill_request_available = "Ödünç alınabilir" ill_request_cancel = "Kütüphaneler arası ödünç alama isteğini iptal et" ill_request_cancel_all = "Tüm kütüphanelerarası ödünç alama isteklerini iptal et" ill_request_cancel_fail = "İsteğiniz iptal edilmedi. Lütfen detayli bilgi için ödünç verme masasına başvurunuz." +ill_request_cancel_fail_items = "%%count%% istek iptal edilemedi" ill_request_cancel_selected = "Seçilmiş kütüphaneler arası ödünç alama isteklerini iptal et" ill_request_cancel_success = "İsteğiniz başarıyla iptal edildi" ill_request_cancel_success_items = "%%count%% istek(ler) başarıyla iptal edildi" @@ -893,6 +898,7 @@ More options = "Daha fazla seçenek" More Summon results = "Daha Fazla Sonuç…" More Topics = "Daha Fazla Konu" more_authors_abbrev = "ve diğerleri" +more_by_author = "Ayrıca %%name%% tarafından" more_ellipsis = "daha fazla…" more_info_toggle = "Detaylı bilgiyi Göster/Sakla." more_options_ellipsis = "Daha fazla seçenek…" @@ -939,6 +945,7 @@ no_email_address = "E-posta adresi eksik." no_items_selected = "Kayıt seçilmedi" no_proxied_user = "Proxied kullanıcı yok (kendiniz için istekte bulunun)" nohit_active_filters = "Bu arama için bir veya daha fazla faset filtresi uygulanmış. Filtreleri kaldırırsanız daha fazla sonuç alabilirsiniz." +nohit_busy = "Sistem şu anda yanıt veremeyecek kadar meşgul. Lütfen daha sonra tekrar deneyin." nohit_change_tab = '"%%activeTab%%" sekmesi içinde arama yapmaktasınız. Diğer sekmelerin birisi içinde bir şeyler bulabilirsiniz:' nohit_filters = "Bu aramaya uygulanan filtreler" nohit_heading = "Sonuç bulunamadı!" @@ -1255,6 +1262,7 @@ renew_all = "Tüm kayıtları uzat" renew_determine_fail = "Uzattığınız kayıda erişemedik. Lütfen detayli bilgi için ödünç verme masasına başvurunuz." renew_empty_selection = "Uzatma için herhangi bir kayıt seçilmedi" renew_error = "Kayıtlarınız uzatılamdı. Lütfen detayli bilgi için ödünç verme masasına başvurunuz." +renew_error_summary = "Hatalar nedeniyle {count, plural, =1 {1 materyal} other {# materyaller}} süresi uzatılamıyor." renew_fail = "Bu kayıt uzatılamadı" renew_item = "Kaydı uzat" renew_item_due = "Kayıt 24 saat içinde gecikecek" @@ -1267,6 +1275,7 @@ renew_item_requested = "Bu kayıt başkası tarafından ayırtıldı" renew_select_box = "Kayıdı uzat" renew_selected = "Seçilmiş kayıtları uzat" renew_success = "Uzatma işlemi başarılı" +renew_success_summary = "{count, plural, =1 {1 materyal} other {# materyaller}} süresi başarıyla uzatıldı." Renewed = "Yenilenmiş" Request full text = "Tam metin isteyin" request_in_transit = "Doğruca istrek yerine gidin" @@ -1338,6 +1347,7 @@ seconds_abbrev = "s" see all = "Hepsini Gör" See also = "Ayrıca Bakınız" see_all_ellipsis = "Hepsini Gör…" +Select multiple filters = "Birden fazla filtre seçin" Select this record = "Bu kaydı seç" Select your carrier = "İletici Seçin" select_all = "Tüm girişleri seç" @@ -1398,6 +1408,8 @@ sort_due_date_desc = "Gecikme Tarihi (İlkönce yeni)" sort_relevance = "İlgili" sort_return_date_asc = "İade Tarihi (İlkönce eski)" sort_return_date_desc = "İade Tarihi (İlkönce yeni)" +sort_saved = "Kaydetme Tarihi (en yenisi ilk)" +sort_saved_asc = "Kaydetme Tarihi (en eskisi ilk)" sort_title = "Materyal Adı" sort_year = "Tarih-Azalan" sort_year_asc = "Tarih-Artan" @@ -1406,6 +1418,7 @@ Source Title = "Kaynak Başlığı" spell_expand_alt = "Aramayı Genişlet" spell_suggest = "Şunu mu demek istediniz" Staff View = "MARC Görünümü" +standalone_record_link = "Bağımsız Kayıt" Start a new Advanced Search = "Yeni Gelişmiş Arama" Start a new Basic Search = "Yeni Basit Arama" Start Page = "Başlangıç Sayfası" @@ -1418,6 +1431,7 @@ storage_retrieval_request_available = "Ödünç alınabilir" storage_retrieval_request_cancel = "Depolama Alma İsteklerini İptal Et" storage_retrieval_request_cancel_all = "Tüm Depolama Alma İsteklerini İptal" storage_retrieval_request_cancel_fail = "İsteğiniz iptal edilmedi. Lütfen detayli bilgi için ödünç verme masasına başvurunuz." +storage_retrieval_request_cancel_fail_items = "%%count%% istek iptal edilemedi" storage_retrieval_request_cancel_selected = "Seçilmiş Depolama Alma İstekleri İptal Et" storage_retrieval_request_cancel_success = "İsteğiniz başarıyla iptal edildi" storage_retrieval_request_cancel_success_items = "%%count%% istek(ler) başarıyla iptal edildi" @@ -1504,6 +1518,7 @@ toggle_dropdown = "Açılır Menüyü Aç/Kapat" Too Many Email Recipients = "Çok fazla eposta alıcısı" too_many_favorites = "Liste bir seferde gösterim için çok büyük. Favori listenizi birden fazla liste için yeniden düzenleyin veya etiketleri kullanarak sınırlayın." too_many_new_items = "Tek bir listede görüntülemek için çok fazla sayıda kayıt var. Aramanızı sınırlandırın." +too_many_query_terms = "Lütfen sorgunuzu basitleştirin; sistem sınırından (%%maxTerms%%) daha fazla terim (%%terms%%) içeriyor." too_many_reserves = "Tek bir liste görüntülemek için çok sayıda kurs rezervleri vardır. Aramanızı sınırlandırın." top_facet_label = "%%label%%" Topic = "Konu (Topic)" @@ -1584,6 +1599,8 @@ What am I looking at = "Neye Bakıyorum?" widen_prefix = "Aramanızı genişletmeyi deneyin." wiki_link = "Yukarıdaki bilgiler Wikipedia'dan alınmıştır" with filters = "Filitreleme" +worldcat_group_related_editions = "Grup İlgili Sürümler" +worldcat_group_variant_records = "Grup Varyant Kayıtları" Year of Publication = "Yayın Tarihi" You do not have any fines = "Geçikme cezanız yoktur" You do not have any holds or recalls placed = "Rezerve etiğiniz kayıt bulunmamaktadır" diff --git a/languages/uk.ini b/languages/uk.ini index e596e227860..8b437be4042 100644 --- a/languages/uk.ini +++ b/languages/uk.ini @@ -76,6 +76,7 @@ APA Citation = "Стиль цитування APA" APA Edition Citation = "Стиль цитування APA (7-ме видання)" applied_filter = "Застосований фільтр:" applied_filters = "Застосовані фільтри:" +Apply filters = "Застосувати фільтри" Archival Material = "Архівний матеріал" Article = "Стаття" Ask a Librarian = "Запитати бібліотекаря" @@ -429,6 +430,7 @@ explain_coord = "* %%coord%% (adjust for number of matches compared to search)" explain_difference_score = "різниця з найкращим результатом" explain_disabled = "Пояснення вимкнено для %%searchClassId%%" explain_for_search = "Пояснення до пошуку" +explain_function_query_label = "Функція" explain_modified_value = "Результат %%relevanceValue%% (значення релевантності)" explain_modifier = "з модифікатором %%modifier%%" explain_record_score = "рекордний результат" @@ -436,6 +438,7 @@ explain_relevance = "Ідентифікатор запису: %%recordId%% зн explain_relevance_score = "Рекордний результат" explain_result_list_chart_title = "Відповідність: %%score%%" explain_result_list_hint = "Оцінка релевантності. Натисніть, щоб переглянути детальне пояснення." +explain_show_raw = "Показувати необроблене пояснення" explain_sum = "сума" explain_top_relevance = "Релевантність найкращих результатів" Export = "Експорт" @@ -620,6 +623,7 @@ hold_edit_title = "Змінити інформацію замовлення" hold_empty_selection = "Замовлення не вибрано." hold_error_age_restricted = "Замовлення не може бути розміщене через вікові обмеження для матеріалу." hold_error_blocked = "У вас недостатньо прав на розміщення замовлення на цей примірник" +hold_error_current_loan_patron_group = "Примірник не може бути замовлений" hold_error_fail = "Ваше замовлення не було зареєстроване. Зверніться за допомогою до бібліотекаря книговидачі." hold_error_item_not_holdable = "Примірник не може бути замовлений." hold_error_not_holdable = "Матеріал не може бути замовлений." @@ -667,6 +671,7 @@ ill_request_available = "Доступно для видачі" ill_request_cancel = "Скасувати замовлення МБА" ill_request_cancel_all = "Скасувати всі замовлення МБА" ill_request_cancel_fail = "Ваше замовлення не було скасоване. Зверніться за допомогою до бібліотекаря." +ill_request_cancel_fail_items = "%%count%% замовлень неможливо скасувати" ill_request_cancel_selected = "Скасувати відмічені замовлення МБА" ill_request_cancel_success = "Ваше замовлення скасоване." ill_request_cancel_success_items = "%%count%% замовлень успішно скасовані." @@ -839,6 +844,7 @@ More options = "Більше опцій" More Summon results = "Більше результатів у Summon..." More Topics = "Більше предметів/тем" more_authors_abbrev = "та інші" +more_by_author = "Також за авторством %%name%%" more_ellipsis = "більше..." more_info_toggle = "Показати/сховати додаткову інформацію." more_options_ellipsis = "Більше опцій..." @@ -885,6 +891,7 @@ no_email_address = "Адреса е-пошти відсутня." no_items_selected = "Ресурси не вибрані." no_proxied_user = "Немає проксі-користувача (запит для себе)" nohit_active_filters = "До цього пошуку були застосовані один або більше фасетних фільтрів. Якщи видалити ці фільтри, можна отримати більше результатів." +nohit_busy = "Наразі система занадто зайнята і не відповідає. Спробуйте пізніше." nohit_change_tab = 'Ви шукали у закладці "%%activeTab%%". Також Ви можете пошукати в одній із наступних закладок:' nohit_filters = "До цього пошуку були застосовані фільтри:" nohit_heading = "Результати відсутні!" @@ -1201,6 +1208,7 @@ renew_all = "Подовжити все" renew_determine_fail = "Неможливо визначити чи був подовжений Ваш примірник. Зверніться за допомогою до бібліотекаря." renew_empty_selection = "Примірники не вибрані" renew_error = "Неможливо подовжити Ваші примірники. Зверніться за допомогою до бібліотекаря." +renew_error_summary = "Неможливо подовжити {count, plural, =1 {1 примірник} other {# примірники/примірників}} через помилки." renew_fail = "Цей примірник не може бути подовжений" renew_item = "Подовжити примірник" renew_item_due = "Термін видачі примірника закінчується в межах наступних 24 годин" @@ -1213,6 +1221,7 @@ renew_item_requested = "Це примірник замовлений іншим renew_select_box = "Подовжити примірник" renew_selected = "Подовжити відмічені примірники" renew_success = "Подовження виконане успішно" +renew_success_summary = "Успішно подовжено {count, plural, =1 {1 примірник} other {# примірники/примірників}}." Renewed = "Подовжено" Request full text = "Замовити повний текст" request_in_transit = "В дорозі до місця видачі" @@ -1284,6 +1293,7 @@ seconds_abbrev = "сек." see all = "див. все" See also = "Див. також" see_all_ellipsis = "див. все..." +Select multiple filters = "Вибрати декілька фільтрів" Select this record = "Вибрати цей запис" Select your carrier = "Виберіть оператора" select_all = "Вибрати все" @@ -1344,6 +1354,8 @@ sort_due_date_desc = "Термін видачі (спочатку новіший sort_relevance = "Релевантність" sort_return_date_asc = "Дата повернення (спочатку пізніша)" sort_return_date_desc = "Дата повернення (спочатку новіша)" +sort_saved = "Дата збереження (спочатку новіша)" +sort_saved_asc = "Дата збереження (спочатку пізніша)" sort_title = "Назва" sort_year = "Дата у спадаючому порядку" sort_year_asc = "Дата у зростаючому порядку" @@ -1352,6 +1364,7 @@ Source Title = "Назва джерела" spell_expand_alt = "Розширити пошук" spell_suggest = "Альтернативи пошуку" Staff View = "Службовий вигляд" +standalone_record_link = "Окремий запис" Start a new Advanced Search = "Почати новий Розширений пошук" Start a new Basic Search = "Почати новий Базовий пошук" Start Page = "Стартова сторінка" @@ -1364,6 +1377,7 @@ storage_retrieval_request_available = "Доступні для видачі" storage_retrieval_request_cancel = "Скасувати замовлення зі сховища" storage_retrieval_request_cancel_all = "Скасувати всі замовлення зі сховища" storage_retrieval_request_cancel_fail = "Ваше замовлення не було скасоване. Зверніться за допомогою до бібліотекаря." +storage_retrieval_request_cancel_fail_items = "%%count%% замовлень неможливо скасувати" storage_retrieval_request_cancel_selected = "Скасувати відмічені замовлення зі сховища" storage_retrieval_request_cancel_success = "Ваше замовлення успішно скасоване." storage_retrieval_request_cancel_success_items = "%%count%% замовлень успішно скасовані." @@ -1450,6 +1464,7 @@ toggle_dropdown = "Показати/сховати спадне меню" Too Many Email Recipients = "Забагато отримувачів листа" too_many_favorites = "Цей список завеликий для відображення. Спробуйте розбити Ваше Вибране на кілька списків або обмежте використання тегів." too_many_new_items = "Забагато ресурсів для відображення в одному списку. Обмежте свій пошук." +too_many_query_terms = "Спростіть свій запит, будь ласка; кількість термінів у ньому (%%terms%%) перевищує системний ліміт (%%maxTerms%%)." too_many_reserves = "Забагато матеріалів для курсів для відображення в одному списку. Обмежте свій пошук." top_facet_label = "%%label%% у межах Вашого пошуку." Topic = "Тема" @@ -1530,6 +1545,8 @@ What am I looking at = "Що я шукаю?" widen_prefix = "Спробуйте розширити Ваш пошук до" wiki_link = "Надано: Wikipedia" with filters = "з фільтрами" +worldcat_group_related_editions = "Згрупувати пов'язані видання" +worldcat_group_variant_records = "Згрупувати варіанти записів" Year of Publication = "Рік видання" You do not have any fines = "Ви не маєте пені" You do not have any holds or recalls placed = "Ви не маєте замовлень та відкликів" diff --git a/module/VuFind/config/module.config.php b/module/VuFind/config/module.config.php index 9b424572786..5ff1d1930d6 100644 --- a/module/VuFind/config/module.config.php +++ b/module/VuFind/config/module.config.php @@ -227,7 +227,8 @@ 'VuFind\Controller\UpgradeController' => 'VuFind\Controller\UpgradeControllerFactory', 'VuFind\Controller\WebController' => 'VuFind\Controller\AbstractBaseFactory', 'VuFind\Controller\WorldcatController' => 'VuFind\Controller\AbstractBaseFactory', - 'VuFind\Controller\WorldcatrecordController' => 'VuFind\Controller\AbstractBaseFactory', + 'VuFind\Controller\Worldcat2Controller' => 'VuFind\Controller\AbstractBaseFactory', + 'VuFind\Controller\Worldcat2recordController' => 'VuFind\Controller\AbstractBaseFactory', ], 'initializers' => [ 'VuFind\ServiceManager\ServiceInitializer', @@ -357,8 +358,13 @@ 'web' => 'VuFind\Controller\WebController', 'Worldcat' => 'VuFind\Controller\WorldcatController', 'worldcat' => 'VuFind\Controller\WorldcatController', - 'WorldcatRecord' => 'VuFind\Controller\WorldcatrecordController', - 'worldcatrecord' => 'VuFind\Controller\WorldcatrecordController', + // Remap legacy WorldcatRecord action to point to Worldcat2recordController + 'WorldcatRecord' => 'VuFind\Controller\Worldcat2recordController', + 'worldcatrecord' => 'VuFind\Controller\Worldcat2recordController', + 'Worldcat2' => 'VuFind\Controller\Worldcat2Controller', + 'worldcat2' => 'VuFind\Controller\Worldcat2Controller', + 'Worldcat2Record' => 'VuFind\Controller\Worldcat2recordController', + 'worldcat2record' => 'VuFind\Controller\Worldcat2recordController', ], ], 'controller_plugins' => [ @@ -442,6 +448,7 @@ 'VuFind\Cover\Loader' => 'VuFind\Cover\LoaderFactory', 'VuFind\Cover\Router' => 'VuFind\Cover\RouterFactory', 'VuFind\Crypt\HMAC' => 'VuFind\Crypt\HMACFactory', + 'VuFind\Crypt\PasswordHasher' => 'Laminas\ServiceManager\Factory\InvokableFactory', 'VuFind\Crypt\SecretCalculator' => 'VuFind\Crypt\SecretCalculatorFactory', 'VuFind\Date\Converter' => 'VuFind\Service\DateConverterFactory', 'VuFind\Db\AdapterFactory' => 'VuFind\Service\ServiceWithConfigIniFactory', @@ -450,6 +457,7 @@ 'VuFind\Db\Table\PluginManager' => 'VuFind\ServiceManager\AbstractPluginManagerFactory', 'VuFind\DigitalContent\OverdriveConnector' => 'VuFind\DigitalContent\OverdriveConnectorFactory', 'VuFind\DoiLinker\PluginManager' => 'VuFind\ServiceManager\AbstractPluginManagerFactory', + 'VuFind\Escaper\Escaper' => 'VuFind\Escaper\EscaperFactory', 'VuFind\Export' => 'VuFind\ExportFactory', 'VuFind\Favorites\FavoritesService' => 'VuFind\Favorites\FavoritesServiceFactory', 'VuFind\Form\Form' => 'VuFind\Form\FormFactory', @@ -532,7 +540,6 @@ 'VuFindHttp\HttpService' => 'VuFind\Service\HttpServiceFactory', 'VuFindSearch\Service' => 'VuFind\Service\SearchServiceFactory', 'Laminas\Db\Adapter\Adapter' => 'VuFind\Db\AdapterFactory', - 'Laminas\Http\PhpEnvironment\RemoteAddress' => 'VuFind\Http\PhpEnvironment\RemoteAddressFactory', 'Laminas\Session\SessionManager' => 'VuFind\Session\ManagerFactory', ], 'delegators' => [ @@ -547,8 +554,6 @@ 'VuFind\ServiceManager\ServiceInitializer', ], 'aliases' => [ - 'League\CommonMark\MarkdownConverterInterface' => 'League\CommonMark\ConverterInterface', - 'Request' => 'VuFind\Http\PhpEnvironment\Request', 'VuFind\AccountCapabilities' => 'VuFind\Config\AccountCapabilities', 'VuFind\AuthManager' => 'VuFind\Auth\Manager', 'VuFind\AuthPluginManager' => 'VuFind\Auth\PluginManager', @@ -607,9 +612,14 @@ 'VuFind\Tags' => 'VuFind\Tags\TagsService', 'VuFind\Translator' => 'Laminas\Mvc\I18n\Translator', 'VuFind\YamlReader' => 'VuFind\Config\YamlReader', - 'Laminas\Validator\Csrf' => 'VuFind\Validator\SessionCsrf', 'VuFind\Validator\Csrf' => 'VuFind\Validator\SessionCsrf', 'VuFind\Validator\CsrfInterface' => 'VuFind\Validator\SessionCsrf', + + // Overrides: + 'Laminas\Escaper\Escaper' => 'VuFind\Escaper\Escaper', + 'Laminas\Validator\Csrf' => 'VuFind\Validator\SessionCsrf', + 'League\CommonMark\MarkdownConverterInterface' => 'League\CommonMark\ConverterInterface', + 'Request' => 'VuFind\Http\PhpEnvironment\Request', ], 'shared' => [ 'VuFind\Form\Form' => false, @@ -745,6 +755,7 @@ 'solrauthrecord' => 'AuthorityRecord', 'summonrecord' => 'SummonRecord', 'worldcatrecord' => 'WorldcatRecord', + 'worldcat2record' => 'Worldcat2Record', 'search2record' => 'Search2Record', 'search2collection' => 'Search2Collection', 'search2collectionrecord' => 'Search2Record', @@ -832,6 +843,7 @@ 'Upgrade/CriticalFixInsecureDatabase', 'Web/Home', 'Web/FacetList', 'Web/Results', 'Worldcat/Advanced', 'Worldcat/Home', 'Worldcat/Search', + 'Worldcat2/Advanced', 'Worldcat2/Home', 'Worldcat2/Search', ]; $routeGenerator = new \VuFind\Route\RouteGenerator(); diff --git a/module/VuFind/src/VuFind/AjaxHandler/GetItemStatuses.php b/module/VuFind/src/VuFind/AjaxHandler/GetItemStatuses.php index 65faef9f84a..980c8822ee2 100644 --- a/module/VuFind/src/VuFind/AjaxHandler/GetItemStatuses.php +++ b/module/VuFind/src/VuFind/AjaxHandler/GetItemStatuses.php @@ -446,14 +446,27 @@ protected function getAvailabilityMessage(AvailabilityStatusInterface $availabil */ protected function renderFullStatus($record, $simpleStatus, array $values = []) { + // Default case: no extra holdings fields are shown + $holdingsTextFieldsToShow = []; + + if ($this->config->Item_Status->include_holdings_text_fields ?? false) { + // If we are showing additional holdings text fields, the set of fields shown is + // either config.ini's displayed_holdings_text_fields[] (if set), or the set of + // all fields reported by the ILS driver otherwise. + $holdingsTextFieldsToShow = $this->config?->Item_Status?->displayed_holdings_text_fields?->toArray() + ?? $this->ils->getHoldingsTextFieldNames(); + } + $values = array_merge( [ 'statusItems' => $record, 'simpleStatus' => $simpleStatus, 'callnumberHandler' => $this->getCallnumberHandler(), + 'holdingsTextFieldNames' => $holdingsTextFieldsToShow, ], $values ); + return $this->renderer->render('ajax/status-full.phtml', $values); } diff --git a/module/VuFind/src/VuFind/AjaxHandler/GetSideFacets.php b/module/VuFind/src/VuFind/AjaxHandler/GetSideFacets.php index 37fb577e70f..a41db1daafb 100644 --- a/module/VuFind/src/VuFind/AjaxHandler/GetSideFacets.php +++ b/module/VuFind/src/VuFind/AjaxHandler/GetSideFacets.php @@ -234,6 +234,7 @@ protected function formatFacets( } else { $context['facet'] = $facet; $context['cluster'] = $facetSet[$facet] ?? [ + 'label' => $results->getParams()->getFacetLabel($facet), 'list' => [], ]; $context['collapsedFacets'] = []; diff --git a/module/VuFind/src/VuFind/Auth/AbstractBase.php b/module/VuFind/src/VuFind/Auth/AbstractBase.php index e160c0c3426..1fa3e429e94 100644 --- a/module/VuFind/src/VuFind/Auth/AbstractBase.php +++ b/module/VuFind/src/VuFind/Auth/AbstractBase.php @@ -33,6 +33,7 @@ use Exception; use Laminas\Http\PhpEnvironment\Request; use VuFind\Db\Entity\UserEntityInterface; +use VuFind\Db\Service\UserCardServiceInterface; use VuFind\Db\Service\UserServiceInterface; use VuFind\Exception\Auth as AuthException; @@ -572,4 +573,43 @@ protected function setUserValueByField(UserEntityInterface $user, string $field, } $user->$setter($value); } + + /** + * Save user and any ILS credentials. + * + * Also updates user card data if library cards are enabled. + * + * @param UserEntityInterface $user User + * @param ?string $catPassword ILS catalog password + * @param ILSAuthenticator $ilsAuthenticator ILS authenticator + * + * @return void + */ + protected function saveUserAndCredentials( + UserEntityInterface $user, + ?string $catPassword, + ILSAuthenticator $ilsAuthenticator + ): void { + // Save credentials if applicable. Note that we want to allow empty + // passwords (see https://github.com/vufind-org/vufind/pull/532), but + // we also want to be careful not to replace a non-blank password with a + // blank one in case the auth mechanism fails to provide a password on + // an occasion after the user has manually stored one. (For discussion, + // see https://github.com/vufind-org/vufind/pull/612). Note that in the + // (unlikely) scenario that a password can actually change from non-blank + // to blank, additional work may need to be done here. + if (!empty($catUsername = $user->getCatUsername())) { + $ilsAuthenticator->setUserCatalogCredentials( + $user, + $catUsername, + empty($catPassword) ? $ilsAuthenticator->getCatPasswordForUser($user) : $catPassword + ); + } + + // Save the user object: + $this->getUserService()->persistEntity($user); + + // Update library card entry after saving the user so that we always have a user id: + $this->getDbService(UserCardServiceInterface::class)->synchronizeUserLibraryCardData($user); + } } diff --git a/module/VuFind/src/VuFind/Auth/CAS.php b/module/VuFind/src/VuFind/Auth/CAS.php index 83236494d0e..233ff531269 100644 --- a/module/VuFind/src/VuFind/Auth/CAS.php +++ b/module/VuFind/src/VuFind/Auth/CAS.php @@ -170,24 +170,8 @@ public function authenticate($request) } } - // Save credentials if applicable. Note that we want to allow empty - // passwords (see https://github.com/vufind-org/vufind/pull/532), but - // we also want to be careful not to replace a non-blank password with a - // blank one in case the auth mechanism fails to provide a password on - // an occasion after the user has manually stored one. (For discussion, - // see https://github.com/vufind-org/vufind/pull/612). Note that in the - // (unlikely) scenario that a password can actually change from non-blank - // to blank, additional work may need to be done here. - if (!empty($catUsername = $user->getCatUsername())) { - $this->ilsAuthenticator->setUserCatalogCredentials( - $user, - $catUsername, - empty($catPassword) ? $this->ilsAuthenticator->getCatPasswordForUser($user) : $catPassword - ); - } - - // Save and return the user object: - $this->getUserService()->persistEntity($user); + // Save and return user data: + $this->saveUserAndCredentials($user, $catPassword, $this->ilsAuthenticator); return $user; } diff --git a/module/VuFind/src/VuFind/Auth/Database.php b/module/VuFind/src/VuFind/Auth/Database.php index aba9bb09854..e342afe4f7b 100644 --- a/module/VuFind/src/VuFind/Auth/Database.php +++ b/module/VuFind/src/VuFind/Auth/Database.php @@ -31,8 +31,8 @@ namespace VuFind\Auth; -use Laminas\Crypt\Password\Bcrypt; use Laminas\Http\PhpEnvironment\Request; +use VuFind\Crypt\PasswordHasher; use VuFind\Db\Entity\UserEntityInterface; use VuFind\Db\Service\UserServiceInterface; use VuFind\Exception\Auth as AuthException; @@ -54,6 +54,13 @@ */ class Database extends AbstractBase { + /** + * Password hasher + * + * @var PasswordHasher + */ + protected $hasher; + /** * Username * @@ -68,6 +75,16 @@ class Database extends AbstractBase */ protected $password; + /** + * Constructor + * + * @param ?PasswordHasher $hasher Password hash service (null to create one) + */ + public function __construct(?PasswordHasher $hasher = null) + { + $this->hasher = $hasher ?? new PasswordHasher(); + } + /** * Attempt to authenticate the current user. Throws exception if login fails. * @@ -121,8 +138,7 @@ protected function passwordHashingEnabled() protected function setUserPassword(UserEntityInterface $user, string $pass): void { if ($this->passwordHashingEnabled()) { - $bcrypt = new Bcrypt(); - $user->setPasswordHash($bcrypt->create($pass)); + $user->setPasswordHash($this->hasher->create($pass)); } else { $user->setRawPassword($pass); } @@ -300,8 +316,7 @@ protected function checkPassword($password, $userRow) ); } - $bcrypt = new Bcrypt(); - return $bcrypt->verify($password, $userRow->getPasswordHash() ?? ''); + return $this->hasher->verify($password, $userRow->getPasswordHash() ?? ''); } // Default case: unencrypted passwords: diff --git a/module/VuFind/src/VuFind/Http/PhpEnvironment/RemoteAddressFactory.php b/module/VuFind/src/VuFind/Auth/DatabaseFactory.php similarity index 76% rename from module/VuFind/src/VuFind/Http/PhpEnvironment/RemoteAddressFactory.php rename to module/VuFind/src/VuFind/Auth/DatabaseFactory.php index f862faa8bdc..a8e0d0af016 100644 --- a/module/VuFind/src/VuFind/Http/PhpEnvironment/RemoteAddressFactory.php +++ b/module/VuFind/src/VuFind/Auth/DatabaseFactory.php @@ -1,12 +1,11 @@ <?php /** - * RemoteAddress utility factory. This uses the core Laminas RemoteAddress but - * configures it according to VuFind settings. + * Factory for Database authentication module. * * PHP version 8 * - * Copyright (C) Villanova University 2019. + * Copyright (C) Villanova University 2024. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -22,30 +21,30 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * @category VuFind - * @package View_Helpers + * @package Authentication * @author Demian Katz <demian.katz@villanova.edu> * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License * @link https://vufind.org/wiki/development Wiki */ -namespace VuFind\Http\PhpEnvironment; +namespace VuFind\Auth; use Laminas\ServiceManager\Exception\ServiceNotCreatedException; use Laminas\ServiceManager\Exception\ServiceNotFoundException; -use Laminas\ServiceManager\Factory\FactoryInterface; use Psr\Container\ContainerExceptionInterface as ContainerException; use Psr\Container\ContainerInterface; +use VuFind\Crypt\PasswordHasher; /** - * RemoteAddress utility factory. + * Factory for Database authentication module. * * @category VuFind - * @package View_Helpers + * @package Authentication * @author Demian Katz <demian.katz@villanova.edu> * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License * @link https://vufind.org/wiki/development Wiki */ -class RemoteAddressFactory implements FactoryInterface +class DatabaseFactory implements \Laminas\ServiceManager\Factory\FactoryInterface { /** * Create an object @@ -69,11 +68,6 @@ public function __invoke( if (!empty($options)) { throw new \Exception('Unexpected options sent to factory.'); } - $cfg = $container->get(\VuFind\Config\PluginManager::class)->get('config'); - $object = new $requestedName(); - if ($cfg->Site->reverse_proxy ?? false) { - $object->setUseProxy(true); - } - return $object; + return new $requestedName($container->get(PasswordHasher::class)); } } diff --git a/module/VuFind/src/VuFind/Auth/EmailAuthenticator.php b/module/VuFind/src/VuFind/Auth/EmailAuthenticator.php index 43fecab7b60..5f7c824a17e 100644 --- a/module/VuFind/src/VuFind/Auth/EmailAuthenticator.php +++ b/module/VuFind/src/VuFind/Auth/EmailAuthenticator.php @@ -29,11 +29,12 @@ namespace VuFind\Auth; -use Laminas\Http\PhpEnvironment\RemoteAddress; use Laminas\Http\Request; use Laminas\View\Renderer\PhpRenderer; +use VuFind\Config\Feature\EmailSettingsTrait; use VuFind\Db\Service\AuthHashServiceInterface; use VuFind\Exception\Auth as AuthException; +use VuFind\Net\UserIpReader; use VuFind\Validator\CsrfInterface; /** @@ -51,6 +52,7 @@ class EmailAuthenticator implements \VuFind\I18n\Translator\TranslatorAwareInterface { use \VuFind\I18n\Translator\TranslatorAwareTrait; + use EmailSettingsTrait; /** * How long a login request is considered to be valid (seconds) @@ -66,7 +68,7 @@ class EmailAuthenticator implements \VuFind\I18n\Translator\TranslatorAwareInter * @param CsrfInterface $csrf CSRF Validator * @param \VuFind\Mailer\Mailer $mailer Mailer * @param PhpRenderer $viewRenderer View Renderer - * @param RemoteAddress $remoteAddress Remote address + * @param UserIpReader $userIpReader User IP address reader * @param \Laminas\Config\Config $config Configuration * @param AuthHashServiceInterface $authHashService AuthHash database service */ @@ -75,7 +77,7 @@ public function __construct( protected CsrfInterface $csrf, protected \VuFind\Mailer\Mailer $mailer, protected PhpRenderer $viewRenderer, - protected RemoteAddress $remoteAddress, + protected UserIpReader $userIpReader, protected \Laminas\Config\Config $config, protected AuthHashServiceInterface $authHashService ) { @@ -121,7 +123,7 @@ public function sendAuthenticationLink( 'timestamp' => time(), 'data' => $data, 'email' => $email, - 'ip' => $this->remoteAddress->getIpAddress(), + 'ip' => $this->userIpReader->getUserIp(), ]; $hash = $this->csrf->getHash(true); @@ -141,9 +143,7 @@ public function sendAuthenticationLink( $viewParams['title'] = $this->config->Site->title; $message = $this->viewRenderer->render($template, $viewParams); - $from = !empty($this->config->Mail->user_email_in_from) - ? $email - : ($this->config->Mail->default_from ?? $this->config->Site->email); + $from = $this->getEmailSenderAddress($this->config, $email); $subject = $this->translator->translate($subject); $subject = str_replace('%%title%%', $viewParams['title'], $subject); @@ -171,7 +171,7 @@ public function authenticate($hash) $sessionId = $this->sessionManager->getId(); if ( $row->getSessionId() !== $sessionId - && $linkData['ip'] !== $this->remoteAddress->getIpAddress() + && $linkData['ip'] !== $this->userIpReader->getUserIp() ) { throw new AuthException('authentication_error_session_ip_mismatch'); } diff --git a/module/VuFind/src/VuFind/Auth/EmailAuthenticatorFactory.php b/module/VuFind/src/VuFind/Auth/EmailAuthenticatorFactory.php index 7672277c799..866d85d0c93 100644 --- a/module/VuFind/src/VuFind/Auth/EmailAuthenticatorFactory.php +++ b/module/VuFind/src/VuFind/Auth/EmailAuthenticatorFactory.php @@ -72,7 +72,7 @@ public function __invoke( $container->get(\VuFind\Validator\CsrfInterface::class), $container->get(\VuFind\Mailer\Mailer::class), $container->get('ViewRenderer'), - $container->get(\Laminas\Http\PhpEnvironment\RemoteAddress::class), + $container->get(\VuFind\Net\UserIpReader::class), $container->get(\VuFind\Config\PluginManager::class)->get('config'), $container->get(\VuFind\Db\Service\PluginManager::class) ->get(\VuFind\Db\Service\AuthHashServiceInterface::class) diff --git a/module/VuFind/src/VuFind/Auth/LDAP.php b/module/VuFind/src/VuFind/Auth/LDAP.php index 341274aaf37..f54ccf0eb01 100644 --- a/module/VuFind/src/VuFind/Auth/LDAP.php +++ b/module/VuFind/src/VuFind/Auth/LDAP.php @@ -285,7 +285,7 @@ protected function processLDAPUser($username, $data) $user = $this->getOrCreateUserByUsername($username); // Variable to hold catalog password (handled separately from other - // attributes since we need to use setUserCatalogCredentials method to store it): + // attributes since we need to pass it to saveUserAndCredentials method to store it): $catPassword = null; // Loop through LDAP response and map fields to database object based @@ -318,24 +318,8 @@ protected function processLDAPUser($username, $data) } } - // Save credentials if applicable. Note that we want to allow empty - // passwords (see https://github.com/vufind-org/vufind/pull/532), but - // we also want to be careful not to replace a non-blank password with a - // blank one in case the auth mechanism fails to provide a password on - // an occasion after the user has manually stored one. (For discussion, - // see https://github.com/vufind-org/vufind/pull/612). Note that in the - // (unlikely) scenario that a password can actually change from non-blank - // to blank, additional work may need to be done here. - if (!empty($catUsername = $user->getCatUsername())) { - $this->ilsAuthenticator->setUserCatalogCredentials( - $user, - $catUsername, - empty($catPassword) ? $this->ilsAuthenticator->getCatPasswordForUser($user) : $catPassword - ); - } - - // Update the user in the database, then return it to the caller: - $this->getUserService()->persistEntity($user); + // Save and return user data: + $this->saveUserAndCredentials($user, $catPassword, $this->ilsAuthenticator); return $user; } } diff --git a/module/VuFind/src/VuFind/Auth/LoginTokenManager.php b/module/VuFind/src/VuFind/Auth/LoginTokenManager.php index cf08244a749..e2a5d43378b 100644 --- a/module/VuFind/src/VuFind/Auth/LoginTokenManager.php +++ b/module/VuFind/src/VuFind/Auth/LoginTokenManager.php @@ -37,6 +37,7 @@ use Laminas\Log\LoggerAwareInterface; use Laminas\Session\SessionManager; use Laminas\View\Renderer\RendererInterface; +use VuFind\Config\Feature\EmailSettingsTrait; use VuFind\Cookie\CookieManager; use VuFind\Db\Entity\UserEntityInterface; use VuFind\Db\Service\LoginTokenServiceInterface; @@ -60,6 +61,7 @@ */ class LoginTokenManager implements LoggerAwareInterface, TranslatorAwareInterface { + use EmailSettingsTrait; use LoggerAwareTrait; use TranslatorAwareTrait; @@ -377,7 +379,7 @@ protected function sendLoginTokenWarningEmail(UserEntityInterface $user) try { $this->mailer->send( $toAddr, - $this->config->Mail->default_from ?? $this->config->Site->email, + $this->getEmailSenderAddress($this->config), $this->translate($subject, ['%%title%%' => $title]), $message ); diff --git a/module/VuFind/src/VuFind/Auth/Manager.php b/module/VuFind/src/VuFind/Auth/Manager.php index 309b47abdf9..55d44e82cc5 100644 --- a/module/VuFind/src/VuFind/Auth/Manager.php +++ b/module/VuFind/src/VuFind/Auth/Manager.php @@ -92,6 +92,13 @@ class Manager implements */ protected $hideLogin = null; + /** + * ILS Authenticator + * + * @var ?ILSAuthenticator + */ + protected $ilsAuthenticator = null; + /** * Constructor * @@ -123,6 +130,18 @@ public function __construct( $this->setAuthMethod($method); // load it } + /** + * Set ILS Authenticator + * + * @param ILSAuthenticator $ilsAuthenticator ILS authenticator + * + * @return void + */ + public function setILSAuthenticator(ILSAuthenticator $ilsAuthenticator): void + { + $this->ilsAuthenticator = $ilsAuthenticator; + } + /** * Get the authentication handler. * @@ -754,6 +773,34 @@ public function login($request) throw new AuthException('authentication_error_technical', 0, $e); } + // Attempt catalog login so that any bad credentials are cleared before further processing + // (avoids e.g. multiple login attempts by account AJAX checks). + if ( + ($this->config->Catalog->checkILSCredentialsOnLogin ?? true) + && $this->ilsAuthenticator + && $this->allowsUserIlsLogin() + && ($catUsername = $user->getCatUsername()) + // If ILS authentication was used, catalog username must not be the same as the username just used for + // authentication: + && (!in_array($user->getAuthMethod(), ['ils', 'multiils']) || $catUsername !== $user->getUsername()) + && !$this->ils->getOfflineMode() + ) { + try { + $patron = $this->ils->patronLogin( + $catUsername, + $this->ilsAuthenticator->getCatPasswordForUser($user) + ); + if (empty($patron)) { + // Problem logging in -- clear user credentials so they can be + // prompted again; perhaps their password has changed in the + // system! + $user->setCatUsername(null)->setRawCatPassword(null)->setCatPassEnc(null); + } + } catch (\Exception $e) { + // Ignore exceptions here so that the login can continue + } + } + // Update user object $this->updateUser($user, $mainAuthMethod); @@ -765,8 +812,11 @@ public function login($request) throw new AuthException('authentication_error_technical', 0, $e); } } - // Store the user in the session and send it back to the caller: + + // Store the user in the session: $this->updateSession($user); + + // Send user back to caller: return $user; } catch (\Exception $e) { $this->getAuth()->resetState(); diff --git a/module/VuFind/src/VuFind/Auth/ManagerFactory.php b/module/VuFind/src/VuFind/Auth/ManagerFactory.php index 835910fd35b..18c3440f3bf 100644 --- a/module/VuFind/src/VuFind/Auth/ManagerFactory.php +++ b/module/VuFind/src/VuFind/Auth/ManagerFactory.php @@ -91,6 +91,7 @@ public function __invoke( $loginTokenManager, $ils ); + $manager->setIlsAuthenticator($container->get(\VuFind\Auth\ILSAuthenticator::class)); $manager->checkForExpiredCredentials(); return $manager; } diff --git a/module/VuFind/src/VuFind/Auth/PluginManager.php b/module/VuFind/src/VuFind/Auth/PluginManager.php index c7f480b75c9..6772f100d06 100644 --- a/module/VuFind/src/VuFind/Auth/PluginManager.php +++ b/module/VuFind/src/VuFind/Auth/PluginManager.php @@ -29,8 +29,6 @@ namespace VuFind\Auth; -use Laminas\ServiceManager\Factory\InvokableFactory; - /** * Auth handler plugin manager * @@ -75,7 +73,7 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager AlmaDatabase::class => ILSFactory::class, CAS::class => CASFactory::class, ChoiceAuth::class => ChoiceAuthFactory::class, - Database::class => InvokableFactory::class, + Database::class => DatabaseFactory::class, Email::class => EmailFactory::class, Facebook::class => FacebookFactory::class, ILS::class => ILSFactory::class, diff --git a/module/VuFind/src/VuFind/Auth/Shibboleth.php b/module/VuFind/src/VuFind/Auth/Shibboleth.php index 12b77aaefae..5bc00efd576 100644 --- a/module/VuFind/src/VuFind/Auth/Shibboleth.php +++ b/module/VuFind/src/VuFind/Auth/Shibboleth.php @@ -203,14 +203,14 @@ public function authenticate($request) $user = $this->getOrCreateUserByUsername($username); // Variable to hold catalog password (handled separately from other - // attributes since we need to use setUserCatalogCredentials method to store it): + // attributes since we need to pass it to saveUserAndCredentials method to store it): $catPassword = null; // Has the user configured attributes to use for populating the user table? foreach ($this->attribsToCheck as $attribute) { if (isset($shib[$attribute])) { $value = $this->getAttribute($request, $shib[$attribute]); - if ($attribute == 'email') { + if ($attribute == 'email' && !empty($value)) { $userService->updateUserEmail($user, $value); } elseif ( $attribute == 'cat_username' && isset($shib['prefix']) @@ -225,26 +225,9 @@ public function authenticate($request) } } - // Save credentials if applicable. Note that we want to allow empty - // passwords (see https://github.com/vufind-org/vufind/pull/532), but - // we also want to be careful not to replace a non-blank password with a - // blank one in case the auth mechanism fails to provide a password on - // an occasion after the user has manually stored one. (For discussion, - // see https://github.com/vufind-org/vufind/pull/612). Note that in the - // (unlikely) scenario that a password can actually change from non-blank - // to blank, additional work may need to be done here. - if (!empty($catUsername = $user->getCatUsername())) { - $this->ilsAuthenticator->setUserCatalogCredentials( - $user, - $catUsername, - empty($catPassword) ? $this->ilsAuthenticator->getCatPasswordForUser($user) : $catPassword - ); - } - + // Save and return user data: + $this->saveUserAndCredentials($user, $catPassword, $this->ilsAuthenticator); $this->storeShibbolethSession($request); - - // Save and return the user object: - $userService->persistEntity($user); return $user; } diff --git a/module/VuFind/src/VuFind/Auth/SimulatedSSO.php b/module/VuFind/src/VuFind/Auth/SimulatedSSO.php index 17cc33717f3..7cc59193af9 100644 --- a/module/VuFind/src/VuFind/Auth/SimulatedSSO.php +++ b/module/VuFind/src/VuFind/Auth/SimulatedSSO.php @@ -127,18 +127,8 @@ public function authenticate($request) $catPassword = $value; } } - if (!empty($catUsername = $user->getCatUsername())) { - $this->ilsAuthenticator->setUserCatalogCredentials( - $user, - $catUsername, - empty($catPassword) ? $this->ilsAuthenticator->getCatPasswordForUser($user) : $catPassword - ); - } - + $this->saveUserAndCredentials($user, $catPassword, $this->ilsAuthenticator); $this->storeExternalSession(); - - // Save and return the user object: - $userService->persistEntity($user); return $user; } diff --git a/module/VuFind/src/VuFind/Autocomplete/Solr.php b/module/VuFind/src/VuFind/Autocomplete/Solr.php index d904e284050..ed41123d2c0 100644 --- a/module/VuFind/src/VuFind/Autocomplete/Solr.php +++ b/module/VuFind/src/VuFind/Autocomplete/Solr.php @@ -47,6 +47,13 @@ */ class Solr implements AutocompleteInterface { + /** + * Parameter for mungeQuery + * + * @var string + */ + protected const NO_WILDCARD = 'NO_WILDCARD'; + /** * Autocomplete handler * @@ -171,21 +178,49 @@ protected function initSearchObject() /** * Process the user query to make it suitable for a Solr query. * - * @param string $query Incoming user query + * @param string $query Incoming user query + * @param array $options Array of extra parameters * - * @return string Processed query + * @return string Processed query */ - protected function mungeQuery($query) + protected function mungeQuery(string $query, array $options = []): string { // Modify the query so it makes a nice, truncated autocomplete query: $forbidden = [':', '(', ')', '*', '+', '"', "'"]; $query = str_replace($forbidden, ' ', $query); - if (!str_ends_with($query, ' ')) { + if (!str_ends_with($query, ' ') && !($options[self::NO_WILDCARD] ?? false)) { $query .= '*'; } return $query; } + /** + * This method perform and returns the search for a query for the autocomplete box. + * + * @param string $query The user query + * @param bool $rerunSearch Force the search to avoid cached results + * + * @return array The suggestions for the provided query + */ + protected function getSearchResultsForSuggestions(string $query, bool $rerunSearch = false): array + { + $this->searchObject->getParams()->setBasicSearch( + $query, + $this->handler + ); + $this->searchObject->getParams()->setSort($this->sortField); + foreach ($this->filters as $current) { + $this->searchObject->getParams()->addFilter($current); + } + + if ($rerunSearch) { + // Perform the search (force the function, not to have cached results): + $this->searchObject->performAndProcessSearch(); + } + // Perform and/or return the search: + return $this->searchObject->getResults(); + } + /** * This method returns an array of strings matching the user's query for * display in the autocomplete box. @@ -202,18 +237,14 @@ public function getSuggestions($query) } try { - $this->searchObject->getParams()->setBasicSearch( - $this->mungeQuery($query), - $this->handler - ); - $this->searchObject->getParams()->setSort($this->sortField); - foreach ($this->filters as $current) { - $this->searchObject->getParams()->addFilter($current); + $mungedQuery = $this->mungeQuery($query); + $searchResults = $this->getSearchResultsForSuggestions($mungedQuery); + // Re-run without wildcard, if previously ran with wildcard + if (empty($searchResults) && str_ends_with($mungedQuery, '*')) { + $mungedQuery = $this->mungeQuery($query, [self::NO_WILDCARD => true]); + $searchResults = $this->getSearchResultsForSuggestions($mungedQuery, true); } - // Perform the search: - $searchResults = $this->searchObject->getResults(); - // Build the recommendation list -- first we'll try with exact matches; // if we don't get anything at all, we'll try again with a less strict // set of rules. diff --git a/module/VuFind/src/VuFind/Autocomplete/SolrCN.php b/module/VuFind/src/VuFind/Autocomplete/SolrCN.php index 2febe3de2c2..28fba26f85b 100644 --- a/module/VuFind/src/VuFind/Autocomplete/SolrCN.php +++ b/module/VuFind/src/VuFind/Autocomplete/SolrCN.php @@ -60,11 +60,14 @@ public function setConfig($params) /** * Process the user query to make it suitable for a Solr query. * - * @param string $query Incoming user query + * @param string $query Incoming user query + * @param array $options Array of extra parameters * - * @return string Processed query + * @return string Processed query + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - protected function mungeQuery($query) + protected function mungeQuery(string $query, array $options = []): string { // Modify the query so it makes a nice, truncated autocomplete query: $forbidden = [':', '(', ')', '*', '+', '"']; diff --git a/module/VuFind/src/VuFind/Autocomplete/SolrReserves.php b/module/VuFind/src/VuFind/Autocomplete/SolrReserves.php index 8a58f86c9b1..f8795cd5433 100644 --- a/module/VuFind/src/VuFind/Autocomplete/SolrReserves.php +++ b/module/VuFind/src/VuFind/Autocomplete/SolrReserves.php @@ -54,4 +54,37 @@ public function __construct(\VuFind\Search\Results\PluginManager $results) $this->defaultDisplayField = 'course'; $this->searchClassId = 'SolrReserves'; } + + /** + * Try to turn an array of record drivers into an array of suggestions. + * Excluding `no_*_listed` matches since those are the translation values + * when there is no data in that field. + * + * @param array $searchResults An array of record drivers + * @param string $query User search query + * @param bool $exact Ignore non-exact matches? + * + * @return array + */ + protected function getSuggestionsFromSearch($searchResults, $query, $exact) + { + $results = []; + foreach ($searchResults as $object) { + $current = $object->getRawData(); + foreach ($this->displayField as $field) { + if (isset($current[$field]) && !preg_match('/no_.*_listed/', $current[$field])) { + $bestMatch = $this->pickBestMatch( + $current[$field], + $query, + $exact + ); + if ($bestMatch) { + $results[] = $bestMatch; + break; + } + } + } + } + return $results; + } } diff --git a/module/VuFind/src/VuFind/Cart.php b/module/VuFind/src/VuFind/Cart.php index c73a73a44d2..05cc185763a 100644 --- a/module/VuFind/src/VuFind/Cart.php +++ b/module/VuFind/src/VuFind/Cart.php @@ -329,36 +329,6 @@ protected function save() $this->cookieManager->set(self::CART_COOKIE_SOURCES, $srcCookie, 0, false); } - /** - * Get cookie domain context (null if unset). - * - * @return string - */ - public function getCookieDomain() - { - return $this->cookieManager->getDomain(); - } - - /** - * Get cookie path ('/' if unset). - * - * @return string - */ - public function getCookiePath() - { - return $this->cookieManager->getPath(); - } - - /** - * Get cookie SameSite attribute. - * - * @return string - */ - public function getCookieSameSite() - { - return $this->cookieManager->getSameSite(); - } - /** * Process parameters and return the cart content. * diff --git a/module/VuFind/src/VuFind/Config/Feature/EmailSettingsTrait.php b/module/VuFind/src/VuFind/Config/Feature/EmailSettingsTrait.php new file mode 100644 index 00000000000..6f30b46b8af --- /dev/null +++ b/module/VuFind/src/VuFind/Config/Feature/EmailSettingsTrait.php @@ -0,0 +1,70 @@ +<?php + +/** + * Trait providing email settings + * + * PHP version 8 + * + * Copyright (C) The National Library of Finland 2024. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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 + * + * @category VuFind + * @package Config + * @author Ere Maijala <ere.maijala@helsinki.fi> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org Main Site + */ + +namespace VuFind\Config\Feature; + +use Laminas\Config\Config; + +/** + * Trait providing email settings + * + * N.B. User-oriented email settings are handled by \VuFind\Config\AccountCapabilities. + * + * @category VuFind + * @package Config + * @author Ere Maijala <ere.maijala@helsinki.fi> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org Main Site + */ +trait EmailSettingsTrait +{ + /** + * Get sender email address + * + * @param array|Config $config VuFind configuration + * @param ?string $userEmail User's own email address that is used if permitted by settings + * + * @return string + */ + protected function getEmailSenderAddress(array|Config $config, ?string $userEmail = null): string + { + if ($config instanceof Config) { + $config = $config->toArray(); + } + if (null !== $userEmail && ($config['Mail']['user_email_in_from'] ?? false)) { + return $userEmail; + } + if (null === ($result = $config['Mail']['default_from'] ?? $config['Site']['email'] ?? null)) { + throw new \Exception( + 'Missing configuration for email sender. Please check settings Mail/default_from and Site/email.' + ); + } + return $result; + } +} diff --git a/module/VuFind/src/VuFind/Config/Feature/IniReaderTrait.php b/module/VuFind/src/VuFind/Config/Feature/IniReaderTrait.php index d41561b4131..ffab6575d86 100644 --- a/module/VuFind/src/VuFind/Config/Feature/IniReaderTrait.php +++ b/module/VuFind/src/VuFind/Config/Feature/IniReaderTrait.php @@ -59,9 +59,8 @@ trait IniReaderTrait protected function getIniReader() { if (null == $this->iniReader) { - // Use ASCII 0 as a nest separator; otherwise some of the unusual key names - // we have (i.e. in WorldCat.ini search options) will get parsed in - // unexpected ways. + // Use ASCII 0 as a nest separator; otherwise some of our unusual key names + // (e.g. strings containing . characters) will get parsed in unexpected ways. $this->iniReader = new IniReader(); $this->iniReader->setNestSeparator(chr(0)); } diff --git a/module/VuFind/src/VuFind/Config/Feature/SecretTrait.php b/module/VuFind/src/VuFind/Config/Feature/SecretTrait.php new file mode 100644 index 00000000000..44eb21c9b6c --- /dev/null +++ b/module/VuFind/src/VuFind/Config/Feature/SecretTrait.php @@ -0,0 +1,84 @@ +<?php + +/** + * Trait to import secret from file rather than a hardcoded config + * + * PHP version 8 + * + * Copyright (C) Michigan State University 2024. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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 + * + * @category VuFind + * @package Config + * @author Robby ROUDON <roudonro@msu.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org Main Page + */ + +namespace VuFind\Config\Feature; + +use Laminas\Config\Config; + +/** + * Trait to import secret from file rather than a hardcoded config + * + * @category VuFind + * @package Config + * @author Robby ROUDON <roudonro@msu.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org Main Page + */ +trait SecretTrait +{ + /** + * Load a secret value from the specified configuration and key. + * Will look for a _file-suffixed version of the key first, + * and load the data from a separate file if configured to do so. + * + * @param Config|array|null $config The config to read from + * @param string $key The key to retrieve + * + * @return string|null + */ + protected function getSecretFromConfig(Config|array|null $config, string $key): ?string + { + if ($config === null) { + return null; + } + if ($config instanceof Config) { + $config = $config->toArray(); + } + if ($secretFile = $config[$key . '_file'] ?? null) { + if (is_readable($secretFile)) { + $value = file_get_contents($secretFile); + return trim($value); + } elseif (method_exists($this, 'logWarning')) { + $this->logWarning( + 'The secret file (' . $secretFile . ')' . + ' for secret ' . $key . ' doesn\'t exist or is not readable.' + ); + } else { + error_log( + 'The secret file (' . $secretFile . ')' . + ' for secret ' . $key . ' doesn\'t exist or is not readable' + ); + } + } + if (isset($config[$key])) { + return $config[$key]; + } + return null; + } +} diff --git a/module/VuFind/src/VuFind/Config/PathResolver.php b/module/VuFind/src/VuFind/Config/PathResolver.php index 732bb4fa10d..41893ad5514 100644 --- a/module/VuFind/src/VuFind/Config/PathResolver.php +++ b/module/VuFind/src/VuFind/Config/PathResolver.php @@ -156,6 +156,16 @@ public function getConfigPath(string $filename, ?string $path = null): ?string return $this->getBaseConfigPath($filename, $path); } + /** + * Get local config dir stack. + * + * @return array + */ + public function getLocalConfigDirStack(): array + { + return $this->localConfigDirStack; + } + /** * Build a complete file path from a directory specification, optional * configuration file sub-directory and a filename. @@ -171,8 +181,7 @@ protected function buildPath( ?string $configSubdir, string $filename ): string { - return $directorySpec['directory'] - . '/' . ($configSubdir ?? $directorySpec['defaultConfigSubdir']) - . "/$filename"; + $configSubdir ??= $directorySpec['defaultConfigSubdir']; + return $directorySpec['directory'] . '/' . $configSubdir . '/' . $filename; } } diff --git a/module/VuFind/src/VuFind/Config/PathResolverFactory.php b/module/VuFind/src/VuFind/Config/PathResolverFactory.php index 4d901eb4ef8..8cd59085b97 100644 --- a/module/VuFind/src/VuFind/Config/PathResolverFactory.php +++ b/module/VuFind/src/VuFind/Config/PathResolverFactory.php @@ -124,6 +124,7 @@ public function __invoke( 'defaultConfigSubdir' => $systemConfig['Local_Dir']['config_subdir'] ?? $this->defaultLocalConfigSubdir, + 'dirLocationConfig' => $systemConfig, ] ); diff --git a/module/VuFind/src/VuFind/Config/Upgrade.php b/module/VuFind/src/VuFind/Config/Upgrade.php index 6f4ad3ac749..afc3cc7be97 100644 --- a/module/VuFind/src/VuFind/Config/Upgrade.php +++ b/module/VuFind/src/VuFind/Config/Upgrade.php @@ -168,7 +168,6 @@ public function run() $this->upgradeSms(); $this->upgradeSummon(); $this->upgradePrimo(); - $this->upgradeWorldCat(); // The previous upgrade routines may have added values to permissions.ini, // so we should save it last. It doesn't have its own upgrade routine. @@ -621,24 +620,13 @@ protected function upgradeConfig() } // Warn the user about deprecated WorldCat settings: - if (isset($newConfig['WorldCat']['LimitCodes'])) { - unset($newConfig['WorldCat']['LimitCodes']); + if (isset($newConfig['WorldCat'])) { + unset($newConfig['WorldCat']); $this->addWarning( - 'The [WorldCat] LimitCodes setting never had any effect and has been' - . ' removed.' + 'The [WorldCat] section of config.ini has been removed following' + . ' the shutdown of the v1 WorldCat search API; use WorldCat2.ini instead.' ); } - $badKeys - = ['id', 'xISBN_token', 'xISBN_secret', 'xISSN_token', 'xISSN_secret']; - foreach ($badKeys as $key) { - if (isset($newConfig['WorldCat'][$key])) { - unset($newConfig['WorldCat'][$key]); - $this->addWarning( - 'The [WorldCat] ' . $key . ' setting is no longer used and' - . ' has been removed.' - ); - } - } if ( isset($newConfig['Record']['related']) && in_array('Editions', $newConfig['Record']['related']) @@ -1232,59 +1220,6 @@ protected function upgradePrimoServerSettings() } } - /** - * Upgrade WorldCat.ini. - * - * @throws FileAccessException - * @return void - */ - protected function upgradeWorldCat() - { - // If WorldCat is disabled in our current configuration, we don't need to - // load any WorldCat-specific settings: - if (!isset($this->newConfigs['config.ini']['WorldCat']['apiKey'])) { - return; - } - - // we want to retain the old installation's search settings exactly as-is - $groups = [ - 'Basic_Searches', 'Advanced_Searches', 'Sorting', - ]; - $this->applyOldSettings('WorldCat.ini', $groups); - - // we need to fix an obsolete search setting for authors - foreach (['Basic_Searches', 'Advanced_Searches'] as $section) { - $new = []; - foreach ($this->newConfigs['WorldCat.ini'][$section] as $k => $v) { - if ($k == 'srw.au:srw.pn:srw.cn') { - $k = 'srw.au'; - } - $new[$k] = $v; - } - $this->newConfigs['WorldCat.ini'][$section] = $new; - } - - // Deal with deprecated related record module. - $newConfig = & $this->newConfigs['WorldCat.ini']; - if ( - isset($newConfig['Record']['related']) - && in_array('WorldCatEditions', $newConfig['Record']['related']) - ) { - $newConfig['Record']['related'] = array_diff( - $newConfig['Record']['related'], - ['WorldCatEditions'] - ); - $this->addWarning( - 'The WorldCatEditions related record module is no longer ' - . 'supported due to OCLC\'s xID API shutdown.' - . ' It has been removed from your settings.' - ); - } - - // save the file - $this->saveModifiedConfig('WorldCat.ini'); - } - /** * Does the specified properties file contain any meaningful * (non-empty/non-comment) lines? diff --git a/module/VuFind/src/VuFind/Content/Covers/Google.php b/module/VuFind/src/VuFind/Content/Covers/Google.php index a0642d14a70..f36a9c7ed55 100644 --- a/module/VuFind/src/VuFind/Content/Covers/Google.php +++ b/module/VuFind/src/VuFind/Content/Covers/Google.php @@ -30,8 +30,7 @@ namespace VuFind\Content\Covers; use VuFind\Exception\HttpDownloadException; - -use function is_callable; +use VuFindCode\ISBN; /** * Google cover content loader. @@ -63,27 +62,30 @@ public function __construct() * @param array $ids Associative array of identifiers (keys may include 'isbn' * pointing to an ISBN object and 'issn' pointing to a string) * - * @return string|bool + * @return string|bool URL of the image, or false if no valid image is found * * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function getUrl($key, $size, $ids) { - // Don't bother trying if we can't read JSON or ISBN and OCLC are missing: - if (!is_callable('json_decode') || (!isset($ids['isbn']) && !isset($ids['oclc']))) { - return false; + // Get an array of ISBNs; note that (array) casting won't have the desired + // effect here, so we need to explicitly check for ISBN objects: + $isbns = $ids['isbns'] ?? $ids['isbn'] ?? []; + $isbns = $isbns instanceof ISBN ? [$isbns] : $isbns; + + // Initialize the identifiers list from our ISBNs; add OCLC number if available: + $identifiers = array_map(fn ($isbn) => "ISBN:{$isbn->get13()}", $isbns); + if (isset($ids['oclc'])) { + $identifiers[] = "OCLC:{$ids['oclc']}"; } - // Construct the request URL and make the HTTP request: - if (isset($ids['isbn']) && $ids['isbn']->isValid()) { - $ident = "ISBN:{$ids['isbn']->get13()}"; - } elseif (isset($ids['oclc'])) { - $ident = "OCLC:{$ids['oclc']}"; - } else { + if (empty($identifiers)) { return false; } - $url = 'https://books.google.com/books?jscmd=viewapi&' . - 'bibkeys=' . $ident . '&callback=addTheCover'; + + // Construct the request URL and make the HTTP request, using a single URL with all identifiers + $url = 'https://books.google.com/books?jscmd=viewapi&bibkeys=' + . urlencode(implode(',', $identifiers)) . '&callback=addTheCover'; $decodeCallback = function (\Laminas\Http\Response $response, $url) { if ( diff --git a/module/VuFind/src/VuFind/Controller/AbstractBase.php b/module/VuFind/src/VuFind/Controller/AbstractBase.php index 9d6958961d9..a8756c02439 100644 --- a/module/VuFind/src/VuFind/Controller/AbstractBase.php +++ b/module/VuFind/src/VuFind/Controller/AbstractBase.php @@ -36,6 +36,7 @@ use Laminas\ServiceManager\ServiceLocatorInterface; use Laminas\Uri\Http; use Laminas\View\Model\ViewModel; +use VuFind\Config\Feature\EmailSettingsTrait; use VuFind\Controller\Feature\AccessPermissionInterface; use VuFind\Db\Entity\UserEntityInterface; use VuFind\Exception\Auth as AuthException; @@ -77,6 +78,7 @@ */ class AbstractBase extends AbstractActionController implements AccessPermissionInterface, TranslatorAwareInterface { + use EmailSettingsTrait; use GetServiceTrait; use TranslatorAwareTrait; @@ -266,7 +268,7 @@ protected function createEmailViewModel($params = null, $defaultSubject = null) // Fail if we're missing a from and the form element is disabled: if ($view->disableFrom) { if (empty($view->from)) { - $view->from = $config->Site->email; + $view->from = $this->getEmailSenderAddress($config); } if (empty($view->from)) { throw new \Exception('Unable to determine email from address'); @@ -868,12 +870,19 @@ protected function getILSLoginSettings() * Construct an HTTP 205 (refresh) response. Useful for reporting success * in the lightbox without actually rendering content. * + * @param bool $forceGet If true, sends a custom header indicating that the page should be reloaded with a GET + * request. This can be useful when it is known that the current page only receives transient params in a POST + * request (such as canceling of holds). + * * @return \Laminas\Http\Response */ - protected function getRefreshResponse() + protected function getRefreshResponse(bool $forceGet = false) { $response = $this->getResponse(); $response->setStatusCode(205); + if ($forceGet) { + $response->getHeaders()->addHeaderLine('X-VuFind-Refresh-Method', 'GET'); + } return $response; } diff --git a/module/VuFind/src/VuFind/Controller/AbstractRecord.php b/module/VuFind/src/VuFind/Controller/AbstractRecord.php index b473d395e73..8e0fbbc6dbc 100644 --- a/module/VuFind/src/VuFind/Controller/AbstractRecord.php +++ b/module/VuFind/src/VuFind/Controller/AbstractRecord.php @@ -455,6 +455,12 @@ public function saveAction() return $response; } + if ($this->formWasSubmitted('newList')) { + // Remove submit now from parameters + $this->getRequest()->getPost()->set('newList', null)->set('submitButton', null); + return $this->forwardTo('MyResearch', 'editList', ['id' => 'NEW']); + } + // Process form submission: if ($this->formWasSubmitted()) { return $this->processSave(); @@ -473,7 +479,8 @@ public function saveAction() // by unsetting the followup and relying on default behavior in processSave. $referer = $this->getRequest()->getServer()->get('HTTP_REFERER'); if ( - !str_ends_with($referer, '/Save') + !empty($referer) + && !str_ends_with($referer, '/Save') && stripos($referer, 'MyResearch/EditList/NEW') === false && $this->isLocalUrl($referer) ) { diff --git a/module/VuFind/src/VuFind/Controller/AbstractSearch.php b/module/VuFind/src/VuFind/Controller/AbstractSearch.php index 8fbcde210a8..8fb7893f57f 100644 --- a/module/VuFind/src/VuFind/Controller/AbstractSearch.php +++ b/module/VuFind/src/VuFind/Controller/AbstractSearch.php @@ -355,6 +355,8 @@ protected function getRssSearchResponse(ViewModel $view): Response protected function getSearchResultsView($setupCallback = null) { $view = $this->createViewModel(); + $config = $this->getConfig($this->getOptionsForClass()->getFacetsIni()); + $view->multiFacetsSelection = (bool)($config->Results_Settings->multiFacetsSelection ?? false); // Handle saved search requests: $savedId = $this->params()->fromQuery('saved', false); diff --git a/module/VuFind/src/VuFind/Controller/AlmaController.php b/module/VuFind/src/VuFind/Controller/AlmaController.php index d4e4ac9a6e7..0f72c2e20c6 100644 --- a/module/VuFind/src/VuFind/Controller/AlmaController.php +++ b/module/VuFind/src/VuFind/Controller/AlmaController.php @@ -379,7 +379,7 @@ protected function sendSetPasswordEmail(UserEntityInterface $user, $config) // Send the email $this->getService(\VuFind\Mailer\Mailer::class)->send( $user->getEmail(), - $config->Site->email, + $this->getEmailSenderAddress($config), $this->translate( 'new_user_welcome_subject', ['%%library%%' => $config->Site->title] diff --git a/module/VuFind/src/VuFind/Controller/AlphabrowseController.php b/module/VuFind/src/VuFind/Controller/AlphabrowseController.php index b7fa1b7995c..75f769f1c3c 100644 --- a/module/VuFind/src/VuFind/Controller/AlphabrowseController.php +++ b/module/VuFind/src/VuFind/Controller/AlphabrowseController.php @@ -177,6 +177,11 @@ protected function addResultsToView( $view->prevpage = $page - 1; } } + + if ($view->source === 'topic') { + $this->applyTopicDelimiters($result); + } + $view->result = $result; // set up highlighting: page 0 contains match location @@ -185,6 +190,26 @@ protected function addResultsToView( } } + /** + * Applies topic delimiters to the 'heading' field of each item in the browse results. + * + * @param array $result The result array containing 'Browse' items to be modified. + * + * @return void + */ + protected function applyTopicDelimiters(&$result): void + { + $config = $this->getConfig(); + + foreach ($result['Browse']['items'] as &$item) { + $item['heading'] = str_replace( + "\u{2002}", + ($config->AlphaBrowse->topic_browse_separator ?? ' > '), + $item['heading'] + ); + } + } + /** * Apply highlighting settings to the view based on the result set. * diff --git a/module/VuFind/src/VuFind/Controller/CartController.php b/module/VuFind/src/VuFind/Controller/CartController.php index 00bfb7fc446..e20473e3d86 100644 --- a/module/VuFind/src/VuFind/Controller/CartController.php +++ b/module/VuFind/src/VuFind/Controller/CartController.php @@ -142,7 +142,7 @@ public function searchresultsbulkAction() // have an external site in the referer, we should ignore that! $referer = $this->getRequest()->getServer()->get('HTTP_REFERER'); $bulk = $this->url()->fromRoute('cart-searchresultsbulk'); - if ($this->isLocalUrl($referer) && !str_ends_with($referer, $bulk)) { + if (!empty($referer) && $this->isLocalUrl($referer) && !str_ends_with($referer, $bulk)) { $this->session->url = $referer; } @@ -533,9 +533,22 @@ public function saveAction() ['cartIds' => $ids, 'cartAction' => 'Save'] ); } - + $viewModel = $this->createViewModel( + [ + 'records' => $this->getRecordLoader()->loadBatch($ids), + 'lists' => $this->getDbService(UserListServiceInterface::class)->getUserListsByUser($user), + ] + ); + if ($submitDisabled ?? false) { + return $viewModel; + } + if ($this->formWasSubmitted('newList')) { + // Remove submit now from parameters + $this->getRequest()->getPost()->set('newList', null)->set('submitButton', null); + return $this->forwardTo('MyResearch', 'editlist', ['id' => 'NEW']); + } // Process submission if necessary: - if (!($submitDisabled ?? false) && $this->formWasSubmitted()) { + if ($this->formWasSubmitted()) { $results = $this->getService(FavoritesService::class) ->saveRecordsToFavorites($this->getRequest()->getPost()->toArray(), $user); $listUrl = $this->url()->fromRoute( @@ -553,11 +566,6 @@ public function saveAction() } // Pass record and list information to view: - return $this->createViewModel( - [ - 'records' => $this->getRecordLoader()->loadBatch($ids), - 'lists' => $this->getDbService(UserListServiceInterface::class)->getUserListsByUser($user), - ] - ); + return $viewModel; } } diff --git a/module/VuFind/src/VuFind/Controller/ExternalAuthController.php b/module/VuFind/src/VuFind/Controller/ExternalAuthController.php index 91eab29f8ef..3b81f3960f3 100644 --- a/module/VuFind/src/VuFind/Controller/ExternalAuthController.php +++ b/module/VuFind/src/VuFind/Controller/ExternalAuthController.php @@ -125,11 +125,10 @@ protected function createEzproxyTicketUrl($user, $url) } $packet = '$u' . time() . '$e'; - $hash = new \Laminas\Crypt\Hash(); $algorithm = !empty($config->EZproxy->secret_hash_method) ? $config->EZproxy->secret_hash_method : 'SHA512'; $ticket = $config->EZproxy->secret . $user . $packet; - $ticket = $hash->compute($algorithm, $ticket); + $ticket = hash($algorithm, $ticket); $ticket .= $packet; $params = http_build_query( ['user' => $user, 'ticket' => $ticket, 'url' => $url] diff --git a/module/VuFind/src/VuFind/Controller/HoldsController.php b/module/VuFind/src/VuFind/Controller/HoldsController.php index 83277c7c90c..2a5ad23f6f2 100644 --- a/module/VuFind/src/VuFind/Controller/HoldsController.php +++ b/module/VuFind/src/VuFind/Controller/HoldsController.php @@ -99,8 +99,7 @@ public function listAction() // Process cancel requests if necessary: $cancelStatus = $catalog->checkFunction('cancelHolds', compact('patron')); $view = $this->createViewModel(); - $view->cancelResults = $cancelStatus - ? $this->holds()->cancelHolds($catalog, $patron) : []; + $view->cancelResults = $cancelStatus ? $this->holds()->cancelHolds($catalog, $patron) : []; // If we need to confirm if (!is_array($view->cancelResults)) { return $view->cancelResults; @@ -281,7 +280,7 @@ public function editAction() $this->flashMessenger()->addErrorMessage($msg); } return $this->inLightbox() - ? $this->getRefreshResponse() + ? $this->getRefreshResponse(true) : $this->redirect()->toRoute('holds-list'); } } diff --git a/module/VuFind/src/VuFind/Controller/InstallController.php b/module/VuFind/src/VuFind/Controller/InstallController.php index 245ef4aeff3..38515d89c77 100644 --- a/module/VuFind/src/VuFind/Controller/InstallController.php +++ b/module/VuFind/src/VuFind/Controller/InstallController.php @@ -29,9 +29,9 @@ namespace VuFind\Controller; -use Laminas\Crypt\Password\Bcrypt; use Laminas\Mvc\MvcEvent; use VuFind\Config\Writer as ConfigWriter; +use VuFind\Crypt\PasswordHasher; use VuFind\Db\Service\TagServiceInterface; use VuFind\Db\Service\UserCardServiceInterface; use VuFind\Db\Service\UserServiceInterface; @@ -117,7 +117,8 @@ protected function installBasicConfig() { $config = $this->getForcedLocalConfigPath('config.ini'); if (!file_exists($config)) { - return copy($this->getBaseConfigFilePath('config.ini'), $config); + // Suppress errors so we don't cause a fatal error if copy is disallowed. + return @copy($this->getBaseConfigFilePath('config.ini'), $config); } return true; // report success if file already exists } @@ -184,6 +185,9 @@ public function fixbasicconfigAction() throw new \Exception('Cannot copy file into position.'); } $writer = new ConfigWriter($config); + // Choose secure defaults when creating initial config.ini: + $this->fixSecurityConfiguration($config, $writer); + // Set appropriate URLs: $serverUrl = $this->getViewRenderer()->plugin('serverurl'); $path = $this->url()->fromRoute('home'); $writer->set('Site', 'url', rtrim($serverUrl($path), '/')); @@ -408,14 +412,17 @@ public function fixdatabaseAction() ->addMessage('Password fields must match.', 'error'); } else { // Connect to database: - $connection = $view->driver . '://' . $view->dbrootuser . ':' - . $this->params()->fromPost('dbrootpass') . '@' - . $view->dbhost; try { - $dbName = ($view->driver == 'pgsql') - ? 'template1' : $view->driver; - $db = $this->getService(\VuFind\Db\AdapterFactory::class) - ->getAdapterFromConnectionString("{$connection}/{$dbName}"); + $dbName = ($view->driver == 'pgsql') ? 'template1' : $view->driver; + $connectionParams = [ + 'driver' => $view->driver, + 'hostname' => $view->dbhost, + 'username' => $view->dbrootuser, + 'password' => $this->params()->fromPost('dbrootpass'), + ]; + $db = $this->serviceLocator->get(\VuFind\Db\AdapterFactory::class)->getAdapterFromArray( + $connectionParams + ['database' => $dbName] + ); } catch (\Exception $e) { $this->flashMessenger() ->addMessage( @@ -451,9 +458,8 @@ public function fixdatabaseAction() foreach ($preCommands as $query) { $db->query($query, $db::QUERY_MODE_EXECUTE); } - $dbFactory = $this->getService(\VuFind\Db\AdapterFactory::class); - $db = $dbFactory->getAdapterFromConnectionString( - $connection . '/' . $view->dbname + $db = $this->getService(\VuFind\Db\AdapterFactory::class)->getAdapterFromArray( + $connectionParams + ['database' => $view->dbname] ); $statements = explode(';', $sql); foreach ($statements as $current) { @@ -718,9 +724,14 @@ public function fixsolrAction() */ protected function checkSecurity() { + try { + $secureDb = $this->hasSecureDatabase(); + } catch (\Throwable $e) { + $secureDb = false; + } return [ 'title' => 'Security', - 'status' => $this->hasSecureDatabase(), + 'status' => $secureDb, 'fix' => 'fixsecurity', ]; } @@ -773,8 +784,16 @@ public function fixsecurityAction() } // If we don't need to prompt the user, or if they confirmed, do the fix: - $userRows = $this->getDbService(UserServiceInterface::class)->getInsecureRows(); - $cardRows = $this->getDbService(UserCardServiceInterface::class)->getInsecureRows(); + try { + $userRows = $this->getDbService(UserServiceInterface::class)->getInsecureRows(); + $cardRows = $this->getDbService(UserCardServiceInterface::class)->getInsecureRows(); + } catch (\Throwable $e) { + $this->flashMessenger()->addMessage( + 'Cannot connect to database; please configure database before fixing security.', + 'error' + ); + return $this->redirect()->toRoute('install-home'); + } if (count($userRows) + count($cardRows) == 0 || $userConfirmation == 'Yes') { return $this->forwardTo('Install', 'performsecurityfix'); } @@ -812,18 +831,19 @@ public function performsecurityfixAction() // Now we want to loop through the database and update passwords (if // necessary). $ilsAuthenticator = $this->getService(\VuFind\Auth\ILSAuthenticator::class); - $userRows = $this->getDbService(UserServiceInterface::class)->getInsecureRows(); + $userService = $this->getDbService(UserServiceInterface::class); + $userRows = $userService->getInsecureRows(); if (count($userRows) > 0) { - $bcrypt = new Bcrypt(); + $hasher = $this->getService(PasswordHasher::class); foreach ($userRows as $row) { if ($row->getRawPassword() != '') { - $row->setPasswordHash($bcrypt->create($row->getRawPassword())); + $row->setPasswordHash($hasher->create($row->getRawPassword())); $row->setRawPassword(''); } if ($rawPassword = $row->getRawCatPassword()) { $ilsAuthenticator->saveUserCatalogCredentials($row, $row->getCatUsername(), $rawPassword); } else { - $row->save(); + $userService->persistEntity($row); } } $msg = count($userRows) . ' user row(s) encrypted.'; diff --git a/module/VuFind/src/VuFind/Controller/MyResearchController.php b/module/VuFind/src/VuFind/Controller/MyResearchController.php index d52b431b83d..e08cf34ed2f 100644 --- a/module/VuFind/src/VuFind/Controller/MyResearchController.php +++ b/module/VuFind/src/VuFind/Controller/MyResearchController.php @@ -42,6 +42,7 @@ use VuFind\Crypt\SecretCalculator; use VuFind\Db\Entity\SearchEntityInterface; use VuFind\Db\Entity\UserEntityInterface; +use VuFind\Db\Entity\UserListEntityInterface; use VuFind\Db\Service\SearchServiceInterface; use VuFind\Db\Service\UserListServiceInterface; use VuFind\Db\Service\UserResourceServiceInterface; @@ -1180,9 +1181,9 @@ protected function processEditList(UserEntityInterface $user, $list) // If the user is in the process of saving a record, send them back // to the save screen; otherwise, send them back to the list they // just edited. - $recordId = $this->params()->fromQuery('recordId'); - $recordSource - = $this->params()->fromQuery('recordSource', DEFAULT_SEARCH_BACKEND); + $recordId = $this->params()->fromQuery('recordId') ?? $this->params()->fromPost('recordId'); + $recordSource = $this->params()->fromQuery('recordSource') + ?? $this->params()->fromPost('recordSource', DEFAULT_SEARCH_BACKEND); if (!empty($recordId)) { $details = $this->getRecordRouter()->getActionRouteDetails( $recordSource . '|' . $recordId, @@ -1196,19 +1197,12 @@ protected function processEditList(UserEntityInterface $user, $list) // Similarly, if the user is in the process of bulk-saving records, // send them back to the appropriate place in the cart. - $bulkIds = $this->params()->fromPost( - 'ids', - $this->params()->fromQuery('ids', []) - ); + $bulkIds = $this->params()->fromPost('ids') ?? $this->params()->fromQuery('ids', []); if (!empty($bulkIds)) { - $params = []; - foreach ($bulkIds as $id) { - $params[] = urlencode('ids[]') . '=' . urlencode($id); - } - $saveUrl = $this->url()->fromRoute('cart-save'); - $saveUrl .= (!str_contains($saveUrl, '?')) ? '?' : '&'; - return $this->redirect() - ->toUrl($saveUrl . implode('&', $params)); + // Add final id of the list to request post so cartcontroller saveaction + // can properly load the list + $this->getRequest()->getPost()->set('list', $finalId); + return $this->forwardTo('Cart', 'Save'); } return $this->redirect()->toRoute('userList', ['id' => $finalId]); @@ -1240,7 +1234,7 @@ public function editlistAction() // Is this a new list or an existing list? Handle the special 'NEW' value // of the ID parameter: - $id = $this->params()->fromRoute('id', $this->params()->fromQuery('id')); + $id = $this->params()->fromRoute('id') ?? $this->params()->fromQuery('id') ?? $this->params()->fromPost('id'); $newList = ($id == 'NEW'); // If this is a new list, use the FavoritesService to pre-populate some values in // a fresh object; if it's an existing list, we can just fetch from the database. @@ -1255,10 +1249,8 @@ public function editlistAction() } // Process form submission: - if ($this->formWasSubmitted()) { - if ($redirect = $this->processEditList($user, $list)) { - return $redirect; - } + if ($this->formWasSubmitted() && $redirect = $this->processEditList($user, $list)) { + return $redirect; } $listTags = null; @@ -1273,6 +1265,10 @@ public function editlistAction() 'list' => $list, 'newList' => $newList, 'listTags' => $listTags, + 'recordIds' => $this->params()->fromQuery('ids') ?? $this->params()->fromPost('ids', []), + 'recordId' => $this->params()->fromQuery('recordId') ?? $this->params()->fromPost('recordId', false), + 'recordSource' => $this->params()->fromQuery('recordSource') + ?? $this->params()->fromPost('recordSource', DEFAULT_SEARCH_BACKEND), ] ); } @@ -1288,8 +1284,8 @@ public function emailNotVerifiedAction() if ($this->params()->fromQuery('reverify')) { $change = false; // Case 1: new user: - $user = $this->getDbService(UserServiceInterface::class) - ->getUserByUsername($this->getUserVerificationContainer()->user); + $username = $this->getUserVerificationContainer()->user; + $user = $username ? $this->getDbService(UserServiceInterface::class)->getUserByUsername($username) : null; // Case 2: pending email change: if (!$user) { $user = $this->getUser(); @@ -1782,7 +1778,7 @@ protected function sendRecoveryEmail(UserEntityInterface $user, $config) ); $this->getService(Mailer::class)->send( $user->getEmail(), - $config->Site->email, + $this->getEmailSenderAddress($config), $this->translate('recovery_email_subject'), $message ); @@ -1843,7 +1839,7 @@ protected function sendChangeNotificationEmail($user, $newEmail) // address; if they have a pending address change, use that. $this->getService(Mailer::class)->send( $user->getEmail(), - $config->Site->email, + $this->getEmailSenderAddress($config), $this->translate('change_notification_email_subject'), $message ); @@ -1893,7 +1889,7 @@ protected function sendVerificationEmail($user, $change = false) $to = ($pending = $user->getPendingEmail()) ? $pending : $user->getEmail(); $this->getService(Mailer::class)->send( $to, - $config->Site->email, + $this->getEmailSenderAddress($config), $this->translate('verification_email_subject'), $message ); diff --git a/module/VuFind/src/VuFind/Controller/OAuth2Controller.php b/module/VuFind/src/VuFind/Controller/OAuth2Controller.php index c0cfd9568e4..a7ddc302957 100644 --- a/module/VuFind/src/VuFind/Controller/OAuth2Controller.php +++ b/module/VuFind/src/VuFind/Controller/OAuth2Controller.php @@ -172,6 +172,20 @@ public function authorizeAction() return $this->handleException('Authorization request', $e); } + // Hide any scopes not allowed by a client-specific filter (see also ScopeRepository for the actual filtering): + if ($allowedScopes = $clientConfig['allowedScopes'] ?? null) { + $scopes = $authRequest->getScopes(); + array_map( + function ($scope) use ($allowedScopes) { + if (!in_array($scope->getIdentifier(), $allowedScopes)) { + $scope->setHidden(true); + } + }, + $scopes + ); + $authRequest->setScopes($scopes); + } + if ($this->formWasSubmitted('allow') || $this->formWasSubmitted('deny')) { // Check CSRF and session: if (!$this->csrf->isValid($this->getRequest()->getPost()->get('csrf'))) { @@ -278,8 +292,9 @@ public function userInfoAction() OAuthServerException::accessDenied('User does not exist anymore') ); } - $result = $this->claimExtractor - ->extract($scopes, $userEntity->getClaims()); + $result = $this->claimExtractor->extract($scopes, $userEntity->getClaims()); + // The sub claim must always be returned: + $result['sub'] = $userId; return $this->getJsonResponse($result); } catch (OAuthServerException $e) { return $this->handleOAuth2Exception('User info request', $e); diff --git a/module/VuFind/src/VuFind/Controller/Worldcat2Controller.php b/module/VuFind/src/VuFind/Controller/Worldcat2Controller.php new file mode 100644 index 00000000000..c707abac1c0 --- /dev/null +++ b/module/VuFind/src/VuFind/Controller/Worldcat2Controller.php @@ -0,0 +1,76 @@ +<?php + +/** + * WorldCat v2 Controller + * + * PHP version 8 + * + * Copyright (C) Villanova University 2024. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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 + * + * @category VuFind + * @package Controller + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org Main Site + */ + +namespace VuFind\Controller; + +use Laminas\ServiceManager\ServiceLocatorInterface; + +/** + * WorldCat v2 Controller + * + * @category VuFind + * @package Controller + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org Main Site + */ +class Worldcat2Controller extends AbstractSearch +{ + /** + * Constructor + * + * @param ServiceLocatorInterface $sm Service locator + */ + public function __construct(ServiceLocatorInterface $sm) + { + $this->searchClassId = 'WorldCat2'; + parent::__construct($sm); + } + + /** + * Is the result scroller active? + * + * @return bool + */ + protected function resultScrollerActive() + { + $config = $this->serviceLocator->get(\VuFind\Config\PluginManager::class)->get('WorldCat2'); + return $config->Record->next_prev_navigation ?? false; + } + + /** + * Search action -- call standard results action + * + * @return mixed + */ + public function searchAction() + { + return $this->resultsAction(); + } +} diff --git a/module/VuFind/src/VuFind/Controller/WorldcatrecordController.php b/module/VuFind/src/VuFind/Controller/Worldcat2recordController.php similarity index 85% rename from module/VuFind/src/VuFind/Controller/WorldcatrecordController.php rename to module/VuFind/src/VuFind/Controller/Worldcat2recordController.php index 8ec083c1b4e..15fca6b28ab 100644 --- a/module/VuFind/src/VuFind/Controller/WorldcatrecordController.php +++ b/module/VuFind/src/VuFind/Controller/Worldcat2recordController.php @@ -1,11 +1,11 @@ <?php /** - * WorldCat Record Controller + * WorldCat v2 Record Controller * * PHP version 8 * - * Copyright (C) Villanova University 2010. + * Copyright (C) Villanova University 2024. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -32,7 +32,7 @@ use Laminas\ServiceManager\ServiceLocatorInterface; /** - * WorldCat Record Controller + * WorldCat v2 Record Controller * * @category VuFind * @package Controller @@ -40,7 +40,7 @@ * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License * @link https://vufind.org Main Site */ -class WorldcatrecordController extends AbstractRecord +class Worldcat2recordController extends AbstractRecord { /** * Constructor @@ -50,7 +50,7 @@ class WorldcatrecordController extends AbstractRecord public function __construct(ServiceLocatorInterface $sm) { // Override some defaults: - $this->sourceId = 'WorldCat'; + $this->sourceId = 'WorldCat2'; // Call standard record controller initialization: parent::__construct($sm); @@ -63,7 +63,7 @@ public function __construct(ServiceLocatorInterface $sm) */ protected function resultScrollerActive() { - $config = $this->getService(\VuFind\Config\PluginManager::class)->get('WorldCat'); + $config = $this->serviceLocator->get(\VuFind\Config\PluginManager::class)->get('WorldCat2'); return $config->Record->next_prev_navigation ?? false; } } diff --git a/module/VuFind/src/VuFind/Controller/WorldcatController.php b/module/VuFind/src/VuFind/Controller/WorldcatController.php index 8a2725a4f23..b3e931d1854 100644 --- a/module/VuFind/src/VuFind/Controller/WorldcatController.php +++ b/module/VuFind/src/VuFind/Controller/WorldcatController.php @@ -1,11 +1,11 @@ <?php /** - * WorldCat Controller + * WorldCat Controller (legacy -- redirects to WorldCat v2 controller) * * PHP version 8 * - * Copyright (C) Villanova University 2010. + * Copyright (C) Villanova University 2024. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -29,10 +29,8 @@ namespace VuFind\Controller; -use Laminas\ServiceManager\ServiceLocatorInterface; - /** - * WorldCat Controller + * WorldCat Controller (legacy -- redirects to WorldCat v2 controller) * * @category VuFind * @package Controller @@ -40,37 +38,42 @@ * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License * @link https://vufind.org Main Site */ -class WorldcatController extends AbstractSearch +class WorldcatController extends AbstractBase { /** - * Constructor + * Home action -- redirect to WorldCat v2. * - * @param ServiceLocatorInterface $sm Service locator + * @return mixed */ - public function __construct(ServiceLocatorInterface $sm) + public function homeAction() { - $this->searchClassId = 'WorldCat'; - parent::__construct($sm); + return $this->redirect()->toRoute('worldcat2-home'); } /** - * Is the result scroller active? + * Advanced search action -- redirect to WorldCat v2. * - * @return bool + * @return mixed */ - protected function resultScrollerActive() + public function advancedAction() { - $config = $this->getService(\VuFind\Config\PluginManager::class)->get('WorldCat'); - return $config->Record->next_prev_navigation ?? false; + return $this->redirect()->toRoute('worldcat2-advanced'); } /** - * Search action -- call standard results action + * Search action -- transform search and redirect to WorldCat v2. * * @return mixed */ public function searchAction() { - return $this->resultsAction(); + $params = $this->params()->fromQuery(); + // v1 types are prefixed with "srw." but v2 types are not; convert! + foreach ($params as $key => $value) { + if (str_starts_with($key, 'type')) { + $params[$key] = str_replace('srw.', '', $value); + } + } + return $this->redirect()->toRoute('worldcat2-search', options: ['query' => $params]); } } diff --git a/module/VuFind/src/VuFind/Cookie/CookieManager.php b/module/VuFind/src/VuFind/Cookie/CookieManager.php index b0e3a6a5c42..a94e8d9fb7a 100644 --- a/module/VuFind/src/VuFind/Cookie/CookieManager.php +++ b/module/VuFind/src/VuFind/Cookie/CookieManager.php @@ -104,7 +104,7 @@ class CookieManager * @param ?string $sessionName Session cookie name (if null defaults to PHP * settings) * @param bool $httpOnly Are cookies HTTP only? (default = true) - * @param string $sameSite Default SameSite attribute (defaut = 'Lax') + * @param string $sameSite Default SameSite attribute (default = 'Lax') */ public function __construct( $cookies, diff --git a/module/VuFind/src/VuFind/Crypt/PasswordHasher.php b/module/VuFind/src/VuFind/Crypt/PasswordHasher.php new file mode 100644 index 00000000000..65b7481a551 --- /dev/null +++ b/module/VuFind/src/VuFind/Crypt/PasswordHasher.php @@ -0,0 +1,90 @@ +<?php + +/** + * Password hasher + * + * This class was developed to replace the deprecated \Laminas\Crypt\Password\Bcrypt + * class. Its default behavior is inspired by that earlier class. + * + * PHP version 8 + * + * Copyright (C) Villanova University 2024. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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 + * + * @category VuFind + * @package Crypt + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ + +namespace VuFind\Crypt; + +use function password_hash; +use function password_verify; + +use const PASSWORD_BCRYPT; + +/** + * Password hasher + * + * @category VuFind + * @package Crypt + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ +class PasswordHasher +{ + /** + * Algorithm to use for hashing + * + * @var string + */ + protected string $algorithm = PASSWORD_BCRYPT; + + /** + * Cost of hashing + * + * @var int + */ + protected int $cost = 10; + + /** + * Create a hash from a password + * + * @param string $password Password to hash + * + * @return string + */ + public function create(string $password): string + { + $options = ['cost' => $this->cost]; + return password_hash($password, $this->algorithm, $options); + } + + /** + * Does the provided password match the provided hash value? + * + * @param string $password Password to check + * @param string $hash Hash to compare against + * + * @return bool + */ + public function verify(string $password, string $hash): bool + { + return password_verify($password, $hash); + } +} diff --git a/module/VuFind/src/VuFind/Db/AdapterFactory.php b/module/VuFind/src/VuFind/Db/AdapterFactory.php index 9d5d623f9f2..339ca2d9e4a 100644 --- a/module/VuFind/src/VuFind/Db/AdapterFactory.php +++ b/module/VuFind/src/VuFind/Db/AdapterFactory.php @@ -36,6 +36,7 @@ use Laminas\ServiceManager\Exception\ServiceNotFoundException; use Psr\Container\ContainerExceptionInterface as ContainerException; use Psr\Container\ContainerInterface; +use VuFind\Config\Feature\SecretTrait; /** * Database utility class. May be used as a service or as a standard @@ -49,6 +50,8 @@ */ class AdapterFactory implements \Laminas\ServiceManager\Factory\FactoryInterface { + use SecretTrait; + /** * VuFind configuration * @@ -106,15 +109,19 @@ public function __invoke( */ public function getAdapter($overrideUser = null, $overridePass = null) { - // Parse details from connection string: - if (!isset($this->config->Database->database)) { - throw new \Exception('"database" setting missing'); + if (isset($this->config->Database->database)) { + // Parse details from connection string: + return $this->getAdapterFromConnectionString( + $this->config->Database->database, + $overrideUser, + $overridePass + ); + } else { + return $this->getAdapterFromConfig( + $overrideUser, + $overridePass + ); } - return $this->getAdapterFromConnectionString( - $this->config->Database->database, - $overrideUser, - $overridePass - ); } /** @@ -233,26 +240,68 @@ public function getAdapterFromConnectionString( $username = $overrideUser ?? $username; $password = $overridePass ?? $password; - $driverName = $this->getDriverName($type); - $driverOptions = $this->getDriverOptions($driverName); - - // Set up default options: - $options = [ - 'driver' => $driverName, + return $this->getAdapterFromArray([ + 'driver' => $type, 'hostname' => $host, 'username' => $username, 'password' => $password, 'database' => $dbName, 'use_ssl' => $this->config->Database->use_ssl ?? false, - 'driver_options' => $driverOptions, + 'port' => $port ?? null, + ]); + } + + /** + * Obtain a Laminas\DB connection using the config. + * + * @param string $overrideUser Username override (leave null to use username from config) + * @param string $overridePass Password override (leave null to use password from config) + * + * @return Adapter + */ + public function getAdapterFromConfig($overrideUser = null, $overridePass = null) + { + if (!isset($this->config->Database)) { + throw new \Exception('[Database] Configuration section missing'); + } + $config = $this->config->Database; + return $this->getAdapterFromArray([ + 'driver' => $config->database_driver ?? null, + 'hostname' => $config->database_host ?? null, + 'username' => $overrideUser ?? $config->database_username ?? null, + 'password' => $overridePass ?? $this->getSecretFromConfig($config, 'database_password'), + 'database' => $config->database_name, + 'port' => $config->database_port ?? null, + ]); + } + + /** + * Obtain a Laminas\DB connection using a set of given element. + * + * @param array $config Config array to connect to the DB containing + * driver (ie: mysql), username, password, hostname, database (db name), port + * + * @return Adapter + */ + public function getAdapterFromArray(array $config) + { + $driverName = $this->getDriverName($config['driver']); + + // Set up default options: + $options = [ + 'driver' => $driverName, + 'hostname' => $config['hostname'] ?? null, + 'username' => $config['username'] ?? null, + 'password' => $config['password'] ?? null, + 'database' => $config['database'] ?? null, + 'use_ssl' => $this->config->Database->use_ssl ?? false, + 'driver_options' => $this->getDriverOptions($driverName), ]; - if (!empty($port)) { - $options['port'] = $port; + if (isset($config['port'])) { + $options['port'] = $config['port']; } // Get extra custom options from config: - $extraOptions = isset($this->config->Database->extra_options) - ? $this->config->Database->extra_options->toArray() - : []; + $extraOptions = $this->config?->Database?->extra_options?->toArray() ?? []; // Note: $options takes precedence over $extraOptions -- we don't want users // using extended settings to override values from core settings. return $this->getAdapterFromOptions($options + $extraOptions); diff --git a/module/VuFind/src/VuFind/Db/Entity/AccessTokenEntityInterface.php b/module/VuFind/src/VuFind/Db/Entity/AccessTokenEntityInterface.php index 01468cfe62e..baa8bd89cfb 100644 --- a/module/VuFind/src/VuFind/Db/Entity/AccessTokenEntityInterface.php +++ b/module/VuFind/src/VuFind/Db/Entity/AccessTokenEntityInterface.php @@ -45,18 +45,18 @@ interface AccessTokenEntityInterface extends EntityInterface * * @param ?UserEntityInterface $user User owning token * - * @return AccessTokenEntityInterface + * @return static */ - public function setUser(?UserEntityInterface $user): AccessTokenEntityInterface; + public function setUser(?UserEntityInterface $user): static; /** * Set data. * * @param string $data Data * - * @return AccessTokenEntityInterface + * @return static */ - public function setData(string $data): AccessTokenEntityInterface; + public function setData(string $data): static; /** * Is the access token revoked? @@ -70,7 +70,7 @@ public function isRevoked(): bool; * * @param bool $revoked Revoked * - * @return AccessTokenEntityInterface + * @return static */ - public function setRevoked(bool $revoked): AccessTokenEntityInterface; + public function setRevoked(bool $revoked): static; } diff --git a/module/VuFind/src/VuFind/Db/Entity/AuthHashEntityInterface.php b/module/VuFind/src/VuFind/Db/Entity/AuthHashEntityInterface.php index 36b8cea943e..4d9ed61751d 100644 --- a/module/VuFind/src/VuFind/Db/Entity/AuthHashEntityInterface.php +++ b/module/VuFind/src/VuFind/Db/Entity/AuthHashEntityInterface.php @@ -61,9 +61,9 @@ public function getSessionId(): ?string; * * @param ?string $sessionId PHP Session id string * - * @return AuthHashEntityInterface + * @return static */ - public function setSessionId(?string $sessionId): AuthHashEntityInterface; + public function setSessionId(?string $sessionId): static; /** * Get hash value. @@ -77,9 +77,9 @@ public function getHash(): string; * * @param string $hash Hash Value * - * @return AuthHashEntityInterface + * @return static */ - public function setHash(string $hash): AuthHashEntityInterface; + public function setHash(string $hash): static; /** * Get type of hash. @@ -93,9 +93,9 @@ public function getHashType(): ?string; * * @param ?string $type Hash Type * - * @return AuthHashEntityInterface + * @return static */ - public function setHashType(?string $type): AuthHashEntityInterface; + public function setHashType(?string $type): static; /** * Get data. @@ -109,9 +109,9 @@ public function getData(): ?string; * * @param ?string $data Data * - * @return AuthHashEntityInterface + * @return static */ - public function setData(?string $data): AuthHashEntityInterface; + public function setData(?string $data): static; /** * Get created date. @@ -125,7 +125,7 @@ public function getCreated(): DateTime; * * @param DateTime $dateTime Created date * - * @return AuthHashEntityInterface + * @return static */ - public function setCreated(DateTime $dateTime): AuthHashEntityInterface; + public function setCreated(DateTime $dateTime): static; } diff --git a/module/VuFind/src/VuFind/Db/Entity/ChangeTrackerEntityInterface.php b/module/VuFind/src/VuFind/Db/Entity/ChangeTrackerEntityInterface.php index 9dc5559c092..90afc00a5d2 100644 --- a/module/VuFind/src/VuFind/Db/Entity/ChangeTrackerEntityInterface.php +++ b/module/VuFind/src/VuFind/Db/Entity/ChangeTrackerEntityInterface.php @@ -47,9 +47,9 @@ interface ChangeTrackerEntityInterface extends EntityInterface * * @param string $id Id * - * @return ChangeTrackerEntityInterface + * @return static */ - public function setId(string $id): ChangeTrackerEntityInterface; + public function setId(string $id): static; /** * Getter for identifier. @@ -63,9 +63,9 @@ public function getId(): string; * * @param string $name Index name * - * @return ChangeTrackerEntityInterface + * @return static */ - public function setIndexName(string $name): ChangeTrackerEntityInterface; + public function setIndexName(string $name): static; /** * Getter for index name (formerly core). @@ -79,9 +79,9 @@ public function getIndexName(): string; * * @param ?DateTime $dateTime Time first added to index. * - * @return ChangeTrackerEntityInterface + * @return static */ - public function setFirstIndexed(?DateTime $dateTime): ChangeTrackerEntityInterface; + public function setFirstIndexed(?DateTime $dateTime): static; /** * FirstIndexed getter. @@ -95,9 +95,9 @@ public function getFirstIndexed(): ?DateTime; * * @param ?DateTime $dateTime Last time changed in index. * - * @return ChangeTrackerEntityInterface + * @return static */ - public function setLastIndexed(?DateTime $dateTime): ChangeTrackerEntityInterface; + public function setLastIndexed(?DateTime $dateTime): static; /** * LastIndexed getter. @@ -111,9 +111,9 @@ public function getLastIndexed(): ?DateTime; * * @param ?DateTime $dateTime Last time original record was edited * - * @return ChangeTrackerEntityInterface + * @return static */ - public function setLastRecordChange(?DateTime $dateTime): ChangeTrackerEntityInterface; + public function setLastRecordChange(?DateTime $dateTime): static; /** * LastRecordChange getter. @@ -127,9 +127,9 @@ public function getLastRecordChange(): ?DateTime; * * @param ?DateTime $dateTime Time record was removed from index * - * @return ChangeTrackerEntityInterface + * @return static */ - public function setDeleted(?DateTime $dateTime): ChangeTrackerEntityInterface; + public function setDeleted(?DateTime $dateTime): static; /** * Deleted getter. diff --git a/module/VuFind/src/VuFind/Db/Entity/CommentsEntityInterface.php b/module/VuFind/src/VuFind/Db/Entity/CommentsEntityInterface.php index aaaf17680b8..892c68632a7 100644 --- a/module/VuFind/src/VuFind/Db/Entity/CommentsEntityInterface.php +++ b/module/VuFind/src/VuFind/Db/Entity/CommentsEntityInterface.php @@ -54,9 +54,9 @@ public function getId(): int; * * @param string $comment Comment * - * @return Comments + * @return static */ - public function setComment(string $comment): CommentsEntityInterface; + public function setComment(string $comment): static; /** * Comment getter @@ -70,9 +70,9 @@ public function getComment(): string; * * @param DateTime $dateTime Created date * - * @return Comments + * @return static */ - public function setCreated(DateTime $dateTime): CommentsEntityInterface; + public function setCreated(DateTime $dateTime): static; /** * Created getter @@ -86,9 +86,9 @@ public function getCreated(): DateTime; * * @param ?UserEntityInterface $user User that created comment * - * @return Comments + * @return static */ - public function setUser(?UserEntityInterface $user): CommentsEntityInterface; + public function setUser(?UserEntityInterface $user): static; /** * User getter @@ -102,7 +102,7 @@ public function getUser(): ?UserEntityInterface; * * @param ResourceEntityInterface $resource Resource id. * - * @return Comments + * @return static */ - public function setResource(ResourceEntityInterface $resource): CommentsEntityInterface; + public function setResource(ResourceEntityInterface $resource): static; } diff --git a/module/VuFind/src/VuFind/Db/Entity/ExternalSessionEntityInterface.php b/module/VuFind/src/VuFind/Db/Entity/ExternalSessionEntityInterface.php index b71efd39945..5ef2136f25d 100644 --- a/module/VuFind/src/VuFind/Db/Entity/ExternalSessionEntityInterface.php +++ b/module/VuFind/src/VuFind/Db/Entity/ExternalSessionEntityInterface.php @@ -61,9 +61,9 @@ public function getSessionId(): string; * * @param string $sessionId PHP session id string * - * @return ExternalSessionEntityInterface + * @return static */ - public function setSessionId(string $sessionId): ExternalSessionEntityInterface; + public function setSessionId(string $sessionId): static; /** * Get external session id string. @@ -77,9 +77,9 @@ public function getExternalSessionId(): string; * * @param string $externalSessionId External session id string * - * @return ExternalSessionEntityInterface + * @return static */ - public function setExternalSessionId(string $externalSessionId): ExternalSessionEntityInterface; + public function setExternalSessionId(string $externalSessionId): static; /** * Get created date. @@ -93,7 +93,7 @@ public function getCreated(): DateTime; * * @param DateTime $dateTime Created date * - * @return ExternalSessionEntityInterface + * @return static */ - public function setCreated(DateTime $dateTime): ExternalSessionEntityInterface; + public function setCreated(DateTime $dateTime): static; } diff --git a/module/VuFind/src/VuFind/Db/Entity/FeedbackEntityInterface.php b/module/VuFind/src/VuFind/Db/Entity/FeedbackEntityInterface.php index b7f91118c4b..4f75f29fa77 100644 --- a/module/VuFind/src/VuFind/Db/Entity/FeedbackEntityInterface.php +++ b/module/VuFind/src/VuFind/Db/Entity/FeedbackEntityInterface.php @@ -54,9 +54,9 @@ public function getId(): int; * * @param string $message Message * - * @return FeedbackEntityInterface + * @return static */ - public function setMessage(string $message): FeedbackEntityInterface; + public function setMessage(string $message): static; /** * Message getter @@ -70,9 +70,9 @@ public function getMessage(): string; * * @param array $data Form data * - * @return FeedbackEntityInterface + * @return static */ - public function setFormData(array $data): FeedbackEntityInterface; + public function setFormData(array $data): static; /** * Form data getter @@ -86,9 +86,9 @@ public function getFormData(): array; * * @param string $name Form name * - * @return FeedbackEntityInterface + * @return static */ - public function setFormName(string $name): FeedbackEntityInterface; + public function setFormName(string $name): static; /** * Form name getter @@ -102,9 +102,9 @@ public function getFormName(): string; * * @param DateTime $dateTime Created date * - * @return FeedbackEntityInterface + * @return static */ - public function setCreated(DateTime $dateTime): FeedbackEntityInterface; + public function setCreated(DateTime $dateTime): static; /** * Created getter @@ -118,9 +118,9 @@ public function getCreated(): DateTime; * * @param DateTime $dateTime Last update date * - * @return FeedbackEntityInterface + * @return static */ - public function setUpdated(DateTime $dateTime): FeedbackEntityInterface; + public function setUpdated(DateTime $dateTime): static; /** * Updated getter @@ -134,9 +134,9 @@ public function getUpdated(): DateTime; * * @param string $status Status * - * @return FeedbackEntityInterface + * @return static */ - public function setStatus(string $status): FeedbackEntityInterface; + public function setStatus(string $status): static; /** * Status getter @@ -150,9 +150,9 @@ public function getStatus(): string; * * @param string $url Site URL * - * @return FeedbackEntityInterface + * @return static */ - public function setSiteUrl(string $url): FeedbackEntityInterface; + public function setSiteUrl(string $url): static; /** * Site URL getter @@ -166,9 +166,9 @@ public function getSiteUrl(): string; * * @param ?UserEntityInterface $user User that created request * - * @return FeedbackEntityInterface + * @return static */ - public function setUser(?UserEntityInterface $user): FeedbackEntityInterface; + public function setUser(?UserEntityInterface $user): static; /** * User getter @@ -182,9 +182,9 @@ public function getUser(): ?UserEntityInterface; * * @param ?UserEntityInterface $user User that updated request * - * @return FeedbackEntityInterface + * @return static */ - public function setUpdatedBy(?UserEntityInterface $user): FeedbackEntityInterface; + public function setUpdatedBy(?UserEntityInterface $user): static; /** * Updatedby getter diff --git a/module/VuFind/src/VuFind/Db/Entity/LoginTokenEntityInterface.php b/module/VuFind/src/VuFind/Db/Entity/LoginTokenEntityInterface.php index 23809fb3c42..b6be0b860aa 100644 --- a/module/VuFind/src/VuFind/Db/Entity/LoginTokenEntityInterface.php +++ b/module/VuFind/src/VuFind/Db/Entity/LoginTokenEntityInterface.php @@ -54,9 +54,9 @@ public function getId(): int; * * @param UserEntityInterface $user User to set * - * @return LoginTokenEntityInterface + * @return static */ - public function setUser(UserEntityInterface $user): LoginTokenEntityInterface; + public function setUser(UserEntityInterface $user): static; /** * User getter (only null if entity has not been populated yet). @@ -70,9 +70,9 @@ public function getUser(): ?UserEntityInterface; * * @param string $token Token * - * @return LoginTokenEntityInterface + * @return static */ - public function setToken(string $token): LoginTokenEntityInterface; + public function setToken(string $token): static; /** * Get token string. @@ -86,9 +86,9 @@ public function getToken(): string; * * @param string $series Series * - * @return LoginTokenEntityInterface + * @return static */ - public function setSeries(string $series): LoginTokenEntityInterface; + public function setSeries(string $series): static; /** * Get series string. @@ -102,9 +102,9 @@ public function getSeries(): string; * * @param DateTime $dateTime Last login date/time * - * @return LoginTokenEntityInterface + * @return static */ - public function setLastLogin(DateTime $dateTime): LoginTokenEntityInterface; + public function setLastLogin(DateTime $dateTime): static; /** * Get last login date/time. @@ -118,9 +118,9 @@ public function getLastLogin(): DateTime; * * @param ?string $browser Browser details (or null for none) * - * @return LoginTokenEntityInterface + * @return static */ - public function setBrowser(?string $browser): LoginTokenEntityInterface; + public function setBrowser(?string $browser): static; /** * Get browser details (or null for none). @@ -134,9 +134,9 @@ public function getBrowser(): ?string; * * @param ?string $platform Platform details (or null for none) * - * @return LoginTokenEntityInterface + * @return static */ - public function setPlatform(?string $platform): LoginTokenEntityInterface; + public function setPlatform(?string $platform): static; /** * Get platform details (or null for none). @@ -150,9 +150,9 @@ public function getPlatform(): ?string; * * @param int $expires Expiration timestamp * - * @return LoginTokenEntityInterface + * @return static */ - public function setExpires(int $expires): LoginTokenEntityInterface; + public function setExpires(int $expires): static; /** * Get expiration timestamp. @@ -166,9 +166,9 @@ public function getExpires(): int; * * @param ?string $sid Last session ID (or null for none) * - * @return LoginTokenEntityInterface + * @return static */ - public function setLastSessionId(?string $sid): LoginTokenEntityInterface; + public function setLastSessionId(?string $sid): static; /** * Get last session ID (or null for none). diff --git a/module/VuFind/src/VuFind/Db/Entity/OaiResumptionEntityInterface.php b/module/VuFind/src/VuFind/Db/Entity/OaiResumptionEntityInterface.php index 5da2323329a..84287e68e3f 100644 --- a/module/VuFind/src/VuFind/Db/Entity/OaiResumptionEntityInterface.php +++ b/module/VuFind/src/VuFind/Db/Entity/OaiResumptionEntityInterface.php @@ -54,9 +54,9 @@ public function getId(): int; * * @param ?string $params Resumption parameters. * - * @return OaiResumptionEntityInterface + * @return static */ - public function setResumptionParameters(?string $params): OaiResumptionEntityInterface; + public function setResumptionParameters(?string $params): static; /** * Get resumption parameters. @@ -70,9 +70,9 @@ public function getResumptionParameters(): ?string; * * @param DateTime $dateTime Expiration date * - * @return OaiResumptionEntityInterface + * @return static */ - public function setExpiry(DateTime $dateTime): OaiResumptionEntityInterface; + public function setExpiry(DateTime $dateTime): static; /** * Get expiry date. diff --git a/module/VuFind/src/VuFind/Db/Entity/RatingsEntityInterface.php b/module/VuFind/src/VuFind/Db/Entity/RatingsEntityInterface.php index b23317960c0..0e520febcd5 100644 --- a/module/VuFind/src/VuFind/Db/Entity/RatingsEntityInterface.php +++ b/module/VuFind/src/VuFind/Db/Entity/RatingsEntityInterface.php @@ -61,9 +61,9 @@ public function getUser(): ?UserEntityInterface; * * @param ?UserEntityInterface $user User * - * @return RatingsEntityInterface + * @return static */ - public function setUser(?UserEntityInterface $user): RatingsEntityInterface; + public function setUser(?UserEntityInterface $user): static; /** * Get resource. @@ -77,9 +77,9 @@ public function getResource(): ResourceEntityInterface; * * @param ResourceEntityInterface $resource Resource * - * @return RatingsEntityInterface + * @return static */ - public function setResource(ResourceEntityInterface $resource): RatingsEntityInterface; + public function setResource(ResourceEntityInterface $resource): static; /** * Get rating. @@ -93,9 +93,9 @@ public function getRating(): int; * * @param int $rating Rating * - * @return RatingsEntityInterface + * @return static */ - public function setRating(int $rating): RatingsEntityInterface; + public function setRating(int $rating): static; /** * Get created date. @@ -109,7 +109,7 @@ public function getCreated(): DateTime; * * @param DateTime $dateTime Created date * - * @return RatingsEntityInterface + * @return static */ - public function setCreated(DateTime $dateTime): RatingsEntityInterface; + public function setCreated(DateTime $dateTime): static; } diff --git a/module/VuFind/src/VuFind/Db/Entity/RecordEntityInterface.php b/module/VuFind/src/VuFind/Db/Entity/RecordEntityInterface.php index 63f2cd4d793..ee4ff96aefc 100644 --- a/module/VuFind/src/VuFind/Db/Entity/RecordEntityInterface.php +++ b/module/VuFind/src/VuFind/Db/Entity/RecordEntityInterface.php @@ -61,9 +61,9 @@ public function getRecordId(): ?string; * * @param ?string $recordId Record id * - * @return RecordEntityInterface + * @return static */ - public function setRecordId(?string $recordId): RecordEntityInterface; + public function setRecordId(?string $recordId): static; /** * Get record source. @@ -77,9 +77,9 @@ public function getSource(): ?string; * * @param ?string $recordSource Record source * - * @return RecordEntityInterface + * @return static */ - public function setSource(?string $recordSource): RecordEntityInterface; + public function setSource(?string $recordSource): static; /** * Get record version. @@ -93,9 +93,9 @@ public function getVersion(): string; * * @param string $recordVersion Record version * - * @return RecordEntityInterface + * @return static */ - public function setVersion(string $recordVersion): RecordEntityInterface; + public function setVersion(string $recordVersion): static; /** * Get record data. @@ -109,9 +109,9 @@ public function getData(): ?string; * * @param ?string $recordData Record data * - * @return RecordEntityInterface + * @return static */ - public function setData(?string $recordData): RecordEntityInterface; + public function setData(?string $recordData): static; /** * Get updated date. @@ -125,7 +125,7 @@ public function getUpdated(): DateTime; * * @param DateTime $dateTime Updated date * - * @return RecordEntityInterface + * @return static */ - public function setUpdated(DateTime $dateTime): RecordEntityInterface; + public function setUpdated(DateTime $dateTime): static; } diff --git a/module/VuFind/src/VuFind/Db/Entity/ResourceEntityInterface.php b/module/VuFind/src/VuFind/Db/Entity/ResourceEntityInterface.php index 0a948ee7ba7..bf7dbae1e85 100644 --- a/module/VuFind/src/VuFind/Db/Entity/ResourceEntityInterface.php +++ b/module/VuFind/src/VuFind/Db/Entity/ResourceEntityInterface.php @@ -52,9 +52,9 @@ public function getId(): int; * * @param string $recordId recordId * - * @return ResourceEntityInterface + * @return static */ - public function setRecordId(string $recordId): ResourceEntityInterface; + public function setRecordId(string $recordId): static; /** * Record Id getter @@ -68,9 +68,9 @@ public function getRecordId(): string; * * @param string $title Title of the record. * - * @return ResourceEntityInterface + * @return static */ - public function setTitle(string $title): ResourceEntityInterface; + public function setTitle(string $title): static; /** * Title getter @@ -84,27 +84,27 @@ public function getTitle(): string; * * @param ?string $author Author of the title. * - * @return ResourceEntityInterface + * @return static */ - public function setAuthor(?string $author): ResourceEntityInterface; + public function setAuthor(?string $author): static; /** * Year setter * * @param ?int $year Year title is published. * - * @return ResourceEntityInterface + * @return static */ - public function setYear(?int $year): ResourceEntityInterface; + public function setYear(?int $year): static; /** * Source setter * * @param string $source Source (a search backend ID). * - * @return ResourceEntityInterface + * @return static */ - public function setSource(string $source): ResourceEntityInterface; + public function setSource(string $source): static; /** * Source getter @@ -118,9 +118,9 @@ public function getSource(): string; * * @param ?string $extraMetadata ExtraMetadata. * - * @return ResourceEntityInterface + * @return static */ - public function setExtraMetadata(?string $extraMetadata): ResourceEntityInterface; + public function setExtraMetadata(?string $extraMetadata): static; /** * Extra Metadata getter diff --git a/module/VuFind/src/VuFind/Db/Entity/ResourceTagsEntityInterface.php b/module/VuFind/src/VuFind/Db/Entity/ResourceTagsEntityInterface.php index dae2e9efa7a..cf307fac330 100644 --- a/module/VuFind/src/VuFind/Db/Entity/ResourceTagsEntityInterface.php +++ b/module/VuFind/src/VuFind/Db/Entity/ResourceTagsEntityInterface.php @@ -61,9 +61,9 @@ public function getResource(): ?ResourceEntityInterface; * * @param ?ResourceEntityInterface $resource Resource * - * @return ResourceTagsEntityInterface + * @return static */ - public function setResource(?ResourceEntityInterface $resource): ResourceTagsEntityInterface; + public function setResource(?ResourceEntityInterface $resource): static; /** * Get tag. @@ -77,9 +77,9 @@ public function getTag(): TagsEntityInterface; * * @param TagsEntityInterface $tag Tag * - * @return ResourceTagsEntityInterface + * @return static */ - public function setTag(TagsEntityInterface $tag): ResourceTagsEntityInterface; + public function setTag(TagsEntityInterface $tag): static; /** * Get user list. @@ -93,9 +93,9 @@ public function getUserList(): ?UserListEntityInterface; * * @param ?UserListEntityInterface $list User list * - * @return ResourceTagsEntityInterface + * @return static */ - public function setUserList(?UserListEntityInterface $list): ResourceTagsEntityInterface; + public function setUserList(?UserListEntityInterface $list): static; /** * Get user. @@ -109,9 +109,9 @@ public function getUser(): ?UserEntityInterface; * * @param ?UserEntityInterface $user User * - * @return ResourceTagsEntityInterface + * @return static */ - public function setUser(?UserEntityInterface $user): ResourceTagsEntityInterface; + public function setUser(?UserEntityInterface $user): static; /** * Get created date. @@ -125,7 +125,7 @@ public function getPosted(): DateTime; * * @param DateTime $dateTime Created date * - * @return ResourceTagsEntityInterface + * @return static */ - public function setPosted(DateTime $dateTime): ResourceTagsEntityInterface; + public function setPosted(DateTime $dateTime): static; } diff --git a/module/VuFind/src/VuFind/Db/Entity/SearchEntityInterface.php b/module/VuFind/src/VuFind/Db/Entity/SearchEntityInterface.php index ca6bf3a8cec..70e45ac46b8 100644 --- a/module/VuFind/src/VuFind/Db/Entity/SearchEntityInterface.php +++ b/module/VuFind/src/VuFind/Db/Entity/SearchEntityInterface.php @@ -61,9 +61,9 @@ public function getUser(): ?UserEntityInterface; * * @param ?UserEntityInterface $user User * - * @return SearchEntityInterface + * @return static */ - public function setUser(?UserEntityInterface $user): SearchEntityInterface; + public function setUser(?UserEntityInterface $user): static; /** * Get session identifier. @@ -77,9 +77,9 @@ public function getSessionId(): ?string; * * @param ?string $sessionId Session id * - * @return SearchEntityInterface + * @return static */ - public function setSessionId(?string $sessionId): SearchEntityInterface; + public function setSessionId(?string $sessionId): static; /** * Get created date. @@ -93,9 +93,9 @@ public function getCreated(): DateTime; * * @param DateTime $dateTime Created date * - * @return SearchEntityInterface + * @return static */ - public function setCreated(DateTime $dateTime): SearchEntityInterface; + public function setCreated(DateTime $dateTime): static; /** * Get title. @@ -109,9 +109,9 @@ public function getTitle(): ?string; * * @param ?string $title Title * - * @return SearchEntityInterface + * @return static */ - public function setTitle(?string $title): SearchEntityInterface; + public function setTitle(?string $title): static; /** * Get saved. @@ -125,9 +125,9 @@ public function getSaved(): bool; * * @param bool $saved Saved * - * @return SearchEntityInterface + * @return static */ - public function setSaved(bool $saved): SearchEntityInterface; + public function setSaved(bool $saved): static; /** * Get the search object from the row. @@ -141,9 +141,9 @@ public function getSearchObject(): ?\VuFind\Search\Minified; * * @param ?\VuFind\Search\Minified $searchObject Search object * - * @return SearchEntityInterface + * @return static */ - public function setSearchObject(?\VuFind\Search\Minified $searchObject): SearchEntityInterface; + public function setSearchObject(?\VuFind\Search\Minified $searchObject): static; /** * Get checksum. @@ -157,9 +157,9 @@ public function getChecksum(): ?int; * * @param ?int $checksum Checksum * - * @return SearchEntityInterface + * @return static */ - public function setChecksum(?int $checksum): SearchEntityInterface; + public function setChecksum(?int $checksum): static; /** * Get notification frequency. @@ -173,9 +173,9 @@ public function getNotificationFrequency(): int; * * @param int $notificationFrequency Notification frequency * - * @return SearchEntityInterface + * @return static */ - public function setNotificationFrequency(int $notificationFrequency): SearchEntityInterface; + public function setNotificationFrequency(int $notificationFrequency): static; /** * When was the last notification sent? @@ -189,9 +189,9 @@ public function getLastNotificationSent(): DateTime; * * @param DateTime $lastNotificationSent Time when last notification was sent * - * @return SearchEntityInterface + * @return static */ - public function setLastNotificationSent(Datetime $lastNotificationSent): SearchEntityInterface; + public function setLastNotificationSent(Datetime $lastNotificationSent): static; /** * Get notification base URL. @@ -205,7 +205,7 @@ public function getNotificationBaseUrl(): string; * * @param string $notificationBaseUrl Notification base URL * - * @return SearchEntityInterface + * @return static */ - public function setNotificationBaseUrl(string $notificationBaseUrl): SearchEntityInterface; + public function setNotificationBaseUrl(string $notificationBaseUrl): static; } diff --git a/module/VuFind/src/VuFind/Db/Entity/SessionEntityInterface.php b/module/VuFind/src/VuFind/Db/Entity/SessionEntityInterface.php index 368d2784731..d5065709c4d 100644 --- a/module/VuFind/src/VuFind/Db/Entity/SessionEntityInterface.php +++ b/module/VuFind/src/VuFind/Db/Entity/SessionEntityInterface.php @@ -54,27 +54,27 @@ public function getId(): int; * * @param ?string $sid Session Id. * - * @return SessionEntityInterface + * @return static */ - public function setSessionId(?string $sid): SessionEntityInterface; + public function setSessionId(?string $sid): static; /** * Created setter. * * @param DateTime $dateTime Created date * - * @return SessionEntityInterface + * @return static */ - public function setCreated(DateTime $dateTime): SessionEntityInterface; + public function setCreated(DateTime $dateTime): static; /** * Set time the session is last used. * * @param int $lastUsed Time last used * - * @return SessionEntityInterface + * @return static */ - public function setLastUsed(int $lastUsed): SessionEntityInterface; + public function setLastUsed(int $lastUsed): static; /** * Get time when the session was last used. @@ -88,9 +88,9 @@ public function getLastUsed(): int; * * @param ?string $data Session data. * - * @return SessionEntityInterface + * @return static */ - public function setData(?string $data): SessionEntityInterface; + public function setData(?string $data): static; /** * Get session data. diff --git a/module/VuFind/src/VuFind/Db/Entity/ShortlinksEntityInterface.php b/module/VuFind/src/VuFind/Db/Entity/ShortlinksEntityInterface.php index e9992fd0e21..a0ad4947a5d 100644 --- a/module/VuFind/src/VuFind/Db/Entity/ShortlinksEntityInterface.php +++ b/module/VuFind/src/VuFind/Db/Entity/ShortlinksEntityInterface.php @@ -62,9 +62,9 @@ public function getPath(): string; * * @param string $path Path * - * @return ShortlinksEntityInterface + * @return static */ - public function setPath(string $path): ShortlinksEntityInterface; + public function setPath(string $path): static; /** * Get shortlinks hash. @@ -78,9 +78,9 @@ public function getHash(): ?string; * * @param ?string $hash Shortlinks hash * - * @return ShortlinksEntityInterface + * @return static */ - public function setHash(?string $hash): ShortlinksEntityInterface; + public function setHash(?string $hash): static; /** * Get creation timestamp. @@ -94,7 +94,7 @@ public function getCreated(): DateTime; * * @param DateTime $dateTime Creation timestamp * - * @return ShortlinksEntityInterface + * @return static */ - public function setCreated(DateTime $dateTime): ShortlinksEntityInterface; + public function setCreated(DateTime $dateTime): static; } diff --git a/module/VuFind/src/VuFind/Db/Entity/TagsEntityInterface.php b/module/VuFind/src/VuFind/Db/Entity/TagsEntityInterface.php index 5166d8aa1c0..430096035d5 100644 --- a/module/VuFind/src/VuFind/Db/Entity/TagsEntityInterface.php +++ b/module/VuFind/src/VuFind/Db/Entity/TagsEntityInterface.php @@ -52,9 +52,9 @@ public function getId(): int; * * @param string $tag Tag * - * @return TagsEntityInterface + * @return static */ - public function setTag(string $tag): TagsEntityInterface; + public function setTag(string $tag): static; /** * Tag getter diff --git a/module/VuFind/src/VuFind/Db/Entity/UserCardEntityInterface.php b/module/VuFind/src/VuFind/Db/Entity/UserCardEntityInterface.php index 08b965f5bd3..48f7a389dbb 100644 --- a/module/VuFind/src/VuFind/Db/Entity/UserCardEntityInterface.php +++ b/module/VuFind/src/VuFind/Db/Entity/UserCardEntityInterface.php @@ -54,9 +54,9 @@ public function getId(): ?int; * * @param string $cardName User card name. * - * @return UserCardEntityInterface + * @return static */ - public function setCardName(string $cardName): UserCardEntityInterface; + public function setCardName(string $cardName): static; /** * Get user card name. @@ -70,9 +70,9 @@ public function getCardName(): string; * * @param string $catUsername Catalog username * - * @return UserCardEntityInterface + * @return static */ - public function setCatUsername(string $catUsername): UserCardEntityInterface; + public function setCatUsername(string $catUsername): static; /** * Get catalog username. @@ -86,9 +86,9 @@ public function getCatUsername(): string; * * @param ?string $catPassword Cat password * - * @return UserCardEntityInterface + * @return static */ - public function setRawCatPassword(?string $catPassword): UserCardEntityInterface; + public function setRawCatPassword(?string $catPassword): static; /** * Get raw catalog password. @@ -102,9 +102,9 @@ public function getRawCatPassword(): ?string; * * @param ?string $passEnc Encrypted password * - * @return UserCardEntityInterface + * @return static */ - public function setCatPassEnc(?string $passEnc): UserCardEntityInterface; + public function setCatPassEnc(?string $passEnc): static; /** * Get encrypted catalog password. @@ -118,9 +118,9 @@ public function getCatPassEnc(): ?string; * * @param ?string $homeLibrary Home library * - * @return UserCardEntityInterface + * @return static */ - public function setHomeLibrary(?string $homeLibrary): UserCardEntityInterface; + public function setHomeLibrary(?string $homeLibrary): static; /** * Get home library. @@ -134,9 +134,9 @@ public function getHomeLibrary(): ?string; * * @param DateTime $dateTime Created date * - * @return UserCardEntityInterface + * @return static */ - public function setCreated(DateTime $dateTime): UserCardEntityInterface; + public function setCreated(DateTime $dateTime): static; /** * Get created date. @@ -150,9 +150,9 @@ public function getCreated(): DateTime; * * @param DateTime $dateTime Saved date and time * - * @return UserCardEntityInterface + * @return static */ - public function setSaved(DateTime $dateTime): UserCardEntityInterface; + public function setSaved(DateTime $dateTime): static; /** * Get saved time. @@ -166,9 +166,9 @@ public function getSaved(): DateTime; * * @param UserEntityInterface $user User that owns card * - * @return UserCardEntityInterface + * @return static */ - public function setUser(UserEntityInterface $user): UserCardEntityInterface; + public function setUser(UserEntityInterface $user): static; /** * User getter diff --git a/module/VuFind/src/VuFind/Db/Entity/UserEntityInterface.php b/module/VuFind/src/VuFind/Db/Entity/UserEntityInterface.php index 8a0752a8fb4..c7d1759c829 100644 --- a/module/VuFind/src/VuFind/Db/Entity/UserEntityInterface.php +++ b/module/VuFind/src/VuFind/Db/Entity/UserEntityInterface.php @@ -54,9 +54,9 @@ public function getId(): ?int; * * @param string $username Username * - * @return UserEntityInterface + * @return static */ - public function setUsername(string $username): UserEntityInterface; + public function setUsername(string $username): static; /** * Get username. @@ -70,9 +70,9 @@ public function getUsername(): string; * * @param string $password Password * - * @return UserEntityInterface + * @return static */ - public function setRawPassword(string $password): UserEntityInterface; + public function setRawPassword(string $password): static; /** * Get raw (unhashed) password (if available). This should only be used when hashing is disabled. @@ -86,9 +86,9 @@ public function getRawPassword(): string; * * @param ?string $hash Password hash * - * @return UserEntityInterface + * @return static */ - public function setPasswordHash(?string $hash): UserEntityInterface; + public function setPasswordHash(?string $hash): static; /** * Get hashed password. This should only be used when hashing is enabled. @@ -102,9 +102,9 @@ public function getPasswordHash(): ?string; * * @param string $firstName New first name * - * @return UserEntityInterface + * @return static */ - public function setFirstname(string $firstName): UserEntityInterface; + public function setFirstname(string $firstName): static; /** * Get firstname. @@ -118,9 +118,9 @@ public function getFirstname(): string; * * @param string $lastName New last name * - * @return UserEntityInterface + * @return static */ - public function setLastname(string $lastName): UserEntityInterface; + public function setLastname(string $lastName): static; /** * Get lastname. @@ -134,9 +134,9 @@ public function getLastname(): string; * * @param string $email Email address * - * @return UserEntityInterface + * @return static */ - public function setEmail(string $email): UserEntityInterface; + public function setEmail(string $email): static; /** * Get email. @@ -150,9 +150,9 @@ public function getEmail(): string; * * @param string $email New pending email * - * @return UserEntityInterface + * @return static */ - public function setPendingEmail(string $email): UserEntityInterface; + public function setPendingEmail(string $email): static; /** * Get pending email. @@ -166,9 +166,9 @@ public function getPendingEmail(): string; * * @param ?string $catId Catalog id * - * @return UserEntityInterface + * @return static */ - public function setCatId(?string $catId): UserEntityInterface; + public function setCatId(?string $catId): static; /** * Get catalog id. @@ -182,9 +182,9 @@ public function getCatId(): ?string; * * @param ?string $catUsername Catalog username * - * @return UserEntityInterface + * @return static */ - public function setCatUsername(?string $catUsername): UserEntityInterface; + public function setCatUsername(?string $catUsername): static; /** * Get catalog username. @@ -198,9 +198,9 @@ public function getCatUsername(): ?string; * * @param ?string $homeLibrary Home library * - * @return UserEntityInterface + * @return static */ - public function setHomeLibrary(?string $homeLibrary): UserEntityInterface; + public function setHomeLibrary(?string $homeLibrary): static; /** * Get home library. @@ -214,9 +214,9 @@ public function getHomeLibrary(): ?string; * * @param ?string $catPassword Cat password * - * @return UserEntityInterface + * @return static */ - public function setRawCatPassword(?string $catPassword): UserEntityInterface; + public function setRawCatPassword(?string $catPassword): static; /** * Get raw catalog password. @@ -230,9 +230,9 @@ public function getRawCatPassword(): ?string; * * @param ?string $passEnc Encrypted password * - * @return UserEntityInterface + * @return static */ - public function setCatPassEnc(?string $passEnc): UserEntityInterface; + public function setCatPassEnc(?string $passEnc): static; /** * Get encrypted catalog password. @@ -246,9 +246,9 @@ public function getCatPassEnc(): ?string; * * @param string $college College * - * @return UserEntityInterface + * @return static */ - public function setCollege(string $college): UserEntityInterface; + public function setCollege(string $college): static; /** * Get college. @@ -262,9 +262,9 @@ public function getCollege(): string; * * @param string $major Major * - * @return UserEntityInterface + * @return static */ - public function setMajor(string $major): UserEntityInterface; + public function setMajor(string $major): static; /** * Get major. @@ -278,9 +278,9 @@ public function getMajor(): string; * * @param string $hash Hash value to save * - * @return UserEntityInterface + * @return static */ - public function setVerifyHash(string $hash): UserEntityInterface; + public function setVerifyHash(string $hash): static; /** * Get verification hash for recovery. @@ -294,9 +294,9 @@ public function getVerifyHash(): string; * * @param ?string $authMethod New value (null for none) * - * @return UserEntityInterface + * @return static */ - public function setAuthMethod(?string $authMethod): UserEntityInterface; + public function setAuthMethod(?string $authMethod): static; /** * Get active authentication method (if any). @@ -310,9 +310,9 @@ public function getAuthMethod(): ?string; * * @param string $lang Last language * - * @return UserEntityInterface + * @return static */ - public function setLastLanguage(string $lang): UserEntityInterface; + public function setLastLanguage(string $lang): static; /** * Get last language. @@ -333,18 +333,18 @@ public function hasUserProvidedEmail(): bool; * * @param bool $userProvided New value * - * @return UserEntityInterface + * @return static */ - public function setHasUserProvidedEmail(bool $userProvided): UserEntityInterface; + public function setHasUserProvidedEmail(bool $userProvided): static; /** * Last login setter. * * @param DateTime $dateTime Last login date * - * @return UserEntityInterface + * @return static */ - public function setLastLogin(DateTime $dateTime): UserEntityInterface; + public function setLastLogin(DateTime $dateTime): static; /** * Last login getter @@ -358,9 +358,9 @@ public function getLastLogin(): DateTime; * * @param DateTime $dateTime Last login date * - * @return UserEntityInterface + * @return static */ - public function setCreated(DateTime $dateTime): UserEntityInterface; + public function setCreated(DateTime $dateTime): static; /** * Created getter @@ -374,9 +374,9 @@ public function getCreated(): Datetime; * * @param ?DateTime $dateTime Verification date (or null) * - * @return UserEntityInterface + * @return static */ - public function setEmailVerified(?DateTime $dateTime): UserEntityInterface; + public function setEmailVerified(?DateTime $dateTime): static; /** * Get email verification date (or null for unverified). diff --git a/module/VuFind/src/VuFind/Db/Entity/UserListEntityInterface.php b/module/VuFind/src/VuFind/Db/Entity/UserListEntityInterface.php index 6dd9ce2d692..a78c5179677 100644 --- a/module/VuFind/src/VuFind/Db/Entity/UserListEntityInterface.php +++ b/module/VuFind/src/VuFind/Db/Entity/UserListEntityInterface.php @@ -54,9 +54,9 @@ public function getId(): ?int; * * @param string $title Title * - * @return UserListEntityInterface + * @return static */ - public function setTitle(string $title): UserListEntityInterface; + public function setTitle(string $title): static; /** * Get title. @@ -70,9 +70,9 @@ public function getTitle(): string; * * @param ?string $description Description * - * @return UserListEntityInterface + * @return static */ - public function setDescription(?string $description): UserListEntityInterface; + public function setDescription(?string $description): static; /** * Get description. @@ -86,9 +86,9 @@ public function getDescription(): ?string; * * @param DateTime $dateTime Created date * - * @return UserListEntityInterface + * @return static */ - public function setCreated(DateTime $dateTime): UserListEntityInterface; + public function setCreated(DateTime $dateTime): static; /** * Get created date. @@ -102,9 +102,9 @@ public function getCreated(): DateTime; * * @param bool $public Is the list public? * - * @return UserListEntityInterface + * @return static */ - public function setPublic(bool $public): UserListEntityInterface; + public function setPublic(bool $public): static; /** * Is this a public list? @@ -118,9 +118,9 @@ public function isPublic(): bool; * * @param ?UserEntityInterface $user User owning the list. * - * @return UserListEntityInterface + * @return static */ - public function setUser(?UserEntityInterface $user): UserListEntityInterface; + public function setUser(?UserEntityInterface $user): static; /** * Get user. diff --git a/module/VuFind/src/VuFind/Db/Entity/UserResourceEntityInterface.php b/module/VuFind/src/VuFind/Db/Entity/UserResourceEntityInterface.php index 6a787b3601e..b9bdcf4f2ff 100644 --- a/module/VuFind/src/VuFind/Db/Entity/UserResourceEntityInterface.php +++ b/module/VuFind/src/VuFind/Db/Entity/UserResourceEntityInterface.php @@ -61,9 +61,9 @@ public function getUser(): UserEntityInterface; * * @param UserEntityInterface $user User * - * @return UserResourceEntityInterface + * @return static */ - public function setUser(UserEntityInterface $user): UserResourceEntityInterface; + public function setUser(UserEntityInterface $user): static; /** * Get resource. @@ -77,9 +77,9 @@ public function getResource(): ResourceEntityInterface; * * @param ResourceEntityInterface $resource Resource * - * @return UserResourceEntityInterface + * @return static */ - public function setResource(ResourceEntityInterface $resource): UserResourceEntityInterface; + public function setResource(ResourceEntityInterface $resource): static; /** * Get user list. @@ -93,9 +93,9 @@ public function getUserList(): ?UserListEntityInterface; * * @param ?UserListEntityInterface $list User list * - * @return UserResourceEntityInterface + * @return static */ - public function setUserList(?UserListEntityInterface $list): UserResourceEntityInterface; + public function setUserList(?UserListEntityInterface $list): static; /** * Get notes. @@ -109,9 +109,9 @@ public function getNotes(): ?string; * * @param ?string $notes Notes associated with the resource * - * @return UserResourceEntityInterface + * @return static */ - public function setNotes(?string $notes): UserResourceEntityInterface; + public function setNotes(?string $notes): static; /** * Get saved date. @@ -125,7 +125,7 @@ public function getSaved(): DateTime; * * @param DateTime $dateTime Created date * - * @return UserResourceEntityInterface + * @return static */ - public function setSaved(DateTime $dateTime): UserResourceEntityInterface; + public function setSaved(DateTime $dateTime): static; } diff --git a/module/VuFind/src/VuFind/Db/Row/AccessToken.php b/module/VuFind/src/VuFind/Db/Row/AccessToken.php index 08136871d61..f771064f8ba 100644 --- a/module/VuFind/src/VuFind/Db/Row/AccessToken.php +++ b/module/VuFind/src/VuFind/Db/Row/AccessToken.php @@ -59,9 +59,9 @@ public function __construct($adapter) * * @param ?UserEntityInterface $user User owning token * - * @return AccessTokenEntityInterface + * @return static */ - public function setUser(?UserEntityInterface $user): AccessTokenEntityInterface + public function setUser(?UserEntityInterface $user): static { $this->__set('user_id', $user?->getId()); return $this; @@ -72,9 +72,9 @@ public function setUser(?UserEntityInterface $user): AccessTokenEntityInterface * * @param string $data Data * - * @return AccessTokenEntityInterface + * @return static */ - public function setData(string $data): AccessTokenEntityInterface + public function setData(string $data): static { $this->__set('data', $data); return $this; @@ -95,9 +95,9 @@ public function isRevoked(): bool * * @param bool $revoked Revoked * - * @return AccessTokenEntityInterface + * @return static */ - public function setRevoked(bool $revoked): AccessTokenEntityInterface + public function setRevoked(bool $revoked): static { $this->__set('revoked', $revoked); return $this; diff --git a/module/VuFind/src/VuFind/Db/Row/AuthHash.php b/module/VuFind/src/VuFind/Db/Row/AuthHash.php index 5f340b829e4..de5a60da0ac 100644 --- a/module/VuFind/src/VuFind/Db/Row/AuthHash.php +++ b/module/VuFind/src/VuFind/Db/Row/AuthHash.php @@ -93,9 +93,9 @@ public function getSessionId(): ?string * * @param ?string $sessionId PHP Session id string * - * @return AuthHashEntityInterface + * @return static */ - public function setSessionId(?string $sessionId): AuthHashEntityInterface + public function setSessionId(?string $sessionId): static { $this->session_id = $sessionId; return $this; @@ -116,9 +116,9 @@ public function getHash(): string * * @param string $hash Hash Value * - * @return AuthHashEntityInterface + * @return static */ - public function setHash(string $hash): AuthHashEntityInterface + public function setHash(string $hash): static { $this->hash = $hash; return $this; @@ -139,9 +139,9 @@ public function getHashType(): ?string * * @param ?string $type Hash Type * - * @return AuthHashEntityInterface + * @return static */ - public function setHashType(?string $type): AuthHashEntityInterface + public function setHashType(?string $type): static { $this->type = $type; return $this; @@ -162,9 +162,9 @@ public function getData(): ?string * * @param ?string $data Data * - * @return AuthHashEntityInterface + * @return static */ - public function setData(?string $data): AuthHashEntityInterface + public function setData(?string $data): static { $this->__set('data', $data); return $this; @@ -185,9 +185,9 @@ public function getCreated(): DateTime * * @param DateTime $dateTime Created date * - * @return AuthHashEntityInterface + * @return static */ - public function setCreated(DateTime $dateTime): AuthHashEntityInterface + public function setCreated(DateTime $dateTime): static { $this->created = $dateTime->format('Y-m-d H:i:s'); return $this; diff --git a/module/VuFind/src/VuFind/Db/Row/ChangeTracker.php b/module/VuFind/src/VuFind/Db/Row/ChangeTracker.php index d5dfd6621dd..e3f15904e1c 100644 --- a/module/VuFind/src/VuFind/Db/Row/ChangeTracker.php +++ b/module/VuFind/src/VuFind/Db/Row/ChangeTracker.php @@ -65,9 +65,9 @@ public function __construct($adapter) * * @param string $id Id * - * @return ChangeTrackerEntityInterface + * @return static */ - public function setId(string $id): ChangeTrackerEntityInterface + public function setId(string $id): static { $this->id = $id; return $this; @@ -88,9 +88,9 @@ public function getId(): string * * @param string $name Index name * - * @return ChangeTrackerEntityInterface + * @return static */ - public function setIndexName(string $name): ChangeTrackerEntityInterface + public function setIndexName(string $name): static { $this->core = $name; return $this; @@ -111,9 +111,9 @@ public function getIndexName(): string * * @param ?DateTime $dateTime Time first added to index. * - * @return ChangeTrackerEntityInterface + * @return static */ - public function setFirstIndexed(?DateTime $dateTime): ChangeTrackerEntityInterface + public function setFirstIndexed(?DateTime $dateTime): static { $this->first_indexed = $dateTime->format('Y-m-d H:i:s'); return $this; @@ -134,9 +134,9 @@ public function getFirstIndexed(): ?DateTime * * @param ?DateTime $dateTime Last time changed in index. * - * @return ChangeTrackerEntityInterface + * @return static */ - public function setLastIndexed(?DateTime $dateTime): ChangeTrackerEntityInterface + public function setLastIndexed(?DateTime $dateTime): static { $this->last_indexed = $dateTime->format('Y-m-d H:i:s'); return $this; @@ -157,9 +157,9 @@ public function getLastIndexed(): ?DateTime * * @param ?DateTime $dateTime Last time original record was edited * - * @return ChangeTrackerEntityInterface + * @return static */ - public function setLastRecordChange(?DateTime $dateTime): ChangeTrackerEntityInterface + public function setLastRecordChange(?DateTime $dateTime): static { $this->last_record_change = $dateTime->format('Y-m-d H:i:s'); return $this; @@ -180,9 +180,9 @@ public function getLastRecordChange(): ?DateTime * * @param ?DateTime $dateTime Time record was removed from index * - * @return ChangeTrackerEntityInterface + * @return static */ - public function setDeleted(?DateTime $dateTime): ChangeTrackerEntityInterface + public function setDeleted(?DateTime $dateTime): static { $this->deleted = $dateTime->format('Y-m-d H:i:s'); return $this; diff --git a/module/VuFind/src/VuFind/Db/Row/Comments.php b/module/VuFind/src/VuFind/Db/Row/Comments.php index 66387f37f05..dbe7d52ad50 100644 --- a/module/VuFind/src/VuFind/Db/Row/Comments.php +++ b/module/VuFind/src/VuFind/Db/Row/Comments.php @@ -80,9 +80,9 @@ public function getId(): int * * @param string $comment Comment * - * @return Comments + * @return static */ - public function setComment(string $comment): CommentsEntityInterface + public function setComment(string $comment): static { $this->comment = $comment; return $this; @@ -103,9 +103,9 @@ public function getComment(): string * * @param DateTime $dateTime Created date * - * @return Comments + * @return static */ - public function setCreated(DateTime $dateTime): CommentsEntityInterface + public function setCreated(DateTime $dateTime): static { $this->created = $dateTime->format('Y-m-d H:i:s'); return $this; @@ -126,9 +126,9 @@ public function getCreated(): DateTime * * @param ?UserEntityInterface $user User that created comment * - * @return Comments + * @return static */ - public function setUser(?UserEntityInterface $user): CommentsEntityInterface + public function setUser(?UserEntityInterface $user): static { $this->user_id = $user ? $user->getId() : null; return $this; @@ -151,9 +151,9 @@ public function getUser(): ?UserEntityInterface * * @param ResourceEntityInterface $resource Resource id. * - * @return Comments + * @return static */ - public function setResource(ResourceEntityInterface $resource): CommentsEntityInterface + public function setResource(ResourceEntityInterface $resource): static { $this->resource_id = $resource->getId(); return $this; diff --git a/module/VuFind/src/VuFind/Db/Row/ExternalSession.php b/module/VuFind/src/VuFind/Db/Row/ExternalSession.php index 7dea047ffb6..ad46955a5fa 100644 --- a/module/VuFind/src/VuFind/Db/Row/ExternalSession.php +++ b/module/VuFind/src/VuFind/Db/Row/ExternalSession.php @@ -49,7 +49,7 @@ * @property string $external_session_id * @property string $created */ -class ExternalSession extends RowGateway implements \VuFind\Db\Entity\ExternalSessionEntityInterface +class ExternalSession extends RowGateway implements ExternalSessionEntityInterface { /** * Constructor @@ -86,9 +86,9 @@ public function getSessionId(): string * * @param string $sessionId PHP session id string * - * @return ExternalSessionEntityInterface + * @return static */ - public function setSessionId(string $sessionId): ExternalSessionEntityInterface + public function setSessionId(string $sessionId): static { $this->session_id = $sessionId; return $this; @@ -109,9 +109,9 @@ public function getExternalSessionId(): string * * @param string $externalSessionId External session id string * - * @return ExternalSessionEntityInterface + * @return static */ - public function setExternalSessionId(string $externalSessionId): ExternalSessionEntityInterface + public function setExternalSessionId(string $externalSessionId): static { $this->external_session_id = $externalSessionId; return $this; @@ -132,9 +132,9 @@ public function getCreated(): DateTime * * @param DateTime $dateTime Created date * - * @return ExternalSessionEntityInterface + * @return static */ - public function setCreated(DateTime $dateTime): ExternalSessionEntityInterface + public function setCreated(DateTime $dateTime): static { $this->created = $dateTime->format('Y-m-d H:i:s'); return $this; diff --git a/module/VuFind/src/VuFind/Db/Row/Feedback.php b/module/VuFind/src/VuFind/Db/Row/Feedback.php index ee39ffc0f36..960cbc621e9 100644 --- a/module/VuFind/src/VuFind/Db/Row/Feedback.php +++ b/module/VuFind/src/VuFind/Db/Row/Feedback.php @@ -87,9 +87,9 @@ public function getId(): int * * @param string $message Message * - * @return FeedbackEntityInterface + * @return static */ - public function setMessage(string $message): FeedbackEntityInterface + public function setMessage(string $message): static { $this->message = $message; return $this; @@ -110,9 +110,9 @@ public function getMessage(): string * * @param array $data Form data * - * @return FeedbackEntityInterface + * @return static */ - public function setFormData(array $data): FeedbackEntityInterface + public function setFormData(array $data): static { $this->form_data = json_encode($data); return $this; @@ -133,9 +133,9 @@ public function getFormData(): array * * @param string $name Form name * - * @return FeedbackEntityInterface + * @return static */ - public function setFormName(string $name): FeedbackEntityInterface + public function setFormName(string $name): static { $this->form_name = $name; return $this; @@ -156,9 +156,9 @@ public function getFormName(): string * * @param DateTime $dateTime Created date * - * @return FeedbackEntityInterface + * @return static */ - public function setCreated(DateTime $dateTime): FeedbackEntityInterface + public function setCreated(DateTime $dateTime): static { $this->created = $dateTime->format('Y-m-d H:i:s'); return $this; @@ -179,9 +179,9 @@ public function getCreated(): DateTime * * @param DateTime $dateTime Last update date * - * @return FeedbackEntityInterface + * @return static */ - public function setUpdated(DateTime $dateTime): FeedbackEntityInterface + public function setUpdated(DateTime $dateTime): static { $this->updated = $dateTime->format('Y-m-d H:i:s'); return $this; @@ -202,9 +202,9 @@ public function getUpdated(): DateTime * * @param string $status Status * - * @return FeedbackEntityInterface + * @return static */ - public function setStatus(string $status): FeedbackEntityInterface + public function setStatus(string $status): static { $this->status = $status; return $this; @@ -225,9 +225,9 @@ public function getStatus(): string * * @param string $url Site URL * - * @return FeedbackEntityInterface + * @return static */ - public function setSiteUrl(string $url): FeedbackEntityInterface + public function setSiteUrl(string $url): static { $this->site_url = $url; return $this; @@ -248,9 +248,9 @@ public function getSiteUrl(): string * * @param ?UserEntityInterface $user User that created request * - * @return FeedbackEntityInterface + * @return static */ - public function setUser(?UserEntityInterface $user): FeedbackEntityInterface + public function setUser(?UserEntityInterface $user): static { $this->user_id = $user?->getId(); return $this; @@ -273,9 +273,9 @@ public function getUser(): ?UserEntityInterface * * @param ?UserEntityInterface $user User that updated request * - * @return FeedbackEntityInterface + * @return static */ - public function setUpdatedBy(?UserEntityInterface $user): FeedbackEntityInterface + public function setUpdatedBy(?UserEntityInterface $user): static { $this->updated_by = $user ? $user->getId() : null; return $this; diff --git a/module/VuFind/src/VuFind/Db/Row/LoginToken.php b/module/VuFind/src/VuFind/Db/Row/LoginToken.php index 5995dcb41ed..645b8ea027d 100644 --- a/module/VuFind/src/VuFind/Db/Row/LoginToken.php +++ b/module/VuFind/src/VuFind/Db/Row/LoginToken.php @@ -84,9 +84,9 @@ public function getId(): int * * @param UserEntityInterface $user User to set * - * @return LoginTokenEntityInterface + * @return static */ - public function setUser(UserEntityInterface $user): LoginTokenEntityInterface + public function setUser(UserEntityInterface $user): static { $this->user_id = $user->getId(); return $this; @@ -109,9 +109,9 @@ public function getUser(): ?UserEntityInterface * * @param string $token Token * - * @return LoginTokenEntityInterface + * @return static */ - public function setToken(string $token): LoginTokenEntityInterface + public function setToken(string $token): static { $this->token = $token; return $this; @@ -132,9 +132,9 @@ public function getToken(): string * * @param string $series Series * - * @return LoginTokenEntityInterface + * @return static */ - public function setSeries(string $series): LoginTokenEntityInterface + public function setSeries(string $series): static { $this->series = $series; return $this; @@ -155,9 +155,9 @@ public function getSeries(): string * * @param DateTime $dateTime Last login date/time * - * @return LoginTokenEntityInterface + * @return static */ - public function setLastLogin(DateTime $dateTime): LoginTokenEntityInterface + public function setLastLogin(DateTime $dateTime): static { $this->last_login = $dateTime->format('Y-m-d H:i:s'); return $this; @@ -178,9 +178,9 @@ public function getLastLogin(): DateTime * * @param ?string $browser Browser details (or null for none) * - * @return LoginTokenEntityInterface + * @return static */ - public function setBrowser(?string $browser): LoginTokenEntityInterface + public function setBrowser(?string $browser): static { $this->browser = $browser; return $this; @@ -201,9 +201,9 @@ public function getBrowser(): ?string * * @param ?string $platform Platform details (or null for none) * - * @return LoginTokenEntityInterface + * @return static */ - public function setPlatform(?string $platform): LoginTokenEntityInterface + public function setPlatform(?string $platform): static { $this->platform = $platform; return $this; @@ -224,9 +224,9 @@ public function getPlatform(): ?string * * @param int $expires Expiration timestamp * - * @return LoginTokenEntityInterface + * @return static */ - public function setExpires(int $expires): LoginTokenEntityInterface + public function setExpires(int $expires): static { $this->expires = $expires; return $this; @@ -247,9 +247,9 @@ public function getExpires(): int * * @param ?string $sid Last session ID (or null for none) * - * @return LoginTokenEntityInterface + * @return static */ - public function setLastSessionId(?string $sid): LoginTokenEntityInterface + public function setLastSessionId(?string $sid): static { $this->last_session_id = $sid; return $this; diff --git a/module/VuFind/src/VuFind/Db/Row/OaiResumption.php b/module/VuFind/src/VuFind/Db/Row/OaiResumption.php index ced137c1682..fbd3e97a4f5 100644 --- a/module/VuFind/src/VuFind/Db/Row/OaiResumption.php +++ b/module/VuFind/src/VuFind/Db/Row/OaiResumption.php @@ -111,9 +111,9 @@ public function getId(): int * * @param ?string $params Resumption parameters. * - * @return OaiResumptionEntityInterface + * @return static */ - public function setResumptionParameters(?string $params): OaiResumptionEntityInterface + public function setResumptionParameters(?string $params): static { $this->params = $params; return $this; @@ -134,9 +134,9 @@ public function getResumptionParameters(): ?string * * @param DateTime $dateTime Expiration date * - * @return OaiResumptionEntityInterface + * @return static */ - public function setExpiry(DateTime $dateTime): OaiResumptionEntityInterface + public function setExpiry(DateTime $dateTime): static { $this->expires = $dateTime->format('Y-m-d H:i:s'); return $this; diff --git a/module/VuFind/src/VuFind/Db/Row/Ratings.php b/module/VuFind/src/VuFind/Db/Row/Ratings.php index c077dd22ec8..a4ded638a5d 100644 --- a/module/VuFind/src/VuFind/Db/Row/Ratings.php +++ b/module/VuFind/src/VuFind/Db/Row/Ratings.php @@ -54,7 +54,7 @@ * @property string $created */ class Ratings extends RowGateway implements - \VuFind\Db\Entity\RatingsEntityInterface, + RatingsEntityInterface, \VuFind\Db\Table\DbTableAwareInterface, DbServiceAwareInterface { @@ -98,9 +98,9 @@ public function getUser(): ?UserEntityInterface * * @param ?UserEntityInterface $user User * - * @return RatingsEntityInterface + * @return static */ - public function setUser(?UserEntityInterface $user): RatingsEntityInterface + public function setUser(?UserEntityInterface $user): static { $this->user_id = $user?->getId(); return $this; @@ -123,9 +123,9 @@ public function getResource(): ResourceEntityInterface * * @param ResourceEntityInterface $resource Resource * - * @return RatingsEntityInterface + * @return static */ - public function setResource(ResourceEntityInterface $resource): RatingsEntityInterface + public function setResource(ResourceEntityInterface $resource): static { $this->resource_id = $resource->getId(); return $this; @@ -146,9 +146,9 @@ public function getRating(): int * * @param int $rating Rating * - * @return RatingsEntityInterface + * @return static */ - public function setRating(int $rating): RatingsEntityInterface + public function setRating(int $rating): static { $this->rating = $rating; return $this; @@ -169,9 +169,9 @@ public function getCreated(): DateTime * * @param DateTime $dateTime Created date * - * @return RatingsEntityInterface + * @return static */ - public function setCreated(DateTime $dateTime): RatingsEntityInterface + public function setCreated(DateTime $dateTime): static { $this->created = $dateTime->format('Y-m-d H:i:s'); return $this; diff --git a/module/VuFind/src/VuFind/Db/Row/Record.php b/module/VuFind/src/VuFind/Db/Row/Record.php index c023ca93952..1e3b897d0ae 100644 --- a/module/VuFind/src/VuFind/Db/Row/Record.php +++ b/module/VuFind/src/VuFind/Db/Row/Record.php @@ -85,9 +85,9 @@ public function getRecordId(): ?string * * @param ?string $recordId Record id * - * @return RecordEntityInterface + * @return static */ - public function setRecordId(?string $recordId): RecordEntityInterface + public function setRecordId(?string $recordId): static { $this->record_id = $recordId; return $this; @@ -108,9 +108,9 @@ public function getSource(): ?string * * @param ?string $recordSource Record source * - * @return RecordEntityInterface + * @return static */ - public function setSource(?string $recordSource): RecordEntityInterface + public function setSource(?string $recordSource): static { $this->source = $recordSource; return $this; @@ -131,9 +131,9 @@ public function getVersion(): string * * @param string $recordVersion Record version * - * @return RecordEntityInterface + * @return static */ - public function setVersion(string $recordVersion): RecordEntityInterface + public function setVersion(string $recordVersion): static { $this->version = $recordVersion; return $this; @@ -158,9 +158,9 @@ public function getData(): ?string * * @param ?string $recordData Record data * - * @return RecordEntityInterface + * @return static */ - public function setData(?string $recordData): RecordEntityInterface + public function setData(?string $recordData): static { $this->__set('data', $recordData); return $this; @@ -181,9 +181,9 @@ public function getUpdated(): DateTime * * @param DateTime $dateTime Updated date * - * @return RecordEntityInterface + * @return static */ - public function setUpdated(DateTime $dateTime): RecordEntityInterface + public function setUpdated(DateTime $dateTime): static { $this->updated = $dateTime->format('Y-m-d H:i:s'); return $this; diff --git a/module/VuFind/src/VuFind/Db/Row/Resource.php b/module/VuFind/src/VuFind/Db/Row/Resource.php index a3ad7ad5594..e369c47f80c 100644 --- a/module/VuFind/src/VuFind/Db/Row/Resource.php +++ b/module/VuFind/src/VuFind/Db/Row/Resource.php @@ -294,9 +294,9 @@ public function getId(): int * * @param string $recordId recordId * - * @return ResourceEntityInterface + * @return static */ - public function setRecordId(string $recordId): ResourceEntityInterface + public function setRecordId(string $recordId): static { $this->record_id = $recordId; return $this; @@ -317,9 +317,9 @@ public function getRecordId(): string * * @param string $title Title of the record. * - * @return ResourceEntityInterface + * @return static */ - public function setTitle(string $title): ResourceEntityInterface + public function setTitle(string $title): static { $this->title = $title; return $this; @@ -340,9 +340,9 @@ public function getTitle(): string * * @param ?string $author Author of the title. * - * @return ResourceEntityInterface + * @return static */ - public function setAuthor(?string $author): ResourceEntityInterface + public function setAuthor(?string $author): static { $this->author = $author; return $this; @@ -353,9 +353,9 @@ public function setAuthor(?string $author): ResourceEntityInterface * * @param ?int $year Year title is published. * - * @return ResourceEntityInterface + * @return static */ - public function setYear(?int $year): ResourceEntityInterface + public function setYear(?int $year): static { $this->year = $year; return $this; @@ -366,9 +366,9 @@ public function setYear(?int $year): ResourceEntityInterface * * @param string $source Source (a search backend ID). * - * @return ResourceEntityInterface + * @return static */ - public function setSource(string $source): ResourceEntityInterface + public function setSource(string $source): static { $this->source = $source; return $this; @@ -389,9 +389,9 @@ public function getSource(): string * * @param ?string $extraMetadata ExtraMetadata. * - * @return ResourceEntityInterface + * @return static */ - public function setExtraMetadata(?string $extraMetadata): ResourceEntityInterface + public function setExtraMetadata(?string $extraMetadata): static { $this->extra_metadata = $extraMetadata; return $this; diff --git a/module/VuFind/src/VuFind/Db/Row/ResourceTags.php b/module/VuFind/src/VuFind/Db/Row/ResourceTags.php index 976a862cdad..783a212f8a7 100644 --- a/module/VuFind/src/VuFind/Db/Row/ResourceTags.php +++ b/module/VuFind/src/VuFind/Db/Row/ResourceTags.php @@ -59,7 +59,7 @@ * @property string $posted */ class ResourceTags extends RowGateway implements - \VuFind\Db\Entity\ResourceTagsEntityInterface, + ResourceTagsEntityInterface, \VuFind\Db\Table\DbTableAwareInterface, DbServiceAwareInterface { @@ -103,9 +103,9 @@ public function getResource(): ?ResourceEntityInterface * * @param ?ResourceEntityInterface $resource Resource * - * @return ResourceTagsEntityInterface + * @return static */ - public function setResource(?ResourceEntityInterface $resource): ResourceTagsEntityInterface + public function setResource(?ResourceEntityInterface $resource): static { $this->resource_id = $resource?->getId(); return $this; @@ -128,9 +128,9 @@ public function getTag(): TagsEntityInterface * * @param TagsEntityInterface $tag Tag * - * @return ResourceTagsEntityInterface + * @return static */ - public function setTag(TagsEntityInterface $tag): ResourceTagsEntityInterface + public function setTag(TagsEntityInterface $tag): static { $this->tag_id = $tag->getId(); return $this; @@ -153,9 +153,9 @@ public function getUserList(): ?UserListEntityInterface * * @param ?UserListEntityInterface $list User list * - * @return ResourceTagsEntityInterface + * @return static */ - public function setUserList(?UserListEntityInterface $list): ResourceTagsEntityInterface + public function setUserList(?UserListEntityInterface $list): static { $this->list_id = $list?->getId(); return $this; @@ -178,9 +178,9 @@ public function getUser(): ?UserEntityInterface * * @param ?UserEntityInterface $user User * - * @return ResourceTagsEntityInterface + * @return static */ - public function setUser(?UserEntityInterface $user): ResourceTagsEntityInterface + public function setUser(?UserEntityInterface $user): static { $this->user_id = $user?->getId(); return $this; @@ -201,9 +201,9 @@ public function getPosted(): DateTime * * @param DateTime $dateTime Created date * - * @return ResourceTagsEntityInterface + * @return static */ - public function setPosted(DateTime $dateTime): ResourceTagsEntityInterface + public function setPosted(DateTime $dateTime): static { $this->posted = $dateTime->format('Y-m-d H:i:s'); return $this; diff --git a/module/VuFind/src/VuFind/Db/Row/Search.php b/module/VuFind/src/VuFind/Db/Row/Search.php index bdaeb354a50..0f5bac540d1 100644 --- a/module/VuFind/src/VuFind/Db/Row/Search.php +++ b/module/VuFind/src/VuFind/Db/Row/Search.php @@ -61,7 +61,7 @@ * @property string $notification_base_url */ class Search extends RowGateway implements - \VuFind\Db\Entity\SearchEntityInterface, + SearchEntityInterface, \VuFind\Db\Table\DbTableAwareInterface, DbServiceAwareInterface { @@ -217,9 +217,9 @@ public function getUser(): ?UserEntityInterface * * @param ?UserEntityInterface $user User * - * @return SearchEntityInterface + * @return static */ - public function setUser(?UserEntityInterface $user): SearchEntityInterface + public function setUser(?UserEntityInterface $user): static { $this->user_id = $user?->getId(); return $this; @@ -240,9 +240,9 @@ public function getSessionId(): ?string * * @param ?string $sessionId Session id * - * @return SearchEntityInterface + * @return static */ - public function setSessionId(?string $sessionId): SearchEntityInterface + public function setSessionId(?string $sessionId): static { $this->session_id = $sessionId; return $this; @@ -263,9 +263,9 @@ public function getCreated(): DateTime * * @param DateTime $dateTime Created date * - * @return SearchEntityInterface + * @return static */ - public function setCreated(DateTime $dateTime): SearchEntityInterface + public function setCreated(DateTime $dateTime): static { $this->created = $dateTime->format('Y-m-d H:i:s'); return $this; @@ -286,9 +286,9 @@ public function getTitle(): ?string * * @param ?string $title Title * - * @return SearchEntityInterface + * @return static */ - public function setTitle(?string $title): SearchEntityInterface + public function setTitle(?string $title): static { $this->title = $title; return $this; @@ -309,9 +309,9 @@ public function getSaved(): bool * * @param bool $saved Saved * - * @return SearchEntityInterface + * @return static */ - public function setSaved(bool $saved): SearchEntityInterface + public function setSaved(bool $saved): static { $this->saved = $saved ? 1 : 0; return $this; @@ -322,9 +322,9 @@ public function setSaved(bool $saved): SearchEntityInterface * * @param ?\VuFind\Search\Minified $searchObject Search object * - * @return SearchEntityInterface + * @return static */ - public function setSearchObject(?\VuFind\Search\Minified $searchObject): SearchEntityInterface + public function setSearchObject(?\VuFind\Search\Minified $searchObject): static { $this->search_object = $searchObject ? serialize($searchObject) : null; return $this; @@ -345,9 +345,9 @@ public function getChecksum(): ?int * * @param ?int $checksum Checksum * - * @return SearchEntityInterface + * @return static */ - public function setChecksum(?int $checksum): SearchEntityInterface + public function setChecksum(?int $checksum): static { $this->checksum = $checksum; return $this; @@ -368,9 +368,9 @@ public function getNotificationFrequency(): int * * @param int $notificationFrequency Notification frequency * - * @return SearchEntityInterface + * @return static */ - public function setNotificationFrequency(int $notificationFrequency): SearchEntityInterface + public function setNotificationFrequency(int $notificationFrequency): static { $this->notification_frequency = $notificationFrequency; return $this; @@ -391,9 +391,9 @@ public function getLastNotificationSent(): DateTime * * @param DateTime $lastNotificationSent Time when last notification was sent * - * @return SearchEntityInterface + * @return static */ - public function setLastNotificationSent(Datetime $lastNotificationSent): SearchEntityInterface + public function setLastNotificationSent(Datetime $lastNotificationSent): static { $this->last_notification_sent = $lastNotificationSent->format('Y-m-d H:i:s'); return $this; @@ -414,9 +414,9 @@ public function getNotificationBaseUrl(): string * * @param string $notificationBaseUrl Notification base URL * - * @return SearchEntityInterface + * @return static */ - public function setNotificationBaseUrl(string $notificationBaseUrl): SearchEntityInterface + public function setNotificationBaseUrl(string $notificationBaseUrl): static { $this->notification_base_url = $notificationBaseUrl; return $this; diff --git a/module/VuFind/src/VuFind/Db/Row/Session.php b/module/VuFind/src/VuFind/Db/Row/Session.php index f3ede294dad..7db832b828a 100644 --- a/module/VuFind/src/VuFind/Db/Row/Session.php +++ b/module/VuFind/src/VuFind/Db/Row/Session.php @@ -74,9 +74,9 @@ public function getId(): int * * @param ?string $sid Session Id. * - * @return SessionEntityInterface + * @return static */ - public function setSessionId(?string $sid): SessionEntityInterface + public function setSessionId(?string $sid): static { $this->session_id = $sid; return $this; @@ -87,9 +87,9 @@ public function setSessionId(?string $sid): SessionEntityInterface * * @param DateTime $dateTime Created date * - * @return SessionEntityInterface + * @return static */ - public function setCreated(DateTime $dateTime): SessionEntityInterface + public function setCreated(DateTime $dateTime): static { $this->created = $dateTime->format('Y-m-d H:i:s'); return $this; @@ -100,9 +100,9 @@ public function setCreated(DateTime $dateTime): SessionEntityInterface * * @param int $lastUsed Time last used * - * @return SessionEntityInterface + * @return static */ - public function setLastUsed(int $lastUsed): SessionEntityInterface + public function setLastUsed(int $lastUsed): static { $this->last_used = $lastUsed; return $this; @@ -123,9 +123,9 @@ public function getLastUsed(): int * * @param ?string $data Session data. * - * @return SessionEntityInterface + * @return static */ - public function setData(?string $data): SessionEntityInterface + public function setData(?string $data): static { $this->data = $data; return $this; diff --git a/module/VuFind/src/VuFind/Db/Row/Shortlinks.php b/module/VuFind/src/VuFind/Db/Row/Shortlinks.php index b116ba0374f..13d1a716a24 100644 --- a/module/VuFind/src/VuFind/Db/Row/Shortlinks.php +++ b/module/VuFind/src/VuFind/Db/Row/Shortlinks.php @@ -46,7 +46,7 @@ * @property string $hash * @property string $created */ -class Shortlinks extends RowGateway implements \VuFind\Db\Entity\ShortlinksEntityInterface +class Shortlinks extends RowGateway implements ShortlinksEntityInterface { /** * Constructor @@ -84,9 +84,9 @@ public function getPath(): string * * @param string $path Path * - * @return ShortlinksEntityInterface + * @return static */ - public function setPath(string $path): ShortlinksEntityInterface + public function setPath(string $path): static { $this->path = $path; return $this; @@ -107,9 +107,9 @@ public function getHash(): ?string * * @param ?string $hash Shortlinks hash * - * @return ShortlinksEntityInterface + * @return static */ - public function setHash(?string $hash): ShortlinksEntityInterface + public function setHash(?string $hash): static { $this->hash = $hash; return $this; @@ -130,9 +130,9 @@ public function getCreated(): DateTime * * @param DateTime $dateTime Creation timestamp * - * @return ShortlinksEntityInterface + * @return static */ - public function setCreated(DateTime $dateTime): ShortlinksEntityInterface + public function setCreated(DateTime $dateTime): static { $this->created = $dateTime->format('Y-m-d H:i:s'); return $this; diff --git a/module/VuFind/src/VuFind/Db/Row/Tags.php b/module/VuFind/src/VuFind/Db/Row/Tags.php index 32a6e245cee..b36457a3c06 100644 --- a/module/VuFind/src/VuFind/Db/Row/Tags.php +++ b/module/VuFind/src/VuFind/Db/Row/Tags.php @@ -129,9 +129,9 @@ public function getId(): int * * @param string $tag Tag * - * @return TagsEntityInterface + * @return static */ - public function setTag(string $tag): TagsEntityInterface + public function setTag(string $tag): static { $this->tag = $tag; return $this; diff --git a/module/VuFind/src/VuFind/Db/Row/User.php b/module/VuFind/src/VuFind/Db/Row/User.php index 3347dc9ade2..0f4505cdc80 100644 --- a/module/VuFind/src/VuFind/Db/Row/User.php +++ b/module/VuFind/src/VuFind/Db/Row/User.php @@ -716,9 +716,9 @@ public function getId(): ?int * * @param string $username Username * - * @return UserEntityInterface + * @return static */ - public function setUsername(string $username): UserEntityInterface + public function setUsername(string $username): static { $this->username = $username; return $this; @@ -739,9 +739,9 @@ public function getUsername(): string * * @param string $password Password * - * @return UserEntityInterface + * @return static */ - public function setRawPassword(string $password): UserEntityInterface + public function setRawPassword(string $password): static { $this->password = $password; return $this; @@ -762,9 +762,9 @@ public function getRawPassword(): string * * @param ?string $hash Password hash * - * @return UserEntityInterface + * @return static */ - public function setPasswordHash(?string $hash): UserEntityInterface + public function setPasswordHash(?string $hash): static { $this->pass_hash = $hash; return $this; @@ -785,9 +785,9 @@ public function getPasswordHash(): ?string * * @param string $firstName New first name * - * @return UserEntityInterface + * @return static */ - public function setFirstname(string $firstName): UserEntityInterface + public function setFirstname(string $firstName): static { $this->firstname = $firstName; return $this; @@ -808,9 +808,9 @@ public function getFirstname(): string * * @param string $lastName New last name * - * @return UserEntityInterface + * @return static */ - public function setLastname(string $lastName): UserEntityInterface + public function setLastname(string $lastName): static { $this->lastname = $lastName; return $this; @@ -831,9 +831,9 @@ public function getLastname(): string * * @param string $email Email address * - * @return UserEntityInterface + * @return static */ - public function setEmail(string $email): UserEntityInterface + public function setEmail(string $email): static { $this->email = $email; return $this; @@ -854,9 +854,9 @@ public function getEmail(): string * * @param string $email New pending email * - * @return UserEntityInterface + * @return static */ - public function setPendingEmail(string $email): UserEntityInterface + public function setPendingEmail(string $email): static { $this->pending_email = $email; return $this; @@ -877,9 +877,9 @@ public function getPendingEmail(): string * * @param ?string $catId Catalog id * - * @return UserEntityInterface + * @return static */ - public function setCatId(?string $catId): UserEntityInterface + public function setCatId(?string $catId): static { $this->cat_id = $catId; return $this; @@ -900,9 +900,9 @@ public function getCatId(): ?string * * @param ?string $catUsername Catalog username * - * @return UserEntityInterface + * @return static */ - public function setCatUsername(?string $catUsername): UserEntityInterface + public function setCatUsername(?string $catUsername): static { $this->cat_username = $catUsername; return $this; @@ -923,9 +923,9 @@ public function getCatUsername(): ?string * * @param ?string $homeLibrary Home library * - * @return UserEntityInterface + * @return static */ - public function setHomeLibrary(?string $homeLibrary): UserEntityInterface + public function setHomeLibrary(?string $homeLibrary): static { $this->home_library = $homeLibrary; return $this; @@ -946,9 +946,9 @@ public function getHomeLibrary(): ?string * * @param ?string $catPassword Cat password * - * @return UserEntityInterface + * @return static */ - public function setRawCatPassword(?string $catPassword): UserEntityInterface + public function setRawCatPassword(?string $catPassword): static { $this->cat_password = $catPassword; return $this; @@ -961,7 +961,7 @@ public function setRawCatPassword(?string $catPassword): UserEntityInterface */ public function getRawCatPassword(): ?string { - return $this->cat_password; + return $this->cat_password ?? null; } /** @@ -969,9 +969,9 @@ public function getRawCatPassword(): ?string * * @param ?string $passEnc Encrypted password * - * @return UserEntityInterface + * @return static */ - public function setCatPassEnc(?string $passEnc): UserEntityInterface + public function setCatPassEnc(?string $passEnc): static { $this->cat_pass_enc = $passEnc; return $this; @@ -992,9 +992,9 @@ public function getCatPassEnc(): ?string * * @param string $college College * - * @return UserEntityInterface + * @return static */ - public function setCollege(string $college): UserEntityInterface + public function setCollege(string $college): static { $this->college = $college; return $this; @@ -1015,9 +1015,9 @@ public function getCollege(): string * * @param string $major Major * - * @return UserEntityInterface + * @return static */ - public function setMajor(string $major): UserEntityInterface + public function setMajor(string $major): static { $this->major = $major; return $this; @@ -1038,9 +1038,9 @@ public function getMajor(): string * * @param string $hash Hash value to save * - * @return UserEntityInterface + * @return static */ - public function setVerifyHash(string $hash): UserEntityInterface + public function setVerifyHash(string $hash): static { $this->verify_hash = $hash; return $this; @@ -1061,9 +1061,9 @@ public function getVerifyHash(): string * * @param ?string $authMethod New value (null for none) * - * @return UserEntityInterface + * @return static */ - public function setAuthMethod(?string $authMethod): UserEntityInterface + public function setAuthMethod(?string $authMethod): static { $this->auth_method = $authMethod; return $this; @@ -1084,9 +1084,9 @@ public function getAuthMethod(): ?string * * @param string $lang Last language * - * @return UserEntityInterface + * @return static */ - public function setLastLanguage(string $lang): UserEntityInterface + public function setLastLanguage(string $lang): static { $this->last_language = $lang; return $this; @@ -1117,9 +1117,9 @@ public function hasUserProvidedEmail(): bool * * @param bool $userProvided New value * - * @return UserEntityInterface + * @return static */ - public function setHasUserProvidedEmail(bool $userProvided): UserEntityInterface + public function setHasUserProvidedEmail(bool $userProvided): static { $this->user_provided_email = $userProvided ? 1 : 0; return $this; @@ -1130,9 +1130,9 @@ public function setHasUserProvidedEmail(bool $userProvided): UserEntityInterface * * @param DateTime $dateTime Last login date * - * @return UserEntityInterface + * @return static */ - public function setLastLogin(DateTime $dateTime): UserEntityInterface + public function setLastLogin(DateTime $dateTime): static { $this->last_login = $dateTime->format('Y-m-d H:i:s'); return $this; @@ -1153,9 +1153,9 @@ public function getLastLogin(): DateTime * * @param DateTime $dateTime Creation date * - * @return UserEntityInterface + * @return static */ - public function setCreated(DateTime $dateTime): UserEntityInterface + public function setCreated(DateTime $dateTime): static { $this->created = $dateTime->format('Y-m-d H:i:s'); return $this; @@ -1176,9 +1176,9 @@ public function getCreated(): DateTime * * @param ?DateTime $dateTime Verification date (or null) * - * @return UserEntityInterface + * @return static */ - public function setEmailVerified(?DateTime $dateTime): UserEntityInterface + public function setEmailVerified(?DateTime $dateTime): static { $this->email_verified = $dateTime?->format('Y-m-d H:i:s'); return $this; diff --git a/module/VuFind/src/VuFind/Db/Row/UserCard.php b/module/VuFind/src/VuFind/Db/Row/UserCard.php index 0811386aff5..0b9b14f8f72 100644 --- a/module/VuFind/src/VuFind/Db/Row/UserCard.php +++ b/module/VuFind/src/VuFind/Db/Row/UserCard.php @@ -82,9 +82,9 @@ public function getId(): ?int * * @param string $cardName User card name. * - * @return UserCardEntityInterface + * @return static */ - public function setCardName(string $cardName): UserCardEntityInterface + public function setCardName(string $cardName): static { $this->card_name = $cardName; return $this; @@ -105,9 +105,9 @@ public function getCardName(): string * * @param string $catUsername Catalog username * - * @return UserCardEntityInterface + * @return static */ - public function setCatUsername(string $catUsername): UserCardEntityInterface + public function setCatUsername(string $catUsername): static { $this->cat_username = $catUsername; return $this; @@ -128,9 +128,9 @@ public function getCatUsername(): string * * @param ?string $catPassword Cat password * - * @return UserCardEntityInterface + * @return static */ - public function setRawCatPassword(?string $catPassword): UserCardEntityInterface + public function setRawCatPassword(?string $catPassword): static { $this->cat_password = $catPassword; return $this; @@ -151,9 +151,9 @@ public function getRawCatPassword(): ?string * * @param ?string $passEnc Encrypted password * - * @return UserCardEntityInterface + * @return static */ - public function setCatPassEnc(?string $passEnc): UserCardEntityInterface + public function setCatPassEnc(?string $passEnc): static { $this->cat_pass_enc = $passEnc; return $this; @@ -174,9 +174,9 @@ public function getCatPassEnc(): ?string * * @param ?string $homeLibrary Home library * - * @return UserCardEntityInterface + * @return static */ - public function setHomeLibrary(?string $homeLibrary): UserCardEntityInterface + public function setHomeLibrary(?string $homeLibrary): static { $this->home_library = $homeLibrary; return $this; @@ -197,9 +197,9 @@ public function getHomeLibrary(): ?string * * @param DateTime $dateTime Created date * - * @return UserCardEntityInterface + * @return static */ - public function setCreated(DateTime $dateTime): UserCardEntityInterface + public function setCreated(DateTime $dateTime): static { $this->created = $dateTime->format('Y-m-d H:i:s'); return $this; @@ -220,9 +220,9 @@ public function getCreated(): DateTime * * @param DateTime $dateTime Saved date and time * - * @return UserCardEntityInterface + * @return static */ - public function setSaved(DateTime $dateTime): UserCardEntityInterface + public function setSaved(DateTime $dateTime): static { $this->saved = $dateTime->format('Y-m-d H:i:s'); return $this; @@ -243,9 +243,9 @@ public function getSaved(): DateTime * * @param UserEntityInterface $user User that owns card * - * @return UserCardEntityInterface + * @return static */ - public function setUser(UserEntityInterface $user): UserCardEntityInterface + public function setUser(UserEntityInterface $user): static { $this->user_id = $user->getId(); return $this; diff --git a/module/VuFind/src/VuFind/Db/Row/UserList.php b/module/VuFind/src/VuFind/Db/Row/UserList.php index c546221339c..a6c9471ef30 100644 --- a/module/VuFind/src/VuFind/Db/Row/UserList.php +++ b/module/VuFind/src/VuFind/Db/Row/UserList.php @@ -263,9 +263,9 @@ public function getId(): ?int * * @param string $title Title * - * @return UserListEntityInterface + * @return static */ - public function setTitle(string $title): UserListEntityInterface + public function setTitle(string $title): static { $this->title = $title; return $this; @@ -286,9 +286,9 @@ public function getTitle(): string * * @param ?string $description Description * - * @return UserListEntityInterface + * @return static */ - public function setDescription(?string $description): UserListEntityInterface + public function setDescription(?string $description): static { $this->description = $description; return $this; @@ -309,9 +309,9 @@ public function getDescription(): ?string * * @param DateTime $dateTime Created date * - * @return UserListEntityInterface + * @return static */ - public function setCreated(DateTime $dateTime): UserListEntityInterface + public function setCreated(DateTime $dateTime): static { $this->created = $dateTime->format('Y-m-d H:i:s'); return $this; @@ -332,9 +332,9 @@ public function getCreated(): DateTime * * @param bool $public Is the list public? * - * @return UserListEntityInterface + * @return static */ - public function setPublic(bool $public): UserListEntityInterface + public function setPublic(bool $public): static { $this->public = $public ? '1' : '0'; return $this; @@ -345,9 +345,9 @@ public function setPublic(bool $public): UserListEntityInterface * * @param ?UserEntityInterface $user User owning the list. * - * @return UserListEntityInterface + * @return static */ - public function setUser(?UserEntityInterface $user): UserListEntityInterface + public function setUser(?UserEntityInterface $user): static { $this->user_id = $user?->getId(); return $this; diff --git a/module/VuFind/src/VuFind/Db/Row/UserResource.php b/module/VuFind/src/VuFind/Db/Row/UserResource.php index d8a5e110ab3..85b9dd2d274 100644 --- a/module/VuFind/src/VuFind/Db/Row/UserResource.php +++ b/module/VuFind/src/VuFind/Db/Row/UserResource.php @@ -57,7 +57,7 @@ * @property string $saved */ class UserResource extends RowGateway implements - \VuFind\Db\Entity\UserResourceEntityInterface, + UserResourceEntityInterface, \VuFind\Db\Table\DbTableAwareInterface, DbServiceAwareInterface { @@ -100,9 +100,9 @@ public function getUser(): UserEntityInterface * * @param UserEntityInterface $user User * - * @return UserResourceEntityInterface + * @return static */ - public function setUser(UserEntityInterface $user): UserResourceEntityInterface + public function setUser(UserEntityInterface $user): static { $this->user_id = $user->getId(); return $this; @@ -124,9 +124,9 @@ public function getResource(): ResourceEntityInterface * * @param ResourceEntityInterface $resource Resource * - * @return UserResourceEntityInterface + * @return static */ - public function setResource(ResourceEntityInterface $resource): UserResourceEntityInterface + public function setResource(ResourceEntityInterface $resource): static { $this->resource_id = $resource->getId(); return $this; @@ -149,9 +149,9 @@ public function getUserList(): ?UserListEntityInterface * * @param ?UserListEntityInterface $list User list * - * @return UserResourceEntityInterface + * @return static */ - public function setUserList(?UserListEntityInterface $list): UserResourceEntityInterface + public function setUserList(?UserListEntityInterface $list): static { $this->list_id = $list?->getId(); return $this; @@ -172,9 +172,9 @@ public function getNotes(): ?string * * @param ?string $notes Notes associated with the resource * - * @return UserResourceEntityInterface + * @return static */ - public function setNotes(?string $notes): UserResourceEntityInterface + public function setNotes(?string $notes): static { $this->notes = $notes; return $this; @@ -195,9 +195,9 @@ public function getSaved(): DateTime * * @param DateTime $dateTime Created date * - * @return UserResourceEntityInterface + * @return static */ - public function setSaved(DateTime $dateTime): UserResourceEntityInterface + public function setSaved(DateTime $dateTime): static { $this->saved = $dateTime->format('Y-m-d H:i:s'); return $this; diff --git a/module/VuFind/src/VuFind/Db/Service/SearchService.php b/module/VuFind/src/VuFind/Db/Service/SearchService.php index d4a64545a55..96391c87ef2 100644 --- a/module/VuFind/src/VuFind/Db/Service/SearchService.php +++ b/module/VuFind/src/VuFind/Db/Service/SearchService.php @@ -154,17 +154,24 @@ public function getSearchByIdAndOwner( /** * Get an array of rows for the specified user. * - * @param string $sessionId Session ID of current user. + * @param ?string $sessionId Session ID of current user or null to ignore searches in session. * @param UserEntityInterface|int|null $userOrId User entity or ID of current user (optional). * * @return SearchEntityInterface[] */ - public function getSearches(string $sessionId, UserEntityInterface|int|null $userOrId = null): array + public function getSearches(?string $sessionId, UserEntityInterface|int|null $userOrId = null): array { + // If we don't get a session id or user id, don't return anything: + if (null === $sessionId && null === $userOrId) { + return []; + } $uid = $userOrId instanceof UserEntityInterface ? $userOrId->getId() : $userOrId; $callback = function ($select) use ($sessionId, $uid) { - $select->where->equalTo('session_id', $sessionId)->and->equalTo('saved', 0); + if (null !== $sessionId) { + $select->where->equalTo('session_id', $sessionId)->and->equalTo('saved', 0); + } if ($uid !== null) { + // Note: It doesn't hurt to use OR here even if there are no other terms $select->where->OR->equalTo('user_id', $uid); } $select->order('created'); diff --git a/module/VuFind/src/VuFind/Db/Service/SearchServiceInterface.php b/module/VuFind/src/VuFind/Db/Service/SearchServiceInterface.php index 098c70a1cae..52683b5d009 100644 --- a/module/VuFind/src/VuFind/Db/Service/SearchServiceInterface.php +++ b/module/VuFind/src/VuFind/Db/Service/SearchServiceInterface.php @@ -99,7 +99,7 @@ public function getSearchByIdAndOwner( /** * Get an array of rows for the specified user. * - * @param string $sessionId Session ID of current user. + * @param ?string $sessionId Session ID of current user or null to ignore searches in session. * @param UserEntityInterface|int|null $userOrId User entity or ID of current user (optional). * * @return SearchEntityInterface[] diff --git a/module/VuFind/src/VuFind/DigitalContent/FakeOverdriveConnector.php b/module/VuFind/src/VuFind/DigitalContent/FakeOverdriveConnector.php index a12c0535ae4..872c730f5bd 100644 --- a/module/VuFind/src/VuFind/DigitalContent/FakeOverdriveConnector.php +++ b/module/VuFind/src/VuFind/DigitalContent/FakeOverdriveConnector.php @@ -313,9 +313,9 @@ public function getAuthHeader() } /** - * Returns permanant links for Ovedrive resources + * Returns permanent links for OverDrive resources * - * @param array $overDriveIds An array of overdrive IDs we need links for + * @param array $overDriveIds An array of OverDrive IDs we need links for * * @return array<string> */ diff --git a/module/VuFind/src/VuFind/DigitalContent/OverdriveConnector.php b/module/VuFind/src/VuFind/DigitalContent/OverdriveConnector.php index 536d7af0561..675369d15bb 100644 --- a/module/VuFind/src/VuFind/DigitalContent/OverdriveConnector.php +++ b/module/VuFind/src/VuFind/DigitalContent/OverdriveConnector.php @@ -276,10 +276,12 @@ public function getAvailability($overDriveId) } } else { $result->status = true; - $res->copiesAvailable ??= 0; - $res->copiesOwned ??= 0; - $res->numberOfHolds ??= 0; - $res->code = 'od_none'; + if ($res) { + $res->copiesAvailable ??= 0; + $res->copiesOwned ??= 0; + $res->numberOfHolds ??= 0; + $res->code = 'od_none'; + } $result->data = $res; } } @@ -983,9 +985,9 @@ public function getFormatNames() } /** - * Returns permanant links for Ovedrive resources + * Returns permanent links for OverDrive resources * - * @param array $overDriveIds An array of overdrive IDs we need links for + * @param array $overDriveIds An array of OverDrive IDs we need links for * * @return array<string> */ diff --git a/module/VuFind/src/VuFind/Mailer/Cc.php b/module/VuFind/src/VuFind/Escaper/Escaper.php similarity index 55% rename from module/VuFind/src/VuFind/Mailer/Cc.php rename to module/VuFind/src/VuFind/Escaper/Escaper.php index bb91280afc0..9214f74d0a1 100644 --- a/module/VuFind/src/VuFind/Mailer/Cc.php +++ b/module/VuFind/src/VuFind/Escaper/Escaper.php @@ -1,11 +1,11 @@ <?php /** - * Tweaked Laminas "Cc" header class + * Escaper with configurable HTML attribute handling. * * PHP version 8 * - * Copyright (C) The National Library of Finland 2023. + * Copyright (C) The National Library of Finland 2024. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -21,24 +21,46 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * @category VuFind - * @package Mailer + * @package Escaper * @author Ere Maijala <ere.maijala@helsinki.fi> * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License * @link https://vufind.org/wiki/development Wiki */ -namespace VuFind\Mailer; +namespace VuFind\Escaper; /** - * Tweaked Laminas "Cc" header class + * Escaper with configurable HTML attribute handling. * * @category VuFind - * @package Mailer + * @package Escaper * @author Ere Maijala <ere.maijala@helsinki.fi> * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License * @link https://vufind.org/wiki/development Wiki */ -class Cc extends \Laminas\Mail\Header\Cc +class Escaper extends \Laminas\Escaper\Escaper { - use GetFieldValueFixTrait; + /** + * Constructor + * + * @param bool $extendedHtmlAttrEscaping Use Laminas' extended HTML attribute escaping? + */ + public function __construct(protected bool $extendedHtmlAttrEscaping = false) + { + parent::__construct(); + } + + /** + * Escape a string for the HTML Attribute context. + * + * @param string $string String to escape + * + * @return string + */ + public function escapeHtmlAttr(string $string) + { + return $this->extendedHtmlAttrEscaping + ? parent::escapeHtmlAttr($string) + : parent::escapeHtml($string); + } } diff --git a/module/VuFind/src/VuFind/Escaper/EscaperFactory.php b/module/VuFind/src/VuFind/Escaper/EscaperFactory.php new file mode 100644 index 00000000000..c8afeebe898 --- /dev/null +++ b/module/VuFind/src/VuFind/Escaper/EscaperFactory.php @@ -0,0 +1,74 @@ +<?php + +/** + * Escaper factory. + * + * PHP version 8 + * + * Copyright (C) The National Library of Finland 2024. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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 + * + * @category VuFind + * @package Escaper + * @author Ere Maijala <ere.maijala@helsinki.fi> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ + +namespace VuFind\Escaper; + +use Laminas\ServiceManager\Exception\ServiceNotCreatedException; +use Laminas\ServiceManager\Exception\ServiceNotFoundException; +use Laminas\ServiceManager\Factory\FactoryInterface; +use Psr\Container\ContainerExceptionInterface as ContainerException; +use Psr\Container\ContainerInterface; + +/** + * Escaper helper factory. + * + * @category VuFind + * @package Escaper + * @author Ere Maijala <ere.maijala@helsinki.fi> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ +class EscaperFactory implements FactoryInterface +{ + /** + * Create an object + * + * @param ContainerInterface $container Service manager + * @param string $requestedName Service being created + * @param null|array $options Extra options (optional) + * + * @return object + * + * @throws ServiceNotFoundException if unable to resolve the service. + * @throws ServiceNotCreatedException if an exception is raised when + * creating a service. + * @throws ContainerException&\Throwable if any other error occurs + */ + public function __invoke( + ContainerInterface $container, + $requestedName, + array $options = null + ) { + if (!empty($options)) { + throw new \Exception('Unexpected options sent to factory.'); + } + $config = $container->get(\VuFind\Config\PluginManager::class)->get('config'); + return new $requestedName((bool)($config->Site->extendedHtmlAttributeEscaping ?? false)); + } +} diff --git a/module/VuFind/src/VuFind/Form/Handler/Email.php b/module/VuFind/src/VuFind/Form/Handler/Email.php index 3d7b61efe46..b06316921c2 100644 --- a/module/VuFind/src/VuFind/Form/Handler/Email.php +++ b/module/VuFind/src/VuFind/Form/Handler/Email.php @@ -33,8 +33,8 @@ use Laminas\Config\Config; use Laminas\Log\LoggerAwareInterface; -use Laminas\Mail\Address; use Laminas\View\Renderer\RendererInterface; +use Symfony\Component\Mime\Address; use VuFind\Db\Entity\UserEntityInterface; use VuFind\Exception\Mail as MailException; use VuFind\Form\Form; @@ -127,7 +127,7 @@ public function handle( foreach ($recipients as $recipient) { if ($recipient['email']) { $success = $this->sendEmail( - $recipient['name'], + $recipient['name'] ?? '', $recipient['email'], $senderName, $senderEmail, @@ -167,14 +167,14 @@ protected function getSender(Form $form) /** * Send form data as email. * - * @param string $recipientName Recipient name - * @param string $recipientEmail Recipient email - * @param string $senderName Sender name - * @param string $senderEmail Sender email - * @param string $replyToName Reply-to name - * @param string $replyToEmail Reply-to email - * @param string $emailSubject Email subject - * @param string $emailMessage Email message + * @param ?string $recipientName Recipient name + * @param string $recipientEmail Recipient email + * @param string $senderName Sender name + * @param string $senderEmail Sender email + * @param string $replyToName Reply-to name + * @param string $replyToEmail Reply-to email + * @param string $emailSubject Email subject + * @param string $emailMessage Email message * * @return bool */ @@ -190,7 +190,7 @@ protected function sendEmail( ): bool { try { $this->mailer->send( - new Address($recipientEmail, $recipientName), + new Address($recipientEmail, $recipientName ?? ''), new Address($senderEmail, $senderName), $emailSubject, $emailMessage, diff --git a/module/VuFind/src/VuFind/I18n/Translator/LanguageInitializerTrait.php b/module/VuFind/src/VuFind/I18n/Translator/LanguageInitializerTrait.php index d45ce57ab95..c74b61a9666 100644 --- a/module/VuFind/src/VuFind/I18n/Translator/LanguageInitializerTrait.php +++ b/module/VuFind/src/VuFind/I18n/Translator/LanguageInitializerTrait.php @@ -30,9 +30,10 @@ namespace VuFind\I18n\Translator; use Laminas\I18n\Translator\TranslatorInterface; +use VuFind\Config\PathResolver; use VuFind\I18n\Locale\LocaleSettings; -use function strlen; +use function get_class; /** * Logic for initializing a language within a translator used by VuFind. @@ -45,19 +46,47 @@ */ trait LanguageInitializerTrait { + /** + * Path resolver. + * + * @var ?PathResolver + */ + protected ?PathResolver $pathResolver = null; + + /** + * Set path resolver. + * + * @param PathResolver $pathResolver Path resolver + * + * @return void + */ + public function setPathResolver(PathResolver $pathResolver): void + { + $this->pathResolver = $pathResolver; + } + /** * Look up all text domains. * * @return array */ - protected function getTextDomains() + protected function getTextDomains(): array { $base = APPLICATION_PATH; - $local = LOCAL_OVERRIDE_DIR; $languagePathParts = ["$base/languages"]; - if (strlen($local) > 0) { - $languagePathParts[] = "$local/languages"; + $localConfigDirStack = []; + if ($this->pathResolver === null) { + error_log( + 'No PathResolver was set for the LanguageInitializerTrait used by class ' + . get_class($this) . '.' + ); + } else { + $localConfigDirStack = $this->pathResolver->getLocalConfigDirStack(); } + $languagePathParts = array_merge($languagePathParts, array_map( + fn ($localConfigDir) => $localConfigDir['directory'] . '/languages', + $localConfigDirStack + )); $languagePathParts[] = "$base/themes/*/languages"; $domains = []; diff --git a/module/VuFind/src/VuFind/I18n/Translator/Loader/ExtendedIniFactory.php b/module/VuFind/src/VuFind/I18n/Translator/Loader/ExtendedIniFactory.php index c1cf9383cb3..61ad329902c 100644 --- a/module/VuFind/src/VuFind/I18n/Translator/Loader/ExtendedIniFactory.php +++ b/module/VuFind/src/VuFind/I18n/Translator/Loader/ExtendedIniFactory.php @@ -33,6 +33,7 @@ use Laminas\ServiceManager\Exception\ServiceNotFoundException; use Psr\Container\ContainerExceptionInterface as ContainerException; use Psr\Container\ContainerInterface; +use VuFind\Config\PathResolver; use VuFind\I18n\Locale\LocaleSettings; /** @@ -72,8 +73,13 @@ public function __invoke( } $pathStack = [ APPLICATION_PATH . '/languages', - LOCAL_OVERRIDE_DIR . '/languages', ]; + $localConfigDirStack = $container->get(PathResolver::class)->getLocalConfigDirStack(); + $localPathStack = array_map( + fn ($localConfigDir) => $localConfigDir['directory'] . '/languages', + $localConfigDirStack + ); + $pathStack = array_merge($pathStack, $localPathStack); $settings = $container->get(LocaleSettings::class); return new $requestedName($pathStack, $settings->getFallbackLocales()); } diff --git a/module/VuFind/src/VuFind/I18n/Translator/TranslatorFactory.php b/module/VuFind/src/VuFind/I18n/Translator/TranslatorFactory.php index 94999f5885e..a2e1fda7d78 100644 --- a/module/VuFind/src/VuFind/I18n/Translator/TranslatorFactory.php +++ b/module/VuFind/src/VuFind/I18n/Translator/TranslatorFactory.php @@ -35,6 +35,7 @@ use Laminas\ServiceManager\Factory\DelegatorFactoryInterface; use Psr\Container\ContainerExceptionInterface as ContainerException; use Psr\Container\ContainerInterface; +use VuFind\Config\PathResolver; use VuFind\I18n\Locale\LocaleSettings; use function extension_loaded; @@ -74,6 +75,7 @@ public function __invoke( callable $callback, array $options = null ) { + $this->setPathResolver($container->get(PathResolver::class)); $translator = $callback(); if (!extension_loaded('intl')) { error_log( diff --git a/module/VuFind/src/VuFind/ILS/Connection.php b/module/VuFind/src/VuFind/ILS/Connection.php index 0767d7ba4cf..fd3bef872da 100644 --- a/module/VuFind/src/VuFind/ILS/Connection.php +++ b/module/VuFind/src/VuFind/ILS/Connection.php @@ -34,6 +34,7 @@ namespace VuFind\ILS; use Laminas\Log\LoggerAwareInterface; +use Laminas\Session\Container; use VuFind\Exception\BadConfig; use VuFind\Exception\ILS as ILSException; use VuFind\I18n\Translator\TranslatorAwareInterface; @@ -44,6 +45,7 @@ use function count; use function func_get_args; use function get_class; +use function in_array; use function intval; use function is_array; use function is_callable; @@ -64,6 +66,10 @@ */ class Connection implements TranslatorAwareInterface, LoggerAwareInterface { + use \VuFind\Cache\CacheTrait { + getCachedData as getSharedCachedData; + putCachedData as putSharedCachedData; + } use \VuFind\I18n\Translator\TranslatorAwareTrait; use \VuFind\Log\LoggerAwareTrait; @@ -130,6 +136,42 @@ class Connection implements TranslatorAwareInterface, LoggerAwareInterface */ protected $request; + /** + * Cache life time per method + * + * @var array + */ + protected $cacheLifeTime = ['*' => 60]; + + /** + * Cache storage per method + * + * Note: Don't cache anything too large in session before + * https://openlibraryfoundation.atlassian.net/browse/VUFIND-1652 is implemented + * + * @var array + */ + protected $cacheStorage = [ + 'patronLogin' => 'session', + 'getProxiedUsers' => 'session', + 'getProxyingUsers' => 'session', + 'getPurchaseHistory' => 'shared', + ]; + + /** + * Methods that invalidate the session cache + * + * @var array + */ + protected $sessionCacheInvalidatingMethods = ['changePassword']; + + /** + * Session cache + * + * @var Container + */ + protected $sessionCache = null; + /** * Constructor * @@ -171,6 +213,31 @@ public function setHoldConfig($settings) return $this; } + /** + * Set session container for cache. + * + * @param Container $container Session container + * + * @return Connection + */ + public function setSessionCache(Container $container) + { + $this->sessionCache = $container; + return $this; + } + + /** + * Set cache lifetime settings + * + * @param array $settings Lifetime settings + * + * @return void + */ + public function setCacheLifeTime(array $settings): void + { + $this->cacheLifeTime = array_merge($this->cacheLifeTime, $settings); + } + /** * Get class name of the driver object. * @@ -1198,9 +1265,7 @@ public function getStatusParser() } /** - * Default method -- pass along calls to the driver if available; return - * false otherwise. This allows custom functions to be implemented in - * the driver without constant modification to the connection class. + * Call an ILS method with failover to NoILS if configured. * * @param string $methodName The name of the called method. * @param array $params Array of passed parameters. @@ -1208,7 +1273,7 @@ public function getStatusParser() * @throws ILSException * @return mixed Varies by method (false if undefined method) */ - public function __call($methodName, $params) + public function callIlsWithFailover($methodName, $params) { try { if ($this->checkCapability($methodName, $params)) { @@ -1227,4 +1292,114 @@ public function __call($methodName, $params) 'Cannot call method: ' . $this->getDriverClass() . '::' . $methodName ); } + + /** + * Get data for an ILS method from shared or session cache + * + * @param array $cacheSettings Cache settings + * + * @return ?array + */ + protected function getCachedData(array $cacheSettings): ?array + { + $cacheKey = $cacheSettings['key']; + if ('shared' === $cacheSettings['storage']) { + return $this->getSharedCachedData($cacheKey); + } + if ($this->sessionCache && ($entry = $this->sessionCache[$cacheKey] ?? null)) { + if (time() - $entry['ts'] <= $cacheSettings['lifeTime']) { + return $entry['payload']; + } + unset($this->sessionCache[$cacheKey]); + } + return null; + } + + /** + * Put data for an ILS method to shared or session cache. + * + * @param array $cacheSettings Cache settings + * @param array $data Data to cache + * + * @return void + */ + protected function putCachedData(array $cacheSettings, array $data): void + { + $cacheKey = $cacheSettings['key']; + if ('shared' === $cacheSettings['storage']) { + $this->putSharedCachedData($cacheKey, $data, $cacheSettings['lifeTime']); + return; + } + if ($this->sessionCache) { + $this->sessionCache[$cacheKey] = [ + 'ts' => time(), + 'payload' => $data, + ]; + } + } + + /** + * Clear session cache if the given method requires it + * + * @param string $methodName Method name + * + * @return void + */ + protected function clearSessionCacheIfRequired($methodName): void + { + if ($this->sessionCache && in_array($methodName, $this->sessionCacheInvalidatingMethods)) { + $this->sessionCache->exchangeArray([]); + } + } + + /** + * Get cache settings for a method + * + * @param string $methodName The name of the called method. + * @param array $params Array of passed parameters. + * + * @return ?array + */ + protected function getCacheSettings($methodName, $params): ?array + { + $lifeTime = (int)($this->cacheLifeTime[$methodName] ?? $this->cacheLifeTime['*'] ?? 0); + $storage = $this->cacheStorage[$methodName] ?? null; + if (!$lifeTime || !$storage) { + return null; + } + $key = $methodName . md5(serialize($params)); + return compact('lifeTime', 'storage', 'key'); + } + + /** + * Default method -- pass along calls to the driver if available; return + * false otherwise. This allows custom functions to be implemented in + * the driver without constant modification to the connection class. + * + * Results of certain methods (such as patronLogin) may be cached to avoid + * hammering the ILS with the same request repeatedly. + * + * @param string $methodName The name of the called method. + * @param array $params Array of passed parameters. + * + * @throws ILSException + * @return mixed Varies by method (false if undefined method) + */ + public function __call($methodName, $params) + { + $cacheSettings = $this->getCacheSettings($methodName, $params); + // Note: The actual data is cached in an array so that we can differentiate + // between a missing cache entry and null as a valid value. + if ($cacheSettings && ($cached = $this->getCachedData($cacheSettings))) { + return $cached['data']; + } + + $this->clearSessionCacheIfRequired($methodName); + + $data = $this->callIlsWithFailover($methodName, $params); + if ($cacheSettings) { + $this->putCachedData($cacheSettings, compact('data')); + } + return $data; + } } diff --git a/module/VuFind/src/VuFind/ILS/ConnectionFactory.php b/module/VuFind/src/VuFind/ILS/ConnectionFactory.php index 9521d8c35b4..635dc28024a 100644 --- a/module/VuFind/src/VuFind/ILS/ConnectionFactory.php +++ b/module/VuFind/src/VuFind/ILS/ConnectionFactory.php @@ -69,15 +69,23 @@ public function __invoke( throw new \Exception('Unexpected options sent to factory.'); } $configManager = $container->get(\VuFind\Config\PluginManager::class); + $config = $configManager->get('config'); $request = $container->get('Request'); $catalog = new $requestedName( - $configManager->get('config')->Catalog, + $config->Catalog, $container->get(\VuFind\ILS\Driver\PluginManager::class), $container->get(\VuFind\Config\PluginManager::class), $request instanceof \Laminas\Http\Request ? $request : null ); - return $catalog->setHoldConfig( + $catalog->setHoldConfig( $container->get(\VuFind\ILS\HoldSettings::class) ); + $catalog->setCacheStorage($container->get(\VuFind\Cache\Manager::class)->getCache('object')); + $manager = $container->get(\Laminas\Session\SessionManager::class); + $catalog->setSessionCache(new \Laminas\Session\Container('ILS', $manager)); + if ($cacheLifeTime = $config->Catalog?->cacheLifeTime?->toArray()) { + $catalog->setCacheLifeTime($cacheLifeTime); + } + return $catalog; } } diff --git a/module/VuFind/src/VuFind/ILS/Driver/Aleph.php b/module/VuFind/src/VuFind/ILS/Driver/Aleph.php index eb0b68d203f..bf251164152 100644 --- a/module/VuFind/src/VuFind/ILS/Driver/Aleph.php +++ b/module/VuFind/src/VuFind/ILS/Driver/Aleph.php @@ -1632,7 +1632,7 @@ public function getConfig($func, $params = []) /** * Get Pick Up Locations * - * This is responsible for gettting a list of valid library locations for + * This is responsible for getting a list of valid library locations for * holds / recall retrieval * * @param array $patron Patron information returned by the patronLogin method. diff --git a/module/VuFind/src/VuFind/ILS/Driver/Alma.php b/module/VuFind/src/VuFind/ILS/Driver/Alma.php index e9628ab8006..4555d9896be 100644 --- a/module/VuFind/src/VuFind/ILS/Driver/Alma.php +++ b/module/VuFind/src/VuFind/ILS/Driver/Alma.php @@ -1481,7 +1481,7 @@ public function getConfig($function, $params = []) * Place a hold request via Alma API. This could be a title level request or * an item level request. * - * @param array $holdDetails An associative array w/ atleast patron and item_id + * @param array $holdDetails An associative array w/ at least patron and item_id * * @return array success: bool, sysMessage: string * diff --git a/module/VuFind/src/VuFind/ILS/Driver/Demo.php b/module/VuFind/src/VuFind/ILS/Driver/Demo.php index 0cb472cfbc5..8e413fa559c 100644 --- a/module/VuFind/src/VuFind/ILS/Driver/Demo.php +++ b/module/VuFind/src/VuFind/ILS/Driver/Demo.php @@ -69,7 +69,7 @@ class Demo extends AbstractBase implements \VuFind\I18n\HasSorterInterface use \VuFind\I18n\HasSorterTrait; /** - * Catalog ID used to distinquish between multiple Demo driver instances with the + * Catalog ID used to distinguish between multiple Demo driver instances with the * MultiBackend driver * * @var string @@ -674,7 +674,12 @@ protected function createRequestList($requestType) */ public function getStatus($id) { - return $this->getSimulatedStatus($id); + $status = $this->getSimulatedStatus($id); + foreach (array_keys($status) as $i) { + $itemNum = $i + 1; + $status[$i] += $this->getNotesAndSummary($itemNum); + } + return $status; } /** @@ -830,6 +835,29 @@ function ($id) { return array_map([$this, 'getStatus'], $ids); } + /** + * Generate random notes and summary for inclusion in a status/holding array. + * + * @param int $itemNum Number of item having notes generated + * + * @return array + */ + protected function getNotesAndSummary(int $itemNum): array + { + $noteCount = rand(1, 3); + $fields = ['holdings_notes' => [], 'item_notes' => [], 'summary' => []]; + for ($j = 1; $j <= $noteCount; $j++) { + $fields['holdings_notes'][] = "Item $itemNum holdings note $j" + . ($j === 1 ? ' https://vufind.org/?f=1&b=2#sample_link' : ''); + $fields['item_notes'][] = "Item $itemNum note $j"; + } + $summCount = rand(1, 3); + for ($j = 1; $j <= $summCount; $j++) { + $fields['summary'][] = "Item $itemNum summary $j"; + } + return $fields; + } + /** * Get Holding * @@ -862,19 +890,7 @@ public function getHolding($id, array $patron = null, array $options = []) // Add notes and summary: foreach (array_keys($status) as $i) { $itemNum = $i + 1; - $noteCount = rand(1, 3); - $status[$i]['holdings_notes'] = []; - $status[$i]['item_notes'] = []; - for ($j = 1; $j <= $noteCount; $j++) { - $status[$i]['holdings_notes'][] = "Item $itemNum holdings note $j" - . ($j === 1 ? ' https://vufind.org/?f=1&b=2#sample_link' : ''); - $status[$i]['item_notes'][] = "Item $itemNum note $j"; - } - $summCount = rand(1, 3); - $status[$i]['summary'] = []; - for ($j = 1; $j <= $summCount; $j++) { - $status[$i]['summary'][] = "Item $itemNum summary $j"; - } + $status[$i] += $this->getNotesAndSummary($itemNum); $volume = intdiv($issue, 4) + 1; $seriesIssue = $issue % 4; $issue = $issue + 1; diff --git a/module/VuFind/src/VuFind/ILS/Driver/Folio.php b/module/VuFind/src/VuFind/ILS/Driver/Folio.php index 0bdef368d65..feddba46c9d 100644 --- a/module/VuFind/src/VuFind/ILS/Driver/Folio.php +++ b/module/VuFind/src/VuFind/ILS/Driver/Folio.php @@ -33,6 +33,7 @@ use DateTimeZone; use Exception; use Laminas\Http\Response; +use VuFind\Config\Feature\SecretTrait; use VuFind\Exception\ILS as ILSException; use VuFind\I18n\Translator\TranslatorAwareInterface; use VuFind\ILS\Logic\AvailabilityStatus; @@ -60,6 +61,7 @@ class Folio extends AbstractAPI implements HttpServiceAwareInterface, TranslatorAwareInterface { + use SecretTrait; use \VuFindHttp\HttpServiceAwareTrait; use \VuFind\I18n\Translator\TranslatorAwareTrait; use \VuFind\Log\LoggerAwareTrait { @@ -283,7 +285,7 @@ protected function renewTenantToken() $this->token = null; $response = $this->performOkapiUsernamePasswordAuthentication( $this->config['API']['username'], - $this->config['API']['password'] + $this->getSecretFromConfig($this->config['API'], 'password') ); $this->token = $this->extractTokenFromResponse($response); $this->sessionCache->folio_token = $this->token; @@ -390,7 +392,7 @@ protected function getItemById($itemId) } /** - * Given an instance object or identifer, or a holding or item identifier, + * Given an instance object or identifier, or a holding or item identifier, * determine an appropriate value to use as VuFind's bibliographic ID. * * @param string $instanceOrInstanceId Instance object or ID (will be looked up @@ -506,7 +508,26 @@ public function getStatuses($idList) */ public function getConfig($function, $params = []) { - return $this->config[$function] ?? false; + $key = match ($function) { + 'getMyTransactions' => 'Loans', + default => $function + }; + return $this->config[$key] ?? false; + } + + /** + * Check whether an item is holdable based on its location and any + * current loan + * + * @param string $locationName locationName from getHolding + * @param ?\stdClass $currentLoan The current loan, or null if none + * + * @return bool + */ + protected function isHoldable($locationName, $currentLoan = null) + { + return $this->isHoldableLocation($locationName) && + (!$currentLoan || $this->isHoldableByCurrentLoan($currentLoan)); } /** @@ -517,7 +538,7 @@ public function getConfig($function, $params = []) * * @return bool */ - protected function isHoldable($locationName) + protected function isHoldableLocation($locationName) { $mode = $this->config['Holds']['excludeHoldLocationsCompareMode'] ?? 'exact'; $excludeLocs = (array)($this->config['Holds']['excludeHoldLocations'] ?? []); @@ -544,6 +565,20 @@ protected function isHoldable($locationName) return !in_array($locationName, $excludeLocs); } + /** + * Check whether an item is holdable based on any current loan + * + * @param \stdClass $currentLoan The current loan + * + * @return bool + */ + protected function isHoldableByCurrentLoan(\stdClass $currentLoan) + { + $currentLoanPatronGroup = $currentLoan->patronGroupAtCheckout->name ?? ''; + $excludePatronGroups = $this->config['Holds']['excludeHoldCurrentLoanPatronGroups'] ?? []; + return !in_array($currentLoanPatronGroup, $excludePatronGroups); + } + /** * Gets locations from the /locations endpoint and sets * an array of location IDs to display names. @@ -694,25 +729,27 @@ protected function getHoldingDetailsForItem($holding): array /** * Support method for getHolding() -- return an array of item-level details from - * both FOLIO holdings and item records. + * other data: the location, the holdings record, and any current loan on the item. * * Depending on where this method is called, $locationId will be the holdings record * location (in the case where no items are attached to a holding) or the item record * location (in cases where there are attached items). * - * @param string $locationId Location identifier from FOLIO - * @param array $holdingDetails Holding details produced by getHoldingDetailsForItem() + * @param string $locationId Location identifier from FOLIO + * @param array $holdingDetails Holding details produced by getHoldingDetailsForItem() + * @param ?\stdClass $currentLoan Any current loan on this item * * @return array */ - protected function getItemFieldsFromLocAndHolding( + protected function getItemFieldsFromNonItemData( string $locationId, array $holdingDetails, + ?\stdClass $currentLoan = null, ): array { $locationData = $this->getLocationData($locationId); $locationName = $locationData['name']; return [ - 'is_holdable' => $this->isHoldable($locationName), + 'is_holdable' => $this->isHoldable($locationName, $currentLoan), 'holdings_notes' => $holdingDetails['hasHoldingNotes'] ? $holdingDetails['holdingNotes'] : null, 'summary' => array_unique($holdingDetails['holdingsStatements']), @@ -728,14 +765,15 @@ protected function getItemFieldsFromLocAndHolding( * Support method for getHolding() -- given a few key details, format an item * for inclusion in the return value. * - * @param string $bibId Current bibliographic ID - * @param array $holdingDetails Holding details produced by - * getHoldingDetailsForItem() - * @param object $item FOLIO item record (decoded from JSON) - * @param int $number The current item number (position within - * current holdings record) - * @param string $dueDateValue The due date to display to the user - * @param array $boundWithRecords Any bib records this holding is bound with + * @param string $bibId Current bibliographic ID + * @param array $holdingDetails Holding details produced by + * getHoldingDetailsForItem() + * @param object $item FOLIO item record (decoded from JSON) + * @param int $number The current item number (position within + * current holdings record) + * @param string $dueDateValue The due date to display to the user + * @param array $boundWithRecords Any bib records this holding is bound with + * @param ?\stdClass $currentLoan Any current loan on this item * * @return array */ @@ -746,6 +784,7 @@ protected function formatHoldingItem( $number, string $dueDateValue, $boundWithRecords, + $currentLoan ): array { $itemNotes = array_filter( array_map([$this, 'formatNote'], $item->notes ?? []) @@ -771,7 +810,7 @@ protected function formatHoldingItem( $item->effectiveCallNumberComponents->callNumber ?? $item->itemLevelCallNumber ?? '' ); - $locAndHoldings = $this->getItemFieldsFromLocAndHolding($locationId, $holdingDetails); + $locAndHoldings = $this->getItemFieldsFromNonItemData($locationId, $holdingDetails, $currentLoan); return $callNumberData + $locAndHoldings + [ 'id' => $bibId, @@ -785,7 +824,7 @@ protected function formatHoldingItem( 'availability' => $item->status->name == 'Available', 'item_notes' => !empty(implode($itemNotes)) ? $itemNotes : null, 'reserve' => 'TODO', - 'addLink' => true, + 'addLink' => 'check', 'bound_with_records' => $boundWithRecords, ]; } @@ -894,6 +933,7 @@ public function getHolding($bibId, array $patron = null, array $options = []) continue; } $number++; + $currentLoan = null; $dueDateValue = ''; $boundWithRecords = null; if ( @@ -901,7 +941,8 @@ public function getHolding($bibId, array $patron = null, array $options = []) && $showDueDate && $dueDateItemCount < $maxNumDueDateItems ) { - $dueDateValue = $this->getDueDate($item->id, $showTime); + $currentLoan = $this->getCurrentLoan($item->id); + $dueDateValue = $currentLoan ? $this->getDueDate($currentLoan, $showTime) : ''; $dueDateItemCount++; } if ($item->isBoundWith ?? false) { @@ -913,7 +954,8 @@ public function getHolding($bibId, array $patron = null, array $options = []) $item, $number, $dueDateValue, - $boundWithRecords ?? [] + $boundWithRecords ?? [], + $currentLoan ); if (!empty($vufindItemSort) && !empty($nextItem[$vufindItemSort])) { $sortNeeded = true; @@ -925,7 +967,7 @@ public function getHolding($bibId, array $patron = null, array $options = []) // fill it with data from the FOLIO holdings record, and make it not appear in // the full record display using a non-visible AvailabilityStatus. if ($number == 0 && $showHoldingsNoItems) { - $locAndHoldings = $this->getItemFieldsFromLocAndHolding($holding->effectiveLocationId, $holdingDetails); + $locAndHoldings = $this->getItemFieldsFromNonItemData($holding->effectiveLocationId, $holdingDetails); $invisibleAvailabilityStatus = new AvailabilityStatus( true, 'HoldingStatus::holding_no_items_availability_message' @@ -968,16 +1010,35 @@ protected function getDateTimeFromString(string $str): DateTime } /** - * Support method for getHolding(): obtaining the Due Date from OKAPI - * by calling /circulation/loans with the item->id, adjusting the - * timezone and formatting in universal time with or without due time + * Support method for getHolding(): obtaining the Due Date from the + * current loan, adjusting the timezone and formatting in universal + * time with or without due time * - * @param string $itemId ID for the item to query - * @param bool $showTime Determines if date or date & time is returned + * @param \stdClass|string $loan The current loan, or its itemId for backwards compatibility + * @param bool $showTime Determines if date or date & time is returned * * @return string */ - protected function getDueDate($itemId, $showTime) + protected function getDueDate($loan, $showTime) + { + if (is_string($loan)) { + $loan = $this->getCurrentLoan($loan); + } + $dueDate = $this->getDateTimeFromString($loan->dueDate); + $method = $showTime + ? 'convertToDisplayDateAndTime' : 'convertToDisplayDate'; + return $this->dateConverter->$method('U', $dueDate->format('U')); + } + + /** + * Support method for getHolding(): obtaining any current loan from OKAPI + * by calling /circulation/loans with the item->id + * + * @param string $itemId ID for the item to query + * + * @return \stdClass|void + */ + protected function getCurrentLoan($itemId) { $query = 'itemId==' . $itemId . ' AND status.name==Open'; foreach ( @@ -990,13 +1051,10 @@ protected function getDueDate($itemId, $showTime) // many loans are returned for an item, the one we want // is the one without a returnDate if (!isset($loan->returnDate) && isset($loan->dueDate)) { - $dueDate = $this->getDateTimeFromString($loan->dueDate); - $method = $showTime - ? 'convertToDisplayDateAndTime' : 'convertToDisplayDate'; - return $this->dateConverter->$method('U', $dueDate->format('U')); + return $loan; } } - return ''; + return null; } /** @@ -1128,6 +1186,52 @@ protected function fetchUserWithCql($query) return count($json->users ?? []) === 1 ? $json->users[0] : null; } + /** + * Get a total count of records from a FOLIO endpoint. + * + * @param string $interface FOLIO api interface to call + * @param array $query Extra GET parameters (e.g. ['query' => 'your cql here']) + * + * @return int + */ + protected function getResultCount(string $interface, array $query = []): int + { + $combinedQuery = array_merge($query, ['limit' => 0]); + $response = $this->makeRequest( + 'GET', + $interface, + $combinedQuery + ); + $json = json_decode($response->getBody()); + return $json->totalRecords ?? 0; + } + + /** + * Helper function to retrieve a single page of results from FOLIO API + * + * @param string $interface FOLIO api interface to call + * @param array $query Extra GET parameters (e.g. ['query' => 'your cql here']) + * @param int $offset Starting record index + * @param int $limit Max number of records to retrieve + * + * @return array + */ + protected function getResultPage($interface, $query = [], $offset = 0, $limit = 1000) + { + $combinedQuery = array_merge($query, compact('offset', 'limit')); + $response = $this->makeRequest( + 'GET', + $interface, + $combinedQuery + ); + $json = json_decode($response->getBody()); + if (!$response->isSuccess() || !$json) { + $msg = $json->errors[0]->message ?? json_last_error_msg(); + throw new ILSException("Error: '$msg' fetching from '$interface'"); + } + return $json; + } + /** * Helper function to retrieve paged results from FOLIO API * @@ -1143,17 +1247,7 @@ protected function getPagedResults($responseKey, $interface, $query = [], $limit $offset = 0; do { - $combinedQuery = array_merge($query, compact('offset', 'limit')); - $response = $this->makeRequest( - 'GET', - $interface, - $combinedQuery - ); - $json = json_decode($response->getBody()); - if (!$response->isSuccess() || !$json) { - $msg = $json->errors[0]->message ?? json_last_error_msg(); - throw new ILSException("Error: '$msg' fetching '$responseKey'"); - } + $json = $this->getResultPage($interface, $query, $offset, $limit); $totalEstimate = $json->totalRecords ?? 0; foreach ($json->$responseKey ?? [] as $item) { yield $item ?? ''; @@ -1223,6 +1317,10 @@ public function patronLogin($username, $password) 'firstname' => $profile->personal->firstName ?? null, 'lastname' => $profile->personal->lastName ?? null, 'email' => $profile->personal->email ?? null, + 'addressTypeIds' => array_map( + fn ($address) => $address->addressTypeId, + $profile->personal->addresses ?? [] + ), ]; } @@ -1275,8 +1373,9 @@ public function getMyProfile($patron) * This method queries the ILS for a patron's current checked out items * * Input: Patron array returned by patronLogin method - * Output: Returns an array of associative arrays. - * Each associative array contains these keys: + * Output: Returns with a 'count' key (overall result set size) and a 'records' + * key (current page of results) containing subarrays representing records + * and containing these keys: * duedate - The item's due date (a string). * dueTime - The item's due time (a string, optional). * dueStatus - A special status – may be 'due' (for items due very soon) @@ -1311,20 +1410,22 @@ public function getMyProfile($patron) * was checked out (optional – introduced in release 2.4) * * @param array $patron Patron login information from $this->patronLogin + * @param array $params Additional parameters (limit, page, sort) * - * @return array Transactions associative arrays + * @return array Transaction data as described above */ - public function getMyTransactions($patron) + public function getMyTransactions($patron, $params = []) { - $query = ['query' => 'userId==' . $patron['id'] . ' and status.name==Open']; + $limit = $params['limit'] ?? 1000; + $offset = isset($params['page']) ? ($params['page'] - 1) * $limit : 0; + + $query = 'userId==' . $patron['id'] . ' and status.name==Open'; + if (isset($params['sort'])) { + $query .= ' sortby ' . $this->escapeCql($params['sort']); + } + $resultPage = $this->getResultPage('/circulation/loans', compact('query'), $offset, $limit); $transactions = []; - foreach ( - $this->getPagedResults( - 'loans', - '/circulation/loans', - $query - ) as $trans - ) { + foreach ($resultPage->loans ?? [] as $trans) { $dueStatus = false; $date = $this->getDateTimeFromString($trans->dueDate); $dueDateTimestamp = $date->getTimestamp(); @@ -1355,7 +1456,14 @@ public function getMyTransactions($patron) 'title' => $trans->item->title, ]; } - return $transactions; + // If we have a full page or have applied an offset, we need to look up the total count of transactions: + $count = count($transactions); + if ($offset > 0 || $count >= $limit) { + // We could use the count in the result page, but that may be an estimate; + // safer to do a separate lookup to be sure we have the right number! + $count = $this->getResultCount('/circulation/loans', compact('query')); + } + return ['count' => $count, 'records' => $transactions]; } /** @@ -1454,6 +1562,22 @@ public function renewMyItems($renewDetails) */ public function getPickupLocations($patron, $holdInfo = null) { + if ('Delivery' == ($holdInfo['requestGroupId'] ?? null)) { + $addressTypes = $this->getAddressTypes(); + $limitDeliveryAddressTypes = $this->config['Holds']['limitDeliveryAddressTypes'] ?? []; + $deliveryPickupLocations = []; + foreach ($patron['addressTypeIds'] as $addressTypeId) { + $addressType = $addressTypes[$addressTypeId]; + if (empty($limitDeliveryAddressTypes) || in_array($addressType, $limitDeliveryAddressTypes)) { + $deliveryPickupLocations[] = [ + 'locationID' => $addressTypeId, + 'locationDisplay' => $addressType, + ]; + } + } + return $deliveryPickupLocations; + } + $limitedServicePoints = null; if ( str_contains($this->config['Holds']['limitPickupLocations'] ?? '', 'itemEffectiveLocation') @@ -1466,6 +1590,23 @@ public function getPickupLocations($patron, $holdInfo = null) $limitedServicePoints = $this->getLocationData($itemLocationId)['servicePointIds']; } + // If we have $holdInfo, we can limit ourselves to pickup locations that are valid in context. Because the + // allowed service point list doesn't include discovery display names, we can't use it directly; we just + // have to obtain a list of IDs to use as a filter below. + $legalServicePoints = null; + if ($holdInfo) { + $allowed = $this->getAllowedServicePoints($this->getInstanceByBibId($holdInfo['id'])->id, $patron['id']); + if ($allowed !== null) { + $legalServicePoints = []; + $preferredRequestType = $this->getPreferredRequestType($holdInfo); + foreach ($this->getRequestTypeList($preferredRequestType) as $requestType) { + foreach ($allowed[$requestType] ?? [] as $servicePoint) { + $legalServicePoints[] = $servicePoint['id']; + } + } + } + } + $query = ['query' => 'pickupLocation=true']; $locations = []; foreach ( @@ -1475,6 +1616,9 @@ public function getPickupLocations($patron, $holdInfo = null) $query ) as $servicePoint ) { + if ($legalServicePoints !== null && !in_array($servicePoint->id, $legalServicePoints)) { + continue; + } if ($limitedServicePoints && !in_array($servicePoint->id, $limitedServicePoints)) { continue; } @@ -1487,6 +1631,107 @@ public function getPickupLocations($patron, $holdInfo = null) return $locations; } + /** + * Get Default Pick Up Location + * + * Returns the default pick up location set in HorizonXMLAPI.ini + * + * @param array $patron Patron information returned by the patronLogin + * method. + * @param array $holdDetails Optional array, only passed in when getting a list + * in the context of placing a hold; contains most of the same values passed to + * placeHold, minus the patron data. May be used to limit the pickup options + * or may be ignored. + * + * @return false|string The default pickup location for the patron or false + * if the user has to choose. + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function getDefaultPickUpLocation($patron = false, $holdDetails = null) + { + if ('Delivery' == ($holdDetails['requestGroupId'] ?? null)) { + $deliveryPickupLocations = $this->getPickupLocations($patron, $holdDetails); + if (count($deliveryPickupLocations) == 1) { + return $deliveryPickupLocations[0]['locationDisplay']; + } + } + return false; + } + + /** + * Get request groups + * + * @param int $bibId BIB ID + * @param array $patron Patron information returned by the patronLogin + * method. + * @param array $holdDetails Optional array, only passed in when getting a list + * in the context of placing a hold; contains most of the same values passed to + * placeHold, minus the patron data. May be used to limit the request group + * options or may be ignored. + * + * @return array False if request groups not in use or an array of + * associative arrays with id and name keys + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function getRequestGroups( + $bibId = null, + $patron = null, + $holdDetails = null + ) { + // circulation-storage.request-preferences.collection.get + $response = $this->makeRequest( + 'GET', + '/request-preference-storage/request-preference?query=userId==' . $patron['id'] + ); + $requestPreferencesResponse = json_decode($response->getBody()); + $requestPreferences = $requestPreferencesResponse->requestPreferences[0]; + $allowHoldShelf = $requestPreferences->holdShelf; + $allowDelivery = $requestPreferences->delivery && ($this->config['Holds']['allowDelivery'] ?? true); + $locationsLabels = $this->config['Holds']['locationsLabelByRequestGroup'] ?? []; + if ($allowHoldShelf && $allowDelivery) { + return [ + [ + 'id' => 'Hold Shelf', + 'name' => 'fulfillment_method_hold_shelf', + 'locationsLabel' => $locationsLabels['Hold Shelf'] ?? null, + ], + [ + 'id' => 'Delivery', + 'name' => 'fulfillment_method_delivery', + 'locationsLabel' => $locationsLabels['Delivery'] ?? null, + ], + ]; + } + return false; + } + + /** + * Get list of address types from FOLIO. Cache as needed. + * + * @return array An array mapping an address type id to its name. + */ + protected function getAddressTypes() + { + $cacheKey = 'addressTypes'; + $addressTypes = $this->getCachedData($cacheKey); + if (null == $addressTypes) { + $addressTypes = []; + // addresstypes.collection.get + foreach ( + $this->getPagedResults( + 'addressTypes', + '/addresstypes' + ) as $addressType + ) { + $addressTypes[$addressType->id] = $addressType->addressType; + } + $this->putCachedData($cacheKey, $addressTypes); + } + return $addressTypes; + } + /** * This method queries the ILS for a patron's current holds * @@ -1691,6 +1936,55 @@ protected function performHoldRequest(array $requestBody): array ]; } + /** + * Get allowed service points for a request. Returns null if data cannot be obtained. + * + * @param string $instanceId Instance UUID being requested + * @param string $requesterId Patron UUID placing request + * @param string $operation Operation type (default = create) + * + * @return ?array + */ + public function getAllowedServicePoints( + string $instanceId, + string $requesterId, + string $operation = 'create' + ): ?array { + try { + // circulation.requests.allowed-service-points.get + $response = $this->makeRequest( + 'GET', + '/circulation/requests/allowed-service-points?' + . http_build_query(compact('instanceId', 'requesterId', 'operation')) + ); + if (!$response->isSuccess()) { + $this->warning('Unexpected service point lookup response: ' . $response->getBody()); + return null; + } + } catch (\Exception $e) { + $this->warning('Exception during allowed service point lookup: ' . (string)$e); + return null; + } + return json_decode($response->getBody(), true); + } + + /** + * Get the preferred request type for the provided hold details. + * + * @param array $holdDetails An array of item and patron data + * + * @return string + */ + protected function getPreferredRequestType(array $holdDetails): string + { + $default_request = $this->config['Holds']['default_request'] ?? 'Hold'; + $isTitleLevel = ($holdDetails['level'] ?? '') === 'title'; + if ($isTitleLevel) { + return $default_request; + } + return ($holdDetails['status'] ?? '') == 'Available' ? 'Page' : $default_request; + } + /** * Place Hold * @@ -1704,7 +1998,6 @@ protected function performHoldRequest(array $requestBody): array */ public function placeHold($holdDetails) { - $default_request = $this->config['Holds']['default_request'] ?? 'Hold'; if ( !empty($holdDetails['requiredByTS']) && !is_int($holdDetails['requiredByTS']) @@ -1714,14 +2007,13 @@ public function placeHold($holdDetails) $requiredBy = !empty($holdDetails['requiredByTS']) ? gmdate('Y-m-d', $holdDetails['requiredByTS']) : null; + $instance = $this->getInstanceByBibId($holdDetails['id']); $isTitleLevel = ($holdDetails['level'] ?? '') === 'title'; if ($isTitleLevel) { - $instance = $this->getInstanceByBibId($holdDetails['id']); $baseParams = [ 'instanceId' => $instance->id, 'requestLevel' => 'Title', ]; - $preferredRequestType = $default_request; } else { // Note: early Lotus releases require instanceId and holdingsRecordId // to be set here as well, but the requirement was lifted in a hotfix @@ -1729,18 +2021,21 @@ public function placeHold($holdDetails) // of those versions, you can add additional identifiers here, but // applying the latest hotfix is a better solution! $baseParams = ['itemId' => $holdDetails['item_id']]; - $preferredRequestType = ($holdDetails['status'] ?? '') == 'Available' - ? 'Page' : $default_request; } // Account for an API spelling change introduced in mod-circulation v24: $fulfillmentKey = $this->getModuleMajorVersion('mod-circulation') >= 24 ? 'fulfillmentPreference' : 'fulfilmentPreference'; + $fulfillmentValue = $holdDetails['requestGroupId'] ?? 'Hold Shelf'; + $fulfillmentLocationKey = match ($fulfillmentValue) { + 'Hold Shelf' => 'pickupServicePointId', + 'Delivery' => 'deliveryAddressTypeId', + }; $requestBody = $baseParams + [ 'requesterId' => $holdDetails['patron']['id'], 'requestDate' => date('c'), - $fulfillmentKey => 'Hold Shelf', + $fulfillmentKey => $fulfillmentValue, 'requestExpirationDate' => $requiredBy, - 'pickupServicePointId' => $holdDetails['pickUpLocation'], + $fulfillmentLocationKey => $holdDetails['pickUpLocation'], ]; if (!empty($holdDetails['proxiedUser'])) { $requestBody['requesterId'] = $holdDetails['proxiedUser']; @@ -1749,7 +2044,20 @@ public function placeHold($holdDetails) if (!empty($holdDetails['comment'])) { $requestBody['patronComments'] = $holdDetails['comment']; } + $allowed = $this->getAllowedServicePoints($instance->id, $holdDetails['patron']['id']); + $preferredRequestType = $this->getPreferredRequestType($holdDetails); foreach ($this->getRequestTypeList($preferredRequestType) as $requestType) { + // Skip illegal request types, if we have validation data available: + if (null !== $allowed) { + if ( + // Unsupported request type: + !isset($allowed[$requestType]) + // Unsupported pickup location: + || !in_array($holdDetails['pickUpLocation'], array_column($allowed[$requestType] ?? [], 'id')) + ) { + continue; + } + } $requestBody['requestType'] = $requestType; $result = $this->performHoldRequest($requestBody); if ($result['success']) { @@ -1834,6 +2142,37 @@ public function cancelHolds($cancelDetails) return $cancelResult; } + /** + * Check if request is valid + * + * This is responsible for determining if an item is requestable + * + * @param string $id The record id + * @param array $data An array of item data + * @param array $patron An array of patron data + * + * @return array Two entries: 'valid' (boolean) plus 'status' (message to display to user) + */ + public function checkRequestIsValid($id, $data, $patron) + { + // Check outstanding loans + $currentLoan = $this->getCurrentLoan($data['item_id']); + if (!$currentLoan || $this->isHoldableByCurrentLoan($currentLoan)) { + $allowed = $this->getAllowedServicePoints($this->getInstanceByBibId($id)->id, $patron['id']); + return [ + // If we got this far, it's valid if we can't obtain allowed service point + // data, or if the allowed service point data is non-empty: + 'valid' => null === $allowed || !empty($allowed), + 'status' => 'request_place_text', + ]; + } else { + return [ + 'valid' => false, + 'status' => 'hold_error_current_loan_patron_group', + ]; + } + } + /** * Obtain a list of course resources, creating an id => value associative array. * diff --git a/module/VuFind/src/VuFind/ILS/Driver/HorizonXMLAPI.php b/module/VuFind/src/VuFind/ILS/Driver/HorizonXMLAPI.php index e1effd36145..fba335654f7 100644 --- a/module/VuFind/src/VuFind/ILS/Driver/HorizonXMLAPI.php +++ b/module/VuFind/src/VuFind/ILS/Driver/HorizonXMLAPI.php @@ -71,7 +71,7 @@ class HorizonXMLAPI extends Horizon implements \VuFindHttp\HttpServiceAwareInter protected $wsPickUpLocations; /** - * Defaut pickup location for holds + * Default pickup location for holds * * @var string */ @@ -561,7 +561,7 @@ protected function renewItems($session, $items) * @param string $session A valid Horizon session key * @param array $requestDetails An array of request details * - * @return array An array witk keys indicating the a success (boolean), + * @return array An array with keys indicating the success (boolean), * status (string) and sysMessage (string) if available */ protected function placeRequest($session, $requestDetails) @@ -676,7 +676,7 @@ protected function cancelRequest($session, $data) } } - // Go through the submited bib ids and look for a match + // Go through the submitted bib ids and look for a match foreach ($data as $values) { $itemID = $values['item_id']; // If the bib id is matched, the cancel must have failed diff --git a/module/VuFind/src/VuFind/ILS/Driver/KohaILSDI.php b/module/VuFind/src/VuFind/ILS/Driver/KohaILSDI.php index 5450fad41b5..e26d20ee42e 100644 --- a/module/VuFind/src/VuFind/ILS/Driver/KohaILSDI.php +++ b/module/VuFind/src/VuFind/ILS/Driver/KohaILSDI.php @@ -568,7 +568,7 @@ public function getConfig($function, $params = []) /** * Get Pick Up Locations * - * This is responsible for gettting a list of valid library locations for + * This is responsible for getting a list of valid library locations for * holds / recall retrieval * * @param array $patron Patron information returned by the patronLogin diff --git a/module/VuFind/src/VuFind/ILS/Driver/KohaRest.php b/module/VuFind/src/VuFind/ILS/Driver/KohaRest.php index db53614740a..e28b277eda5 100644 --- a/module/VuFind/src/VuFind/ILS/Driver/KohaRest.php +++ b/module/VuFind/src/VuFind/ILS/Driver/KohaRest.php @@ -1030,7 +1030,7 @@ public function cancelHolds($cancelDetails) /** * Get Pick Up Locations * - * This is responsible for gettting a list of valid library locations for + * This is responsible for getting a list of valid library locations for * holds / recall retrieval * * @param array $patron Patron information returned by the patronLogin diff --git a/module/VuFind/src/VuFind/ILS/Driver/PAIA.php b/module/VuFind/src/VuFind/ILS/Driver/PAIA.php index 99ec486bfb8..f0405d70329 100644 --- a/module/VuFind/src/VuFind/ILS/Driver/PAIA.php +++ b/module/VuFind/src/VuFind/ILS/Driver/PAIA.php @@ -880,7 +880,7 @@ public function getNewItems($page, $limit, $daysOld, $fundID) /** * Get Pick Up Locations * - * This is responsible for gettting a list of valid library locations for + * This is responsible for getting a list of valid library locations for * holds / recall retrieval * * @param array $patron Patron information returned by the patronLogin @@ -1746,7 +1746,7 @@ protected function paiaGetRequest($file, $access_token) } /** - * Helper function for PAIA to uniformely parse JSON + * Helper function for PAIA to uniformly parse JSON * * @param string $file JSON data * diff --git a/module/VuFind/src/VuFind/ILS/Driver/Polaris.php b/module/VuFind/src/VuFind/ILS/Driver/Polaris.php index d4a29b7d681..d31b5351c8c 100644 --- a/module/VuFind/src/VuFind/ILS/Driver/Polaris.php +++ b/module/VuFind/src/VuFind/ILS/Driver/Polaris.php @@ -462,7 +462,7 @@ public function placeHold($holdDetails) /** * Get Pick Up Locations * - * This is responsible for gettting a list of valid library locations for + * This is responsible for getting a list of valid library locations for * holds / recall retrieval * * @param array $patron Patron information returned by the patronLogin diff --git a/module/VuFind/src/VuFind/ILS/Driver/SierraRest.php b/module/VuFind/src/VuFind/ILS/Driver/SierraRest.php index da327e21ae6..fe5e0aeb20f 100644 --- a/module/VuFind/src/VuFind/ILS/Driver/SierraRest.php +++ b/module/VuFind/src/VuFind/ILS/Driver/SierraRest.php @@ -1370,7 +1370,7 @@ public function updateHolds( /** * Get Pick Up Locations * - * This is responsible for gettting a list of valid library locations for + * This is responsible for getting a list of valid library locations for * holds / recall retrieval * * @param array $patron Patron information returned by the patronLogin diff --git a/module/VuFind/src/VuFind/ILS/Driver/Unicorn.php b/module/VuFind/src/VuFind/ILS/Driver/Unicorn.php index c759ee22a71..28ed63302dc 100644 --- a/module/VuFind/src/VuFind/ILS/Driver/Unicorn.php +++ b/module/VuFind/src/VuFind/ILS/Driver/Unicorn.php @@ -162,7 +162,7 @@ public function getConfig($function, $params = []) /** * Get Pick Up Locations * - * This is responsible for gettting a list of valid library locations for + * This is responsible for getting a list of valid library locations for * holds / recall retrieval * * @param array $patron Patron information returned by the patronLogin diff --git a/module/VuFind/src/VuFind/ILS/Driver/Virtua.php b/module/VuFind/src/VuFind/ILS/Driver/Virtua.php index 45d26690ae1..1343003c1fb 100644 --- a/module/VuFind/src/VuFind/ILS/Driver/Virtua.php +++ b/module/VuFind/src/VuFind/ILS/Driver/Virtua.php @@ -481,7 +481,7 @@ public function getHolding($id, array $patron = null, array $options = []) * - Return the holdings array with true/false and a reason. * * Because of the location comparisons with the patron's - * location that occur here we also take the oppurtunity + * location that occur here we also take the opportunity * to push their "Home" location to the top. * * @param string $patron_id ID of patron diff --git a/module/VuFind/src/VuFind/ILS/Driver/VoyagerRestful.php b/module/VuFind/src/VuFind/ILS/Driver/VoyagerRestful.php index 5ed7d5a1568..ea97cfdc968 100644 --- a/module/VuFind/src/VuFind/ILS/Driver/VoyagerRestful.php +++ b/module/VuFind/src/VuFind/ILS/Driver/VoyagerRestful.php @@ -682,7 +682,7 @@ protected function pickUpLocationIsValid($pickUpLocation, $patron, $holdDetails) /** * Get Pick Up Locations * - * This is responsible for gettting a list of valid library locations for + * This is responsible for getting a list of valid library locations for * holds / recall retrieval * * @param array $patron Patron information returned by the patronLogin diff --git a/module/VuFind/src/VuFind/ILS/Logic/Holds.php b/module/VuFind/src/VuFind/ILS/Logic/Holds.php index f7dabb1ab7c..6f8a7702655 100644 --- a/module/VuFind/src/VuFind/ILS/Logic/Holds.php +++ b/module/VuFind/src/VuFind/ILS/Logic/Holds.php @@ -534,6 +534,13 @@ protected function getRequestDetails($details, $HMACKeys, $action) // Include request type in the details $details['requestType'] = $action; + if ( + ($details['availability'] ?? null) instanceof AvailabilityStatusInterface + && empty($details['status']) + ) { + $details['status'] = $details['availability']->getStatusDescription(); + } + // Generate HMAC $HMACkey = $this->hmac->generate($HMACKeys, $details); diff --git a/module/VuFind/src/VuFind/Log/LoggerFactory.php b/module/VuFind/src/VuFind/Log/LoggerFactory.php index 49fb7715ad1..3d730d35dcc 100644 --- a/module/VuFind/src/VuFind/Log/LoggerFactory.php +++ b/module/VuFind/src/VuFind/Log/LoggerFactory.php @@ -36,6 +36,7 @@ use Laminas\ServiceManager\Factory\FactoryInterface; use Psr\Container\ContainerExceptionInterface as ContainerException; use Psr\Container\ContainerInterface; +use VuFind\Config\Feature\EmailSettingsTrait; use function count; use function is_array; @@ -54,6 +55,8 @@ */ class LoggerFactory implements FactoryInterface { + use EmailSettingsTrait; + /** * Configure database writers. * @@ -109,16 +112,14 @@ protected function addEmailWriters( $email = $parts[0]; $error_types = $parts[1] ?? ''; - // use smtp - $mailer = $container->get(\VuFind\Mailer\Mailer::class); - $msg = $mailer->getNewMessage() - ->addFrom($config->Site->email) - ->addTo($email) - ->setSubject('VuFind Log Message'); - // Make Writers $filters = explode(',', $error_types); - $writer = new Writer\Mail($msg, $mailer->getTransport()); + $writer = new Writer\Mail( + $container->get(\VuFind\Mailer\Mailer::class), + $this->getEmailSenderAddress($config), + $email, + 'VuFind Log Message' + ); $this->addWriters($logger, $writer, $filters); } diff --git a/module/VuFind/src/VuFind/Log/Writer/Mail.php b/module/VuFind/src/VuFind/Log/Writer/Mail.php index 822c27b01fe..7953183dfe6 100644 --- a/module/VuFind/src/VuFind/Log/Writer/Mail.php +++ b/module/VuFind/src/VuFind/Log/Writer/Mail.php @@ -3,9 +3,11 @@ /** * Mail log writer * + * Inspired by Laminas Mail log writer + * * PHP version 8 * - * Copyright (C) Villanova University 2010. + * Copyright (C) The National Library of Finland 2024. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -22,26 +24,70 @@ * * @category VuFind * @package Error_Logging - * @author Chris Hallberg <challber@villanova.edu> + * @author Ere Maijala <ere.maijala@helsinki.fi> * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License * @link https://vufind.org Main Site */ namespace VuFind\Log\Writer; +use Laminas\Log\Formatter\FormatterInterface; +use Laminas\Log\Formatter\Simple as SimpleFormatter; +use Laminas\Log\Writer\AbstractWriter; +use Symfony\Component\Mime\Email; +use VuFind\Exception\Mail as MailException; +use VuFind\Mailer\Mailer; + /** - * This class extends the Laminas Logging towards Mail systems + * This class implements the Laminas Logging interface for Mail systems + * + * Inspired by Laminas Mail log writer * * @category VuFind * @package Error_Logging - * @author Chris Hallberg <challber@villanova.edu> + * @author Ere Maijala <ere.maijala@helsinki.fi> * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License * @link https://vufind.org Main Site */ -class Mail extends \Laminas\Log\Writer\Mail +class Mail extends AbstractWriter { use VerbosityTrait; + /** + * Array of formatted events to include in message body. + * + * @var array + */ + protected $eventsToMail = []; + + /** + * Array keeping track of the number of entries per priority level. + * + * @var array + */ + protected $numEntriesPerPriority = []; + + /** + * Constructor + * + * @param Mailer $mailer Mailer + * @param string $from Sender address + * @param string $to Recipient address + * @param string $subject Email subject + * @param ?FormatterInterface $formatter Log entry formatter + * + * @throws Exception\InvalidArgumentException + */ + public function __construct( + protected Mailer $mailer, + protected string $from, + protected string $to, + protected string $subject, + ?FormatterInterface $formatter = null + ) { + $this->setFormatter($formatter ?? new SimpleFormatter()); + } + /** * Write a message to the log. * @@ -52,7 +98,63 @@ class Mail extends \Laminas\Log\Writer\Mail */ protected function doWrite(array $event) { - // Apply verbosity, Call parent method: - parent::doWrite($this->applyVerbosity($event)); + $event = $this->applyVerbosity($event); + // Track the number of entries per priority level. + if (!isset($this->numEntriesPerPriority[$event['priorityName']])) { + $this->numEntriesPerPriority[$event['priorityName']] = 1; + } else { + $this->numEntriesPerPriority[$event['priorityName']]++; + } + + // All plaintext events are to use the standard formatter. + $this->eventsToMail[] = $this->formatter->format($event); + } + + /** + * Sends mail to recipient(s) if log entries are present. Note that both + * plaintext and HTML portions of email are handled here. + * + * @return void + */ + public function shutdown() + { + if (!$this->eventsToMail) { + return; + } + + // Merge all messages into a single text: + $message = implode(PHP_EOL, $this->eventsToMail); + + // Finally, send the mail. If an exception occurs, convert it into a + // warning-level message so we can avoid an exception thrown without a + // stack frame. + // N.B. Logger cannot be used when reporting errors with Logger! + try { + $this->mailer->send( + $this->to, + $this->from, + $this->subject, + $message + ); + } catch (MailException $e) { + trigger_error('Unable to send log entries via email: ' . (string)$e, E_USER_WARNING); + } + } + + /** + * Gets a string of number of entries per-priority level that occurred, or + * an empty string if none occurred. + * + * @return string + */ + protected function getFormattedNumEntriesPerPriority() + { + $strings = []; + + foreach ($this->numEntriesPerPriority as $priority => $numEntries) { + $strings[] = "{$priority}={$numEntries}"; + } + + return implode(', ', $strings); } } diff --git a/module/VuFind/src/VuFind/Mailer/Bcc.php b/module/VuFind/src/VuFind/Mailer/Bcc.php deleted file mode 100644 index 9eb625c6bb0..00000000000 --- a/module/VuFind/src/VuFind/Mailer/Bcc.php +++ /dev/null @@ -1,44 +0,0 @@ -<?php - -/** - * Tweaked Laminas "Bcc" header class - * - * PHP version 8 - * - * Copyright (C) The National Library of Finland 2023. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2, - * as published by the Free Software Foundation. - * - * 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 - * - * @category VuFind - * @package Mailer - * @author Ere Maijala <ere.maijala@helsinki.fi> - * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License - * @link https://vufind.org/wiki/development Wiki - */ - -namespace VuFind\Mailer; - -/** - * Tweaked Laminas "Bcc" header class - * - * @category VuFind - * @package Mailer - * @author Ere Maijala <ere.maijala@helsinki.fi> - * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License - * @link https://vufind.org/wiki/development Wiki - */ -class Bcc extends \Laminas\Mail\Header\Bcc -{ - use GetFieldValueFixTrait; -} diff --git a/module/VuFind/src/VuFind/Mailer/Factory.php b/module/VuFind/src/VuFind/Mailer/Factory.php index f05f4568a8e..43a393f17d1 100644 --- a/module/VuFind/src/VuFind/Mailer/Factory.php +++ b/module/VuFind/src/VuFind/Mailer/Factory.php @@ -6,6 +6,7 @@ * PHP version 8 * * Copyright (C) Villanova University 2009. + * Copyright (C) The National Library of Finland 2024. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -23,20 +24,19 @@ * @category VuFind * @package Mailer * @author Demian Katz <demian.katz@villanova.edu> + * @author Ere Maijala <ere.maijala@helsinki.fi> * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License * @link https://vufind.org/wiki/development Wiki */ namespace VuFind\Mailer; -use Laminas\Mail\Transport\InMemory; -use Laminas\Mail\Transport\Smtp; -use Laminas\Mail\Transport\SmtpOptions; use Laminas\ServiceManager\Exception\ServiceNotCreatedException; use Laminas\ServiceManager\Exception\ServiceNotFoundException; use Laminas\ServiceManager\Factory\FactoryInterface; use Psr\Container\ContainerExceptionInterface as ContainerException; use Psr\Container\ContainerInterface; +use VuFind\Config\Feature\SecretTrait; /** * Factory for instantiating Mailer objects @@ -44,6 +44,7 @@ * @category VuFind * @package Mailer * @author Demian Katz <demian.katz@villanova.edu> + * @author Ere Maijala <ere.maijala@helsinki.fi> * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License * @link https://vufind.org/wiki/development Wiki * @@ -51,48 +52,52 @@ */ class Factory implements FactoryInterface { + use SecretTrait; + /** - * Build the mail transport object. + * Return DSN from the configuration * - * @param \Laminas\Config\Config $config Configuration + * @param array $config Configuration * - * @return InMemory|Smtp + * @return string */ - protected function getTransport($config) + protected function getDSN(array $config): string { - // In test mode? Return fake object: - if (isset($config->Mail->testOnly) && $config->Mail->testOnly) { - return new InMemory(); + // In test mode? Use null transport: + if ($config['Mail']['testOnly'] ?? false) { + return 'null://null'; + } + + if ($dsn = $config['Mail']['dsn'] ?? null) { + return $dsn; } - // Create mail transport: - $settings = [ - 'host' => $config->Mail->host, 'port' => $config->Mail->port, - ]; - if (isset($config->Mail->name)) { - $settings['name'] = $config->Mail->name; + // Create DSN from settings: + $protocol = ($config['Mail']['secure'] ?? false) ? 'smtps' : 'smtp'; + $dsn = "$protocol://"; + if ( + ($username = $config['Mail']['username'] ?? null) + && ($password = $this->getSecretFromConfig($config['Mail'], 'password')) + ) { + $dsn .= "$username:$password@"; } - if (isset($config->Mail->username) && isset($config->Mail->password)) { - $settings['connection_class'] = 'login'; - $settings['connection_config'] = [ - 'username' => $config->Mail->username, - 'password' => $config->Mail->password, - ]; - // Set user defined secure connection if provided; otherwise set default - // secure connection based on configured port number. - if (isset($config->Mail->secure)) { - $settings['connection_config']['ssl'] = $config->Mail->secure; - } elseif ($settings['port'] == '587') { - $settings['connection_config']['ssl'] = 'tls'; - } elseif ($settings['port'] == '487') { - $settings['connection_config']['ssl'] = 'ssl'; - } + $dsn .= $config['Mail']['host']; + if ($port = $config['Mail']['port'] ?? null) { + $dsn .= ":$port"; } - if (isset($config->Mail->connection_time_limit)) { - $settings['connection_time_limit'] - = $config->Mail->connection_time_limit; + + $dsnParams = []; + if ($name = $config['Mail']['name'] ?? null) { + $dsnParams['local_domain'] = $name; } - return new Smtp(new SmtpOptions($settings)); + if (null !== ($limit = $config['Mail']['connection_time_limit'] ?? null)) { + $dsnParams['ping_threshold'] = $limit; + } + if ($dsnParams) { + $dsn .= '?' . http_build_query($dsnParams); + } + + return $dsn; } /** @@ -119,16 +124,20 @@ public function __invoke( } // Load configurations: - $config = $container->get(\VuFind\Config\PluginManager::class) - ->get('config'); + $config = $container->get(\VuFind\Config\PluginManager::class)->get('config')->toArray(); // Create service: $class = new $requestedName( - $this->getTransport($config), - $config->Mail->message_log + new \Symfony\Component\Mailer\Mailer( + \Symfony\Component\Mailer\Transport::fromDsn($this->getDSN($config)) + ), + [ + 'message_log' => $config['Mail']['message_log'] ?? null, + 'message_log_format' => $config['Mail']['message_log_format'] ?? null, + ] ); if (!empty($config->Mail->override_from)) { - $class->setFromAddressOverride($config->Mail->override_from); + $class->setFromAddressOverride($config['Mail']['override_from'] ?? null); } return $class; } diff --git a/module/VuFind/src/VuFind/Mailer/GetFieldValueFixTrait.php b/module/VuFind/src/VuFind/Mailer/GetFieldValueFixTrait.php deleted file mode 100644 index 1390c21bddb..00000000000 --- a/module/VuFind/src/VuFind/Mailer/GetFieldValueFixTrait.php +++ /dev/null @@ -1,101 +0,0 @@ -<?php - -/** - * Trait that provides an improved version of the getFieldValue method. - * - * PHP version 8 - * - * Copyright (C) The National Library of Finland 2023. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2, - * as published by the Free Software Foundation. - * - * 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 - * - * @category VuFind - * @package Mailer - * @author Ere Maijala <ere.maijala@helsinki.fi> - * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License - * @link https://vufind.org/wiki/development Wiki - */ - -namespace VuFind\Mailer; - -use Laminas\Mail\Header\HeaderInterface; -use Laminas\Mail\Header\HeaderValue; -use Laminas\Mail\Header\HeaderWrap; -use Laminas\Mail\Headers; - -use function sprintf; - -/** - * Trait that provides an improved version of the getFieldValue method. - * - * @category VuFind - * @package Mailer - * @author Ere Maijala <ere.maijala@helsinki.fi> - * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License - * @link https://vufind.org/wiki/development Wiki - */ -trait GetFieldValueFixTrait -{ - /** - * Retrieve header value - * - * Overrides the original implementation to always enclose name in quotes. - * - * @param bool $format Return the value in Mime::Encoded (HeaderInterface::FORMAT_ENCODED) or - * in Raw format (HeaderInterface::FORMAT_RAW). Using a constant from HeaderInterface is - * recommended instead of a raw boolean value. - * - * @return string - */ - public function getFieldValue($format = HeaderInterface::FORMAT_RAW) - { - $emails = []; - $encoding = $this->getEncoding(); - - foreach ($this->getAddressList() as $address) { - $email = $address->getEmail(); - $name = $address->getName(); - - if ( - $format === HeaderInterface::FORMAT_ENCODED - && 'ASCII' !== $encoding - ) { - if (! empty($name)) { - $name = HeaderWrap::mimeEncodeValue($name, $encoding); - } - - if (preg_match('/^(.+)@([^@]+)$/', $email, $matches)) { - $localPart = $matches[1]; - $hostname = $this->idnToAscii($matches[2]); - $email = sprintf('%s@%s', $localPart, $hostname); - } - } - - if (empty($name)) { - $emails[] = $email; - } else { - $emails[] = sprintf('"%s" <%s>', $name, $email); - } - } - - // Ensure the values are valid before sending them. - if ($format !== HeaderInterface::FORMAT_RAW) { - foreach ($emails as $email) { - HeaderValue::assertValid($email); - } - } - - return implode(',' . Headers::FOLDING, $emails); - } -} diff --git a/module/VuFind/src/VuFind/Mailer/Mailer.php b/module/VuFind/src/VuFind/Mailer/Mailer.php index 5d28ab88056..446e8acc2e7 100644 --- a/module/VuFind/src/VuFind/Mailer/Mailer.php +++ b/module/VuFind/src/VuFind/Mailer/Mailer.php @@ -6,6 +6,7 @@ * PHP version 8 * * Copyright (C) Villanova University 2009. + * Copyright (C) The National Library of Finland 2024. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -23,23 +24,23 @@ * @category VuFind * @package Mailer * @author Demian Katz <demian.katz@villanova.edu> + * @author Ere Maijala <ere.maijala@helsinki.fi> * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License * @link https://vufind.org/wiki/development Wiki */ namespace VuFind\Mailer; -use Laminas\Mail\Address; -use Laminas\Mail\AddressList; -use Laminas\Mail\Header\ContentType; -use Laminas\Mail\Transport\TransportInterface; -use Laminas\Mime\Message as MimeMessage; -use Laminas\Mime\Mime; -use Laminas\Mime\Part as MimePart; +use Laminas\View\Renderer\PhpRenderer; +use Symfony\Component\Mailer\MailerInterface; +use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\Email; +use Symfony\Component\Mime\Exception\RfcComplianceException; use VuFind\Exception\Mail as MailException; +use VuFind\RecordDriver\AbstractBase; use function count; -use function is_callable; +use function is_array; /** * VuFind Mailer Class @@ -47,6 +48,7 @@ * @category VuFind * @package Mailer * @author Demian Katz <demian.katz@villanova.edu> + * @author Ere Maijala <ere.maijala@helsinki.fi> * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License * @link https://vufind.org/wiki/development Wiki */ @@ -60,17 +62,14 @@ class Mailer implements /** * Mail transport * - * @var TransportInterface + * @var MailerInterface */ protected $transport; /** - * A clone of $transport above. This can be used to reset the connection state - * in case transport doesn't support the disconnect method or it throws an - * exception (this can happen if the connection is stale and the connector tries - * to issue a QUIT message for clean disconnect). + * A clone of $transport above. This can be used to reset the connection state. * - * @var TransportInterface + * @var MailerInterface */ protected $initialTransport; @@ -91,10 +90,10 @@ class Mailer implements /** * Constructor * - * @param TransportInterface $transport Mail transport - * @param ?string $messageLog File to log messages into (null for no logging) + * @param MailerInterface $transport Mail transport + * @param array $options Message log options */ - public function __construct(TransportInterface $transport, protected ?string $messageLog = null) + public function __construct(MailerInterface $transport, protected array $options = []) { $this->setTransport($transport); } @@ -102,27 +101,21 @@ public function __construct(TransportInterface $transport, protected ?string $me /** * Get the mail transport object. * - * @return TransportInterface + * @return MailerInterface */ - public function getTransport() + public function getTransport(): MailerInterface { return $this->transport; } /** - * Get a text email message object. + * Get an email message object. * - * @return Message + * @return Email */ - public function getNewMessage() + public function getNewMessage(): Email { - $message = $this->getNewBlankMessage(); - $headers = $message->getHeaders(); - $ctype = new ContentType(); - $ctype->setType(Mime::TYPE_TEXT); - $ctype->addParameter('charset', 'UTF-8'); - $headers->addHeader($ctype); - return $message; + return new Email(); } /** @@ -132,46 +125,33 @@ public function getNewMessage() */ public function resetConnection() { - // If the transport has a disconnect method, call it. Otherwise, and in case - // disconnect fails, revert to the transport instance clone made before a - // connection was made. - $transport = $this->getTransport(); - if (is_callable([$transport, 'disconnect'])) { - try { - $transport->disconnect(); - } catch (\Exception $e) { - $this->setTransport($this->initialTransport); - } - } else { - $this->setTransport($this->initialTransport); - } + $this->setTransport($this->initialTransport); return $this; } /** * Get a blank email message object. * - * @return Message + * @return Email + * + * @deprecated Use getNewMessage */ - public function getNewBlankMessage() + public function getNewBlankMessage(): Email { - $message = new Message(); - $message->setEncoding('UTF-8'); - return $message; + return $this->getNewMessage(); } /** * Set the mail transport object. * - * @param TransportInterface $transport Mail transport object + * @param MailerInterface $transport Mail transport object * * @return void */ - public function setTransport($transport) + public function setTransport(MailerInterface $transport): void { $this->transport = $transport; - // Store a clone of the given transport so that we can reset the connection - // as necessary. + // Store a clone of the given transport so that we can reset the connection as necessary: $this->initialTransport = clone $this->transport; } @@ -180,83 +160,91 @@ public function setTransport($transport) * * @param string $input String to convert * - * @return AddressList + * @return array */ - public function stringToAddressList($input) + public function stringToAddressList($input): array { // Create recipient list - $list = new AddressList(); + $list = []; foreach (preg_split('/[\s,;]/', $input) as $current) { $current = trim($current); if (!empty($current)) { - $list->add($current); + $list[] = $current; } } return $list; } /** - * Constructs a {@see MimeMessage} body from given text and html content. + * Constructs an {@see Email} body from given text and html content. * * @param string|null $text Mail content used for plain text part * @param string|null $html Mail content used for html part * - * @return MimeMessage + * @return Email */ public function buildMultipartBody( string $text = null, string $html = null - ): MimeMessage { - $parts = new MimeMessage(); - - if ($text) { - $textPart = new MimePart($text); - $textPart->setType(Mime::TYPE_TEXT); - $textPart->setCharset('utf-8'); - $textPart->setEncoding(Mime::ENCODING_QUOTEDPRINTABLE); - $parts->addPart($textPart); + ): Email { + $email = $this->getNewMessage(); + if (null !== $text) { + $email->text($text); } - - if ($html) { - $htmlPart = new MimePart($html); - $htmlPart->setType(Mime::TYPE_HTML); - $htmlPart->setCharset('utf-8'); - $htmlPart->setEncoding(Mime::ENCODING_QUOTEDPRINTABLE); - $parts->addPart($htmlPart); + if (null !== $html) { + $email->html($html); } - - $alternativePart = new MimePart($parts->generateMessage()); - $alternativePart->setType('multipart/alternative'); - $alternativePart->setBoundary($parts->getMime()->boundary()); - $alternativePart->setCharset('utf-8'); - - $body = new MimeMessage(); - $body->setParts([$alternativePart]); - - return $body; + return $email; } /** * Send an email message. * - * @param string|Address|AddressList $to Recipient email address (or - * delimited list) - * @param string|Address $from Sender name and email address - * @param string $subject Subject line for message - * @param string|MimeMessage $body Message body - * @param string $cc CC recipient (null for none) - * @param string|Address|AddressList $replyTo Reply-To address (or delimited - * list, null for none) + * @param string|string[]|Address|Address[] $to Recipient email address(es) (or delimited list) + * @param string|Address $from Sender name and email address + * @param string $subject Subject line for message + * @param string|Email $body Message body + * @param string|string[]|Address|Address[]|null $cc CC recipient(s) (null for none) + * @param string|string[]|Address|Address[]|null $replyTo Reply-To address(es) (or delimited list, null for none) * * @throws MailException * @return void */ - public function send($to, $from, $subject, $body, $cc = null, $replyTo = null) - { - $recipients = $this->convertToAddressList($to); - $replyTo = $this->convertToAddressList($replyTo); + public function send( + string|Address|array $to, + string|Address $from, + string $subject, + string|Email $body, + string|Address|array|null $cc = null, + string|Address|array|null $replyTo = null + ) { + try { + if (!($from instanceof Address)) { + $from = new Address($from); + } + } catch (RfcComplianceException $e) { + throw new MailException('Invalid Sender Email Address', MailException::ERROR_INVALID_SENDER, $e); + } + try { + $recipients = $this->convertToAddressList($to); + } catch (RfcComplianceException $e) { + throw new MailException('Invalid Recipient Email Address', MailException::ERROR_INVALID_RECIPIENT, $e); + } + try { + $replyTo = $this->convertToAddressList($replyTo); + } catch (RfcComplianceException $e) { + throw new MailException('Invalid Reply-To Email Address', MailException::ERROR_INVALID_REPLY_TO, $e); + } + try { + $cc = $this->convertToAddressList($cc); + } catch (RfcComplianceException $e) { + throw new MailException('Invalid CC Email Address', MailException::ERROR_INVALID_RECIPIENT, $e); + } - // Validate email addresses: + // Validate recipient email address count: + if (count($recipients) == 0) { + throw new MailException('Invalid Recipient Email Address', MailException::ERROR_INVALID_RECIPIENT); + } if ($this->maxRecipients > 0) { if ($this->maxRecipients < count($recipients)) { throw new MailException( @@ -265,53 +253,19 @@ public function send($to, $from, $subject, $body, $cc = null, $replyTo = null) ); } } - $validator = new \Laminas\Validator\EmailAddress(); - if (count($recipients) == 0) { - throw new MailException( - 'Invalid Recipient Email Address', - MailException::ERROR_INVALID_RECIPIENT - ); - } - foreach ($recipients as $current) { - if (!$validator->isValid($current->getEmail())) { - throw new MailException( - 'Invalid Recipient Email Address', - MailException::ERROR_INVALID_RECIPIENT - ); - } - } - foreach ($replyTo as $current) { - if (!$validator->isValid($current->getEmail())) { - throw new MailException( - 'Invalid Reply-To Email Address', - MailException::ERROR_INVALID_REPLY_TO - ); - } - } - $fromEmail = ($from instanceof Address) - ? $from->getEmail() : $from; - if (!$validator->isValid($fromEmail)) { - throw new MailException( - 'Invalid Sender Email Address', - MailException::ERROR_INVALID_SENDER - ); - } if ( !empty($this->fromAddressOverride) - && $this->fromAddressOverride != $fromEmail + && $this->fromAddressOverride != $from->getAddress() ) { // Add the original from address as the reply-to address unless // a reply-to address has been specified - if (count($replyTo) === 0) { - $replyTo->add($fromEmail); - } - if (!($from instanceof Address)) { - $from = new Address($from); + if (!$replyTo) { + $replyTo[] = $from->getAddress(); } $name = $from->getName(); if (!$name) { - [$fromPre] = explode('@', $from->getEmail()); + [$fromPre] = explode('@', $from->getAddress()); $name = $fromPre ? $fromPre : null; } $from = new Address($this->fromAddressOverride, $name); @@ -320,59 +274,64 @@ public function send($to, $from, $subject, $body, $cc = null, $replyTo = null) // Convert all exceptions thrown by mailer into MailException objects: try { // Send message - $message = $body instanceof MimeMessage - ? $this->getNewBlankMessage() - : $this->getNewMessage(); - $message->addFrom($from) - ->addTo($recipients) - ->setBody($body) - ->setSubject($subject); - if ($cc !== null) { - $message->addCc($cc); + if ($body instanceof Email) { + $email = $body; + if (null === $email->getSubject()) { + $email->subject($subject); + } + } else { + $email = $this->getNewMessage(); + $email->text($body); + $email->subject($subject); } - if ($replyTo) { - $message->addReplyTo($replyTo); + $email->addFrom($from); + foreach ($recipients as $current) { + $email->addTo($current); } - $this->getTransport()->send($message); - if ($this->messageLog) { - file_put_contents($this->messageLog, $message->toString() . "\n", FILE_APPEND); + foreach ($cc as $current) { + $email->addCc($current); + } + foreach ($replyTo as $current) { + $email->addReplyTo($current); + } + $this->getTransport()->send($email); + if ($logFile = $this->options['message_log'] ?? null) { + $format = $this->options['message_log_format'] ?? 'plain'; + $data = 'serialized' === $format + ? serialize($email) . "\x1E" // use Record Separator to separate messages + : $email->toString() . "\n\n"; + file_put_contents($logFile, $data, FILE_APPEND); } } catch (\Exception $e) { $this->logError($e->getMessage()); - throw new MailException($e->getMessage(), MailException::ERROR_UNKNOWN); + throw new MailException($e->getMessage(), MailException::ERROR_UNKNOWN, $e); } } /** * Send an email message representing a link. * - * @param string $to Recipient email address - * @param string|\Laminas\Mail\Address $from Sender name and email - * address - * @param string $msg User notes to include in - * message - * @param string $url URL to share - * @param \Laminas\View\Renderer\PhpRenderer $view View object (used to render - * email templates) - * @param string $subject Subject for email - * (optional) - * @param string $cc CC recipient (null for - * none) - * @param string|Address|AddressList $replyTo Reply-To address (or - * delimited list, null for none) + * @param string|string[]|Address|Address[] $to Recipient email address(es) (or delimited list) + * @param string|Address $from Sender name and email address + * @param string $msg User notes to include in message + * @param string $url URL to share + * @param PhpRenderer $view View object (used to render email templates) + * @param ?string $subject Subject for email (optional) + * @param string|string[]|Address|Address[]|null $cc CC recipient(s) (null for none) + * @param string|string[]|Address|Address[]|null $replyTo Reply-To address(es) (or delimited list, null for none) * * @throws MailException * @return void */ public function sendLink( - $to, - $from, - $msg, - $url, - $view, - $subject = null, - $cc = null, - $replyTo = null + string|Address|array $to, + string|Address $from, + string $msg, + string $url, + PhpRenderer $view, + ?string $subject = null, + string|Address|array|null $cc = null, + string|Address|array|null $replyTo = null ) { if (null === $subject) { $subject = $this->getDefaultLinkSubject(); @@ -399,33 +358,27 @@ public function getDefaultLinkSubject() /** * Send an email message representing a record. * - * @param string $to Recipient email address - * @param string|\Laminas\Mail\Address $from Sender name and email - * address - * @param string $msg User notes to include in - * message - * @param \VuFind\RecordDriver\AbstractBase $record Record being emailed - * @param \Laminas\View\Renderer\PhpRenderer $view View object (used to render - * email templates) - * @param string $subject Subject for email - * (optional) - * @param string $cc CC recipient (null for - * none) - * @param string|Address|AddressList $replyTo Reply-To address (or - * delimited list, null for none) + * @param string|Address|Address[] $to Recipient email address(es) (or delimited list) + * @param string|Address $from Sender name and email address + * @param string $msg User notes to include in message + * @param AbstractBase $record Record being emailed + * @param PhpRenderer $view View object (used to render email templates) + * @param ?string $subject Subject for email (optional) + * @param string|Address|Address[]|null $cc CC recipient(s) (null for none) + * @param string|Address|Address[]|null $replyTo Reply-To address(es) (or delimited list, null for none) * * @throws MailException * @return void */ public function sendRecord( - $to, - $from, - $msg, - $record, - $view, - $subject = null, - $cc = null, - $replyTo = null + string|Address|array $to, + string|Address $from, + string $msg, + AbstractBase $record, + PhpRenderer $view, + ?string $subject = null, + string|Address|array|null $cc = null, + string|Address|array|null $replyTo = null ) { if (null === $subject) { $subject = $this->getDefaultRecordSubject($record); @@ -460,8 +413,7 @@ public function setMaxRecipients($max) */ public function getDefaultRecordSubject($record) { - return $this->translate('Library Catalog Record') . ': ' - . $record->getBreadcrumb(); + return $this->translate('Library Catalog Record') . ': ' . $record->getBreadcrumb(); } /** @@ -487,21 +439,27 @@ public function setFromAddressOverride($address) } /** - * Convert the given addresses to an AddressList object + * Convert the given addresses to an array * - * @param string|Address|AddressList $addresses Addresses + * @param string|Address|Address[]|null $addresses Addresses * - * @return AddressList + * @return array */ - protected function convertToAddressList($addresses) + protected function convertToAddressList(string|Address|array|null $addresses): array { - if ($addresses instanceof AddressList) { - $result = $addresses; - } elseif ($addresses instanceof Address) { - $result = new AddressList(); - $result->add($addresses); - } else { - $result = $this->stringToAddressList($addresses ? $addresses : ''); + if (empty($addresses)) { + return []; + } + if ($addresses instanceof Address) { + return [$addresses]; + } + if (is_array($addresses)) { + // Address::createArray takes an array of strings or Address objects, so this handles both cases: + return Address::createArray($addresses); + } + $result = []; + foreach (explode(';', $addresses) as $current) { + $result[] = Address::create($current); } return $result; } diff --git a/module/VuFind/src/VuFind/Mailer/Message.php b/module/VuFind/src/VuFind/Mailer/Message.php deleted file mode 100644 index 2d291b1e301..00000000000 --- a/module/VuFind/src/VuFind/Mailer/Message.php +++ /dev/null @@ -1,96 +0,0 @@ -<?php - -/** - * Tweaked Laminas Message class - * - * PHP version 8 - * - * Copyright (C) The National Library of Finland 2023. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2, - * as published by the Free Software Foundation. - * - * 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 - * - * @category VuFind - * @package Mailer - * @author Ere Maijala <ere.maijala@helsinki.fi> - * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License - * @link https://vufind.org/wiki/development Wiki - */ - -namespace VuFind\Mailer; - -use Laminas\Mail\AddressList; - -/** - * Tweaked Laminas Message class - * - * @category VuFind - * @package Mailer - * @author Ere Maijala <ere.maijala@helsinki.fi> - * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License - * @link https://vufind.org/wiki/development Wiki - */ -class Message extends \Laminas\Mail\Message -{ - /** - * Retrieve list of From senders - * - * Returns our local "From" class - * - * @return AddressList - */ - public function getFrom() - { - return $this->getAddressListFromHeader('from', From::class); - } - - /** - * Access the address list of the To header - * - * @return AddressList - */ - public function getTo() - { - return $this->getAddressListFromHeader('to', To::class); - } - - /** - * Retrieve list of CC recipients - * - * @return AddressList - */ - public function getCc() - { - return $this->getAddressListFromHeader('cc', Cc::class); - } - - /** - * Retrieve list of BCC recipients - * - * @return AddressList - */ - public function getBcc() - { - return $this->getAddressListFromHeader('bcc', Bcc::class); - } - - /** - * Access the address list of the Reply-To header - * - * @return AddressList - */ - public function getReplyTo() - { - return $this->getAddressListFromHeader('reply-to', ReplyTo::class); - } -} diff --git a/module/VuFind/src/VuFind/Mailer/To.php b/module/VuFind/src/VuFind/Mailer/To.php deleted file mode 100644 index 44390d2f968..00000000000 --- a/module/VuFind/src/VuFind/Mailer/To.php +++ /dev/null @@ -1,44 +0,0 @@ -<?php - -/** - * Tweaked Laminas "To" header class - * - * PHP version 8 - * - * Copyright (C) The National Library of Finland 2023. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2, - * as published by the Free Software Foundation. - * - * 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 - * - * @category VuFind - * @package Mailer - * @author Ere Maijala <ere.maijala@helsinki.fi> - * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License - * @link https://vufind.org/wiki/development Wiki - */ - -namespace VuFind\Mailer; - -/** - * Tweaked Laminas "To" header class - * - * @category VuFind - * @package Mailer - * @author Ere Maijala <ere.maijala@helsinki.fi> - * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License - * @link https://vufind.org/wiki/development Wiki - */ -class To extends \Laminas\Mail\Header\To -{ - use GetFieldValueFixTrait; -} diff --git a/module/VuFind/src/VuFind/OAuth2/Repository/ScopeRepository.php b/module/VuFind/src/VuFind/OAuth2/Repository/ScopeRepository.php index 333f74b8e2a..0217e6f0a5f 100644 --- a/module/VuFind/src/VuFind/OAuth2/Repository/ScopeRepository.php +++ b/module/VuFind/src/VuFind/OAuth2/Repository/ScopeRepository.php @@ -33,6 +33,8 @@ use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; use VuFind\OAuth2\Entity\ScopeEntity; +use function in_array; + /** * OAuth2 scope repository implementation. * @@ -98,6 +100,18 @@ public function finalizeScopes( ClientEntityInterface $clientEntity, $userIdentifier = null ) { + $clientId = $clientEntity->getIdentifier(); + // Apply any client-specific filter to scopes: + if ($allowedScopes = $this->oauth2Config['Clients'][$clientId]['allowedScopes'] ?? null) { + $scopes = array_values( + array_filter( + $scopes, + function ($scope) use ($allowedScopes) { + return in_array($scope->getIdentifier(), $allowedScopes); + } + ) + ); + } return $scopes; } } diff --git a/module/VuFind/src/VuFind/Record/Cache.php b/module/VuFind/src/VuFind/Record/Cache.php index eb005f1da19..be7c76bbaad 100644 --- a/module/VuFind/src/VuFind/Record/Cache.php +++ b/module/VuFind/src/VuFind/Record/Cache.php @@ -229,7 +229,7 @@ public function isFallback($source) } /** - * Check whether a record source is cachable + * Check whether a record source is cacheable * * @param string $source Record source * diff --git a/module/VuFind/src/VuFind/Record/Loader.php b/module/VuFind/src/VuFind/Record/Loader.php index 996d0f11bda..d54b0e743ca 100644 --- a/module/VuFind/src/VuFind/Record/Loader.php +++ b/module/VuFind/src/VuFind/Record/Loader.php @@ -209,7 +209,7 @@ public function loadBatchForSource( $list = new Checklist($ids); $cachedRecords = []; if (null !== $this->recordCache && $this->recordCache->isPrimary($source)) { - // Try to load records from cache if source is cachable + // Try to load records from cache if source is cacheable $cachedRecords = $this->recordCache->lookupBatch($ids, $source); // Check which records could not be loaded from the record cache foreach ($cachedRecords as $cachedRecord) { @@ -273,7 +273,7 @@ public function loadBatchForSource( $list->hasUnchecked() && null !== $this->recordCache && $this->recordCache->isFallback($source) ) { - // Try to load missing records from cache if source is cachable + // Try to load missing records from cache if source is cacheable $cachedRecords = $this->recordCache ->lookupBatch($list->getUnchecked(), $source); } diff --git a/module/VuFind/src/VuFind/RecordDriver/DefaultRecord.php b/module/VuFind/src/VuFind/RecordDriver/DefaultRecord.php index aaf4cdbc4b2..6a3ddb08a76 100644 --- a/module/VuFind/src/VuFind/RecordDriver/DefaultRecord.php +++ b/module/VuFind/src/VuFind/RecordDriver/DefaultRecord.php @@ -124,6 +124,23 @@ public function getAllSubjectHeadings($extended = false) return array_map($callback, array_unique($headings)); } + /** + * Get the subject headings as a flat array of strings. + * + * @return array Subject headings + */ + public function getAllSubjectHeadingsFlattened() + { + $headings = []; + $subjects = $this->getAllSubjectHeadings(); + if (is_array($subjects)) { + foreach ($subjects as $subj) { + $headings[] = implode(' -- ', $subj); + } + } + return $headings; + } + /** * Get all record links related to the current record. Each link is returned as * array. @@ -243,7 +260,7 @@ public function getCallNumber() */ public function getCallNumbers() { - return (array)($this->fields['callnumber-raw'] ?? []); + return array_unique((array)($this->fields['callnumber-raw'] ?? [])); } /** diff --git a/module/VuFind/src/VuFind/RecordDriver/EDS.php b/module/VuFind/src/VuFind/RecordDriver/EDS.php index 24be3057f4b..e22b99c73c8 100644 --- a/module/VuFind/src/VuFind/RecordDriver/EDS.php +++ b/module/VuFind/src/VuFind/RecordDriver/EDS.php @@ -115,6 +115,18 @@ public function getItemsAbstract() return $abstract[0]['Data'] ?? ''; } + /** + * Get the abstract notes. + * For EDS, returns the abstract in an array or an empty array. + * + * @return array + */ + public function getAbstractNotes() + { + $abstract = $this->getItems(null, null, 'Ab'); + return (array)($abstract[0]['Data'] ?? []); + } + /** * Get the access level of the record. * @@ -431,19 +443,20 @@ public function getLinkedFullTextLink() } /** - * Get the subject data of the record. + * Get the subject headings as a flat array of strings. * - * @return string + * @return array Subject headings */ - public function getItemsSubjects() + public function getAllSubjectHeadingsFlattened() { - $subjects = array_map( + $subject_arrays = array_map( function ($data) { - return $data['Data']; + $str = preg_replace('/>\s*[;,]\s*</', '>|<', $data['Data']); + return explode('|', rtrim(strip_tags($str), '.')); }, $this->getItems(null, null, 'Su') ); - return empty($subjects) ? '' : implode(', ', $subjects); + return array_merge(...$subject_arrays); } /** @@ -682,7 +695,7 @@ public function getCleanDOI() { $doi = $this->getItems(null, null, null, 'DOI'); if (isset($doi[0]['Data'])) { - return $doi[0]['Data']; + return strip_tags($doi[0]['Data']); } $dois = $this->getFilteredIdentifiers(['doi']); return $dois[0] ?? false; diff --git a/module/VuFind/src/VuFind/RecordDriver/Feature/MarcAdvancedTrait.php b/module/VuFind/src/VuFind/RecordDriver/Feature/MarcAdvancedTrait.php index 3e7444ffc4b..0c198cc14f3 100644 --- a/module/VuFind/src/VuFind/RecordDriver/Feature/MarcAdvancedTrait.php +++ b/module/VuFind/src/VuFind/RecordDriver/Feature/MarcAdvancedTrait.php @@ -558,16 +558,6 @@ protected function getSeriesFromMARC($fieldInfo) return $matches; } - /** - * Get an array of summary strings for the record. - * - * @return array - */ - public function getSummary() - { - return $this->getFieldArray('520'); - } - /** * Get an array of technical details on the item represented by the record. * @@ -1170,4 +1160,150 @@ public function getTextualHoldings() { return $this->getFieldArray('866'); } + + /** + * Check if an array of indicator filters match the provided marc data + * + * @param array $marc_data MARC data for a specific field + * @param array $indFilter Array with up to 2 keys ('1', and '2') with an array as their value + * containing what to match on in the marc indicator. + * ex: ['1' => ['0','1']] would filter ind1 with 0 or 1 + * + * @return bool + */ + protected function checkIndicatorFilter($marc_data, $indFilter): bool + { + foreach (range(1, 2) as $indNum) { + if (isset($indFilter[$indNum])) { + if (!in_array(trim(($marc_data['i' . $indNum] ?? '')), (array)$indFilter[$indNum])) { + return false; + } + } + } + // If we got this far, no non-matching filters were encountered. + return true; + } + + /** + * Check if the indicator filters match the provided marc data + * + * @param array $marc_data MARC data for a specific field + * @param array $indData Indicator filters as described in getMarcFieldWithInd() + * + * @return bool + */ + protected function checkIndicatorFilters($marc_data, $indData): bool + { + foreach ($indData as $indFilter) { + if ($this->checkIndicatorFilter($marc_data, $indFilter)) { + return true; + } + } + // If we got this far, either $indData is empty (no filters defined -- return true) + // or it is non-empty (no filters matched -- return false) + return empty($indData); + } + + /** + * Takes a Marc field that notes are stored in (ex: 950) and a list of + * sub fields (ex: ['a','b']) optionally as well as what indicator + * numbers and values to filter for and concatenates the subfields + * together and returns the fields back as an array + * (ex: ['subA subB subC', 'field2SubA field2SubB']) + * + * @param string $field Marc field to search within + * @param ?array $subfield Sub-fields to return or empty for all + * @param array $indData Array of filter arrays, each in the format indicator number => + * array of allowed indicator values. If any one of the filter arrays fully matches the indicator + * values in the field, data will be returned. If no filter arrays are defined, data will always + * be returned regardless of indicators. + * ex: [['1' => ['1', '2']], ['2' => ['']]] would filter fields ind1 = 1 or 2 or ind2 = blank + * ex: [['1' => ['1'], '2' => ['7']]] would filter fields with ind1 = 1 and ind2 = 7 + * ex: [] would apply no filtering based on indicators + * + * @return array The values within the subfields under the field + */ + public function getMarcFieldWithInd( + string $field, + ?array $subfield = null, + array $indData = [] + ) { + $vals = []; + $marc = $this->getMarcReader(); + $marc_fields = $marc->getFields($field, $subfield); + foreach ($marc_fields as $marc_data) { + $field_vals = []; + if ($this->checkIndicatorFilters($marc_data, $indData)) { + $subfields = $marc_data['subfields']; + foreach ($subfields as $subfield) { + $field_vals[] = $subfield['data']; + } + } + $newVal = implode(' ', $field_vals); + if (!empty($field_vals) && !in_array($newVal, $vals)) { + $vals[] = $newVal; + } + } + return $vals; + } + + /** + * Get the location of other archival materials notes + * + * @return array Note fields from the MARC record + */ + public function getLocationOfArchivalMaterialsNotes() + { + return $this->getMarcFieldWithInd('544', range('a', 'z'), [[1 => ['', '0']]]); + } + + /** + * Get an array of summary strings for the record with only the 'a' subfield. + * + * @return array + */ + public function getSummary() + { + return $this->getMarcFieldWithInd('520', ['a'], [[1 => ['', '0', '2', '8']]]); + } + + /** + * Get the summary note + * + * @return array Note fields from the MARC record + */ + public function getSummaryNotes() + { + return $this->getMarcFieldWithInd('520', range('a', 'z'), [[1 => ['', '0', '2', '8']]]); + } + + /** + * Get the abstract notes + * + * @return array Note fields from the MARC record + */ + public function getAbstractNotes() + { + return $this->getMarcFieldWithInd('520', range('a', 'z'), [[1 => ['3']]]); + } + + /** + * Get the review notes + * + * @return array Note fields from the MARC record + */ + public function getReviewNotes() + { + return $this->getMarcFieldWithInd('520', range('a', 'z'), [[1 => ['1']]]); + } + + /** + * Get the content advice notes + * + * @return array Note fields from the MARC record + */ + public function getContentAdviceNotes() + { + return $this->getMarcFieldWithInd('520', range('a', 'z'), [[1 => ['4']]]); + } } diff --git a/module/VuFind/src/VuFind/RecordDriver/PluginManager.php b/module/VuFind/src/VuFind/RecordDriver/PluginManager.php index 8d740aa16b8..1b31a9458d1 100644 --- a/module/VuFind/src/VuFind/RecordDriver/PluginManager.php +++ b/module/VuFind/src/VuFind/RecordDriver/PluginManager.php @@ -72,7 +72,7 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager 'solrreserves' => SolrReserves::class, 'solrweb' => SolrWeb::class, 'summon' => Summon::class, - 'worldcat' => WorldCat::class, + 'worldcat2' => WorldCat2::class, ]; /** @@ -111,7 +111,7 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager SolrReserves::class => SolrDefaultWithoutSearchServiceFactory::class, SolrWeb::class => SolrWebFactory::class, Summon::class => SummonFactory::class, - WorldCat::class => NameBasedConfigFactory::class, + WorldCat2::class => NameBasedConfigFactory::class, ]; /** diff --git a/module/VuFind/src/VuFind/RecordDriver/SolrOverdrive.php b/module/VuFind/src/VuFind/RecordDriver/SolrOverdrive.php index 444d362dc30..608626e9718 100644 --- a/module/VuFind/src/VuFind/RecordDriver/SolrOverdrive.php +++ b/module/VuFind/src/VuFind/RecordDriver/SolrOverdrive.php @@ -229,15 +229,11 @@ public function getPreviewLinks() $data = json_decode($jsonData, false); } - if (isset($data->formats[0]->samples[0])) { - foreach ($data->formats[0]->samples as $format) { - if ( - $format->formatType == 'audiobook-overdrive' - || $format->formatType == 'ebook-overdrive' - || $format->formatType == 'magazine-overdrive' - ) { - $results = $format; - } + $samples = $data->formats[0]->samples ?? []; + $previewFormats = ['audiobook-overdrive', 'ebook-overdrive', 'magazine-overdrive']; + foreach ($samples as $format) { + if (in_array($format->formatType ?? '', $previewFormats) && isset($format->url)) { + $results[] = $format->url; } } return $results; diff --git a/module/VuFind/src/VuFind/RecordDriver/WorldCat.php b/module/VuFind/src/VuFind/RecordDriver/WorldCat.php deleted file mode 100644 index e766970a397..00000000000 --- a/module/VuFind/src/VuFind/RecordDriver/WorldCat.php +++ /dev/null @@ -1,83 +0,0 @@ -<?php - -/** - * Model for MARC records in WorldCat. - * - * PHP version 8 - * - * Copyright (C) Villanova University 2010. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2, - * as published by the Free Software Foundation. - * - * 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 - * - * @category VuFind - * @package RecordDrivers - * @author Demian Katz <demian.katz@villanova.edu> - * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License - * @link https://vufind.org/wiki/development:plugins:record_drivers Wiki - */ - -namespace VuFind\RecordDriver; - -use VuFind\RecordDriver\Feature\MarcAdvancedTrait; -use VuFind\RecordDriver\Feature\MarcBasicTrait; -use VuFind\RecordDriver\Feature\MarcReaderTrait; - -/** - * Model for MARC records in WorldCat. - * - * @category VuFind - * @package RecordDrivers - * @author Demian Katz <demian.katz@villanova.edu> - * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License - * @link https://vufind.org/wiki/development:plugins:record_drivers Wiki - */ -class WorldCat extends DefaultRecord -{ - use MarcReaderTrait, MarcAdvancedTrait, MarcBasicTrait { - MarcBasicTrait::getNewerTitles insteadof MarcAdvancedTrait; - MarcBasicTrait::getPreviousTitles insteadof MarcAdvancedTrait; - } - - /** - * Set raw data to initialize the object. - * - * @param mixed $data Raw data representing the record; Record Model - * objects are normally constructed by Record Driver objects using data - * passed in from a Search Results object. In this case, $data is a MARCXML - * document. - * - * @return void - */ - public function setRawData($data) - { - // Ensure that $driver->setRawData($driver->getRawData()) doesn't blow up: - if (isset($data['fullrecord'])) { - $data = $data['fullrecord']; - } - - // Map the WorldCat response into a format that the parent Solr-based - // record driver can understand. - parent::setRawData(['fullrecord' => $data]); - } - - /** - * Get the OCLC number of the record. - * - * @return array - */ - public function getOCLC() - { - return [$this->getUniqueID()]; - } -} diff --git a/module/VuFind/src/VuFind/RecordDriver/WorldCat2.php b/module/VuFind/src/VuFind/RecordDriver/WorldCat2.php new file mode 100644 index 00000000000..8a7d127cb73 --- /dev/null +++ b/module/VuFind/src/VuFind/RecordDriver/WorldCat2.php @@ -0,0 +1,551 @@ +<?php + +/** + * Model for WorldCat v2 records. + * + * PHP version 8 + * + * Copyright (C) Villanova University 2024. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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 + * + * @category VuFind + * @package RecordDrivers + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:record_drivers Wiki + */ + +namespace VuFind\RecordDriver; + +use function count; + +/** + * Model for WorldCat v2 records. + * + * @category VuFind + * @package RecordDrivers + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:record_drivers Wiki + */ +class WorldCat2 extends DefaultRecord +{ + /** + * Return the unique identifier of this record within the index; + * useful for retrieving additional information (like tags and user + * comments) from the external MySQL database. + * + * @return string Unique identifier. + */ + public function getUniqueID() + { + if (!isset($this->fields['identifier']['oclcNumber'])) { + throw new \Exception('ID not set!'); + } + return $this->fields['identifier']['oclcNumber']; + } + + /** + * Get the call numbers associated with the record (empty array if none). + * + * @return array + */ + public function getCallNumbers() + { + $retVal = []; + foreach (['lc', 'dewey'] as $type) { + $retVal = array_merge($retVal, (array)($this->fields['classification'][$type] ?? [])); + } + return $retVal; + } + + /** + * Get the Dewey call number associated with this record (empty string if none). + * + * @return string + */ + public function getDeweyCallNumber() + { + return ((array)($this->fields['classification']['dewey'] ?? []))[0] ?? ''; + } + + /** + * Get a raw, unnormalized LCCN. (See DefaultRecord::getLCCN for normalization). + * + * @return string + */ + protected function getRawLCCN() + { + return ((array)($this->fields['classification']['lc'] ?? []))[0] ?? ''; + } + + /** + * Get an array of all the formats associated with the record. + * + * @return array + */ + public function getFormats() + { + $formats = []; + foreach (['generalFormat', 'specificFormat'] as $key) { + if (isset($this->fields['format'][$key])) { + $formats[] = $this->translate( + 'WorldCatFormats::' . strtolower($this->fields['format'][$key]), + default: $this->fields['format'][$key] + ); + } + } + return $formats; + } + + /** + * Get an array of all ISBNs associated with the record (may be empty). + * + * @return array + */ + public function getISBNs() + { + return (array)($this->fields['identifier']['isbns'] ?? []); + } + + /** + * Get an array of all ISBNs associated with the record (may be empty). + * + * @return array + */ + public function getISSNs() + { + return (array)($this->fields['identifier']['issns'] ?? []); + } + + /** + * Get an array of all the languages associated with the record. + * + * @return array + */ + public function getLanguages() + { + return (array)($this->fields['language']['itemLanguage'] ?? []); + } + + /** + * Get the OCLC number of the record. + * + * @return array + */ + public function getOCLC() + { + return array_merge( + [$this->getUniqueID()], + $this->fields['identifier']['mergedOclcNumbers'] ?? [] + ); + } + + /** + * Get the item's place of publication. + * + * @return array + */ + public function getPlacesOfPublication() + { + return array_map( + fn ($publisher) => ($publisher['publicationPlace'] ?? '') . ' :', + $this->fields['publishers'] ?? [] + ); + } + + /** + * Convert an author array into a string. + * + * @param array $data Author data + * + * @return string + */ + protected function formatCreatorName(array $data): string + { + if (!empty($data['nonPersonName']['text'])) { + return $data['nonPersonName']['text']; + } + return implode( + ', ', + array_filter( + array_merge( + [ + $data['secondName']['text'] ?? null, + $data['firstName']['text'] ?? null, + ], + $data['creatorNotes'] ?? [] + ) + ) + ); + } + + /** + * Get the main authors of the record. + * + * @return array + */ + public function getPrimaryAuthors() + { + return array_values( + array_map( + [$this, 'formatCreatorName'], + array_filter( + $this->fields['contributor']['creators'] ?? [], + fn ($creator) => ($creator['isPrimary'] ?? false) && ($creator['type'] ?? '') !== 'corporation' + ) + ) + ); + } + + /** + * Get the secondary authors of the record. + * + * @return array + */ + public function getSecondaryAuthors() + { + return array_values( + array_map( + [$this, 'formatCreatorName'], + array_filter( + $this->fields['contributor']['creators'] ?? [], + fn ($creator) => !($creator['isPrimary'] ?? false) && ($creator['type'] ?? '') !== 'corporation' + ) + ) + ); + } + + /** + * Get an array of all corporate authors (complementing getPrimaryAuthor()). + * + * @return array + */ + public function getCorporateAuthors() + { + return array_map( + [$this, 'formatCreatorName'], + array_filter( + $this->fields['contributor']['creators'] ?? [], + fn ($creator) => ($creator['type'] ?? '') === 'corporation' + ) + ); + } + + /** + * Get the date coverage for a record which spans a period of time (i.e. a + * journal). Use getPublicationDates for publication dates of particular + * monographic items. + * + * @return array + */ + public function getDateSpan() + { + return (array)($this->fields['date']['publicationSequentialDesignationDate'] ?? []); + } + + /** + * Get the publication dates of the record. See also getDateSpan(). + * + * @return array + */ + public function getPublicationDates() + { + return (array)($this->fields['date']['machineReadableDate'] ?? []); + } + + /** + * Get human readable publication dates for display purposes (may not be suitable + * for computer processing -- use getPublicationDates() for that). + * + * @return array + */ + public function getHumanReadablePublicationDates() + { + return (array)($this->fields['date']['publicationDate'] ?? []); + } + + /** + * Get the publishers of the record. + * + * @return array + */ + public function getPublishers() + { + return array_map( + fn ($publisher) => $publisher['publisherName']['text'] ?? '', + $this->fields['publishers'] ?? [] + ); + } + + /** + * Get an array of newer titles for the record. + * + * @return array + */ + public function getNewerTitles() + { + return array_filter( + array_map( + fn ($entry) => $entry['relatedItemTitle'], + (array)($this->fields['related']['succeedingEntries'] ?? []) + ) + ); + } + + /** + * Get an array of previous titles for the record. + * + * @return array + */ + public function getPreviousTitles() + { + return array_filter( + array_map( + fn ($entry) => $entry['relatedItemTitle'], + (array)($this->fields['related']['precedingEntries'] ?? []) + ) + ); + } + + /** + * Get an array of summary strings for the record. + * + * @return array + */ + public function getSummary() + { + return array_map( + fn ($summary) => $summary['text'] ?? '', + $this->fields['description']['summaries'] ?? [] + ); + } + + /** + * Get the full title of the record. + * + * @return string + */ + public function getTitle() + { + $full = $this->fields['title']['mainTitles'][0]['text'] ?? ''; + $parts = explode(' / ', $full); + return $parts[0]; + } + + /** + * Get the short (pre-subtitle) title of the record. + * + * @return string + */ + public function getShortTitle() + { + $parts = explode(':', $this->getTitle(), 2); + return trim($parts[0] . (count($parts) > 1 ? ':' : '')); + } + + /** + * Get the subtitle of the record. + * + * @return string + */ + public function getSubtitle() + { + $parts = explode(':', $this->getTitle(), 2); + return trim($parts[1] ?? ''); + } + + /** + * Get the edition of the current record. + * + * @return string + */ + public function getEdition() + { + return ((array)($this->fields['edition']['statement'] ?? []))[0] ?? ''; + } + + /** + * Get an array of physical descriptions of the item. + * + * @return array + */ + public function getPhysicalDescriptions() + { + return ((array)($this->fields['description']['physicalDescription'] ?? []))[0] ?? ''; + } + + /** + * Get all subject headings associated with this record. Each heading is + * returned as an array of chunks, increasing from least specific to most + * specific. + * + * @param bool $extended Whether to return a keyed array with the following + * keys: + * - heading: the actual subject heading chunks + * - type: heading type + * - source: source vocabulary + * + * @return array + */ + public function getAllSubjectHeadings($extended = false) + { + // Get all the unique subject strings: + $values = array_unique( + array_map( + fn ($subject) => $subject['subjectName']['text'] ?? '', + $this->fields['subjects'] ?? [] + ) + ); + // Now convert to the expected format: + return array_map( + fn ($value) => [$value], + array_values(array_filter($values)) + ); + } + + /** + * Get award notes for the record. + * + * @return array + */ + public function getAwards() + { + return (array)($this->fields['note']['awardNote'] ?? []); + } + + /** + * Get general notes on the record. + * + * @return array + */ + public function getGeneralNotes() + { + return array_filter( + array_map( + fn ($note) => $note['text'], + array_filter( + (array)($this->fields['note']['generalNotes'] ?? []), + fn ($note) => $note['local'] === 'N' + ) + ) + ); + } + + /** + * Get notes on bibliography content. + * + * @return array + */ + public function getBibliographyNotes() + { + return array_filter( + array_map( + fn ($note) => $note['text'], + (array)($this->fields['description']['bibliographies'] ?? []) + ) + ); + } + + /** + * Get credits of people involved in production of the item. + * + * @return array + */ + public function getProductionCredits() + { + return (array)($this->fields['note']['creditNotes'] ?? []); + } + + /** + * Get an array of publication frequency information. + * + * @return array + */ + public function getPublicationFrequency() + { + return (array)($this->fields['date']['currentPublicationFrequency'] ?? []); + } + + /** + * Get an array of all series names containing the record. Array entries may + * be either the name string, or an associative array with 'name' and 'number' + * keys. + * + * @return array + */ + public function getSeries() + { + $separator = '|||||'; + $raw = array_map( + fn ($series) => ($series['seriesTitle'] ?? '') . $separator . ($series['volume'] ?? ''), + (array)($this->fields['title']['seriesTitles'] ?? []) + ); + return array_map( + function ($series) use ($separator) { + [$name, $number] = explode($separator, $series); + return compact('name', 'number'); + }, + array_unique($raw) + ); + } + + /** + * Get an array of lines from the table of contents. + * + * @return array + */ + public function getTOC() + { + return array_map( + fn ($toc) => $toc['contentNote']['text'] ?? '', + (array)($this->fields['description']['contents'] ?? []) + ); + } + + /** + * Return an array of associative URL arrays with one or more of the following + * keys: + * + * <li> + * <ul>desc: URL description text to display (optional)</ul> + * <ul>url: fully-formed URL (required if 'route' is absent)</ul> + * <ul>route: VuFind route to build URL with (required if 'url' is absent)</ul> + * <ul>routeParams: Parameters for route (optional)</ul> + * <ul>queryString: Query params to append after building route (optional)</ul> + * </li> + * + * @return array + */ + public function getURLs() + { + if (!($this->recordConfig->Record->show_urls ?? false)) { + return []; + } + $raw = array_map( + fn ($loc) => $loc['uri'], + (array)($this->fields['digitalAccessAndLocations'] ?? []) + ); + $retVal = []; + foreach ($raw as $current) { + preg_match_all('|https?://[^ ]+|', $current, $matches); + $retVal = array_merge($retVal, $matches[0] ?? []); + } + return array_map( + fn ($url) => compact('url'), + $retVal + ); + } +} diff --git a/module/VuFind/src/VuFind/RecordTab/HierarchyTree.php b/module/VuFind/src/VuFind/RecordTab/HierarchyTree.php index 9a42100c29f..4ed1d593f3e 100644 --- a/module/VuFind/src/VuFind/RecordTab/HierarchyTree.php +++ b/module/VuFind/src/VuFind/RecordTab/HierarchyTree.php @@ -208,6 +208,16 @@ public function getSearchLimit() return $config->Hierarchy->treeSearchLimit ?? -1; } + /** + * Disable record preview when screen width is narrow + * + * @return bool + */ + public function hidePreviewInNarrowDisplays(): bool + { + return (bool)$this->config->Hierarchy?->hide_preview_in_narrow_displays; + } + /** * Get the current active record. Returns record driver if there is an active * record or null otherwise. diff --git a/module/VuFind/src/VuFind/RecordTab/HoldingsWorldCat.php b/module/VuFind/src/VuFind/RecordTab/HoldingsWorldCat2.php similarity index 58% rename from module/VuFind/src/VuFind/RecordTab/HoldingsWorldCat.php rename to module/VuFind/src/VuFind/RecordTab/HoldingsWorldCat2.php index 1561756e1fb..f7fe84deca8 100644 --- a/module/VuFind/src/VuFind/RecordTab/HoldingsWorldCat.php +++ b/module/VuFind/src/VuFind/RecordTab/HoldingsWorldCat2.php @@ -1,11 +1,11 @@ <?php /** - * Holdings (WorldCat) tab + * Holdings (WorldCat2) tab * * PHP version 8 * - * Copyright (C) Villanova University 2010. + * Copyright (C) Villanova University 2024. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -29,11 +29,12 @@ namespace VuFind\RecordTab; -use VuFindSearch\Backend\WorldCat\Command\GetHoldingsCommand; +use VuFindSearch\Backend\WorldCat2\Command\GetHoldingsCommand; +use VuFindSearch\ParamBag; use VuFindSearch\Service; /** - * Holdings (WorldCat) tab + * Holdings (WorldCat2) tab * * @category VuFind * @package RecordTabs @@ -41,23 +42,16 @@ * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License * @link https://vufind.org/wiki/development:plugins:record_tabs Wiki */ -class HoldingsWorldCat extends AbstractBase +class HoldingsWorldCat2 extends AbstractBase { - /** - * Search service - * - * @var Service - */ - protected $searchService; - /** * Constructor * * @param Service $searchService Search service + * @param array $defaults Default parameters to include in API requests */ - public function __construct(Service $searchService) + public function __construct(protected Service $searchService, protected array $defaults = []) { - $this->searchService = $searchService; } /** @@ -71,17 +65,17 @@ public function getDescription() } /** - * Get holdings information from WorldCat (false if none available). + * Get holdings information from WorldCat (null if none available). * - * @return \SimpleXMLElement|bool + * @return ?array */ public function getHoldings() { - $id = $this->getOCLCNum(); - if (empty($id)) { - return false; + $ids = $this->getIdentifiers(); + if (!$ids) { + return null; } - $command = new GetHoldingsCommand('WorldCat', $id); + $command = new GetHoldingsCommand('WorldCat2', new ParamBag($ids + $this->defaults)); return $this->searchService->invoke($command)->getResult(); } @@ -92,17 +86,26 @@ public function getHoldings() */ public function isActive() { - $id = $this->getOCLCNum(); - return !empty($id); + $ids = $this->getIdentifiers(); + return !empty($ids); } /** - * Get the OCLC number from the active record driver. + * Get the identifiers from the active record driver. * - * @return string + * @return array */ - protected function getOCLCNum() + protected function getIdentifiers(): array { - return $this->getRecordDriver()->tryMethod('getCleanOCLCNum'); + $driver = $this->getRecordDriver(); + $params = []; + if ($oclc = $driver->tryMethod('getCleanOCLCNum')) { + $params['oclcNumber'] = $oclc; + } elseif ($isbn = $driver->tryMethod('getCleanISBN')) { + $params['isbn'] = $isbn; + } elseif ($issn = $driver->tryMethod('getCleanISSN')) { + $params['issn'] = $issn; + } + return $params; } } diff --git a/module/VuFind/src/VuFind/RecordTab/HoldingsWorldCatFactory.php b/module/VuFind/src/VuFind/RecordTab/HoldingsWorldCat2Factory.php similarity index 82% rename from module/VuFind/src/VuFind/RecordTab/HoldingsWorldCatFactory.php rename to module/VuFind/src/VuFind/RecordTab/HoldingsWorldCat2Factory.php index 8d1748cd0cd..5a503287092 100644 --- a/module/VuFind/src/VuFind/RecordTab/HoldingsWorldCatFactory.php +++ b/module/VuFind/src/VuFind/RecordTab/HoldingsWorldCat2Factory.php @@ -1,11 +1,11 @@ <?php /** - * Factory for building the HoldingsWorldCat tab. + * Factory for building the HoldingsWorldCat2 tab. * * PHP version 8 * - * Copyright (C) Villanova University 2019. + * Copyright (C) Villanova University 2024. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -35,7 +35,7 @@ use Psr\Container\ContainerInterface; /** - * Factory for building the HoldingsWorldCat tab. + * Factory for building the HoldingsWorldCat2 tab. * * @category VuFind * @package RecordTabs @@ -43,7 +43,7 @@ * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License * @link https://vufind.org/wiki/development Wiki */ -class HoldingsWorldCatFactory implements \Laminas\ServiceManager\Factory\FactoryInterface +class HoldingsWorldCat2Factory implements \Laminas\ServiceManager\Factory\FactoryInterface { /** * Create an object @@ -69,6 +69,10 @@ public function __invoke( if (!empty($options)) { throw new \Exception('Unexpected options passed to factory.'); } - return new $requestedName($container->get(\VuFindSearch\Service::class)); + $config = $container->get(\VuFind\Config\PluginManager::class)->get('WorldCat2'); + return new $requestedName( + $container->get(\VuFindSearch\Service::class), + $config?->Holdings?->toArray() ?? [] + ); } } diff --git a/module/VuFind/src/VuFind/RecordTab/PluginManager.php b/module/VuFind/src/VuFind/RecordTab/PluginManager.php index 440760366f2..6bb011e1b2c 100644 --- a/module/VuFind/src/VuFind/RecordTab/PluginManager.php +++ b/module/VuFind/src/VuFind/RecordTab/PluginManager.php @@ -56,7 +56,7 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager 'formats' => Formats::class, 'hierarchytree' => HierarchyTree::class, 'holdingsils' => HoldingsILS::class, - 'holdingsworldcat' => HoldingsWorldCat::class, + 'holdingsworldcat2' => HoldingsWorldCat2::class, 'map' => Map::class, 'preview' => Preview::class, 'reviews' => Reviews::class, @@ -84,7 +84,7 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager Formats::class => InvokableFactory::class, HierarchyTree::class => HierarchyTreeFactory::class, HoldingsILS::class => HoldingsILSFactory::class, - HoldingsWorldCat::class => HoldingsWorldCatFactory::class, + HoldingsWorldCat2::class => HoldingsWorldCat2Factory::class, Map::class => MapFactory::class, OverdriveHoldings::class => InvokableFactory::class, Preview::class => PreviewFactory::class, diff --git a/module/VuFind/src/VuFind/Related/PluginManager.php b/module/VuFind/src/VuFind/Related/PluginManager.php index a1bcb44a024..0332afdaaee 100644 --- a/module/VuFind/src/VuFind/Related/PluginManager.php +++ b/module/VuFind/src/VuFind/Related/PluginManager.php @@ -54,7 +54,7 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager 'morebyauthorsolr' => MoreByAuthorSolr::class, 'similar' => Similar::class, 'worldcateditions' => Deprecated::class, - 'worldcatsimilar' => WorldCatSimilar::class, + 'worldcat2similar' => WorldCat2Similar::class, ]; /** @@ -68,7 +68,7 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager Deprecated::class => InvokableFactory::class, MoreByAuthorSolr::class => SimilarFactory::class, Similar::class => SimilarFactory::class, - WorldCatSimilar::class => SimilarFactory::class, + WorldCat2Similar::class => WorldCat2SimilarFactory::class, ]; /** diff --git a/module/VuFind/src/VuFind/Related/WorldCat2Similar.php b/module/VuFind/src/VuFind/Related/WorldCat2Similar.php new file mode 100644 index 00000000000..2fd2d0fb4b9 --- /dev/null +++ b/module/VuFind/src/VuFind/Related/WorldCat2Similar.php @@ -0,0 +1,150 @@ +<?php + +/** + * Related Records: WorldCat v2-based similarity + * + * PHP version 8 + * + * Copyright (C) Villanova University 2024. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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 + * + * @category VuFind + * @package Related_Records + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:related_records_modules Wiki + */ + +namespace VuFind\Related; + +use Exception; +use VuFindSearch\Command\SearchCommand; +use VuFindSearch\ParamBag; +use VuFindSearch\Query\Query; +use VuFindSearch\Query\QueryGroup; + +use function array_slice; +use function count; +use function in_array; + +/** + * Related Records: WorldCat v2-based similarity + * + * @category VuFind + * @package Related_Records + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:related_records_modules Wiki + */ +class WorldCat2Similar extends Similar +{ + /** + * Maximum number of recommendations to return. + * + * @var int + */ + protected int $maxRecommendations = 5; + + /** + * Maximum term count in query. + * + * @var int + */ + protected int $termLimit = 30; + + /** + * Add a phrase to the query if it's not already too long. + * + * @param QueryGroup $queryObj Query object being updated + * @param string $phrase Query phrase + * @param string $type Search type + * + * @return void + */ + protected function addPhraseToQuery(QueryGroup $queryObj, string $phrase, string $type): void + { + $terms = explode(' ', trim($queryObj->getAllTerms() . ' ' . $phrase)); + if (count($terms) <= $this->termLimit) { + $queryObj->addQuery(new Query('"' . $phrase . '"', $type)); + } + } + + /** + * Establishes base settings for making recommendations. + * + * @param string $settings Settings from config.ini + * @param \VuFind\RecordDriver\AbstractBase $driver Record driver object + * + * @return void + */ + public function init($settings, $driver) + { + // Create advanced query: + $queryObj = new QueryGroup('OR'); + + // Add subjects to query + $subjects = array_slice($driver->tryMethod('getAllSubjectHeadings', default: []), 0, 4); + foreach ($subjects as $current) { + $this->addPhraseToQuery($queryObj, implode(' ', $current), 'su'); + } + + // Add author to query + $author = $driver->tryMethod('getPrimaryAuthor', default: ''); + if (!empty($author)) { + $this->addPhraseToQuery($queryObj, $author, 'au'); + } + + // Add title to query + $title = $driver->tryMethod('getTitle', default: ''); + if (!empty($title)) { + $this->addPhraseToQuery($queryObj, str_replace('"', '', $title), 'ti'); + } + + // We don't want to show a recommendation that represents the same OCLC record. + $idsToExclude = (array)($driver->tryMethod('getOCLC', default: [])); + + // Group together related editions to ensure that suggestions are more diverse: + $extraParams = new ParamBag(['groupRelatedEditions' => 'true']); + + // Perform the search and save filtered results: + $command = new SearchCommand('WorldCat2', $queryObj, 1, $this->maxRecommendations + 1, $extraParams); + $this->results = []; + try { + $result = $this->searchService->invoke($command)->getResult(); + foreach ($result->getRecords() as $record) { + if ( + !in_array($record->getUniqueId(), $idsToExclude) + && count($this->results) < $this->maxRecommendations + ) { + $this->results[] = $record; + } + } + } catch (Exception $e) { + error_log('Unexpected error in WorldCat2 similar records module: ' . ((string)$e)); + } + } + + /** + * Set the term limit. + * + * @param int $limit Term limit + * + * @return void + */ + public function setTermLimit(int $limit): void + { + $this->termLimit = $limit; + } +} diff --git a/module/VuFind/src/VuFind/Related/WorldCat2SimilarFactory.php b/module/VuFind/src/VuFind/Related/WorldCat2SimilarFactory.php new file mode 100644 index 00000000000..496e59da013 --- /dev/null +++ b/module/VuFind/src/VuFind/Related/WorldCat2SimilarFactory.php @@ -0,0 +1,72 @@ +<?php + +/** + * Factory for WorldCat2Similar related record module. + * + * PHP version 8 + * + * Copyright (C) Villanova University 2024. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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 + * + * @category VuFind + * @package Related_Records + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ + +namespace VuFind\Related; + +use Laminas\ServiceManager\Exception\ServiceNotCreatedException; +use Laminas\ServiceManager\Exception\ServiceNotFoundException; +use Psr\Container\ContainerExceptionInterface as ContainerException; +use Psr\Container\ContainerInterface; + +/** + * Factory for WorldCat2Similar related record module. + * + * @category VuFind + * @package Related_Records + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ +class WorldCat2SimilarFactory extends SimilarFactory +{ + /** + * Create an object + * + * @param ContainerInterface $container Service manager + * @param string $requestedName Service being created + * @param null|array $options Extra options (optional) + * + * @return object + * + * @throws ServiceNotFoundException if unable to resolve the service. + * @throws ServiceNotCreatedException if an exception is raised when + * creating a service. + * @throws ContainerException&\Throwable if any other error occurs + */ + public function __invoke( + ContainerInterface $container, + $requestedName, + array $options = null + ) { + $module = parent::__invoke($container, $requestedName, $options); + $wc2Options = $container->get(\VuFind\Search\Options\PluginManager::class)->get('WorldCat2'); + $module->setTermLimit($wc2Options->getQueryTermsLimit()); + return $module; + } +} diff --git a/module/VuFind/src/VuFind/Related/WorldCatSimilar.php b/module/VuFind/src/VuFind/Related/WorldCatSimilar.php deleted file mode 100644 index 8091cc14e0b..00000000000 --- a/module/VuFind/src/VuFind/Related/WorldCatSimilar.php +++ /dev/null @@ -1,101 +0,0 @@ -<?php - -/** - * Related Records: WorldCat-based similarity - * - * PHP version 8 - * - * Copyright (C) Villanova University 2009, 2022. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2, - * as published by the Free Software Foundation. - * - * 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 - * - * @category VuFind - * @package Related_Records - * @author Demian Katz <demian.katz@villanova.edu> - * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License - * @link https://vufind.org/wiki/development:plugins:related_records_modules Wiki - */ - -namespace VuFind\Related; - -use VuFindSearch\Command\SearchCommand; - -/** - * Related Records: WorldCat-based similarity - * - * @category VuFind - * @package Related_Records - * @author Demian Katz <demian.katz@villanova.edu> - * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License - * @link https://vufind.org/wiki/development:plugins:related_records_modules Wiki - */ -class WorldCatSimilar extends Similar -{ - /** - * Establishes base settings for making recommendations. - * - * @param string $settings Settings from config.ini - * @param \VuFind\RecordDriver\AbstractBase $driver Record driver object - * - * @return void - */ - public function init($settings, $driver) - { - // Create array of query parts: - $parts = []; - - // Add Dewey class to query - $deweyClass = $driver->tryMethod('getDeweyCallNumber'); - if (!empty($deweyClass)) { - // Skip "English Fiction" Dewey class -- this won't give us useful - // matches because there's too much of it and it's too broad. - if (!str_starts_with($deweyClass, '823')) { - $parts[] = 'srw.dd any "' . $deweyClass . '"'; - } - } - - // Add author to query - $author = $driver->getPrimaryAuthor(); - if (!empty($author)) { - $parts[] = 'srw.au all "' . $author . '"'; - } - - // Add subjects to query - $subjects = $driver->getAllSubjectHeadings(); - foreach ($subjects as $current) { - $parts[] = 'srw.su all "' . implode(' ', $current) . '"'; - } - - // Add title to query - $title = $driver->getTitle(); - if (!empty($title)) { - $parts[] = 'srw.ti any "' . str_replace('"', '', $title) . '"'; - } - - // Build basic query: - $query = '(' . implode(' or ', $parts) . ')'; - - // Not current record ID if this is already a WorldCat record: - if ($driver->getSourceIdentifier() == 'WorldCat') { - $id = $driver->getUniqueId(); - $query .= " not srw.no all \"$id\""; - } - - // Perform the search and save results: - $queryObj = new \VuFindSearch\Query\Query($query); - $command = new SearchCommand('WorldCat', $queryObj, 0, 5); - $result = $this->searchService->invoke($command)->getResult(); - $this->results = $result->getRecords(); - } -} diff --git a/module/VuFind/src/VuFind/Search/BackendRegistry.php b/module/VuFind/src/VuFind/Search/BackendRegistry.php index f7fa9598ff8..fc3c9d7429f 100644 --- a/module/VuFind/src/VuFind/Search/BackendRegistry.php +++ b/module/VuFind/src/VuFind/Search/BackendRegistry.php @@ -52,6 +52,7 @@ class BackendRegistry extends \VuFind\ServiceManager\AbstractPluginManager 'reserves' => 'SolrReserves', // Legacy: 'VuFind' => 'Solr', + 'WorldCat' => 'WorldCat2', ]; /** @@ -77,7 +78,7 @@ class BackendRegistry extends \VuFind\ServiceManager\AbstractPluginManager 'SolrReserves' => Factory\SolrReservesBackendFactory::class, 'SolrWeb' => Factory\SolrWebBackendFactory::class, 'Summon' => Factory\SummonBackendFactory::class, - 'WorldCat' => Factory\WorldCatBackendFactory::class, + 'WorldCat2' => Factory\WorldCat2BackendFactory::class, ]; /** diff --git a/module/VuFind/src/VuFind/Search/Base/Results.php b/module/VuFind/src/VuFind/Search/Base/Results.php index f042b85b3e0..0a9cda48f39 100644 --- a/module/VuFind/src/VuFind/Search/Base/Results.php +++ b/module/VuFind/src/VuFind/Search/Base/Results.php @@ -512,6 +512,9 @@ public function getNotificationFrequency(): int public function updateSaveStatus($row) { $this->searchId = $row->getId(); + foreach ($this->results as $driver) { + $driver->setExtraDetail('searchId', $this->searchId); + } $this->savedSearch = $row->getSaved(); $this->notificationFrequency = $this->savedSearch ? $row->getNotificationFrequency() : 0; } diff --git a/module/VuFind/src/VuFind/Search/Factory/BrowZineBackendFactory.php b/module/VuFind/src/VuFind/Search/Factory/BrowZineBackendFactory.php index 683d8d7b241..3506b694afa 100644 --- a/module/VuFind/src/VuFind/Search/Factory/BrowZineBackendFactory.php +++ b/module/VuFind/src/VuFind/Search/Factory/BrowZineBackendFactory.php @@ -30,6 +30,7 @@ namespace VuFind\Search\Factory; use Psr\Container\ContainerInterface; +use VuFind\Config\Feature\SecretTrait; use VuFindSearch\Backend\BrowZine\Backend; use VuFindSearch\Backend\BrowZine\Connector; use VuFindSearch\Backend\BrowZine\QueryBuilder; @@ -46,6 +47,8 @@ */ class BrowZineBackendFactory extends AbstractBackendFactory { + use SecretTrait; + /** * Logger. * @@ -108,8 +111,9 @@ protected function createBackend(Connector $connector) */ protected function createConnector() { + $token = $this->getSecretFromConfig($this->browzineConfig?->General, 'access_token'); // Validate configuration: - if (empty($this->browzineConfig->General->access_token)) { + if ($token === null) { throw new \Exception('Missing access token in BrowZine.ini'); } if (empty($this->browzineConfig->General->library_id)) { @@ -119,7 +123,7 @@ protected function createConnector() // Create connector: $connector = new Connector( $this->createHttpClient($this->browzineConfig->General->timeout ?? 30), - $this->browzineConfig->General->access_token, + $token, $this->browzineConfig->General->library_id ); $connector->setLogger($this->logger); diff --git a/module/VuFind/src/VuFind/Search/Factory/WorldCatBackendFactory.php b/module/VuFind/src/VuFind/Search/Factory/WorldCat2BackendFactory.php similarity index 60% rename from module/VuFind/src/VuFind/Search/Factory/WorldCatBackendFactory.php rename to module/VuFind/src/VuFind/Search/Factory/WorldCat2BackendFactory.php index 2b18022edcb..85ab0636452 100644 --- a/module/VuFind/src/VuFind/Search/Factory/WorldCatBackendFactory.php +++ b/module/VuFind/src/VuFind/Search/Factory/WorldCat2BackendFactory.php @@ -1,11 +1,11 @@ <?php /** - * Factory for WorldCat backends. + * Factory for WorldCat v2 backends. * * PHP version 8 * - * Copyright (C) Villanova University 2013. + * Copyright (C) Villanova University 2024. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -22,6 +22,7 @@ * * @category VuFind * @package Search + * @author Demian Katz <demian.katz@villanova.edu> * @author David Maus <maus@hab.de> * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License * @link https://vufind.org Main Site @@ -29,22 +30,27 @@ namespace VuFind\Search\Factory; +use Laminas\Session\Container; +use League\OAuth2\Client\OptionProvider\HttpBasicAuthOptionProvider; +use League\OAuth2\Client\Provider\GenericProvider; use Psr\Container\ContainerInterface; -use VuFindSearch\Backend\WorldCat\Backend; -use VuFindSearch\Backend\WorldCat\Connector; -use VuFindSearch\Backend\WorldCat\QueryBuilder; -use VuFindSearch\Backend\WorldCat\Response\XML\RecordCollectionFactory; +use VuFind\Http\GuzzleService; +use VuFindSearch\Backend\WorldCat2\Backend; +use VuFindSearch\Backend\WorldCat2\Connector; +use VuFindSearch\Backend\WorldCat2\QueryBuilder; +use VuFindSearch\Backend\WorldCat2\Response\RecordCollectionFactory; /** - * Factory for WorldCat backends. + * Factory for WorldCat v2 backends. * * @category VuFind * @package Search + * @author Demian Katz <demian.katz@villanova.edu> * @author David Maus <maus@hab.de> * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License * @link https://vufind.org Main Site */ -class WorldCatBackendFactory extends AbstractBackendFactory +class WorldCat2BackendFactory extends AbstractBackendFactory { /** * Logger. @@ -61,7 +67,7 @@ class WorldCatBackendFactory extends AbstractBackendFactory protected $config; /** - * WorldCat configuration + * WorldCat v2 configuration * * @var \Laminas\Config\Config */ @@ -81,8 +87,9 @@ class WorldCatBackendFactory extends AbstractBackendFactory public function __invoke(ContainerInterface $sm, $name, array $options = null) { $this->setup($sm); - $this->config = $this->getService(\VuFind\Config\PluginManager::class)->get('config'); - $this->wcConfig = $this->getService(\VuFind\Config\PluginManager::class)->get('WorldCat'); + $configManager = $this->getService(\VuFind\Config\PluginManager::class); + $this->config = $configManager->get('config'); + $this->wcConfig = $configManager->get('WorldCat2'); if ($this->serviceLocator->has(\VuFind\Log\Logger::class)) { $this->logger = $this->getService(\VuFind\Log\Logger::class); } @@ -106,6 +113,35 @@ protected function createBackend(Connector $connector) return $backend; } + /** + * Create the OAuth2 provider for the connector. + * + * @param array $options Configuration options + * + * @return GenericProvider + */ + protected function createAuthProvider(array $options): GenericProvider + { + foreach (['wskey', 'secret'] as $param) { + if (empty($options[$param])) { + throw new \Exception($param . ' setting is required in WorldCat2.ini [Connector] section'); + } + } + $authOptions = [ + 'clientId' => $options['wskey'], + 'clientSecret' => $options['secret'], + 'urlAuthorize' => $options['auth_url'] ?? 'https://oauth.oclc.org/auth', + 'urlAccessToken' => $options['token_url'] ?? 'https://oauth.oclc.org/token', + 'urlResourceOwnerDetails' => '', + ]; + $optionProvider = new HttpBasicAuthOptionProvider(); + $provider = new GenericProvider($authOptions, compact('optionProvider')); + $provider->setHttpClient( + $this->getService(GuzzleService::class)->createClient() + ); + return $provider; + } + /** * Create the WorldCat connector. * @@ -113,12 +149,11 @@ protected function createBackend(Connector $connector) */ protected function createConnector() { - $wsKey = $this->config->WorldCat->apiKey ?? null; - $connectorOptions = isset($this->wcConfig->Connector) - ? $this->wcConfig->Connector->toArray() : []; + $connectorOptions = $this?->wcConfig?->Connector?->toArray() ?? []; $connector = new Connector( - $wsKey, $this->createHttpClient(), + $this->createAuthProvider($connectorOptions), + new Container('WorldCat2', $this->getService(\Laminas\Session\SessionManager::class)), $connectorOptions ); $connector->setLogger($this->logger); @@ -132,7 +167,7 @@ protected function createConnector() */ protected function createQueryBuilder() { - $exclude = $this->config->WorldCat->OCLCCode ?? null; + $exclude = $this->wcConfig->General->exclude_code ?? null; return new QueryBuilder($exclude); } @@ -145,7 +180,7 @@ protected function createRecordCollectionFactory() { $manager = $this->getService(\VuFind\RecordDriver\PluginManager::class); $callback = function ($data) use ($manager) { - $driver = $manager->get('WorldCat'); + $driver = $manager->get('WorldCat2'); $driver->setRawData($data); return $driver; }; diff --git a/module/VuFind/src/VuFind/Search/Memory.php b/module/VuFind/src/VuFind/Search/Memory.php index 2650c171a78..0188c9ceba7 100644 --- a/module/VuFind/src/VuFind/Search/Memory.php +++ b/module/VuFind/src/VuFind/Search/Memory.php @@ -31,6 +31,7 @@ use Laminas\Http\Request; use Laminas\Session\Container; +use VuFind\Db\Entity\UserEntityInterface; use VuFind\Db\Service\SearchServiceInterface; use VuFind\Search\Results\PluginManager as ResultsManager; @@ -254,16 +255,19 @@ public function getLastSearch(): ?\VuFind\Search\Base\Results /** * Get a search by id * - * @param int $id Search ID + * @param int $id Search ID + * @param ?UserEntityInterface $user Currently logged-in user to also check saved searches * * @return ?\VuFind\Search\Base\Results */ - protected function getSearchById(int $id): ?\VuFind\Search\Base\Results + public function getSearchById(int $id, ?UserEntityInterface $user = null): ?\VuFind\Search\Base\Results { - if (!array_key_exists($id, $this->searchCache)) { - $search = $this->searchService->getSearchByIdAndOwner($id, $this->sessionId, null); - $this->searchCache[$id] = $search?->getSearchObject()?->deminify($this->resultsManager); + $userId = $user?->getId(); + $cacheKey = $userId ? "{$id}_$userId" : $id; + if (!array_key_exists($cacheKey, $this->searchCache)) { + $search = $this->searchService->getSearchByIdAndOwner($id, $this->sessionId, $user); + $this->searchCache[$cacheKey] = $search?->getSearchObject()?->deminify($this->resultsManager); } - return $this->searchCache[$id]; + return $this->searchCache[$cacheKey]; } } diff --git a/module/VuFind/src/VuFind/Search/Options/PluginManager.php b/module/VuFind/src/VuFind/Search/Options/PluginManager.php index 63d7fafd800..d16f08cbff1 100644 --- a/module/VuFind/src/VuFind/Search/Options/PluginManager.php +++ b/module/VuFind/src/VuFind/Search/Options/PluginManager.php @@ -72,6 +72,7 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager 'summon' => \VuFind\Search\Summon\Options::class, 'tags' => \VuFind\Search\Tags\Options::class, 'worldcat' => \VuFind\Search\WorldCat\Options::class, + 'worldcat2' => \VuFind\Search\WorldCat2\Options::class, ]; /** @@ -107,6 +108,7 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager \VuFind\Search\Summon\Options::class => OptionsFactory::class, \VuFind\Search\Tags\Options::class => OptionsFactory::class, \VuFind\Search\WorldCat\Options::class => OptionsFactory::class, + \VuFind\Search\WorldCat2\Options::class => OptionsFactory::class, ]; /** diff --git a/module/VuFind/src/VuFind/Search/Params/PluginManager.php b/module/VuFind/src/VuFind/Search/Params/PluginManager.php index 6c7431687ac..26c8c6c00ae 100644 --- a/module/VuFind/src/VuFind/Search/Params/PluginManager.php +++ b/module/VuFind/src/VuFind/Search/Params/PluginManager.php @@ -71,6 +71,7 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager 'summon' => \VuFind\Search\Summon\Params::class, 'tags' => \VuFind\Search\Tags\Params::class, 'worldcat' => \VuFind\Search\WorldCat\Params::class, + 'worldcat2' => \VuFind\Search\WorldCat2\Params::class, ]; /** @@ -107,6 +108,7 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager \VuFind\Search\Summon\Params::class => ParamsFactory::class, \VuFind\Search\Tags\Params::class => ParamsFactory::class, \VuFind\Search\WorldCat\Params::class => ParamsFactory::class, + \VuFind\Search\WorldCat2\Params::class => ParamsFactory::class, ]; /** diff --git a/module/VuFind/src/VuFind/Search/QueryAdapter.php b/module/VuFind/src/VuFind/Search/QueryAdapter.php index a3274b65fc6..ebae9ba59ef 100644 --- a/module/VuFind/src/VuFind/Search/QueryAdapter.php +++ b/module/VuFind/src/VuFind/Search/QueryAdapter.php @@ -72,7 +72,7 @@ public function deminify(array $search) // Basic search $handler = $search['i'] ?? $search['f']; return new Query( - $search['l'], + $search['l'] ?? '', $handler, $search['o'] ?? null ); @@ -92,7 +92,7 @@ public function deminify(array $search) ); } else { // Simple query - return new Query($search[0]['l'], $search[0]['i']); + return new Query($search[0]['l'] ?? '', $search[0]['i']); } } } diff --git a/module/VuFind/src/VuFind/Search/Results/PluginManager.php b/module/VuFind/src/VuFind/Search/Results/PluginManager.php index bb7cabfe0b7..254d104fe9c 100644 --- a/module/VuFind/src/VuFind/Search/Results/PluginManager.php +++ b/module/VuFind/src/VuFind/Search/Results/PluginManager.php @@ -72,6 +72,7 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager 'summon' => \VuFind\Search\Summon\Results::class, 'tags' => \VuFind\Search\Tags\Results::class, 'worldcat' => \VuFind\Search\WorldCat\Results::class, + 'worldcat2' => \VuFind\Search\WorldCat2\Results::class, ]; /** @@ -112,6 +113,7 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager \VuFind\Search\Tags\Results::class => \VuFind\Search\Tags\ResultsFactory::class, \VuFind\Search\WorldCat\Results::class => ResultsFactory::class, + \VuFind\Search\WorldCat2\Results::class => ResultsFactory::class, ]; /** diff --git a/module/VuFind/src/VuFind/Search/UrlQueryHelper.php b/module/VuFind/src/VuFind/Search/UrlQueryHelper.php index db0efc57a79..ec7a8b9c222 100644 --- a/module/VuFind/src/VuFind/Search/UrlQueryHelper.php +++ b/module/VuFind/src/VuFind/Search/UrlQueryHelper.php @@ -340,7 +340,7 @@ public function addFilter($filter) /** * Remove all filters. * - * @return string + * @return UrlQueryHelper */ public function removeAllFilters() { @@ -354,7 +354,7 @@ public function removeAllFilters() /** * Reset default filter state. * - * @return string + * @return UrlQueryHelper */ public function resetDefaultFilters() { @@ -474,7 +474,7 @@ public function removeFacet($field, $value, $operator = 'AND') * * @param string $filter Filter to add * - * @return string + * @return UrlQueryHelper */ public function removeFilter($filter) { @@ -488,7 +488,7 @@ public function removeFilter($filter) * * @param string $p New page parameter (null for NO page parameter) * - * @return string + * @return UrlQueryHelper */ public function setPage($p) { @@ -501,7 +501,7 @@ public function setPage($p) * * @param string $s New sort parameter (null for NO sort parameter) * - * @return string + * @return UrlQueryHelper */ public function setSort($s) { @@ -519,7 +519,7 @@ public function setSort($s) * * @param string $handler new Handler. * - * @return string + * @return UrlQueryHelper */ public function setHandler($handler) { @@ -540,7 +540,7 @@ public function setHandler($handler) * * @param string $v New sort parameter (null for NO view parameter) * - * @return string + * @return UrlQueryHelper */ public function setViewParam($v) { @@ -556,7 +556,7 @@ public function setViewParam($v) * * @param string $l New limit parameter (null for NO limit parameter) * - * @return string + * @return UrlQueryHelper */ public function setLimit($l) { @@ -568,13 +568,29 @@ public function setLimit($l) ); } + /** + * Return HTTP parameters to render the current page with a different jumpto + * parameter. + * + * @param null|false|int $jumpto If results page is skipped when a search has only one hit + * + * @return UrlQueryHelper + */ + public function setJumpto(null|false|int $jumpto): UrlQueryHelper + { + return $this->updateQueryString( + 'jumpto', + $jumpto + ); + } + /** * Return HTTP parameters to render the current page with a different set * of search terms. * * @param string $lookfor New search terms * - * @return string + * @return UrlQueryHelper */ public function setSearchTerms($lookfor) { @@ -664,7 +680,7 @@ protected function filtered($field, $value, $filter) * for no default). * @param bool $clearPage Should we clear the page number, if any? * - * @return string + * @return UrlQueryHelper */ protected function updateQueryString( $field, @@ -673,7 +689,7 @@ protected function updateQueryString( $clearPage = false ) { $params = $this->urlParams; - if (null === $value || $value == $default) { + if (null === $value || $value === $default) { unset($params[$field]); } else { $params[$field] = $value; diff --git a/module/VuFind/src/VuFind/Search/WorldCat/Options.php b/module/VuFind/src/VuFind/Search/WorldCat/Options.php index 88823603627..d528f093d25 100644 --- a/module/VuFind/src/VuFind/Search/WorldCat/Options.php +++ b/module/VuFind/src/VuFind/Search/WorldCat/Options.php @@ -1,7 +1,8 @@ <?php /** - * WorldCat Search Options + * WorldCat Search Options (legacy -- retained only for compatibility + * with stored searches, which will be redirected to WorldCat v2) * * PHP version 8 * @@ -29,10 +30,9 @@ namespace VuFind\Search\WorldCat; -use function count; - /** - * WorldCat Search Options + * WorldCat Search Options (legacy -- retained only for compatibility + * with stored searches, which will be redirected to WorldCat v2) * * @category VuFind * @package Search_WorldCat @@ -42,55 +42,6 @@ */ class Options extends \VuFind\Search\Base\Options { - /** - * Constructor - * - * @param \VuFind\Config\PluginManager $configLoader Config loader - */ - public function __construct(\VuFind\Config\PluginManager $configLoader) - { - parent::__construct($configLoader); - $this->searchIni = $this->facetsIni = 'WorldCat'; - - // Load the configuration file: - $searchSettings = $configLoader->get($this->searchIni); - - // Search handler setup: - $this->defaultHandler = 'srw.kw'; - if (isset($searchSettings->Basic_Searches)) { - foreach ($searchSettings->Basic_Searches as $key => $value) { - $this->basicHandlers[$key] = $value; - } - } - if (isset($searchSettings->Advanced_Searches)) { - foreach ($searchSettings->Advanced_Searches as $key => $value) { - $this->advancedHandlers[$key] = $value; - } - } - - // Load sort preferences: - if (isset($searchSettings->Sorting)) { - foreach ($searchSettings->Sorting as $key => $value) { - $this->sortOptions[$key] = $value; - } - } - if (isset($searchSettings->General->default_sort)) { - $this->defaultSort = $searchSettings->General->default_sort; - } - if ( - isset($searchSettings->DefaultSortingByType) - && count($searchSettings->DefaultSortingByType) > 0 - ) { - foreach ($searchSettings->DefaultSortingByType as $key => $val) { - $this->defaultSortByHandler[$key] = $val; - } - } - // Load list view for result (controls AJAX embedding vs. linking) - if (isset($searchSettings->List->view)) { - $this->listviewOption = $searchSettings->List->view; - } - } - /** * Return the route name for the search results action. * diff --git a/module/VuFind/src/VuFind/Search/WorldCat/Params.php b/module/VuFind/src/VuFind/Search/WorldCat/Params.php index 2d2ff441175..15836ccce61 100644 --- a/module/VuFind/src/VuFind/Search/WorldCat/Params.php +++ b/module/VuFind/src/VuFind/Search/WorldCat/Params.php @@ -1,7 +1,8 @@ <?php /** - * WorldCat Search Parameters + * WorldCat Search Parameters (legacy -- retained only for compatibility + * with stored searches, which will be redirected to WorldCat v2) * * PHP version 8 * @@ -29,10 +30,9 @@ namespace VuFind\Search\WorldCat; -use VuFindSearch\ParamBag; - /** - * WorldCat Search Parameters + * WorldCat Search Parameters (legacy -- retained only for compatibility + * with stored searches, which will be redirected to WorldCat v2) * * @category VuFind * @package Search_WorldCat @@ -42,19 +42,4 @@ */ class Params extends \VuFind\Search\Base\Params { - /** - * Create search backend parameters for advanced features. - * - * @return ParamBag - */ - public function getBackendParameters() - { - $backendParams = new ParamBag(); - - // Sort - $sort = $this->getSort(); - $backendParams->set('sortKeys', empty($sort) ? 'relevance' : $sort); - - return $backendParams; - } } diff --git a/module/VuFind/src/VuFind/Search/WorldCat/Results.php b/module/VuFind/src/VuFind/Search/WorldCat/Results.php index c30455b7f66..8df13b425d1 100644 --- a/module/VuFind/src/VuFind/Search/WorldCat/Results.php +++ b/module/VuFind/src/VuFind/Search/WorldCat/Results.php @@ -1,7 +1,8 @@ <?php /** - * WorldCat Search Results + * WorldCat Search Results (legacy -- retained only for compatibility + * with stored searches, which will be redirected to WorldCat v2) * * PHP version 8 * @@ -29,10 +30,9 @@ namespace VuFind\Search\WorldCat; -use VuFindSearch\Command\SearchCommand; - /** - * WorldCat Search Parameters + * WorldCat Search Results (legacy -- retained only for compatibility + * with stored searches, which will be redirected to WorldCat v2) * * @category VuFind * @package Search_WorldCat @@ -57,22 +57,7 @@ class Results extends \VuFind\Search\Base\Results */ protected function performSearch() { - $query = $this->getParams()->getQuery(); - $limit = $this->getParams()->getLimit(); - $offset = $this->getStartRecord(); - $params = $this->getParams()->getBackendParameters(); - $command = new SearchCommand( - $this->backendId, - $query, - $offset, - $limit, - $params - ); - $collection = $this->getSearchService() - ->invoke($command)->getResult(); - - $this->resultTotal = $collection->getTotal(); - $this->results = $collection->getRecords(); + throw new \Exception('WorldCat v1 API no longer supported.'); } /** @@ -85,7 +70,6 @@ protected function performSearch() */ public function getFacetList($filter = null) { - // No facets in WorldCat: - return []; + throw new \Exception('WorldCat v1 API no longer supported.'); } } diff --git a/module/VuFind/src/VuFind/Search/WorldCat2/Options.php b/module/VuFind/src/VuFind/Search/WorldCat2/Options.php new file mode 100644 index 00000000000..05c7085538a --- /dev/null +++ b/module/VuFind/src/VuFind/Search/WorldCat2/Options.php @@ -0,0 +1,145 @@ +<?php + +/** + * WorldCat v2 Search Options + * + * PHP version 8 + * + * Copyright (C) Villanova University 2024. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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 + * + * @category VuFind + * @package Search_WorldCat2 + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org Main Page + */ + +namespace VuFind\Search\WorldCat2; + +use function count; + +/** + * WorldCat v2 Search Options + * + * @category VuFind + * @package Search_WorldCat2 + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org Main Page + */ +class Options extends \VuFind\Search\Base\Options +{ + use \VuFind\Config\Feature\ExplodeSettingTrait; + + /** + * Max number of terms allowed in a search. + * + * @var int + */ + protected int $termsLimit = 30; + + /** + * Constructor + * + * @param \VuFind\Config\PluginManager $configLoader Config loader + */ + public function __construct(\VuFind\Config\PluginManager $configLoader) + { + parent::__construct($configLoader); + $this->searchIni = $this->facetsIni = 'WorldCat2'; + + // Load the configuration file: + $searchSettings = $configLoader->get($this->searchIni); + + // Term limit setup: + if (isset($searchSettings->General->terms_limit)) { + $this->termsLimit = $searchSettings->General->terms_limit; + } + + // Limit setup: + if (isset($searchSettings->General->default_limit)) { + $this->defaultLimit = $searchSettings->General->default_limit; + } + if (isset($searchSettings->General->limit_options)) { + $this->limitOptions = $this->explodeListSetting($searchSettings->General->limit_options); + } + + // Search handler setup: + $this->defaultHandler = 'kw'; + foreach ($searchSettings->Basic_Searches ?? [] as $key => $value) { + $this->basicHandlers[$key] = $value; + } + foreach ($searchSettings->Advanced_Searches ?? [] as $key => $value) { + $this->advancedHandlers[$key] = $value; + } + + // Load sort preferences: + foreach ($searchSettings->Sorting ?? [] as $key => $value) { + $this->sortOptions[$key] = $value; + } + $this->defaultSort = $searchSettings->General->default_sort ?? 'bestMatch'; + foreach ($searchSettings->DefaultSortingByType ?? [] as $key => $val) { + $this->defaultSortByHandler[$key] = $val; + } + // Load list view for result (controls AJAX embedding vs. linking) + if (isset($searchSettings->List->view)) { + $this->listviewOption = $searchSettings->List->view; + } + + // Load default filters, if any: + if (isset($searchSettings->General->default_filters)) { + $this->defaultFilters = $searchSettings->General->default_filters->toArray(); + } + + $facetConf = $configLoader->get($this->facetsIni); + if (count($facetConf->Advanced_Facet_Settings->translated_facets ?? []) > 0) { + $this->setTranslatedFacets( + $facetConf->Advanced_Facet_Settings->translated_facets->toArray() + ); + } + } + + /** + * Return the route name for the search results action. + * + * @return string + */ + public function getSearchAction() + { + return 'worldcat2-search'; + } + + /** + * Return the route name of the action used for performing advanced searches. + * Returns false if the feature is not supported. + * + * @return string|bool + */ + public function getAdvancedSearchAction() + { + return 'worldcat2-advanced'; + } + + /** + * Get limit of terms per query. + * + * @return int + */ + public function getQueryTermsLimit(): int + { + return $this->termsLimit; + } +} diff --git a/module/VuFind/src/VuFind/Search/WorldCat2/Params.php b/module/VuFind/src/VuFind/Search/WorldCat2/Params.php new file mode 100644 index 00000000000..8ac8764b8bf --- /dev/null +++ b/module/VuFind/src/VuFind/Search/WorldCat2/Params.php @@ -0,0 +1,117 @@ +<?php + +/** + * WorldCat v2 Search Parameters + * + * PHP version 8 + * + * Copyright (C) Villanova University 2024. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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 + * + * @category VuFind + * @package Search_WorldCat2 + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org Main Page + */ + +namespace VuFind\Search\WorldCat2; + +use VuFindSearch\ParamBag; + +/** + * WorldCat v2 Search Parameters + * + * @category VuFind + * @package Search_WorldCat2 + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org Main Page + */ +class Params extends \VuFind\Search\Base\Params +{ + /** + * Some facet fields are named differently than the corresponding filter parameters; + * this map lists those exceptions. + * + * @var array + */ + protected $facetToFilterFieldMap = [ + 'language' => 'inLanguage', + ]; + + /** + * Create search backend parameters for advanced features. + * + * @return ParamBag + */ + public function getBackendParameters() + { + $backendParams = new ParamBag(); + + // Sort + $sort = $this->getSort(); + // Translate internal default/relevance sort to WorldCat equivalent: + if (empty($sort) || $sort === 'relevance') { + $sort = 'bestMatch'; + } + $backendParams->set('orderBy', $sort); + $backendParams->set('facets', array_keys($this->getFacetConfig())); + $this->createBackendFilterParameters($backendParams); + + return $backendParams; + } + + /** + * Given a facet field name, return the filter parameter to use to apply the filter. + * + * @param string $field Name of facet field. + * + * @return string + */ + protected function mapFacetFieldToFilterParam(string $field): string + { + return $this->facetToFilterFieldMap[$field] ?? $field; + } + + /** + * Set up filters based on VuFind settings. + * + * @param ParamBag $params Parameter collection to update + * + * @return void + */ + protected function createBackendFilterParameters(ParamBag $params): void + { + // Which filters should be applied to our query? + $filterList = $this->getFilterList(); + $hiddenFilterList = $this->getHiddenFilters(); + if (!empty($filterList)) { + // Loop through all filters and add appropriate values to request: + foreach ($filterList as $filterArray) { + foreach ($filterArray as $filt) { + $params->add($this->mapFacetFieldToFilterParam($filt['field']), $filt['value']); + } + } + } + if (!empty($hiddenFilterList)) { + foreach ($hiddenFilterList as $field => $hiddenFilters) { + foreach ($hiddenFilters as $value) { + $params->add($this->mapFacetFieldToFilterParam($field), $value); + } + } + } + } +} diff --git a/module/VuFind/src/VuFind/Search/WorldCat2/Results.php b/module/VuFind/src/VuFind/Search/WorldCat2/Results.php new file mode 100644 index 00000000000..83a5168b079 --- /dev/null +++ b/module/VuFind/src/VuFind/Search/WorldCat2/Results.php @@ -0,0 +1,137 @@ +<?php + +/** + * WorldCat v2 Search Results + * + * PHP version 8 + * + * Copyright (C) Villanova University 2024. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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 + * + * @category VuFind + * @package Search_WorldCat2 + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org Main Page + */ + +namespace VuFind\Search\WorldCat2; + +use VuFindSearch\Command\SearchCommand; + +use function count; +use function strlen; + +/** + * WorldCat v2 Search Parameters + * + * @category VuFind + * @package Search_WorldCat2 + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org Main Page + */ +class Results extends \VuFind\Search\Base\Results +{ + /** + * Search backend identifier. + * + * @var string + */ + protected $backendId = 'WorldCat2'; + + /** + * Facet list + * + * @var array|null + */ + protected $responseFacets = null; + + /** + * Store an empty response with an error message instead of performing a search. + * + * @param string|array $error Error message(s) to display to user. + * + * @return void + */ + protected function storeErrorResponse(string|array $error): void + { + $this->resultTotal = 0; + $this->results = []; + $this->responseFacets = []; + $this->errors = (array)$error; + } + + /** + * Support method for performAndProcessSearch -- perform a search based on the + * parameters passed to the object. + * + * @return void + */ + protected function performSearch() + { + $query = $this->getParams()->getQuery(); + $allTerms = $query->getAllTerms(); + if (!strlen($allTerms)) { + $this->storeErrorResponse('empty_search_disallowed'); + return; + } + $termCount = count(explode(' ', $allTerms)); + $termLimit = $this->getOptions()->getQueryTermsLimit(); + if ($termCount > $termLimit) { + $this->storeErrorResponse( + [ + [ + 'msg' => 'too_many_query_terms', + 'tokens' => ['%%terms%%' => $termCount, '%%maxTerms%%' => $termLimit], + ], + ] + ); + return; + } + $limit = $this->getParams()->getLimit(); + $offset = $this->getStartRecord(); + $params = $this->getParams()->getBackendParameters(); + $command = new SearchCommand( + $this->backendId, + $query, + $offset, + $limit, + $params + ); + $collection = $this->getSearchService()->invoke($command)->getResult(); + + $this->resultTotal = $collection->getTotal(); + $this->results = $collection->getRecords(); + $this->responseFacets = $collection->getFacets(); + $this->errors = $collection->getErrors(); + } + + /** + * Returns the stored list of facets for the last search + * + * @param array $filter Array of field => on-screen description listing + * all of the desired facet fields; set to null to get all configured values. + * + * @return array Facets data arrays + */ + public function getFacetList($filter = null) + { + if (null === $this->responseFacets) { + $this->performAndProcessSearch(); + } + return $this->buildFacetList($this->responseFacets, $filter); + } +} diff --git a/module/VuFind/src/VuFind/Service/ReCaptchaFactory.php b/module/VuFind/src/VuFind/Service/ReCaptchaFactory.php index f2e24f2b2dd..2935a961b5b 100644 --- a/module/VuFind/src/VuFind/Service/ReCaptchaFactory.php +++ b/module/VuFind/src/VuFind/Service/ReCaptchaFactory.php @@ -34,6 +34,7 @@ use Laminas\ServiceManager\Factory\FactoryInterface; use Psr\Container\ContainerExceptionInterface as ContainerException; use Psr\Container\ContainerInterface; +use VuFind\Config\Feature\SecretTrait; use VuFind\I18n\Locale\LocaleSettings; /** @@ -47,6 +48,8 @@ */ class ReCaptchaFactory implements FactoryInterface { + use SecretTrait; + /** * Create an object * @@ -95,7 +98,7 @@ public function __invoke( } $siteKey = $recaptchaConfig['recaptcha_siteKey'] ?? ''; - $secretKey = $recaptchaConfig['recaptcha_secretKey'] ?? ''; + $secretKey = $this->getSecretFromConfig($recaptchaConfig, 'recaptcha_secretKey') ?? ''; $httpClient = $container->get(\VuFindHttp\HttpService::class) ->createClient(); $language = $container->get(LocaleSettings::class)->getUserLocale(); diff --git a/module/VuFind/src/VuFind/View/Helper/Root/AlphaBrowse.php b/module/VuFind/src/VuFind/View/Helper/Root/AlphaBrowse.php index ca241e0b8b4..3be01b3fca0 100644 --- a/module/VuFind/src/VuFind/View/Helper/Root/AlphaBrowse.php +++ b/module/VuFind/src/VuFind/View/Helper/Root/AlphaBrowse.php @@ -84,7 +84,7 @@ public function getUrl($source, $item) $query = [ 'type' => ucwords($source) . 'Browse', - 'lookfor' => $this->escapeForSolr($item['heading']), + 'lookfor' => $this->escapeForSolr($item['sort_key']), ]; if ($this->options['bypass_default_filters'] ?? true) { $query['dfApplied'] = 1; diff --git a/module/VuFind/src/VuFind/Mailer/From.php b/module/VuFind/src/VuFind/View/Helper/Root/CookieManager.php similarity index 50% rename from module/VuFind/src/VuFind/Mailer/From.php rename to module/VuFind/src/VuFind/View/Helper/Root/CookieManager.php index 1ac5fea05fe..9fd7f2e8477 100644 --- a/module/VuFind/src/VuFind/Mailer/From.php +++ b/module/VuFind/src/VuFind/View/Helper/Root/CookieManager.php @@ -1,11 +1,11 @@ <?php /** - * Tweaked Laminas "From" header class + * CookieManager view helper * * PHP version 8 * - * Copyright (C) The National Library of Finland 2023. + * Copyright (C) Hebis Verbundzentrale 2024. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -21,24 +21,42 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * @category VuFind - * @package Mailer - * @author Ere Maijala <ere.maijala@helsinki.fi> + * @package View_Helpers + * @author Thomas Wagener <wagener@hebis.uni-frankfurt.de> * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License - * @link https://vufind.org/wiki/development Wiki + * @link https://vufind.org Main Site */ -namespace VuFind\Mailer; +namespace VuFind\View\Helper\Root; /** - * Tweaked Laminas "From" header class + * CookieManager view helper * * @category VuFind - * @package Mailer - * @author Ere Maijala <ere.maijala@helsinki.fi> + * @package View_Helpers + * @author Thomas Wagener <wagener@hebis.uni-frankfurt.de> * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License - * @link https://vufind.org/wiki/development Wiki + * @link https://vufind.org Main Site */ -class From extends \Laminas\Mail\Header\From +class CookieManager extends \Laminas\View\Helper\AbstractHelper { - use GetFieldValueFixTrait; + /** + * Constructor + * + * @param \VuFind\Cookie\CookieManager $cookieManager Cookie manager + */ + public function __construct( + protected \VuFind\Cookie\CookieManager $cookieManager, + ) { + } + + /** + * Get cookie manager. + * + * @return \VuFind\Cookie\CookieManager + */ + public function __invoke(): \VuFind\Cookie\CookieManager + { + return $this->cookieManager; + } } diff --git a/module/VuFind/src/VuFind/View/Helper/Root/CookieManagerFactory.php b/module/VuFind/src/VuFind/View/Helper/Root/CookieManagerFactory.php new file mode 100644 index 00000000000..ed9361e51fb --- /dev/null +++ b/module/VuFind/src/VuFind/View/Helper/Root/CookieManagerFactory.php @@ -0,0 +1,75 @@ +<?php + +/** + * CookieManager helper factory. + * + * PHP version 8 + * + * Copyright (C) Hebis Verbundzentrale 2024. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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 + * + * @category VuFind + * @package View_Helpers + * @author Thomas Wagener <wagener@hebis.uni-frankfurt.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ + +namespace VuFind\View\Helper\Root; + +use Laminas\ServiceManager\Exception\ServiceNotCreatedException; +use Laminas\ServiceManager\Exception\ServiceNotFoundException; +use Laminas\ServiceManager\Factory\FactoryInterface; +use Psr\Container\ContainerExceptionInterface as ContainerException; +use Psr\Container\ContainerInterface; + +/** + * CookieManager helper factory. + * + * @category VuFind + * @package View_Helpers + * @author Thomas Wagener <wagener@hebis.uni-frankfurt.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ +class CookieManagerFactory implements FactoryInterface +{ + /** + * Create an object + * + * @param ContainerInterface $container Service manager + * @param string $requestedName Service being created + * @param null|array $options Extra options (optional) + * + * @return object + * + * @throws ServiceNotFoundException if unable to resolve the service. + * @throws ServiceNotCreatedException if an exception is raised when + * creating a service. + * @throws ContainerException&\Throwable if any other error occurs + */ + public function __invoke( + ContainerInterface $container, + $requestedName, + array $options = null + ) { + if (!empty($options)) { + throw new \Exception('Unexpected options sent to factory.'); + } + return new $requestedName( + $container->get(\VuFind\Cookie\CookieManager::class), + ); + } +} diff --git a/module/VuFind/src/VuFind/View/Helper/Root/EscapeHtmlAttrFactory.php b/module/VuFind/src/VuFind/View/Helper/Root/EscapeHtmlAttrFactory.php new file mode 100644 index 00000000000..b0f0d7931f3 --- /dev/null +++ b/module/VuFind/src/VuFind/View/Helper/Root/EscapeHtmlAttrFactory.php @@ -0,0 +1,73 @@ +<?php + +/** + * EscapeHtmlAttr helper factory. + * + * PHP version 8 + * + * Copyright (C) The National Library of Finland 2024. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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 + * + * @category VuFind + * @package View_Helpers + * @author Ere Maijala <ere.maijala@helsinki.fi> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ + +namespace VuFind\View\Helper\Root; + +use Laminas\ServiceManager\Exception\ServiceNotCreatedException; +use Laminas\ServiceManager\Exception\ServiceNotFoundException; +use Laminas\ServiceManager\Factory\FactoryInterface; +use Psr\Container\ContainerExceptionInterface as ContainerException; +use Psr\Container\ContainerInterface; + +/** + * EscapeHtmlAttr helper factory. + * + * @category VuFind + * @package View_Helpers + * @author Ere Maijala <ere.maijala@helsinki.fi> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ +class EscapeHtmlAttrFactory implements FactoryInterface +{ + /** + * Create an object + * + * @param ContainerInterface $container Service manager + * @param string $requestedName Service being created + * @param null|array $options Extra options (optional) + * + * @return object + * + * @throws ServiceNotFoundException if unable to resolve the service. + * @throws ServiceNotCreatedException if an exception is raised when + * creating a service. + * @throws ContainerException&\Throwable if any other error occurs + */ + public function __invoke( + ContainerInterface $container, + $requestedName, + array $options = null + ) { + if (!empty($options)) { + throw new \Exception('Unexpected options sent to factory.'); + } + return new $requestedName($container->get(\VuFind\Escaper\Escaper::class)); + } +} diff --git a/module/VuFind/src/VuFind/Mailer/ReplyTo.php b/module/VuFind/src/VuFind/View/Helper/Root/HelperInitializer.php similarity index 54% rename from module/VuFind/src/VuFind/Mailer/ReplyTo.php rename to module/VuFind/src/VuFind/View/Helper/Root/HelperInitializer.php index a022782aaf2..06f8fc3dbc7 100644 --- a/module/VuFind/src/VuFind/Mailer/ReplyTo.php +++ b/module/VuFind/src/VuFind/View/Helper/Root/HelperInitializer.php @@ -1,11 +1,11 @@ <?php /** - * Tweaked Laminas "ReplyTo" header class + * View Helper Initializer * * PHP version 8 * - * Copyright (C) The National Library of Finland 2023. + * Copyright (C) The National Library of Finland 2024. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -21,24 +21,42 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * @category VuFind - * @package Mailer + * @package View_Helpers * @author Ere Maijala <ere.maijala@helsinki.fi> * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License * @link https://vufind.org/wiki/development Wiki */ -namespace VuFind\Mailer; +namespace VuFind\View\Helper\Root; + +use Laminas\ServiceManager\Initializer\InitializerInterface; +use Laminas\View\Helper\Placeholder\Container\AbstractStandalone; +use Psr\Container\ContainerInterface; /** - * Tweaked Laminas "ReplyTo" header class + * View Helper Initializer * * @category VuFind - * @package Mailer + * @package View_Helpers * @author Ere Maijala <ere.maijala@helsinki.fi> * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License * @link https://vufind.org/wiki/development Wiki */ -class ReplyTo extends \Laminas\Mail\Header\ReplyTo +class HelperInitializer implements InitializerInterface { - use GetFieldValueFixTrait; + /** + * Given an instance and a Service Manager, initialize the instance. + * + * @param ContainerInterface $container Service manager + * @param object $instance Instance to initialize + * + * @return object + */ + public function __invoke(ContainerInterface $container, $instance) + { + if ($instance instanceof AbstractStandalone) { + $instance->setEscaper($container->get(\VuFind\Escaper\Escaper::class)); + } + return $instance; + } } diff --git a/module/VuFind/src/VuFind/View/Helper/Root/Metadata.php b/module/VuFind/src/VuFind/View/Helper/Root/Metadata.php index 5289381edbf..d35d483ddf3 100644 --- a/module/VuFind/src/VuFind/View/Helper/Root/Metadata.php +++ b/module/VuFind/src/VuFind/View/Helper/Root/Metadata.php @@ -102,7 +102,7 @@ protected function getVocabularies(\VuFind\RecordDriver\AbstractBase $driver) * Generate all metatags for RecordDriver and add to page * * Decide which Plugins to load for the given RecordDriver - * dependant on configuration. (only by class name, + * dependent on configuration. (only by class name, * namespace will not be considered) * * @param \VuFind\RecordDriver\AbstractBase $driver Record driver diff --git a/module/VuFind/src/VuFind/View/Helper/Root/Record.php b/module/VuFind/src/VuFind/View/Helper/Root/Record.php index 2c71299b1cc..26a4a3a0326 100644 --- a/module/VuFind/src/VuFind/View/Helper/Root/Record.php +++ b/module/VuFind/src/VuFind/View/Helper/Root/Record.php @@ -38,6 +38,8 @@ use VuFind\Db\Service\DbServiceAwareTrait; use VuFind\Db\Service\UserListServiceInterface; use VuFind\Db\Service\UserResourceServiceInterface; +use VuFind\Search\Memory; +use VuFind\Search\UrlQueryHelper; use VuFind\Tags\TagsService; use function get_class; @@ -74,6 +76,13 @@ class Record extends \Laminas\View\Helper\AbstractHelper implements DbServiceAwa */ protected $coverRouter = null; + /** + * Search memory + * + * @var Memory + */ + protected $searchMemory = null; + /** * Record driver * @@ -104,6 +113,18 @@ public function setCoverRouter($router) $this->coverRouter = $router; } + /** + * Inject the search memory + * + * @param Memory $memory Search memory + * + * @return void + */ + public function setSearchMemory(Memory $memory): void + { + $this->searchMemory = $memory; + } + /** * Render a template within a record driver folder. * @@ -462,13 +483,34 @@ public function getLink($type, $lookfor) $prepend = (!str_contains($link, '?')) ? '?' : '&'; - $link .= $this->getView()->plugin('searchTabs') - ->getCurrentHiddenFilterParams( - $this->driver->getSearchBackendIdentifier(), - false, - $prepend - ); - return $link; + $hiddenFilters = null; + // Try to get hidden filters for the current search: + if ($this->searchMemory) { + $view = $this->getView(); + $searchId = $this->driver->getExtraDetail('searchId') + ?? $view->plugin('searchMemory')->getLastSearchId(); + if ( + $searchId + && ($search = $this->searchMemory->getSearchById($searchId, $view->plugin('auth')->getUserObject())) + ) { + $filters = UrlQueryHelper::buildQueryString( + [ + 'hiddenFilters' => $search->getParams()->getHiddenFiltersAsQueryParams(), + ] + ); + $hiddenFilters = $filters ? $prepend . $filters : ''; + } + } + // If we couldn't get hidden filters for the current search, use last filters: + if (null === $hiddenFilters) { + $hiddenFilters = $this->getView()->plugin('searchTabs') + ->getCurrentHiddenFilterParams( + $this->driver->getSearchBackendIdentifier(), + false, + $prepend + ); + } + return $link . $hiddenFilters; } /** @@ -522,10 +564,11 @@ public function getSearchResult($view) */ public function getCheckbox($idPrefix = '', $formAttr = false, $number = null) { - $id = $this->driver->getSourceIdentifier() . '|' - . $this->driver->getUniqueId(); - $context - = ['id' => $id, 'number' => $number, 'prefix' => $idPrefix]; + $context = compact('number') + [ + 'id' => $this->getUniqueIdWithSourcePrefix(), + 'checkboxElementId' => $this->getUniqueHtmlElementId($idPrefix), + 'prefix' => $idPrefix, + ]; if ($formAttr) { $context['formAttr'] = $formAttr; } @@ -819,4 +862,38 @@ protected function deduplicateLinks($links) { return array_values(array_unique($links, SORT_REGULAR)); } + + /** + * Get the source identifier + unique id of the record without spaces + * + * @param string $idPrefix Prefix for HTML ids + * + * @return string + */ + public function getUniqueHtmlElementId($idPrefix = '') + { + $resultSetId = $this->driver->getResultSetIdentifier() ?? ''; + + return preg_replace( + "/\s+/", + '_', + ($idPrefix ? $idPrefix . '-' : '') + . ($resultSetId ? $resultSetId . '-' : '') + . $this->driver->getUniqueId() + ); + } + + /** + * Get the source identifier + unique id of the record + * + * @return string + */ + public function getUniqueIdWithSourcePrefix() + { + if ($this->driver) { + return "{$this->driver->getSourceIdentifier()}" + . "|{$this->driver->getUniqueId()}"; + } + throw new \Exception('No record driver found.'); + } } diff --git a/module/VuFind/src/VuFind/View/Helper/Root/RecordDataFormatterFactory.php b/module/VuFind/src/VuFind/View/Helper/Root/RecordDataFormatterFactory.php index 086cde54380..f8f4fce9ac1 100644 --- a/module/VuFind/src/VuFind/View/Helper/Root/RecordDataFormatterFactory.php +++ b/module/VuFind/src/VuFind/View/Helper/Root/RecordDataFormatterFactory.php @@ -191,6 +191,7 @@ public function getDefaultCollectionInfoSpecs() $this->getAuthorFunction() ); $spec->setLine('Summary', 'getSummary'); + $spec->setLine('Abstract', 'getAbstractNotes'); $spec->setLine( 'Format', 'getFormats', @@ -255,6 +256,7 @@ public function getDefaultCollectionRecordSpecs() { $spec = new RecordDataFormatter\SpecBuilder(); $spec->setLine('Summary', 'getSummary'); + $spec->setLine('Abstract', 'getAbstractNotes'); $spec->setMultiLine( 'Authors', 'getDeduplicatedAuthors', @@ -369,6 +371,9 @@ public function getDefaultDescriptionSpecs() { $spec = new RecordDataFormatter\SpecBuilder(); $spec->setTemplateLine('Summary', true, 'data-summary.phtml'); + $spec->setLine('Abstract', 'getAbstractNotes'); + $spec->setLine('Review', 'getReviewNotes'); + $spec->setLine('Content Advice', 'getContentAdviceNotes'); $spec->setLine('Published', 'getDateSpan'); $spec->setLine('Item Description', 'getGeneralNotes'); $spec->setLine('Physical Description', 'getPhysicalDescriptions'); diff --git a/module/VuFind/src/VuFind/View/Helper/Root/RecordFactory.php b/module/VuFind/src/VuFind/View/Helper/Root/RecordFactory.php index 336dc2ee301..a337b07c3fb 100644 --- a/module/VuFind/src/VuFind/View/Helper/Root/RecordFactory.php +++ b/module/VuFind/src/VuFind/View/Helper/Root/RecordFactory.php @@ -72,6 +72,7 @@ public function __invoke( $config = $container->get(\VuFind\Config\PluginManager::class)->get('config'); $helper = new $requestedName($container->get(TagsService::class), $config); $helper->setCoverRouter($container->get(\VuFind\Cover\Router::class)); + $helper->setSearchMemory($container->get(\VuFind\Search\Memory::class)); return $helper; } } diff --git a/module/VuFind/src/VuFind/View/Helper/Root/SearchMemory.php b/module/VuFind/src/VuFind/View/Helper/Root/SearchMemory.php index c6928656be6..7ad8007e3ff 100644 --- a/module/VuFind/src/VuFind/View/Helper/Root/SearchMemory.php +++ b/module/VuFind/src/VuFind/View/Helper/Root/SearchMemory.php @@ -111,6 +111,7 @@ public function getLastSearchUrl(): ?string if (!empty($searchContext['page'])) { $queryHelper = $queryHelper->setPage($searchContext['page']); } + $queryHelper = $queryHelper->setJumpto(false); $url .= $queryHelper->getParams(false); diff --git a/module/VuFind/src/VuFind/View/Helper/Root/SearchTabs.php b/module/VuFind/src/VuFind/View/Helper/Root/SearchTabs.php index f5bd7e2b58f..69e3b346253 100644 --- a/module/VuFind/src/VuFind/View/Helper/Root/SearchTabs.php +++ b/module/VuFind/src/VuFind/View/Helper/Root/SearchTabs.php @@ -219,10 +219,8 @@ public function getHiddenFilters( * Get current hidden filters as a string suitable for search URLs * * @param string $searchClassId Active search class - * @param bool $ignoreHiddenFilterMemory Whether to ignore hidden filters in - * search memory - * @param string $prepend String to prepend to the hidden - * filters if they're not empty + * @param bool $ignoreHiddenFilterMemory Whether to ignore hidden filters in search memory + * @param string $prepend String to prepend to the hidden filters if they're not empty * * @return string */ @@ -263,7 +261,10 @@ public function getCurrentHiddenFilterParams( $this->cachedHiddenFilterParams[$searchClassId] = ''; } } - return $prepend . $this->cachedHiddenFilterParams[$searchClassId]; + if ('' !== ($filters = $this->cachedHiddenFilterParams[$searchClassId])) { + return $prepend . $filters; + } + return ''; } /** diff --git a/module/VuFind/src/VuFind/View/Helper/Root/SystemEmailFactory.php b/module/VuFind/src/VuFind/View/Helper/Root/SystemEmailFactory.php index 1a5ffb8313f..52bb3fc7ca4 100644 --- a/module/VuFind/src/VuFind/View/Helper/Root/SystemEmailFactory.php +++ b/module/VuFind/src/VuFind/View/Helper/Root/SystemEmailFactory.php @@ -68,8 +68,7 @@ public function __invoke( if (!empty($options)) { throw new \Exception('Unexpected options sent to factory.'); } - $config = $container->get(\VuFind\Config\PluginManager::class) - ->get('config'); + $config = $container->get(\VuFind\Config\PluginManager::class)->get('config'); return new $requestedName($config->Site->email ?? ''); } } diff --git a/module/VuFind/src/VuFind/XSLT/Import/VuFindGeo.php b/module/VuFind/src/VuFind/XSLT/Import/VuFindGeo.php index a35929a7233..420c6ad8e1e 100644 --- a/module/VuFind/src/VuFind/XSLT/Import/VuFindGeo.php +++ b/module/VuFind/src/VuFind/XSLT/Import/VuFindGeo.php @@ -45,7 +45,7 @@ class VuFindGeo { /** - * Method for logging errors (overrideable for testing purposes) + * Method for logging errors (overridable for testing purposes) * * @var callable */ diff --git a/module/VuFind/src/VuFindTest/Feature/EmailTrait.php b/module/VuFind/src/VuFindTest/Feature/EmailTrait.php index 77da4e60e67..61ac8b3802f 100644 --- a/module/VuFind/src/VuFindTest/Feature/EmailTrait.php +++ b/module/VuFind/src/VuFindTest/Feature/EmailTrait.php @@ -29,6 +29,8 @@ namespace VuFindTest\Feature; +use Symfony\Component\Mime\Email; + /** * Trait adding the ability to inspect sent emails. * @@ -50,6 +52,16 @@ protected function getEmailLogPath(): string return APPLICATION_PATH . '/vufind-mail.log'; } + /** + * Get the format to use for email message log. + * + * @return string + */ + protected function getEmailLogFormat(): string + { + return 'serialized'; + } + /** * Clear out the email log to eliminate any past contents. * @@ -59,4 +71,24 @@ protected function resetEmailLog(): void { file_put_contents($this->getEmailLogPath(), ''); } + + /** + * Get a logged email from the log file. + * + * @param int $index Index of the message to get (0-based) + * + * @return Email + */ + protected function getLoggedEmail(int $index = 0): Email + { + $data = file_get_contents($this->getEmailLogPath()); + if (!$data) { + throw new \Exception('No serialized email message data found'); + } + $records = explode("\x1E", $data); + if (null === ($record = $records[$index] ?? null)) { + throw new \Exception("Message with index $index not found"); + } + return unserialize($record); + } } diff --git a/module/VuFind/src/VuFindTest/Integration/MinkTestCase.php b/module/VuFind/src/VuFindTest/Integration/MinkTestCase.php index 91112bd4a5d..4fc03dd3f40 100644 --- a/module/VuFind/src/VuFindTest/Integration/MinkTestCase.php +++ b/module/VuFind/src/VuFindTest/Integration/MinkTestCase.php @@ -231,7 +231,7 @@ protected function changeYamlConfigs($configs, $replace = []) * * @return void */ - protected function changeConfigFile($configName, $settings, $replace = false) + protected function changeConfigFile(string $configName, array $settings, bool $replace = false): void { $file = $configName . '.ini'; $local = $this->pathResolver->getLocalConfigPath($file, null, true); @@ -246,12 +246,27 @@ protected function changeConfigFile($configName, $settings, $replace = false) $this->modifiedConfigs[] = $configName; } + $this->writeConfigFile($local, $settings, $replace); + } + + /** + * Write settings to a file. + * + * @param string $path Path of file to modify. + * @param array $settings Settings to change. + * @param bool $replace Should we replace the existing config entirely + * (as opposed to extending it with new settings)? + * + * @return void + */ + protected function writeConfigFile(string $path, array $settings, bool $replace = false): void + { // If we're replacing the existing file, wipe it out now: if ($replace) { - file_put_contents($local, ''); + file_put_contents($path, ''); } - $writer = new ConfigWriter($local); + $writer = new ConfigWriter($path); foreach ($settings as $section => $contents) { foreach ($contents as $key => $value) { $writer->set($section, $key, $value); diff --git a/module/VuFind/src/VuFindTest/Integration/Session.php b/module/VuFind/src/VuFindTest/Integration/Session.php index ae11cf330cd..86effe5d4ad 100644 --- a/module/VuFind/src/VuFindTest/Integration/Session.php +++ b/module/VuFind/src/VuFindTest/Integration/Session.php @@ -55,7 +55,14 @@ class Session extends \Behat\Mink\Session protected $coverageDir = ''; /** - * Set remote code coverate configuration + * Whether Whoops error handler needs to be disabled + * + * @var bool + */ + protected $disableWhoops = false; + + /** + * Set remote code coverage configuration * * @param string $testName Test name * @param string $coverageDir Coverage data directory @@ -70,6 +77,18 @@ public function setRemoteCoverageConfig( $this->coverageDir = $coverageDir; } + /** + * Toggle HTTP header that disables Whoops + * + * @param bool $disable Whether to disable Whoops + * + * @return void + */ + public function setWhoopsDisabled(bool $disable): void + { + $this->disableWhoops = $disable; + } + /** * Visit specified URL and automatically start session if not already running. * @@ -92,6 +111,9 @@ public function visit($url) ) ); } + if ($this->disableWhoops) { + $this->setRequestHeader('X-VuFind-Disable-Whoops', '1'); + } parent::visit($url); } diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/WorldCatTest.php b/module/VuFind/src/VuFindTest/RecordDriver/MarcBasicTraitTestHarness.php similarity index 56% rename from module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/WorldCatTest.php rename to module/VuFind/src/VuFindTest/RecordDriver/MarcBasicTraitTestHarness.php index 52c22de223a..dc78033e20a 100644 --- a/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/WorldCatTest.php +++ b/module/VuFind/src/VuFindTest/RecordDriver/MarcBasicTraitTestHarness.php @@ -1,11 +1,11 @@ <?php /** - * WorldCat Record Driver Test Class + * Test harness for simulating MARC record drivers (ignore outside of test suite!) * * PHP version 8 * - * Copyright (C) Villanova University 2021. + * Copyright (C) Villanova University 2024. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -21,7 +21,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * @category VuFind - * @package Tests + * @package RecordDrivers * @author Demian Katz <demian.katz@villanova.edu> * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License * @link https://vufind.org/wiki/development:testing:unit_tests Wiki @@ -29,34 +29,21 @@ namespace VuFindTest\RecordDriver; +use VuFind\RecordDriver\DefaultRecord; +use VuFind\RecordDriver\Feature\MarcBasicTrait; +use VuFind\RecordDriver\Feature\MarcReaderTrait; + /** - * WorldCat Record Driver Test Class + * Test harness for simulating MARC record drivers (ignore outside of test suite!) * * @category VuFind - * @package Tests + * @package RecordDrivers * @author Demian Katz <demian.katz@villanova.edu> * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License * @link https://vufind.org/wiki/development:testing:unit_tests Wiki */ -class WorldCatTest extends \PHPUnit\Framework\TestCase +class MarcBasicTraitTestHarness extends DefaultRecord { - use \VuFindTest\Feature\FixtureTrait; - - /** - * Test that we can setRawData() with getRawData() without breaking anything. - * - * @return void - */ - public function testSetRawData() - { - $marc = $this->getFixture('marc/marctraits.xml'); - $driver = new \VuFind\RecordDriver\WorldCat(); - $driver->setRawData($marc); - // Test that we can pull data from MARC... - $this->assertEquals('The Foo:', $driver->getShortTitle()); - // Now reassign the raw data... - $driver->setRawData($driver->getRawData()); - // Now confirm that we still get the same result... - $this->assertEquals('The Foo:', $driver->getShortTitle()); - } + use MarcReaderTrait; + use MarcBasicTrait; } diff --git a/module/VuFind/src/VuFindTest/Unit/AbstractMakeTagTestCase.php b/module/VuFind/src/VuFindTest/Unit/AbstractMakeTagTestCase.php index 14f83674898..4cd10bc0521 100644 --- a/module/VuFind/src/VuFindTest/Unit/AbstractMakeTagTestCase.php +++ b/module/VuFind/src/VuFindTest/Unit/AbstractMakeTagTestCase.php @@ -51,7 +51,7 @@ protected function getViewWithHelpers() { $helpers = [ 'escapehtml' => new \Laminas\View\Helper\EscapeHtml(), - 'escapehtmlattr' => new \Laminas\View\Helper\EscapeHtmlAttr(), + 'escapehtmlattr' => new \Laminas\View\Helper\EscapeHtmlAttr(new \VuFind\Escaper\Escaper()), 'htmlattributes' => new \Laminas\View\Helper\HtmlAttributes(), 'maketag' => new \VuFind\View\Helper\Root\MakeTag(), ]; diff --git a/module/VuFind/tests/fixtures/configs/contentsecuritypolicy/contentsecuritypolicy.ini b/module/VuFind/tests/fixtures/configs/contentsecuritypolicy/contentsecuritypolicy.ini index 79b3768bc1e..1d6247c0819 100644 --- a/module/VuFind/tests/fixtures/configs/contentsecuritypolicy/contentsecuritypolicy.ini +++ b/module/VuFind/tests/fixtures/configs/contentsecuritypolicy/contentsecuritypolicy.ini @@ -69,7 +69,7 @@ base-uri[] = "'self'" ; ; Set URI that some browsers use to report CSP violation. report-uri[] = 'https://abc.report-uri.com' -; Set the named endpoint that other borwsers use to report CSP violations. The endpoint name +; Set the named endpoint that other browsers use to report CSP violations. The endpoint name ; should match a group name in ReportTo below. report-to[] = 'CSPReportingEndpoint' diff --git a/module/VuFind/tests/fixtures/dirlocations/DirLocations.ini b/module/VuFind/tests/fixtures/dirlocations/DirLocations.ini new file mode 100644 index 00000000000..56548e8ca6e --- /dev/null +++ b/module/VuFind/tests/fixtures/dirlocations/DirLocations.ini @@ -0,0 +1,13 @@ +; A parent local directory can be specified. When resolving paths to configuration +; files this dir will also be searched if the file does not exist in the current dir. +[Parent_Dir] +; Path to the parent directory +;path = +; Whether the path is relative to this file (true) or absolute (false). +; Defaults to false. +;is_relative_path = + +; Configurations for this local directory +[Local_Dir] +; A custom config subdir can be configured. The default is config/vufind. +config_subdir = test_subdir diff --git a/module/VuFind/tests/fixtures/dirlocations/cache/.gitignore b/module/VuFind/tests/fixtures/dirlocations/cache/.gitignore new file mode 100644 index 00000000000..d6b7ef32c84 --- /dev/null +++ b/module/VuFind/tests/fixtures/dirlocations/cache/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/module/VuFind/tests/fixtures/dirlocations/languages/ParentDomain/en.ini b/module/VuFind/tests/fixtures/dirlocations/languages/ParentDomain/en.ini new file mode 100644 index 00000000000..6b3b4513962 --- /dev/null +++ b/module/VuFind/tests/fixtures/dirlocations/languages/ParentDomain/en.ini @@ -0,0 +1 @@ +test = "Parent Text" diff --git a/module/VuFind/tests/fixtures/dirlocations/languages/en.ini b/module/VuFind/tests/fixtures/dirlocations/languages/en.ini new file mode 100644 index 00000000000..eca9299b7db --- /dev/null +++ b/module/VuFind/tests/fixtures/dirlocations/languages/en.ini @@ -0,0 +1,2 @@ +test_string_1 = "should not be displayed" +test_string_2 = "Parent Text" diff --git a/module/VuFind/tests/fixtures/dirlocations/test_subdir/AccountMenu.yaml b/module/VuFind/tests/fixtures/dirlocations/test_subdir/AccountMenu.yaml new file mode 100644 index 00000000000..d518669e1e8 --- /dev/null +++ b/module/VuFind/tests/fixtures/dirlocations/test_subdir/AccountMenu.yaml @@ -0,0 +1,5 @@ +MenuItems: + - name: parent-test + label: parent test + route: myresearch-favorites + icon: user-favorites diff --git a/module/VuFind/tests/fixtures/dirlocations/test_subdir/searches.ini b/module/VuFind/tests/fixtures/dirlocations/test_subdir/searches.ini new file mode 100644 index 00000000000..d57aa70afb0 --- /dev/null +++ b/module/VuFind/tests/fixtures/dirlocations/test_subdir/searches.ini @@ -0,0 +1,5 @@ +[Parent_Config] +relative_path = ../../../../../../config/vufind/searches.ini + +[Basic_Searches] +ParentTest = "ParentTest" diff --git a/module/VuFind/tests/fixtures/folio/responses/request-type-fallback.json b/module/VuFind/tests/fixtures/folio/responses/request-type-fallback.json index 5e5e74237bb..665597dc831 100644 --- a/module/VuFind/tests/fixtures/folio/responses/request-type-fallback.json +++ b/module/VuFind/tests/fixtures/folio/responses/request-type-fallback.json @@ -78,6 +78,27 @@ "bodyType": "json", "status": 200 }, + { + "expectedMethod": "GET", + "expectedPath": "\/circulation\/requests\/allowed-service-points?instanceId=record1&requesterId=user1&operation=create", + "expectedParams": [], + "body": { + "Page": [ + { + "id": "servicepoint1", + "name": "Main Service Desk" + } + ], + "Recall": [ + { + "id": "servicepoint1", + "name": "Main Service Desk" + } + ] + }, + "bodyType": "json", + "status": 200 + }, { "expectedMethod": "POST", "expectedPath": "\/circulation\/requests", diff --git a/module/VuFind/tests/fixtures/folio/responses/successful-patron-login-with-okapi-legacy.json b/module/VuFind/tests/fixtures/folio/responses/successful-patron-login-with-okapi-legacy.json index fade83cc077..be24136947b 100644 --- a/module/VuFind/tests/fixtures/folio/responses/successful-patron-login-with-okapi-legacy.json +++ b/module/VuFind/tests/fixtures/folio/responses/successful-patron-login-with-okapi-legacy.json @@ -14,6 +14,6 @@ "expectedParams": { "query": "username == foo" }, - "body": "{ \"users\": [ { \"id\": \"fake-id\", \"personal\": { \"firstName\": \"first\", \"lastName\": \"last\", \"email\": \"fake@fake.com\" } } ] }" + "body": "{ \"users\": [ { \"id\": \"fake-id\", \"personal\": { \"firstName\": \"first\", \"lastName\": \"last\", \"email\": \"fake@fake.com\", \"addresses\": [] } } ] }" } ] diff --git a/module/VuFind/tests/fixtures/folio/responses/successful-patron-login-with-okapi.json b/module/VuFind/tests/fixtures/folio/responses/successful-patron-login-with-okapi.json index 717c7f64540..789020bb68d 100644 --- a/module/VuFind/tests/fixtures/folio/responses/successful-patron-login-with-okapi.json +++ b/module/VuFind/tests/fixtures/folio/responses/successful-patron-login-with-okapi.json @@ -14,6 +14,6 @@ "expectedParams": { "query": "username == foo" }, - "body": "{ \"users\": [ { \"id\": \"fake-id\", \"personal\": { \"firstName\": \"first\", \"lastName\": \"last\", \"email\": \"fake@fake.com\" } } ] }" + "body": "{ \"users\": [ { \"id\": \"fake-id\", \"personal\": { \"firstName\": \"first\", \"lastName\": \"last\", \"email\": \"fake@fake.com\", \"addresses\": [] } } ] }" } ] diff --git a/module/VuFind/tests/fixtures/folio/responses/successful-place-hold-legacy.json b/module/VuFind/tests/fixtures/folio/responses/successful-place-hold-legacy.json index 8e82a61b612..59b0c600211 100644 --- a/module/VuFind/tests/fixtures/folio/responses/successful-place-hold-legacy.json +++ b/module/VuFind/tests/fixtures/folio/responses/successful-place-hold-legacy.json @@ -2,6 +2,89 @@ { "comment": "Initial token check" }, + { + "expectedMethod": "GET", + "expectedPath": "\/instance-storage\/instances", + "expectedParams": { + "query": "(id==\"instanceid\")" + }, + "body": { + "instances": [ + { + "id": "instanceid", + "_version": 1, + "hrid": "foo", + "source": "MARC", + "title": "The bride of the tomb; or, Lancelot Darling's betrothed \/ By Mrs. Alex. McVeigh Miller.", + "indexTitle": "Bride of the tomb; or, lancelot darling's betrothed", + "editions": [], + "series": [ + "Munro's library ; v. 1, no. 2" + ], + "identifiers": [], + "contributors": [ + { + "name": "Miller, Alex. McVeigh, Mrs", + "contributorTypeId": "contypeID", + "contributorTypeText": "Contributor", + "contributorNameTypeId": "conNameTypeID", + "primary": true + } + ], + "subjects": [ + "Dime novels Specimens", + "Genre: Popular literature Specimens", + "Genre: Mystery and detective fiction" + ], + "classifications": [ + { + "classificationNumber": "PS2394 .M643 1883", + "classificationTypeId": "ctypeId" + } + ], + "publication": [ + { + "publisher": "Norman L. Munro", + "place": "New York", + "dateOfPublication": "1883" + } + ], + "publicationFrequency": [], + "publicationRange": [], + "publicationPeriod": { + "start": 1883 + }, + "electronicAccess": [], + "instanceTypeId": "insttypeID", + "instanceFormatIds": [], + "instanceFormats": [], + "physicalDescriptions": [ + "144 p. ; 19 cm." + ], + "languages": [ + "eng" + ], + "notes": [], + "modeOfIssuanceId": "moiid", + "previouslyHeld": false, + "staffSuppress": false, + "discoverySuppress": false, + "statisticalCodeIds": [], + "statusUpdatedDate": "2022-12-22T23:34:26.209+0000", + "holdingsRecords2": [], + "natureOfContentTermIds": [] + } + ], + "totalRecords": 1, + "resultInfo": { + "totalRecords": 1, + "facets": [], + "diagnostics": [] + } + }, + "bodyType": "json", + "status": 200 + }, { "comment": "Version check", "expectedMethod": "GET", @@ -11,6 +94,21 @@ "bodyType": "json", "status": 200 }, + { + "expectedMethod": "GET", + "expectedPath": "\/circulation\/requests\/allowed-service-points?instanceId=instanceid&requesterId=foo&operation=create", + "expectedParams": [], + "body": { + "Page": [ + { + "id": "desk1", + "name": "Main Service Desk" + } + ] + }, + "bodyType": "json", + "status": 200 + }, { "expectedMethod": "POST", "expectedPath": "/circulation/requests", diff --git a/module/VuFind/tests/fixtures/folio/responses/successful-place-hold-no-expiration-date.json b/module/VuFind/tests/fixtures/folio/responses/successful-place-hold-no-expiration-date.json index f4aef19c21e..c71a6c8b66a 100644 --- a/module/VuFind/tests/fixtures/folio/responses/successful-place-hold-no-expiration-date.json +++ b/module/VuFind/tests/fixtures/folio/responses/successful-place-hold-no-expiration-date.json @@ -1,6 +1,89 @@ [ { "comment": "Initial request for token" }, + { + "expectedMethod": "GET", + "expectedPath": "\/instance-storage\/instances", + "expectedParams": { + "query": "(id==\"instanceid\")" + }, + "body": { + "instances": [ + { + "id": "instanceid", + "_version": 1, + "hrid": "foo", + "source": "MARC", + "title": "The bride of the tomb; or, Lancelot Darling's betrothed \/ By Mrs. Alex. McVeigh Miller.", + "indexTitle": "Bride of the tomb; or, lancelot darling's betrothed", + "editions": [], + "series": [ + "Munro's library ; v. 1, no. 2" + ], + "identifiers": [], + "contributors": [ + { + "name": "Miller, Alex. McVeigh, Mrs", + "contributorTypeId": "contypeID", + "contributorTypeText": "Contributor", + "contributorNameTypeId": "conNameTypeID", + "primary": true + } + ], + "subjects": [ + "Dime novels Specimens", + "Genre: Popular literature Specimens", + "Genre: Mystery and detective fiction" + ], + "classifications": [ + { + "classificationNumber": "PS2394 .M643 1883", + "classificationTypeId": "ctypeId" + } + ], + "publication": [ + { + "publisher": "Norman L. Munro", + "place": "New York", + "dateOfPublication": "1883" + } + ], + "publicationFrequency": [], + "publicationRange": [], + "publicationPeriod": { + "start": 1883 + }, + "electronicAccess": [], + "instanceTypeId": "insttypeID", + "instanceFormatIds": [], + "instanceFormats": [], + "physicalDescriptions": [ + "144 p. ; 19 cm." + ], + "languages": [ + "eng" + ], + "notes": [], + "modeOfIssuanceId": "moiid", + "previouslyHeld": false, + "staffSuppress": false, + "discoverySuppress": false, + "statisticalCodeIds": [], + "statusUpdatedDate": "2022-12-22T23:34:26.209+0000", + "holdingsRecords2": [], + "natureOfContentTermIds": [] + } + ], + "totalRecords": 1, + "resultInfo": { + "totalRecords": 1, + "facets": [], + "diagnostics": [] + } + }, + "bodyType": "json", + "status": 200 + }, { "comment": "Version check", "expectedMethod": "GET", @@ -10,6 +93,21 @@ "bodyType": "json", "status": 200 }, + { + "expectedMethod": "GET", + "expectedPath": "\/circulation\/requests\/allowed-service-points?instanceId=instanceid&requesterId=foo&operation=create", + "expectedParams": [], + "body": { + "Page": [ + { + "id": "desk1", + "name": "Main Service Desk" + } + ] + }, + "bodyType": "json", + "status": 200 + }, { "expectedMethod": "POST", "expectedPath": "/circulation/requests", diff --git a/module/VuFind/tests/fixtures/folio/responses/successful-place-hold.json b/module/VuFind/tests/fixtures/folio/responses/successful-place-hold.json index a36c5c6db5c..26950bd5ed0 100644 --- a/module/VuFind/tests/fixtures/folio/responses/successful-place-hold.json +++ b/module/VuFind/tests/fixtures/folio/responses/successful-place-hold.json @@ -2,6 +2,89 @@ { "comment": "Initial token check" }, + { + "expectedMethod": "GET", + "expectedPath": "\/instance-storage\/instances", + "expectedParams": { + "query": "(id==\"instanceid\")" + }, + "body": { + "instances": [ + { + "id": "instanceid", + "_version": 1, + "hrid": "foo", + "source": "MARC", + "title": "The bride of the tomb; or, Lancelot Darling's betrothed \/ By Mrs. Alex. McVeigh Miller.", + "indexTitle": "Bride of the tomb; or, lancelot darling's betrothed", + "editions": [], + "series": [ + "Munro's library ; v. 1, no. 2" + ], + "identifiers": [], + "contributors": [ + { + "name": "Miller, Alex. McVeigh, Mrs", + "contributorTypeId": "contypeID", + "contributorTypeText": "Contributor", + "contributorNameTypeId": "conNameTypeID", + "primary": true + } + ], + "subjects": [ + "Dime novels Specimens", + "Genre: Popular literature Specimens", + "Genre: Mystery and detective fiction" + ], + "classifications": [ + { + "classificationNumber": "PS2394 .M643 1883", + "classificationTypeId": "ctypeId" + } + ], + "publication": [ + { + "publisher": "Norman L. Munro", + "place": "New York", + "dateOfPublication": "1883" + } + ], + "publicationFrequency": [], + "publicationRange": [], + "publicationPeriod": { + "start": 1883 + }, + "electronicAccess": [], + "instanceTypeId": "insttypeID", + "instanceFormatIds": [], + "instanceFormats": [], + "physicalDescriptions": [ + "144 p. ; 19 cm." + ], + "languages": [ + "eng" + ], + "notes": [], + "modeOfIssuanceId": "moiid", + "previouslyHeld": false, + "staffSuppress": false, + "discoverySuppress": false, + "statisticalCodeIds": [], + "statusUpdatedDate": "2022-12-22T23:34:26.209+0000", + "holdingsRecords2": [], + "natureOfContentTermIds": [] + } + ], + "totalRecords": 1, + "resultInfo": { + "totalRecords": 1, + "facets": [], + "diagnostics": [] + } + }, + "bodyType": "json", + "status": 200 + }, { "comment": "Version check", "expectedMethod": "GET", @@ -11,6 +94,21 @@ "bodyType": "json", "status": 200 }, + { + "expectedMethod": "GET", + "expectedPath": "\/circulation\/requests\/allowed-service-points?instanceId=instanceid&requesterId=foo&operation=create", + "expectedParams": [], + "body": { + "Page": [ + { + "id": "desk1", + "name": "Main Service Desk" + } + ] + }, + "bodyType": "json", + "status": 200 + }, { "expectedMethod": "POST", "expectedPath": "/circulation/requests", diff --git a/module/VuFind/tests/fixtures/folio/responses/unsuccessful-place-hold.json b/module/VuFind/tests/fixtures/folio/responses/unsuccessful-place-hold.json index d4c3c99e5cd..10d534b101c 100644 --- a/module/VuFind/tests/fixtures/folio/responses/unsuccessful-place-hold.json +++ b/module/VuFind/tests/fixtures/folio/responses/unsuccessful-place-hold.json @@ -1,6 +1,89 @@ [ { "comment": "Initial request for token" }, + { + "expectedMethod": "GET", + "expectedPath": "\/instance-storage\/instances", + "expectedParams": { + "query": "(id==\"instanceid\")" + }, + "body": { + "instances": [ + { + "id": "instanceid", + "_version": 1, + "hrid": "foo", + "source": "MARC", + "title": "The bride of the tomb; or, Lancelot Darling's betrothed \/ By Mrs. Alex. McVeigh Miller.", + "indexTitle": "Bride of the tomb; or, lancelot darling's betrothed", + "editions": [], + "series": [ + "Munro's library ; v. 1, no. 2" + ], + "identifiers": [], + "contributors": [ + { + "name": "Miller, Alex. McVeigh, Mrs", + "contributorTypeId": "contypeID", + "contributorTypeText": "Contributor", + "contributorNameTypeId": "conNameTypeID", + "primary": true + } + ], + "subjects": [ + "Dime novels Specimens", + "Genre: Popular literature Specimens", + "Genre: Mystery and detective fiction" + ], + "classifications": [ + { + "classificationNumber": "PS2394 .M643 1883", + "classificationTypeId": "ctypeId" + } + ], + "publication": [ + { + "publisher": "Norman L. Munro", + "place": "New York", + "dateOfPublication": "1883" + } + ], + "publicationFrequency": [], + "publicationRange": [], + "publicationPeriod": { + "start": 1883 + }, + "electronicAccess": [], + "instanceTypeId": "insttypeID", + "instanceFormatIds": [], + "instanceFormats": [], + "physicalDescriptions": [ + "144 p. ; 19 cm." + ], + "languages": [ + "eng" + ], + "notes": [], + "modeOfIssuanceId": "moiid", + "previouslyHeld": false, + "staffSuppress": false, + "discoverySuppress": false, + "statisticalCodeIds": [], + "statusUpdatedDate": "2022-12-22T23:34:26.209+0000", + "holdingsRecords2": [], + "natureOfContentTermIds": [] + } + ], + "totalRecords": 1, + "resultInfo": { + "totalRecords": 1, + "facets": [], + "diagnostics": [] + } + }, + "bodyType": "json", + "status": 200 + }, { "comment": "Version check", "expectedMethod": "GET", @@ -10,6 +93,21 @@ "bodyType": "json", "status": 200 }, + { + "expectedMethod": "GET", + "expectedPath": "\/circulation\/requests\/allowed-service-points?instanceId=instanceid&requesterId=foo&operation=create", + "expectedParams": [], + "body": { + "Page": [ + { + "id": "desk1", + "name": "Main Service Desk" + } + ] + }, + "bodyType": "json", + "status": 200 + }, { "status": 500, "expectedPath": "/circulation/requests", diff --git a/module/VuFind/tests/fixtures/marc/marctraits.xml b/module/VuFind/tests/fixtures/marc/marctraits.xml index 8af2c699fdc..046a8180e7f 100644 --- a/module/VuFind/tests/fixtures/marc/marctraits.xml +++ b/module/VuFind/tests/fixtures/marc/marctraits.xml @@ -135,12 +135,28 @@ <subfield code="a">Summary.</subfield> <subfield code="b">Expanded.</subfield> </datafield> + <datafield tag="520" ind1="1" ind2=" "> + <subfield code="a">Review Note.</subfield> + <subfield code="b">Expanded.</subfield> + <subfield code="1">http://expanded.</subfield> + </datafield> + <datafield tag="520" ind1="3" ind2=" "> + <subfield code="a">Abstract.</subfield> + <subfield code="b">Expanded.</subfield> + </datafield> + <datafield tag="520" ind1="4" ind2=" "> + <subfield code="a">Content Advice.</subfield> + <subfield code="b">Expanded.</subfield> + </datafield> <datafield tag="521" ind1=" " ind2=" "> <subfield code="a">Developers</subfield> </datafield> <datafield tag="538" ind1=" " ind2=" "> <subfield code="a">Data in UTF-8</subfield> </datafield> + <datafield tag="544" ind1=" " ind2=" "> + <subfield code="a">Location of archival materials</subfield> + </datafield> <datafield tag="555" ind1="0" ind2=" "> <subfield code="a">Finding aid available</subfield> </datafield> diff --git a/module/VuFind/tests/fixtures/worldcat2/award.json b/module/VuFind/tests/fixtures/worldcat2/award.json new file mode 100644 index 00000000000..4255d0fe2f4 --- /dev/null +++ b/module/VuFind/tests/fixtures/worldcat2/award.json @@ -0,0 +1 @@ +{"note":{"awardNote":"Fake example award"}} \ No newline at end of file diff --git a/module/VuFind/tests/fixtures/worldcat2/dmg.json b/module/VuFind/tests/fixtures/worldcat2/dmg.json new file mode 100644 index 00000000000..00cdb66ae21 --- /dev/null +++ b/module/VuFind/tests/fixtures/worldcat2/dmg.json @@ -0,0 +1 @@ +{"identifier":{"oclcNumber":"884396716","lccn":"2015458232","isbns":["9780786965625","0786965622"],"externalIdentifiers":[{"oclcSymbol":"AU@","systemControlNumber":"000054532080"},{"oclcSymbol":"NZ1","systemControlNumber":"15869533"}],"mergedOclcNumbers":["881017038","1018399980","1322197532"]},"title":{"mainTitles":[{"text":"Dungeon master's guide"}],"seriesTitles":[{"seriesTitle":"Dungeons & Dragons"},{"seriesTitle":"Dungeons & Dragons"}]},"contributor":{"creators":[{"firstName":{"text":"Mike"},"secondName":{"text":"Mearls"},"isPrimary":false,"type":"person","creatorNotes":["lead designer."],"relators":[{"term":"Lead designer","alternateTerm":"lead designer."}]},{"firstName":{"text":"Jeremy"},"secondName":{"text":"Crawford"},"isPrimary":false,"type":"person","creatorNotes":["lead designer, handbook lead."],"relators":[{"term":"Lead designer","alternateTerm":"lead designer,"},{"term":"Handbook lead","alternateTerm":"handbook lead."}]},{"firstName":{"text":"Christopher"},"secondName":{"text":"Perkins"},"isPrimary":false,"type":"person","creatorNotes":["1968- handbook lead."],"relators":[{"term":"Handbook lead","alternateTerm":"handbook lead."}]},{"firstName":{"text":"James"},"secondName":{"text":"Wyatt"},"isPrimary":false,"type":"person","creatorNotes":["1968- handbook lead."],"relators":[{"term":"Handbook lead","alternateTerm":"handbook lead."}]},{"firstName":{"text":"Robert J."},"secondName":{"text":"Schwalb"},"isPrimary":false,"type":"person","creatorNotes":["(Illustrator), designer."],"relators":[{"term":"Designer","alternateTerm":"dsr"}]},{"firstName":{"text":"Rodney"},"secondName":{"text":"Thompson"},"isPrimary":false,"type":"person","creatorNotes":["designer."],"relators":[{"term":"Designer","alternateTerm":"dsr"}]},{"firstName":{"text":"Peter"},"secondName":{"text":"Lee"},"isPrimary":false,"type":"person","creatorNotes":["(Game designer), designer."],"relators":[{"term":"Designer","alternateTerm":"dsr"}]},{"firstName":{"text":"Scott Fitzgerald"},"secondName":{"text":"Gray"},"isPrimary":false,"type":"person","creatorNotes":["editor."],"relators":[{"term":"Editor","alternateTerm":"edt"}]},{"firstName":{"text":"Michele"},"secondName":{"text":"Carter"},"isPrimary":false,"type":"person","creatorNotes":["editor."],"relators":[{"term":"Editor","alternateTerm":"edt"}]},{"firstName":{"text":"Chris"},"secondName":{"text":"Sims"},"isPrimary":false,"type":"person","creatorNotes":["1971- editor."],"relators":[{"term":"Editor","alternateTerm":"edt"}]},{"firstName":{"text":"Jennifer Clarke"},"secondName":{"text":"Wilkes"},"isPrimary":false,"type":"person","creatorNotes":["editor."],"relators":[{"term":"Editor","alternateTerm":"edt"}]},{"firstName":{"text":"Greg"},"secondName":{"text":"Bilsland"},"isPrimary":false,"type":"person","creatorNotes":["producer."],"relators":[{"term":"Producer","alternateTerm":"pro"}]},{"nonPersonName":{"text":"Wizards of the Coast, Inc"},"isPrimary":false,"type":"corporation","creatorNotes":["publisher."],"relators":[{"term":"Publisher","alternateTerm":"pbl"}]}]},"subjects":[{"subjectName":{"text":"Dungeons and Dragons (Game) Handbooks, manuals, etc"},"vocabulary":"Library of Congress Subject Headings","subjectType":"topic"},{"subjectName":{"text":"Dungeons and Dragons (Game) Rules"},"vocabulary":"Library of Congress Subject Headings","subjectType":"topic"},{"subjectName":{"text":"Fantasy games Handbooks, manuals, etc"},"vocabulary":"Library of Congress Subject Headings","subjectType":"topic"},{"subjectName":{"text":"Donjons et dragons Guides, manuels, etc"},"vocabulary":"Répertoire de vedettes-matière","subjectType":"topic"},{"subjectName":{"text":"Donjons et dragons Règles"},"vocabulary":"Répertoire de vedettes-matière","subjectType":"topic"},{"subjectName":{"text":"GAMES & ACTIVITIES / Role Playing & Fantasy"},"vocabulary":"bisacsh","subjectType":"topic"},{"subjectName":{"text":"GAMES & ACTIVITIES / Reference"},"vocabulary":"bisacsh","subjectType":"topic"},{"subjectName":{"text":"REFERENCE / Personal & Practical Guides"},"vocabulary":"bisacsh","subjectType":"topic"},{"subjectName":{"text":"Dungeons and Dragons (Game)"},"vocabulary":"fast","subjectType":"topic"},{"subjectName":{"text":"Fantasy games"},"vocabulary":"fast","subjectType":"topic"},{"subjectName":{"text":"Rollspel"},"vocabulary":"sao","subjectType":"topic"},{"subjectName":{"text":"Handböcker, manualer, etc"},"vocabulary":"saogf","subjectType":"topic"},{"subjectName":{"text":"Regler"},"vocabulary":"saogf","subjectType":"topic"},{"subjectName":{"text":"Handbook"},"vocabulary":"Medical Subject Headings","subjectType":"genreFormTerm"},{"subjectName":{"text":"handbooks"},"vocabulary":"aat","subjectType":"genreFormTerm"},{"subjectName":{"text":"Handbooks and manuals"},"vocabulary":"fast","subjectType":"genreFormTerm"},{"subjectName":{"text":"Rules"},"vocabulary":"fast","subjectType":"genreFormTerm"},{"subjectName":{"text":"Handbooks and manuals"},"vocabulary":"lcgft","subjectType":"genreFormTerm"},{"subjectName":{"text":"Guides et manuels"},"vocabulary":"rvmgf","subjectType":"genreFormTerm"}],"classification":{"dewey":"793.93","lc":"GV1469.62.D84 D827 2014"},"publishers":[{"publisherName":{"text":"Wizards of the Coast"},"publicationPlace":"Renton, WA"}],"date":{"publicationDate":"2014","machineReadableDate":"2014","createDate":"20150822","replaceDate":"20240531"},"language":{"itemLanguage":"eng","catalogingLanguage":"eng"},"edition":{"statement":"Fifth edition"},"note":{"generalNotes":[{"text":"\"Everything a Dungeon Master needs to weave legendary stories for the world's greatest roleplaying game\"--Cover","local":"N"}],"creditNotes":"D&D lead designers, Mike Mearls, Jeremy Crawford ; Dungeon master's guide leads, Jeremy Crawford, Christopher Perkins, James Wyatt ; designers, Robert J. Schwalb, Rodney Thompson, Peter Lee ; editors, Scott Fitzgerald Gray, Michele Carter, Chris Sims, Jennifer Clarke Wilkes ; producer, Greg Bilsland."},"format":{"generalFormat":"Book","specificFormat":"PrintBook"},"digitalAccessAndLocations":[{"uri":"http://catdir.loc.gov/catdir/enhancements/fy1601/2015458232-d.html","materialSpecified":"Publisher description"}],"description":{"physicalDescription":"320 pages : color illustrations, color maps ; 29 cm.","genres":["Handbook","Role playing games","handbooks","Handbooks and manuals","Rules","Guides et manuels","Handbooks, manuals, etc","Guides, manuels, etc","Règles"],"summaries":[{"text":"This book contains tools a Dungeon Master needs to provide stories and game play. A resource for new and existing Dungeon Masters to engage in both adventure and world creation, with rules, guidelines, and advice from the game's experts. Created as part of a massive public playtest involving more than 170,000 fans of the game"}],"contents":[{"contentNote":{"text":"A world of your own -- Creating a multiverse -- Creating adventures -- Creating nonplayer characters -- Adventure environments -- Between adventures -- Treasure -- Running the game -- Dungeon master's workshop -- Appendix A: Random dungeons -- Appendix B: Monster lists -- Appendix C: Maps -- Appendix D: Dungeon Master inspiration."}}],"bibliographies":[{"text":"Includes bibliographical references (page 316) and index"}],"peerReviewed":"N"},"work":{"id":"2908525242","count":16},"editionCluster":{"id":"3996c0b46a151d9fd50ccbe400a028bc","count":5},"database":{"source":"xwc","collection":"xwc"}} \ No newline at end of file diff --git a/module/VuFind/tests/fixtures/worldcat2/issn.json b/module/VuFind/tests/fixtures/worldcat2/issn.json new file mode 100644 index 00000000000..13389196480 --- /dev/null +++ b/module/VuFind/tests/fixtures/worldcat2/issn.json @@ -0,0 +1 @@ +{"identifier":{"issns":["0362-4331"]}} \ No newline at end of file diff --git a/module/VuFind/tests/fixtures/worldcat2/pride.json b/module/VuFind/tests/fixtures/worldcat2/pride.json new file mode 100644 index 00000000000..dc1945e25e3 --- /dev/null +++ b/module/VuFind/tests/fixtures/worldcat2/pride.json @@ -0,0 +1 @@ +{"identifier":{"oclcNumber":"49569228","isbns":["9780191592539","0191592536","0585377618","9780585377612"],"externalIdentifiers":[{"oclcSymbol":"DEBBG","systemControlNumber":"BV043078578"},{"oclcSymbol":"DEBSZ","systemControlNumber":"42248301X"},{"oclcSymbol":"GBVCP","systemControlNumber":"800832604"}],"mergedOclcNumbers":["530699569","702096151","1036823755","1332980563"]},"title":{"mainTitles":[{"text":"Pride and prejudice / Jane Austen ; edited by James Kinsley ; with a new introduction by Isobel Armstrong ; notes by Frank W. Bradbrook"}],"seriesTitles":[{"seriesTitle":"Oxford world's classics (Oxford University Press)"},{"seriesTitle":"World's classics"}]},"contributor":{"creators":[{"firstName":{"text":"Jane"},"secondName":{"text":"Austen"},"isPrimary":true,"type":"person","creatorNotes":["1775-1817."]},{"firstName":{"text":"James"},"secondName":{"text":"Kinsley"},"isPrimary":false,"type":"person"}],"statementOfResponsibility":{"text":"Jane Austen ; edited by James Kinsley ; with a new introduction by Isobel Armstrong ; notes by Frank W. Bradbrook."}},"subjects":[{"subjectName":{"text":"Young women Fiction"},"vocabulary":"Library of Congress Subject Headings","subjectType":"topic"},{"subjectName":{"text":"Courtship Fiction"},"vocabulary":"Library of Congress Subject Headings","subjectType":"topic"},{"subjectName":{"text":"Sisters Fiction"},"vocabulary":"Library of Congress Subject Headings","subjectType":"topic"},{"subjectName":{"text":"Jeunes femmes Romans, nouvelles, etc"},"vocabulary":"Répertoire de vedettes-matière","subjectType":"topic"},{"subjectName":{"text":"Amours Romans, nouvelles, etc"},"vocabulary":"Répertoire de vedettes-matière","subjectType":"topic"},{"subjectName":{"text":"Sœurs Romans, nouvelles, etc"},"vocabulary":"Répertoire de vedettes-matière","subjectType":"topic"},{"subjectName":{"text":"FICTION Romance General"},"vocabulary":"bisacsh","subjectType":"topic"},{"subjectName":{"text":"Courtship"},"vocabulary":"fast","subjectType":"topic"},{"subjectName":{"text":"Sisters"},"vocabulary":"fast","subjectType":"topic"},{"subjectName":{"text":"Young women"},"vocabulary":"fast","subjectType":"topic"},{"subjectName":{"text":"England Fiction"},"vocabulary":"Library of Congress Subject Headings","subjectType":"geographicalTerm"},{"subjectName":{"text":"Angleterre Romans, nouvelles, etc"},"vocabulary":"Répertoire de vedettes-matière","subjectType":"geographicalTerm"},{"subjectName":{"text":"England"},"vocabulary":"fast","subjectType":"geographicalTerm","uri":"https://id.oclc.org/worldcat/entity/E39PBJpYDdYvBpjXV6WpybK68C"},{"subjectName":{"text":"Domestic fiction"},"vocabulary":"Library of Congress Subject Headings","subjectType":"genreFormTerm"},{"subjectName":{"text":"Domestic fiction"},"vocabulary":"fast","subjectType":"genreFormTerm"},{"subjectName":{"text":"Fiction"},"vocabulary":"fast","subjectType":"genreFormTerm"},{"subjectName":{"text":"Love stories"},"vocabulary":"gsafd","subjectType":"genreFormTerm"},{"subjectName":{"text":"Domestic fiction"},"vocabulary":"lcgft","subjectType":"genreFormTerm"}],"classification":{"dewey":"823/.7","lc":"PR4034 .P7 1990eb"},"publishers":[{"publisherName":{"text":"Oxford University Press"},"publicationPlace":"Oxford"}],"date":{"publicationDate":"1990","machineReadableDate":"1990","createDate":"20010911","replaceDate":"20240804"},"language":{"itemLanguage":"eng","catalogingLanguage":"eng"},"note":{"generalNotes":[{"text":"Reprint. Originally published: Oxford University Press, 1970","local":"N"}]},"format":{"generalFormat":"Book","specificFormat":"Digital","materialTypes":["dct","fic","url"]},"digitalAccessAndLocations":[{"uri":"https://search.ebscohost.com/login.aspx?direct=true&scope=site&db=nlebk&db=nlabk&AN=55923","materialSpecified":"EBSCOhost"},{"uri":"http://www.netlibrary.com/UrlApi.aspx?action=browse&v=1&bookid=1085113","materialSpecified":"EBSCOhost"},{"uri":"https://archive.org/details/prideprejudice100aust","materialSpecified":"Internet Archive"},{"uri":"https://openlibrary.org/books/OL1875263M","materialSpecified":"Open Library"}],"description":{"physicalDescription":"1 online resource (xxxii, 303 pages)","genres":["Domestic fiction","Fiction","Love stories","Romans, nouvelles, etc"],"peerReviewed":"N"},"related":{"additionalPhysicalFormEntries":[{"displayConstant":"Print version:","titles":["Pride and prejudice."],"isbns":["0192833553"],"mainEntryHeadings":["Austen, Jane, 1775-1817."]}]},"work":{"id":"1881837462","count":6253},"editionCluster":{"id":"267e11753755b57b1381950a700d4947","count":3},"database":{"source":"xwc","collection":"xwc"}} \ No newline at end of file diff --git a/module/VuFind/tests/fixtures/worldcat2/sherlock.json b/module/VuFind/tests/fixtures/worldcat2/sherlock.json new file mode 100644 index 00000000000..e7c28dd1603 --- /dev/null +++ b/module/VuFind/tests/fixtures/worldcat2/sherlock.json @@ -0,0 +1 @@ +{"identifier":{"oclcNumber":"823161342","isbns":["9780191505744","0191505749"],"externalIdentifiers":[{"oclcSymbol":"AU@","systemControlNumber":"000051598652"},{"oclcSymbol":"DEBBG","systemControlNumber":"BV043078479"},{"oclcSymbol":"DEBSZ","systemControlNumber":"421293322"},{"oclcSymbol":"GBVCP","systemControlNumber":"804204616"}],"mergedOclcNumbers":["823719746","894778442","978430800","978867723","1035789555","1036867639","1047952250","1055398513","1064550341","1081286312","1101722123"]},"title":{"mainTitles":[{"text":"The sign of the four / Arthur Conan Doyle ; edited with an introduction by Christopher Roden"}],"seriesTitles":[{"seriesTitle":"World's classics"},{"seriesTitle":"The World's classics"},{"seriesTitle":"The World's classics"},{"seriesTitle":"The Oxford Sherlock Holmes"}]},"contributor":{"creators":[{"firstName":{"text":"Arthur Conan"},"secondName":{"text":"Doyle"},"isPrimary":true,"type":"person","creatorNotes":["1859-1930."]},{"firstName":{"text":"Christopher"},"secondName":{"text":"Roden"},"isPrimary":false,"type":"person"}],"statementOfResponsibility":{"text":"Arthur Conan Doyle ; edited with an introduction by Christopher Roden."}},"subjects":[{"subjectName":{"text":"Holmes, Sherlock Fiction"},"vocabulary":"Library of Congress Subject Headings","subjectType":"personalName"},{"subjectName":{"text":"Holmes, Sherlock"},"vocabulary":"fast","subjectType":"personalName"},{"subjectName":{"text":"Private investigators England Fiction"},"vocabulary":"Library of Congress Subject Headings","subjectType":"topic"},{"subjectName":{"text":"Détectives Angleterre Romans, nouvelles, etc"},"vocabulary":"Répertoire de vedettes-matière","subjectType":"topic"},{"subjectName":{"text":"FICTION General"},"vocabulary":"bisacsh","subjectType":"topic"},{"subjectName":{"text":"Private investigators"},"vocabulary":"fast","subjectType":"topic"},{"subjectName":{"text":"England"},"vocabulary":"fast","subjectType":"geographicalTerm","uri":"https://id.oclc.org/worldcat/entity/E39PBJpYDdYvBpjXV6WpybK68C"},{"subjectName":{"text":"Detective and mystery fiction"},"vocabulary":"fast","subjectType":"genreFormTerm"},{"subjectName":{"text":"Fiction"},"vocabulary":"fast","subjectType":"genreFormTerm"},{"subjectName":{"text":"Mystery fiction"},"vocabulary":"gsafd","subjectType":"genreFormTerm"},{"subjectName":{"text":"Detective and mystery fiction"},"vocabulary":"lcgft","subjectType":"genreFormTerm"},{"subjectName":{"text":"English fiction"},"subjectType":"uncontrolledTerm"}],"classification":{"dewey":"823/.8","lc":"PR4622 .S44 1994eb"},"publishers":[{"publisherName":{"text":"Oxford University Press"},"publicationPlace":"Oxford [England]"}],"date":{"publicationDate":"1994","machineReadableDate":"1994","createDate":"20130102","replaceDate":"20240202"},"language":{"itemLanguage":"eng","catalogingLanguage":"eng"},"note":{"reproductionNotes":[{"reproductionType":"Electronic reproduction","reproductionPlace":["[Place of publication not identified]"],"agency":["HathiTrust Digital Library"],"date":"2011","institution":"MiAaHDL"}],"systemDetailNote":"Master and use copy. Digital master created according to Benchmark for Faithful Digital Reproductions of Monographs and Serials, Version 1. Digital Library Federation, December 2002. http://purl.oclc.org/DLF/benchrepro0212"},"format":{"generalFormat":"Book","specificFormat":"Digital","materialTypes":["dct","fic","url"]},"digitalAccessAndLocations":[{"uri":"https://search.ebscohost.com/login.aspx?direct=true&scope=site&db=nlebk&db=nlabk&AN=516787","materialSpecified":"EBSCOhost"},{"uri":"http://catalog.hathitrust.org/api/volumes/oclc/27726864.html","materialSpecified":"HathiTrust Digital Library, Limited view (search only)"},{"uri":"https://archive.org/details/signoffour00doyl","materialSpecified":"Internet Archive"},{"uri":"http://www.myilibrary.com?id=423620","materialSpecified":"MyiLibrary"},{"uri":"http://www.myilibrary.com?id=885888","materialSpecified":"MyiLibrary"},{"uri":"http://www.myilibrary.com?id=918808","materialSpecified":"MyiLibrary"},{"uri":"https://openlibrary.org/books/OL1082236M","materialSpecified":"Open Library"},{"uri":"https://public.ebookcentral.proquest.com/choice/publicfullrecord.aspx?p=1107724","materialSpecified":"ProQuest Ebook Central"},{"uri":"http://VH7QX3XE2P.search.serialssolutions.com/?V=1.0&L=VH7QX3XE2P&S=JCs&C=TC0000804177&T=marc&tab=BOOKS"},{"uri":"http://catdir.loc.gov/catdir/enhancements/fy0635/94005819-d.html","materialSpecified":"Publisher description"}],"description":{"physicalDescription":"1 online resource (lv, 137 pages)","genres":["Detective and mystery fiction","Fiction","Mystery fiction","Romans, nouvelles, etc"],"summaries":[{"text":"Miss Mary Marstan receives through the post once a year a large pearl without any clue as to the sender. When her mysterious correspondent requests a meeting, Holmes and Watson start out on a case. A terrible death and vanishing treasure lead to an epic pursuit through the dawn streets and later along the River Thames. The cast of characters include the unfortunate Sholto twins, the mongrel Toby, and the wooden-legged man, as the fire and blood of Mutiny-torn India throwgigantic, distorted silhouettes across late Victorian London. - ;Miss Mary Marstan receives through the post once a year a la"}],"contents":[{"contentNote":{"text":"Cover; THE SIGN OF THE FOUR; CHAPTER I: THE SCIENCE OF DEDUCTION; CHAPTER II: THE STATEMENT OF THE CASE; CHAPTER III: IN QUEST OF A SOLUTION; CHAPTER IV: THE STORY OF THE BALD-HEADED MAN; CHAPTER V: THE TRAGEDY OF PONDICHERRY LODGE; CHAPTER VI: SHERLOCK HOLMES GIVES A DEMONSTRATION; CHAPTER VII: THE EPISODE OF THE BARREL; CHAPTER VIII: THE BAKER STREET IRREGULARS; CHAPTER IX: A BREAK IN THE CHAIN; CHAPTER X: THE END OF THE ISLANDER; CHAPTER XI: THE GREAT AGRA TREASURE; CHAPTER XII: THE STRANGE STORY OF JONATHAN SMALL."}}],"bibliographies":[{"text":"Includes bibliographical references (pages xliii-lv)"}],"peerReviewed":"N"},"related":{"additionalPhysicalFormEntries":[{"displayConstant":"Print version:","titles":["Sign of the four."],"recordControlOclcNumbers":["29911356"],"isbns":["0192823795"],"mainEntryHeadings":["Doyle, Arthur Conan, Sir, 1859-1930."]}]},"work":{"id":"4927808835","count":478},"editionCluster":{"id":"b3bf6d2c520d757429ecc461e979a6e3","count":3},"database":{"source":"xwc","collection":"xwc"}} \ No newline at end of file diff --git a/module/VuFind/tests/fixtures/worldcat2/star.json b/module/VuFind/tests/fixtures/worldcat2/star.json new file mode 100644 index 00000000000..5156b195156 --- /dev/null +++ b/module/VuFind/tests/fixtures/worldcat2/star.json @@ -0,0 +1 @@ +{"identifier":{"oclcNumber":"31438318"},"title":{"mainTitles":[{"text":"The New York Saturday star journal"}]},"contributor":{"creators":[{"nonPersonName":{"text":"Beadle and Adams (1872-1898)"},"isPrimary":false,"type":"corporation"},{"nonPersonName":{"text":"Johannsen Collection"},"isPrimary":false,"type":"corporation"}]},"subjects":[{"subjectName":{"text":"Popular literature United States Periodicals"},"vocabulary":"Library of Congress Subject Headings","subjectType":"topic"},{"subjectName":{"text":"American fiction 19th century Periodicals"},"vocabulary":"Library of Congress Subject Headings","subjectType":"topic"},{"subjectName":{"text":"Paralittérature États-Unis Périodiques"},"vocabulary":"Répertoire de vedettes-matière","subjectType":"topic"},{"subjectName":{"text":"Roman américain 19e siècle Périodiques"},"vocabulary":"Répertoire de vedettes-matière","subjectType":"topic"},{"subjectName":{"text":"American fiction"},"vocabulary":"fast","subjectType":"topic"},{"subjectName":{"text":"Popular literature"},"vocabulary":"fast","subjectType":"topic"},{"subjectName":{"text":"United States"},"vocabulary":"fast","subjectType":"geographicalTerm","uri":"https://id.oclc.org/worldcat/entity/E39PBJtxgQXMWqmjMjjwXRHgrq"},{"subjectName":{"text":"periodicals"},"vocabulary":"aat","subjectType":"genreFormTerm"},{"subjectName":{"text":"Periodicals"},"vocabulary":"fast","subjectType":"genreFormTerm"},{"subjectName":{"text":"Periodicals"},"vocabulary":"lcgft","subjectType":"genreFormTerm"},{"subjectName":{"text":"Périodiques"},"vocabulary":"rvmgf","subjectType":"genreFormTerm"},{"subjectName":{"text":"1800-1899"},"vocabulary":"fast","subjectType":"chronologyTerm"}],"classification":{"lc":"PS535 .S373a"},"publishers":[{"publisherName":{"text":"E.F. Beadle, William Adams, David Adams"},"publicationPlace":"New York"}],"date":{"publicationDate":"1872-1873","machineReadableDate":"1872-1873","publicationSequentialDesignationDate":"Vol. 2, no. 97 (Jan. 20, 1872)-v. 3, no. 156 (Mar. 8, 1873).","currentPublicationFrequency":"Weekly","createDate":"19941110","replaceDate":"20231213"},"language":{"itemLanguage":"eng","catalogingLanguage":"eng"},"note":{"generalNotes":[{"text":"\"A popular paper for pleasure & profit.\"","local":"N"},{"text":"Title from caption","local":"N"}]},"format":{"generalFormat":"Jrnl","materialTypes":["per"]},"description":{"physicalDescription":"volumes : illustrations ; 54 cm","genres":["periodicals","Periodicals","Périodiques"],"peerReviewed":"N"},"related":{"precedingEntries":[{"typeOfRelationship":"Continues","relatedItemTitle":"Saturday weekly journal","relatedItemNotes":["Saturday weekly journal"]}],"succeedingEntries":[{"typeOfRelationship":"Continued by","relatedItemTitle":"New York Saturday journal","relatedItemNotes":["New York Saturday journal"]}]},"work":{"id":"3863831066","count":2},"editionCluster":{"id":"90f6d7fb8dbccaff07885126f1fc3599","count":1},"database":{"source":"xwc","collection":"xwc"}} \ No newline at end of file diff --git a/module/VuFind/tests/fixtures/worldcat2/title-subtitle.json b/module/VuFind/tests/fixtures/worldcat2/title-subtitle.json new file mode 100644 index 00000000000..515b8556321 --- /dev/null +++ b/module/VuFind/tests/fixtures/worldcat2/title-subtitle.json @@ -0,0 +1 @@ +{"identifier":{"oclcNumber":"foo"},"title":{"mainTitles":[{"text":"title : the subtitle"}]}} \ No newline at end of file diff --git a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/AccountActionsTest.php b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/AccountActionsTest.php index 75ee44791b1..b0b71ebd103 100644 --- a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/AccountActionsTest.php +++ b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/AccountActionsTest.php @@ -474,6 +474,7 @@ public function testRecoverPasswordByUsername(): void 'Mail' => [ 'testOnly' => true, 'message_log' => $this->getEmailLogPath(), + 'message_log_format' => $this->getEmailLogFormat(), ], ], ] @@ -498,8 +499,8 @@ public function testRecoverPasswordByUsername(): void ); // Extract URL from email: - $email = file_get_contents($this->getEmailLogPath()); - preg_match('/You can reset your password at this URL: (http.*)/', $email, $matches); + $email = $this->getLoggedEmail(); + preg_match('/You can reset your password at this URL: (http.*)/', $email->getBody()->getBody(), $matches); $link = $matches[1]; // Reset the password: diff --git a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/BlendedSearchTest.php b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/BlendedSearchTest.php index 51357a7d104..6a06c9eb8f6 100644 --- a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/BlendedSearchTest.php +++ b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/BlendedSearchTest.php @@ -94,7 +94,19 @@ protected function getBlenderIniOverrides() */ public function testDisabledSearch() { + // Disable logging of a known exception: + $this->changeConfigs( + [ + 'config' => [ + 'Logging' => [ + 'file' => null, + ], + ], + ] + ); $session = $this->getMinkSession(); + // We expect an error, so let's act like production mode for realistic testing: + $session->setWhoopsDisabled(true); $session->visit($this->getVuFindUrl() . '/Blender/Results'); $page = $session->getPage(); $this->assertEquals( diff --git a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/CartTest.php b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/CartTest.php index 601fedf0b88..6f4082ca483 100644 --- a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/CartTest.php +++ b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/CartTest.php @@ -766,7 +766,7 @@ public function testCartPrint() * Assert visibility * * @param array $combo Current Site configuration - * @param bool[] $elements Array of element visibilty states indexed by name + * @param bool[] $elements Array of element visibility states indexed by name * @param string $name Name of element to check * @param string $exp Expected visibility * diff --git a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/ChoiceAuthTest.php b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/ChoiceAuthTest.php index ccba4bc5b0e..c4988cad62d 100644 --- a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/ChoiceAuthTest.php +++ b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/ChoiceAuthTest.php @@ -104,6 +104,7 @@ protected function getConfigIniEmailOverrides(): array 'Mail' => [ 'testOnly' => true, 'message_log' => $this->getEmailLogPath(), + 'message_log_format' => $this->getEmailLogFormat(), ], ]; } @@ -286,8 +287,8 @@ public function testEmailAuthentication(): void ); // Extract the link from the provided message: - $email = file_get_contents($this->getEmailLogPath()); - preg_match('/Link to login: <(http.*)>/', $email, $matches); + $email = $this->getLoggedEmail(); + preg_match('/Link to login: <(http.*)>/', $email->getBody()->getBody(), $matches); $session->visit($matches[1]); // Log out diff --git a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/CombinedSearchTest.php b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/CombinedSearchTest.php index 6d527e88791..ed9f6337641 100644 --- a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/CombinedSearchTest.php +++ b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/CombinedSearchTest.php @@ -146,6 +146,47 @@ public static function ajaxCombinationsProvider(): array ]; } + /** + * Test that combined results contain valid author links with appropriate filtering. + * + * @param bool $leftAjax Should left column load via AJAX? + * @param bool $rightAjax Should right column load via AJAX? + * + * @return void + * + * @dataProvider ajaxCombinationsProvider + */ + public function testCombinedSearchResultsAuthorLinks(bool $leftAjax, bool $rightAjax): void + { + $config = $this->getCombinedIniOverrides(); + // Default configuration does not have authors in both columns; switch to a + // different data set that will let us test authors: + $config['Solr:one']['hiddenFilter'] = 'building:author_relators.mrc'; + $config['Solr:one']['ajax'] = $leftAjax; + $config['Solr:two']['ajax'] = $rightAjax; + $this->changeConfigs( + ['combined' => $config], + ['combined'] + ); + $session = $this->getMinkSession(); + $session->visit($this->getVuFindUrl() . '/Combined'); + $page = $session->getPage(); + $this->findCss($page, '#searchForm_lookfor') + ->setValue('id:"0001732009-1" OR id:"theplus+andtheminus-"'); + $this->clickCss($page, '.btn.btn-primary'); + $this->waitForPageLoad($page); + $this->unFindCss($page, '.fa-spinner.icon--spin'); + // The author link in each column should have an appropriate hidden filter applied: + $this->assertStringContainsString( + 'hiddenFilters%5B%5D=building%3A%22author_relators.mrc%22', + $this->findCss($page, '#combined_Solr____one .result-author')->getAttribute('href') + ); + $this->assertStringContainsString( + 'hiddenFilters%5B%5D=building%3A%22weird_ids.mrc%22', + $this->findCss($page, '#combined_Solr____two .result-author')->getAttribute('href') + ); + } + /** * Test that combined results work in AJAX mode. * diff --git a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/ContainerLinksTest.php b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/ContainerLinksTest.php index 36bede702cf..5b54d62595e 100644 --- a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/ContainerLinksTest.php +++ b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/ContainerLinksTest.php @@ -66,10 +66,14 @@ protected function goToCollection() public function testDefaultContainerLinks(): void { $page = $this->performSearch('id:jnl1-1'); + $url = $this->findCss($page, '.result-body a.container-link')->getAttribute('href'); $this->assertMatchesRegularExpression( - '{.*/Search/Results\?lookfor=%22Arithmetic\+Facts%22&type=JournalTitle}', - $this->findCss($page, '.result-body a.container-link')->getAttribute('href') + '{.*/Search/Results}', + parse_url($url, PHP_URL_PATH) ); + parse_str(parse_url($url, PHP_URL_QUERY), $query); + $this->assertEquals('JournalTitle', $query['type']); + $this->assertEquals('"Arithmetic Facts"', $query['lookfor']); } /** diff --git a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/DirLocationsTest.php b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/DirLocationsTest.php new file mode 100644 index 00000000000..e77e120649a --- /dev/null +++ b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/DirLocationsTest.php @@ -0,0 +1,243 @@ +<?php + +/** + * Mink test class for inheritance on local configuration dir inheritance. + * + * PHP version 8 + * + * Copyright (C) Hebis Verbundzentrale 2024. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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 + * + * @category VuFind + * @package Tests + * @author Thomas Wagener <wagener@hebis.uni-frankfurt.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org Main Page + */ + +namespace VuFindTest\Mink; + +use Behat\Mink\Element\DocumentElement; + +/** + * Mink test class for inheritance on local configuration dir inheritance. + * + * @category VuFind + * @package Tests + * @author Thomas Wagener <wagener@hebis.uni-frankfurt.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org Main Page + */ +class DirLocationsTest extends \VuFindTest\Integration\MinkTestCase +{ + use \VuFindTest\Feature\FixtureTrait; + + /** + * Test that the ini configs of the local dir stack are processed. + * + * @return void + */ + public function testIniConfigs(): void + { + $session = $this->getMinkSession(); + $session->visit($this->getVuFindUrl()); + $page = $session->getPage(); + $this->findCss($page, '#searchForm_type option[value="ParentTest"]'); + $this->changeConfigs( + ['searches' => ['Basic_Searches' => ['ChildTest' => 'ChildTest']]] + ); + $session->visit($this->getVuFindUrl()); + $page = $session->getPage(); + $this->unFindCss($page, '#searchForm_type option[value="ParentTest"]'); + $this->findCss($page, '#searchForm_type option[value="ChildTest"]'); + } + + /** + * Test that the yaml configs of the local dir stack are processed. + * + * @return void + */ + public function testYamlConfigs(): void + { + $session = $this->getMinkSession(); + $session->visit($this->getVuFindUrl() . '/Search/History'); + $page = $session->getPage(); + $this->findCss($page, '.account-menu .parent-test'); + $this->changeYamlConfigs( + [ + 'AccountMenu' => [ + 'MenuItems' => [ + [ + 'name' => 'child-test', + 'label' => 'child test', + 'route' => 'myresearch-favorites', + 'icon' => 'user-favorites', + ], + ], + ], + ] + ); + $session->visit($this->getVuFindUrl() . '/Search/History'); + $page = $session->getPage(); + $this->unFindCss($page, '.account-menu a.parent-test'); + $this->findCss($page, '.account-menu a.child-test'); + } + + /** + * Test that the text domains of the local dir stack and the base languages are processed. + * + * @return void + */ + public function testTranslations(): void + { + $page = $this->setupTranslations([ + 'ChildTranslation' => 'test_string_1', + 'ParentTranslations' => 'test_string_2', + 'BaseTranslations' => 'bulk_email', + ]); + $this->checkTranslation($page, 'Child Text', 'ChildTranslation'); + $this->checkTranslation($page, 'Parent Text', 'ParentTranslations'); + $this->checkTranslation($page, 'Email Selected', 'BaseTranslations'); + } + + /** + * Test that the text domains of the local dir stack and the base languages are processed. + * + * @return void + */ + public function testTextDomains(): void + { + $page = $this->setupTranslations([ + 'ChildTranslation' => 'ChildDomain::test', + 'ParentTranslations' => 'ParentDomain::test', + 'BaseTranslations' => 'HoldingStatus::availability_uncertain', + ]); + $this->checkTranslation($page, 'Child Text', 'ChildTranslation'); + $this->checkTranslation($page, 'Parent Text', 'ParentTranslations'); + $this->checkTranslation($page, 'Uncertain', 'BaseTranslations'); + } + + /** + * Setup custom translation keys using the search type configuration. + * + * @param array $translations Translations + * + * @return DocumentElement + */ + protected function setupTranslations(array $translations): DocumentElement + { + $this->changeConfigs( + [ + 'config' => [ + 'Cache' => [ + 'disabled' => true, + ], + ], + 'searches' => [ + 'Basic_Searches' => $translations, + ], + ] + ); + $session = $this->getMinkSession(); + $session->visit($this->getVuFindUrl()); + return $session->getPage(); + } + + /** + * Checks the translation of the searchForm_type option. + * + * @param DocumentElement $page Page + * @param string $expected Expected translation + * @param string $field Value of option that contains the translation + * + * @return void + */ + protected function checkTranslation(DocumentElement $page, string $expected, string $field): void + { + $this->assertEquals( + $expected, + $this->findCssAndGetText($page, '#searchForm_type option[value="' . $field . '"]') + ); + } + + /** + * Get DirLocations.ini path. + * + * @return string + */ + protected function getDirLocationsIni(): string + { + $localDir = $this->pathResolver->getLocalConfigDirStack()[0]['directory']; + return $localDir . '/DirLocations.ini'; + } + + /** + * Setup DirLocations.ini. + * + * @return void + */ + protected function setupDirLocationsIni(): void + { + $dirLocationsIni = $this->getDirLocationsIni(); + if (file_exists($dirLocationsIni)) { + rename($dirLocationsIni, $dirLocationsIni . '.bak'); + } + file_put_contents($dirLocationsIni, ''); + $this->writeConfigFile($dirLocationsIni, [ + 'Parent_Dir' => [ + 'path' => $this->getFixtureDir() . 'dirlocations', + 'is_relative_path' => false, + ], + ]); + } + + /** + * Standard setup method. + * + * @return void + */ + public function setUp(): void + { + parent::setUp(); + $this->setupDirLocationsIni(); + } + + /** + * Reset DirLocations.ini. + * + * @return void + */ + protected function resetDirLocationsIni(): void + { + $dirLocationsIni = $this->getDirLocationsIni(); + if (file_exists($dirLocationsIni)) { + unlink($dirLocationsIni); + } + if (file_exists($dirLocationsIni . '.bak')) { + rename($dirLocationsIni . '.bak', $dirLocationsIni); + } + } + + /** + * Standard teardown method. + * + * @return void + */ + public function tearDown(): void + { + $this->resetDirLocationsIni(); + parent::tearDown(); + } +} diff --git a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/EmailAuthenticationTest.php b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/EmailAuthenticationTest.php new file mode 100644 index 00000000000..e52351ac9a2 --- /dev/null +++ b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/EmailAuthenticationTest.php @@ -0,0 +1,244 @@ +<?php + +/** + * Email authentication test class. + * + * PHP version 8 + * + * Copyright (C) The National Library of Finland 2024. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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 + * + * @category VuFind + * @package Tests + * @author Ere Maijala <ere.maijala@helsinki.fi> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org Main Page + */ + +namespace VuFindTest\Mink; + +/** + * Email authentication test class. + * + * Class must be final due to use of "new static()" by LiveDatabaseTrait. + * + * @category VuFind + * @package Tests + * @author Ere Maijala <ere.maijala@helsinki.fi> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org Main Page + */ +final class EmailAuthenticationTest extends \VuFindTest\Integration\MinkTestCase +{ + use \VuFindTest\Feature\EmailTrait; + use \VuFindTest\Feature\LiveDatabaseTrait; + use \VuFindTest\Feature\UserCreationTrait; + + /** + * Standard setup method. + * + * @return void + */ + public static function setUpBeforeClass(): void + { + static::failIfDataExists(); + } + + /** + * Test the (non-ILS) email authentication process. + * + * @return void + */ + public function testEmailAuthentication(): void + { + $this->setUpDatabaseEmailConfig(); + + $this->resetEmailLog(); + $session = $this->getMinkSession(); + $session->visit($this->getVuFindUrl()); + $page = $session->getPage(); + + // Create account + $this->clickCss($page, '#loginOptions a'); + $this->clickCss($page, '.modal-body .createAccountLink'); + $this->fillInAccountForm($page); + $this->clickCss($page, '.modal-body .btn.btn-primary'); + + // Log out + $this->clickCss($page, '.logoutOptions a.logout'); + + // Request login: + $this->clickCss($page, '#loginOptions a'); + $this->findCssAndSetValue($page, '.modal-body #login_Email_username', 'username1@ignore.com'); + $this->clickCss($page, '.modal-body .btn.btn-primary', null, 1); + $this->assertEquals( + 'We have sent a login link to your email address. It may take a few moments for the link to arrive.' + . " If you don't receive the link shortly, please check also your spam filter.", + $this->findCssAndGetText($page, '.alert-success') + ); + + // Extract the link from the provided message: + $email = $this->getLoggedEmail(); + $headers = $email->getHeaders(); + $body = $email->getBody()->getBody(); + $this->assertEquals('From: noreply@vufind.org', $headers->get('from')->toString()); + $this->assertEquals('To: username1@ignore.com', $headers->get('to')->toString()); + preg_match('/Link to login: <(http.*)>/', $body, $matches); + $loginLink = $matches[1]; + + // Follow the verification link: + $session->visit($loginLink); + + // Log out (we can't log out unless we successfully logged in): + $this->clickCss($page, '.logoutOptions a.logout'); + + // Clean up the email log: + $this->resetEmailLog(); + } + + /** + * Test the (non-ILS) email authentication process with invalid email address. + * + * @return void + * + * @depends testEmailAuthentication + */ + public function testEmailAuthenticationBadEmail(): void + { + $this->setUpDatabaseEmailConfig(); + + $this->resetEmailLog(); + $session = $this->getMinkSession(); + $session->visit($this->getVuFindUrl()); + $page = $session->getPage(); + + // Request login: + $this->clickCss($page, '#loginOptions a'); + $this->findCssAndSetValue($page, '.modal-body #login_Email_username', 'username1@foo.bar'); + $this->clickCss($page, '.modal-body .btn.btn-primary', null, 1); + $this->assertEquals( + 'We have sent a login link to your email address. It may take a few moments for the link to arrive.' + . " If you don't receive the link shortly, please check also your spam filter.", + $this->findCssAndGetText($page, '.alert-success') + ); + + $this->expectExceptionMessage('No serialized email message data found'); + $this->getLoggedEmail(); + } + + /** + * Test the ILS email authentication process. + * + * @return void + */ + public function testILSEmailAuthentication(): void + { + // Set up configs, session and message logging: + $this->changeConfigs( + [ + 'config' => [ + 'Authentication' => [ + 'method' => 'ILS', + ], + 'Catalog' => [ + 'driver' => 'Demo', + ], + 'Mail' => [ + 'testOnly' => true, + 'message_log' => $this->getEmailLogPath(), + 'message_log_format' => $this->getEmailLogFormat(), + 'default_from' => 'noreply@vufind.org', + ], + ], + 'Demo' => [ + 'Catalog' => [ + 'loginMethod' => 'email', + ], + ], + ] + ); + + $this->resetEmailLog(); + $session = $this->getMinkSession(); + $session->visit($this->getVuFindUrl()); + $page = $session->getPage(); + + // Request login: + $this->clickCss($page, '#loginOptions a'); + $this->findCssAndSetValue($page, '.modal-body [name="username"]', 'catuser@vufind.org'); + $this->clickCss($page, '.modal-body .btn.btn-primary'); + $this->assertEquals( + 'We have sent a login link to your email address. It may take a few moments for the link to arrive.' + . " If you don't receive the link shortly, please check also your spam filter.", + $this->findCssAndGetText($page, '.alert-success') + ); + + // Extract the link from the provided message: + $email = $this->getLoggedEmail(); + $headers = $email->getHeaders(); + $body = $email->getBody()->getBody(); + $this->assertEquals('From: noreply@vufind.org', $headers->get('from')->toString()); + $this->assertEquals('To: catuser@vufind.org', $headers->get('to')->toString()); + preg_match('/Link to login: <(http.*)>/', $body, $matches); + $loginLink = $matches[1]; + + // Follow the verification link: + $session->visit($loginLink); + + // Log out (we can't log out unless we successfully logged in): + $this->clickCss($page, '.logoutOptions a.logout'); + + // Clean up the email log: + $this->resetEmailLog(); + } + + /** + * Standard teardown method. + * + * @return void + */ + public static function tearDownAfterClass(): void + { + static::removeUsers(['username1', 'catuser@vufind.org']); + } + + /** + * Set up configuration for Database+Email authentication + * + * @return void + */ + protected function setUpDatabaseEmailConfig(): void + { + // Set up configs, session and message logging: + $this->changeConfigs( + [ + 'config' => [ + 'Authentication' => [ + 'method' => 'ChoiceAuth', + ], + 'ChoiceAuth' => [ + 'choice_order' => 'Database,Email', + ], + 'Mail' => [ + 'testOnly' => true, + 'message_log' => $this->getEmailLogPath(), + 'message_log_format' => $this->getEmailLogFormat(), + 'default_from' => 'noreply@vufind.org', + ], + ], + ] + ); + } +} diff --git a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/EmailVerificationTest.php b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/EmailVerificationTest.php index b224bfa19cd..74382855c19 100644 --- a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/EmailVerificationTest.php +++ b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/EmailVerificationTest.php @@ -73,6 +73,7 @@ public function testEmailVerification(): void 'Mail' => [ 'testOnly' => true, 'message_log' => $this->getEmailLogPath(), + 'message_log_format' => $this->getEmailLogFormat(), ], ], ] @@ -94,8 +95,12 @@ public function testEmailVerification(): void ); // Extract the link from the provided message: - $email = file_get_contents($this->getEmailLogPath()); - preg_match('/You can verify your email address with this link: <(http.*)>/', $email, $matches); + $email = $this->getLoggedEmail(); + preg_match( + '/You can verify your email address with this link: <(http.*)>/', + $email->getBody()->getBody(), + $matches + ); $verifyLink = $matches[1]; // Follow the verification link: @@ -136,6 +141,7 @@ public function testEmailAddressChange(): void 'Mail' => [ 'testOnly' => true, 'message_log' => $this->getEmailLogPath(), + 'message_log_format' => $this->getEmailLogFormat(), ], ], ] @@ -170,15 +176,21 @@ public function testEmailAddressChange(): void ); // Confirm that messages went to both new and old email addresses, and extract the verify link: - $email = file_get_contents($this->getEmailLogPath()); - $this->assertStringContainsString('To: changed@example.com', $email); - $this->assertStringContainsString('To: username1@ignore.com', $email); + $email = $this->getLoggedEmail(0); + $this->assertEquals('To: changed@example.com', $email->getHeaders()->get('to')->toString()); + preg_match( + '/You can verify your email address with this link: <(http.*)>/', + $email->getBody()->getBody(), + $matches + ); + $verifyLink = $matches[1]; + + $notifyEmail = $this->getLoggedEmail(1); + $this->assertEquals('To: username1@ignore.com', $notifyEmail->getHeaders()->get('to')->toString()); $this->assertStringContainsString( 'A request was just made to change your email address at Library Catalog.', - $email + $notifyEmail->getBody()->getBody() ); - preg_match('/You can verify your email address with this link: <(http.*)>/', $email, $matches); - $verifyLink = $matches[1]; // Follow the verification link: $session->visit($verifyLink); diff --git a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/ErrorHandlingTest.php b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/ErrorHandlingTest.php new file mode 100644 index 00000000000..da2138fe5c5 --- /dev/null +++ b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/ErrorHandlingTest.php @@ -0,0 +1,86 @@ +<?php + +/** + * Mink tests for basic error handling. + * + * PHP version 8 + * + * Copyright (C) The National Library of Finland 2024. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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 + * + * @category VuFind + * @package Tests + * @author Ere Maijala <ere.maijala@helsinki.fi> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org Main Page + */ + +namespace VuFindTest\Mink; + +/** + * Mink tests for basic error handling. + * + * @category VuFind + * @package Tests + * @author Ere Maijala <ere.maijala@helsinki.fi> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org Main Page + */ +class ErrorHandlingTest extends \VuFindTest\Integration\MinkTestCase +{ + /** + * Test error message when site email is displayed. + * + * @return void + */ + public function testErrorWithEmail(): void + { + $session = $this->getMinkSession(); + $session->visit($this->getVuFindUrl('/Record/does_not_exist')); + $page = $session->getPage(); + $this->assertEquals( + 'An error has occurred An error occurred during execution; please try again later. Please contact the' + . ' Library Reference Department for assistance support@myuniversity.edu', + $this->findCssAndGetText($page, '.alert-danger') + ); + } + + /** + * Test error message when site email is hidden. + * + * @return void + */ + public function testErrorWithoutEmail(): void + { + $this->changeConfigs( + [ + 'config' => [ + 'Site' => [ + 'email' => '', + ], + ], + ] + ); + + $session = $this->getMinkSession(); + $session->visit($this->getVuFindUrl('/Record/does_not_exist')); + $page = $session->getPage(); + $this->assertEquals( + 'An error has occurred An error occurred during execution; please try again later. Please contact the' + . ' Library Reference Department for assistance', + $this->findCssAndGetText($page, '.alert-danger') + ); + } +} diff --git a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/HoldsTest.php b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/HoldsTest.php index e63eb10b141..5ed6087ec2e 100644 --- a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/HoldsTest.php +++ b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/HoldsTest.php @@ -115,21 +115,31 @@ protected function gotoRecordWithSearch( /** * Support method to place a hold and click through to "Your Holds and Recalls." * - * @param Element $page Page element. - * @param array $extras Associative array of selector => value for additional + * @param Element $page Page element. + * @param array $extras Associative array of selector => value for additional * form values to set. + * @param ?string $expectedStatus The status value expected in the hold URL + * (null to skip the check) * * @return void */ protected function placeHold( Element $page, - array $extras = [] + array $extras = [], + ?string $expectedStatus = null ): void { $this->waitForPageLoad($page); // Wait for request checks to complete (they may affect layout): $this->unFindCss($page, '.request-check'); // Open the "place hold" dialog - $this->clickCss($page, 'a.placehold'); + $placeHold = $this->findCss($page, 'a.placehold'); + $href = $placeHold->getAttribute('href'); + if ($expectedStatus) { + [, $query] = explode('?', $href); + parse_str($query, $queryParams); + $this->assertEquals($expectedStatus, $queryParams['status']); + } + $placeHold->click(); // Set pickup location to a non-default value so we can confirm that // the element is being passed through correctly, then submit form: @@ -147,17 +157,20 @@ protected function placeHold( /** * Support method to place a hold and click through to "Your Holds and Recalls." * - * @param Element $page Page element. - * @param array $extras Associative array of selector => value for additional + * @param Element $page Page element. + * @param array $extras Associative array of selector => value for additional * form values to set. + * @param ?string $expectedStatus The status value expected in the hold URL + * (null to skip the check) * * @return void */ protected function placeHoldAndGoToHoldsScreen( Element $page, - array $extras = [] + array $extras = [], + ?string $expectedStatus = null ): void { - $this->placeHold($page, $extras); + $this->placeHold($page, $extras, $expectedStatus); // If successful, we should now have a link to review the hold: $link = $this->findCss($page, '.modal-body a'); @@ -346,7 +359,8 @@ public function testPlaceSecondHoldWithSSO(): void } /** - * Test placing a hold with an optional "required by" date + * Test placing a hold with an optional "required by" date, and with the + * status included in the URL. * * @return void */ @@ -354,7 +368,7 @@ public function testPlaceHoldWithOptionalRequiredBy(): void { $demoConfig = $this->getDemoIniOverrides(); $demoConfig['Holds'] = [ - 'HMACKeys' => 'record_id:item_id:level', + 'HMACKeys' => 'record_id:item_id:level:status', 'extraHoldFields' => 'comments:requestGroup:pickUpLocation:requiredByDateOptional', 'defaultRequiredDate' => '', @@ -388,7 +402,7 @@ public function testPlaceHoldWithOptionalRequiredBy(): void $this->submitCatalogLoginForm($page, 'catuser', 'catpass'); // Test placing a hold with an invalid "required by" date: - $this->placeHold($page, ['#requiredByDate' => '01-01-2023']); + $this->placeHold($page, ['#requiredByDate' => '01-01-2023'], 'Available'); $this->assertEquals( "Please enter a valid 'required by' date", $this->findCssAndGetText($page, '.alert.alert-danger') @@ -401,7 +415,7 @@ public function testPlaceHoldWithOptionalRequiredBy(): void $this->closeLightbox($page); // Create the hold and go to the holds screen: - $this->placeHoldAndGoToHoldsScreen($page, ['#requiredByDate' => '']); + $this->placeHoldAndGoToHoldsScreen($page, ['#requiredByDate' => ''], 'Available'); // Verify the hold is correct: $this->assertEquals( diff --git a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/JumpToRecordTest.php b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/JumpToRecordTest.php index ba30f4bf249..df7b2b66e30 100644 --- a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/JumpToRecordTest.php +++ b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/JumpToRecordTest.php @@ -57,6 +57,16 @@ public function testJumpToFirst() 'La congiura dei Principi Napoletani 1701 : (prima e seconda stesura) /', trim($this->findCssAndGetText($page, 'h1')) ); + + // check if jump to is disabled on breadcrumb link + $this->clickCss($page, '.breadcrumb li:first-child'); + $this->waitForPageLoad($page); + + $expected = 'Showing 1 - 1 results of 1'; + $this->assertStringStartsWith( + $expected, + $this->findCssAndGetText($page, '.search-stats') + ); } /** diff --git a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/OAuth2Test.php b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/OAuth2Test.php index d0ba141af13..bbd6e8657ff 100644 --- a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/OAuth2Test.php +++ b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/OAuth2Test.php @@ -31,6 +31,8 @@ namespace VuFindTest\Mink; +use function count; + /** * OAuth2/OIDC test class. * @@ -115,6 +117,16 @@ protected function getOauth2ConfigOverrides(string $redirectUri): array 'isConfidential' => true, 'secret' => password_hash('mysecret', PASSWORD_DEFAULT), ], + 'test_limited' => [ + 'name' => 'Integration Test', + 'redirectUri' => $redirectUri, + 'isConfidential' => true, + 'secret' => password_hash('mysecret', PASSWORD_DEFAULT), + 'allowedScopes' => [ + 'openid', + 'profile', + ], + ], ], ]; } @@ -140,24 +152,61 @@ protected function setUpTest(string $redirectUri): void ); } + /** + * Data provider for testOAuth2Authorization + * + * @return array + */ + public static function oauth2AuthorizationProvider(): array + { + return [ + 'test client' => [ + 'test', + [ + 'Read your user identifier', + 'Read your basic profile information (name, language, birthdate)', + 'Read a unique hash based on your library user identifier', + 'Read your age', + ], + false, + ], + 'limited test client' => [ + 'test_limited', + [ + 'Read your user identifier', + 'Read your basic profile information (name, language, birthdate)', + ], + true, + ], + ]; + } + /** * Test OAuth2 authorization. * + * @param string $clientId Client ID + * @param array $expectedPermissions Expected permissions in the request + * @param bool $limited Whether the permission set has been limited by the server + * * @return void + * + * @dataProvider oauth2AuthorizationProvider */ - public function testOAuth2Authorization(): void + public function testOAuth2Authorization(string $clientId, array $expectedPermissions, bool $limited): void { // Bogus redirect URI, but it doesn't matter since the page won't handle the // authorization response: $redirectUri = $this->getVuFindUrl() . '/Content/faq'; $this->setUpTest($redirectUri); + static::removeUsers(['username1']); + $nonce = time(); $state = md5((string)$nonce); // Go to OAuth2 authorization screen: $params = [ - 'client_id' => 'test', + 'client_id' => $clientId, 'scope' => 'openid profile library_user_id age', 'response_type' => 'code', 'redirect_uri' => $redirectUri, @@ -184,18 +233,14 @@ public function testOAuth2Authorization(): void 'catuser' . $oauth2ConfigOverrides['Server']['hashSalt'] ); - $expectedPermissions = [ - 'Read your user identifier', - 'Read your basic profile information (name, language, birthdate)', - 'Read a unique hash based on your library user identifier', - 'Read your age', - ]; foreach ($expectedPermissions as $index => $permission) { $this->assertEquals( $permission, $this->findCssAndGetText($page, 'div.oauth2-prompt li', null, $index) ); } + // Ensure that there are no more permissions: + $this->unFindCss($page, 'div.oauth2-prompt li', null, count($expectedPermissions)); $this->clickCss($page, '.form-oauth2-authorize button.btn.btn-primary'); @@ -213,7 +258,7 @@ public function testOAuth2Authorization(): void 'code' => $queryParams['code'], 'grant_type' => 'authorization_code', 'redirect_uri' => $redirectUri, - 'client_id' => 'test', + 'client_id' => $clientId, 'client_secret' => 'mysecret', ]; $response = $this->httpPost( @@ -243,21 +288,26 @@ public function testOAuth2Authorization(): void ); $this->assertInstanceOf(\stdClass::class, $idToken); - $this->assertEquals('test', $idToken->aud); + $this->assertEquals($clientId, $idToken->aud); $this->assertEquals($nonce, $idToken->nonce); $this->assertEquals('Tester McTestenson', $idToken->name); $this->assertEquals('Tester', $idToken->given_name); $this->assertEquals('McTestenson', $idToken->family_name); - $this->assertEquals($catIdHash, $idToken->library_user_id); $this->assertMatchesRegularExpression( '/^\d{4}-\d{2}-\d{2}$/', $idToken->birthdate ); - $this->assertEquals( - \DateTime::createFromFormat('Y-m-d', $idToken->birthdate) - ->diff(new \DateTimeImmutable())->format('%y'), - $idToken->age - ); + if ($limited) { + $this->assertObjectNotHasProperty('library_user_id', $idToken); + $this->assertObjectNotHasProperty('age', $idToken); + } else { + $this->assertEquals($catIdHash, $idToken->library_user_id); + $this->assertEquals( + \DateTime::createFromFormat('Y-m-d', $idToken->birthdate) + ->diff(new \DateTimeImmutable())->format('%y'), + $idToken->age + ); + } // Test the userinfo endpoint: $response = $this->httpGet( @@ -276,15 +326,26 @@ public function testOAuth2Authorization(): void ); $userInfo = json_decode($response->getBody(), true); + $this->assertEquals($idToken->sub, $userInfo['sub']); $this->assertEquals($nonce, $userInfo['nonce']); $this->assertEquals('Tester McTestenson', $userInfo['name']); $this->assertEquals('Tester', $userInfo['given_name']); $this->assertEquals('McTestenson', $userInfo['family_name']); - $this->assertEquals($catIdHash, $userInfo['library_user_id']); $this->assertMatchesRegularExpression( '/^\d{4}-\d{2}-\d{2}$/', $userInfo['birthdate'] ); + if ($limited) { + $this->assertObjectNotHasProperty('library_user_id', $idToken); + $this->assertObjectNotHasProperty('age', $idToken); + } else { + $this->assertEquals($catIdHash, $userInfo['library_user_id']); + $this->assertEquals( + \DateTime::createFromFormat('Y-m-d', $userInfo['birthdate']) + ->diff(new \DateTimeImmutable())->format('%y'), + $userInfo['age'] + ); + } // Test token request with bad credentials: $tokenParams['client_secret'] = 'badsecret'; @@ -400,6 +461,16 @@ public function testOAuth2InvalidScope(): void */ public function testOAuth2InvalidClient(): void { + // Disable logging of a known exception: + $this->changeConfigs( + [ + 'config' => [ + 'Logging' => [ + 'file' => null, + ], + ], + ] + ); // Bogus redirect URI, but it doesn't matter since the page won't handle the // authorization response: $redirectUri = $this->getVuFindUrl() . '/Content/faq'; @@ -418,6 +489,8 @@ public function testOAuth2InvalidClient(): void 'state' => $state, ]; $session = $this->getMinkSession(); + // We expect an error, so let's act like production mode for realistic testing: + $session->setWhoopsDisabled(true); $session->visit( $this->getVuFindUrl() . '/OAuth2/Authorize?' . http_build_query($params) ); diff --git a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/SavedSearchesTest.php b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/SavedSearchesTest.php index 1c962aae5c4..e13423680dc 100644 --- a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/SavedSearchesTest.php +++ b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/SavedSearchesTest.php @@ -240,7 +240,9 @@ public function testSavedSearchSecurity(): void $this->findAndAssertLink($page, 'Log Out')->click(); // Use user A's delete link, but try to execute it as user B: - [$base, $params] = explode('?', $delete); + [, $params] = explode('?', $delete); + // We expect an error, so let's act like production mode for realistic testing: + $session->setWhoopsDisabled(true); $session->visit($this->getVuFindUrl() . '/MyResearch/SaveSearch?' . $params); $page = $session->getPage(); $this->clickCss($page, '.createAccountLink'); @@ -252,6 +254,8 @@ public function testSavedSearchSecurity(): void $this->waitForPageLoad($page); $this->findAndAssertLink($page, 'Log Out')->click(); + // Go back to stricter error handling: + $session->setWhoopsDisabled(false); // Go back in as user A -- see if the saved search still exists. $this->findAndAssertLink($page, 'Search History')->click(); $this->clickCss($page, '#loginOptions a'); diff --git a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/SearchFacetsTest.php b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/SearchFacetsTest.php index a66799ac995..2a25c6bb815 100644 --- a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/SearchFacetsTest.php +++ b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/SearchFacetsTest.php @@ -858,7 +858,7 @@ public function testCollapseStatePersistence(): void // We have now reloaded the page. Let's toggle format off and on to confirm // that it was opened, and let's also toggle building on to confirm that - // it was not alread opened. + // it was not already opened. $this->clickCss($page, '#side-panel-format .title'); // off $this->waitForPageLoad($page); $this->clickCss($page, '#side-panel-format .collapsed'); // on diff --git a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/SsoTest.php b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/SsoTest.php index c1f272896bf..023da5fe079 100644 --- a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/SsoTest.php +++ b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/SsoTest.php @@ -67,15 +67,42 @@ public function getConfigIniOverrides(): array ]; } + /** + * Data provider for testLogin() + * + * @return array[] + */ + public static function loginConfigProvider(): array + { + return [ + 'with cat_username mapping' => [ // test for regression of #3992 + [ + 'General' => [ + 'attributes' => [ + 'cat_username' => 'foo', + ], + ], + ], + ], + 'defaults' => [], + ]; + } + /** * Test SSO login * + * @param array $extraSsoConfigs Extra configurations for SimulatedSSO.ini + * * @return void + * + * @dataProvider loginConfigProvider */ - public function testLogin(): void + public function testLogin(array $extraSsoConfigs = []): void { // Set up configs - $this->changeConfigs($this->getConfigIniOverrides()); + $configs = $this->getConfigIniOverrides(); + $configs['SimulatedSSO'] = array_merge_recursive($configs['SimulatedSSO'], $extraSsoConfigs); + $this->changeConfigs($configs); $session = $this->getMinkSession(); $session->visit($this->getVuFindUrl()); $page = $session->getPage(); diff --git a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/UrlShortenerTest.php b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/UrlShortenerTest.php index 84f7897161b..5c2355eea91 100644 --- a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/UrlShortenerTest.php +++ b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/UrlShortenerTest.php @@ -57,6 +57,7 @@ public function testDatabaseDrivenShortening(): void 'email_action' => 'enabled', 'testOnly' => true, 'message_log' => $this->getEmailLogPath(), + 'message_log_format' => $this->getEmailLogFormat(), 'url_shortener' => 'database', ], ], @@ -76,8 +77,8 @@ public function testDatabaseDrivenShortening(): void $this->assertEquals('Message Sent', $this->findCssAndGetText($page, '.modal .alert-success')); // Extract the link from the provided message: - $email = file_get_contents($this->getEmailLogPath()); - preg_match('/Link: <(http.*)>/', $email, $matches); + $email = $this->getLoggedEmail(); + preg_match('/Link: <(http.*)>/', $email->getBody()->getBody(), $matches); $shortLink = $matches[1]; $this->assertNotEquals($searchUrl, $shortLink); $this->assertStringContainsString('/short', $shortLink); diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/Auth/EmailAuthenticatorTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/Auth/EmailAuthenticatorTest.php index 08258548b6f..9e0ba84c4fa 100644 --- a/module/VuFind/tests/unit-tests/src/VuFindTest/Auth/EmailAuthenticatorTest.php +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/Auth/EmailAuthenticatorTest.php @@ -31,7 +31,6 @@ use DateTime; use Laminas\Config\Config; -use Laminas\Http\PhpEnvironment\RemoteAddress; use Laminas\Http\Request; use Laminas\I18n\Translator\TranslatorInterface; use Laminas\Session\SessionManager; @@ -43,6 +42,7 @@ use VuFind\Db\Entity\AuthHashEntityInterface; use VuFind\Db\Service\AuthHashServiceInterface; use VuFind\Mailer\Mailer; +use VuFind\Net\UserIpReader; use VuFind\Validator\CsrfInterface; /** @@ -63,7 +63,7 @@ class EmailAuthenticatorTest extends \PHPUnit\Framework\TestCase * @param ?CsrfInterface $csrf CSRF validator * @param ?Mailer $mailer Mailer service * @param ?PhpRenderer $renderer View renderer - * @param ?RemoteAddress $remoteAddress Remote address details + * @param ?userIpReader $userIpReader User IP reader * @param array $config Configuration settings * @param ?AuthHashServiceInterface $authHashService AuthHash database service * @@ -77,7 +77,7 @@ protected function getEmailAuthenticator( CsrfInterface $csrf = null, Mailer $mailer = null, PhpRenderer $renderer = null, - RemoteAddress $remoteAddress = null, + UserIpReader $userIpReader = null, array $config = [], AuthHashServiceInterface $authHashService = null ): EmailAuthenticator { @@ -86,7 +86,7 @@ protected function getEmailAuthenticator( $csrf ?? $this->createMock(CsrfInterface::class), $mailer ?? $this->createMock(Mailer::class), $renderer ?? $this->createMock(PhpRenderer::class), - $remoteAddress ?? $this->createMock(RemoteAddress::class), + $userIpReader ?? $this->createMock(UserIpReader::class), new Config($config), $authHashService ?? $this->createMock(AuthHashServiceInterface::class) ); @@ -186,14 +186,14 @@ function ($name) use ($mockServerUrl, $mockUrl) { $renderer->expects($this->once())->method('render') ->with('Email/login-link.phtml', $this->callback($checkViewParams)) ->willReturn('foo-message'); - $remoteAddress = $this->createMock(RemoteAddress::class); - $remoteAddress->expects($this->once())->method('getIpAddress')->willReturn('foo-ip'); + $userIpReader = $this->createMock(userIpReader::class); + $userIpReader->expects($this->once())->method('getUserIp')->willReturn('foo-ip'); $authenticator = $this->getEmailAuthenticator( sessionManager: $sessionManager, csrf: $csrf, mailer: $mailer, renderer: $renderer, - remoteAddress: $remoteAddress, + userIpReader: $userIpReader, config: ['Site' => ['title' => 'foo-site-title', 'email' => 'from@example.com']], authHashService: $authHashService ); diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/CartTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/CartTest.php index 4ceb05eaadf..34c82154cdb 100644 --- a/module/VuFind/tests/unit-tests/src/VuFindTest/CartTest.php +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/CartTest.php @@ -111,18 +111,6 @@ protected function getCart($maxSize = 100, $active = true, $cookies = []) return new \VuFind\Cart($this->loader, $cookies, $maxSize, $active); } - /** - * Test cookie domain setting. - * - * @return void - */ - public function testCookieDomain() - { - $manager = $this->getMockCookieManager([], '/', '.example.com'); - $cart = $this->getCart(100, true, $manager); - $this->assertEquals('.example.com', $cart->getCookieDomain()); - } - /** * Check that the cart is empty by default. * diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/Config/UpgradeTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/Config/UpgradeTest.php index 714fb59a223..34423a36645 100644 --- a/module/VuFind/tests/unit-tests/src/VuFindTest/Config/UpgradeTest.php +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/Config/UpgradeTest.php @@ -62,7 +62,7 @@ class UpgradeTest extends \PHPUnit\Framework\TestCase * * @return Upgrade */ - protected function getUpgrader($version) + protected function getUpgrader(string $version): Upgrade { $oldDir = realpath($this->getFixtureDir() . 'configs/' . $version); $rawDir = realpath(__DIR__ . '/../../../../../../../config/vufind'); @@ -78,7 +78,7 @@ protected function getUpgrader($version) * * @return array */ - protected function checkVersion($version) + protected function checkVersion(string $version): array { $upgrader = $this->getUpgrader($version); $upgrader->run(); @@ -204,7 +204,7 @@ protected function checkVersion($version) * * @return void */ - public function testUpgrade11() + public function testUpgrade11(): void { $this->checkVersion('1.1'); } @@ -214,7 +214,7 @@ public function testUpgrade11() * * @return void */ - public function testUpgrade12() + public function testUpgrade12(): void { $this->checkVersion('1.2'); } @@ -224,7 +224,7 @@ public function testUpgrade12() * * @return void */ - public function testUpgrade13() + public function testUpgrade13(): void { $this->checkVersion('1.3'); } @@ -234,7 +234,7 @@ public function testUpgrade13() * * @return void */ - public function testUpgrade14() + public function testUpgrade14(): void { $this->checkVersion('1.4'); } @@ -244,7 +244,7 @@ public function testUpgrade14() * * @return void */ - public function testDefaultGenerator() + public function testDefaultGenerator(): void { // We expect the upgrader to switch default values: $upgrader = $this->getUpgrader('defaultgenerator'); @@ -270,7 +270,7 @@ public function testDefaultGenerator() * * @return void */ - public function testSpelling() + public function testSpelling(): void { $upgrader = $this->getUpgrader('spelling'); $upgrader->run(); @@ -286,7 +286,7 @@ public function testSpelling() * * @return void */ - public function testSyndetics() + public function testSyndetics(): void { // Test upgrading an SSL URL $upgrader = $this->getUpgrader('syndeticsurlssl'); @@ -312,7 +312,7 @@ public function testSyndetics() * * @return void */ - public function testGooglePreviewUpgrade() + public function testGooglePreviewUpgrade(): void { $upgrader = $this->getUpgrader('googlepreview'); $upgrader->run(); @@ -323,45 +323,12 @@ public function testGooglePreviewUpgrade() ); } - /** - * Test removal of xID settings - * - * @return void - */ - public function testXidDeprecation() - { - $upgrader = $this->getUpgrader('xid'); - $upgrader->run(); - $results = $upgrader->getNewConfigs(); - $this->assertEquals( - ['Similar'], - $results['config.ini']['Record']['related'] - ); - $this->assertEquals( - ['WorldCatSimilar'], - $results['WorldCat.ini']['Record']['related'] - ); - $this->assertEquals(['apiKey' => 'foo'], $results['config.ini']['WorldCat']); - $expectedWarnings = [ - 'The [WorldCat] id setting is no longer used and has been removed.', - 'The [WorldCat] xISBN_token setting is no longer used and has been removed.', - 'The [WorldCat] xISBN_secret setting is no longer used and has been removed.', - 'The [WorldCat] xISSN_token setting is no longer used and has been removed.', - 'The [WorldCat] xISSN_secret setting is no longer used and has been removed.', - 'The Editions related record module is no longer supported due to OCLC\'s xID ' - . 'API shutdown. It has been removed from your settings.', - 'The WorldCatEditions related record module is no longer supported due to OCLC\'s ' - . 'xID API shutdown. It has been removed from your settings.', - ]; - $this->assertEquals($expectedWarnings, $upgrader->getWarnings()); - } - /** * Test permission upgrade * * @return void */ - public function testPermissionUpgrade() + public function testPermissionUpgrade(): void { $upgrader = $this->getUpgrader('permissions'); $upgrader->run(); @@ -429,7 +396,7 @@ public function testPermissionUpgrade() * * @return void */ - public function testGoogleWarnings() + public function testGoogleWarnings(): void { $upgrader = $this->getUpgrader('googlewarnings'); $upgrader->run(); @@ -467,46 +434,26 @@ public function testGoogleWarnings() * * @return void */ - public function testWorldCatWarnings() + public function testWorldCatWarnings(): void { $upgrader = $this->getUpgrader('worldcatwarnings'); $upgrader->run(); $warnings = $upgrader->getWarnings(); $this->assertTrue( in_array( - 'The [WorldCat] LimitCodes setting never had any effect and has been' - . ' removed.', + 'The [WorldCat] section of config.ini has been removed following' + . ' the shutdown of the v1 WorldCat search API; use WorldCat2.ini instead.', $warnings ) ); } - /** - * Test WorldCat-specific upgrades. - * - * @return void - */ - public function testWorldCatUpgrades() - { - $upgrader = $this->getUpgrader('worldcatupgrades'); - $upgrader->run(); - $results = $upgrader->getNewConfigs(); - $this->assertEquals( - 'Author', - $results['WorldCat.ini']['Basic_Searches']['srw.au'] - ); - $this->assertEquals( - 'adv_search_author', - $results['WorldCat.ini']['Advanced_Searches']['srw.au'] - ); - } - /** * Test "meaningful line" detection in SolrMarc properties files. * * @return void */ - public function testMeaningfulLineDetection() + public function testMeaningfulLineDetection(): void { $upgrader = $this->getUpgrader('1.4'); $meaningless = realpath( @@ -536,7 +483,7 @@ public function testMeaningfulLineDetection() * * @return void */ - public function testCommentExtraction() + public function testCommentExtraction(): void { $upgrader = $this->getUpgrader('comments'); $config = $this->getFixtureDir() . 'configs/comments/config.ini'; @@ -574,7 +521,7 @@ public function testCommentExtraction() * * @return void */ - public function testPrimoUpgrade() + public function testPrimoUpgrade(): void { $upgrader = $this->getUpgrader('primo'); $upgrader->run(); @@ -591,7 +538,7 @@ public function testPrimoUpgrade() * * @return void */ - public function testAmazonCoverWarning() + public function testAmazonCoverWarning(): void { $upgrader = $this->getUpgrader('amazoncover'); $upgrader->run(); @@ -610,7 +557,7 @@ public function testAmazonCoverWarning() * * @return void */ - public function testAmazonReviewWarning() + public function testAmazonReviewWarning(): void { $upgrader = $this->getUpgrader('amazonreview'); $upgrader->run(); @@ -629,7 +576,7 @@ public function testAmazonReviewWarning() * * @return void */ - public function testReCaptcha() + public function testReCaptcha(): void { $upgrader = $this->getUpgrader('recaptcha'); $upgrader->run(); diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/Content/Covers/GoogleTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/Content/Covers/GoogleTest.php index 98b9c5a2332..651ae3aa3c8 100644 --- a/module/VuFind/tests/unit-tests/src/VuFindTest/Content/Covers/GoogleTest.php +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/Content/Covers/GoogleTest.php @@ -50,35 +50,30 @@ class GoogleTest extends \PHPUnit\Framework\TestCase /** * Get a callback to check the download function call. * - * @param string $body Body for mock to return - * @param string $expectedId Identifier expected in request URL - * @param string $expectedIdType Expected identifier type in request URL + * @param string $body Body for mock to return + * @param string|string[] $expectedIds Identifier(s) expected in request URL * * @return callable */ protected function getDownloadCallback( string $body, - string $expectedId, - string $expectedIdType = 'ISBN' + string|array $expectedIds, ): callable { - return function ($url, $params, $callback) use ($body, $expectedId, $expectedIdType) { + return function ($url, $params, $callback) use ($body, $expectedIds) { $this->assertEquals( 'https://books.google.com/books?jscmd=viewapi' - . '&bibkeys=' . $expectedIdType . ':' . $expectedId . '&callback=addTheCover', + . '&bibkeys=' . urlencode(implode(',', (array)$expectedIds)) . '&callback=addTheCover', $url ); $this->assertEquals([], $params); - $response = $this->getMockBuilder(\Laminas\Http\Response::class) - ->disableOriginalConstructor() - ->getMock(); - $response->expects($this->any())->method('getBody') - ->will($this->returnValue($body)); + $response = $this->createMock(\Laminas\Http\Response::class); + $response->expects($this->any())->method('getBody')->willReturn($body); return $callback($response, $url); }; } /** - * Test cover loading + * Test cover loading with a single ISBN * * @return void */ @@ -86,15 +81,12 @@ public function testValidCoverLoading(): void { $loader = new Google(); - $mockDownloader = $this->getMockBuilder(CachingDownloader::class) - ->disableOriginalConstructor() - ->getMock(); + $mockDownloader = $this->createMock(CachingDownloader::class); $downloadCallback = $this->getDownloadCallback( $this->getFixture('content/covers/google-cover.js'), - '9781612917986' + 'ISBN:9781612917986' ); - $mockDownloader->expects($this->once())->method('download') - ->will($this->returnCallback($downloadCallback)); + $mockDownloader->expects($this->once())->method('download')->willReturnCallback($downloadCallback); $loader->setCachingDownloader($mockDownloader); $this->assertEquals( @@ -108,6 +100,34 @@ public function testValidCoverLoading(): void ); } + /** + * Test cover loading with multiple IDs + * + * @return void + */ + public function testValidCoverLoadingWithMultipleIds(): void + { + $loader = new Google(); + + $mockDownloader = $this->createMock(CachingDownloader::class); + $downloadCallback = $this->getDownloadCallback( + $this->getFixture('content/covers/google-cover.js'), + ['ISBN:9781612917986', 'ISBN:9780123456786', 'OCLC:1234'] + ); + $mockDownloader->expects($this->once())->method('download')->willReturnCallback($downloadCallback); + $loader->setCachingDownloader($mockDownloader); + + $this->assertEquals( + 'https://books.google.com/books/content' + . '?id=dEMBBAAAQBAJ&printsec=frontcover&img=1&zoom=5&edge=curl', + $loader->getUrl( + '', + 'small', + ['isbns' => [new ISBN('1612917984'), new ISBN('0123456789')], 'oclc' => '1234'] + ) + ); + } + /** * Test cover loading at a larger size * @@ -117,15 +137,12 @@ public function testValidLargeCoverLoading(): void { $loader = new Google(); - $mockDownloader = $this->getMockBuilder(CachingDownloader::class) - ->disableOriginalConstructor() - ->getMock(); + $mockDownloader = $this->createMock(CachingDownloader::class); $downloadCallback = $this->getDownloadCallback( $this->getFixture('content/covers/google-cover.js'), - '9781612917986' + 'ISBN:9781612917986' ); - $mockDownloader->expects($this->once())->method('download') - ->will($this->returnCallback($downloadCallback)); + $mockDownloader->expects($this->once())->method('download')->willReturnCallback($downloadCallback); $loader->setCachingDownloader($mockDownloader); $this->assertEquals( @@ -148,15 +165,12 @@ public function testNoAvailableThumbnailLoading(): void { $loader = new Google(); - $mockDownloader = $this->getMockBuilder(CachingDownloader::class) - ->disableOriginalConstructor() - ->getMock(); + $mockDownloader = $this->createMock(CachingDownloader::class); $downloadCallback = $this->getDownloadCallback( $this->getFixture('content/covers/google-cover-no-thumbnail.js'), - '9781612917986' + 'ISBN:9781612917986' ); - $mockDownloader->expects($this->once())->method('download') - ->will($this->returnCallback($downloadCallback)); + $mockDownloader->expects($this->once())->method('download')->willReturnCallback($downloadCallback); $loader->setCachingDownloader($mockDownloader); $this->assertFalse( @@ -177,16 +191,12 @@ public function testOCLCLoading(): void { $loader = new Google(); - $mockDownloader = $this->getMockBuilder(CachingDownloader::class) - ->disableOriginalConstructor() - ->getMock(); + $mockDownloader = $this->createMock(CachingDownloader::class); $downloadCallback = $this->getDownloadCallback( $this->getFixture('content/covers/google-cover-no-thumbnail.js'), - '1234', - 'OCLC' + 'OCLC:1234' ); - $mockDownloader->expects($this->once())->method('download') - ->will($this->returnCallback($downloadCallback)); + $mockDownloader->expects($this->once())->method('download')->willReturnCallback($downloadCallback); $loader->setCachingDownloader($mockDownloader); $this->assertFalse( @@ -207,12 +217,9 @@ public function testEmptyResponse(): void { $loader = new Google(); - $mockDownloader = $this->getMockBuilder(CachingDownloader::class) - ->disableOriginalConstructor() - ->getMock(); - $downloadCallback = $this->getDownloadCallback('', '9781612917986'); - $mockDownloader->expects($this->once())->method('download') - ->will($this->returnCallback($downloadCallback)); + $mockDownloader = $this->createMock(CachingDownloader::class); + $downloadCallback = $this->getDownloadCallback('', 'ISBN:9781612917986'); + $mockDownloader->expects($this->once())->method('download')->willReturnCallback($downloadCallback); $loader->setCachingDownloader($mockDownloader); $this->expectExceptionMessage('Invalid response body (raw)'); $loader->getUrl( @@ -231,15 +238,12 @@ public function testInvalidNonEmptyResponse(): void { $loader = new Google(); - $mockDownloader = $this->getMockBuilder(CachingDownloader::class) - ->disableOriginalConstructor() - ->getMock(); + $mockDownloader = $this->createMock(CachingDownloader::class); $downloadCallback = $this->getDownloadCallback( $this->getFixture('content/covers/google-cover-invalid.js'), - '9781612917986' + 'ISBN:9781612917986' ); - $mockDownloader->expects($this->once())->method('download') - ->will($this->returnCallback($downloadCallback)); + $mockDownloader->expects($this->once())->method('download')->willReturnCallback($downloadCallback); $loader->setCachingDownloader($mockDownloader); $this->expectExceptionMessage('Invalid response body (json)'); $loader->getUrl( diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/ILS/Driver/FolioTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/ILS/Driver/FolioTest.php index 5853232e1bf..50c117f7964 100644 --- a/module/VuFind/tests/unit-tests/src/VuFindTest/ILS/Driver/FolioTest.php +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/ILS/Driver/FolioTest.php @@ -332,6 +332,7 @@ public function testSuccessfulPatronLoginWithOkapi(): void 'firstname' => 'first', 'lastname' => 'last', 'email' => 'fake@fake.com', + 'addressTypeIds' => [], ]; $this->assertEquals($expected, $result); } @@ -361,6 +362,7 @@ public function testSuccessfulPatronLoginWithOkapiLegacyAuth(): void 'firstname' => 'first', 'lastname' => 'last', 'email' => 'fake@fake.com', + 'addressTypeIds' => [], ]; $this->assertEquals($expected, $result); } @@ -380,6 +382,7 @@ public function testSuccessfulPlaceHold(): void 'requiredByTS' => 1641049790, 'patron' => ['id' => 'foo'], 'item_id' => 'record1', + 'id' => 'instanceid', 'status' => 'Available', 'pickUpLocation' => 'desk1', ]; @@ -406,6 +409,7 @@ public function testSuccessfulPlaceHoldLegacy(): void 'requiredByTS' => 1641049790, 'patron' => ['id' => 'foo'], 'item_id' => 'record1', + 'id' => 'instanceid', 'status' => 'Available', 'pickUpLocation' => 'desk1', ]; @@ -430,6 +434,7 @@ public function testSuccessfulPlaceHoldNoExpirationDate(): void $details = [ 'patron' => ['id' => 'foo'], 'item_id' => 'record1', + 'id' => 'instanceid', 'status' => 'Available', 'pickUpLocation' => 'desk1', ]; @@ -458,6 +463,7 @@ public function testUnsuccessfulPlaceHoldInvalidExpirationDate(): void 'requiredByTS' => '3333-33-33', 'patron' => ['id' => 'foo'], 'item_id' => 'record1', + 'id' => 'instanceid', 'status' => 'Available', 'pickUpLocation' => 'desk1', ]; @@ -514,6 +520,7 @@ public function testUnsuccessfulPlaceHold(): void 'requiredByTS' => 946739390, 'patron' => ['id' => 'foo'], 'item_id' => 'record1', + 'id' => 'instanceid', 'status' => 'Available', 'pickUpLocation' => 'desk1', ]; @@ -807,7 +814,7 @@ public function testIsHoldableCaseSensitivityConfig(): void /** * Test calls to isHoldable using exact mode with invalid - * location values and paramter values to isHoldable + * location values and parameter values to isHoldable * * @depends testTokens * @@ -1264,7 +1271,7 @@ public function testGetPagedResultsEqualToLimit(): void * Test getPagedResults with estimates being passed back from folio * for the first response. This is different from * testGetPagedResultsEqualToLimit since the totalRecords in the - * response from the API is inacurrate for the first response + * response from the API is inaccurate for the first response * (i.e. just an estimate). * * @depends testTokens diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/Mailer/MailerTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/Mailer/MailerTest.php index 1906d9ff29e..b1b9bef9fe8 100644 --- a/module/VuFind/tests/unit-tests/src/VuFindTest/Mailer/MailerTest.php +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/Mailer/MailerTest.php @@ -29,8 +29,8 @@ namespace VuFindTest\Mailer; -use Laminas\Mail\Address; -use Laminas\Mail\AddressList; +use Symfony\Component\Mailer\MailerInterface; +use Symfony\Component\Mime\Address; use VuFind\Mailer\Factory as MailerFactory; use VuFind\Mailer\Mailer; use VuFindTest\Container\MockContainer; @@ -49,6 +49,7 @@ class MailerTest extends \PHPUnit\Framework\TestCase { use \VuFindTest\Feature\ConfigPluginManagerTrait; + use \VuFindTest\Feature\ReflectionTrait; /** * Test that the factory configures the object correctly. @@ -61,26 +62,30 @@ public function testFactoryConfiguration() 'Mail' => [ 'host' => 'vufindtest.localhost', 'port' => 123, - 'connection_time_limit' => 600, - 'name' => 'foo', + 'name' => 'foo?bar', 'username' => 'vufinduser', 'password' => 'vufindpass', + 'connection_time_limit' => 60, + ], + ]; + $configDsn = [ + 'Mail' => [ + 'dsn' => 'esmtp://foo@bar/', ], ]; $cm = $this->getMockConfigPluginManager(compact('config')); $sm = new MockContainer($this); $sm->set(\VuFind\Config\PluginManager::class, $cm); $factory = new MailerFactory(); - $mailer = $factory($sm, Mailer::class); - $options = $mailer->getTransport()->getOptions(); - $this->assertEquals('vufindtest.localhost', $options->getHost()); - $this->assertEquals('foo', $options->getName()); - $this->assertEquals(123, $options->getPort()); - $this->assertEquals(600, $options->getConnectionTimeLimit()); - $this->assertEquals('login', $options->getConnectionClass()); + + $this->assertEquals( + 'smtp://vufinduser:vufindpass@vufindtest.localhost:123?local_domain=foo%3Fbar&ping_threshold=60', + $this->callMethod($factory, 'getDSN', [$config]) + ); + $this->assertEquals( - ['username' => 'vufinduser', 'password' => 'vufindpass'], - $options->getConnectionConfig() + 'esmtp://foo@bar/', + $this->callMethod($factory, 'getDSN', [$configDsn]) ); } @@ -92,14 +97,12 @@ public function testFactoryConfiguration() public function testSend() { $callback = function ($message): bool { - return '<to@example.com>' == $message->getTo()->current()->toString() - && '<from@example.com>' == $message->getFrom()->current()->toString() - && 'body' == $message->getBody() + return 'to@example.com' == $message->getTo()[0]->toString() + && 'from@example.com' == $message->getFrom()[0]->toString() + && 'body' == $message->getBody()->getBody() && 'subject' == $message->getSubject(); }; - $transport = $this->createMock(\Laminas\Mail\Transport\TransportInterface::class); - $transport->expects($this->once())->method('send')->with($this->callback($callback)); - $mailer = new Mailer($transport); + $mailer = $this->getMailer($callback); $mailer->send('to@example.com', 'from@example.com', 'subject', 'body'); } @@ -111,16 +114,13 @@ public function testSend() public function testSendWithAddressObjectInSender() { $callback = function ($message): bool { - $fromString = $message->getFrom()->current()->toString(); - return '<to@example.com>' == $message->getTo()->current()->toString() - && 'Sender TextName <from@example.com>' == $fromString - && 'body' == $message->getBody() + return 'to@example.com' == $message->getTo()[0]->toString() + && '"Sender TextName" <from@example.com>' == $message->getFrom()[0]->toString() + && 'body' == $message->getBody()->getBody() && 'subject' == $message->getSubject(); }; - $transport = $this->createMock(\Laminas\Mail\Transport\TransportInterface::class); - $transport->expects($this->once())->method('send')->with($this->callback($callback)); + $mailer = $this->getMailer($callback); $address = new Address('from@example.com', 'Sender TextName'); - $mailer = new Mailer($transport); $mailer->send('to@example.com', $address, 'subject', 'body'); } @@ -132,36 +132,33 @@ public function testSendWithAddressObjectInSender() public function testSendWithAddressObjectInRecipient() { $callback = function ($message): bool { - return 'Recipient TextName <to@example.com>' == $message->getTo()->current()->toString() - && '<from@example.com>' == $message->getFrom()->current()->toString() - && 'body' == $message->getBody() + return '"Recipient TextName" <to@example.com>' == $message->getTo()[0]->toString() + && 'from@example.com' == $message->getFrom()[0]->toString() + && 'body' == $message->getBody()->getBody() && 'subject' == $message->getSubject(); }; - $transport = $this->createMock(\Laminas\Mail\Transport\TransportInterface::class); - $transport->expects($this->once())->method('send')->with($this->callback($callback)); + $mailer = $this->getMailer($callback); $address = new Address('to@example.com', 'Recipient TextName'); - $mailer = new Mailer($transport); $mailer->send($address, 'from@example.com', 'subject', 'body'); } /** - * Test sending an email using an address list object for the To field. + * Test sending an email using an address list for the To field. * * @return void */ - public function testSendWithAddressListObjectInRecipient() + public function testSendWithAddressListInRecipient() { $callback = function ($message): bool { - return 'Recipient TextName <to@example.com>' == $message->getTo()->current()->toString() - && '<from@example.com>' == $message->getFrom()->current()->toString() - && 'body' == $message->getBody() + return '"Recipient TextName" <to@example.com>' == $message->getTo()[0]->toString() + && 'from@example.com' == $message->getFrom()[0]->toString() + && 'body' == $message->getBody()->getBody() && 'subject' == $message->getSubject(); }; - $transport = $this->createMock(\Laminas\Mail\Transport\TransportInterface::class); - $transport->expects($this->once())->method('send')->with($this->callback($callback)); - $list = new AddressList(); - $list->add(new Address('to@example.com', 'Recipient TextName')); - $mailer = new Mailer($transport); + $mailer = $this->getMailer($callback); + $list = [ + new Address('to@example.com', 'Recipient TextName'), + ]; $mailer->send($list, 'from@example.com', 'subject', 'body'); } @@ -173,18 +170,15 @@ public function testSendWithAddressListObjectInRecipient() public function testSendWithFromOverride() { $callback = function ($message): bool { - $fromString = $message->getFrom()->current()->toString(); - return '<to@example.com>' == $message->getTo()->current()->toString() - && '<me@example.com>' == $message->getReplyTo()->current()->toString() - && 'me <no-reply@example.com>' == $fromString - && 'body' == $message->getBody() + return 'to@example.com' == $message->getTo()[0]->toString() + && 'me@example.com' == $message->getReplyTo()[0]->toString() + && '"me" <no-reply@example.com>' == $message->getFrom()[0]->toString() + && 'body' == $message->getBody()->getBody() && 'subject' == $message->getSubject(); }; - $transport = $this->createMock(\Laminas\Mail\Transport\TransportInterface::class); - $transport->expects($this->once())->method('send')->with($this->callback($callback)); - $address = new Address('me@example.com'); - $mailer = new Mailer($transport); + $mailer = $this->getMailer($callback); $mailer->setFromAddressOverride('no-reply@example.com'); + $address = new Address('me@example.com'); $mailer->send('to@example.com', $address, 'subject', 'body'); } @@ -196,17 +190,14 @@ public function testSendWithFromOverride() public function testSendWithReplyTo() { $callback = function ($message): bool { - $fromString = $message->getFrom()->current()->toString(); - return '<to@example.com>' == $message->getTo()->current()->toString() - && '<reply-to@example.com>' == $message->getReplyTo()->current()->toString() - && '<me@example.com>' == $fromString - && 'body' == $message->getBody() + return 'to@example.com' == $message->getTo()[0]->toString() + && 'reply-to@example.com' == $message->getReplyTo()[0]->toString() + && 'me@example.com' == $message->getFrom()[0]->toString() + && 'body' == $message->getBody()->getBody() && 'subject' == $message->getSubject(); }; - $transport = $this->createMock(\Laminas\Mail\Transport\TransportInterface::class); - $transport->expects($this->once())->method('send')->with($this->callback($callback)); + $mailer = $this->getMailer($callback); $address = new Address('me@example.com'); - $mailer = new Mailer($transport); $mailer->send('to@example.com', $address, 'subject', 'body', null, 'reply-to@example.com'); } @@ -219,18 +210,16 @@ public function testSendWithReplyTo() public function testSendWithFromOverrideAndReplyTo() { $callback = function ($message): bool { - $fromString = $message->getFrom()->current()->toString(); - return '<to@example.com>' == $message->getTo()->current()->toString() - && '<reply-to@example.com>' == $message->getReplyTo()->current()->toString() - && 'me <no-reply@example.com>' == $fromString - && 'body' == $message->getBody() + $fromString = $message->getFrom()[0]->toString(); + return 'to@example.com' == $message->getTo()[0]->toString() + && 'reply-to@example.com' == $message->getReplyTo()[0]->toString() + && '"me" <no-reply@example.com>' == $fromString + && 'body' == $message->getBody()->getBody() && 'subject' == $message->getSubject(); }; - $transport = $this->createMock(\Laminas\Mail\Transport\TransportInterface::class); - $transport->expects($this->once())->method('send')->with($this->callback($callback)); - $address = new Address('me@example.com'); - $mailer = new Mailer($transport); + $mailer = $this->getMailer($callback); $mailer->setFromAddressOverride('no-reply@example.com'); + $address = new Address('me@example.com'); $mailer->send('to@example.com', $address, 'subject', 'body', null, 'reply-to@example.com'); } @@ -245,9 +234,8 @@ public function testBadTo() $this->expectExceptionMessage('Invalid Recipient Email Address'); $this->expectExceptionCode(\VuFind\Exception\Mail::ERROR_INVALID_RECIPIENT); - $transport = $this->createMock(\Laminas\Mail\Transport\TransportInterface::class); - $mailer = new Mailer($transport); - $mailer->send('bad@bad', 'from@example.com', 'subject', 'body'); + $mailer = $this->getMailer(); + $mailer->send('bad@.bad', 'from@example.com', 'subject', 'body'); } /** @@ -261,15 +249,14 @@ public function testBadReplyTo() $this->expectExceptionMessage('Invalid Reply-To Email Address'); $this->expectExceptionCode(\VuFind\Exception\Mail::ERROR_INVALID_REPLY_TO); - $transport = $this->createMock(\Laminas\Mail\Transport\TransportInterface::class); - $mailer = new Mailer($transport); + $mailer = $this->getMailer(); $mailer->send( 'good@good.com', 'from@example.com', 'subject', 'body', null, - 'bad@bad' + 'bad@.bad' ); } @@ -284,8 +271,7 @@ public function testEmptyTo() $this->expectExceptionMessage('Invalid Recipient Email Address'); $this->expectExceptionCode(\VuFind\Exception\Mail::ERROR_INVALID_RECIPIENT); - $transport = $this->createMock(\Laminas\Mail\Transport\TransportInterface::class); - $mailer = new Mailer($transport); + $mailer = $this->getMailer(); $mailer->send('', 'from@example.com', 'subject', 'body'); } @@ -300,8 +286,7 @@ public function testTooManyRecipients() $this->expectExceptionMessage('Too Many Email Recipients'); $this->expectExceptionCode(\VuFind\Exception\Mail::ERROR_TOO_MANY_RECIPIENTS); - $transport = $this->createMock(\Laminas\Mail\Transport\TransportInterface::class); - $mailer = new Mailer($transport); + $mailer = $this->getMailer(); $mailer->send('one@test.com;two@test.com', 'from@example.com', 'subject', 'body'); } @@ -316,25 +301,8 @@ public function testBadFrom() $this->expectExceptionMessage('Invalid Sender Email Address'); $this->expectExceptionCode(\VuFind\Exception\Mail::ERROR_INVALID_SENDER); - $transport = $this->createMock(\Laminas\Mail\Transport\TransportInterface::class); - $mailer = new Mailer($transport); - $mailer->send('to@example.com', 'bad@bad', 'subject', 'body'); - } - - /** - * Test bad from address in Address object. - * - * @return void - */ - public function testBadFromInAddressObject() - { - $this->expectException(\VuFind\Exception\Mail::class); - $this->expectExceptionMessage('Invalid Sender Email Address'); - $this->expectExceptionCode(\VuFind\Exception\Mail::ERROR_INVALID_SENDER); - - $transport = $this->createMock(\Laminas\Mail\Transport\TransportInterface::class); - $mailer = new Mailer($transport); - $mailer->send('to@example.com', new Address('bad@bad'), 'subject', 'body'); + $mailer = $this->getMailer(); + $mailer->send('to@example.com', 'bad@.bad', 'subject', 'body'); } /** @@ -347,7 +315,7 @@ public function testTransportException() $this->expectException(\VuFind\Exception\Mail::class); $this->expectExceptionMessage('Boom'); - $transport = $this->createMock(\Laminas\Mail\Transport\TransportInterface::class); + $transport = $this->createMock(MailerInterface::class); $transport->expects($this->once())->method('send')->will($this->throwException(new \Exception('Boom'))); $mailer = new Mailer($transport); $mailer->send('to@example.com', 'from@example.com', 'subject', 'body'); @@ -397,17 +365,15 @@ public function testSendLink() $callback = function ($message): bool { $to = $message->getTo(); - return $to->has('to@example.com') - && $to->has('to2@example.com') + return 'to@example.com' === $to[0]->toString() + && 'to2@example.com' === $to[1]->toString() && 2 == count($to) - && '<from@example.com>' == $message->getFrom()->current()->toString() - && '<cc@example.com>' == $message->getCc()->current()->toString() - && 'body' == $message->getBody() + && 'from@example.com' == $message->getFrom()[0]->toString() + && 'cc@example.com' == $message->getCc()[0]->toString() + && 'body' == $message->getBody()->getBody() && 'Library Catalog Search Result' == $message->getSubject(); }; - $transport = $this->createMock(\Laminas\Mail\Transport\TransportInterface::class); - $transport->expects($this->once())->method('send')->with($this->callback($callback)); - $mailer = new Mailer($transport); + $mailer = $this->getMailer($callback); $mailer->setMaxRecipients(2); $mailer->sendLink( 'to@example.com;to2@example.com', @@ -443,30 +409,15 @@ public function testSendRecord() ->will($this->returnValue('body')); $callback = function ($message): bool { - return '<to@example.com>' == $message->getTo()->current()->toString() - && '<from@example.com>' == $message->getFrom()->current()->toString() - && 'body' == $message->getBody() + return 'to@example.com' == $message->getTo()[0]->toString() + && 'from@example.com' == $message->getFrom()[0]->toString() + && 'body' == $message->getBody()->getBody() && 'Library Catalog Record: breadcrumb' == $message->getSubject(); }; - $transport = $this->createMock(\Laminas\Mail\Transport\TransportInterface::class); - $transport->expects($this->once())->method('send')->with($this->callback($callback)); - $mailer = new Mailer($transport); + $mailer = $this->getMailer($callback); $mailer->sendRecord('to@example.com', 'from@example.com', 'message', $driver, $view); } - /** - * Test connection reset - * - * @return void - */ - public function testResetConnection() - { - $transport = $this->createMock(\Laminas\Mail\Transport\Smtp::class); - $transport->expects($this->once())->method('disconnect'); - $mailer = new Mailer($transport); - $mailer->resetConnection(); - } - /** * Test sending an email using with text part and html part and multipart content type. * @@ -478,19 +429,31 @@ public function testSendMimeMessageWithMultipartAlternativeContentType() $html = '<!DOCTYPE html><head><title>htmlhtml body part'; $text = 'this is the text part'; $callback = function ($message) use ($html, $text): bool { - $fromString = $message->getFrom()->current()->toString(); - return '' == $message->getTo()->current()->toString() - && 'Sender TextName ' == $fromString + return 'to@example.com' == $message->getTo()[0]->toString() + && '"Sender TextName" ' == $message->getFrom()[0]->toString() && 'subject' == $message->getSubject() - && 0 <= strpos($message->getBody()->getParts()[0]->getContent(), $html) - && 0 <= strpos($message->getBody()->getParts()[0]->getContent(), $text) - && 'multipart/alternative' == $message->getHeaders()->get('Content-Type')->getType(); + && str_contains($message->getBody()->getParts()[0]->getBody(), $text) + && str_contains($message->getBody()->getParts()[1]->getBody(), $html); }; - $transport = $this->createMock(\Laminas\Mail\Transport\TransportInterface::class); - $transport->expects($this->once())->method('send')->with($this->callback($callback)); $address = new Address('from@example.com', 'Sender TextName'); - $mailer = new Mailer($transport); + $mailer = $this->getMailer($callback); $body = $mailer->buildMultipartBody($text, $html); $mailer->send('to@example.com', $address, 'subject', $body); } + + /** + * Create mailer with a mock transport + * + * @param ?callable $callback Mock send method result callback + * + * @return Mailer + */ + protected function getMailer($callback = null) + { + $transport = $this->createMock(MailerInterface::class); + if ($callback) { + $transport->expects($this->once())->method('send')->with($this->callback($callback)); + } + return new Mailer($transport); + } } diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/Record/CacheTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/Record/CacheTest.php index 0a36fb051fb..0faae71ff6b 100644 --- a/module/VuFind/tests/unit-tests/src/VuFindTest/Record/CacheTest.php +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/Record/CacheTest.php @@ -66,14 +66,14 @@ class CacheTest extends \PHPUnit\Framework\TestCase * @param string $data Data * @param string $version Version * - * @return RecordEntityInterface + * @return MockObject&RecordEntityInterface */ protected function getMockRecord( string $id, string $source, string $data, string $version = '2.5' - ): RecordEntityInterface { + ): MockObject&RecordEntityInterface { $mock = $this->createMock(RecordEntityInterface::class); $mock->method('getRecordId')->willReturn($id); $mock->method('getSource')->willReturn($source); @@ -91,7 +91,7 @@ protected function setUp(): void { $this->recordData = [ $this->getMockRecord('020645147', 'Solr', 's:17:"dummy_solr_record";'), - $this->getMockRecord('70764764', 'WorldCat', 's:21:"dummy_worldcat_record";'), + $this->getMockRecord('70764764', 'WorldCat2', 's:22:"dummy_worldcat2_record";'), $this->getMockRecord('00033321', 'Solr', 's:19:"dummy_solr_record_2";'), ]; } @@ -101,14 +101,14 @@ protected function setUp(): void * * @return void */ - public function testLookup() + public function testLookup(): void { $recordCache = $this->getRecordCache(); $record = $recordCache->lookup('020645147', 'Solr'); $this->assertNotEmpty($record); - $record = $recordCache->lookup('70764764', 'WorldCat'); + $record = $recordCache->lookup('70764764', 'WorldCat2'); $this->assertNotEmpty($record); $record = $recordCache->lookup('1234', 'Solr'); @@ -123,7 +123,7 @@ public function testLookup() * * @return void */ - public function testLookupBatch() + public function testLookupBatch(): void { $recordCache = $this->getRecordCache(); @@ -133,7 +133,7 @@ public function testLookupBatch() $records = $recordCache->lookupBatch(['020645147', '1234'], 'Solr'); $this->assertCount(1, $records); - $records = $recordCache->lookupBatch(['020645147', '00033321'], 'WorldCat'); + $records = $recordCache->lookupBatch(['020645147', '00033321'], 'WorldCat2'); $this->assertEmpty($records); $records = $recordCache->lookupBatch(['0206451', '1234'], 'Solr'); @@ -145,12 +145,12 @@ public function testLookupBatch() * * @return void */ - public function testIsFallback() + public function testIsFallback(): void { $recordCache = $this->getRecordCache(); $this->assertFalse($recordCache->isFallback('Solr')); - $this->assertTrue($recordCache->isFallback('WorldCat')); + $this->assertTrue($recordCache->isFallback('WorldCat2')); $this->assertFalse($recordCache->isFallback('Summon')); } @@ -160,12 +160,12 @@ public function testIsFallback() * * @return void */ - public function testIsPrimary() + public function testIsPrimary(): void { $recordCache = $this->getRecordCache(); $this->assertTrue($recordCache->isPrimary('Solr')); - $this->assertFalse($recordCache->isPrimary('WorldCat')); + $this->assertFalse($recordCache->isPrimary('WorldCat2')); $this->assertFalse($recordCache->isPrimary('Summon')); } @@ -175,12 +175,12 @@ public function testIsPrimary() * * @return void */ - public function testIsCachable() + public function testIsCachable(): void { $recordCache = $this->getRecordCache(); $this->assertTrue($recordCache->isCachable('Solr')); - $this->assertTrue($recordCache->isCachable('WorldCat')); + $this->assertTrue($recordCache->isCachable('WorldCat2')); $this->assertFalse($recordCache->isCachable('Summon')); } @@ -189,7 +189,7 @@ public function testIsCachable() * * @return void */ - public function testSetContext() + public function testSetContext(): void { $recordCache = $this->getRecordCache(); @@ -211,7 +211,7 @@ public function testSetContext() * * @return void */ - public function testCreateOrUpdate() + public function testCreateOrUpdate(): void { $recordCache = $this->getRecordCache(); @@ -230,7 +230,7 @@ protected function getConfig(): Config $configArr = [ 'Default' => [ 'Solr' => ['operatingMode' => 'primary'], - 'WorldCat' => ['operatingMode' => 'fallback'], + 'WorldCat2' => ['operatingMode' => 'fallback'], ], 'Disabled' => [ 'Solr' => [], @@ -292,16 +292,16 @@ protected function getRecordFactoryManager(): MockObject&\VuFind\RecordDriver\Pl { $recordFactoryManager = $this->createMock(\VuFind\RecordDriver\PluginManager::class); $recordFactoryManager->method('getSolrRecord')->willReturn($this->getDriver('test', 'Solr')); - $recordFactoryManager->method('get')->willReturn($this->getDriver('test', 'WorldCat')); + $recordFactoryManager->method('get')->willReturn($this->getDriver('test', 'WorldCat2')); return $recordFactoryManager; } /** * Create a Cache object * - * @return \VuFind\Record\Cache + * @return Cache */ - protected function getRecordCache() + protected function getRecordCache(): Cache { $recordCache = new Cache( $this->getRecordFactoryManager(), @@ -318,12 +318,12 @@ protected function getRecordCache() * @param string $id id * @param string $source source * - * @return \VuFind\RecordDriver\AbstractBase + * @return MockObject&\VuFind\RecordDriver\AbstractBase */ protected function getDriver( $id = 'test', $source = 'Solr' - ): \VuFind\RecordDriver\AbstractBase { + ): MockObject&\VuFind\RecordDriver\AbstractBase { $driver = $this->createMock(\VuFind\RecordDriver\AbstractBase::class); $driver->method('getUniqueId')->willReturn($id); $driver->method('getSourceIdentifier')->willReturn($source); diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/Record/LoaderTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/Record/LoaderTest.php index 953274e090c..46bbfc24921 100644 --- a/module/VuFind/tests/unit-tests/src/VuFindTest/Record/LoaderTest.php +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/Record/LoaderTest.php @@ -29,6 +29,7 @@ namespace VuFindTest\Record; +use PHPUnit\Framework\MockObject\MockObject; use VuFind\Record\Cache; use VuFind\Record\FallbackLoader\PluginManager as FallbackLoader; use VuFind\Record\Loader; @@ -58,23 +59,20 @@ class LoaderTest extends \PHPUnit\Framework\TestCase * * @return void */ - public function testMissingRecord() + public function testMissingRecord(): void { $this->expectException(\VuFind\Exception\RecordMissing::class); $this->expectExceptionMessage('Record Solr:test does not exist.'); $collection = $this->getCollection([]); - $commandObj = $this->getMockBuilder(\VuFindSearch\Command\AbstractBase::class) - ->disableOriginalConstructor() - ->getMock(); + $commandObj = $this->createMock(\VuFindSearch\Command\AbstractBase::class); $commandObj->expects($this->once())->method('getResult') - ->will($this->returnValue($collection)); - $service = $this->getMockBuilder(\VuFindSearch\Service::class) - ->disableOriginalConstructor()->getMock(); + ->willReturn($collection); + $service = $this->createMock(\VuFindSearch\Service::class); $arguments = ['test', new ParamBag()]; $service->expects($this->once())->method('invoke') ->with($this->callback($this->getCommandChecker($arguments))) - ->will($this->returnValue($commandObj)); + ->willReturn($commandObj); $loader = $this->getLoader($service); $loader->load('test'); } @@ -84,16 +82,13 @@ public function testMissingRecord() * * @return void */ - public function testMissingRecordWithFallback() + public function testMissingRecordWithFallback(): void { $collection = $this->getCollection([]); - $commandObj = $this->getMockBuilder(\VuFindSearch\Command\AbstractBase::class) - ->disableOriginalConstructor() - ->getMock(); + $commandObj = $this->createMock(\VuFindSearch\Command\AbstractBase::class); $commandObj->expects($this->once())->method('getResult') - ->will($this->returnValue($collection)); - $service = $this->getMockBuilder(\VuFindSearch\Service::class) - ->disableOriginalConstructor()->getMock(); + ->willReturn($collection); + $service = $this->createMock(\VuFindSearch\Service::class); $class = \VuFindSearch\Command\RetrieveCommand::class; $arguments = ['test', new ParamBag()]; $service->expects($this->once())->method('invoke') @@ -101,7 +96,7 @@ public function testMissingRecordWithFallback() $this->callback( $this->getCommandChecker($arguments, $class, 'Summon') ) - )->will($this->returnValue($commandObj)); + )->willReturn($commandObj); $driver = $this->getDriver(); $fallbackLoader = $this->getFallbackLoader([$driver]); $loader = $this->getLoader($service, null, null, $fallbackLoader); @@ -113,27 +108,21 @@ public function testMissingRecordWithFallback() * * @return void */ - public function testToleratedMissingRecord() + public function testToleratedMissingRecord(): void { $collection = $this->getCollection([]); - $commandObj = $this->getMockBuilder(\VuFindSearch\Command\AbstractBase::class) - ->disableOriginalConstructor() - ->getMock(); - $commandObj->expects($this->once())->method('getResult') - ->will($this->returnValue($collection)); - $service = $this->getMockBuilder(\VuFindSearch\Service::class) - ->disableOriginalConstructor()->getMock(); + $commandObj = $this->createMock(\VuFindSearch\Command\AbstractBase::class); + $commandObj->expects($this->once())->method('getResult')->willReturn($collection); + $service = $this->createMock(\VuFindSearch\Service::class); $arguments = ['test', new ParamBag()]; $service->expects($this->once())->method('invoke') ->with($this->callback($this->getCommandChecker($arguments))) - ->will($this->returnValue($commandObj)); + ->willReturn($commandObj); $missing = $this->getDriver('missing', 'Missing'); - $factory = $this->getMockBuilder(\VuFind\RecordDriver\PluginManager::class) - ->disableOriginalConstructor() - ->getMock(); + $factory = $this->createMock(\VuFind\RecordDriver\PluginManager::class); $factory->expects($this->once())->method('get') ->with($this->equalTo('Missing')) - ->will($this->returnValue($missing)); + ->willReturn($missing); $loader = $this->getLoader($service, $factory); $record = $loader->load('test', 'Solr', true); $this->assertEquals($missing, $record); @@ -144,21 +133,18 @@ public function testToleratedMissingRecord() * * @return void */ - public function testSingleRecord() + public function testSingleRecord(): void { $driver = $this->getDriver(); $collection = $this->getCollection([$driver]); - $commandObj = $this->getMockBuilder(\VuFindSearch\Command\AbstractBase::class) - ->disableOriginalConstructor() - ->getMock(); + $commandObj = $this->createMock(\VuFindSearch\Command\AbstractBase::class); $commandObj->expects($this->once())->method('getResult') - ->will($this->returnValue($collection)); - $service = $this->getMockBuilder(\VuFindSearch\Service::class) - ->disableOriginalConstructor()->getMock(); + ->willReturn($collection); + $service = $this->createMock(\VuFindSearch\Service::class); $arguments = ['test', new ParamBag()]; $service->expects($this->once())->method('invoke') ->with($this->callback($this->getCommandChecker($arguments))) - ->will($this->returnValue($commandObj)); + ->willReturn($commandObj); $loader = $this->getLoader($service); $this->assertEquals($driver, $loader->load('test')); } @@ -168,7 +154,7 @@ public function testSingleRecord() * * @return void */ - public function testSingleRecordWithBackendParameters() + public function testSingleRecordWithBackendParameters(): void { $params = new ParamBag(); $params->set('fq', 'id:test'); @@ -176,17 +162,13 @@ public function testSingleRecordWithBackendParameters() $driver = $this->getDriver(); $collection = $this->getCollection([$driver]); - $commandObj = $this->getMockBuilder(\VuFindSearch\Command\AbstractBase::class) - ->disableOriginalConstructor() - ->getMock(); - $commandObj->expects($this->once())->method('getResult') - ->will($this->returnValue($collection)); - $service = $this->getMockBuilder(\VuFindSearch\Service::class) - ->disableOriginalConstructor()->getMock(); + $commandObj = $this->createMock(\VuFindSearch\Command\AbstractBase::class); + $commandObj->expects($this->once())->method('getResult')->willReturn($collection); + $service = $this->createMock(\VuFindSearch\Service::class); $arguments = ['test', $params]; $service->expects($this->once())->method('invoke') ->with($this->callback($this->getCommandChecker($arguments))) - ->will($this->returnValue($commandObj)); + ->willReturn($commandObj); $loader = $this->getLoader($service); $this->assertEquals($driver, $loader->load('test', 'Solr', false, $params)); } @@ -196,7 +178,7 @@ public function testSingleRecordWithBackendParameters() * * @return void */ - public function testBatchLoad() + public function testBatchLoad(): void { $driver1 = $this->getDriver('test1', 'Solr'); $driver2 = $this->getDriver('test2', 'Solr'); @@ -213,21 +195,16 @@ public function testBatchLoad() $worldCatParams = new ParamBag(); $worldCatParams->set('fq', 'id:test4'); - $factory = $this->getMockBuilder(\VuFind\RecordDriver\PluginManager::class) - ->disableOriginalConstructor() - ->getMock(); + $factory = $this->createMock(\VuFind\RecordDriver\PluginManager::class); $factory->expects($this->once())->method('get') ->with($this->equalTo('Missing')) - ->will($this->returnValue($missing)); + ->willReturn($missing); - $commandObj = $this->getMockBuilder(\VuFindSearch\Command\AbstractBase::class) - ->disableOriginalConstructor() - ->getMock(); + $commandObj = $this->createMock(\VuFindSearch\Command\AbstractBase::class); $commandObj->expects($this->exactly(3))->method('getResult') ->willReturnOnConsecutiveCalls($collection1, $collection2, $collection3); - $service = $this->getMockBuilder(\VuFindSearch\Service::class) - ->disableOriginalConstructor()->getMock(); + $service = $this->createMock(\VuFindSearch\Service::class); $class = \VuFindSearch\Command\RetrieveBatchCommand::class; $arguments1 = [['test1', 'test2'], $solrParams]; @@ -240,7 +217,7 @@ public function testBatchLoad() [ [$this->callback($this->getCommandChecker($arguments1, $class))], [$this->callback($this->getCommandChecker($arguments2, $class, 'Summon'))], - [$this->callback($this->getCommandChecker($arguments3, $class, 'WorldCat'))], + [$this->callback($this->getCommandChecker($arguments3, $class, 'WorldCat2'))], ], $commandObj ); @@ -248,14 +225,14 @@ public function testBatchLoad() $loader = $this->getLoader($service, $factory); $input = [ ['source' => 'Solr', 'id' => 'test1'], - 'Solr|test2', 'Summon|test3', 'WorldCat|test4', + 'Solr|test2', 'Summon|test3', 'WorldCat2|test4', ]; $this->assertEquals( [$driver1, $driver2, $driver3, $missing], $loader->loadBatch( $input, false, - ['Solr' => $solrParams, 'WorldCat' => $worldCatParams] + ['Solr' => $solrParams, 'WorldCat2' => $worldCatParams] ) ); } @@ -265,7 +242,7 @@ public function testBatchLoad() * * @return void */ - public function testBatchLoadWithFallback() + public function testBatchLoadWithFallback(): void { $driver1 = $this->getDriver('test1', 'Solr'); $driver2 = $this->getDriver('test2', 'Solr'); @@ -277,14 +254,11 @@ public function testBatchLoadWithFallback() $solrParams = new ParamBag(); $solrParams->set('fq', 'id:test1'); - $commandObj = $this->getMockBuilder(\VuFindSearch\Command\AbstractBase::class) - ->disableOriginalConstructor() - ->getMock(); + $commandObj = $this->createMock(\VuFindSearch\Command\AbstractBase::class); $commandObj->expects($this->exactly(2))->method('getResult') ->willReturnOnConsecutiveCalls($collection1, $collection2); - $service = $this->getMockBuilder(\VuFindSearch\Service::class) - ->disableOriginalConstructor()->getMock(); + $service = $this->createMock(\VuFindSearch\Service::class); $arguments1 = [['test1', 'test2'], $solrParams]; $arguments2 = [['test3'], new ParamBag()]; @@ -325,10 +299,10 @@ public function testBatchLoadWithFallback() * @return callable */ protected function getCommandChecker( - $args = [], - $class = \VuFindSearch\Command\RetrieveCommand::class, - $target = 'Solr' - ) { + array $args = [], + string $class = \VuFindSearch\Command\RetrieveCommand::class, + string $target = 'Solr' + ): callable { return function ($command) use ($class, $args, $target) { return $command::class === $class && $command->getArguments() == $args @@ -342,16 +316,13 @@ protected function getCommandChecker( * @param string $id Record ID * @param string $source Record source * - * @return RecordDriver + * @return MockObject&RecordDriver */ - protected function getDriver($id = 'test', $source = 'Solr') + protected function getDriver(string $id = 'test', string $source = 'Solr'): MockObject&RecordDriver { - $driver = $this->getMockBuilder(\VuFind\RecordDriver\AbstractBase::class) - ->getMock(); - $driver->expects($this->any())->method('getUniqueId') - ->will($this->returnValue($id)); - $driver->expects($this->any())->method('getSourceIdentifier') - ->will($this->returnValue($source)); + $driver = $this->createMock(\VuFind\RecordDriver\AbstractBase::class); + $driver->expects($this->any())->method('getUniqueId')->willReturn($id); + $driver->expects($this->any())->method('getSourceIdentifier')->willReturn($source); return $driver; } @@ -370,10 +341,9 @@ protected function getLoader( RecordFactory $factory = null, Cache $recordCache = null, FallbackLoader $fallbackLoader = null - ) { + ): Loader { if (null === $factory) { - $factory = $this->getMockBuilder(\VuFind\RecordDriver\PluginManager::class) - ->disableOriginalConstructor()->getMock(); + $factory = $this->createMock(\VuFind\RecordDriver\PluginManager::class); } return new Loader($service, $factory, $recordCache, $fallbackLoader); } @@ -383,9 +353,9 @@ protected function getLoader( * * @param array $records Records to return from the fallback plugin * - * @return FallbackLoader + * @return MockObject&FallbackLoader */ - protected function getFallbackLoader($records) + protected function getFallbackLoader($records): MockObject&FallbackLoader { $fallbackPlugin = $this ->getMockBuilder(\VuFind\Record\FallbackLoader\Summon::class) @@ -398,17 +368,17 @@ protected function getFallbackLoader($records) $expectedIds = array_map($callback, $records); $fallbackPlugin->expects($this->once())->method('load') ->with($this->equalTo($expectedIds)) - ->will($this->returnValue($records)); + ->willReturn($records); $fallbackLoader = $this->getMockBuilder(FallbackLoader::class) ->disableOriginalConstructor() ->onlyMethods(['get', 'has']) ->getMock(); $fallbackLoader->expects($this->once())->method('has') ->with($this->equalTo('Summon')) - ->will($this->returnValue(true)); + ->willReturn(true); $fallbackLoader->expects($this->once())->method('get') ->with($this->equalTo('Summon')) - ->will($this->returnValue($fallbackPlugin)); + ->willReturn($fallbackPlugin); return $fallbackLoader; } @@ -419,12 +389,11 @@ protected function getFallbackLoader($records) * * @return RecordCollectionInterface */ - protected function getCollection($records) + protected function getCollection(array $records): MockObject&RecordCollectionInterface { - $collection = $this->getMockBuilder(\VuFindSearch\Response\RecordCollectionInterface::class) - ->getMock(); - $collection->expects($this->any())->method('getRecords')->will($this->returnValue($records)); - $collection->expects($this->any())->method('count')->will($this->returnValue(count($records))); + $collection = $this->createMock(RecordCollectionInterface::class); + $collection->expects($this->any())->method('getRecords')->willReturn($records); + $collection->expects($this->any())->method('count')->willReturn(count($records)); return $collection; } } diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/DefaultRecordTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/DefaultRecordTest.php index ddbb6a7b57d..931a5193ad7 100644 --- a/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/DefaultRecordTest.php +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/DefaultRecordTest.php @@ -552,7 +552,7 @@ public function testGetCleanISBNs($result, $mode, $filterInvalid) } /** - * Test whether author deduplication works corrrectly. + * Test whether author deduplication works correctly. * * @return void */ diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/EDSTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/EDSTest.php index 34730db3e17..655a34a1596 100644 --- a/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/EDSTest.php +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/EDSTest.php @@ -564,17 +564,19 @@ public function testGetLinkedFullTextLink(): void } /** - * Test getItemsSubjects for a record. + * Test getAllSubjectHeadingsFlattened for a record. * * @return void */ - public function testGetItemsSubjects(): void + public function testGetAllSubjectHeadingsFlattened(): void { $driver = $this->getDriver('valid-eds-record'); $this->assertEquals( - 'PSYCHOTHERAPY, ' . - 'METAPHOR.', - $driver->getItemsSubjects() + [ + 'PSYCHOTHERAPY', + 'METAPHOR', + ], + $driver->getAllSubjectHeadingsFlattened() ); } diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/Feature/MarcAdvancedTraitTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/Feature/MarcAdvancedTraitTest.php index 139dce0779e..d27baca966e 100644 --- a/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/Feature/MarcAdvancedTraitTest.php +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/Feature/MarcAdvancedTraitTest.php @@ -204,4 +204,176 @@ public function testMarcAdvancedTraitAltScript(): void $obj->getTitleSectionsAltScript() ); } + + /** + * Test getMarcFieldWithInd when a single indicator value is sent + * + * @return void + */ + public function testGetMarcFieldWithIndOneValue(): void + { + $obj = $this->getMockDriverFromFixture('marc/marctraits.xml'); + + // Test when a single ind value is passed + $this->assertEquals( + ['upc'], + $obj->getMarcFieldWithInd('024', null, [['1' => ['1']]]) + ); + } + + /** + * Test getMarcFieldWithInd when multiple values for the indicator are sent + * + * @return void + */ + public function testGetMarcFieldWithIndTwoValues(): void + { + $obj = $this->getMockDriverFromFixture('marc/marctraits.xml'); + + // Test when multiple values for the same ind are passed + $this->assertEquals( + ['upc', 'ismn'], + $obj->getMarcFieldWithInd('024', null, [['1' => ['1', '2']]]) + ); + } + + /** + * Test getMarcFieldWithInd when multiple indicators are requested + * as AND conditions + * + * @return void + */ + public function testGetMarcFieldWithIndMultAndInds(): void + { + $obj = $this->getMockDriverFromFixture('marc/marctraits.xml'); + + // Test when different ind values are passed for each ind + $this->assertEquals( + ['spa'], + $obj->getMarcFieldWithInd('041', null, [['1' => ['1'], '2' => ['7']]]) + ); + } + + /** + * Test getMarcFieldWithInd when multiple indicators are requested + * as OR conditions + * + * @return void + */ + public function testGetMarcFieldWithIndMultOrInds(): void + { + $obj = $this->getMockDriverFromFixture('marc/marctraits.xml'); + + // Test when different ind values are passed for each ind + $this->assertEquals( + ['ger', 'spa'], + $obj->getMarcFieldWithInd('041', null, [['1' => ['0']], ['2' => ['7']]]) + ); + } + + /** + * Test getMarcFieldWithInd when no indicator filters are sent + * + * @return void + */ + public function testGetMarcFieldWithIndNoValues(): void + { + $obj = $this->getMockDriverFromFixture('marc/marctraits.xml'); + + // Test when no indicator is passed + $this->assertEquals( + ['upc', 'ismn', 'ian'], + $obj->getMarcFieldWithInd('024', null, []) + ); + } + + /** + * Test calling getSummary to get expected marc data + * + * @return void + */ + public function testGetSummary(): void + { + $obj = $this->getMockDriverFromFixture('marc/marctraits.xml'); + + $this->assertEquals( + ['Summary.'], + $obj->getSummary() + ); + } + + /** + * Test calling getSummaryNotes to get expected marc data + * + * @return void + */ + public function testGetSummaryNotes(): void + { + $obj = $this->getMockDriverFromFixture('marc/marctraits.xml'); + + $this->assertEquals( + ['Summary. Expanded.'], + $obj->getSummaryNotes() + ); + } + + /** + * Test calling getAbstractNotes to get expected marc data + * + * @return void + */ + public function testGetAbstractNotes(): void + { + $obj = $this->getMockDriverFromFixture('marc/marctraits.xml'); + + $this->assertEquals( + ['Abstract. Expanded.'], + $obj->getAbstractNotes() + ); + } + + /** + * Test calling getReviewtNotes to get expected marc data + * + * @return void + */ + public function testGetReviewNotes(): void + { + $obj = $this->getMockDriverFromFixture('marc/marctraits.xml'); + + $this->assertEquals( + ['Review Note. Expanded.'], + $obj->getReviewNotes() + ); + } + + /** + * Test calling getContentAdviceNotes to get expected marc data + * + * @return void + */ + public function testGetContentAdviceNotes(): void + { + $obj = $this->getMockDriverFromFixture('marc/marctraits.xml'); + + $this->assertEquals( + ['Content Advice. Expanded.'], + $obj->getContentAdviceNotes() + ); + } + + /** + * Test calling getLocationOfArchivalMaterialsNotes to get expected marc data + * + * @return void + */ + public function testGetLocationOfArchivalMaterialsNotes(): void + { + $obj = $this->getMockDriverFromFixture('marc/marctraits.xml'); + + $this->assertEquals( + ['Location of archival materials'], + $obj->getLocationOfArchivalMaterialsNotes() + ); + } } diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/Feature/MarcBasicTraitTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/Feature/MarcBasicTraitTest.php index e9d2121cc29..d0111481482 100644 --- a/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/Feature/MarcBasicTraitTest.php +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/Feature/MarcBasicTraitTest.php @@ -29,7 +29,8 @@ namespace VuFindTest\RecordDriver\Feature; -use VuFind\RecordDriver\WorldCat; +use PHPUnit\Framework\MockObject\MockObject; +use VuFindTest\RecordDriver\MarcBasicTraitTestHarness; /** * Record Driver Marc Traits Test Class @@ -110,17 +111,17 @@ public function testMarcBasicTraitMissingFields() * * @param string $fixture Record metadata fixture * - * @return MockObjec&WorldCat + * @return MarcBasicTraitTestHarness&MockObject */ - protected function createMockRecord(string $fixture): WorldCat + protected function createMockRecord(string $fixture): MarcBasicTraitTestHarness&MockObject { $xml = $this->getFixture("marc/$fixture"); $record = new \VuFind\Marc\MarcReader($xml); - $obj = $this->getMockBuilder(WorldCat::class) + $obj = $this->getMockBuilder(MarcBasicTraitTestHarness::class) ->onlyMethods(['getMarcReader'])->getMock(); $obj->expects($this->any()) ->method('getMarcReader') - ->will($this->returnValue($record)); + ->willReturn($record); return $obj; } } diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/WorldCat2Test.php b/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/WorldCat2Test.php new file mode 100644 index 00000000000..95b7d4c0c51 --- /dev/null +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/WorldCat2Test.php @@ -0,0 +1,276 @@ + + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ + +namespace VuFindTest\RecordDriver; + +use Laminas\Config\Config; +use VuFind\RecordDriver\WorldCat2; + +/** + * WorldCat v2 Record Driver Test Class + * + * @category VuFind + * @package Tests + * @author Demian Katz + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ +class WorldCat2Test extends \PHPUnit\Framework\TestCase +{ + use \VuFindTest\Feature\FixtureTrait; + + /** + * Data provider for testMethod(). + * + * @return array[] + */ + public static function methodTests(): array + { + return [ + 'default call numbers' => ['getCallNumbers', []], + 'default dewey call number' => ['getDeweyCallNumber', ''], + 'default raw LCCN' => ['getLCCN', ''], + 'default formats' => ['getFormats', []], + 'default ISBNs' => ['getISBNs', []], + 'default ISSNs' => ['getISSNs', []], + 'default languages' => ['getLanguages', []], + 'default places of publication' => ['getPlacesOfPublication', []], + 'default primary authors' => ['getPrimaryAuthors', []], + 'default secondary authors' => ['getSecondaryAuthors', []], + 'default corporate authors' => ['getCorporateAuthors', []], + 'default date span' => ['getDateSpan', []], + 'default publication dates' => ['getPublicationDates', []], + 'default human-readable dates' => ['getHumanReadablePublicationDates', []], + 'default publishers' => ['getPublishers', []], + 'default newer titles' => ['getNewerTitles', []], + 'default previous titles' => ['getPreviousTitles', []], + 'default summary' => ['getSummary', []], + 'default title' => ['getTitle', ''], + 'default short title' => ['getShortTitle', ''], + 'default subtitle' => ['getSubtitle', ''], + 'default edition' => ['getEdition', ''], + 'default physical description' => ['getPhysicalDescriptions', ''], + 'default subject headings' => ['getAllSubjectHeadings', []], + 'default awards' => ['getAwards', []], + 'default general notes' => ['getGeneralNotes', []], + 'default bibliography notes' => ['getBibliographyNotes', []], + 'default production credits' => ['getProductionCredits', []], + 'default publication frequency' => ['getPublicationFrequency', []], + 'default series' => ['getSeries', []], + 'default table of contents' => ['getTOC', []], + 'default URLs (no config)' => ['getURLs', []], + 'default URLs (config disabled)' => ['getURLs', [], null, ['Record' => ['show_urls' => false]]], + 'default URLs (config enabled)' => ['getURLs', [], null, ['Record' => ['show_urls' => true]]], + + 'non-default call numbers' => ['getCallNumbers', ['PR4034 .P7 1990eb', '823/.7'], 'worldcat2/pride.json'], + 'non-default dewey call number' => ['getDeweyCallNumber', '823/.7', 'worldcat2/pride.json'], + 'non-default raw LCCN' => ['getLCCN', 'PR4034.P71990eb', 'worldcat2/pride.json'], + 'non-default formats' => ['getFormats', ['Book', 'Digital'], 'worldcat2/pride.json'], + 'non-default ISBNs' => [ + 'getISBNs', + ['9780191592539', '0191592536', '0585377618', '9780585377612'], + 'worldcat2/pride.json', + ], + 'non-default languages' => ['getLanguages', ['eng'], 'worldcat2/pride.json'], + 'non-default places of publication' => ['getPlacesOfPublication', ['Oxford :'], 'worldcat2/pride.json'], + 'non-default primary authors' => + ['getPrimaryAuthors', ['Austen, Jane, 1775-1817.'], 'worldcat2/pride.json'], + 'non-default secondary authors' => ['getSecondaryAuthors', ['Kinsley, James'], 'worldcat2/pride.json'], + 'non-default publication dates' => ['getPublicationDates', ['1990'], 'worldcat2/pride.json'], + 'non-default human-readable dates' => + ['getHumanReadablePublicationDates', ['1990'], 'worldcat2/pride.json'], + 'non-default publishers' => ['getPublishers', ['Oxford University Press'], 'worldcat2/pride.json'], + 'non-default title' => ['getTitle', 'Pride and prejudice', 'worldcat2/pride.json'], + 'non-default physical description' => [ + 'getPhysicalDescriptions', + '1 online resource (xxxii, 303 pages)', + 'worldcat2/pride.json', + ], + 'non-default subject headings' => [ + 'getAllSubjectHeadings', + [ + ['Young women Fiction'], + ['Courtship Fiction'], + ['Sisters Fiction'], + ['Jeunes femmes Romans, nouvelles, etc'], + ['Amours Romans, nouvelles, etc'], + ['Sœurs Romans, nouvelles, etc'], + ['FICTION Romance General'], + ['Courtship'], + ['Sisters'], + ['Young women'], + ['England Fiction'], + ['Angleterre Romans, nouvelles, etc'], + ['England'], + ['Domestic fiction'], + ['Fiction'], + ['Love stories'], + ], + 'worldcat2/pride.json', + ], + 'non-default general notes' => [ + 'getGeneralNotes', + ['Reprint. Originally published: Oxford University Press, 1970'], + 'worldcat2/pride.json', + ], + 'non-default series' => [ + 'getSeries', + [ + ['name' => 'Oxford world\'s classics (Oxford University Press)', 'number' => ''], + ['name' => 'World\'s classics', 'number' => ''], + ], + 'worldcat2/pride.json', + ], + 'non-default URLs (no config)' => ['getURLs', [], 'worldcat2/pride.json'], + 'non-default URLs (config disabled)' => [ + 'getURLs', + [], + 'worldcat2/pride.json', + ['Record' => ['show_urls' => false]], + ], + 'non-default URLs (config enabled)' => [ + 'getURLs', + [ + [ + 'url' => + 'https://search.ebscohost.com/login.aspx?direct=true&scope=site&db=nlebk&db=nlabk&AN=55923', + ], + ['url' => 'http://www.netlibrary.com/UrlApi.aspx?action=browse&v=1&bookid=1085113'], + ['url' => 'https://archive.org/details/prideprejudice100aust'], + ['url' => 'https://openlibrary.org/books/OL1875263M'], + ], + 'worldcat2/pride.json', + ['Record' => ['show_urls' => true]], + ], + 'non-default ID' => ['getUniqueID', '49569228', 'worldcat2/pride.json'], + 'non-default OCLC numbers' => [ + 'getOCLC', + ['49569228', '530699569', '702096151', '1036823755', '1332980563'], + 'worldcat2/pride.json', + ], + + 'non-default short title' => ['getShortTitle', 'title :', 'worldcat2/title-subtitle.json'], + 'non-default subtitle' => ['getSubtitle', 'the subtitle', 'worldcat2/title-subtitle.json'], + + 'non-default newer titles' => ['getNewerTitles', ['New York Saturday journal'], 'worldcat2/star.json'], + 'non-default previous titles' => ['getPreviousTitles', ['Saturday weekly journal'], 'worldcat2/star.json'], + 'non-default publication frequency' => ['getPublicationFrequency', ['Weekly'], 'worldcat2/star.json'], + 'non-default date span' => [ + 'getDateSpan', + ['Vol. 2, no. 97 (Jan. 20, 1872)-v. 3, no. 156 (Mar. 8, 1873).'], + 'worldcat2/star.json', + ], + 'non-default corporate authors' => [ + 'getCorporateAuthors', + ['Beadle and Adams (1872-1898)', 'Johannsen Collection'], + 'worldcat2/star.json', + ], + + 'non-default ISSNs' => ['getISSNs', ['0362-4331'], 'worldcat2/issn.json'], + + 'non-default summary' => [ + 'getSummary', + [ + 'This book contains tools a Dungeon Master needs to provide stories and game play. A resource for ' + . 'new and existing Dungeon Masters to engage in both adventure and world creation, with rules, ' + . 'guidelines, and advice from the game\'s experts. Created as part of a massive public playtest ' + . 'involving more than 170,000 fans of the game', + ], + 'worldcat2/dmg.json', + ], + 'non-default production credits' => [ + 'getProductionCredits', + [ + 'D&D lead designers, Mike Mearls, Jeremy Crawford ; Dungeon master\'s guide leads, Jeremy ' + . 'Crawford, Christopher Perkins, James Wyatt ; designers, Robert J. Schwalb, Rodney ' + . 'Thompson, Peter Lee ; editors, Scott Fitzgerald Gray, Michele Carter, Chris Sims, ' + . 'Jennifer Clarke Wilkes ; producer, Greg Bilsland.', + ], + 'worldcat2/dmg.json', + ], + 'non-default edition' => ['getEdition', 'Fifth edition', 'worldcat2/dmg.json'], + 'non-default table of contents' => [ + 'getTOC', + [ + 'A world of your own -- Creating a multiverse -- Creating adventures -- ' + . 'Creating nonplayer characters -- Adventure environments -- Between adventures -- ' + . 'Treasure -- Running the game -- Dungeon master\'s workshop -- Appendix A: ' + . 'Random dungeons -- Appendix B: Monster lists -- Appendix C: Maps -- Appendix ' + . 'D: Dungeon Master inspiration.', + ], + 'worldcat2/dmg.json', + ], + + 'non-default awards' => ['getAwards', ['Fake example award'], 'worldcat2/award.json'], + + 'non-default bibliography notes' => [ + 'getBibliographyNotes', + ['Includes bibliographical references (pages xliii-lv)'], + 'worldcat2/sherlock.json', + ], + ]; + } + + /** + * Test that a method returns an expected value when given a particular fixture. + * + * @param string $method Method to test + * @param mixed $expected Expected return value + * @param ?string $fixture Fixture to use (null for empty data) + * @param array $config Configuration to apply + * + * @return void + * + * @dataProvider methodTests + */ + public function testMethod(string $method, $expected, ?string $fixture = null, array $config = []) + { + $configObj = new Config($config); + $driver = new WorldCat2($configObj, $configObj, $configObj); + if ($fixture) { + $driver->setRawData( + json_decode($this->getFixture($fixture), true) + ); + } + $this->assertEquals($expected, $driver->$method()); + } + + /** + * Test that an exception is thrown if the OCLC number is missing. + * + * @return void + */ + public function testMissingIdentifier(): void + { + $configObj = new Config([]); + $driver = new WorldCat2($configObj, $configObj, $configObj); + $this->expectExceptionMessage('ID not set!'); + $driver->getOCLC(); + } +} diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/RecordTab/HoldingsWorldCatTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/RecordTab/HoldingsWorldCat2Test.php similarity index 58% rename from module/VuFind/tests/unit-tests/src/VuFindTest/RecordTab/HoldingsWorldCatTest.php rename to module/VuFind/tests/unit-tests/src/VuFindTest/RecordTab/HoldingsWorldCat2Test.php index 5297122b821..e21c1859e9d 100644 --- a/module/VuFind/tests/unit-tests/src/VuFindTest/RecordTab/HoldingsWorldCatTest.php +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/RecordTab/HoldingsWorldCat2Test.php @@ -1,11 +1,11 @@ + * @author Demian Katz * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License * @link https://vufind.org/wiki/development:testing:unit_tests Wiki */ namespace VuFindTest\RecordTab; -use VuFind\RecordTab\HoldingsWorldCat; -use VuFindSearch\Service; +use VuFind\RecordTab\HoldingsWorldCat2; +use VuFindSearch\ParamBag; /** - * HoldingsWorldCat Test Class + * HoldingsWorldCat2 Test Class * * @category VuFind * @package Tests * @author Sudharma Kellampalli + * @author Demian Katz * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License * @link https://vufind.org/wiki/development:testing:unit_tests Wiki */ -class HoldingsWorldCatTest extends \PHPUnit\Framework\TestCase +class HoldingsWorldCat2Test extends \PHPUnit\Framework\TestCase { /** * Test getting Description. @@ -50,8 +52,8 @@ class HoldingsWorldCatTest extends \PHPUnit\Framework\TestCase */ public function testGetDescription(): void { - $searchObj = $this->getService(); - $obj = new HoldingsWorldCat($searchObj); + $searchObj = $this->createMock(\VuFindSearch\Service::class); + $obj = new HoldingsWorldCat2($searchObj); $expected = 'Holdings'; $this->assertSame($expected, $obj->getDescription()); @@ -79,14 +81,13 @@ public static function isActiveProvider(): array */ public function testIsActive(string $oclcnum, bool $expectedResult): void { - $searchObj = $this->getService(); - $obj = new HoldingsWorldCat($searchObj); - $recordDriver = $this->getMockBuilder(\VuFind\RecordDriver\WorldCat::class) - ->disableOriginalConstructor() - ->getMock(); - $recordDriver->expects($this->once())->method('tryMethod') - ->with($this->equalTo('getCleanOCLCNum')) - ->will($this->returnValue($oclcnum)); + $searchObj = $this->createMock(\VuFindSearch\Service::class); + $obj = new HoldingsWorldCat2($searchObj); + $recordDriver = $this->createMock(\VuFind\RecordDriver\WorldCat2::class); + $callback = function ($method) use ($oclcnum) { + return $method === 'getCleanOCLCNum' ? $oclcnum : null; + }; + $recordDriver->method('tryMethod')->willReturnCallback($callback); $obj->setRecordDriver($recordDriver); $this->assertSame($expectedResult, $obj->isActive()); } @@ -98,43 +99,33 @@ public function testIsActive(string $oclcnum, bool $expectedResult): void */ public function testGetHoldings(): void { - $searchObj = $this->getMockBuilder(\VuFindSearch\Service::class) - ->disableOriginalConstructor() - ->getMock(); - $obj = new HoldingsWorldCat($searchObj); - $recordDriver = $this->getMockBuilder(\VuFind\RecordDriver\WorldCat::class) - ->disableOriginalConstructor() - ->getMock(); - $recordDriver->expects($this->once())->method('tryMethod') - ->with($this->equalTo('getCleanOCLCNum')) - ->will($this->returnValue('bar')); + $searchObj = $this->createMock(\VuFindSearch\Service::class); + $obj = new HoldingsWorldCat2($searchObj, ['extra' => 'xyzzy']); + $recordDriver = $this->createMock(\VuFind\RecordDriver\WorldCat2::class); + $callback = function ($method) { + return $method === 'getCleanOCLCNum' ? 'bar' : null; + }; + $recordDriver->method('tryMethod')->willReturnCallback($callback); $obj->setRecordDriver($recordDriver); $commandObj = $this->getMockBuilder(\VuFindSearch\Command\AbstractBase::class) ->disableOriginalConstructor() ->getMock(); - $commandObj->expects($this->any())->method('getResult') - ->will($this->returnValue(true)); + $commandObj->expects($this->any())->method('getResult')->willReturn(true); $checkCommand = function ($command) { - return $command::class === \VuFindSearch\Backend\WorldCat\Command\GetHoldingsCommand::class - && $command->getArguments()[0] === 'bar' - && $command->getTargetIdentifier() === 'WorldCat'; + $this->assertEquals($command::class, \VuFindSearch\Backend\WorldCat2\Command\GetHoldingsCommand::class); + $expectedParams = new ParamBag( + [ + 'oclcNumber' => 'bar', + 'extra' => 'xyzzy', + ] + ); + $this->assertEquals([$expectedParams], $command->getArguments()); + $this->assertEquals('WorldCat2', $command->getTargetIdentifier()); + return true; }; $searchObj->expects($this->any())->method('invoke') ->with($this->callback($checkCommand)) - ->will($this->returnValue($commandObj)); + ->willReturn($commandObj); $this->assertTrue($obj->getHoldings()); } - - /** - * Get a Service object - * - * @return Service - */ - public function getService() - { - $searchObj = $this->getMockBuilder(\VuFindSearch\Service::class) - ->disableOriginalConstructor() - ->getMock(); - return $searchObj; - } } diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/Related/WorldCat2SimilarTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/Related/WorldCat2SimilarTest.php new file mode 100644 index 00000000000..80034e4d22b --- /dev/null +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/Related/WorldCat2SimilarTest.php @@ -0,0 +1,144 @@ + + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ + +namespace VuFindTest\Related; + +use PHPUnit\Framework\MockObject\MockObject; +use VuFind\RecordDriver\WorldCat2; +use VuFind\Related\WorldCat2Similar; +use VuFindSearch\ParamBag; + +/** + * WorldCat v2 Similar Related Items Test Class + * + * @category VuFind + * @package Tests + * @author Demian Katz + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ +class WorldCat2SimilarTest extends \PHPUnit\Framework\TestCase +{ + /** + * Create a mock record driver object. + * + * @param string $id ID of mock object. + * + * @return MockObject&WorldCat2 + */ + protected function getMockRecordDriver(string $id): MockObject&WorldCat2 + { + $driver = $this->getMockBuilder(WorldCat2::class) + ->onlyMethods( + [ + 'getPrimaryAuthor', + 'getAllSubjectHeadings', + 'getTitle', + 'getOCLC', + 'getSourceIdentifier', + 'getUniqueId', + ] + )->getMock(); + $driver->method('getPrimaryAuthor')->willReturn('fakepa'); + // Create a 100-term subject that will get ignored due to the query length limits: + $longTermThatWillGetSkipped = implode(' ', range(1, 100)); + $driver->method('getAllSubjectHeadings') + ->willReturn([['fakesh1a', 'fakesh1b'], ['fakesh2'], [$longTermThatWillGetSkipped]]); + $driver->method('getTitle')->willReturn('faketitle'); + $driver->method('getSourceIdentifier')->willReturn('WorldCat2'); + $driver->method('getOCLC')->willReturn([$id]); + $driver->method('getUniqueID')->willReturn($id); + return $driver; + } + + /** + * Data provider for testGetResults() + * + * @return void + */ + public static function getResultsProvider(): array + { + return [ + 'default limit' => ['"fakesh1a fakesh1b" "fakesh2" "fakepa" "faketitle"', null], + 'limit of 1' => ['"fakesh2"', 1], + 'limit of 2' => ['"fakesh1a fakesh1b"', 2], + 'limit of 3' => ['"fakesh1a fakesh1b" "fakesh2"', 3], + ]; + } + + /** + * Test results. + * + * @param string $expectedTerms Terms expected in generated query + * @param ?int $termLimit Term limit setting (null = default) + * + * @return void + * + * @dataProvider getResultsProvider + */ + public function testGetResults(string $expectedTerms, ?int $termLimit): void + { + $driver1 = $this->getMockRecordDriver('1'); + $driver2 = $this->getMockRecordDriver('2'); + $driver3 = $this->getMockRecordDriver('3'); + $service = $this->createMock(\VuFindSearch\Service::class); + $response = $this->getMockBuilder(\VuFindSearch\Backend\WorldCat2\Response\RecordCollection::class) + ->onlyMethods(['getRecords']) + ->setConstructorArgs([['offset' => 0, 'total' => 0]]) + ->getMock(); + $response->expects($this->once()) + ->method('getRecords') + ->willReturn([$driver1, $driver2, $driver3]); + + $commandObj = $this->createMock(\VuFindSearch\Command\AbstractBase::class); + $commandObj->expects($this->once())->method('getResult')->willReturn($response); + + $checkCommand = function ($command) use ($expectedTerms) { + $this->assertEquals(\VuFindSearch\Command\SearchCommand::class, $command::class); + $this->assertEquals('WorldCat2', $command->getTargetIdentifier()); + $this->assertEquals($expectedTerms, $command->getArguments()[0]->getAllTerms()); + $args = $command->getArguments(); + $this->assertEquals(1, $args[1]); + $this->assertEquals(6, $args[2]); + $expectedParams = new ParamBag(['groupRelatedEditions' => 'true']); + $this->assertEquals($expectedParams, $args[3]); + return true; + }; + $service->expects($this->once())->method('invoke') + ->with($this->callback($checkCommand)) + ->willReturn($commandObj); + + $similar = new WorldCat2Similar($service); + if ($termLimit) { + $similar->setTermLimit($termLimit); + } + $similar->init('', $driver1); + $this->assertEquals([$driver2, $driver3], $similar->getResults()); + } +} diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/Related/WorldCatSimilarTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/Related/WorldCatSimilarTest.php deleted file mode 100644 index b0343d7fc0f..00000000000 --- a/module/VuFind/tests/unit-tests/src/VuFindTest/Related/WorldCatSimilarTest.php +++ /dev/null @@ -1,117 +0,0 @@ - - * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License - * @link https://vufind.org/wiki/development:testing:unit_tests Wiki - */ - -namespace VuFindTest\Related; - -use VuFind\Related\WorldCatSimilar; - -/** - * WorldCat Similar Related Items Test Class - * - * @category VuFind - * @package Tests - * @author Demian Katz - * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License - * @link https://vufind.org/wiki/development:testing:unit_tests Wiki - */ -class WorldCatSimilarTest extends \PHPUnit\Framework\TestCase -{ - /** - * Test results. - * - * @return void - */ - public function testGetResults() - { - $driver = $this->getMockBuilder(\VuFind\RecordDriver\WorldCat::class) - ->onlyMethods( - [ - 'tryMethod', - 'getPrimaryAuthor', - 'getAllSubjectHeadings', - 'getTitle', - 'getUniqueId', - 'getSourceIdentifier', - ] - )->getMock(); - $driver->expects($this->once()) - ->method('tryMethod') - ->with($this->equalTo('getDeweyCallNumber')) - ->will($this->returnValue('fakedc')); - $driver->expects($this->once()) - ->method('getPrimaryAuthor') - ->will($this->returnValue('fakepa')); - $driver->expects($this->once()) - ->method('getAllSubjectHeadings') - ->will($this->returnValue([['fakesh1a', 'fakesh1b'], ['fakesh2']])); - $driver->expects($this->once()) - ->method('getTitle') - ->will($this->returnValue('faketitle')); - $driver->expects($this->once()) - ->method('getUniqueId') - ->will($this->returnValue('fakeid')); - $driver->expects($this->once()) - ->method('getSourceIdentifier') - ->will($this->returnValue('WorldCat')); - $service = $this->getMockBuilder(\VuFindSearch\Service::class) - ->disableOriginalConstructor() - ->getMock(); - $response = $this->getMockBuilder(\VuFindSearch\Backend\WorldCat\Response\XML\RecordCollection::class) - ->onlyMethods(['getRecords']) - ->setConstructorArgs([['offset' => 0, 'total' => 0]]) - ->getMock(); - $response->expects($this->once()) - ->method('getRecords') - ->will($this->returnValue(['fakeresponse'])); - - $commandObj = $this->getMockBuilder(\VuFindSearch\Command\AbstractBase::class) - ->disableOriginalConstructor() - ->getMock(); - $commandObj->expects($this->once())->method('getResult') - ->will($this->returnValue($response)); - - $checkCommand = function ($command) { - $expectedTerms = '(srw.dd any "fakedc" or srw.au all "fakepa" or ' - . 'srw.su all "fakesh1a fakesh1b" or srw.su all "fakesh2" or ' - . 'srw.ti any "faketitle") not srw.no all "fakeid"'; - return $command::class === \VuFindSearch\Command\SearchCommand::class - && $command->getTargetIdentifier() === 'WorldCat' - && $command->getArguments()[0]->getAllTerms() === $expectedTerms - && $command->getArguments()[1] === 0 - && $command->getArguments()[2] === 5; - }; - $service->expects($this->once())->method('invoke') - ->with($this->callback($checkCommand)) - ->will($this->returnValue($commandObj)); - - $similar = new WorldCatSimilar($service); - $similar->init('', $driver); - $this->assertEquals(['fakeresponse'], $similar->getResults()); - } -} diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/Search/WorldCat2/OptionsTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/Search/WorldCat2/OptionsTest.php new file mode 100644 index 00000000000..72d897eb025 --- /dev/null +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/Search/WorldCat2/OptionsTest.php @@ -0,0 +1,102 @@ + + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ + +namespace VuFindTest\Search\WorldCat2; + +use Laminas\Config\Config; +use VuFind\Config\PluginManager; +use VuFind\Search\WorldCat2\Options; + +/** + * WorldCat2 Search Object Options Test + * + * @category VuFind + * @package Tests + * @author Demian Katz + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ +class OptionsTest extends \PHPUnit\Framework\TestCase +{ + /** + * Test configured terms limit setting. + * + * @return void + */ + public function testGetTermsLimitWithConfiguration(): void + { + $config = ['General' => ['terms_limit' => 5]]; + $this->assertEquals(5, $this->getOptions($config)->getQueryTermsLimit()); + } + + /** + * Test default terms limit setting. + * + * @return void + */ + public function testGetTermsLimitWithDefault(): void + { + $this->assertEquals(30, $this->getOptions()->getQueryTermsLimit()); + } + + /** + * Test getting search action. + * + * @return void + */ + public function testGetSearchAction(): void + { + $this->assertEquals('worldcat2-search', $this->getOptions()->getSearchAction()); + } + + /** + * Test getting advanced search action. + * + * @return void + */ + public function testGetAdvancedSearchAction(): void + { + $this->assertEquals('worldcat2-advanced', $this->getOptions()->getAdvancedSearchAction()); + } + + /** + * Get Params object + * + * @param array $config Configuration to get from config manager + * + * @return Options + */ + protected function getOptions(array $config = []): Options + { + $mockConfig = $this->createMock(PluginManager::class); + $configObj = new Config($config); + $mockConfig->method('get')->willReturn($configObj); + return new Options($mockConfig); + } +} diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/Search/WorldCat2/ParamsTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/Search/WorldCat2/ParamsTest.php new file mode 100644 index 00000000000..397abc5a197 --- /dev/null +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/Search/WorldCat2/ParamsTest.php @@ -0,0 +1,108 @@ + + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ + +namespace VuFindTest\Search\WorldCat2; + +use VuFind\Config\PluginManager; +use VuFind\Search\WorldCat2\Options; +use VuFind\Search\WorldCat2\Params; + +/** + * WorldCat2 Search Object Parameters Test + * + * @category VuFind + * @package Tests + * @author Demian Katz + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ +class ParamsTest extends \PHPUnit\Framework\TestCase +{ + use \VuFindTest\Feature\ConfigPluginManagerTrait; + + /** + * Test that appropriate default backend parameters are created. + * + * @return void + */ + public function testDefaultGetBackendParameters(): void + { + $config = []; + $configManager = $this->getMockConfigPluginManager($config); + $params = $this->getParams(null, $configManager); + $expected = [ + 'orderBy' => ['bestMatch'], + 'facets' => [], + ]; + $this->assertEquals($expected, $params->getBackendParameters()->getArrayCopy()); + } + + /** + * Test that configured backend parameters are passed through as expected. + * + * @return void + */ + public function testNonDefaultGetBackendParameters(): void + { + $config = []; + $configManager = $this->getMockConfigPluginManager($config); + $params = $this->getParams(null, $configManager); + $params->setSort('foo', true); + $params->addFacet('bar'); + $params->addFacet('baz'); + $params->addFilter('bar:zoop'); + $params->addHiddenFilter('baz:zap'); + $expected = [ + 'orderBy' => ['foo'], + 'facets' => ['bar', 'baz'], + 'bar' => ['zoop'], + 'baz' => ['zap'], + ]; + $this->assertEquals($expected, $params->getBackendParameters()->getArrayCopy()); + } + + /** + * Get Params object + * + * @param ?Options $options Options object (null to create) + * @param ?PluginManager $mockConfig Mock config plugin manager (null to create) + * + * @return Params + */ + protected function getParams( + ?Options $options = null, + ?PluginManager $mockConfig = null + ): Params { + $mockConfig ??= $this->createMock(PluginManager::class); + return new Params( + $options ?? new Options($mockConfig), + $mockConfig + ); + } +} diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/Search/WorldCat2/ResultsTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/Search/WorldCat2/ResultsTest.php new file mode 100644 index 00000000000..8aa0a5d9442 --- /dev/null +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/Search/WorldCat2/ResultsTest.php @@ -0,0 +1,163 @@ + + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ + +namespace VuFindTest\Search\WorldCat2; + +use VuFind\Config\PluginManager; +use VuFind\Record\Loader; +use VuFind\Search\WorldCat2\Options; +use VuFind\Search\WorldCat2\Params; +use VuFind\Search\WorldCat2\Results; +use VuFindSearch\Backend\WorldCat2\Response\RecordCollection; +use VuFindSearch\Command\SearchCommand; +use VuFindSearch\ParamBag; +use VuFindSearch\Query\Query; +use VuFindSearch\Service; + +/** + * WorldCat2 Search Object Results Test + * + * @category VuFind + * @package Tests + * @author Demian Katz + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ +class ResultsTest extends \PHPUnit\Framework\TestCase +{ + use \VuFindTest\Feature\ConfigPluginManagerTrait; + + /** + * Test that empty searches are blocked. + * + * @return void + */ + public function testEmptySearch(): void + { + $query = new Query(); + $params = $this->getParams(); + $params->setQuery($query); + $results = $this->getResults($params); + // Test getting facets first, see testOverlongSearch for a different approach + $this->assertEquals([], $results->getFacetList()); + $this->assertEquals([], $results->getResults()); + $this->assertEquals(['empty_search_disallowed'], $results->getErrors()); + } + + /** + * Test that overlong searches are blocked. + * + * @return void + */ + public function testOverlongSearch(): void + { + $query = new Query(implode(' ', range(1, 100))); + $params = $this->getParams(); + $params->setQuery($query); + $results = $this->getResults($params); + // Test getting results first, as a variation of testEmptySearch() + $this->assertEquals([], $results->getResults()); + $this->assertEquals([], $results->getFacetList()); + $this->assertEquals( + [ + [ + 'msg' => 'too_many_query_terms', + 'tokens' => ['%%terms%%' => 100, '%%maxTerms%%' => 30], + ], + ], + $results->getErrors() + ); + } + + /** + * Test a successful search. + * + * @return void + */ + public function testSuccessfulSearch(): void + { + $query = new Query('foo'); + $params = $this->getParams(); + $params->setQuery($query); + $searchService = $this->createMock(Service::class); + $expectedParams = new ParamBag(['orderBy' => 'bestMatch', 'facets' => []]); + $expectedCommand = new SearchCommand('WorldCat2', $query, 1, 20, $expectedParams); + $records = new RecordCollection( + [ + 'offset' => 0, + 'total' => 5, + ] + ); + $mockCommand = $this->createMock(SearchCommand::class); + $mockCommand->method('getResult')->willReturn($records); + $searchService->expects($this->once())->method('invoke')->with($expectedCommand)->willReturn($mockCommand); + $results = $this->getResults($params, $searchService); + $this->assertEquals(5, $results->getResultTotal()); + } + + /** + * Get Params object + * + * @param ?Options $options Options object (null to create) + * @param ?PluginManager $mockConfig Mock config plugin manager (null to create) + * + * @return Params + */ + protected function getParams( + ?Options $options = null, + ?PluginManager $mockConfig = null + ): Params { + $mockConfig ??= $this->createMock(PluginManager::class); + return new Params( + $options ?? new Options($mockConfig), + $mockConfig + ); + } + + /** + * Get Results object + * + * @param ?Params $params Params object (null to create) + * @param ?\VuFindSearch\Service $search Search service (null to create) + * @param ?Loader $loader Record loader (null to create) + * + * @return Results + */ + protected function getResults( + ?Params $params = null, + ?\VuFindSearch\Service $search = null, + ?Loader $loader = null, + ): Results { + return new Results( + $params ?? $this->getParams(), + $search ?? $this->createMock(\VuFindSearch\Service::class), + $loader ?? $this->createMock(Loader::class) + ); + } +} diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/View/Helper/Root/AlphaBrowseTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/View/Helper/Root/AlphaBrowseTest.php index 3d06796fc09..2f61c0b9bb5 100644 --- a/module/VuFind/tests/unit-tests/src/VuFindTest/View/Helper/Root/AlphaBrowseTest.php +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/View/Helper/Root/AlphaBrowseTest.php @@ -91,7 +91,7 @@ public function testGetUrlWithMultipleRecordsAndDefaultSettings(): void ] ); $helper = $this->getHelper($url); - $item = ['heading' => 'xyzzy', 'count' => 2]; + $item = ['heading' => 'xyzzy', 'sort_key' => 'xyzzy', 'count' => 2]; $this->assertEquals('foo', $helper->getUrl('title', $item)); } @@ -112,7 +112,7 @@ public function testGetUrlWithSingleRecordAndDefaultSettings(): void ] ); $helper = $this->getHelper($url); - $item = ['heading' => 'xyzzy', 'count' => 1]; + $item = ['heading' => 'xyzzy', 'sort_key' => 'xyzzy', 'count' => 1]; $this->assertEquals('foo', $helper->getUrl('title', $item)); } @@ -131,7 +131,7 @@ public function testGetUrlEscapesQuotes(): void ] ); $helper = $this->getHelper($url); - $item = ['heading' => '"xyzzy"', 'count' => 100]; + $item = ['heading' => '"xyzzy"', 'sort_key' => '"xyzzy"', 'count' => 100]; $this->assertEquals('foo', $helper->getUrl('title', $item)); } @@ -150,7 +150,7 @@ public function testGetUrlAppliesFilterBypassSetting(): void ] ); $helper = $this->getHelper($url, ['bypass_default_filters' => false]); - $item = ['heading' => 'xyzzy', 'count' => 100]; + $item = ['heading' => 'xyzzy', 'sort_key' => 'xyzzy', 'count' => 100]; $this->assertEquals('foo', $helper->getUrl('title', $item)); } } diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/View/Helper/Root/CookieConsentTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/View/Helper/Root/CookieConsentTest.php index 382edda4fb9..6ffae3100dc 100644 --- a/module/VuFind/tests/unit-tests/src/VuFindTest/View/Helper/Root/CookieConsentTest.php +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/View/Helper/Root/CookieConsentTest.php @@ -30,7 +30,6 @@ namespace VuFindTest\View\Helper\Root; use Laminas\View\Helper\EscapeHtmlAttr; -use Laminas\View\Helper\Layout; use Laminas\View\Helper\ServerUrl; use Laminas\View\Renderer\PhpRenderer; use Symfony\Component\Yaml\Yaml; @@ -213,7 +212,7 @@ public function __invoke($template = null) }; $plugins = [ - 'escapeHtmlAttr' => new EscapeHtmlAttr(), + 'escapeHtmlAttr' => new EscapeHtmlAttr(new \VuFind\Escaper\Escaper()), 'layout' => $layout, 'serverUrl' => $serverUrl, 'url' => $url, diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/View/Helper/Root/IconTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/View/Helper/Root/IconTest.php index 9cfb89ffaac..591b2d2449e 100644 --- a/module/VuFind/tests/unit-tests/src/VuFindTest/View/Helper/Root/IconTest.php +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/View/Helper/Root/IconTest.php @@ -32,6 +32,7 @@ use Laminas\Cache\Storage\Adapter\BlackHole; use Laminas\Cache\Storage\StorageInterface; use Laminas\View\Helper\EscapeHtmlAttr; +use VuFind\Escaper\Escaper; use VuFind\View\Helper\Root\Icon; use VuFindTheme\View\Helper\ImageLink; @@ -127,12 +128,19 @@ protected function getIconHelper( array $plugins = [], $rtl = false ): Icon { + $escaper = new Escaper(); $icon = new Icon( $config ?? $this->getDefaultTestConfig(), $cache ?? new BlackHole(), - new EscapeHtmlAttr(), + new EscapeHtmlAttr($escaper), $rtl ); + $plugins = array_merge( + [ + 'escapeHtmlAttr' => new EscapeHtmlAttr($escaper), + ], + $plugins + ); $icon->setView($this->getPhpRenderer($plugins)); return $icon; } @@ -145,7 +153,7 @@ protected function getIconHelper( public function testFontIcon(): void { $helper = $this->getIconHelper(); - $expected = ''; $this->assertEquals($expected, trim($helper('foo'))); } @@ -158,7 +166,7 @@ public function testFontIcon(): void public function testFontIconWithExtraClass(): void { $helper = $this->getIconHelper(); - $expected = ''; $this->assertEquals($expected, trim($helper('classy'))); } @@ -171,12 +179,12 @@ public function testFontIconWithExtraClass(): void public function testFontIconWithExtras(): void { $helper = $this->getIconHelper(); - $expected = ''; $this->assertEquals($expected, trim($helper('foo', ['bar' => 'baz']))); // Add class to class - $expected = ''; $this->assertEquals($expected, trim($helper('foo', ['class' => 'foo-bar']))); @@ -207,14 +215,14 @@ public static function unicodeIconProvider(): array '', ], [ - 'super wry', + 'super wry', '', '1F600', 'wrySmile', 'super', ], [ - 'super wry', + 'super wry', '', '1F600', 'wrySmile', @@ -222,7 +230,7 @@ public static function unicodeIconProvider(): array ], [ 'wry', - 'foo="b+r"', + 'foo="b+r"', '1F600', 'wrySmile', [ @@ -230,8 +238,8 @@ public static function unicodeIconProvider(): array ], ], [ - 'super wry', - 'foo="b+r"', + 'super wry', + 'foo="b+r"', '1F600', 'wrySmile', [ @@ -270,8 +278,8 @@ public function testUnicodeIcons( string|array $attrs ): void { $helper = $this->getIconHelper(); - $expected = ''; $this->assertEquals($expected, trim($helper($icon, $attrs))); @@ -284,7 +292,7 @@ public function testUnicodeIcons( */ public function testCaching(): void { - $expected = ''; $key = 'foo+c0dc783820069fb9337be7366f7945bf'; @@ -314,7 +322,7 @@ public function testImageIcon(): void { $plugins = ['imageLink' => $this->getMockImageLink('icons/baz.png')]; $helper = $this->getIconHelper(null, null, $plugins); - $expected = ''; $this->assertEquals($expected, $helper('bar')); } @@ -328,7 +336,7 @@ public function testImageIconWithSpecialChars(): void { $plugins = ['imageLink' => $this->getMockImageLink('icons/"quoted".png')]; $helper = $this->getIconHelper(null, null, $plugins); - $expected = ''; $this->assertEquals($expected, $helper('quoted')); } @@ -343,7 +351,7 @@ public function testImageIconWithExtraClasses(): void { $plugins = ['imageLink' => $this->getMockImageLink('icons/zzz.png')]; $helper = $this->getIconHelper(null, null, $plugins); - $expected = ''; $this->assertEquals($expected, $helper('extraClassy')); } @@ -357,7 +365,7 @@ public function testImageIconWithExtras(): void { $plugins = ['imageLink' => $this->getMockImageLink('icons/baz.png')]; $helper = $this->getIconHelper(null, null, $plugins); - $expected = ''; // Send a string, validating the shortcut where strings are treated as // classes, in addition to confirming that extras work for image icons. @@ -374,14 +382,14 @@ public function testRTL(): void // RTL exists $plugins = ['imageLink' => $this->getMockImageLink('icons/zab.png')]; $helper = $this->getIconHelper(null, null, $plugins, true); - $expected = ''; $this->assertEquals($expected, $helper('bar')); // RTL does not exist $plugins = ['imageLink' => $this->getMockImageLink('icons/ltronly.png')]; $helper = $this->getIconHelper(null, null, $plugins, true); - $expected = ''; $this->assertEquals($expected, $helper('ltronly')); } @@ -394,7 +402,7 @@ public function testRTL(): void public function testAlias(): void { $helper = $this->getIconHelper(); - $expected = ''; // same is an alias for foo! $this->assertEquals($expected, $helper('same')); @@ -432,7 +440,7 @@ public function testSvgIcon(): void $plugins = ['imageLink' => $this->getMockImageLink('mysprites.svg')]; $helper = $this->getIconHelper(null, null, $plugins); $expected = <<