189 Commits

Author SHA1 Message Date
El RIDO
b2ef205411 extended script to test jdenticon ImageMagick and documented option to work without GD 2022-11-07 19:42:20 +01:00
El RIDO
c0758e7bbb correct labels, Jdenticon renders PNG or SVG, both in pure PHP 2022-11-07 19:09:16 +01:00
El RIDO
89d575ace3 in light of the perf/size test results of Jdenticons, switch back to Identicons as the default 2022-10-30 09:24:35 +01:00
El RIDO
1892264cf0 add perf/size test for Jdenticons 2022-10-30 09:04:27 +01:00
El RIDO
f5332ee6ff Merge pull request #1005 from PrivateBin/crowdin-translation
New Crowdin updates
2022-10-29 15:07:46 +02:00
PrivateBin Translator Bot
e39a2eeb61 New translations en.json (Corsican) 2022-10-29 13:09:30 +02:00
El RIDO
405067668a Merge pull request #1004 from PrivateBin/dependabot/composer/phpunit/phpunit-5.6.3
Bump phpunit/phpunit from 5.2.7 to 5.6.3
2022-10-29 08:17:33 +02:00
dependabot[bot]
922feb2779 Bump phpunit/phpunit from 5.2.7 to 5.6.3
Bumps [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) from 5.2.7 to 5.6.3.
- [Release notes](https://github.com/sebastianbergmann/phpunit/releases)
- [Changelog](https://github.com/sebastianbergmann/phpunit/blob/main/ChangeLog-8.5.md)
- [Commits](https://github.com/sebastianbergmann/phpunit/compare/5.2.7...5.6.3)

---
updated-dependencies:
- dependency-name: phpunit/phpunit
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-29 06:07:06 +00:00
El RIDO
968d7a19bf Merge pull request #1001 from PrivateBin/jdenticon
add new Jdenticon comment icon library and set it as default
2022-10-29 08:06:43 +02:00
El RIDO
795c030bdb Revert several commits that didn't solve the Scrutinizer issues 2022-10-26 08:16:30 +02:00
El RIDO
613e4e9d57 custom php-cs doesn't support website config on Scrutinizer 2022-10-26 08:11:35 +02:00
El RIDO
c91d20ae75 try using the updated php-cs dependency on Scrutinizer 2022-10-26 08:03:55 +02:00
El RIDO
d5104a1d63 Merge pull request #1000 from PrivateBin/set-output-deprecation
handle github actions deprecation warnings
2022-10-26 07:57:33 +02:00
El RIDO
78b3630eb5 try updating that borked php-cs dependency on Scrutinizer 2022-10-26 07:55:26 +02:00
El RIDO
31f75ee138 undo accidental PHP 8 induced upgrade of php-timer, making it incompatible with PHP 7 used on Scrutinizer 2022-10-26 07:39:35 +02:00
El RIDO
ae3a0b19ee update PHP version used in Scrutinizer CI to 7.4, fixing php-timer requirement 2022-10-26 07:23:37 +02:00
El RIDO
d5e7e6e2ab document Jdenticon change 2022-10-26 07:11:02 +02:00
El RIDO
8ac69590cf add new Jdenticon comment icon library, set it as default, fixes #793 2022-10-26 06:53:56 +02:00
El RIDO
ba4878056b misleading documentation 2022-10-26 05:51:36 +02:00
El RIDO
ae6248e27e handle github actions deprecation warnings
see: https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/
2022-10-26 05:48:51 +02:00
El RIDO
78aa70e3ab Merge pull request #999 from PrivateBin/php82-deprecated-callables
PHP 8.2 compatibility: Use of "self" in callables is deprecated
2022-10-26 04:39:11 +02:00
El RIDO
670f9ef548 Merge pull request #998 from PrivateBin/crowdin-translation
New Crowdin updates
2022-10-25 21:11:30 +02:00
PrivateBin Translator Bot
1a828884d1 New translations en.json (Hungarian) 2022-10-25 21:01:18 +02:00
PrivateBin Translator Bot
18a957ee54 New translations en.json (Hebrew) 2022-10-25 21:01:17 +02:00
El RIDO
8f8adb9b0d Merge branch 'master' into crowdin-translation 2022-10-25 20:27:22 +02:00
PrivateBin Translator Bot
c3041924b6 New translations en.json (Occitan) 2022-10-25 20:03:32 +02:00
PrivateBin Translator Bot
5584fdb347 New translations en.json (Chinese Simplified) 2022-10-25 20:03:31 +02:00
PrivateBin Translator Bot
ec75ef6e36 New translations en.json (Ukrainian) 2022-10-25 20:03:30 +02:00
PrivateBin Translator Bot
b1f24a51c8 New translations en.json (Turkish) 2022-10-25 20:03:29 +02:00
PrivateBin Translator Bot
60b091d0e1 New translations en.json (Slovak) 2022-10-25 20:03:28 +02:00
PrivateBin Translator Bot
ed5859c6b6 New translations en.json (Russian) 2022-10-25 20:03:27 +02:00
PrivateBin Translator Bot
cd0a7effa0 New translations en.json (Portuguese) 2022-10-25 20:03:25 +02:00
PrivateBin Translator Bot
ab3e8ffd49 New translations en.json (Norwegian) 2022-10-25 20:03:24 +02:00
PrivateBin Translator Bot
7269755bc2 New translations en.json (Dutch) 2022-10-25 20:03:23 +02:00
PrivateBin Translator Bot
5e48a927f2 New translations en.json (Lithuanian) 2022-10-25 20:03:22 +02:00
El RIDO
bff4d3a016 PHP 8.2 compatibility: Use of "self" in callables is deprecated 2022-10-25 07:15:09 +02:00
PrivateBin Translator Bot
2eab8adcd1 New translations en.json (Corsican) 2022-10-25 07:05:13 +02:00
PrivateBin Translator Bot
92c42afdc7 New translations en.json (Arabic) 2022-10-25 07:05:12 +02:00
PrivateBin Translator Bot
da48f8a9a1 New translations en.json (Bulgarian) 2022-10-25 07:05:11 +02:00
PrivateBin Translator Bot
7b40893497 New translations en.json (Catalan) 2022-10-25 07:05:10 +02:00
PrivateBin Translator Bot
0a1fccea11 New translations en.json (Czech) 2022-10-25 07:05:09 +02:00
PrivateBin Translator Bot
3de21004c4 New translations en.json (German) 2022-10-25 07:05:07 +02:00
PrivateBin Translator Bot
d0d0621bac New translations en.json (Greek) 2022-10-25 07:05:05 +02:00
PrivateBin Translator Bot
3845298804 New translations en.json (Finnish) 2022-10-25 07:05:04 +02:00
PrivateBin Translator Bot
d6c5f97d58 New translations en.json (Hebrew) 2022-10-25 07:05:03 +02:00
PrivateBin Translator Bot
f713b92893 New translations en.json (Hungarian) 2022-10-25 07:05:01 +02:00
PrivateBin Translator Bot
f254285b01 New translations en.json (Italian) 2022-10-25 07:05:00 +02:00
PrivateBin Translator Bot
c2d5c6fc43 New translations en.json (Japanese) 2022-10-25 07:04:59 +02:00
PrivateBin Translator Bot
3d4f9cc1b3 New translations en.json (Kurdish) 2022-10-25 07:04:58 +02:00
PrivateBin Translator Bot
945435e133 New translations en.json (Lithuanian) 2022-10-25 07:04:57 +02:00
PrivateBin Translator Bot
d288f2ab73 New translations en.json (Dutch) 2022-10-25 07:04:56 +02:00
PrivateBin Translator Bot
7ab3f3f271 New translations en.json (Spanish) 2022-10-25 07:04:54 +02:00
PrivateBin Translator Bot
019c8b64b8 New translations en.json (Norwegian) 2022-10-25 07:04:53 +02:00
PrivateBin Translator Bot
06b01c57ac New translations en.json (Portuguese) 2022-10-25 07:04:52 +02:00
PrivateBin Translator Bot
b756da5839 New translations en.json (Russian) 2022-10-25 07:04:51 +02:00
PrivateBin Translator Bot
8a9761b430 New translations en.json (Slovak) 2022-10-25 07:04:50 +02:00
PrivateBin Translator Bot
79c4ace626 New translations en.json (Slovenian) 2022-10-25 07:04:49 +02:00
PrivateBin Translator Bot
534d014254 New translations en.json (Swedish) 2022-10-25 07:04:48 +02:00
PrivateBin Translator Bot
40a037b625 New translations en.json (Turkish) 2022-10-25 07:04:47 +02:00
PrivateBin Translator Bot
5cb40e1f9e New translations en.json (Ukrainian) 2022-10-25 07:04:46 +02:00
PrivateBin Translator Bot
c9144def9f New translations en.json (Chinese Simplified) 2022-10-25 07:04:45 +02:00
PrivateBin Translator Bot
989fe6cc3a New translations en.json (Indonesian) 2022-10-25 07:04:44 +02:00
PrivateBin Translator Bot
08ac373b12 New translations en.json (Estonian) 2022-10-25 07:04:42 +02:00
PrivateBin Translator Bot
c2ec38e0a3 New translations en.json (Hindi) 2022-10-25 07:04:41 +02:00
PrivateBin Translator Bot
f31e384980 New translations en.json (Lojban) 2022-10-25 07:04:39 +02:00
PrivateBin Translator Bot
a1b4fcdefb New translations en.json (Latin) 2022-10-25 07:04:39 +02:00
PrivateBin Translator Bot
09902b9d67 New translations en.json (Occitan) 2022-10-25 07:04:38 +02:00
PrivateBin Translator Bot
f8bb6efcf9 New translations en.json (Polish) 2022-10-25 07:04:37 +02:00
PrivateBin Translator Bot
930063442a New translations en.json (French) 2022-10-25 07:04:35 +02:00
El RIDO
849c1c7cd1 fix display of configured name in twitter title 2022-10-25 06:34:40 +02:00
PrivateBin Translator Bot
5e513588db New translations en.json (German) 2022-10-25 06:08:09 +02:00
PrivateBin Translator Bot
fef83a37ef New translations en.json (Slovak) 2022-10-23 22:46:25 +02:00
El RIDO
4f9af21fb2 Merge pull request #997 from jmozd/serverside_yourls
Serverside yourls
2022-10-23 18:03:14 +02:00
El RIDO
a66f170c5e PHP 5.6 seems to tolerate an empty string as valid JSON 2022-10-23 13:21:22 +02:00
El RIDO
44f78ffcdf apply StyleCI recommendations 2022-10-23 13:14:27 +02:00
El RIDO
2776033997 apply StyleCI recommendations 2022-10-23 13:13:12 +02:00
El RIDO
0a949d3903 credit change, document it and improve wording 2022-10-23 13:10:55 +02:00
El RIDO
78e915e049 adding tests for YOURLS functionality 2022-10-23 13:09:54 +02:00
El RIDO
4bd5ef9cda add new messages to translate 2022-10-23 10:50:18 +02:00
El RIDO
69034ef9d1 apply StyleCI recommendations 2022-10-23 09:16:55 +02:00
El RIDO
2a162d075c allow unit tests to pass 2022-10-23 09:12:31 +02:00
El RIDO
f4000150fa avoid cURL dependency, native functions should suffice for such a simple call 2022-10-23 09:05:17 +02:00
El RIDO
b768a2e8cb use JSON wrapper for decoding error catching 2022-10-23 08:21:37 +02:00
El RIDO
0a2094f069 code style 2022-10-23 08:16:05 +02:00
El RIDO
0dc9ab7576 refactor shortenviayourls.php for our MVC framework 2022-10-23 08:10:56 +02:00
El RIDO
304ae76a04 Merge branch 'serverside_yourls' of https://github.com/jmozd/PrivateBin into serverside_yourls 2022-10-23 05:43:18 +02:00
El RIDO
13b4dc79b9 Merge pull request #992 from PrivateBin/zlib-1.2.13
update zlib to 1.2.13
2022-10-23 05:14:15 +02:00
El RIDO
9d07e68e7d Merge branch 'master' into zlib-1.2.13 2022-10-23 05:06:50 +02:00
Jens-U. Mozdzen
dce8b8d352 updated code formatting 2022-10-23 01:07:43 +02:00
Jens-U. Mozdzen
3115cb8883 added parameters for server-side YOURLS shortener call 2022-10-23 00:19:43 +02:00
Jens-U. Mozdzen
b0f17f0a91 added server-side YOURLS shortener call 2022-10-23 00:18:56 +02:00
El RIDO
a36351ca37 Merge pull request #994 from felixjogris/s3backend
implemented S3 storage backend
2022-10-22 18:35:03 +02:00
Felix J. Ogris
ee212b1a33 implemented S3 storage backend
added sample configuration + aws php sdk version

coding style cleanup
2022-10-22 18:30:24 +02:00
El RIDO
08b6070359 update zlib to 1.2.13 2022-10-15 09:05:19 +02:00
El RIDO
2e2c70ed15 Merge pull request #986 from PrivateBin/crowdin-translation
New Crowdin updates
2022-10-07 07:53:30 +02:00
PrivateBin Translator Bot
38245287d0 New translations en.json (Slovak) 2022-10-07 07:11:45 +02:00
El RIDO
688574f70c Merge pull request #988 from RaJiska/master
GCS Uniform ACL Support
2022-10-07 05:58:54 +02:00
Ra'Jiska
8dbe60621d Fix GCS Upload Metadata Mistake 2022-10-06 14:41:37 +08:00
PrivateBin Translator Bot
23e0000101 New translations en.json (Greek) 2022-10-06 08:30:59 +02:00
PrivateBin Translator Bot
1aa93f7fb2 New translations en.json (Slovak) 2022-10-06 08:30:58 +02:00
Ra'Jiska
8dded4e8e4 GCS Support for Uniform ACL Buckets 2022-10-06 12:19:06 +08:00
PrivateBin Translator Bot
ad6e802e8a New translations en.json (Greek) 2022-10-05 12:30:02 +02:00
PrivateBin Translator Bot
c6659747ae New translations en.json (Greek) 2022-10-05 09:01:55 +02:00
El RIDO
160207b25a Merge pull request #983 from PrivateBin/slovak
enable use of Slovak translations
2022-09-30 05:24:21 +02:00
El RIDO
77409e6065 crediting greek language as well, plus docs 2022-09-29 21:15:00 +02:00
El RIDO
abef3ad37b Merge branch 'master' into slovak 2022-09-29 21:10:50 +02:00
El RIDO
0e630e14b7 Merge pull request #985 from eellak/greek-lang
Update strings in el.json and enable greek language
2022-09-29 21:06:18 +02:00
Christos Karamolegkos
0f1c2fdb04 Update strings in el.json and enable greek language 2022-09-29 15:34:15 +03:00
El RIDO
b61b4253a6 enabled use of Slovak translations 2022-09-29 05:34:49 +02:00
R4SAS
afd84da6b2 Merge pull request #981 from PrivateBin/crowdin-translation
New Crowdin updates
2022-09-26 10:15:30 +00:00
PrivateBin Translator Bot
51f5082f39 New translations en.json (Slovak) 2022-09-25 17:09:10 +02:00
PrivateBin Translator Bot
1b727f7fa4 New translations en.json (Slovak) 2022-09-25 16:12:48 +02:00
El RIDO
4ea2d6020f Merge pull request #979 from PrivateBin/crowdin-translation
New Crowdin updates
2022-09-24 10:05:58 +02:00
PrivateBin Translator Bot
3255a4d954 New translations en.json (Slovak) 2022-09-20 21:21:45 +02:00
PrivateBin Translator Bot
4025619236 New translations en.json (Slovak) 2022-09-20 20:11:36 +02:00
El RIDO
eef9a52b69 Merge pull request #968 from PrivateBin/crowdin-translation
New Crowdin updates
2022-09-18 14:44:56 +02:00
PrivateBin Translator Bot
7580155243 New translations en.json (Slovak) 2022-09-18 14:19:01 +02:00
PrivateBin Translator Bot
49b8312505 New translations en.json (Dutch) 2022-09-18 14:19:00 +02:00
PrivateBin Translator Bot
d1b53360d5 New translations en.json (Slovak) 2022-09-18 13:16:32 +02:00
PrivateBin Translator Bot
e35710ca30 New translations en.json (Turkish) 2022-09-18 13:16:31 +02:00
PrivateBin Translator Bot
123210bb8f New translations en.json (Dutch) 2022-09-18 13:16:30 +02:00
PrivateBin Translator Bot
e11c89ab85 New translations en.json (Czech) 2022-09-16 23:59:49 +02:00
PrivateBin Translator Bot
e56b24fc3b New translations en.json (Turkish) 2022-09-10 22:29:16 +02:00
PrivateBin Translator Bot
76fe7063ca New translations en.json (Dutch) 2022-09-10 22:29:14 +02:00
PrivateBin Translator Bot
fa1e4728dc New translations en.json (Russian) 2022-09-10 21:32:33 +02:00
PrivateBin Translator Bot
d57a849a40 New translations en.json (Dutch) 2022-09-10 21:32:32 +02:00
PrivateBin Translator Bot
d065f42785 New translations en.json (Czech) 2022-09-10 14:08:53 +02:00
PrivateBin Translator Bot
d848ce2ed2 New translations en.json (Ukrainian) 2022-08-30 00:53:01 +02:00
El RIDO
ac06627d9f Merge pull request #971 from PrivateBin/coop-header-disable
Remove COOP header for now
2022-08-24 06:28:09 +02:00
rugk
e740d0f761 Remove COOP header for now
Same as https://github.com/PrivateBin/docker-nginx-fpm-alpine/pull/108

Disable the header here as it breaks links to the own site.
2022-08-22 13:25:56 +02:00
PrivateBin Translator Bot
6e60efc2dd New translations en.json (Dutch) 2022-08-21 17:21:33 +02:00
PrivateBin Translator Bot
a105ba7566 New translations en.json (Turkish) 2022-08-21 17:21:28 +02:00
PrivateBin Translator Bot
67365f8602 New translations en.json (Ukrainian) 2022-08-21 17:21:27 +02:00
El RIDO
628700afb1 Merge pull request #960 from PrivateBin/crowdin-translation
New Crowdin updates
2022-07-23 07:23:55 +02:00
PrivateBin Translator Bot
5c20a424e1 New translations en.json (Lithuanian) 2022-07-22 14:07:48 +02:00
El RIDO
1a5eafe424 Merge pull request #958 from PrivateBin/crowdin-translation
New Crowdin updates
2022-07-16 09:33:14 +02:00
PrivateBin Translator Bot
925f8cb338 New translations en.json (Spanish) 2022-07-14 09:41:47 +02:00
PrivateBin Translator Bot
9e499652be New translations en.json (Spanish) 2022-07-14 08:34:07 +02:00
El RIDO
61efe71e31 Merge pull request #957 from PrivateBin/nodejs-16-tests
Use NodeJs v16 for tests
2022-07-10 10:31:02 +02:00
rugk
48bb2fdf0f Use NodeJs v16 for tests
So 14 worked, let's try 16. (Actually noticed fedora uses v16 not 14 which makes sense if you see the support time.)
2022-07-10 00:13:47 +02:00
El RIDO
b46b4300ec Merge pull request #955 from PrivateBin/node14
chore: run tests with NodeJS 14
2022-07-09 17:45:23 +02:00
El RIDO
67fdd54ae5 Merge pull request #954 from PrivateBin/packagelock
Add missing package-json.lock
2022-07-09 17:41:39 +02:00
rugk
e536db9b7e style: run tests via npm script insread of custom command
I.e. not call mocha directly but let the script defined in package.json do it's job.
2022-07-09 17:04:28 +02:00
rugk
9a476ac34d chore: switch to proper cache file now we have it, i.e. package-lock.json
as per https://github.com/actions/setup-node#caching-global-packages-data
2022-07-09 17:00:45 +02:00
rugk
79fd33d21f chore: run tests with NodeJS 14
I expect no stuff to break or so, so let's just try to use the current recommend LTS version. (v14 will also die at some time, but Fedora e.g. still seems to use it for now by default. Likely we may upgrade soon even more.)

Ref https://nodejs.org/en/about/releases/
2022-07-09 16:57:06 +02:00
rugk
08946d1cab Use npm ci instead of npm install for tests in CI
So it uses the package-json.lock file actually.
2022-07-09 16:48:21 +02:00
rugk
29b8215332 Add missing package-json.lock
NodeJS v16.14.0
2022-07-09 16:44:35 +02:00
El RIDO
f9d78d62a0 Merge pull request #945 from PrivateBin/mysql-index-exists
CREATE INDEX IF NOT EXISTS is not supported as of MySQL <= 8.0
2022-06-30 06:43:36 +02:00
El RIDO
e6d606ba88 clarify that it is only unsupported by Oracle MySQL, while supported in MariaDB, Postgres, SQLite, ... 2022-06-29 22:25:54 +02:00
El RIDO
4ad4aed875 apply table prefix to indexes as well, to support multiple instances sharing a single database 2022-06-28 06:51:21 +02:00
El RIDO
b7cffbddd0 CREATE INDEX IF NOT EXISTS is not supported as of MySQL <= 8.0, fixes #943 2022-06-27 19:05:57 +02:00
El RIDO
9b132f4054 Merge pull request #941 from PrivateBin/dependabot/github_actions/actions/checkout-3
Bump actions/checkout from 2 to 3
2022-06-10 05:04:21 +02:00
El RIDO
e052dd9d83 Merge pull request #940 from PrivateBin/dependabot/github_actions/actions/cache-3
Bump actions/cache from 2 to 3
2022-06-10 05:03:19 +02:00
dependabot[bot]
b6f35fc8ab Bump actions/checkout from 2 to 3
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-10 03:02:26 +00:00
El RIDO
9291e57ac6 Merge pull request #939 from PrivateBin/dependabot/github_actions/github/codeql-action-2
Bump github/codeql-action from 1 to 2
2022-06-10 05:02:04 +02:00
El RIDO
9df0faf7b8 Merge pull request #938 from PrivateBin/dependabot/github_actions/actions/setup-node-3
Bump actions/setup-node from 2 to 3
2022-06-10 05:01:17 +02:00
dependabot[bot]
be23ae2874 Bump actions/cache from 2 to 3
Bumps [actions/cache](https://github.com/actions/cache) from 2 to 3.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-10 02:59:25 +00:00
dependabot[bot]
86794be1c4 Bump github/codeql-action from 1 to 2
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 1 to 2.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v1...v2)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-10 02:59:19 +00:00
dependabot[bot]
48a6bf4416 Bump actions/setup-node from 2 to 3
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 2 to 3.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-10 02:59:15 +00:00
El RIDO
b75bfc3e34 Merge pull request #937 from PrivateBin/dependabot
Add/enable Dependabot to keep dependencies up-to-date
2022-06-10 04:58:52 +02:00
rugk
3c8c32fbcb Add/enable Dependabot to keep dependencies up-to-date
Enabled update checking via Dependabot for
* GitHub Actions for updates or in case you are going to use that (just my default)
* PHP aka Composer

We successfully use it [for our Docker container](https://github.com/PrivateBin/docker-nginx-fpm-alpine/blob/master/.github/dependabot.yml) and IMHO that has worked quite well, so IMHO, we can also use it here now.
2022-06-09 23:42:03 +02:00
El RIDO
0ebac188a8 Merge pull request #936 from AnonymousWP/master
Add missing Dutch translations
2022-06-08 20:28:52 +02:00
AnonymousWP
dad1c5dc5b Add Dutch translations
Also fixed Dutch spelling and grammar mistakes.
2022-06-08 11:32:16 +02:00
El RIDO
c3302b5f8d Merge pull request #935 from PeGaSuS-Coder/patch-1
Update conf.sample.php
2022-06-06 07:36:21 +02:00
PeGaSuS
f8ff49509b Update conf.sample.php
Fixed typo to match the mysql database name
2022-06-05 18:42:54 +02:00
PeGaSuS
6d748de33a Update conf.sample.php
Added an working PostgreSQL database configuration.
2022-06-05 18:41:09 +02:00
El RIDO
d9857017de Merge pull request #932 from PrivateBin/php-8.1-deprecations
addressing deprecation warnings in php 8.1
2022-06-02 19:21:46 +02:00
El RIDO
07a23d7f0b addressing deprecation warnings in php 8.1
Deprecated: strlen(): Passing null to parameter #1 ($string) of type string is deprecated in lib/Data/Database.php on line 280 & 555
2022-06-01 21:05:08 +02:00
El RIDO
4487171263 Merge pull request #926 from PrivateBin/crowdin-translation
New Crowdin updates
2022-05-02 19:21:58 +02:00
PrivateBin Translator Bot
3ae97cde7a New translations en.json (Catalan) 2022-05-02 00:14:26 +02:00
El RIDO
e1b5d277c7 Merge pull request #925 from MTBBK/patch-1
Update tr.json
2022-04-30 09:45:20 +02:00
El RIDO
c065a4b739 Merge pull request #924 from PrivateBin/enable-turkish
Enable Turkish translation
2022-04-30 09:22:47 +02:00
MTBBK
b6fc4bd19d Update tr.json 2022-04-28 23:00:31 +03:00
El RIDO
f717334ee0 - credit & document Turkish translation
- remove plural indicators
- add plural logic and enable Turkish translation
2022-04-28 20:05:57 +02:00
El RIDO
2c01892ee1 bump github/codeql-action from 1 to 2 2022-04-28 19:47:28 +02:00
El RIDO
14440630bb Merge pull request #923 from rahmanlar/patch-1
Update Turkish localization
2022-04-28 19:44:23 +02:00
Emir Ensar Rahmanlar
b023351991 Update Turkish localization 2022-04-28 11:37:27 +03:00
El RIDO
d0142c31cc Merge pull request #920 from PrivateBin/mysql_ansi_quotes
Avoid SUPER privilege when setting the sql_mode for MariaDB/MySQL, fixes #919
2022-04-20 19:56:24 +02:00
El RIDO
0e2ec27033 Avoid privilege for setting the for MariaDB/MySQL, fixes #919 2022-04-19 18:44:00 +02:00
El RIDO
a15b395eaf fix php8 refresh workflow
requires updating the commit action due to
https://github.com/github-actions-x/commit/issues/30
2022-04-14 06:19:19 +02:00
El RIDO
15a9b8d826 fix php8 refresh workflow
requires updating the commit action due to
https://github.com/github-actions-x/commit/issues/30
2022-04-14 06:17:10 +02:00
El RIDO
d2126d6dd6 fix php8 refresh workflow
requires updating the commit action due to
https://github.com/github-actions-x/commit/issues/30
2022-04-14 06:08:48 +02:00
El RIDO
62b14ae20f Merge pull request #916 from HLeithner/patch-1
Replace FLoC Header with browsing-topics header
2022-04-11 19:09:00 +02:00
Harald Leithner
4b3d11c988 Add browsing-topics premission policy 2022-04-10 11:28:52 +02:00
El RIDO
81b8620006 Merge pull request #915 from PrivateBin/crowdin-translation
New Crowdin updates
2022-04-10 11:16:18 +02:00
Harald Leithner
7b8e031ab5 Remove FLoC Header
Google announced that it is discontinuing FLoC.
2022-04-10 10:36:39 +02:00
PrivateBin Translator Bot
cdea2917c1 New translations en.json (Chinese Simplified) 2022-04-10 09:43:11 +02:00
El RIDO
38b419945c Merge pull request #914 from imtms/master
Update zh translation
2022-04-09 19:55:59 +02:00
TMs
ba7bcbbb62 update zh translation 2022-04-09 22:42:47 +08:00
112 changed files with 10516 additions and 674 deletions

14
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,14 @@
version: 2
updates:
# Maintain dependencies for GitHub Actions
# src: https://github.com/marketplace/actions/build-and-push-docker-images#keep-up-to-date-with-github-dependabot
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
# Also keep PHP (Composer) dependencies up-to-date
# see: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem
- package-ecosystem: "composer"
directory: "/"
schedule:
interval: "daily"

View File

@@ -33,11 +33,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -46,4 +46,4 @@ jobs:
# queries: ./path/to/local/query, your-org/your-repo/queries@main
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v2

View File

@@ -2,9 +2,9 @@ name: Refresh PHP 8 branch
on:
push:
branches: [ master ]
branches: [ master ]
schedule:
- cron: '42 2 * * *'
- cron: '42 2 * * *'
workflow_dispatch:
jobs:
@@ -12,8 +12,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout php8 branch
uses: actions/checkout@v2
- name: Checkout php8 branch
uses: actions/checkout@v3
with:
# directly checkout the php8 branch
ref: php8
@@ -28,7 +28,7 @@ jobs:
git merge origin/master
- name: Push new changes
uses: github-actions-x/commit@v2.8
uses: github-actions-x/commit@v2.9
with:
name: github-actions[bot]
email: 41898282+github-actions[bot]@users.noreply.github.com

View File

@@ -13,7 +13,7 @@ jobs:
snyk-php:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: actions/checkout@v3
- name: Install Google Cloud Storage
run: composer require --no-update google/cloud-storage && composer update --no-dev
- name: Run Snyk to check for vulnerabilities
@@ -24,6 +24,6 @@ jobs:
with:
args: --sarif-file-output=snyk.sarif
- name: Upload result to GitHub Code Scanning
uses: github/codeql-action/upload-sarif@v1
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: snyk.sarif

View File

@@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Validate composer.json and composer.lock
run: composer validate
- name: Install dependencies
@@ -27,7 +27,7 @@ jobs:
# let's get started!
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
# cache PHP extensions
- name: Setup cache environment
@@ -39,7 +39,7 @@ jobs:
key: ${{ runner.os }}-${{ env.extensions-cache-key }}
- name: Cache extensions
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: ${{ steps.extcache.outputs.dir }}
key: ${{ steps.extcache.outputs.key }}
@@ -65,18 +65,17 @@ jobs:
- name: Get composer cache directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
# http://man7.org/linux/man-pages/man1/date.1.html
# https://github.com/actions/cache#creating-a-cache-key
- name: Get Date
id: get-date
run: |
echo "::set-output name=date::$(/bin/date -u "+%Y%m%d")"
run: echo "date=$(/bin/date -u "+%Y%m%d")" >> $GITHUB_OUTPUT
shell: bash
- name: Cache dependencies
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ steps.get-date.outputs.date }}-${{ hashFiles('**/composer.json') }}
@@ -99,23 +98,23 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: '12'
node-version: '16'
cache: 'npm'
cache-dependency-path: 'js/package.json'
cache-dependency-path: 'js/package-lock.json'
- name: Setup Mocha
run: npm install -g mocha
- name: Setup Node modules
run: npm install
run: npm ci
working-directory: js
- name: Run unit tests
run: mocha
run: npm test
working-directory: js

View File

@@ -1,5 +1,14 @@
# PrivateBin version history
* **1.4.1 (not yet released)**
* ADDED: Translations for Turkish, Slovak and Greek
* ADDED: S3 Storage backend (#994)
* ADDED: Jdenticons as an option for comment icons (#793)
* CHANGED: Avoid `SUPER` privilege for setting the `sql_mode` for MariaDB/MySQL (#919)
* CHANGED: Upgrading libraries to: zlib 1.2.13
* FIXED: Revert to CREATE INDEX without IF NOT EXISTS clauses, to support MySQL (#943)
* FIXED: Apply table prefix to indexes as well, to support multiple instances sharing a single database (#943)
* FIXED: YOURLS integration via new proxy, storing signature in configuration (#725)
* **1.4 (2022-04-09)**
* ADDED: Translations for Corsican, Estonian, Finnish and Lojban
* ADDED: new HTTP headers improving security (#765)

View File

@@ -29,6 +29,8 @@
* rodehoed - option to exempt ips from the rate-limiter
* Mark van Holsteijn - Google Cloud Storage backend
* Austin Huang - Oracle database support
* Felix J. Ogris - S3 Storage backend
* Mounir Idrassi & J. Mozdzen - secure YOURLS integration
## Translations
* Hexalyse - French
@@ -56,3 +58,6 @@
* foxsouns - Lojban
* Patriccollu di Santa Maria è Sichè - Corsican
* Markus Mikkonen - Finnish
* Emir Ensar Rahmanlar - Turkish
* Stevo984 - Slovak
* Christos Karamolegkos - Greek

View File

@@ -26,7 +26,7 @@ install and configure PrivateBin on your server. It's available on
- `open_basedir` access to `/dev/urandom`
- mcrypt extension AND `open_basedir` access to `/dev/urandom`
- com_dotnet extension
- GD extension
- GD extension (when using identicon or vizhash icons, jdenticon works without it)
- zlib extension
- some disk space or a database supported by [PDO](https://php.net/manual/book.pdo.php)
- ability to create files and folders in the installation directory and the PATH
@@ -232,3 +232,42 @@ Platform using Google Cloud Run is easy and cheap.
To use the Google Cloud Storage backend you have to install the suggested
library using the command `composer require google/cloud-storage`.
#### Using S3 Storage
Similar to Google Cloud Storage, you can choose S3 as storage backend. It uses
the AWS SDK for PHP, but can also talk to a Rados gateway as part of a CEPH
cluster. To use this backend, you first have to install the SDK in the
document root of PrivateBin: `composer require aws/aws-sdk-php`. You have to
create the S3 bucket on the CEPH cluster before using the S3 backend.
In the `[model]` section of cfg/conf.php, set `class` to `S3Storage`.
You can set any combination of the following options in the `[model_options]`
section:
* region
* version
* endpoint
* bucket
* prefix
* accesskey
* secretkey
* use_path_style_endpoint
By default, prefix is empty. If set, the S3 backend will place all PrivateBin
data beneath this prefix.
For AWS, you have to provide at least `region`, `bucket`, `accesskey`, and
`secretkey`.
For CEPH, follow this example:
```
region = ""
version = "2006-03-01"
endpoint = "https://s3.my-ceph.invalid"
use_path_style_endpoint = true
bucket = "my-bucket"
accesskey = "my-rados-user"
secretkey = "my-rados-pass"
```

View File

@@ -65,10 +65,11 @@ languageselection = false
; qrcode = true
; (optional) IP based icons are a weak mechanism to detect if a comment was from
; a different user when the same username was used in a comment. It might be
; used to get the IP of a non anonymous comment poster if the server salt is
; leaked and a SHA256 HMAC rainbow table is generated for all (relevant) IPs.
; Can be set to one these values: "none" / "vizhash" / "identicon" (default).
; a different user when the same username was used in a comment. It might get
; used to get the IP of a comment poster if the server salt is leaked and a
; SHA512 HMAC rainbow table is generated for all (relevant) IPs.
; Can be set to one these values:
; "none" / "identicon" (default) / "jdenticon" / "vizhash".
; icon = "none"
; Content Security Policy headers allow a website to restrict what sources are
@@ -175,6 +176,7 @@ dir = PATH "data"
;[model_options]
;bucket = "my-private-bin"
;prefix = "pastes"
;uniformacl = false
;[model]
; example of DB configuration for MySQL
@@ -194,3 +196,52 @@ dir = PATH "data"
;usr = null
;pwd = null
;opt[12] = true ; PDO::ATTR_PERSISTENT
;[model]
; example of DB configuration for PostgreSQL
;class = Database
;[model_options]
;dsn = "pgsql:host=localhost;dbname=privatebin"
;tbl = "privatebin_" ; table prefix
;usr = "privatebin"
;pwd = "Z3r0P4ss"
;opt[12] = true ; PDO::ATTR_PERSISTENT
;[model]
; example of S3 configuration for Rados gateway / CEPH
;class = S3Storage
;[model_options]
;region = ""
;version = "2006-03-01"
;endpoint = "https://s3.my-ceph.invalid"
;use_path_style_endpoint = true
;bucket = "my-bucket"
;accesskey = "my-rados-user"
;secretkey = "my-rados-pass"
;[model]
; example of S3 configuration for AWS
;class = S3Storage
;[model_options]
;region = "eu-central-1"
;version = "latest"
;bucket = "my-bucket"
;accesskey = "access key id"
;secretkey = "secret access key"
[yourls]
; don't mix this up with "urlshortener" config item:
; - when using a standard configuration, "urlshortener" will point to the YOURLS
; API, including access credentials, and will be part of the PrivateBin public
; web page (insecure!)
; - when using the parameters in this section ("signature" and "apiurl"),
; "urlshortener" will point to a fixed PrivateBin page
; ("$basepath/shortenviayourls?link=") and that URL will in turn call YOURLS
; server-side, using the URL from "apiurl" and the "access signature" from the
; "signature" parameters below.
; (optional) the "signature" (access key) issued by YOURLS for the using account
; signature = ""
; (optional) the URL of the YOURLS API, called to shorten a PrivateBin URL
; apiurl = ""

View File

@@ -27,10 +27,12 @@
"php" : "^5.6.0 || ^7.0 || ^8.0",
"paragonie/random_compat" : "2.0.21",
"yzalis/identicon" : "2.0.0",
"mlocati/ip-lib" : "1.18.0"
"mlocati/ip-lib" : "1.18.0",
"jdenticon/jdenticon": "^1.0"
},
"suggest" : {
"google/cloud-storage" : "1.26.1"
"google/cloud-storage" : "1.26.1",
"aws/aws-sdk-php" : "3.239.0"
},
"require-dev" : {
"phpunit/phpunit" : "^4.6 || ^5.0"

469
composer.lock generated
View File

@@ -4,8 +4,57 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "fa52d4988bfe17d4b27e3a4789a1ec49",
"content-hash": "17bceced29627163f7aa330a0697f68b",
"packages": [
{
"name": "jdenticon/jdenticon",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/dmester/jdenticon-php.git",
"reference": "994ee07293fb978f983393ffcb2c0250592a6ac4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dmester/jdenticon-php/zipball/994ee07293fb978f983393ffcb2c0250592a6ac4",
"reference": "994ee07293fb978f983393ffcb2c0250592a6ac4",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "^5.7"
},
"type": "library",
"autoload": {
"psr-4": {
"Jdenticon\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Daniel Mester Pirttijärvi"
}
],
"description": "Render PNG and SVG identicons.",
"homepage": "https://jdenticon.com/",
"keywords": [
"avatar",
"identicon",
"jdenticon"
],
"support": {
"docs": "https://jdenticon.com/php-api.html",
"issues": "https://github.com/dmester/jdenticon-php/issues",
"source": "https://github.com/dmester/jdenticon-php"
},
"time": "2022-07-02T11:03:15+00:00"
},
{
"name": "mlocati/ip-lib",
"version": "1.18.0",
@@ -61,6 +110,10 @@
"range",
"subnet"
],
"support": {
"issues": "https://github.com/mlocati/ip-lib/issues",
"source": "https://github.com/mlocati/ip-lib/tree/1.18.0"
},
"funding": [
{
"url": "https://github.com/sponsors/mlocati",
@@ -120,6 +173,11 @@
"pseudorandom",
"random"
],
"support": {
"email": "info@paragonie.com",
"issues": "https://github.com/paragonie/random_compat/issues",
"source": "https://github.com/paragonie/random_compat"
},
"time": "2022-02-16T17:07:03+00:00"
},
{
@@ -172,6 +230,10 @@
"identicon",
"image"
],
"support": {
"issues": "https://github.com/yzalis/Identicon/issues",
"source": "https://github.com/yzalis/Identicon/tree/master"
},
"abandoned": true,
"time": "2019-10-14T09:30:57+00:00"
}
@@ -179,31 +241,34 @@
"packages-dev": [
{
"name": "doctrine/instantiator",
"version": "1.4.0",
"version": "1.0.5",
"source": {
"type": "git",
"url": "https://github.com/doctrine/instantiator.git",
"reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b"
"reference": "8e884e78f9f0eb1329e445619e04456e64d8051d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b",
"reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b",
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d",
"reference": "8e884e78f9f0eb1329e445619e04456e64d8051d",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0"
"php": ">=5.3,<8.0-DEV"
},
"require-dev": {
"doctrine/coding-standard": "^8.0",
"athletic/athletic": "~0.1.8",
"ext-pdo": "*",
"ext-phar": "*",
"phpbench/phpbench": "^0.13 || 1.0.0-alpha2",
"phpstan/phpstan": "^0.12",
"phpstan/phpstan-phpunit": "^0.12",
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
"phpunit/phpunit": "~4.0",
"squizlabs/php_codesniffer": "~2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
@@ -217,55 +282,42 @@
{
"name": "Marco Pivetta",
"email": "ocramius@gmail.com",
"homepage": "https://ocramius.github.io/"
"homepage": "http://ocramius.github.com/"
}
],
"description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
"homepage": "https://www.doctrine-project.org/projects/instantiator.html",
"homepage": "https://github.com/doctrine/instantiator",
"keywords": [
"constructor",
"instantiate"
],
"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%2Finstantiator",
"type": "tidelift"
}
],
"time": "2020-11-10T18:47:58+00:00"
"support": {
"issues": "https://github.com/doctrine/instantiator/issues",
"source": "https://github.com/doctrine/instantiator/tree/master"
},
"time": "2015-06-14T21:17:01+00:00"
},
{
"name": "myclabs/deep-copy",
"version": "1.10.2",
"version": "1.7.0",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
"reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220"
"reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220",
"reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e",
"reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0"
},
"replace": {
"myclabs/deep-copy": "self.version"
"php": "^5.6 || ^7.0"
},
"require-dev": {
"doctrine/collections": "^1.0",
"doctrine/common": "^2.6",
"phpunit/phpunit": "^7.1"
"phpunit/phpunit": "^4.1"
},
"type": "library",
"autoload": {
@@ -288,40 +340,43 @@
"object",
"object graph"
],
"funding": [
{
"url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
"type": "tidelift"
}
],
"time": "2020-11-13T09:40:50+00:00"
"support": {
"issues": "https://github.com/myclabs/DeepCopy/issues",
"source": "https://github.com/myclabs/DeepCopy/tree/1.x"
},
"time": "2017-10-19T19:58:43+00:00"
},
{
"name": "phpdocumentor/reflection-common",
"version": "2.2.0",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionCommon.git",
"reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b"
"reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b",
"reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
"reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
"php": ">=5.5"
},
"require-dev": {
"phpunit/phpunit": "^4.6"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-2.x": "2.x-dev"
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"phpDocumentor\\Reflection\\": "src/"
"phpDocumentor\\Reflection\\": [
"src"
]
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -343,42 +398,42 @@
"reflection",
"static analysis"
],
"time": "2020-06-27T09:03:43+00:00"
"support": {
"issues": "https://github.com/phpDocumentor/ReflectionCommon/issues",
"source": "https://github.com/phpDocumentor/ReflectionCommon/tree/master"
},
"time": "2017-09-11T18:02:19+00:00"
},
{
"name": "phpdocumentor/reflection-docblock",
"version": "5.3.0",
"version": "3.3.2",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
"reference": "622548b623e81ca6d78b721c5e029f4ce664f170"
"reference": "bf329f6c1aadea3299f08ee804682b7c45b326a2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170",
"reference": "622548b623e81ca6d78b721c5e029f4ce664f170",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/bf329f6c1aadea3299f08ee804682b7c45b326a2",
"reference": "bf329f6c1aadea3299f08ee804682b7c45b326a2",
"shasum": ""
},
"require": {
"ext-filter": "*",
"php": "^7.2 || ^8.0",
"phpdocumentor/reflection-common": "^2.2",
"phpdocumentor/type-resolver": "^1.3",
"webmozart/assert": "^1.9.1"
"php": "^5.6 || ^7.0",
"phpdocumentor/reflection-common": "^1.0.0",
"phpdocumentor/type-resolver": "^0.4.0",
"webmozart/assert": "^1.0"
},
"require-dev": {
"mockery/mockery": "~1.3.2",
"psalm/phar": "^4.8"
"mockery/mockery": "^0.9.4",
"phpunit/phpunit": "^4.4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.x-dev"
}
},
"autoload": {
"psr-4": {
"phpDocumentor\\Reflection\\": "src"
"phpDocumentor\\Reflection\\": [
"src/"
]
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -389,46 +444,48 @@
{
"name": "Mike van Riel",
"email": "me@mikevanriel.com"
},
{
"name": "Jaap van Otterdijk",
"email": "account@ijaap.nl"
}
],
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
"time": "2021-10-19T17:43:47+00:00"
"support": {
"issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
"source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/release/3.x"
},
"time": "2017-11-10T14:09:06+00:00"
},
{
"name": "phpdocumentor/type-resolver",
"version": "1.6.0",
"version": "0.4.0",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/TypeResolver.git",
"reference": "93ebd0014cab80c4ea9f5e297ea48672f1b87706"
"reference": "9c977708995954784726e25d0cd1dddf4e65b0f7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/93ebd0014cab80c4ea9f5e297ea48672f1b87706",
"reference": "93ebd0014cab80c4ea9f5e297ea48672f1b87706",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7",
"reference": "9c977708995954784726e25d0cd1dddf4e65b0f7",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0",
"phpdocumentor/reflection-common": "^2.0"
"php": "^5.5 || ^7.0",
"phpdocumentor/reflection-common": "^1.0"
},
"require-dev": {
"ext-tokenizer": "*",
"psalm/phar": "^4.8"
"mockery/mockery": "^0.9.4",
"phpunit/phpunit": "^5.2||^4.8.24"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-1.x": "1.x-dev"
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"phpDocumentor\\Reflection\\": "src"
"phpDocumentor\\Reflection\\": [
"src/"
]
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -441,8 +498,11 @@
"email": "me@mikevanriel.com"
}
],
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
"time": "2022-01-04T19:58:01+00:00"
"support": {
"issues": "https://github.com/phpDocumentor/TypeResolver/issues",
"source": "https://github.com/phpDocumentor/TypeResolver/tree/master"
},
"time": "2017-07-14T14:27:02+00:00"
},
{
"name": "phpspec/prophecy",
@@ -505,6 +565,10 @@
"spy",
"stub"
],
"support": {
"issues": "https://github.com/phpspec/prophecy/issues",
"source": "https://github.com/phpspec/prophecy/tree/v1.10.3"
},
"time": "2020-03-05T15:02:03+00:00"
},
{
@@ -568,6 +632,11 @@
"testing",
"xunit"
],
"support": {
"irc": "irc://irc.freenode.net/phpunit",
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/4.0"
},
"time": "2017-04-02T07:44:40+00:00"
},
{
@@ -615,6 +684,11 @@
"filesystem",
"iterator"
],
"support": {
"irc": "irc://irc.freenode.net/phpunit",
"issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
"source": "https://github.com/sebastianbergmann/php-file-iterator/tree/1.4.5"
},
"time": "2017-11-27T13:52:08+00:00"
},
{
@@ -656,6 +730,10 @@
"keywords": [
"template"
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-text-template/issues",
"source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1"
},
"time": "2015-06-21T13:50:34+00:00"
},
{
@@ -705,33 +783,37 @@
"keywords": [
"timer"
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-timer/issues",
"source": "https://github.com/sebastianbergmann/php-timer/tree/master"
},
"time": "2017-02-26T11:10:40+00:00"
},
{
"name": "phpunit/php-token-stream",
"version": "2.0.2",
"version": "1.4.12",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-token-stream.git",
"reference": "791198a2c6254db10131eecfe8c06670700904db"
"reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db",
"reference": "791198a2c6254db10131eecfe8c06670700904db",
"url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1ce90ba27c42e4e44e6d8458241466380b51fa16",
"reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16",
"shasum": ""
},
"require": {
"ext-tokenizer": "*",
"php": "^7.0"
"php": ">=5.3.3"
},
"require-dev": {
"phpunit/phpunit": "^6.2.4"
"phpunit/phpunit": "~4.2"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0-dev"
"dev-master": "1.4-dev"
}
},
"autoload": {
@@ -754,21 +836,25 @@
"keywords": [
"tokenizer"
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-token-stream/issues",
"source": "https://github.com/sebastianbergmann/php-token-stream/tree/1.4"
},
"abandoned": true,
"time": "2017-11-27T05:48:46+00:00"
"time": "2017-12-04T08:55:13+00:00"
},
{
"name": "phpunit/phpunit",
"version": "5.7.27",
"version": "5.6.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c"
"reference": "a9de0dbafeb6b1391b391fbb034734cb0af9f67c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c",
"reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a9de0dbafeb6b1391b391fbb034734cb0af9f67c",
"reference": "a9de0dbafeb6b1391b391fbb034734cb0af9f67c",
"shasum": ""
},
"require": {
@@ -779,21 +865,21 @@
"ext-xml": "*",
"myclabs/deep-copy": "~1.3",
"php": "^5.6 || ^7.0",
"phpspec/prophecy": "^1.6.2",
"phpunit/php-code-coverage": "^4.0.4",
"phpspec/prophecy": "^1.3.1",
"phpunit/php-code-coverage": "^4.0.1",
"phpunit/php-file-iterator": "~1.4",
"phpunit/php-text-template": "~1.2",
"phpunit/php-timer": "^1.0.6",
"phpunit/phpunit-mock-objects": "^3.2",
"sebastian/comparator": "^1.2.4",
"sebastian/diff": "^1.4.3",
"sebastian/environment": "^1.3.4 || ^2.0",
"sebastian/exporter": "~2.0",
"sebastian/global-state": "^1.1",
"sebastian/object-enumerator": "~2.0",
"sebastian/comparator": "~1.1",
"sebastian/diff": "~1.2",
"sebastian/environment": "^1.3 || ^2.0",
"sebastian/exporter": "~1.2",
"sebastian/global-state": "~1.0",
"sebastian/object-enumerator": "~1.0",
"sebastian/resource-operations": "~1.0",
"sebastian/version": "^1.0.6|^2.0.1",
"symfony/yaml": "~2.1|~3.0|~4.0"
"sebastian/version": "~1.0|~2.0",
"symfony/yaml": "~2.1|~3.0"
},
"conflict": {
"phpdocumentor/reflection-docblock": "3.0.2"
@@ -811,7 +897,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.7.x-dev"
"dev-master": "5.6.x-dev"
}
},
"autoload": {
@@ -837,7 +923,11 @@
"testing",
"xunit"
],
"time": "2018-02-01T05:50:59+00:00"
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"source": "https://github.com/sebastianbergmann/phpunit/tree/5.6.3"
},
"time": "2016-11-14T06:39:40+00:00"
},
{
"name": "phpunit/phpunit-mock-objects",
@@ -896,6 +986,11 @@
"mock",
"xunit"
],
"support": {
"irc": "irc://irc.freenode.net/phpunit",
"issues": "https://github.com/sebastianbergmann/phpunit-mock-objects/issues",
"source": "https://github.com/sebastianbergmann/phpunit-mock-objects/tree/3.4"
},
"abandoned": true,
"time": "2017-06-30T09:13:00+00:00"
},
@@ -942,6 +1037,10 @@
],
"description": "Looks up which function or method a line of code belongs to",
"homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
"support": {
"issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
"source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.2"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
@@ -1012,6 +1111,10 @@
"compare",
"equality"
],
"support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues",
"source": "https://github.com/sebastianbergmann/comparator/tree/1.2"
},
"time": "2017-01-29T09:50:25+00:00"
},
{
@@ -1064,6 +1167,10 @@
"keywords": [
"diff"
],
"support": {
"issues": "https://github.com/sebastianbergmann/diff/issues",
"source": "https://github.com/sebastianbergmann/diff/tree/1.4"
},
"time": "2017-05-22T07:24:03+00:00"
},
{
@@ -1114,25 +1221,29 @@
"environment",
"hhvm"
],
"support": {
"issues": "https://github.com/sebastianbergmann/environment/issues",
"source": "https://github.com/sebastianbergmann/environment/tree/master"
},
"time": "2016-11-26T07:53:53+00:00"
},
{
"name": "sebastian/exporter",
"version": "2.0.0",
"version": "1.2.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git",
"reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4"
"reference": "42c4c2eec485ee3e159ec9884f95b431287edde4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4",
"reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4",
"url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4",
"reference": "42c4c2eec485ee3e159ec9884f95b431287edde4",
"shasum": ""
},
"require": {
"php": ">=5.3.3",
"sebastian/recursion-context": "~2.0"
"sebastian/recursion-context": "~1.0"
},
"require-dev": {
"ext-mbstring": "*",
@@ -1141,7 +1252,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
"dev-master": "1.3.x-dev"
}
},
"autoload": {
@@ -1181,7 +1292,11 @@
"export",
"exporter"
],
"time": "2016-11-19T08:54:04+00:00"
"support": {
"issues": "https://github.com/sebastianbergmann/exporter/issues",
"source": "https://github.com/sebastianbergmann/exporter/tree/master"
},
"time": "2016-06-17T09:04:28+00:00"
},
{
"name": "sebastian/global-state",
@@ -1232,25 +1347,29 @@
"keywords": [
"global state"
],
"support": {
"issues": "https://github.com/sebastianbergmann/global-state/issues",
"source": "https://github.com/sebastianbergmann/global-state/tree/1.1.1"
},
"time": "2015-10-12T03:26:01+00:00"
},
{
"name": "sebastian/object-enumerator",
"version": "2.0.1",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/object-enumerator.git",
"reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7"
"reference": "d4ca2fb70344987502567bc50081c03e6192fb26"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1311872ac850040a79c3c058bea3e22d0f09cbb7",
"reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7",
"url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/d4ca2fb70344987502567bc50081c03e6192fb26",
"reference": "d4ca2fb70344987502567bc50081c03e6192fb26",
"shasum": ""
},
"require": {
"php": ">=5.6",
"sebastian/recursion-context": "~2.0"
"sebastian/recursion-context": "~1.0"
},
"require-dev": {
"phpunit/phpunit": "~5"
@@ -1258,7 +1377,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
"dev-master": "1.0.x-dev"
}
},
"autoload": {
@@ -1278,20 +1397,24 @@
],
"description": "Traverses array structures and object graphs to enumerate all referenced objects",
"homepage": "https://github.com/sebastianbergmann/object-enumerator/",
"time": "2017-02-18T15:18:39+00:00"
"support": {
"issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
"source": "https://github.com/sebastianbergmann/object-enumerator/tree/master"
},
"time": "2016-01-28T13:25:10+00:00"
},
{
"name": "sebastian/recursion-context",
"version": "2.0.0",
"version": "1.0.5",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/recursion-context.git",
"reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a"
"reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/2c3ba150cbec723aa057506e73a8d33bdb286c9a",
"reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a",
"url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b19cc3298482a335a95f3016d2f8a6950f0fbcd7",
"reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7",
"shasum": ""
},
"require": {
@@ -1303,7 +1426,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
"dev-master": "1.0.x-dev"
}
},
"autoload": {
@@ -1331,7 +1454,11 @@
],
"description": "Provides functionality to recursively process PHP variables",
"homepage": "http://www.github.com/sebastianbergmann/recursion-context",
"time": "2016-11-19T07:33:16+00:00"
"support": {
"issues": "https://github.com/sebastianbergmann/recursion-context/issues",
"source": "https://github.com/sebastianbergmann/recursion-context/tree/master"
},
"time": "2016-10-03T07:41:43+00:00"
},
{
"name": "sebastian/resource-operations",
@@ -1373,6 +1500,10 @@
],
"description": "Provides a list of PHP built-in functions that operate on resources",
"homepage": "https://www.github.com/sebastianbergmann/resource-operations",
"support": {
"issues": "https://github.com/sebastianbergmann/resource-operations/issues",
"source": "https://github.com/sebastianbergmann/resource-operations/tree/master"
},
"time": "2015-07-28T20:34:47+00:00"
},
{
@@ -1416,27 +1547,28 @@
],
"description": "Library that helps with managing the version number of Git-hosted PHP projects",
"homepage": "https://github.com/sebastianbergmann/version",
"support": {
"issues": "https://github.com/sebastianbergmann/version/issues",
"source": "https://github.com/sebastianbergmann/version/tree/master"
},
"time": "2016-10-03T07:35:21+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.24.0",
"version": "v1.19.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "30885182c981ab175d4d034db0f6f469898070ab"
"reference": "aed596913b70fae57be53d86faa2e9ef85a2297b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab",
"reference": "30885182c981ab175d4d034db0f6f469898070ab",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/aed596913b70fae57be53d86faa2e9ef85a2297b",
"reference": "aed596913b70fae57be53d86faa2e9ef85a2297b",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"provide": {
"ext-ctype": "*"
"php": ">=5.3.3"
},
"suggest": {
"ext-ctype": "For best performance"
@@ -1444,7 +1576,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.23-dev"
"dev-main": "1.19-dev"
},
"thanks": {
"name": "symfony/polyfill",
@@ -1452,12 +1584,12 @@
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
},
"files": [
"bootstrap.php"
]
],
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@@ -1481,6 +1613,9 @@
"polyfill",
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.19.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
@@ -1495,31 +1630,31 @@
"type": "tidelift"
}
],
"time": "2021-10-20T20:35:02+00:00"
"time": "2020-10-23T09:01:57+00:00"
},
{
"name": "symfony/yaml",
"version": "v4.4.37",
"version": "v3.4.47",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "d7f637cc0f0cc14beb0984f2bb50da560b271311"
"reference": "88289caa3c166321883f67fe5130188ebbb47094"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/d7f637cc0f0cc14beb0984f2bb50da560b271311",
"reference": "d7f637cc0f0cc14beb0984f2bb50da560b271311",
"url": "https://api.github.com/repos/symfony/yaml/zipball/88289caa3c166321883f67fe5130188ebbb47094",
"reference": "88289caa3c166321883f67fe5130188ebbb47094",
"shasum": ""
},
"require": {
"php": ">=7.1.3",
"php": "^5.5.9|>=7.0.8",
"symfony/polyfill-ctype": "~1.8"
},
"conflict": {
"symfony/console": "<3.4"
},
"require-dev": {
"symfony/console": "^3.4|^4.0|^5.0"
"symfony/console": "~3.4|~4.0"
},
"suggest": {
"symfony/console": "For validating YAML files using the lint command"
@@ -1547,8 +1682,11 @@
"homepage": "https://symfony.com/contributors"
}
],
"description": "Loads and dumps YAML files",
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/yaml/tree/v3.4.47"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
@@ -1563,39 +1701,34 @@
"type": "tidelift"
}
],
"time": "2022-01-24T20:11:01+00:00"
"time": "2020-10-24T10:57:07+00:00"
},
{
"name": "webmozart/assert",
"version": "1.10.0",
"version": "1.9.1",
"source": {
"type": "git",
"url": "https://github.com/webmozarts/assert.git",
"reference": "6964c76c7804814a842473e0c8fd15bab0f18e25"
"reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25",
"reference": "6964c76c7804814a842473e0c8fd15bab0f18e25",
"url": "https://api.github.com/repos/webmozarts/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389",
"reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0",
"php": "^5.3.3 || ^7.0 || ^8.0",
"symfony/polyfill-ctype": "^1.8"
},
"conflict": {
"phpstan/phpstan": "<0.12.20",
"vimeo/psalm": "<4.6.1 || 4.6.2"
"vimeo/psalm": "<3.9.1"
},
"require-dev": {
"phpunit/phpunit": "^8.5.13"
"phpunit/phpunit": "^4.8.36 || ^7.5.13"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.10-dev"
}
},
"autoload": {
"psr-4": {
"Webmozart\\Assert\\": "src/"
@@ -1617,7 +1750,11 @@
"check",
"validate"
],
"time": "2021-03-09T10:59:23+00:00"
"support": {
"issues": "https://github.com/webmozarts/assert/issues",
"source": "https://github.com/webmozarts/assert/tree/1.9.1"
},
"time": "2020-07-08T17:02:28+00:00"
}
],
"aliases": [],
@@ -1629,5 +1766,5 @@
"php": "^5.6.0 || ^7.0 || ^8.0"
},
"platform-dev": [],
"plugin-api-version": "1.1.0"
"plugin-api-version": "2.3.0"
}

View File

@@ -182,9 +182,12 @@
"Use Current Timezone": "Use Current Timezone",
"Convert To UTC": "Convert To UTC",
"Close": "Close",
"Encrypted note on PrivateBin": "Encrypted note on PrivateBin",
"Encrypted note on %s": "Encrypted note on %s",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.",
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
"Save paste": "Save paste",
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes."
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes.",
"Trying to shorten a URL that isn't pointing at our instance.": "Trying to shorten a URL that isn't pointing at our instance.",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".",
"Error parsing YOURLS response.": "Error parsing YOURLS response."
}

View File

@@ -182,9 +182,12 @@
"Use Current Timezone": "Use Current Timezone",
"Convert To UTC": "Convert To UTC",
"Close": "Close",
"Encrypted note on PrivateBin": "Encrypted note on PrivateBin",
"Encrypted note on %s": "Encrypted note on %s",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.",
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
"Save paste": "Save paste",
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes."
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes.",
"Trying to shorten a URL that isn't pointing at our instance.": "Trying to shorten a URL that isn't pointing at our instance.",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".",
"Error parsing YOURLS response.": "Error parsing YOURLS response."
}

View File

@@ -79,34 +79,34 @@
"Never": "Mai",
"Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.": "Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.",
"This document will expire in %d seconds.": [
"This document will expire in %d second. (singular)",
"This document will expire in %d seconds. (1st plural)",
"This document will expire in %d seconds. (2nd plural)",
"This document will expire in %d seconds. (3rd plural)"
"Aquest document caducarà d'aquí %d segon.",
"Aquest document caducarà d'aquí %d segons.",
"Aquest document caducarà d'aquí %d segons.",
"Aquest document caducarà d'aquí %d segons."
],
"This document will expire in %d minutes.": [
"This document will expire in %d minute. (singular)",
"This document will expire in %d minutes. (1st plural)",
"This document will expire in %d minutes. (2nd plural)",
"This document will expire in %d minutes. (3rd plural)"
"Aquest document caducarà d'aquí %d minut.",
"Aquest document caducarà d'aquí %d minuts.",
"Aquest document caducarà d'aquí %d minuts.",
"Aquest document caducarà d'aquí %d minuts."
],
"This document will expire in %d hours.": [
"This document will expire in %d hour. (singular)",
"This document will expire in %d hours. (1st plural)",
"This document will expire in %d hours. (2nd plural)",
"This document will expire in %d hours. (3rd plural)"
"Aquest document caducarà d'aquí %d hora.",
"Aquest document caducarà d'aquí %d hores.",
"Aquest document caducarà d'aquí %d hores.",
"Aquest document caducarà d'aquí %d hores."
],
"This document will expire in %d days.": [
"This document will expire in %d day. (singular)",
"This document will expire in %d days. (1st plural)",
"This document will expire in %d days. (2nd plural)",
"This document will expire in %d days. (3rd plural)"
"Aquest document caducarà d'aquí %d dia.",
"Aquest document caducarà d'aquí %d dies.",
"Aquest document caducarà d'aquí %d dies.",
"Aquest document caducarà d'aquí %d dies."
],
"This document will expire in %d months.": [
"This document will expire in %d month. (singular)",
"This document will expire in %d months. (1st plural)",
"This document will expire in %d months. (2nd plural)",
"This document will expire in %d months. (3rd plural)"
"Aquest document caducarà d'aquí %d mes.",
"Aquest document caducarà d'aquí %d mesos.",
"Aquest document caducarà d'aquí %d mesos.",
"Aquest document caducarà d'aquí %d mesos."
],
"Please enter the password for this paste:": "Si us plau, introdueix la contrasenya per aquest paste:",
"Could not decrypt data (Wrong key?)": "No s'han pogut desxifrar les dades (Clau incorrecte?)",
@@ -124,7 +124,7 @@
"Could not refresh display: %s": "Could not refresh display: %s",
"unknown status": "estat desconegut",
"server error or not responding": "server error or not responding",
"Could not post comment: %s": "Could not post comment: %s",
"Could not post comment: %s": "No s'ha pogut publicar el comentari: %s",
"Sending paste…": "Enviant paste…",
"Your paste is <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Hit [Ctrl]+[c] to copy)</span>": "Your paste is <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Hit [Ctrl]+[c] to copy)</span>",
"Delete data": "Esborrar les dades",
@@ -146,7 +146,7 @@
"Download attachment": "Baixar els adjunts",
"Cloned: '%s'": "Cloned: '%s'",
"The cloned file '%s' was attached to this paste.": "The cloned file '%s' was attached to this paste.",
"Attach a file": "Attach a file",
"Attach a file": "Adjuntar un fitxer",
"alternatively drag & drop a file or paste an image from the clipboard": "alternatively drag & drop a file or paste an image from the clipboard",
"File too large, to display a preview. Please download the attachment.": "File too large, to display a preview. Please download the attachment.",
"Remove attachment": "Remove attachment",
@@ -182,9 +182,12 @@
"Use Current Timezone": "Use Current Timezone",
"Convert To UTC": "Convert To UTC",
"Close": "Close",
"Encrypted note on PrivateBin": "Encrypted note on PrivateBin",
"Encrypted note on %s": "Encrypted note on %s",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.",
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
"Save paste": "Save paste",
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes."
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes.",
"Trying to shorten a URL that isn't pointing at our instance.": "Trying to shorten a URL that isn't pointing at our instance.",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".",
"Error parsing YOURLS response.": "Error parsing YOURLS response."
}

View File

@@ -182,9 +182,12 @@
"Use Current Timezone": "Impiegà u fusu orariu attuale",
"Convert To UTC": "Cunvertisce in UTC",
"Close": "Chjode",
"Encrypted note on PrivateBin": "Nota cifrata nantà PrivateBin",
"Encrypted note on %s": "Nota cifrata nantà %s",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visitate stu liame per vede a nota. Date lindirizzu à qualunque li permette daccede à a nota dinù.",
"URL shortener may expose your decrypt key in URL.": "Un ammuzzatore dindirizzu pò palisà a vostra chjave di dicifratura in lindirizzu.",
"Save paste": "Arregistrà lappiccicu",
"Your IP is not authorized to create pastes.": "U vostru indirizzu IP ùn hè micca auturizatu à creà lappiccichi."
"Your IP is not authorized to create pastes.": "U vostru indirizzu IP ùn hè micca auturizatu à creà lappiccichi.",
"Trying to shorten a URL that isn't pointing at our instance.": "Pruvate dammuzzà un indirizzu web chì ùn punta micca versu a vostra instanza.",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Sbagliu à a chjama di YOURLS. Seria forse una cunfigurazione gattiva, tale una \"apiurl\" o \"signature\" falsa o assente.",
"Error parsing YOURLS response.": "Sbagliu durante lanalisa di a risposta di YOURLS."
}

View File

@@ -182,9 +182,12 @@
"Use Current Timezone": "Použít aktuální časové pásmo",
"Convert To UTC": "Převést na UTC",
"Close": "Zavřít",
"Encrypted note on PrivateBin": "Šifrovaná poznámka ve službě PrivateBin",
"Encrypted note on %s": "Šifrovaná poznámka ve službě %s",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Navštivte tento odkaz pro zobrazení poznámky. Přeposláním URL umožníte také jiným lidem přístup.",
"URL shortener may expose your decrypt key in URL.": "Zkracovač URL může odhalit váš dešifrovací klíč v URL.",
"Save paste": "Uložit příspěvek",
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes."
"Your IP is not authorized to create pastes.": "Vaše IP adresa nemá oprávnění k vytvoření vložení.",
"Trying to shorten a URL that isn't pointing at our instance.": "Trying to shorten a URL that isn't pointing at our instance.",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".",
"Error parsing YOURLS response.": "Error parsing YOURLS response."
}

View File

@@ -182,9 +182,12 @@
"Use Current Timezone": "Aktuelle Zeitzone verwenden",
"Convert To UTC": "In UTC umwandeln",
"Close": "Schliessen",
"Encrypted note on PrivateBin": "Verschlüsselte Notiz auf PrivateBin",
"Encrypted note on %s": "Verschlüsselte Notiz auf %s",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Besuche diesen Link um das Dokument zu sehen. Wird die URL an eine andere Person gegeben, so kann diese Person ebenfalls auf dieses Dokument zugreifen.",
"URL shortener may expose your decrypt key in URL.": "Der URL-Verkürzer kann den Schlüssel in der URL enthüllen.",
"Save paste": "Text speichern",
"Your IP is not authorized to create pastes.": "Deine IP ist nicht berechtigt, Texte zu erstellen."
"Your IP is not authorized to create pastes.": "Deine IP ist nicht berechtigt, Texte zu erstellen.",
"Trying to shorten a URL that isn't pointing at our instance.": "Versuch eine URL zu verkürzen, die nicht auf unsere Instanz zeigt.",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Fehler beim Aufruf von YOURLS. Wahrscheinlich ein Konfigurationsproblem, wie eine falsche oder fehlende \"apiurl\" oder \"signature\".",
"Error parsing YOURLS response.": "Fehler beim Verarbeiten der YOURLS-Antwort."
}

View File

@@ -1,135 +1,135 @@
{
"PrivateBin": "PrivateBin",
"%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.",
"More information on the <a href=\"https://privatebin.info/\">project page</a>.": "More information on the <a href=\"https://privatebin.info/\">project page</a>.",
"Because ignorance is bliss": "Because ignorance is bliss",
"%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s είναι ένα λιτό, ανοικτού λογισμικού διαδικτυακής υπηρεσίας επικόλλησης όπου ο διακομιστής έχει πλήρη άγνια του περιεχομένου που επικολλήθηκαν. Τα Δεδομένα κρυπτογραφούνται και αποκρυπτογραφούνται %sστον φιλομετρητή (browser)%s χρησιμοποιόντας 256 bits AES.",
"More information on the <a href=\"https://privatebin.info/\">project page</a>.": "Περισσότερες πληροφορίες στον <a href=\"https://privatebin.info/\">ιστότοπο του εργαλείου</a>.",
"Because ignorance is bliss": "Επειδή η άγνοια είναι ευτυχία",
"en": "el",
"Paste does not exist, has expired or has been deleted.": "Paste does not exist, has expired or has been deleted.",
"%s requires php %s or above to work. Sorry.": "%s requires php %s or above to work. Sorry.",
"%s requires configuration section [%s] to be present in configuration file.": "%s requires configuration section [%s] to be present in configuration file.",
"Paste does not exist, has expired or has been deleted.": "Η επικόλληση δεν υπάρχει, έληξε ή διαγράφηκε",
"%s requires php %s or above to work. Sorry.": "%s απαιτεί php %s ή νεότερη για να λειτουργήσει. Συγγνώμη.",
"%s requires configuration section [%s] to be present in configuration file.": "%s απαιτεί οι ρυθμίσεις [%s] να υπάρχουν στο αρχείο ρυθμίσεων.",
"Please wait %d seconds between each post.": [
"Please wait %d second between each post. (singular)",
"Please wait %d seconds between each post. (1st plural)",
"Please wait %d seconds between each post. (2nd plural)",
"Please wait %d seconds between each post. (3rd plural)"
"Παρακαλώ περιμένετε %d δευτερόλεπτο μεταξύ κάθε επικόλλησης.",
"Παρακαλώ περιμένετε %d δευτερόλεπτα μεταξύ κάθε επικόλλησης.",
"Παρακαλώ περιμένετε %d δευτερόλεπτα μεταξύ κάθε επικόλλησης.",
"Παρακαλώ περιμένετε %d δευτερόλεπτα μεταξύ κάθε επικόλλησης."
],
"Paste is limited to %s of encrypted data.": "Paste is limited to %s of encrypted data.",
"Invalid data.": "Invalid data.",
"You are unlucky. Try again.": "You are unlucky. Try again.",
"Error saving comment. Sorry.": "Error saving comment. Sorry.",
"Error saving paste. Sorry.": "Error saving paste. Sorry.",
"Invalid paste ID.": "Invalid paste ID.",
"Paste is not of burn-after-reading type.": "Paste is not of burn-after-reading type.",
"Wrong deletion token. Paste was not deleted.": "Wrong deletion token. Paste was not deleted.",
"Paste was properly deleted.": "Paste was properly deleted.",
"JavaScript is required for %s to work. Sorry for the inconvenience.": "JavaScript is required for %s to work. Sorry for the inconvenience.",
"%s requires a modern browser to work.": "%s requires a modern browser to work.",
"New": "New",
"Send": "Send",
"Clone": "Clone",
"Raw text": "Raw text",
"Expires": "Expires",
"Burn after reading": "Burn after reading",
"Open discussion": "Open discussion",
"Password (recommended)": "Password (recommended)",
"Discussion": "Discussion",
"Toggle navigation": "Toggle navigation",
"Paste is limited to %s of encrypted data.": "Η επικόλληση είναι περιορισμένη σε %s κρυπτογραφημένων δεδομένων.",
"Invalid data.": "Λάθος δεδομένα.",
"You are unlucky. Try again.": "Ατυχήσατε. Προσπαθήστε πάλι.",
"Error saving comment. Sorry.": "Λάθος στην αποθήκευση του σχόλιου. Συγγνώμη.",
"Error saving paste. Sorry.": "Λάθος στην αποθήκευση της επικόλλησης. Συγγνώμη.",
"Invalid paste ID.": "Λάθος αναγνωριστικό επικόλλησης.",
"Paste is not of burn-after-reading type.": "Η επικόληση δεν είναι τύπου καταστροφή-μετά-το-διάβασμα.",
"Wrong deletion token. Paste was not deleted.": "Λάθος αναγνωριστικό διαγραφής. Η επικόλληση δεν διαγράφηκε.",
"Paste was properly deleted.": "Η επικόλληση διαγράφηκε επιτυχώς.",
"JavaScript is required for %s to work. Sorry for the inconvenience.": "Η JavaScript είναι απαραίτητη για να λειτουργήσει το %s. Συγγνώμη για την ταλαιπωρία.",
"%s requires a modern browser to work.": "%s απαιτεί σύγχρονο φυλλομετρητή (browser) για να λειτουργήσει.",
"New": "Νέο",
"Send": "Αποστολή",
"Clone": "Κλωνοποίηση",
"Raw text": "Κείμενο",
"Expires": "Λήγει",
"Burn after reading": "Διαγραφή μετά την ανάγνωση",
"Open discussion": "Ανοικτή συζήτηση",
"Password (recommended)": "Κωδικός (προτείνεται)",
"Discussion": "Συζήτηση",
"Toggle navigation": "Εναλλαγή πλοήγησης",
"%d seconds": [
"%d second (singular)",
"%d seconds (1st plural)",
"%d seconds (2nd plural)",
"%d seconds (3rd plural)"
"%d δευτερόλεπτο",
"%d δευτερόλεπτα",
"%d δευτερόλεπτα",
"%d δευτερόλεπτα"
],
"%d minutes": [
"%d minute (singular)",
"%d minutes (1st plural)",
"%d minutes (2nd plural)",
"%d minutes (3rd plural)"
"%d λεπτό",
"%d λεπτά",
"%d λεπτά",
"%d λεπτά"
],
"%d hours": [
"%d hour (singular)",
"%d hours (1st plural)",
"%d hours (2nd plural)",
"%d hours (3rd plural)"
"%d ώρα",
"%d ώρες",
"%d ώρες",
"%d ώρες"
],
"%d days": [
"%d day (singular)",
"%d days (1st plural)",
"%d days (2nd plural)",
"%d days (3rd plural)"
"%d ημέρα",
"%d ημέρες",
"%d ημέρες",
"%d ημέρες"
],
"%d weeks": [
"%d week (singular)",
"%d weeks (1st plural)",
"%d weeks (2nd plural)",
"%d weeks (3rd plural)"
"%d εβδομάδα",
"%d εβδομάδες",
"%d εβδομάδες",
"%d εβδομάδες"
],
"%d months": [
"%d month (singular)",
"%d months (1st plural)",
"%d months (2nd plural)",
"%d months (3rd plural)"
"%d μήνας",
"%d μήνες",
"%d μήνες",
"%d μήνες"
],
"%d years": [
"%d year (singular)",
"%d years (1st plural)",
"%d years (2nd plural)",
"%d years (3rd plural)"
"%d χρόνο",
"%d χρόνια",
"%d χρόνια",
"%d χρόνια"
],
"Never": "Never",
"Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.": "Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.",
"Never": "Ποτέ",
"Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.": "Σημείωση: αυτή είναι μία δοκιμαστική υπηρεσία. Τα δεδομένα μπορεί να σβηστούν ανά πάσα στιγμή.",
"This document will expire in %d seconds.": [
"This document will expire in %d second. (singular)",
"This document will expire in %d seconds. (1st plural)",
"This document will expire in %d seconds. (2nd plural)",
"This document will expire in %d seconds. (3rd plural)"
"Αυτό το έγγραφο θα λήξει σε %d δευτερόλεπτο.",
"Αυτό το έγγραφο θα λήξει σε %d δευτερόλεπτα.",
"Αυτό το έγγραφο θα λήξει σε %d δευτερόλεπτα.",
"Αυτό το έγγραφο θα λήξει σε %d δευτερόλεπτα."
],
"This document will expire in %d minutes.": [
"This document will expire in %d minute. (singular)",
"This document will expire in %d minutes. (1st plural)",
"This document will expire in %d minutes. (2nd plural)",
"This document will expire in %d minutes. (3rd plural)"
"Αυτό το έγγραφο θα λήξει σε %d λεπτό.",
"Αυτό το έγγραφο θα λήξει σε %d λεπτά.",
"Αυτό το έγγραφο θα λήξει σε %d λεπτά.",
"Αυτό το έγγραφο θα λήξει σε %d λεπτά."
],
"This document will expire in %d hours.": [
"This document will expire in %d hour. (singular)",
"This document will expire in %d hours. (1st plural)",
"This document will expire in %d hours. (2nd plural)",
"This document will expire in %d hours. (3rd plural)"
"Αυτό το έγγραφο θα λήξει σε %d ώρα.",
"Αυτό το έγγραφο θα λήξει σε %d ώρες.",
"Αυτό το έγγραφο θα λήξει σε %d ώρες.",
"Αυτό το έγγραφο θα λήξει σε %d ώρες."
],
"This document will expire in %d days.": [
"This document will expire in %d day. (singular)",
"This document will expire in %d days. (1st plural)",
"This document will expire in %d days. (2nd plural)",
"This document will expire in %d days. (3rd plural)"
"Αυτό το έγγραφο θα λήξει σε %d ημέρα.",
"Αυτό το έγγραφο θα λήξει σε %d ημέρες.",
"Αυτό το έγγραφο θα λήξει σε %d ημέρες.",
"Αυτό το έγγραφο θα λήξει σε %d ημέρες."
],
"This document will expire in %d months.": [
"This document will expire in %d month. (singular)",
"This document will expire in %d months. (1st plural)",
"This document will expire in %d months. (2nd plural)",
"This document will expire in %d months. (3rd plural)"
"Αυτό το έγγραφο θα λήξει σε %d μήνα.",
"Αυτό το έγγραφο θα λήξει σε %d μήνες.",
"Αυτό το έγγραφο θα λήξει σε %d μήνες.",
"Αυτό το έγγραφο θα λήξει σε %d μήνες."
],
"Please enter the password for this paste:": "Please enter the password for this paste:",
"Could not decrypt data (Wrong key?)": "Could not decrypt data (Wrong key?)",
"Could not delete the paste, it was not stored in burn after reading mode.": "Could not delete the paste, it was not stored in burn after reading mode.",
"FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.": "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.",
"Could not decrypt comment; Wrong key?": "Could not decrypt comment; Wrong key?",
"Reply": "Reply",
"Anonymous": "Anonymous",
"Avatar generated from IP address": "Avatar generated from IP address",
"Add comment": "Add comment",
"Optional nickname…": "Optional nickname…",
"Post comment": "Post comment",
"Sending comment…": "Sending comment…",
"Comment posted.": "Comment posted.",
"Could not refresh display: %s": "Could not refresh display: %s",
"unknown status": "unknown status",
"server error or not responding": "server error or not responding",
"Could not post comment: %s": "Could not post comment: %s",
"Sending paste…": "Sending paste…",
"Your paste is <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Hit [Ctrl]+[c] to copy)</span>": "Your paste is <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Hit [Ctrl]+[c] to copy)</span>",
"Delete data": "Delete data",
"Could not create paste: %s": "Could not create paste: %s",
"Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)": "Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)",
"Please enter the password for this paste:": "Παρακαλώ εισάγετε τον κωδικό για αυτή την επικόληση:",
"Could not decrypt data (Wrong key?)": "Δεν ήταν δυνατή η αποκρυπτογράφηση των δεδομένων (πιθανώς λανθασμένο κλειδί;)",
"Could not delete the paste, it was not stored in burn after reading mode.": "Δεν ήταν δυνατή η διαγραφή της επικόλλησης, δεν ήταν αποθηκευμένη σε μορφή διαγραφής μετά την ανάγνωση.",
"FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.": "ΜΟΝΟ ΓΙΑ ΕΣΑΣ. Μην κλείσετε το αυτό το παράθυρο, αυτό το μήνυμα δεν μπορεί να εμφανιστεί ξανά.",
"Could not decrypt comment; Wrong key?": "Δεν ήταν δυνατή η αποκρυπτογράφηση του σχολίου. Λάθος κλειδί;",
"Reply": "Απάντηση",
"Anonymous": "Ανώνυμος",
"Avatar generated from IP address": "το avatar δημιουργήθηκε από τη διεύθυνση IP",
"Add comment": "Σχολιάστε",
"Optional nickname…": "Προαιρετικό ψευδώνυμο…",
"Post comment": "Αποστολή σχολίου",
"Sending comment…": "Το σχόλιο αποστέλλεται…",
"Comment posted.": "Το σχόλιο δημοσιεύτηκε.",
"Could not refresh display: %s": "Δεν ήταν δυνατή η ανανέωση της σελίδας: %s",
"unknown status": "άγνωστη κατάσταση",
"server error or not responding": "Πρόβλημα του διακομιστή ή δεν υπάρχει απάντηση",
"Could not post comment: %s": "Δεν ήταν δυνατή η δημοσίευση του σχολίου: %s",
"Sending paste…": "Η επικόλληση αποστέλλεται…",
"Your paste is <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Hit [Ctrl]+[c] to copy)</span>": "Η επικόλλησή σας είναι <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Πληκτρολογήστε [Ctrl]+[c] για αντιγραφή)</span>",
"Delete data": "Διαγραφή δεδομένων",
"Could not create paste: %s": "Δεν ήταν δυνατή η δημιουργία επικόλλησης: %s",
"Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)": "Δεν ήταν δυνατή η αποκρυπτογράφηση της επικόλλησης: Το κλειδί αποκρυπτογράφησης λείπει από τον σύνδεσμο (Μήπως χρησιμοποιήσατε ανακατεύθυνση συνδέσμου ή υπηρεσία συντόμευσης συνδέσμου;)",
"B": "B",
"KiB": "KiB",
"MiB": "MiB",
@@ -139,52 +139,55 @@
"EiB": "EiB",
"ZiB": "ZiB",
"YiB": "YiB",
"Format": "Format",
"Plain Text": "Plain Text",
"Source Code": "Source Code",
"Format": "Μορφοποίηση",
"Plain Text": "Απλό κείμενο",
"Source Code": "Πηγαίος Κώδικας",
"Markdown": "Markdown",
"Download attachment": "Download attachment",
"Cloned: '%s'": "Cloned: '%s'",
"The cloned file '%s' was attached to this paste.": "The cloned file '%s' was attached to this paste.",
"Attach a file": "Attach a file",
"alternatively drag & drop a file or paste an image from the clipboard": "alternatively drag & drop a file or paste an image from the clipboard",
"File too large, to display a preview. Please download the attachment.": "File too large, to display a preview. Please download the attachment.",
"Remove attachment": "Remove attachment",
"Your browser does not support uploading encrypted files. Please use a newer browser.": "Your browser does not support uploading encrypted files. Please use a newer browser.",
"Invalid attachment.": "Invalid attachment.",
"Options": "Options",
"Shorten URL": "Shorten URL",
"Editor": "Editor",
"Preview": "Preview",
"%s requires the PATH to end in a \"%s\". Please update the PATH in your index.php.": "%s requires the PATH to end in a \"%s\". Please update the PATH in your index.php.",
"Decrypt": "Decrypt",
"Enter password": "Enter password",
"Loading…": "Loading…",
"Decrypting paste…": "Decrypting paste…",
"Preparing new paste…": "Preparing new paste…",
"In case this message never disappears please have a look at <a href=\"%s\">this FAQ for information to troubleshoot</a>.": "In case this message never disappears please have a look at <a href=\"%s\">this FAQ for information to troubleshoot</a>.",
"+++ no paste text +++": "+++ no paste text +++",
"Could not get paste data: %s": "Could not get paste data: %s",
"QR code": "QR code",
"This website is using an insecure HTTP connection! Please use it only for testing.": "This website is using an insecure HTTP connection! Please use it only for testing.",
"For more information <a href=\"%s\">see this FAQ entry</a>.": "For more information <a href=\"%s\">see this FAQ entry</a>.",
"Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.": "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.",
"Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.": "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.",
"waiting on user to provide a password": "waiting on user to provide a password",
"Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.": "Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.",
"Retry": "Retry",
"Showing raw text…": "Showing raw text…",
"Notice:": "Notice:",
"This link will expire after %s.": "This link will expire after %s.",
"This link can only be accessed once, do not use back or refresh button in your browser.": "This link can only be accessed once, do not use back or refresh button in your browser.",
"Link:": "Link:",
"Recipient may become aware of your timezone, convert time to UTC?": "Recipient may become aware of your timezone, convert time to UTC?",
"Use Current Timezone": "Use Current Timezone",
"Convert To UTC": "Convert To UTC",
"Close": "Close",
"Encrypted note on PrivateBin": "Encrypted note on PrivateBin",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.",
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
"Save paste": "Save paste",
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes."
"Download attachment": "Λήψη επισυναπτόμενου",
"Cloned: '%s'": "Κλώνος: '%s'",
"The cloned file '%s' was attached to this paste.": "Το κλωνοποιημένο αρχείο '%s' επισυνάφθηκε στ αυτή την επικόλληση.",
"Attach a file": "Επισύναψη αρχείου",
"alternatively drag & drop a file or paste an image from the clipboard": "εναλλακτικά σύρετε το αρχείο ή επικολλήστε μία εικόνα από το clipboard",
"File too large, to display a preview. Please download the attachment.": "Πολύ μεγάλο αρχείο για προεπισκόπηση. Παρακαλώ κατεβάστε το επισυναπτόμενο.",
"Remove attachment": "Αφαίρεση επισυναπτόμενου",
"Your browser does not support uploading encrypted files. Please use a newer browser.": "Ο φυλλομετρητής (browser) σας δεν υποστηρίζει κρυπτογραφημένα αρχεία. Παρακαλώ χρησιμοποιήστε νεότερο φιλομετρητή.",
"Invalid attachment.": "Λάθος επισυναπτόμενο.",
"Options": "Επιλογές",
"Shorten URL": "Συντόμευση σύνδεσμου",
"Editor": "Διορθωτής",
"Preview": "Προεπισκόπηση",
"%s requires the PATH to end in a \"%s\". Please update the PATH in your index.php.": "%s απαιτεί το PATH να τελειώνει σε \"%s\". Παρακαλώ ενημερώστε το PATH στο index.php σας.",
"Decrypt": "Αποκρυπτογράφηση",
"Enter password": "Εισαγωγή κωδικού",
"Loading…": "Φόρτωση…",
"Decrypting paste…": "Η επικόλληση αποκρυπτογραφείται…",
"Preparing new paste…": "Προετοιμασία νέας επικόλλησης…",
"In case this message never disappears please have a look at <a href=\"%s\">this FAQ for information to troubleshoot</a>.": "Σε περίπτωση που αυτό το μήνυμα δεν εξαφανίζεται παρακαλώ κοιτάξτε στις <a href=\"%s\">Ερωταποκρίσεις για πληροφορίες στην αντιμετώπιση προβλημάτων</a>.",
"+++ no paste text +++": "+++ Δεν υπάρχει επικόλληση +++",
"Could not get paste data: %s": "Δεν ήταν δυνατή η λήψη της επικόλλησης: %s",
"QR code": "QR εικονοστοιχειοσειρά",
"This website is using an insecure HTTP connection! Please use it only for testing.": "Αυτός ο ιστότοπος χρησιμοποιεί μη ασφαλή HTTP σύνδεση! Παρακαλώ χρησιμοποιήστε το μόνο δοκιμαστικά.",
"For more information <a href=\"%s\">see this FAQ entry</a>.": "Για περισσότερες πληροφορίες <a href=\"%s\">δείτε τις ερωταπαντήσεις</a>.",
"Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.": "Ο φυλλομετρητής σας μπορεί να απαιτεί HTTPS σύνδεση για να υποστηρίξει το WebCrypto API. Δοκιμάστε <a href=\"%s\">το HTTPS</a>.",
"Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.": "Ο φυλλομετρητής σας δεν υποστηρίζει WebAssembly, που χρησιμοποιήθηκε για zlib συμπίεση. Μπορείτε να δημιουργήσετε ασυμπίεστα αρχεία αλλά δεν μπορείτε να διαβάσετε συμπιεσμένα.",
"waiting on user to provide a password": "Αναμονή ο χρήστης να δώσει τον κωδικό",
"Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.": "Δεν ήταν δυνατή η αποκρυπτογράφηση των δεδομένων. Μήπως εισάγατε λάθος κωδικό; Προσπαθήστε με το κουμπί στο επάνω μέρος.",
"Retry": "Επαναπροσπάθεια",
"Showing raw text…": "Προβολή κειμένου…",
"Notice:": "Επισήμανση:",
"This link will expire after %s.": "Ο σύνδεσμος θα λήξει σε %s.",
"This link can only be accessed once, do not use back or refresh button in your browser.": "Αυτός ο σύνδεσμος μπορεί να προσπελαστεί μόνο μία φορά, μην χρησιμοποιήσετε το κουμπί επιστροφή ή ανανέωση στον φυλλομετρητή σας.",
"Link:": "Σύνδεσμος:",
"Recipient may become aware of your timezone, convert time to UTC?": "Ο παραλήπτης μπορεί να αναγνωρίσει τη ζώνη ώρας σας, θέλετε μετατροπή της ώρας σε UTC;",
"Use Current Timezone": "Χρήση τρέχουσας ζώνης ώρας",
"Convert To UTC": "Μετατροπή σε UTC",
"Close": "Κλείσιμο",
"Encrypted note on %s": "Κρυπτογραφημένο μήνυμα από το %s",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Επισκεφτείτε αυτόν τον σύνδεσμο για να δείτε το μήνυμα. Δίνοντας τον σύνδεσμο σε οποιονδήποτε, του επιτρέπετε να επισκεφτεί το μήνυμα επίσης.",
"URL shortener may expose your decrypt key in URL.": "Συντομευτές συνδέσμων πιθανώς να δημοσιοποιήσουν το κλειδί αποκρυπτογράφισης στον σύνδεσμο.",
"Save paste": "Αποθήκευση επικόλλησης",
"Your IP is not authorized to create pastes.": "Η IP σας δεν επιτρέπεται να δημιουργεί επικολλήσεις.",
"Trying to shorten a URL that isn't pointing at our instance.": "Trying to shorten a URL that isn't pointing at our instance.",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".",
"Error parsing YOURLS response.": "Error parsing YOURLS response."
}

View File

@@ -182,9 +182,12 @@
"Use Current Timezone": "Use Current Timezone",
"Convert To UTC": "Convert To UTC",
"Close": "Close",
"Encrypted note on PrivateBin": "Encrypted note on PrivateBin",
"Encrypted note on %s": "Encrypted note on %s",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.",
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
"Save paste": "Save paste",
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes."
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes.",
"Trying to shorten a URL that isn't pointing at our instance.": "Trying to shorten a URL that isn't pointing at our instance.",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".",
"Error parsing YOURLS response.": "Error parsing YOURLS response."
}

View File

@@ -2,7 +2,7 @@
"PrivateBin": "PrivateBin",
"%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s es un \"pastebin\" en línea minimalista de código abierto, donde el servidor no tiene ningún conocimiento de los datos guardados. Los datos son cifrados/descifrados %sen el navegador%s usando 256 bits AES.",
"More information on the <a href=\"https://privatebin.info/\">project page</a>.": "Más información en la <a href=\"https://privatebin.info/\">página del proyecto</a>.",
"Because ignorance is bliss": "Porque la ignorancia es dicha",
"Because ignorance is bliss": "Porque la ignorancia es felicidad",
"en": "es",
"Paste does not exist, has expired or has been deleted.": "El \"paste\" no existe, ha caducado o ha sido eliminado.",
"%s requires php %s or above to work. Sorry.": "%s requiere php %s o superior para funcionar. Lo siento.",
@@ -182,9 +182,12 @@
"Use Current Timezone": "Usar Zona Horaria Actual",
"Convert To UTC": "Convertir A UTC",
"Close": "Cerrar",
"Encrypted note on PrivateBin": "Nota cifrada en PrivateBin",
"Encrypted note on %s": "Nota cifrada en %s",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visite este enlace para ver la nota. Dar la URL a cualquier persona también les permite acceder a la nota.",
"URL shortener may expose your decrypt key in URL.": "El acortador de URL puede exponer su clave de descifrado en el URL.",
"Save paste": "Guardar \"paste\"",
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes."
"Your IP is not authorized to create pastes.": "Tu IP no está autorizada para crear contenido.",
"Trying to shorten a URL that isn't pointing at our instance.": "Trying to shorten a URL that isn't pointing at our instance.",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".",
"Error parsing YOURLS response.": "Error parsing YOURLS response."
}

View File

@@ -182,9 +182,12 @@
"Use Current Timezone": "Kasuta praegust ajavööndit",
"Convert To UTC": "Teisenda UTC-ks",
"Close": "Sulge",
"Encrypted note on PrivateBin": "Krüpteeritud kiri PrivateBin-is",
"Encrypted note on %s": "Krüpteeritud kiri %s-is",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Kirja nägemiseks külasta seda linki. Teistele URL-i andmine lubab ka neil ligi pääseda kirjale.",
"URL shortener may expose your decrypt key in URL.": "URL-i lühendaja võib paljastada sinu dekrüpteerimisvõtme URL-is.",
"Save paste": "Salvesta kleebe",
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes."
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes.",
"Trying to shorten a URL that isn't pointing at our instance.": "Trying to shorten a URL that isn't pointing at our instance.",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".",
"Error parsing YOURLS response.": "Error parsing YOURLS response."
}

View File

@@ -182,9 +182,12 @@
"Use Current Timezone": "Käytä nykyistä aikavyöhykettä",
"Convert To UTC": "Muuta UTC:ksi",
"Close": "Sulje",
"Encrypted note on PrivateBin": "Salattu viesti PrivateBinissä",
"Encrypted note on %s": "Salattu viesti %sissä",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Käy tässä linkissä nähdäksesi viestin. URL:n antaminen kenellekään antaa heidänkin päästä katsomeen viestiä. ",
"URL shortener may expose your decrypt key in URL.": "URL-lyhentäjä voi paljastaa purkuavaimesi URL:ssä.",
"Save paste": "Tallenna paste",
"Your IP is not authorized to create pastes.": "IP:llesi ei ole annettu oikeutta luoda pasteja."
"Your IP is not authorized to create pastes.": "IP:llesi ei ole annettu oikeutta luoda pasteja.",
"Trying to shorten a URL that isn't pointing at our instance.": "Trying to shorten a URL that isn't pointing at our instance.",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".",
"Error parsing YOURLS response.": "Error parsing YOURLS response."
}

View File

@@ -182,9 +182,12 @@
"Use Current Timezone": "Conserver l'actuel",
"Convert To UTC": "Convertir en UTC",
"Close": "Fermer",
"Encrypted note on PrivateBin": "Message chiffré sur PrivateBin",
"Encrypted note on %s": "Message chiffré sur %s",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visiter ce lien pour voir la note. Donner l'URL à une autre personne lui permet également d'accéder à la note.",
"URL shortener may expose your decrypt key in URL.": "Raccourcir l'URL peut exposer votre clé de déchiffrement dans l'URL.",
"Save paste": "Sauver le paste",
"Your IP is not authorized to create pastes.": "Votre adresse IP n'est pas autorisée à créer des pastes."
"Your IP is not authorized to create pastes.": "Votre adresse IP n'est pas autorisée à créer des pastes.",
"Trying to shorten a URL that isn't pointing at our instance.": "Trying to shorten a URL that isn't pointing at our instance.",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".",
"Error parsing YOURLS response.": "Error parsing YOURLS response."
}

View File

@@ -182,9 +182,12 @@
"Use Current Timezone": "להשתמש באזור הזמן הנוכחי",
"Convert To UTC": "המרה ל־UTC",
"Close": "סגירה",
"Encrypted note on PrivateBin": "הערה מוצפנת ב־PrivateBin",
"Encrypted note on %s": "%sהערה מוצפנת ב־",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "נא לבקר בקישור כדי לצפות בהערה. מסירת הקישור לאנשים כלשהם תאפשר גם להם לגשת להערה.",
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
"Save paste": "Save paste",
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes."
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes.",
"Trying to shorten a URL that isn't pointing at our instance.": "Trying to shorten a URL that isn't pointing at our instance.",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".",
"Error parsing YOURLS response.": "Error parsing YOURLS response."
}

View File

@@ -182,9 +182,12 @@
"Use Current Timezone": "Use Current Timezone",
"Convert To UTC": "Convert To UTC",
"Close": "Close",
"Encrypted note on PrivateBin": "Encrypted note on PrivateBin",
"Encrypted note on %s": "Encrypted note on %s",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.",
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
"Save paste": "Save paste",
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes."
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes.",
"Trying to shorten a URL that isn't pointing at our instance.": "Trying to shorten a URL that isn't pointing at our instance.",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".",
"Error parsing YOURLS response.": "Error parsing YOURLS response."
}

View File

@@ -182,9 +182,12 @@
"Use Current Timezone": "Az aktuális időzóna használata",
"Convert To UTC": "Átalakítás UTC időzónára",
"Close": "Bezárás",
"Encrypted note on PrivateBin": "Titkosított jegyzet a PrivateBinen",
"Encrypted note on %s": "Titkosított jegyzet a %s",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Látogasd meg ezt a hivatkozást a bejegyzés megtekintéséhez. Ha mások számára is megadod ezt a linket, azzal hozzáférnek ők is.",
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
"Save paste": "Save paste",
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes."
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes.",
"Trying to shorten a URL that isn't pointing at our instance.": "Trying to shorten a URL that isn't pointing at our instance.",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".",
"Error parsing YOURLS response.": "Error parsing YOURLS response."
}

View File

@@ -182,9 +182,12 @@
"Use Current Timezone": "Gunakan Zonawaktu Saat Ini",
"Convert To UTC": "Konversi Ke UTC",
"Close": "Tutup",
"Encrypted note on PrivateBin": "Catatan ter-ekrip di PrivateBin",
"Encrypted note on %s": "Catatan ter-ekrip di %s",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Kunjungi tautan ini untuk melihat catatan. Memberikan alamat URL pada siapapun juga, akan mengizinkan mereka untuk mengakses catatan, so pasti gitu loh Kaka.",
"URL shortener may expose your decrypt key in URL.": "Pemendek URL mungkin akan menampakkan kunci dekrip Anda dalam URL.",
"Save paste": "Simpan paste",
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes."
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes.",
"Trying to shorten a URL that isn't pointing at our instance.": "Trying to shorten a URL that isn't pointing at our instance.",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".",
"Error parsing YOURLS response.": "Error parsing YOURLS response."
}

View File

@@ -182,9 +182,12 @@
"Use Current Timezone": "Usa Fuso Orario Corrente",
"Convert To UTC": "Converti a UTC",
"Close": "Chiudi",
"Encrypted note on PrivateBin": "Nota crittografata su PrivateBin",
"Encrypted note on %s": "Nota crittografata su %s",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visita questo collegamento per vedere la nota. Dare l'URL a chiunque consente anche a loro di accedere alla nota.",
"URL shortener may expose your decrypt key in URL.": "URL shortener può esporre la tua chiave decrittografata nell'URL.",
"Save paste": "Salva il messagio",
"Your IP is not authorized to create pastes.": "Il tuo IP non è autorizzato a creare dei messaggi."
"Your IP is not authorized to create pastes.": "Il tuo IP non è autorizzato a creare dei messaggi.",
"Trying to shorten a URL that isn't pointing at our instance.": "Trying to shorten a URL that isn't pointing at our instance.",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".",
"Error parsing YOURLS response.": "Error parsing YOURLS response."
}

View File

@@ -182,9 +182,12 @@
"Use Current Timezone": "Use Current Timezone",
"Convert To UTC": "Convert To UTC",
"Close": "Close",
"Encrypted note on PrivateBin": "Encrypted note on PrivateBin",
"Encrypted note on %s": "Encrypted note on %s",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.",
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
"Save paste": "Save paste",
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes."
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes.",
"Trying to shorten a URL that isn't pointing at our instance.": "Trying to shorten a URL that isn't pointing at our instance.",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".",
"Error parsing YOURLS response.": "Error parsing YOURLS response."
}

View File

@@ -182,9 +182,12 @@
"Use Current Timezone": "Use Current Timezone",
"Convert To UTC": "galfi lo cabni la utc",
"Close": "ganlo",
"Encrypted note on PrivateBin": ".i lo lo notci ku mifra cu zvati sivlolnitvanku'a",
"Encrypted note on %s": ".i lo lo notci ku mifra cu zvati %s",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.",
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
"Save paste": "rejgau fukpi",
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes."
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes.",
"Trying to shorten a URL that isn't pointing at our instance.": "Trying to shorten a URL that isn't pointing at our instance.",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".",
"Error parsing YOURLS response.": "Error parsing YOURLS response."
}

View File

@@ -182,9 +182,12 @@
"Use Current Timezone": "Use Current Timezone",
"Convert To UTC": "Convert To UTC",
"Close": "Close",
"Encrypted note on PrivateBin": "Encrypted note on PrivateBin",
"Encrypted note on %s": "Encrypted note on %s",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.",
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
"Save paste": "Save paste",
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes."
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes.",
"Trying to shorten a URL that isn't pointing at our instance.": "Trying to shorten a URL that isn't pointing at our instance.",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".",
"Error parsing YOURLS response.": "Error parsing YOURLS response."
}

View File

@@ -182,9 +182,12 @@
"Use Current Timezone": "Use Current Timezone",
"Convert To UTC": "Convert To UTC",
"Close": "Close",
"Encrypted note on PrivateBin": "Encrypted note on PrivateBin",
"Encrypted note on %s": "Encrypted note on %s",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.",
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
"Save paste": "Save paste",
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes."
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes.",
"Trying to shorten a URL that isn't pointing at our instance.": "Trying to shorten a URL that isn't pointing at our instance.",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".",
"Error parsing YOURLS response.": "Error parsing YOURLS response."
}

View File

@@ -182,9 +182,12 @@
"Use Current Timezone": "Naudoti esamą laiko juostą",
"Convert To UTC": "Konvertuoti į UTC",
"Close": "Užverti",
"Encrypted note on PrivateBin": "Šifruoti užrašai ties PrivateBin",
"Encrypted note on %s": "Šifruoti užrašai ties %s",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Norėdami matyti užrašus, aplankykite šį tinklalapį. Pasidalinus šiuo URL adresu su kitais žmonėmis, jiems taip pat bus leidžiama prieiga prie šių užrašų.",
"URL shortener may expose your decrypt key in URL.": "URL trumpinimo įrankis gali atskleisti URL adrese jūsų iššifravimo raktą.",
"Save paste": "Įrašyti įdėjimą",
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes."
"Your IP is not authorized to create pastes.": "Jūsų IP adresas neturi įgaliojimų kurti įdėjimų.",
"Trying to shorten a URL that isn't pointing at our instance.": "Trying to shorten a URL that isn't pointing at our instance.",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".",
"Error parsing YOURLS response.": "Error parsing YOURLS response."
}

View File

@@ -35,7 +35,7 @@
"Discussion": "Discussie",
"Toggle navigation": "Navigatie openen/sluiten",
"%d seconds": [
"%d second",
"%d seconde",
"%d seconden",
"%d seconds (2nd plural)",
"%d seconds (3rd plural)"
@@ -72,14 +72,14 @@
],
"%d years": [
"%d jaar",
"%d jaaren",
"%d jaren",
"%d years (2nd plural)",
"%d years (3rd plural)"
],
"Never": "Nooit",
"Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.": "Opmerking: Dit is een testservice: Gegevens kunnen op elk gegeven moment verwijderd worden.",
"This document will expire in %d seconds.": [
"Dit document verloopt over %d second.",
"Dit document verloopt over %d seconde.",
"Dit document verloopt over %d seconden.",
"Dit document verloopt over %d seconden.",
"Dit document verloopt over %d seconden."
@@ -108,7 +108,7 @@
"Dit document verloopt over %d maanden.",
"Dit document verloopt over %d maanden."
],
"Please enter the password for this paste:": "Voer het wachtwoord in voor deze geplakte tekst:",
"Please enter the password for this paste:": "Voer het wachtwoord in voor deze geplakte tekst:",
"Could not decrypt data (Wrong key?)": "Kon de gegevens niet decoderen (verkeerde sleutel?)",
"Could not delete the paste, it was not stored in burn after reading mode.": "Verwijderen van de geplakte tekst niet mogelijk, deze werd niet opgeslagen in 'vernietig na lezen' modus.",
"FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.": "FOR YOUR EYES ONLY. Sluit dit venster niet, dit bericht kan niet opnieuw worden weergegeven.",
@@ -129,7 +129,7 @@
"Your paste is <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Hit [Ctrl]+[c] to copy)</span>": "Uw geplakte tekst is <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Druk [Ctrl]+[c] om te kopiëren)</span>",
"Delete data": "Gegevens wissen",
"Could not create paste: %s": "Kon de geplakte tekst niet aanmaken: %s",
"Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)": "Kon de geplakte tekst niet decoderen: Decoderingssleutel ontbreekt in URL (Hebt u een redirector of een URL-verkorter gebruikt die een deel van de URL verwijdert?)",
"Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)": "Kon de geplakte tekst niet decoderen: Decoderingssleutel ontbreekt in URL (Hebt u een redirector of een URL-verkorter gebruikt die een deel van de URL verwijdert?)",
"B": "B",
"KiB": "KiB",
"MiB": "MiB",
@@ -164,27 +164,30 @@
"Preparing new paste…": "Nieuwe geplakte tekst voorbereiden…",
"In case this message never disappears please have a look at <a href=\"%s\">this FAQ for information to troubleshoot</a>.": "In het geval dat dit bericht nooit verdwijnt, kijkt u dan eens naar <a href=\"%s\"> veelgestelde vragen voor informatie over het oplossen van problemen </a>.",
"+++ no paste text +++": "+++ geen geplakte tekst +++",
"Could not get paste data: %s": "Could not get paste data: %s",
"QR code": "QR code",
"This website is using an insecure HTTP connection! Please use it only for testing.": "This website is using an insecure HTTP connection! Please use it only for testing.",
"For more information <a href=\"%s\">see this FAQ entry</a>.": "For more information <a href=\"%s\">see this FAQ entry</a>.",
"Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.": "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.",
"Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.": "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.",
"waiting on user to provide a password": "waiting on user to provide a password",
"Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.": "Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.",
"Retry": "Retry",
"Showing raw text…": "Showing raw text…",
"Notice:": "Notice:",
"This link will expire after %s.": "This link will expire after %s.",
"This link can only be accessed once, do not use back or refresh button in your browser.": "This link can only be accessed once, do not use back or refresh button in your browser.",
"Could not get paste data: %s": "Kon geen klembordgegevens verkrijgen: %s",
"QR code": "QR-code",
"This website is using an insecure HTTP connection! Please use it only for testing.": "Deze website gebruikt een onveilige HTTP-verbinding! Gelieve deze enkel te gebruiken om te testen.",
"For more information <a href=\"%s\">see this FAQ entry</a>.": "Voor meer informatie <a href=\"%s\">zie dit FAQ-artikel</a>.",
"Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.": "Uw browser kan een HTTPS-verbinding nodig hebben om de WebCrypto API te ondersteunen. Probeer <a href=\"%s\">het met HTTPS</a>.",
"Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.": "Uw browser ondersteunt WebAssembly niet, wat wordt gebruikt voor zlib compressie. U kunt niet-gecomprimeerde documenten maken, maar geen gecomprimeerde documenten lezen.",
"waiting on user to provide a password": "wachtend op gebruiker om een wachtwoord te geven",
"Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.": "Kon de gegevens niet decoderen. Heeft u een verkeerd wachtwoord ingevoerd? Probeer het opnieuw met de knop bovenaan.",
"Retry": "Opnieuw proberen",
"Showing raw text…": "Platte tekst tonen…",
"Notice:": "Let op:",
"This link will expire after %s.": "Deze link vervalt na %s.",
"This link can only be accessed once, do not use back or refresh button in your browser.": "Deze link kan slechts eenmaal worden geopend, gebruik niet de terug- of verversknop in uw browser.",
"Link:": "Link:",
"Recipient may become aware of your timezone, convert time to UTC?": "Recipient may become aware of your timezone, convert time to UTC?",
"Use Current Timezone": "Use Current Timezone",
"Convert To UTC": "Convert To UTC",
"Close": "Close",
"Encrypted note on PrivateBin": "Encrypted note on PrivateBin",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.",
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
"Save paste": "Save paste",
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes."
"Recipient may become aware of your timezone, convert time to UTC?": "Ontvanger kan zich bewust worden van uw tijdzone, tijd omzetten naar UTC?",
"Use Current Timezone": "Gebruik huidige tijdzone",
"Convert To UTC": "Omzetten naar UTC",
"Close": "Sluiten",
"Encrypted note on %s": "Versleutelde notitie op %s",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Bezoek deze link om de notitie te bekijken. Als je de URL aan iemand geeft, kan die de notitie ook bekijken.",
"URL shortener may expose your decrypt key in URL.": "URL-verkorter kan uw ontcijferingssleutel in URL blootleggen.",
"Save paste": "Notitie opslaan",
"Your IP is not authorized to create pastes.": "Uw IP-adres is niet gemachtigd om geplakte tekst te maken.",
"Trying to shorten a URL that isn't pointing at our instance.": "Trying to shorten a URL that isn't pointing at our instance.",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".",
"Error parsing YOURLS response.": "Error parsing YOURLS response."
}

View File

@@ -182,9 +182,12 @@
"Use Current Timezone": "Bruk gjeldende tidssone",
"Convert To UTC": "Konverter til UTC",
"Close": "Steng",
"Encrypted note on PrivateBin": "Kryptert notat på PrivateBin",
"Encrypted note on %s": "Kryptert notat på %s",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Besøk denne lenken for å se notatet. Hvis lenken deles med andre, vil de også kunne se notatet.",
"URL shortener may expose your decrypt key in URL.": "URL forkorter kan avsløre dekrypteringsnøkkelen.",
"Save paste": "Lagre utklipp",
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes."
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes.",
"Trying to shorten a URL that isn't pointing at our instance.": "Trying to shorten a URL that isn't pointing at our instance.",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".",
"Error parsing YOURLS response.": "Error parsing YOURLS response."
}

View File

@@ -182,9 +182,12 @@
"Use Current Timezone": "Utilizar lactual",
"Convert To UTC": "Convertir en UTC",
"Close": "Tampar",
"Encrypted note on PrivateBin": "Nòtas chifradas sus PrivateBin",
"Encrypted note on %s": "Nòtas chifradas sus %s",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visitatz aqueste ligam per veire la nòta. Fornir lo ligam a qualquun mai li permet tanben daccedir a la nòta.",
"URL shortener may expose your decrypt key in URL.": "Los espleches dacorchiment dURL pòdon expausar la clau de deschiframent dins lURL.",
"Save paste": "Enregistrar lo tèxt",
"Your IP is not authorized to create pastes.": "Vòstra adreça IP a pas lautorizacion de crear de tèxtes."
"Your IP is not authorized to create pastes.": "Vòstra adreça IP a pas lautorizacion de crear de tèxtes.",
"Trying to shorten a URL that isn't pointing at our instance.": "Trying to shorten a URL that isn't pointing at our instance.",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".",
"Error parsing YOURLS response.": "Error parsing YOURLS response."
}

View File

@@ -182,9 +182,12 @@
"Use Current Timezone": "Use Current Timezone",
"Convert To UTC": "Convert To UTC",
"Close": "Close",
"Encrypted note on PrivateBin": "Encrypted note on PrivateBin",
"Encrypted note on %s": "Encrypted note on %s",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.",
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
"Save paste": "Save paste",
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes."
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes.",
"Trying to shorten a URL that isn't pointing at our instance.": "Trying to shorten a URL that isn't pointing at our instance.",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".",
"Error parsing YOURLS response.": "Error parsing YOURLS response."
}

View File

@@ -182,9 +182,12 @@
"Use Current Timezone": "Usar Fuso Horário Atual",
"Convert To UTC": "Converter para UTC",
"Close": "Fechar",
"Encrypted note on PrivateBin": "Nota criptografada no PrivateBin",
"Encrypted note on %s": "Nota criptografada no %s",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visite esse link para ver a nota. Dar a URL para qualquer um permite que eles também acessem a nota.",
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
"Save paste": "Save paste",
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes."
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes.",
"Trying to shorten a URL that isn't pointing at our instance.": "Trying to shorten a URL that isn't pointing at our instance.",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".",
"Error parsing YOURLS response.": "Error parsing YOURLS response."
}

View File

@@ -182,9 +182,12 @@
"Use Current Timezone": "Использовать текущий часовой пояс",
"Convert To UTC": "Конвертировать в UTC",
"Close": "Закрыть",
"Encrypted note on PrivateBin": "Зашифрованная запись на PrivateBin",
"Encrypted note on %s": "Зашифрованная запись на %s",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Посетите эту ссылку чтобы просмотреть запись. Передача ссылки кому либо позволит им получить доступ к записи тоже.",
"URL shortener may expose your decrypt key in URL.": "Сервис сокращения ссылок может получить ваш ключ расшифровки из ссылки.",
"Save paste": "Сохранить запись",
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes."
"Your IP is not authorized to create pastes.": "Вашему IP адресу не разрешено создавать записи.",
"Trying to shorten a URL that isn't pointing at our instance.": "Trying to shorten a URL that isn't pointing at our instance.",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".",
"Error parsing YOURLS response.": "Error parsing YOURLS response."
}

193
i18n/sk.json Normal file
View File

@@ -0,0 +1,193 @@
{
"PrivateBin": "PrivateBin",
"%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s je minimalistický, open source online pastebin, kde server nemá žiadne znalosti o vložených údajoch. Údaje sú šifrované/dešifrované %sv prehliadači%s pomocou 256-bitového AES.",
"More information on the <a href=\"https://privatebin.info/\">project page</a>.": "Viac informácií na <a href=\"https://privatebin.info/\">stránke projektu</a>.",
"Because ignorance is bliss": "Pretože nevedomosť je sladká",
"en": "sk",
"Paste does not exist, has expired or has been deleted.": "Vložený text neexistuje, jeho platnosť vypršala alebo bol vymazaný.",
"%s requires php %s or above to work. Sorry.": "%s vyžaduje php %s alebo vyššie. Prepáčte.",
"%s requires configuration section [%s] to be present in configuration file.": "%s vyžaduje, aby bola v konfiguračnom súbore prítomná sekcia [%s].",
"Please wait %d seconds between each post.": [
"Počet sekúnd do ďalšieho príspevku: %d",
"Počet sekúnd do ďalšieho príspevku: %d",
"Počet sekúnd do ďalšieho príspevku: %d",
"Počet sekúnd do ďalšieho príspevku: %d"
],
"Paste is limited to %s of encrypted data.": "Príspevok je obmedzený na %s zašifrovaných údajov.",
"Invalid data.": "Neplatné údaje.",
"You are unlucky. Try again.": "Ľutujem. Skúste to znova.",
"Error saving comment. Sorry.": "Pri ukladaní komentára sa vyskytla chyba.",
"Error saving paste. Sorry.": "Pri ukladaní príspevku sa vyskytla chyba.",
"Invalid paste ID.": "Chybne vložené ID.",
"Paste is not of burn-after-reading type.": "Príspevok nieje nastavený na zmazanie po prečítaní.",
"Wrong deletion token. Paste was not deleted.": "Nesprávny token odstránenia. Príspevok nebol odstránený.",
"Paste was properly deleted.": "Príspevok bol správne vymazaný.",
"JavaScript is required for %s to work. Sorry for the inconvenience.": "Na fungovanie %s je potrebný JavaScript. Ospravedlňujeme sa za nepríjemnosti.",
"%s requires a modern browser to work.": "%s vyžaduje na fungovanie moderný prehliadač.",
"New": "Nový",
"Send": "Odoslať",
"Clone": "Klonovať",
"Raw text": "Surový text",
"Expires": "Expirácia",
"Burn after reading": "Po prečítaní zmazať",
"Open discussion": "Povoliť komentáre",
"Password (recommended)": "Heslo (doporučené)",
"Discussion": "Komentáre",
"Toggle navigation": "Prepnúť navigáciu",
"%d seconds": [
"%d sekunda",
"%d sekundy",
"%d sekúnd",
"%d sekúnd"
],
"%d minutes": [
"%d minúta",
"%d minúty",
"%d minút",
"%d minút"
],
"%d hours": [
"%d hodina",
"%d hodiny",
"%d hodín",
"%d hodín"
],
"%d days": [
"%d deň",
"%d dni",
"%d dní",
"%d dní"
],
"%d weeks": [
"%d týždeň",
"%d týždne",
"%d týždňov",
"%d týždňov"
],
"%d months": [
"%d mesiac",
"%d mesiace",
"%d mesiacov",
"%d mesiacov"
],
"%d years": [
"%d rok",
"%d roky",
"%d rokov",
"%d rokov"
],
"Never": "Nikdy",
"Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.": "Poznámka: Toto je testovacia služba: Údaje môžu byť kedykoľvek vymazané. Pri zneužití tejto služby zomrú mačiatka.",
"This document will expire in %d seconds.": [
"Platnosť tohto dokumentu vyprší o %d sekundu.",
"Platnosť tohto dokumentu vyprší o %d sekundy.",
"Platnosť tohto dokumentu vyprší o %d sekúnd.",
"Platnosť tohto dokumentu vyprší o %d sekúnd."
],
"This document will expire in %d minutes.": [
"Platnosť tohto dokumentu vyprší o %d minútu.",
"Platnosť tohto dokumentu vyprší o %d minúty.",
"Platnosť tohto dokumentu vyprší o %d minút.",
"Platnosť tohto dokumentu vyprší o %d minút."
],
"This document will expire in %d hours.": [
"Platnosť tohto dokumentu vyprší o %d hodinu.",
"Platnosť tohto dokumentu vyprší o %d hodiny.",
"Platnosť tohto dokumentu vyprší o %d hodín.",
"Platnosť tohto dokumentu vyprší o %d hodín."
],
"This document will expire in %d days.": [
"Platnosť tohto dokumentu vyprší o %d deň.",
"Platnosť tohto dokumentu vyprší o %d dni.",
"Platnosť tohto dokumentu vyprší o %d dní.",
"Platnosť tohto dokumentu vyprší o %d dní."
],
"This document will expire in %d months.": [
"Platnosť tohto dokumentu vyprší o %d mesiac.",
"Platnosť tohto dokumentu vyprší o %d mesiace.",
"Platnosť tohto dokumentu vyprší o %d mesiacov.",
"Platnosť tohto dokumentu vyprší o %d mesiacov."
],
"Please enter the password for this paste:": "Zadajte prosím heslo:",
"Could not decrypt data (Wrong key?)": "Nepodarilo sa dešifrovať údaje (nesprávny kľúč?)",
"Could not delete the paste, it was not stored in burn after reading mode.": "Nepodarilo sa odstrániť príspevok, nebol uložený v režime zmazania po prečítaní.",
"FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.": "IBA PRE VAŠE OČI. Toto okno nezatvárajte, táto správa sa nedá znova zobraziť.",
"Could not decrypt comment; Wrong key?": "Nepodarilo sa dešifrovať komentár. Nesprávny kľúč?",
"Reply": "Odpovedať",
"Anonymous": "Anonymný",
"Avatar generated from IP address": "Avatar vygenerovaný z IP adresy",
"Add comment": "Pridať komentár",
"Optional nickname…": "Voliteľná prezývka…",
"Post comment": "Odoslať komentár",
"Sending comment…": "Odosielanie komentára…",
"Comment posted.": "Komentár odoslaný.",
"Could not refresh display: %s": "Nepodarilo sa obnoviť zobrazenie: %s",
"unknown status": "neznámy stav",
"server error or not responding": "chyba servera alebo server neodpovedá",
"Could not post comment: %s": "Nepodarilo sa pridať komentár: %s",
"Sending paste…": "Odosiela sa príspevok…",
"Your paste is <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Hit [Ctrl]+[c] to copy)</span>": "Váš príspevok je <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(skopírujte stlačením [Ctrl]+[c])</span>",
"Delete data": "Odstrániť dáta",
"Could not create paste: %s": "Nepodarilo sa vytvoriť príspevok: %s",
"Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)": "Nie je možné dešifrovať príspevok: V URL adrese chýba dešifrovací kľúč (Použili ste presmerovač alebo skracovač adresy, ktorý odstráni časť adresy URL?)",
"B": "B",
"KiB": "KiB",
"MiB": "MiB",
"GiB": "GiB",
"TiB": "TiB",
"PiB": "PiB",
"EiB": "EiB",
"ZiB": "ZiB",
"YiB": "YiB",
"Format": "Formát",
"Plain Text": "Čistý text",
"Source Code": "Zdrojový kód",
"Markdown": "Markdown",
"Download attachment": "Stiahnuť prílohu",
"Cloned: '%s'": "Naklonované: '%s'",
"The cloned file '%s' was attached to this paste.": "K tomuto príspevku bol pripojený klonovaný súbor '%s'.",
"Attach a file": "Priložiť súbor",
"alternatively drag & drop a file or paste an image from the clipboard": "prípadne presuňte súbor myšou alebo vložte obrázok zo schránky",
"File too large, to display a preview. Please download the attachment.": "Súbor je príliš veľký na zobrazenie ukážky. Stiahnite si prosím prílohu.",
"Remove attachment": "Odstrániť prílohu",
"Your browser does not support uploading encrypted files. Please use a newer browser.": "Váš prehliadač nepodporuje nahrávanie šifrovaných súborov. Použite prosím novší prehliadač.",
"Invalid attachment.": "Neplatná príloha.",
"Options": "Možnosti",
"Shorten URL": "Skrátiť URL",
"Editor": "Editor",
"Preview": "Náhľad",
"%s requires the PATH to end in a \"%s\". Please update the PATH in your index.php.": "%s vyžaduje, aby PATH končila na \"%s\". Aktualizujte prosím PATH vo svojom index.php.",
"Decrypt": "Dešifrovať",
"Enter password": "Zadajte heslo",
"Loading…": "Načítava sa…",
"Decrypting paste…": "Dešifrovanie príspevku…",
"Preparing new paste…": "Príprava nového príspevku…",
"In case this message never disappears please have a look at <a href=\"%s\">this FAQ for information to troubleshoot</a>.": "V prípade, že táto správa nezmizne, pozrite si <a href=\"%s\">tieto často kladené otázky, kde nájdete informácie na riešenie problémov</a>.",
"+++ no paste text +++": "+++ žiadny vložený text +++",
"Could not get paste data: %s": "Nepodarilo sa načítať údaje príspevku: %s",
"QR code": "QR kód",
"This website is using an insecure HTTP connection! Please use it only for testing.": "Táto webová stránka používa nezabezpečené pripojenie HTTP! Používajte ju len na testovanie.",
"For more information <a href=\"%s\">see this FAQ entry</a>.": "Pre viac informácií si pozrite <a href=\"%s\">tento záznam FAQ</a>.",
"Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.": "Váš prehliadač môže na podporu rozhrania WebCrypto API vyžadovať pripojenie HTTPS. Skúste <a href=\"%s\">prepnúť na HTTPS</a>.",
"Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.": "Váš prehliadač nepodporuje WebAssembly, ktorý sa používa na kompresiu zlib. Môžete vytvárať nekomprimované dokumenty, ale nemôžete čítať komprimované.",
"waiting on user to provide a password": "čakám na zadanie hesla",
"Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.": "Údaje sa nepodarilo dešifrovať. Zadali ste nesprávne heslo? Skúste to znova pomocou tlačidla v hornej časti.",
"Retry": "Opakovať",
"Showing raw text…": "Zobrazuje sa surový text…",
"Notice:": "Upozornenie:",
"This link will expire after %s.": "Platnosť odkazu vyprší za %s.",
"This link can only be accessed once, do not use back or refresh button in your browser.": "Tento odkaz je prístupný iba raz, nepoužívajte v prehliadači tlačidlo Späť ani Obnoviť.",
"Link:": "Odkaz:",
"Recipient may become aware of your timezone, convert time to UTC?": "Príjemca sa môže dozvedieť o vašom časovom pásme, previesť čas na UTC?",
"Use Current Timezone": "Použiť aktuálne časové pásmo",
"Convert To UTC": "Previesť na UTC",
"Close": "Zavrieť",
"Encrypted note on %s": "Zašifrovaná poznámka na %s",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Ak chcete zobraziť poznámku, navštívte tento odkaz. Poskytnutie adresy URL komukoľvek im umožní prístup aj k poznámke.",
"URL shortener may expose your decrypt key in URL.": "Skracovač adries URL môže odhaliť váš dešifrovací kľúč v adrese URL.",
"Save paste": "Uložiť príspevok",
"Your IP is not authorized to create pastes.": "Vaša IP adresa nie je oprávnená vytvárať príspevky.",
"Trying to shorten a URL that isn't pointing at our instance.": "Pokúšate sa skrátiť adresu URL, ktorá neukazuje na túto inštanciu.",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".",
"Error parsing YOURLS response.": "Error parsing YOURLS response."
}

View File

@@ -182,9 +182,12 @@
"Use Current Timezone": "Use Current Timezone",
"Convert To UTC": "Convert To UTC",
"Close": "Close",
"Encrypted note on PrivateBin": "Encrypted note on PrivateBin",
"Encrypted note on %s": "Encrypted note on %s",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.",
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
"Save paste": "Save paste",
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes."
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes.",
"Trying to shorten a URL that isn't pointing at our instance.": "Trying to shorten a URL that isn't pointing at our instance.",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".",
"Error parsing YOURLS response.": "Error parsing YOURLS response."
}

View File

@@ -182,9 +182,12 @@
"Use Current Timezone": "Use Current Timezone",
"Convert To UTC": "Convert To UTC",
"Close": "Close",
"Encrypted note on PrivateBin": "Encrypted note on PrivateBin",
"Encrypted note on %s": "Encrypted note on %s",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.",
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
"Save paste": "Save paste",
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes."
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes.",
"Trying to shorten a URL that isn't pointing at our instance.": "Trying to shorten a URL that isn't pointing at our instance.",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".",
"Error parsing YOURLS response.": "Error parsing YOURLS response."
}

View File

@@ -1,135 +1,135 @@
{
"PrivateBin": "PrivateBin",
"%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.",
"%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s sunucunun burada paylaştığınız veriyi görmediği, minimal, açık kaynak bir pastebindir. Veriler tarayıcıda 256 bit AES kullanılarak şifrelenir/çözülür.",
"More information on the <a href=\"https://privatebin.info/\">project page</a>.": "Daha fazla bilgi için <a href=\"https://privatebin.info/\">proje sayfası</a>'na göz atabilirsiniz.",
"Because ignorance is bliss": "Çünkü, cehalet mutluluktur",
"en": "tr",
"Paste does not exist, has expired or has been deleted.": "Paste does not exist, has expired or has been deleted.",
"%s requires php %s or above to work. Sorry.": "%s requires php %s or above to work. Sorry.",
"%s requires configuration section [%s] to be present in configuration file.": "%s requires configuration section [%s] to be present in configuration file.",
"%s requires php %s or above to work. Sorry.": "%s PHP %s veya daha üstünü gerektirir.",
"%s requires configuration section [%s] to be present in configuration file.": "%s konfigürasyon bölümünün [%s] bulunmasını gerektir.",
"Please wait %d seconds between each post.": [
"Please wait %d second between each post. (singular)",
"Please wait %d seconds between each post. (1st plural)",
"Please wait %d seconds between each post. (2nd plural)",
"Please wait %d seconds between each post. (3rd plural)"
"Lütfen paylaşımlar arasında %d saniye bekleyiniz.",
"Lütfen paylaşımlar arasında %d saniye bekleyiniz.",
"Lütfen paylaşımlar arasında %d saniye bekleyiniz.",
"Lütfen paylaşımlar arasında %d saniye bekleyiniz."
],
"Paste is limited to %s of encrypted data.": "Paste is limited to %s of encrypted data.",
"Paste is limited to %s of encrypted data.": "Yazılar %s şifreli veriyle sınırlıdır.",
"Invalid data.": "Geçersiz veri.",
"You are unlucky. Try again.": "Lütfen tekrar deneyiniz.",
"Error saving comment. Sorry.": "Error saving comment. Sorry.",
"Error saving paste. Sorry.": "Error saving paste. Sorry.",
"Invalid paste ID.": "Invalid paste ID.",
"Paste is not of burn-after-reading type.": "Paste is not of burn-after-reading type.",
"Wrong deletion token. Paste was not deleted.": "Wrong deletion token. Paste was not deleted.",
"Paste was properly deleted.": "Paste was properly deleted.",
"JavaScript is required for %s to work. Sorry for the inconvenience.": "JavaScript is required for %s to work. Sorry for the inconvenience.",
"%s requires a modern browser to work.": "%s requires a modern browser to work.",
"Error saving comment. Sorry.": "Yorum kaydedilemedi.",
"Error saving paste. Sorry.": "Yazı kaydedilemedi. Üzgünüz.",
"Invalid paste ID.": "Geçersiz yazı ID'si.",
"Paste is not of burn-after-reading type.": "Yazı okunduğunda silinmeyecek şekilde ayarlanmış.",
"Wrong deletion token. Paste was not deleted.": "Yanlış silme anahtarı. Yazı silinemedi.",
"Paste was properly deleted.": "Yazı başarıyla silindi.",
"JavaScript is required for %s to work. Sorry for the inconvenience.": "JavaScript %s 'in çalışması için gereklidir. Rahatsızlıktan dolayı özür dileriz.",
"%s requires a modern browser to work.": "%s çalışmak için çağdaş bir tarayıcı gerektirir.",
"New": "Yeni",
"Send": "Gönder",
"Clone": "Kopyala",
"Raw text": "Raw text",
"Raw text": "ık yazı",
"Expires": "Süre Sonu",
"Burn after reading": "Burn after reading",
"Burn after reading": "Okuduktan sonra sil",
"Open discussion": "Açık Tartışmalar",
"Password (recommended)": "Password (recommended)",
"Password (recommended)": "Şifre (önerilir)",
"Discussion": "Tartışma",
"Toggle navigation": "Gezinmeyi değiştir",
"%d seconds": [
"%d second (singular)",
"%d seconds (1st plural)",
"%d seconds (2nd plural)",
"%d seconds (3rd plural)"
"%d saniye",
"%d saniye",
"%d saniye",
"%d saniye"
],
"%d minutes": [
"%d minute (singular)",
"%d minutes (1st plural)",
"%d minutes (2nd plural)",
"%d minutes (3rd plural)"
"%d dakika",
"%d dakika",
"%d dakika",
"%d dakika"
],
"%d hours": [
"%d hour (singular)",
"%d hours (1st plural)",
"%d hours (2nd plural)",
"%d hours (3rd plural)"
"%d saat",
"%d saat",
"%d saat",
"%d saat"
],
"%d days": [
"%d day (singular)",
"%d days (1st plural)",
"%d days (2nd plural)",
"%d days (3rd plural)"
"%d gün",
"%d gün",
"%d gün",
"%d gün"
],
"%d weeks": [
"%d hafta (tekil)",
"%d haftalar (çoğul)",
"%d weeks (2nd plural)",
"%d weeks (3rd plural)"
"%d hafta",
"%d haftalar",
"%d hafta",
"%d hafta"
],
"%d months": [
"%d month (singular)",
"%d months (1st plural)",
"%d months (2nd plural)",
"%d months (3rd plural)"
"%d ay",
"%d ay",
"%d ay",
"%d ay"
],
"%d years": [
"%d year (singular)",
"%d years (1st plural)",
"%d years (2nd plural)",
"%d years (3rd plural)"
"%d yıl",
"%d yıl",
"%d yıl",
"%d yıl"
],
"Never": "Never",
"Never": "Asla",
"Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.": "Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.",
"This document will expire in %d seconds.": [
"This document will expire in %d second. (singular)",
"This document will expire in %d seconds. (1st plural)",
"This document will expire in %d seconds. (2nd plural)",
"This document will expire in %d seconds. (3rd plural)"
"Bu belge %d saniyede silinecektir.",
"Bu belge %d saniyede silinecektir.",
"Bu belge %d saniyede silinecektir.",
"Bu belge %d saniyede silinecektir."
],
"This document will expire in %d minutes.": [
"This document will expire in %d minute. (singular)",
"This document will expire in %d minutes. (1st plural)",
"This document will expire in %d minutes. (2nd plural)",
"This document will expire in %d minutes. (3rd plural)"
"Bu belge %d dakikada silinecektir.",
"Bu belge %d dakikada silinecektir.",
"Bu belge %d dakikada silinecektir.",
"Bu belge %d dakikada silinecektir."
],
"This document will expire in %d hours.": [
"This document will expire in %d hour. (singular)",
"This document will expire in %d hours. (1st plural)",
"This document will expire in %d hours. (2nd plural)",
"This document will expire in %d hours. (3rd plural)"
"Bu belge %d saatte silinecektir.",
"Bu belge %d saatte silinecektir.",
"Bu belge %d saatte silinecektir.",
"Bu belge %d saatte silinecektir."
],
"This document will expire in %d days.": [
"This document will expire in %d day. (singular)",
"This document will expire in %d days. (1st plural)",
"This document will expire in %d days. (2nd plural)",
"This document will expire in %d days. (3rd plural)"
"Bu belge %d günde silinecektir.",
"Bu belge %d günde silinecektir.",
"Bu belge %d günde silinecektir.",
"Bu belge %d günde silinecektir."
],
"This document will expire in %d months.": [
"This document will expire in %d month. (singular)",
"This document will expire in %d months. (1st plural)",
"This document will expire in %d months. (2nd plural)",
"This document will expire in %d months. (3rd plural)"
"Bu belge %d ayda silinecektir.",
"Bu belge %d ayda silinecektir.",
"Bu belge %d ayda silinecektir.",
"Bu belge %d ayda silinecektir."
],
"Please enter the password for this paste:": "Please enter the password for this paste:",
"Could not decrypt data (Wrong key?)": "Could not decrypt data (Wrong key?)",
"Could not delete the paste, it was not stored in burn after reading mode.": "Could not delete the paste, it was not stored in burn after reading mode.",
"FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.": "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.",
"Could not decrypt comment; Wrong key?": "Could not decrypt comment; Wrong key?",
"Please enter the password for this paste:": "Lütfen bu yazı için şifrenizi girin:",
"Could not decrypt data (Wrong key?)": "Şifre çözülemedi (Yanlış anahtar mı kullandınız?)",
"Could not delete the paste, it was not stored in burn after reading mode.": "Yazı silinemedi, okunduktan sonra silinmek için ayarlanmadı.",
"FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.": "BU DOSYAYI SADECE SİZ GÖRÜNTÜLEYEBİLİRSİNİZ. Bu pencereyi kapatmayın, yazıyı tekrar görüntüleyemeyeceksiniz.",
"Could not decrypt comment; Wrong key?": "Dosya şifresi çözülemedi, doğru anahtarı girdiğinizden emin misiniz?",
"Reply": "Cevapla",
"Anonymous": "Anonim",
"Avatar generated from IP address": "Avatar generated from IP address",
"Avatar generated from IP address": "IP adresinden oluşturulmuş avatar",
"Add comment": "Yorum ekle",
"Optional nickname…": "Optional nickname…",
"Optional nickname…": "İsteğe bağlı takma isim…",
"Post comment": "Yorumu gönder",
"Sending comment…": "Sending comment…",
"Sending comment…": "Yorum gönderiliyor…",
"Comment posted.": "Yorum gönderildi.",
"Could not refresh display: %s": "Could not refresh display: %s",
"unknown status": "unknown status",
"server error or not responding": "server error or not responding",
"Could not post comment: %s": "Could not post comment: %s",
"Sending paste…": "Sending paste…",
"Your paste is <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Hit [Ctrl]+[c] to copy)</span>": "Your paste is <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Hit [Ctrl]+[c] to copy)</span>",
"Could not refresh display: %s": "Görüntü yenilenemedi: %s",
"unknown status": "bilinmeyen durum",
"server error or not responding": "sunucu hatası veya yanıt vermiyor",
"Could not post comment: %s": "Yorum paylaşılamadı: %s",
"Sending paste…": "Yazı gönderiliyor…",
"Your paste is <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">(Hit [Ctrl]+[c] to copy)</span>": "Yazınız: <a id=\"pasteurl\" href=\"%s\">%s</a> <span id=\"copyhint\">([Ctrl]+[c] tuşlarına basarak kopyalayın.)</span>",
"Delete data": "Veriyi sil",
"Could not create paste: %s": "Could not create paste: %s",
"Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)": "Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)",
"Could not create paste: %s": "Yazı oluşturulamadı: %s",
"Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)": "Yazı şifresi çözülemedi, çözme anahtarı URL'de bulunamadı. (Buraya bir yönlendirici veya URL kısaltıcı kullanarak gelmiş olabilirsiniz.)",
"B": "B",
"KiB": "KiB",
"MiB": "MiB",
@@ -140,51 +140,54 @@
"ZiB": "ZiB",
"YiB": "YiB",
"Format": "Format",
"Plain Text": "Plain Text",
"Source Code": "Source Code",
"Plain Text": "Düz Yazı",
"Source Code": "Kaynak Kodu",
"Markdown": "Markdown",
"Download attachment": "Download attachment",
"Cloned: '%s'": "Cloned: '%s'",
"The cloned file '%s' was attached to this paste.": "The cloned file '%s' was attached to this paste.",
"Attach a file": "Attach a file",
"alternatively drag & drop a file or paste an image from the clipboard": "alternatively drag & drop a file or paste an image from the clipboard",
"File too large, to display a preview. Please download the attachment.": "File too large, to display a preview. Please download the attachment.",
"Remove attachment": "Remove attachment",
"Your browser does not support uploading encrypted files. Please use a newer browser.": "Your browser does not support uploading encrypted files. Please use a newer browser.",
"Invalid attachment.": "Invalid attachment.",
"Options": "Options",
"Shorten URL": "Shorten URL",
"Editor": "Editor",
"Download attachment": "Eki indir",
"Cloned: '%s'": "Klonlandı: '%s'",
"The cloned file '%s' was attached to this paste.": "Klonlanmış dosya '%s' bu yazıya eklendi.",
"Attach a file": "Dosya ekle",
"alternatively drag & drop a file or paste an image from the clipboard": "alternatif olarak dosyasyı yapıştırabilir veya sürükleyip bırakabilirsin",
"File too large, to display a preview. Please download the attachment.": "Dosya önizleme için çok büyük. Lütfen eki indirin.",
"Remove attachment": "Eki sil",
"Your browser does not support uploading encrypted files. Please use a newer browser.": "Tarayıcınız şifreli dosyaları desteklemiyor.",
"Invalid attachment.": "Geçersiz ek.",
"Options": "Seçenekler",
"Shorten URL": "URL kısaltma",
"Editor": "Düzenleyici",
"Preview": "Ön izleme",
"%s requires the PATH to end in a \"%s\". Please update the PATH in your index.php.": "%s requires the PATH to end in a \"%s\". Please update the PATH in your index.php.",
"Decrypt": "Decrypt",
"Decrypt": "Şifreyi çöz",
"Enter password": "Şifreyi girin",
"Loading…": "Yükleniyor…",
"Decrypting paste…": "Decrypting paste…",
"Preparing new paste…": "Preparing new paste…",
"Decrypting paste…": "Yazı şifresi çözülüyor…",
"Preparing new paste…": "Yeni yazı hazırlanıyor…",
"In case this message never disappears please have a look at <a href=\"%s\">this FAQ for information to troubleshoot</a>.": "In case this message never disappears please have a look at <a href=\"%s\">this FAQ for information to troubleshoot</a>.",
"+++ no paste text +++": "+++ no paste text +++",
"Could not get paste data: %s": "Could not get paste data: %s",
"Could not get paste data: %s": "Yazı verisi alınamıyor: %s",
"QR code": "QR kodu",
"This website is using an insecure HTTP connection! Please use it only for testing.": "This website is using an insecure HTTP connection! Please use it only for testing.",
"For more information <a href=\"%s\">see this FAQ entry</a>.": "For more information <a href=\"%s\">see this FAQ entry</a>.",
"Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.": "Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.",
"Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.": "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.",
"waiting on user to provide a password": "waiting on user to provide a password",
"Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.": "Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.",
"Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.": "Dosya şifresi çözülemedi, doğru şifreyi kullandığınıza emin misiniz? Üstteki buton ile tekrar deneyin.",
"Retry": "Yeniden Dene",
"Showing raw text…": "Showing raw text…",
"Showing raw text…": "ık yazı gösteriliyor…",
"Notice:": "Bildirim:",
"This link will expire after %s.": "This link will expire after %s.",
"This link can only be accessed once, do not use back or refresh button in your browser.": "This link can only be accessed once, do not use back or refresh button in your browser.",
"This link will expire after %s.": "Bu bağlantı şu kadar zaman sonra etkisiz kalacaktır: %s.",
"This link can only be accessed once, do not use back or refresh button in your browser.": "Bu bağlantı sadece bir kere erişilebilir, lütfen sayfayı yenilemeyiniz.",
"Link:": "Bağlantı:",
"Recipient may become aware of your timezone, convert time to UTC?": "Recipient may become aware of your timezone, convert time to UTC?",
"Use Current Timezone": "Use Current Timezone",
"Convert To UTC": "Convert To UTC",
"Recipient may become aware of your timezone, convert time to UTC?": "Alıcı zaman dilmini öğrenebilir, zaman dilimini UTC'ye çevirmek ister misin?",
"Use Current Timezone": "Şuanki zaman dilimini kullan",
"Convert To UTC": "UTC zaman dilimine çevir",
"Close": "Kapat",
"Encrypted note on PrivateBin": "Encrypted note on PrivateBin",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.",
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
"Save paste": "Save paste",
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes."
"Encrypted note on %s": "%s üzerinde şifrelenmiş not",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Notu görmek için bu bağlantıyı ziyaret et. Bağlantıya sahip olan birisi notu görebilir.",
"URL shortener may expose your decrypt key in URL.": "URL kısaltıcı şifreleme anahtarınızı URL içerisinde gösterebilir.",
"Save paste": "Yazıyı kaydet",
"Your IP is not authorized to create pastes.": "IP adresinizin yazı oluşturmaya yetkisi yoktur.",
"Trying to shorten a URL that isn't pointing at our instance.": "Trying to shorten a URL that isn't pointing at our instance.",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".",
"Error parsing YOURLS response.": "Error parsing YOURLS response."
}

View File

@@ -8,10 +8,10 @@
"%s requires php %s or above to work. Sorry.": "Для роботи %s потрібен php %s и вище. Вибачте.",
"%s requires configuration section [%s] to be present in configuration file.": "%s потрібна секція [%s] в конфігураційному файлі.",
"Please wait %d seconds between each post.": [
"Please wait %d second between each post. (singular)",
"Please wait %d seconds between each post. (1st plural)",
"Please wait %d seconds between each post. (2nd plural)",
"Please wait %d seconds between each post. (3rd plural)"
"Будь ласка, зачекайте %d секунду між створеннями.",
"Будь ласка, зачекайте %d секунди між створеннями.",
"Будь ласка, зачекайте %d секунд між створеннями.",
"Будь ласка, зачекайте %d секунд між створеннями."
],
"Paste is limited to %s of encrypted data.": "Розмір допису обмежений %s зашифрованих даних.",
"Invalid data.": "Неправильні дані.",
@@ -170,21 +170,24 @@
"For more information <a href=\"%s\">see this FAQ entry</a>.": "Для подробиць <a href=\"%s\">дивіться інформацію в FAQ</a>.",
"Your browser may require an HTTPS connection to support the WebCrypto API. Try <a href=\"%s\">switching to HTTPS</a>.": "Ваш переглядач вимагає підключення HTTPS для підтримки WebCrypto API. Спробуйте <a href=\"%s\">перемкнутися на HTTPS</a>.",
"Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.": "Ваш переглядач не підтримує WebAssembly, що використовується для стиснення zlib. Ви можете створювати нестиснені документи, але не зможете читати стиснені.",
"waiting on user to provide a password": "waiting on user to provide a password",
"Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.": "Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.",
"Retry": "Retry",
"Showing raw text…": "Showing raw text…",
"Notice:": "Notice:",
"This link will expire after %s.": "This link will expire after %s.",
"This link can only be accessed once, do not use back or refresh button in your browser.": "This link can only be accessed once, do not use back or refresh button in your browser.",
"Link:": "Link:",
"Recipient may become aware of your timezone, convert time to UTC?": "Recipient may become aware of your timezone, convert time to UTC?",
"Use Current Timezone": "Use Current Timezone",
"Convert To UTC": "Convert To UTC",
"Close": "Close",
"Encrypted note on PrivateBin": "Encrypted note on PrivateBin",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.",
"URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.",
"Save paste": "Save paste",
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes."
"waiting on user to provide a password": "очікування користувача для вводу паролю",
"Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.": "Не вдалося розшифрувати дані. Може, ви ввели неправильний пароль? Спробуйте знову за допомогою верхньої кнопки.",
"Retry": "Спробуйте ще раз",
"Showing raw text…": "Відображається неформатований текст…",
"Notice:": "Зверніть увагу:",
"This link will expire after %s.": "Термін дії цього посилання сплине через %s.",
"This link can only be accessed once, do not use back or refresh button in your browser.": "Дане посилання доступна тільки один раз, не натискайте кнопку назад та не обновляйте сторінку браузера.",
"Link:": "Посилання:",
"Recipient may become aware of your timezone, convert time to UTC?": "Отримувач дізнається ваш часовий пояс, перетворити час в UTC?",
"Use Current Timezone": "Використовувати поточний часовий пояс",
"Convert To UTC": "Конвертувати в UTC",
"Close": "Закрити",
"Encrypted note on %s": "Зашифрована нотатка на %s",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Відвідайте посилання, щоб переглянути нотатку. Передача посилання будь-кому дозволить їм переглянути нотатку.",
"URL shortener may expose your decrypt key in URL.": "Сервіс скорочення посилань може викрити ваш ключ дешифрування з URL.",
"Save paste": "Зберегти вставку",
"Your IP is not authorized to create pastes.": "Вашому IP не дозволено створювати вставки.",
"Trying to shorten a URL that isn't pointing at our instance.": "Trying to shorten a URL that isn't pointing at our instance.",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".",
"Error parsing YOURLS response.": "Error parsing YOURLS response."
}

View File

@@ -182,9 +182,12 @@
"Use Current Timezone": "使用当前时区",
"Convert To UTC": "转换为 UTC",
"Close": "关闭",
"Encrypted note on PrivateBin": "PrivateBin 上的加密笔记",
"Encrypted note on %s": "%s 上的加密笔记",
"Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "访问此链接来查看该笔记。将此 URL 发送给任何人即可允许其访问该笔记。",
"URL shortener may expose your decrypt key in URL.": "短链接服务可能会暴露您在 URL 中的解密密钥。",
"Save paste": "保存内容",
"Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes."
"Your IP is not authorized to create pastes.": "您的 IP 无权创建粘贴。",
"Trying to shorten a URL that isn't pointing at our instance.": "Trying to shorten a URL that isn't pointing at our instance.",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".",
"Error parsing YOURLS response.": "Error parsing YOURLS response."
}

View File

@@ -12,7 +12,7 @@ global.WebCrypto = require('@peculiar/webcrypto').Crypto;
// application libraries to test
global.$ = global.jQuery = require('./jquery-3.6.0');
global.RawDeflate = require('./rawinflate-0.3').RawDeflate;
global.zlib = require('./zlib-1.2.12').zlib;
global.zlib = require('./zlib-1.2.13').zlib;
require('./prettify');
global.prettyPrint = window.PR.prettyPrint;
global.prettyPrintOne = window.PR.prettyPrintOne;

1785
js/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -627,7 +627,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
* @prop {string[]}
* @readonly
*/
const supportedLanguages = ['bg', 'ca', 'co', 'cs', 'de', 'es', 'et', 'fi', 'fr', 'he', 'hu', 'id', 'it', 'jbo', 'lt', 'no', 'nl', 'pl', 'pt', 'oc', 'ru', 'sl', 'uk', 'zh'];
const supportedLanguages = ['bg', 'ca', 'co', 'cs', 'de', 'el', 'es', 'et', 'fi', 'fr', 'he', 'hu', 'id', 'it', 'jbo', 'lt', 'no', 'nl', 'pl', 'pt', 'oc', 'ru', 'sk', 'sl', 'tr', 'uk', 'zh'];
/**
* built in language
@@ -803,10 +803,12 @@ jQuery.PrivateBin = (function($, RawDeflate) {
switch (language)
{
case 'cs':
return n === 1 ? 0 : (n >= 2 && n <=4 ? 1 : 2);
case 'sk':
return n === 1 ? 0 : (n >= 2 && n <= 4 ? 1 : 2);
case 'co':
case 'fr':
case 'oc':
case 'tr':
case 'zh':
return n > 1 ? 1 : 0;
case 'he':
@@ -817,13 +819,13 @@ jQuery.PrivateBin = (function($, RawDeflate) {
case 'lt':
return n % 10 === 1 && n % 100 !== 11 ? 0 : ((n % 10 >= 2 && n % 100 < 10 || n % 100 >= 20) ? 1 : 2);
case 'pl':
return n === 1 ? 0 : (n % 10 >= 2 && n %10 <=4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2);
return n === 1 ? 0 : (n % 10 >= 2 && n %10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2);
case 'ru':
case 'uk':
return n % 10 === 1 && n % 100 !== 11 ? 0 : (n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2);
case 'sl':
return n % 100 === 1 ? 1 : (n % 100 === 2 ? 2 : (n % 100 === 3 || n % 100 === 4 ? 3 : 0));
// bg, ca, de, en, es, et, fi, hu, it, nl, no, pt
// bg, ca, de, el, en, es, et, fi, hu, it, nl, no, pt
default:
return n !== 1 ? 1 : 0;
}

View File

@@ -26,9 +26,9 @@
let buff;
if (typeof fetch === 'undefined') {
buff = fs.readFileSync('zlib-1.2.12.wasm');
buff = fs.readFileSync('zlib-1.2.13.wasm');
} else {
const resp = await fetch('js/zlib-1.2.12.wasm');
const resp = await fetch('js/zlib-1.2.13.wasm');
buff = await resp.arrayBuffer();
}
const module = await WebAssembly.compile(buff);

View File

@@ -93,6 +93,10 @@ class Configuration
'model_options' => array(
'dir' => 'data',
),
'yourls' => array(
'signature' => '',
'apiurl' => '',
),
);
/**
@@ -153,8 +157,25 @@ class Configuration
)
) {
$values = array(
'bucket' => getenv('PRIVATEBIN_GCS_BUCKET') ? getenv('PRIVATEBIN_GCS_BUCKET') : null,
'prefix' => 'pastes',
'bucket' => getenv('PRIVATEBIN_GCS_BUCKET') ? getenv('PRIVATEBIN_GCS_BUCKET') : null,
'prefix' => 'pastes',
'uniformacl' => false,
);
} elseif (
$section == 'model_options' && in_array(
$this->_configuration['model']['class'],
array('S3Storage')
)
) {
$values = array(
'region' => null,
'version' => null,
'endpoint' => null,
'accesskey' => null,
'secretkey' => null,
'use_path_style_endpoint' => null,
'bucket' => null,
'prefix' => '',
);
}

View File

@@ -136,6 +136,9 @@ class Controller
case 'jsonld':
$this->_jsonld($this->_request->getParam('jsonld'));
return;
case 'yourlsproxy':
$this->_yourlsproxy($this->_request->getParam('link'));
break;
}
// output JSON or HTML
@@ -341,8 +344,11 @@ class Controller
header('Content-Security-Policy: ' . $this->_conf->getKey('cspheader'));
header('Cross-Origin-Resource-Policy: same-origin');
header('Cross-Origin-Embedder-Policy: require-corp');
header('Cross-Origin-Opener-Policy: same-origin');
header('Permissions-Policy: interest-cohort=()');
// disabled, because it prevents links from a paste to the same site to
// be opened. Didn't work with `same-origin-allow-popups` either.
// See issue https://github.com/PrivateBin/PrivateBin/issues/970 for details.
// header('Cross-Origin-Opener-Policy: same-origin');
header('Permissions-Policy: browsing-topics=()');
header('Referrer-Policy: no-referrer');
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: deny');
@@ -375,9 +381,15 @@ class Controller
);
$page = new View;
$page->assign('NAME', $this->_conf->getKey('name'));
$page->assign('BASEPATH', I18n::_($this->_conf->getKey('basepath')));
$page->assign('CSPHEADER', $metacspheader);
$page->assign('ERROR', I18n::_($this->_error));
$page->assign('NAME', $this->_conf->getKey('name'));
if ($this->_request->getOperation() === 'yourlsproxy') {
$page->assign('SHORTURL', $this->_status);
$page->draw('yourlsproxy');
return;
}
$page->assign('BASEPATH', I18n::_($this->_conf->getKey('basepath')));
$page->assign('STATUS', I18n::_($this->_status));
$page->assign('VERSION', self::VERSION);
$page->assign('DISCUSSION', $this->_conf->getKey('discussion'));
@@ -402,7 +414,6 @@ class Controller
$page->assign('HTTPWARNING', $this->_conf->getKey('httpwarning'));
$page->assign('HTTPSLINK', 'https://' . $this->_request->getHost() . $this->_request->getRequestUri());
$page->assign('COMPRESSION', $this->_conf->getKey('compression'));
$page->assign('CSPHEADER', $metacspheader);
$page->draw($this->_conf->getKey('template'));
}
@@ -436,6 +447,22 @@ class Controller
echo $content;
}
/**
* proxies link to YOURLS, updates status or error with response
*
* @access private
* @param string $link
*/
private function _yourlsproxy($link)
{
$yourls = new YourlsProxy($this->_conf, $link);
if ($yourls->isError()) {
$this->_error = $yourls->getError();
} else {
$this->_status = $yourls->getUrl();
}
}
/**
* prepares JSON encoded status message
*

View File

@@ -100,7 +100,7 @@ class Database extends AbstractData
// MySQL uses backticks to quote identifiers by default,
// tell it to expect ANSI SQL double quotes
if (self::$_type === 'mysql' && defined('PDO::MYSQL_ATTR_INIT_COMMAND')) {
$options['opt'][PDO::MYSQL_ATTR_INIT_COMMAND] = "SET sql_mode='ANSI_QUOTES'";
$options['opt'][PDO::MYSQL_ATTR_INIT_COMMAND] = "SET SESSION sql_mode='ANSI_QUOTES'";
}
$tableQuery = self::_getTableQuery(self::$_type);
self::$_db = new PDO(
@@ -277,9 +277,9 @@ class Database extends AbstractData
}
// support v1 attachments
if (array_key_exists('attachment', $paste) && strlen($paste['attachment'])) {
if (array_key_exists('attachment', $paste) && !empty($paste['attachment'])) {
self::$_cache[$pasteid]['attachment'] = $paste['attachment'];
if (array_key_exists('attachmentname', $paste) && strlen($paste['attachmentname'])) {
if (array_key_exists('attachmentname', $paste) && !empty($paste['attachmentname'])) {
self::$_cache[$pasteid]['attachmentname'] = $paste['attachmentname'];
}
}
@@ -552,7 +552,7 @@ class Database extends AbstractData
$position = $key + 1;
if (is_int($parameter)) {
$statement->bindParam($position, $parameter, PDO::PARAM_INT);
} elseif (strlen($parameter) >= 4000) {
} elseif (is_string($parameter) && strlen($parameter) >= 4000) {
$statement->bindParam($position, $parameter, PDO::PARAM_STR, strlen($parameter));
} else {
$statement->bindParam($position, $parameter);
@@ -584,7 +584,7 @@ class Database extends AbstractData
// workaround for https://bugs.php.net/bug.php?id=46728
$result = array();
while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
$result[] = array_map('self::_sanitizeClob', $row);
$result[] = array_map('PrivateBin\Data\Database::_sanitizeClob', $row);
}
} else {
$result = $statement->fetchAll(PDO::FETCH_ASSOC);
@@ -593,7 +593,7 @@ class Database extends AbstractData
if (self::$_type === 'oci' && is_array($result)) {
// returned CLOB values are streams, convert these into strings
$result = $firstOnly ?
array_map('self::_sanitizeClob', $result) :
array_map('PrivateBin\Data\Database::_sanitizeClob', $result) :
$result;
}
return $result;
@@ -837,8 +837,10 @@ class Database extends AbstractData
end;'
);
} else {
// CREATE INDEX IF NOT EXISTS not supported as of Oracle MySQL <= 8.0
self::$_db->exec(
'CREATE INDEX IF NOT EXISTS "comment_parent" ON "' .
'CREATE INDEX "' .
self::_sanitizeIdentifier('comment_parent') . '" ON "' .
self::_sanitizeIdentifier('comment') . '" ("pasteid")'
);
}
@@ -941,16 +943,20 @@ class Database extends AbstractData
);
} else {
self::$_db->exec(
'CREATE UNIQUE INDEX IF NOT EXISTS "paste_dataid" ON "' .
'CREATE UNIQUE INDEX IF NOT EXISTS "' .
self::_sanitizeIdentifier('paste_dataid') . '" ON "' .
self::_sanitizeIdentifier('paste') . '" ("dataid")'
);
self::$_db->exec(
'CREATE UNIQUE INDEX IF NOT EXISTS "comment_dataid" ON "' .
'CREATE UNIQUE INDEX IF NOT EXISTS "' .
self::_sanitizeIdentifier('comment_dataid') . '" ON "' .
self::_sanitizeIdentifier('comment') . '" ("dataid")'
);
}
// CREATE INDEX IF NOT EXISTS not supported as of Oracle MySQL <= 8.0
self::$_db->exec(
'CREATE INDEX IF NOT EXISTS "comment_parent" ON "' .
'CREATE INDEX "' .
self::_sanitizeIdentifier('comment_parent') . '" ON "' .
self::_sanitizeIdentifier('comment') . '" ("pasteid")'
);
// no break, continue with updates for 0.22 and later

View File

@@ -351,7 +351,7 @@ class Filesystem extends AbstractData
$pastes = array();
$firstLevel = array_filter(
scandir(self::$_path),
'self::_isFirstLevelDir'
'PrivateBin\Data\Filesystem::_isFirstLevelDir'
);
if (count($firstLevel) > 0) {
// try at most 10 times the $batchsize pastes before giving up
@@ -359,7 +359,7 @@ class Filesystem extends AbstractData
$firstKey = array_rand($firstLevel);
$secondLevel = array_filter(
scandir(self::$_path . DIRECTORY_SEPARATOR . $firstLevel[$firstKey]),
'self::_isSecondLevelDir'
'PrivateBin\Data\Filesystem::_isSecondLevelDir'
);
// skip this folder in the next checks if it is empty

View File

@@ -37,6 +37,15 @@ class GoogleCloudStorage extends AbstractData
*/
private static $_prefix = 'pastes';
/**
* bucket acl type
*
* @access private
* @static
* @var bool
*/
private static $_uniformacl = false;
/**
* returns a Google Cloud Storage data backend.
*
@@ -62,6 +71,9 @@ class GoogleCloudStorage extends AbstractData
if (is_array($options) && array_key_exists('prefix', $options)) {
self::$_prefix = $options['prefix'];
}
if (is_array($options) && array_key_exists('uniformacl', $options)) {
self::$_uniformacl = $options['uniformacl'];
}
if (empty(self::$_client)) {
self::$_client = class_exists('StorageClientStub', false) ?
@@ -106,15 +118,18 @@ class GoogleCloudStorage extends AbstractData
$metadata[$k] = strval($v);
}
try {
self::$_bucket->upload(Json::encode($payload), array(
$data = array(
'name' => $key,
'chunkSize' => 262144,
'predefinedAcl' => 'private',
'metadata' => array(
'content-type' => 'application/json',
'metadata' => $metadata,
),
));
);
if (!self::$_uniformacl) {
$data['predefinedAcl'] = 'private';
}
self::$_bucket->upload(Json::encode($payload), $data);
} catch (Exception $e) {
error_log('failed to upload ' . $key . ' to ' . self::$_bucket->name() . ', ' .
trim(preg_replace('/\s\s+/', ' ', $e->getMessage())));
@@ -277,15 +292,18 @@ class GoogleCloudStorage extends AbstractData
$metadata['value'] = strval($value);
}
try {
self::$_bucket->upload($value, array(
$data = array(
'name' => $key,
'chunkSize' => 262144,
'predefinedAcl' => 'private',
'metadata' => array(
'content-type' => 'application/json',
'metadata' => $metadata,
),
));
);
if (!self::$_uniformacl) {
$data['predefinedAcl'] = 'private';
}
self::$_bucket->upload($value, $data);
} catch (Exception $e) {
error_log('failed to set key ' . $key . ' to ' . self::$_bucket->name() . ', ' .
trim(preg_replace('/\s\s+/', ' ', $e->getMessage())));

464
lib/Data/S3Storage.php Normal file
View File

@@ -0,0 +1,464 @@
<?php
/**
* S3.php
*
* an S3 compatible data backend for PrivateBin with CEPH/RadosGW in mind
* see https://docs.ceph.com/en/latest/radosgw/s3/php/
* based on lib/Data/GoogleCloudStorage.php from PrivateBin version 1.4.0
*
* @link https://github.com/PrivateBin/PrivateBin
* @copyright 2022 Felix J. Ogris (https://ogris.de/)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.4.1
*
* Installation:
* 1. Make sure you have composer.lock and composer.json in the document root of your PasteBin
* 2. If not, grab a copy from https://github.com/PrivateBin/PrivateBin
* 3. As non-root user, install the AWS SDK for PHP:
* composer require aws/aws-sdk-php
* (On FreeBSD, install devel/php-composer2 prior, e.g.: make -C /usr/ports/devel/php-composer2 install clean)
* 4. In cfg/conf.php, comment out all [model] and [model_options] settings
* 5. Still in cfg/conf.php, add a new [model] section:
* [model]
* class = S3Storage
* 6. Add a new [model_options] as well, e.g. for a Rados gateway as part of your CEPH cluster:
* [model_options]
* region = ""
* version = "2006-03-01"
* endpoint = "https://s3.my-ceph.invalid"
* use_path_style_endpoint = true
* bucket = "my-bucket"
* prefix = "privatebin" (place all PrivateBin data beneath this prefix)
* accesskey = "my-rados-user"
* secretkey = "my-rados-pass"
*/
namespace PrivateBin\Data;
use Aws\S3\Exception\S3Exception;
use Aws\S3\S3Client;
use PrivateBin\Json;
class S3Storage extends AbstractData
{
/**
* S3 client
*
* @access private
* @static
* @var S3Client
*/
private static $_client = null;
/**
* S3 client options
*
* @access private
* @static
* @var array
*/
private static $_options = array();
/**
* S3 bucket
*
* @access private
* @static
* @var string
*/
private static $_bucket = null;
/**
* S3 prefix for all PrivateBin data in this bucket
*
* @access private
* @static
* @var string
*/
private static $_prefix = '';
/**
* returns an S3 data backend.
*
* @access public
* @static
* @param array $options
* @return S3Storage
*/
public static function getInstance(array $options)
{
// if needed initialize the singleton
if (!(self::$_instance instanceof self)) {
self::$_instance = new self;
}
self::$_options = array();
self::$_options['credentials'] = array();
if (is_array($options) && array_key_exists('region', $options)) {
self::$_options['region'] = $options['region'];
}
if (is_array($options) && array_key_exists('version', $options)) {
self::$_options['version'] = $options['version'];
}
if (is_array($options) && array_key_exists('endpoint', $options)) {
self::$_options['endpoint'] = $options['endpoint'];
}
if (is_array($options) && array_key_exists('accesskey', $options)) {
self::$_options['credentials']['key'] = $options['accesskey'];
}
if (is_array($options) && array_key_exists('secretkey', $options)) {
self::$_options['credentials']['secret'] = $options['secretkey'];
}
if (is_array($options) && array_key_exists('use_path_style_endpoint', $options)) {
self::$_options['use_path_style_endpoint'] = filter_var($options['use_path_style_endpoint'], FILTER_VALIDATE_BOOLEAN);
}
if (is_array($options) && array_key_exists('bucket', $options)) {
self::$_bucket = $options['bucket'];
}
if (is_array($options) && array_key_exists('prefix', $options)) {
self::$_prefix = $options['prefix'];
}
if (empty(self::$_client)) {
self::$_client = new S3Client(self::$_options);
}
return self::$_instance;
}
/**
* returns all objects in the given prefix.
*
* @access private
* @param $prefix string with prefix
* @return array all objects in the given prefix
*/
private function _listAllObjects($prefix)
{
$allObjects = array();
$options = array(
'Bucket' => self::$_bucket,
'Prefix' => $prefix,
);
do {
$objectsListResponse = self::$_client->listObjects($options);
$objects = $objectsListResponse['Contents'] ?? array();
foreach ($objects as $object) {
$allObjects[] = $object;
$options['Marker'] = $object['Key'];
}
} while ($objectsListResponse['IsTruncated']);
return $allObjects;
}
/**
* returns the S3 storage object key for $pasteid in self::$_bucket.
*
* @access private
* @param $pasteid string to get the key for
* @return string
*/
private function _getKey($pasteid)
{
if (self::$_prefix != '') {
return self::$_prefix . '/' . $pasteid;
}
return $pasteid;
}
/**
* Uploads the payload in the self::$_bucket under the specified key.
* The entire payload is stored as a JSON document. The metadata is replicated
* as the S3 object's metadata except for the fields attachment, attachmentname
* and salt.
*
* @param $key string to store the payload under
* @param $payload array to store
* @return bool true if successful, otherwise false.
*/
private function _upload($key, $payload)
{
$metadata = array_key_exists('meta', $payload) ? $payload['meta'] : array();
unset($metadata['attachment'], $metadata['attachmentname'], $metadata['salt']);
foreach ($metadata as $k => $v) {
$metadata[$k] = strval($v);
}
try {
self::$_client->putObject(array(
'Bucket' => self::$_bucket,
'Key' => $key,
'Body' => Json::encode($payload),
'ContentType' => 'application/json',
'Metadata' => $metadata,
));
} catch (S3Exception $e) {
error_log('failed to upload ' . $key . ' to ' . self::$_bucket . ', ' .
trim(preg_replace('/\s\s+/', ' ', $e->getMessage())));
return false;
}
return true;
}
/**
* @inheritDoc
*/
public function create($pasteid, array $paste)
{
if ($this->exists($pasteid)) {
return false;
}
return $this->_upload($this->_getKey($pasteid), $paste);
}
/**
* @inheritDoc
*/
public function read($pasteid)
{
try {
$object = self::$_client->getObject(array(
'Bucket' => self::$_bucket,
'Key' => $this->_getKey($pasteid),
));
$data = $object['Body']->getContents();
return Json::decode($data);
} catch (S3Exception $e) {
error_log('failed to read ' . $pasteid . ' from ' . self::$_bucket . ', ' .
trim(preg_replace('/\s\s+/', ' ', $e->getMessage())));
return false;
}
}
/**
* @inheritDoc
*/
public function delete($pasteid)
{
$name = $this->_getKey($pasteid);
try {
$comments = $this->_listAllObjects($name . '/discussion/');
foreach ($comments as $comment) {
try {
self::$_client->deleteObject(array(
'Bucket' => self::$_bucket,
'Key' => $comment['Key'],
));
} catch (S3Exception $e) {
// ignore if already deleted.
}
}
} catch (S3Exception $e) {
// there are no discussions associated with the paste
}
try {
self::$_client->deleteObject(array(
'Bucket' => self::$_bucket,
'Key' => $name,
));
} catch (S3Exception $e) {
// ignore if already deleted
}
}
/**
* @inheritDoc
*/
public function exists($pasteid)
{
return self::$_client->doesObjectExistV2(self::$_bucket, $this->_getKey($pasteid));
}
/**
* @inheritDoc
*/
public function createComment($pasteid, $parentid, $commentid, array $comment)
{
if ($this->existsComment($pasteid, $parentid, $commentid)) {
return false;
}
$key = $this->_getKey($pasteid) . '/discussion/' . $parentid . '/' . $commentid;
return $this->_upload($key, $comment);
}
/**
* @inheritDoc
*/
public function readComments($pasteid)
{
$comments = array();
$prefix = $this->_getKey($pasteid) . '/discussion/';
try {
$entries = $this->_listAllObjects($prefix);
foreach ($entries as $entry) {
$object = self::$_client->getObject(array(
'Bucket' => self::$_bucket,
'Key' => $entry['Key'],
));
$body = JSON::decode($object['Body']->getContents());
$items = explode('/', $entry['Key']);
$body['id'] = $items[3];
$body['parentid'] = $items[2];
$slot = $this->getOpenSlot($comments, (int) $object['Metadata']['created']);
$comments[$slot] = $body;
}
} catch (S3Exception $e) {
// no comments found
}
return $comments;
}
/**
* @inheritDoc
*/
public function existsComment($pasteid, $parentid, $commentid)
{
$name = $this->_getKey($pasteid) . '/discussion/' . $parentid . '/' . $commentid;
return self::$_client->doesObjectExistV2(self::$_bucket, $name);
}
/**
* @inheritDoc
*/
public function purgeValues($namespace, $time)
{
$path = self::$_prefix;
if ($path != '') {
$path .= '/';
}
$path .= 'config/' . $namespace;
try {
foreach ($this->_listAllObjects($path) as $object) {
$name = $object['Key'];
if (strlen($name) > strlen($path) && substr($name, strlen($path), 1) !== '/') {
continue;
}
$head = self::$_client->headObject(array(
'Bucket' => self::$_bucket,
'Key' => $name,
));
if (array_key_exists('Metadata', $head) && array_key_exists('value', $head['Metadata'])) {
$value = $head['Metadata']['value'];
if (is_numeric($value) && intval($value) < $time) {
try {
self::$_client->deleteObject(array(
'Bucket' => self::$_bucket,
'Key' => $name,
));
} catch (S3Exception $e) {
// deleted by another instance.
}
}
}
}
} catch (S3Exception $e) {
// no objects in the bucket yet
}
}
/**
* For S3, the value will also be stored in the metadata for the
* namespaces traffic_limiter and purge_limiter.
* @inheritDoc
*/
public function setValue($value, $namespace, $key = '')
{
$prefix = self::$_prefix;
if ($prefix != '') {
$prefix .= '/';
}
if ($key === '') {
$key = $prefix . 'config/' . $namespace;
} else {
$key = $prefix . 'config/' . $namespace . '/' . $key;
}
$metadata = array('namespace' => $namespace);
if ($namespace != 'salt') {
$metadata['value'] = strval($value);
}
try {
self::$_client->putObject(array(
'Bucket' => self::$_bucket,
'Key' => $key,
'Body' => $value,
'ContentType' => 'application/json',
'Metadata' => $metadata,
));
} catch (S3Exception $e) {
error_log('failed to set key ' . $key . ' to ' . self::$_bucket . ', ' .
trim(preg_replace('/\s\s+/', ' ', $e->getMessage())));
return false;
}
return true;
}
/**
* @inheritDoc
*/
public function getValue($namespace, $key = '')
{
$prefix = self::$_prefix;
if ($prefix != '') {
$prefix .= '/';
}
if ($key === '') {
$key = $prefix . 'config/' . $namespace;
} else {
$key = $prefix . 'config/' . $namespace . '/' . $key;
}
try {
$object = self::$_client->getObject(array(
'Bucket' => self::$_bucket,
'Key' => $key,
));
return $object['Body']->getContents();
} catch (S3Exception $e) {
return '';
}
}
/**
* @inheritDoc
*/
protected function _getExpiredPastes($batchsize)
{
$expired = array();
$now = time();
$prefix = self::$_prefix;
if ($prefix != '') {
$prefix .= '/';
}
try {
foreach ($this->_listAllObjects($prefix) as $object) {
$head = self::$_client->headObject(array(
'Bucket' => self::$_bucket,
'Key' => $object['Key'],
));
if (array_key_exists('Metadata', $head) && array_key_exists('expire_date', $head['Metadata'])) {
$expire_at = intval($head['Metadata']['expire_date']);
if ($expire_at != 0 && $expire_at < $now) {
array_push($expired, $object['Key']);
}
}
if (count($expired) > $batchsize) {
break;
}
}
} catch (S3Exception $e) {
// no objects in the bucket yet
}
return $expired;
}
}

View File

@@ -84,7 +84,7 @@ class I18n
*/
public static function _($messageId)
{
return forward_static_call_array('self::translate', func_get_args());
return forward_static_call_array('PrivateBin\I18n::translate', func_get_args());
}
/**
@@ -316,10 +316,12 @@ class I18n
{
switch (self::$_language) {
case 'cs':
return $n == 1 ? 0 : ($n >= 2 && $n <= 4 ? 1 : 2);
case 'sk':
return $n === 1 ? 0 : ($n >= 2 && $n <= 4 ? 1 : 2);
case 'co':
case 'fr':
case 'oc':
case 'tr':
case 'zh':
return $n > 1 ? 1 : 0;
case 'he':
@@ -330,15 +332,15 @@ class I18n
case 'lt':
return $n % 10 === 1 && $n % 100 !== 11 ? 0 : (($n % 10 >= 2 && $n % 100 < 10 || $n % 100 >= 20) ? 1 : 2);
case 'pl':
return $n == 1 ? 0 : ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2);
return $n === 1 ? 0 : ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2);
case 'ru':
case 'uk':
return $n % 10 == 1 && $n % 100 != 11 ? 0 : ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2);
return $n % 10 === 1 && $n % 100 != 11 ? 0 : ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2);
case 'sl':
return $n % 100 == 1 ? 1 : ($n % 100 == 2 ? 2 : ($n % 100 == 3 || $n % 100 == 4 ? 3 : 0));
// bg, ca, de, en, es, et, fi, hu, it, nl, no, pt
return $n % 100 === 1 ? 1 : ($n % 100 === 2 ? 2 : ($n % 100 === 3 || $n % 100 === 4 ? 3 : 0));
// bg, ca, de, el, en, es, et, fi, hu, it, nl, no, pt
default:
return $n != 1 ? 1 : 0;
return $n !== 1 ? 1 : 0;
}
}

View File

@@ -14,6 +14,7 @@ namespace PrivateBin\Model;
use Exception;
use Identicon\Identicon;
use Jdenticon\Identicon as Jdenticon;
use PrivateBin\Persistence\TrafficLimiter;
use PrivateBin\Vizhash16x16;
@@ -167,6 +168,16 @@ class Comment extends AbstractModel
if ($icon == 'identicon') {
$identicon = new Identicon();
$pngdata = $identicon->getImageDataUri($hmac, 16);
} elseif ($icon == 'jdenticon') {
$jdenticon = new Jdenticon(array(
'hash' => $hmac,
'size' => 16,
'style' => array(
'backgroundColor' => '#fff0', // fully transparent, for dark mode
'padding' => 0,
),
));
$pngdata = $jdenticon->getImageDataUri('png');
} elseif ($icon == 'vizhash') {
$vh = new Vizhash16x16();
$pngdata = 'data:image/png;base64,' . base64_encode(

View File

@@ -120,6 +120,7 @@ class Request
if (
!array_key_exists('pasteid', $this->_params) &&
!array_key_exists('jsonld', $this->_params) &&
!array_key_exists('link', $this->_params) &&
array_key_exists('QUERY_STRING', $_SERVER) &&
!empty($_SERVER['QUERY_STRING'])
) {
@@ -135,6 +136,10 @@ class Request
}
} elseif (array_key_exists('jsonld', $this->_params) && !empty($this->_params['jsonld'])) {
$this->_operation = 'jsonld';
} elseif (array_key_exists('link', $this->_params) && !empty($this->_params['link'])) {
if (strpos($this->getRequestUri(), '/shortenviayourls') !== false) {
$this->_operation = 'yourlsproxy';
}
}
}

132
lib/YourlsProxy.php Normal file
View File

@@ -0,0 +1,132 @@
<?php
/**
* PrivateBin
*
* a zero-knowledge paste bin
*
* @link https://github.com/PrivateBin/PrivateBin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 1.4.0
*/
namespace PrivateBin;
use Exception;
/**
* YourlsProxy
*
* Forwards a URL for shortening to YOURLS (your own URL shortener) and stores
* the result.
*/
class YourlsProxy
{
/**
* error message
*
* @access private
* @var string
*/
private $_error = '';
/**
* shortened URL
*
* @access private
* @var string
*/
private $_url = '';
/**
* constructor
*
* initializes and runs PrivateBin
*
* @access public
* @param string $link
*/
public function __construct(Configuration $conf, $link)
{
if (strpos($link, $conf->getKey('basepath') . '/?') === false) {
$this->_error = 'Trying to shorten a URL that isn\'t pointing at our instance.';
return;
}
$yourls_api_url = $conf->getKey('apiurl', 'yourls');
if (empty($yourls_api_url)) {
$this->_error = 'Error calling YOURLS. Probably a configuration issue, like wrong or missing "apiurl" or "signature".';
return;
}
$data = file_get_contents(
$yourls_api_url, false, stream_context_create(
array(
'http' => array(
'method' => 'POST',
'header' => "Content-Type: application/x-www-form-urlencoded\r\n",
'content' => http_build_query(
array(
'signature' => $conf->getKey('signature', 'yourls'),
'format' => 'json',
'action' => 'shorturl',
'url' => $link,
)
),
),
)
)
);
try {
$data = Json::decode($data);
} catch (Exception $e) {
$this->_error = 'Error calling YOURLS. Probably a configuration issue, like wrong or missing "apiurl" or "signature".';
error_log('Error calling YOURLS: ' . $e->getMessage());
return;
}
if (
!is_null($data) &&
array_key_exists('statusCode', $data) &&
$data['statusCode'] == 200 &&
array_key_exists('shorturl', $data)
) {
$this->_url = $data['shorturl'];
} else {
$this->_error = 'Error parsing YOURLS response.';
}
}
/**
* Returns the (untranslated) error message
*
* @access public
* @return string
*/
public function getError()
{
return $this->_error;
}
/**
* Returns the shortened URL
*
* @access public
* @return string
*/
public function getUrl()
{
return $this->_url;
}
/**
* Returns true if any error has occurred
*
* @access public
* @return bool
*/
public function isError()
{
return !empty($this->_error);
}
}

View File

@@ -55,7 +55,7 @@ if ($ZEROBINCOMPATIBILITY) :
<?php
endif;
?>
<script type="text/javascript" data-cfasync="false" src="js/zlib-1.2.12.js" integrity="sha512-Ewve1dyEW/Vf97OY91/aWqMx9NaaUK5d8Z6JB1RR5gFXtMhse/Ya7D/5CE/UrQTwOWqmkvn97JjP4YDUrmq/yA==" crossorigin="anonymous"></script>
<script type="text/javascript" data-cfasync="false" src="js/zlib-1.2.13.js" integrity="sha512-Lv4PCbSge8B4odE2blatgggJ/mkX1Ak21e7jL8mY3vzrVHS8FGsrEoqCrizxIJB4sW3T2w5Q+RG7hhUvp7+9tw==" crossorigin="anonymous"></script>
<script type="text/javascript" data-cfasync="false" src="js/base-x-4.0.0.js" integrity="sha512-nNPg5IGCwwrveZ8cA/yMGr5HiRS5Ps2H+s0J/mKTPjCPWUgFGGw7M5nqdnPD3VsRwCVysUh3Y8OWjeSKGkEQJQ==" crossorigin="anonymous"></script>
<script type="text/javascript" data-cfasync="false" src="js/rawinflate-0.3.js" integrity="sha512-g8uelGgJW9A/Z1tB6Izxab++oj5kdD7B4qC7DHwZkB6DGMXKyzx7v5mvap2HXueI2IIn08YlRYM56jwWdm2ucQ==" crossorigin="anonymous"></script>
<script type="text/javascript" data-cfasync="false" src="js/bootstrap-3.4.1.js" integrity="sha512-oBTprMeNEKCnqfuqKd6sbvFzmFQtlXS3e0C/RGFV0hD6QzhHV+ODfaQbAlmY6/q0ubbwlAM/nCJjkrgA3waLzg==" crossorigin="anonymous"></script>
@@ -73,7 +73,7 @@ endif;
?>
<script type="text/javascript" data-cfasync="false" src="js/purify-2.3.6.js" integrity="sha512-N1GGPjbqLbwK821ZN7C925WuTwU4aDxz2CEEOXQ6/s6m6MBwVj8fh5fugiE2hzsm0xud3q7jpjZQ4ILnpMREYQ==" crossorigin="anonymous"></script>
<script type="text/javascript" data-cfasync="false" src="js/legacy.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==" crossorigin="anonymous"></script>
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-/F0+9bIbFUC8mKQzrcAjIs2Jg92w1DMcczT2Y/KqHVkFEXH1ZSrqtUX7QjLH6RgVR0YhTxhmWkZ2c8scGCwpkQ==" crossorigin="anonymous"></script>
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-sQhu+q8ayRj0LO80orHnhEudlTf5Qx+Yhb/+U84ixMvSdwijuLEHGiEuWctqUPOFSe56L6aeq6z7K0ONpbgaew==" crossorigin="anonymous"></script>
<!-- icon -->
<link rel="apple-touch-icon" href="<?php echo I18n::encode($BASEPATH); ?>img/apple-touch-icon.png" sizes="180x180" />
<link rel="icon" type="image/png" href="img/favicon-32x32.png" sizes="32x32" />
@@ -85,7 +85,7 @@ endif;
<meta name="theme-color" content="#ffe57e" />
<!-- Twitter/social media cards -->
<meta name="twitter:card" content="summary" />
<meta name="twitter:title" content="<?php echo I18n::_('Encrypted note on PrivateBin') ?>" />
<meta name="twitter:title" content="<?php echo I18n::_('Encrypted note on %s', I18n::_($NAME)) ?>" />
<meta name="twitter:description" content="<?php echo I18n::_('Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.') ?>" />
<meta name="twitter:image" content="<?php echo I18n::encode($BASEPATH); ?>img/apple-touch-icon.png" />
<meta property="og:title" content="<?php echo I18n::_($NAME); ?>" />

View File

@@ -34,7 +34,7 @@ if ($ZEROBINCOMPATIBILITY):
<?php
endif;
?>
<script type="text/javascript" data-cfasync="false" src="js/zlib-1.2.12.js" integrity="sha512-Ewve1dyEW/Vf97OY91/aWqMx9NaaUK5d8Z6JB1RR5gFXtMhse/Ya7D/5CE/UrQTwOWqmkvn97JjP4YDUrmq/yA==" crossorigin="anonymous"></script>
<script type="text/javascript" data-cfasync="false" src="js/zlib-1.2.13.js" integrity="sha512-Lv4PCbSge8B4odE2blatgggJ/mkX1Ak21e7jL8mY3vzrVHS8FGsrEoqCrizxIJB4sW3T2w5Q+RG7hhUvp7+9tw==" crossorigin="anonymous"></script>
<script type="text/javascript" data-cfasync="false" src="js/base-x-4.0.0.js" integrity="sha512-nNPg5IGCwwrveZ8cA/yMGr5HiRS5Ps2H+s0J/mKTPjCPWUgFGGw7M5nqdnPD3VsRwCVysUh3Y8OWjeSKGkEQJQ==" crossorigin="anonymous"></script>
<script type="text/javascript" data-cfasync="false" src="js/rawinflate-0.3.js" integrity="sha512-g8uelGgJW9A/Z1tB6Izxab++oj5kdD7B4qC7DHwZkB6DGMXKyzx7v5mvap2HXueI2IIn08YlRYM56jwWdm2ucQ==" crossorigin="anonymous"></script>
<?php
@@ -51,7 +51,7 @@ endif;
?>
<script type="text/javascript" data-cfasync="false" src="js/purify-2.3.6.js" integrity="sha512-N1GGPjbqLbwK821ZN7C925WuTwU4aDxz2CEEOXQ6/s6m6MBwVj8fh5fugiE2hzsm0xud3q7jpjZQ4ILnpMREYQ==" crossorigin="anonymous"></script>
<script type="text/javascript" data-cfasync="false" src="js/legacy.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==" crossorigin="anonymous"></script>
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-/F0+9bIbFUC8mKQzrcAjIs2Jg92w1DMcczT2Y/KqHVkFEXH1ZSrqtUX7QjLH6RgVR0YhTxhmWkZ2c8scGCwpkQ==" crossorigin="anonymous"></script>
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-sQhu+q8ayRj0LO80orHnhEudlTf5Qx+Yhb/+U84ixMvSdwijuLEHGiEuWctqUPOFSe56L6aeq6z7K0ONpbgaew==" crossorigin="anonymous"></script>
<!-- icon -->
<link rel="apple-touch-icon" href="img/apple-touch-icon.png?<?php echo rawurlencode($VERSION); ?>" sizes="180x180" />
<link rel="icon" type="image/png" href="img/favicon-32x32.png?<?php echo rawurlencode($VERSION); ?>" sizes="32x32" />
@@ -63,7 +63,7 @@ endif;
<meta name="theme-color" content="#ffe57e" />
<!-- Twitter/social media cards -->
<meta name="twitter:card" content="summary" />
<meta name="twitter:title" content="<?php echo I18n::_('Encrypted note on PrivateBin') ?>" />
<meta name="twitter:title" content="<?php echo I18n::_('Encrypted note on %s', I18n::_($NAME)) ?>" />
<meta name="twitter:description" content="<?php echo I18n::_('Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.') ?>" />
<meta name="twitter:image" content="img/apple-touch-icon.png?<?php echo rawurlencode($VERSION); ?>" />
<meta property="og:title" content="<?php echo I18n::_($NAME); ?>" />

27
tpl/yourlsproxy.php Normal file
View File

@@ -0,0 +1,27 @@
<?php
use PrivateBin\I18n;
?><!DOCTYPE html>
<html lang="<?php echo I18n::_('en'); ?>">
<head>
<meta charset="utf-8" />
<meta http-equiv="Content-Security-Policy" content="<?php echo I18n::encode($CSPHEADER); ?>">
<meta name="robots" content="noindex" />
<meta name="google" content="notranslate">
<title><?php echo I18n::_($NAME); ?></title>
</head>
<body>
<?php
if (empty($ERROR)) :
?>
<p><?php echo I18n::_('Your paste is <a id="pasteurl" href="%s">%s</a> <span id="copyhint">(Hit [Ctrl]+[c] to copy)</span>', $SHORTURL, $SHORTURL); ?></p>
<?php
else:
?>
<div id="errormessage">
<p><?php echo I18n::_('Could not create paste: %s', $ERROR); ?></p>
</div>
<?php
endif;
?>
</body>
</html>

View File

@@ -48,6 +48,7 @@ class ControllerTest extends PHPUnit_Framework_TestCase
*/
public function testView()
{
$_SERVER['HTTP_HOST'] = 'example.com';
$_SERVER['QUERY_STRING'] = Helper::getPasteId();
$_GET[Helper::getPasteId()] = '';
ob_start();
@@ -64,6 +65,11 @@ class ControllerTest extends PHPUnit_Framework_TestCase
$content,
'doesn\'t output shortener button'
);
$this->assertRegExp(
'# href="https://' . preg_quote($_SERVER['HTTP_HOST']) . '/">switching to HTTPS#',
$content,
'outputs configured https URL correctly'
);
}
/**

View File

@@ -9,6 +9,7 @@ use Identicon\Generator\GdGenerator;
use Identicon\Generator\ImageMagickGenerator;
use Identicon\Generator\SvgGenerator;
use Identicon\Identicon;
use Jdenticon\Identicon as Jdenticon;
use PrivateBin\Vizhash16x16;
@@ -17,7 +18,19 @@ $vizhash = new Vizhash16x16();
$identiconGenerators = array(
'identicon GD' => new Identicon(new GdGenerator()),
'identicon ImageMagick' => new Identicon(new ImageMagickGenerator()),
'identicon SVG' => new Identicon(new SvgGenerator())
'identicon SVG' => new Identicon(new SvgGenerator()),
);
$jdenticon = new Jdenticon(array(
'size' => 16,
'style' => array(
'backgroundColor' => '#fff0', // fully transparent, for dark mode
'padding' => 0,
),
));
$jdenticonGenerators = array(
'jdenticon' => 'png',
'jdenticon ImageMagick' => 'png',
'jdenticon SVG' => 'svg',
);
$results = array(
'vizhash' => array(
@@ -35,21 +48,30 @@ $results = array(
'identicon SVG' => array(
'lengths' => array(),
'time' => 0
)
),
'jdenticon' => array(
'lengths' => array(),
'time' => 0
),
'jdenticon ImageMagick' => array(
'lengths' => array(),
'time' => 0
),
'jdenticon SVG' => array(
'lengths' => array(),
'time' => 0
),
);
$hmacs = array();
echo 'generate ', ITERATIONS, ' hmacs and pre-populate the result array, so tests wont be slowed down', PHP_EOL;
for ($i = 0; $i < ITERATIONS; ++$i) {
$hmacs[$i] = hash_hmac('sha512', '127.0.0.1', bin2hex(random_bytes(256)));
$results['vizhash']['lengths'][$i] = 0;
$results['identicon GD']['lengths'][$i] = 0;
$results['identicon ImageMagick']['lengths'][$i] = 0;
$results['identicon SVG']['lengths'][$i] = 0;
foreach (array_keys($results) as $test) {
$results[$test]['lengths'][$i] = 0;
}
}
echo 'run vizhash tests', PHP_EOL;
$start = microtime(true);
foreach ($hmacs as $i => $hmac) {
@@ -60,7 +82,6 @@ foreach ($hmacs as $i => $hmac) {
}
$results['vizhash']['time'] = microtime(true) - $start;
foreach ($identiconGenerators as $key => $identicon) {
echo 'run ', $key,' tests', PHP_EOL;
$start = microtime(true);
@@ -71,9 +92,35 @@ foreach ($identiconGenerators as $key => $identicon) {
$results[$key]['time'] = microtime(true) - $start;
}
foreach ($jdenticonGenerators as $key => $format) {
echo 'run ', $key,' tests', PHP_EOL;
if ($key === 'jdenticon ImageMagick') {
$jdenticon->enableImageMagick = true;
} else {
$jdenticon->enableImageMagick = false;
}
$start = microtime(true);
foreach ($hmacs as $i => $hmac) {
$jdenticon->setHash($hmac);
$data = $jdenticon->getImageDataUri($format);
$results[$key]['lengths'][$i] = strlen($data);
}
$results[$key]['time'] = microtime(true) - $start;
}
define('PADDING_LENGTH', max(array_map(function ($key) { return strlen($key); }, array_keys($results))) + 1);
define(
'PADDING_LENGTH',
max(
array_map(
function ($key) {
return strlen($key);
},
array_keys($results)
)
) + 1
);
function format_result_line($generator, $min, $max, $avg, $sec) {
echo str_pad($generator, PADDING_LENGTH, ' '), "\t",
str_pad($min, 4, ' ', STR_PAD_LEFT), "\t",
@@ -84,7 +131,10 @@ function format_result_line($generator, $min, $max, $avg, $sec) {
echo PHP_EOL;
format_result_line('Generator:', 'min', 'max', 'avg', 'seconds');
format_result_line(str_repeat('─', PADDING_LENGTH), str_repeat('─', 4), str_repeat('─', 4), str_repeat('─', 4), str_repeat('─', 7));
format_result_line(
str_repeat('─', PADDING_LENGTH), str_repeat('─', 4), str_repeat('─', 4),
str_repeat('─', 4), str_repeat('─', 7)
);
foreach ($results as $generator => $result) {
sort($result['lengths']);
format_result_line(

View File

@@ -15,6 +15,9 @@ class JsonApiTest extends PHPUnit_Framework_TestCase
{
/* Setup Routine */
$this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data';
if (!is_dir($this->_path)) {
mkdir($this->_path);
}
$this->_model = Filesystem::getInstance(array('dir' => $this->_path));
ServerSalt::setStore($this->_model);
@@ -186,8 +189,6 @@ class JsonApiTest extends PHPUnit_Framework_TestCase
*/
public function testJsonLdPaste()
{
$paste = Helper::getPaste();
$this->_model->create(Helper::getPasteId(), $paste);
$_GET['jsonld'] = 'paste';
ob_start();
new Controller;
@@ -205,8 +206,6 @@ class JsonApiTest extends PHPUnit_Framework_TestCase
*/
public function testJsonLdComment()
{
$paste = Helper::getPaste();
$this->_model->create(Helper::getPasteId(), $paste);
$_GET['jsonld'] = 'comment';
ob_start();
new Controller;
@@ -224,8 +223,6 @@ class JsonApiTest extends PHPUnit_Framework_TestCase
*/
public function testJsonLdPasteMeta()
{
$paste = Helper::getPaste();
$this->_model->create(Helper::getPasteId(), $paste);
$_GET['jsonld'] = 'pastemeta';
ob_start();
new Controller;
@@ -243,8 +240,6 @@ class JsonApiTest extends PHPUnit_Framework_TestCase
*/
public function testJsonLdCommentMeta()
{
$paste = Helper::getPaste();
$this->_model->create(Helper::getPasteId(), $paste);
$_GET['jsonld'] = 'commentmeta';
ob_start();
new Controller;
@@ -262,8 +257,6 @@ class JsonApiTest extends PHPUnit_Framework_TestCase
*/
public function testJsonLdInvalid()
{
$paste = Helper::getPaste();
$this->_model->create(Helper::getPasteId(), $paste);
$_GET['jsonld'] = CONF;
ob_start();
new Controller;
@@ -271,4 +264,42 @@ class JsonApiTest extends PHPUnit_Framework_TestCase
ob_end_clean();
$this->assertEquals('{}', $content, 'does not output nasty data');
}
/**
* @runInSeparateProcess
*/
public function testShortenViaYourls()
{
$mock_yourls_service = $this->_path . DIRECTORY_SEPARATOR . 'yourls.json';
$options = parse_ini_file(CONF, true);
$options['main']['basepath'] = 'https://example.com/path';
$options['main']['urlshortener'] = 'https://example.com/path/shortenviayourls?link=';
$options['yourls']['apiurl'] = $mock_yourls_service;
Helper::createIniFile(CONF, $options);
// the real service answer is more complex, but we only look for the shorturl & statusCode
file_put_contents($mock_yourls_service, '{"shorturl":"https:\/\/example.com\/1","statusCode":200}');
$_SERVER['REQUEST_URI'] = '/path/shortenviayourls?link=https%3A%2F%2Fexample.com%2Fpath%2F%3Ffoo%23bar';
$_GET['link'] = 'https://example.com/path/?foo#bar';
ob_start();
new Controller;
$content = ob_get_contents();
ob_end_clean();
$this->assertContains('id="pasteurl" href="https://example.com/1"', $content, 'outputs shortened URL correctly');
}
/**
* @runInSeparateProcess
*/
public function testShortenViaYourlsFailure()
{
$_SERVER['REQUEST_URI'] = '/path/shortenviayourls?link=https%3A%2F%2Fexample.com%2Fpath%2F%3Ffoo%23bar';
$_GET['link'] = 'https://example.com/path/?foo#bar';
ob_start();
new Controller;
$content = ob_get_contents();
ob_end_clean();
$this->assertContains('Error calling YOURLS.', $content, 'outputs error correctly');
}
}

View File

@@ -106,6 +106,10 @@ class ViewTest extends PHPUnit_Framework_TestCase
$content,
$template . ': outputs error correctly'
);
if ($template === 'yourlsproxy') {
// yourlsproxy template only displays error message
continue;
}
$this->assertRegExp(
'#<[^>]+id="password"[^>]*>#',
$content,

75
tst/YourlsProxyTest.php Normal file
View File

@@ -0,0 +1,75 @@
<?php
use PrivateBin\Configuration;
use PrivateBin\YourlsProxy;
class YourlsProxyTest extends PHPUnit_Framework_TestCase
{
private $_conf;
private $_path;
private $_mock_yourls_service;
public function setUp()
{
/* Setup Routine */
$this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data';
if (!is_dir($this->_path)) {
mkdir($this->_path);
}
$this->_mock_yourls_service = $this->_path . DIRECTORY_SEPARATOR . 'yourls.json';
$options = parse_ini_file(CONF_SAMPLE, true);
$options['main']['basepath'] = 'https://example.com';
$options['main']['urlshortener'] = 'https://example.com/shortenviayourls?link=';
$options['yourls']['apiurl'] = $this->_mock_yourls_service;
Helper::confBackup();
Helper::createIniFile(CONF, $options);
$this->_conf = new Configuration;
}
public function tearDown()
{
/* Tear Down Routine */
unlink(CONF);
Helper::confRestore();
Helper::rmDir($this->_path);
}
public function testYourlsProxy()
{
// the real service answer is more complex, but we only look for the shorturl & statusCode
file_put_contents($this->_mock_yourls_service, '{"shorturl":"https:\/\/example.com\/1","statusCode":200}');
$yourls = new YourlsProxy($this->_conf, 'https://example.com/?foo#bar');
$this->assertFalse($yourls->isError());
$this->assertEquals($yourls->getUrl(), 'https://example.com/1');
}
public function testForeignUrl()
{
$yourls = new YourlsProxy($this->_conf, 'https://other.example.com/?foo#bar');
$this->assertTrue($yourls->isError());
$this->assertEquals($yourls->getError(), 'Trying to shorten a URL that isn\'t pointing at our instance.');
}
public function testYourlsError()
{
// when statusCode is not 200, shorturl may not have been set
file_put_contents($this->_mock_yourls_service, '{"statusCode":403}');
$yourls = new YourlsProxy($this->_conf, 'https://example.com/?foo#bar');
$this->assertTrue($yourls->isError());
$this->assertEquals($yourls->getError(), 'Error parsing YOURLS response.');
}
public function testServerError()
{
// simulate some other server error that results in a non-JSON reply
file_put_contents($this->_mock_yourls_service, '500 Internal Server Error');
$yourls = new YourlsProxy($this->_conf, 'https://example.com/?foo#bar');
$this->assertTrue($yourls->isError());
$this->assertEquals($yourls->getError(), 'Error calling YOURLS. Probably a configuration issue, like wrong or missing "apiurl" or "signature".');
}
}

View File

@@ -37,57 +37,130 @@ namespace Composer\Autoload;
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see http://www.php-fig.org/psr/psr-0/
* @see http://www.php-fig.org/psr/psr-4/
* @see https://www.php-fig.org/psr/psr-0/
* @see https://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
/** @var ?string */
private $vendorDir;
// PSR-4
/**
* @var array[]
* @psalm-var array<string, array<string, int>>
*/
private $prefixLengthsPsr4 = array();
/**
* @var array[]
* @psalm-var array<string, array<int, string>>
*/
private $prefixDirsPsr4 = array();
/**
* @var array[]
* @psalm-var array<string, string>
*/
private $fallbackDirsPsr4 = array();
// PSR-0
/**
* @var array[]
* @psalm-var array<string, array<string, string[]>>
*/
private $prefixesPsr0 = array();
/**
* @var array[]
* @psalm-var array<string, string>
*/
private $fallbackDirsPsr0 = array();
/** @var bool */
private $useIncludePath = false;
/**
* @var string[]
* @psalm-var array<string, string>
*/
private $classMap = array();
/** @var bool */
private $classMapAuthoritative = false;
/**
* @var bool[]
* @psalm-var array<string, bool>
*/
private $missingClasses = array();
/** @var ?string */
private $apcuPrefix;
/**
* @var self[]
*/
private static $registeredLoaders = array();
/**
* @param ?string $vendorDir
*/
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
}
/**
* @return string[]
*/
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', $this->prefixesPsr0);
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
}
return array();
}
/**
* @return array[]
* @psalm-return array<string, array<int, string>>
*/
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
/**
* @return array[]
* @psalm-return array<string, string>
*/
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
/**
* @return array[]
* @psalm-return array<string, string>
*/
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
/**
* @return string[] Array of classname => path
* @psalm-return array<string, string>
*/
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array $classMap Class to filename map
* @param string[] $classMap Class to filename map
* @psalm-param array<string, string> $classMap
*
* @return void
*/
public function addClassMap(array $classMap)
{
@@ -102,9 +175,11 @@ class ClassLoader
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
* @param string $prefix The prefix
* @param string[]|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*
* @return void
*/
public function add($prefix, $paths, $prepend = false)
{
@@ -147,11 +222,13 @@ class ClassLoader
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param string[]|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
@@ -195,8 +272,10 @@ class ClassLoader
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 base directories
* @param string $prefix The prefix
* @param string[]|string $paths The PSR-0 base directories
*
* @return void
*/
public function set($prefix, $paths)
{
@@ -211,10 +290,12 @@ class ClassLoader
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param string[]|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function setPsr4($prefix, $paths)
{
@@ -234,6 +315,8 @@ class ClassLoader
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*
* @return void
*/
public function setUseIncludePath($useIncludePath)
{
@@ -256,6 +339,8 @@ class ClassLoader
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*
* @return void
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
@@ -276,6 +361,8 @@ class ClassLoader
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*
* @return void
*/
public function setApcuPrefix($apcuPrefix)
{
@@ -296,25 +383,44 @@ class ClassLoader
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*
* @return void
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
if (null === $this->vendorDir) {
return;
}
if ($prepend) {
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
} else {
unset(self::$registeredLoaders[$this->vendorDir]);
self::$registeredLoaders[$this->vendorDir] = $this;
}
}
/**
* Unregisters this instance as an autoloader.
*
* @return void
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
if (null !== $this->vendorDir) {
unset(self::$registeredLoaders[$this->vendorDir]);
}
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return bool|null True if loaded, null otherwise
* @return true|null True if loaded, null otherwise
*/
public function loadClass($class)
{
@@ -323,6 +429,8 @@ class ClassLoader
return true;
}
return null;
}
/**
@@ -367,6 +475,21 @@ class ClassLoader
return $file;
}
/**
* Returns the currently registered loaders indexed by their corresponding vendor directories.
*
* @return self[]
*/
public static function getRegisteredLoaders()
{
return self::$registeredLoaders;
}
/**
* @param string $class
* @param string $ext
* @return string|false
*/
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
@@ -438,6 +561,10 @@ class ClassLoader
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*
* @param string $file
* @return void
* @private
*/
function includeFile($file)
{

350
vendor/composer/InstalledVersions.php vendored Normal file
View File

@@ -0,0 +1,350 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer;
use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;
/**
* This class is copied in every Composer installed project and available to all
*
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
*
* To require its presence, you can require `composer-runtime-api ^2.0`
*/
class InstalledVersions
{
/**
* @var mixed[]|null
* @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}|array{}|null
*/
private static $installed;
/**
* @var bool|null
*/
private static $canGetVendors;
/**
* @var array[]
* @psalm-var array<string, array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}>
*/
private static $installedByVendor = array();
/**
* Returns a list of all package names which are present, either by being installed, replaced or provided
*
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackages()
{
$packages = array();
foreach (self::getInstalled() as $installed) {
$packages[] = array_keys($installed['versions']);
}
if (1 === \count($packages)) {
return $packages[0];
}
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
}
/**
* Returns a list of all package names with a specific type e.g. 'library'
*
* @param string $type
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackagesByType($type)
{
$packagesByType = array();
foreach (self::getInstalled() as $installed) {
foreach ($installed['versions'] as $name => $package) {
if (isset($package['type']) && $package['type'] === $type) {
$packagesByType[] = $name;
}
}
}
return $packagesByType;
}
/**
* Checks whether the given package is installed
*
* This also returns true if the package name is provided or replaced by another package
*
* @param string $packageName
* @param bool $includeDevRequirements
* @return bool
*/
public static function isInstalled($packageName, $includeDevRequirements = true)
{
foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']);
}
}
return false;
}
/**
* Checks whether the given package satisfies a version constraint
*
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
*
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
*
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
* @param string $packageName
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
* @return bool
*/
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
$constraint = $parser->parseConstraints($constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
}
/**
* Returns a version constraint representing all the range(s) which are installed for a given package
*
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
* whether a given version of a package is installed, and not just whether it exists
*
* @param string $packageName
* @return string Version constraint usable with composer/semver
*/
public static function getVersionRanges($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
$ranges = array();
if (isset($installed['versions'][$packageName]['pretty_version'])) {
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
}
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
}
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
}
if (array_key_exists('provided', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
}
return implode(' || ', $ranges);
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['version'])) {
return null;
}
return $installed['versions'][$packageName]['version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getPrettyVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
return null;
}
return $installed['versions'][$packageName]['pretty_version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
*/
public static function getReference($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['reference'])) {
return null;
}
return $installed['versions'][$packageName]['reference'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
*/
public static function getInstallPath($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @return array
* @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}
*/
public static function getRootPackage()
{
$installed = self::getInstalled();
return $installed[0]['root'];
}
/**
* Returns the raw installed.php data for custom implementations
*
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
* @return array[]
* @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}
*/
public static function getRawData()
{
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = include __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
return self::$installed;
}
/**
* Returns the raw data of all installed.php which are currently loaded for custom implementations
*
* @return array[]
* @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}>
*/
public static function getAllRawData()
{
return self::getInstalled();
}
/**
* Lets you reload the static array from another file
*
* This is only useful for complex integrations in which a project needs to use
* this class but then also needs to execute another project's autoloader in process,
* and wants to ensure both projects have access to their version of installed.php.
*
* A typical case would be PHPUnit, where it would need to make sure it reads all
* the data it needs from this class, then call reload() with
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
* the project in which it runs can then also use this class safely, without
* interference between PHPUnit's dependencies and the project's dependencies.
*
* @param array[] $data A vendor/composer/installed.php data set
* @return void
*
* @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>} $data
*/
public static function reload($data)
{
self::$installed = $data;
self::$installedByVendor = array();
}
/**
* @return array[]
* @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}>
*/
private static function getInstalled()
{
if (null === self::$canGetVendors) {
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
}
$installed = array();
if (self::$canGetVendors) {
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
self::$installed = $installed[count($installed) - 1];
}
}
}
}
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = require __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
$installed[] = self::$installed;
return $installed;
}
}

View File

@@ -6,6 +6,7 @@ $vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'IPLib\\Address\\AddressInterface' => $vendorDir . '/mlocati/ip-lib/src/Address/AddressInterface.php',
'IPLib\\Address\\AssignedRange' => $vendorDir . '/mlocati/ip-lib/src/Address/AssignedRange.php',
'IPLib\\Address\\IPv4' => $vendorDir . '/mlocati/ip-lib/src/Address/IPv4.php',
@@ -28,12 +29,49 @@ return array(
'Identicon\\Generator\\ImageMagickGenerator' => $vendorDir . '/yzalis/identicon/src/Identicon/Generator/ImageMagickGenerator.php',
'Identicon\\Generator\\SvgGenerator' => $vendorDir . '/yzalis/identicon/src/Identicon/Generator/SvgGenerator.php',
'Identicon\\Identicon' => $vendorDir . '/yzalis/identicon/src/Identicon/Identicon.php',
'Jdenticon\\Canvas\\Canvas' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Canvas.php',
'Jdenticon\\Canvas\\CanvasContext' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/CanvasContext.php',
'Jdenticon\\Canvas\\ColorUtils' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/ColorUtils.php',
'Jdenticon\\Canvas\\Matrix' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Matrix.php',
'Jdenticon\\Canvas\\Png\\PngBuffer' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Png/PngBuffer.php',
'Jdenticon\\Canvas\\Png\\PngEncoder' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Png/PngEncoder.php',
'Jdenticon\\Canvas\\Png\\PngPalette' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Png/PngPalette.php',
'Jdenticon\\Canvas\\Point' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Point.php',
'Jdenticon\\Canvas\\Rasterization\\Edge' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Rasterization/Edge.php',
'Jdenticon\\Canvas\\Rasterization\\EdgeIntersection' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Rasterization/EdgeIntersection.php',
'Jdenticon\\Canvas\\Rasterization\\EdgeSuperSampleIntersection' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Rasterization/EdgeSuperSampleIntersection.php',
'Jdenticon\\Canvas\\Rasterization\\EdgeTable' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Rasterization/EdgeTable.php',
'Jdenticon\\Canvas\\Rasterization\\Layer' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Rasterization/Layer.php',
'Jdenticon\\Canvas\\Rasterization\\LayerManager' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Rasterization/LayerManager.php',
'Jdenticon\\Canvas\\Rasterization\\Rasterizer' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Rasterization/Rasterizer.php',
'Jdenticon\\Canvas\\Rasterization\\SuperSampleBuffer' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Rasterization/SuperSampleBuffer.php',
'Jdenticon\\Canvas\\Rasterization\\SuperSampleRange' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Rasterization/SuperSampleRange.php',
'Jdenticon\\Color' => $vendorDir . '/jdenticon/jdenticon/src/Color.php',
'Jdenticon\\Identicon' => $vendorDir . '/jdenticon/jdenticon/src/Identicon.php',
'Jdenticon\\IdenticonStyle' => $vendorDir . '/jdenticon/jdenticon/src/IdenticonStyle.php',
'Jdenticon\\Rendering\\AbstractRenderer' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/AbstractRenderer.php',
'Jdenticon\\Rendering\\ColorTheme' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/ColorTheme.php',
'Jdenticon\\Rendering\\IconGenerator' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/IconGenerator.php',
'Jdenticon\\Rendering\\ImagickRenderer' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/ImagickRenderer.php',
'Jdenticon\\Rendering\\InternalPngRenderer' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/InternalPngRenderer.php',
'Jdenticon\\Rendering\\Point' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/Point.php',
'Jdenticon\\Rendering\\Rectangle' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/Rectangle.php',
'Jdenticon\\Rendering\\RendererInterface' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/RendererInterface.php',
'Jdenticon\\Rendering\\SvgPath' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/SvgPath.php',
'Jdenticon\\Rendering\\SvgRenderer' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/SvgRenderer.php',
'Jdenticon\\Rendering\\Transform' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/Transform.php',
'Jdenticon\\Rendering\\TriangleDirection' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/TriangleDirection.php',
'Jdenticon\\Shapes\\Shape' => $vendorDir . '/jdenticon/jdenticon/src/Shapes/Shape.php',
'Jdenticon\\Shapes\\ShapeCategory' => $vendorDir . '/jdenticon/jdenticon/src/Shapes/ShapeCategory.php',
'Jdenticon\\Shapes\\ShapeDefinitions' => $vendorDir . '/jdenticon/jdenticon/src/Shapes/ShapeDefinitions.php',
'Jdenticon\\Shapes\\ShapePosition' => $vendorDir . '/jdenticon/jdenticon/src/Shapes/ShapePosition.php',
'PrivateBin\\Configuration' => $baseDir . '/lib/Configuration.php',
'PrivateBin\\Controller' => $baseDir . '/lib/Controller.php',
'PrivateBin\\Data\\AbstractData' => $baseDir . '/lib/Data/AbstractData.php',
'PrivateBin\\Data\\Database' => $baseDir . '/lib/Data/Database.php',
'PrivateBin\\Data\\Filesystem' => $baseDir . '/lib/Data/Filesystem.php',
'PrivateBin\\Data\\GoogleCloudStorage' => $baseDir . '/lib/Data/GoogleCloudStorage.php',
'PrivateBin\\Data\\S3Storage' => $baseDir . '/lib/Data/S3Storage.php',
'PrivateBin\\Filter' => $baseDir . '/lib/Filter.php',
'PrivateBin\\FormatV2' => $baseDir . '/lib/FormatV2.php',
'PrivateBin\\I18n' => $baseDir . '/lib/I18n.php',
@@ -49,4 +87,5 @@ return array(
'PrivateBin\\Request' => $baseDir . '/lib/Request.php',
'PrivateBin\\View' => $baseDir . '/lib/View.php',
'PrivateBin\\Vizhash16x16' => $baseDir . '/lib/Vizhash16x16.php',
'PrivateBin\\YourlsProxy' => $baseDir . '/lib/YourlsProxy.php',
);

View File

@@ -7,6 +7,7 @@ $baseDir = dirname($vendorDir);
return array(
'PrivateBin\\' => array($baseDir . '/lib'),
'Jdenticon\\' => array($vendorDir . '/jdenticon/jdenticon/src'),
'Identicon\\' => array($vendorDir . '/yzalis/identicon/src/Identicon'),
'IPLib\\' => array($vendorDir . '/mlocati/ip-lib/src'),
);

View File

@@ -22,13 +22,15 @@ class ComposerAutoloaderInitDontChange
return self::$loader;
}
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInitDontChange', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
spl_autoload_unregister(array('ComposerAutoloaderInitDontChange', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
if ($useStaticLoader) {
require_once __DIR__ . '/autoload_static.php';
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInitDontChange::getInitializer($loader));
} else {
@@ -63,11 +65,16 @@ class ComposerAutoloaderInitDontChange
}
}
/**
* @param string $fileIdentifier
* @param string $file
* @return void
*/
function composerRequireDontChange($fileIdentifier, $file)
{
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
require $file;
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
require $file;
}
}

View File

@@ -15,6 +15,10 @@ class ComposerStaticInitDontChange
array (
'PrivateBin\\' => 11,
),
'J' =>
array (
'Jdenticon\\' => 10,
),
'I' =>
array (
'Identicon\\' => 10,
@@ -27,6 +31,10 @@ class ComposerStaticInitDontChange
array (
0 => __DIR__ . '/../..' . '/lib',
),
'Jdenticon\\' =>
array (
0 => __DIR__ . '/..' . '/jdenticon/jdenticon/src',
),
'Identicon\\' =>
array (
0 => __DIR__ . '/..' . '/yzalis/identicon/src/Identicon',
@@ -38,6 +46,7 @@ class ComposerStaticInitDontChange
);
public static $classMap = array (
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'IPLib\\Address\\AddressInterface' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/AddressInterface.php',
'IPLib\\Address\\AssignedRange' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/AssignedRange.php',
'IPLib\\Address\\IPv4' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/IPv4.php',
@@ -60,12 +69,49 @@ class ComposerStaticInitDontChange
'Identicon\\Generator\\ImageMagickGenerator' => __DIR__ . '/..' . '/yzalis/identicon/src/Identicon/Generator/ImageMagickGenerator.php',
'Identicon\\Generator\\SvgGenerator' => __DIR__ . '/..' . '/yzalis/identicon/src/Identicon/Generator/SvgGenerator.php',
'Identicon\\Identicon' => __DIR__ . '/..' . '/yzalis/identicon/src/Identicon/Identicon.php',
'Jdenticon\\Canvas\\Canvas' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Canvas.php',
'Jdenticon\\Canvas\\CanvasContext' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/CanvasContext.php',
'Jdenticon\\Canvas\\ColorUtils' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/ColorUtils.php',
'Jdenticon\\Canvas\\Matrix' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Matrix.php',
'Jdenticon\\Canvas\\Png\\PngBuffer' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Png/PngBuffer.php',
'Jdenticon\\Canvas\\Png\\PngEncoder' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Png/PngEncoder.php',
'Jdenticon\\Canvas\\Png\\PngPalette' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Png/PngPalette.php',
'Jdenticon\\Canvas\\Point' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Point.php',
'Jdenticon\\Canvas\\Rasterization\\Edge' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Rasterization/Edge.php',
'Jdenticon\\Canvas\\Rasterization\\EdgeIntersection' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Rasterization/EdgeIntersection.php',
'Jdenticon\\Canvas\\Rasterization\\EdgeSuperSampleIntersection' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Rasterization/EdgeSuperSampleIntersection.php',
'Jdenticon\\Canvas\\Rasterization\\EdgeTable' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Rasterization/EdgeTable.php',
'Jdenticon\\Canvas\\Rasterization\\Layer' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Rasterization/Layer.php',
'Jdenticon\\Canvas\\Rasterization\\LayerManager' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Rasterization/LayerManager.php',
'Jdenticon\\Canvas\\Rasterization\\Rasterizer' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Rasterization/Rasterizer.php',
'Jdenticon\\Canvas\\Rasterization\\SuperSampleBuffer' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Rasterization/SuperSampleBuffer.php',
'Jdenticon\\Canvas\\Rasterization\\SuperSampleRange' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Rasterization/SuperSampleRange.php',
'Jdenticon\\Color' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Color.php',
'Jdenticon\\Identicon' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Identicon.php',
'Jdenticon\\IdenticonStyle' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/IdenticonStyle.php',
'Jdenticon\\Rendering\\AbstractRenderer' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/AbstractRenderer.php',
'Jdenticon\\Rendering\\ColorTheme' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/ColorTheme.php',
'Jdenticon\\Rendering\\IconGenerator' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/IconGenerator.php',
'Jdenticon\\Rendering\\ImagickRenderer' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/ImagickRenderer.php',
'Jdenticon\\Rendering\\InternalPngRenderer' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/InternalPngRenderer.php',
'Jdenticon\\Rendering\\Point' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/Point.php',
'Jdenticon\\Rendering\\Rectangle' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/Rectangle.php',
'Jdenticon\\Rendering\\RendererInterface' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/RendererInterface.php',
'Jdenticon\\Rendering\\SvgPath' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/SvgPath.php',
'Jdenticon\\Rendering\\SvgRenderer' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/SvgRenderer.php',
'Jdenticon\\Rendering\\Transform' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/Transform.php',
'Jdenticon\\Rendering\\TriangleDirection' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/TriangleDirection.php',
'Jdenticon\\Shapes\\Shape' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Shapes/Shape.php',
'Jdenticon\\Shapes\\ShapeCategory' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Shapes/ShapeCategory.php',
'Jdenticon\\Shapes\\ShapeDefinitions' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Shapes/ShapeDefinitions.php',
'Jdenticon\\Shapes\\ShapePosition' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Shapes/ShapePosition.php',
'PrivateBin\\Configuration' => __DIR__ . '/../..' . '/lib/Configuration.php',
'PrivateBin\\Controller' => __DIR__ . '/../..' . '/lib/Controller.php',
'PrivateBin\\Data\\AbstractData' => __DIR__ . '/../..' . '/lib/Data/AbstractData.php',
'PrivateBin\\Data\\Database' => __DIR__ . '/../..' . '/lib/Data/Database.php',
'PrivateBin\\Data\\Filesystem' => __DIR__ . '/../..' . '/lib/Data/Filesystem.php',
'PrivateBin\\Data\\GoogleCloudStorage' => __DIR__ . '/../..' . '/lib/Data/GoogleCloudStorage.php',
'PrivateBin\\Data\\S3Storage' => __DIR__ . '/../..' . '/lib/Data/S3Storage.php',
'PrivateBin\\Filter' => __DIR__ . '/../..' . '/lib/Filter.php',
'PrivateBin\\FormatV2' => __DIR__ . '/../..' . '/lib/FormatV2.php',
'PrivateBin\\I18n' => __DIR__ . '/../..' . '/lib/I18n.php',
@@ -81,6 +127,7 @@ class ComposerStaticInitDontChange
'PrivateBin\\Request' => __DIR__ . '/../..' . '/lib/Request.php',
'PrivateBin\\View' => __DIR__ . '/../..' . '/lib/View.php',
'PrivateBin\\Vizhash16x16' => __DIR__ . '/../..' . '/lib/Vizhash16x16.php',
'PrivateBin\\YourlsProxy' => __DIR__ . '/../..' . '/lib/YourlsProxy.php',
);
public static function getInitializer(ClassLoader $loader)

59
vendor/composer/installed.php vendored Normal file
View File

@@ -0,0 +1,59 @@
<?php return array(
'root' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => '78aa70e3ab9277172489f9d88f8fd08cc1d03c97',
'name' => 'privatebin/privatebin',
'dev' => false,
),
'versions' => array(
'jdenticon/jdenticon' => array(
'pretty_version' => '1.0.1',
'version' => '1.0.1.0',
'type' => 'library',
'install_path' => __DIR__ . '/../jdenticon/jdenticon',
'aliases' => array(),
'reference' => '994ee07293fb978f983393ffcb2c0250592a6ac4',
'dev_requirement' => false,
),
'mlocati/ip-lib' => array(
'pretty_version' => '1.18.0',
'version' => '1.18.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../mlocati/ip-lib',
'aliases' => array(),
'reference' => 'c77bd0b1f3e3956c7e9661e75cb1f54ed67d95d2',
'dev_requirement' => false,
),
'paragonie/random_compat' => array(
'pretty_version' => 'v2.0.21',
'version' => '2.0.21.0',
'type' => 'library',
'install_path' => __DIR__ . '/../paragonie/random_compat',
'aliases' => array(),
'reference' => '96c132c7f2f7bc3230723b66e89f8f150b29d5ae',
'dev_requirement' => false,
),
'privatebin/privatebin' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => '78aa70e3ab9277172489f9d88f8fd08cc1d03c97',
'dev_requirement' => false,
),
'yzalis/identicon' => array(
'pretty_version' => '2.0.0',
'version' => '2.0.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../yzalis/identicon',
'aliases' => array(),
'reference' => 'ff5ed090129cab9bfa2a322857d4a01d107aa0ae',
'dev_requirement' => false,
),
),
);

26
vendor/composer/platform_check.php vendored Normal file
View File

@@ -0,0 +1,26 @@
<?php
// platform_check.php @generated by Composer
$issues = array();
if (!(PHP_VERSION_ID >= 50600)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 5.6.0". You are running ' . PHP_VERSION . '.';
}
if ($issues) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
} elseif (!headers_sent()) {
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
trigger_error(
'Composer detected issues in your platform: ' . implode(' ', $issues),
E_USER_ERROR
);
}

View File

@@ -0,0 +1,130 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas;
use Jdenticon\Canvas\Rasterization\Edge;
use Jdenticon\Canvas\Rasterization\EdgeTable;
use Jdenticon\Canvas\Rasterization\Rasterizer;
use Jdenticon\Canvas\Png\PngPalette;
use Jdenticon\Canvas\Png\PngEncoder;
use Jdenticon\Canvas\CanvasContext;
use Jdenticon\Canvas\ColorUtils;
class Canvas
{
private $edges;
/**
* Creates a new canvas with the specified dimensions given in pixels.
*
* @param integer $width Canvas width in pixels.
* @param integer $height Canvas height in pixels.
*/
public function __construct($width, $height)
{
$this->width = $width;
$this->height = $height;
$this->edges = new EdgeTable($width, $height);
}
/**
* The width of the canvas in pixels.
*
* @var integer
*/
public $width = 0;
/**
* The height of the canvas in pixels.
*
* @var integer
*/
public $height = 0;
/**
* Specifies the background color. Allowed values are:
* - 32 bit integers on the format 0xRRGGBBAA
* - strings on the format #RGB
* - strings on the format #RRGGBB
* - strings on the format #RRGGBBAA
*
* @var integer|string
*/
public $backColor = 0x00000000;
/**
* Gets a context used to draw polygons on this canvas.
*
* @returns \Jdenticon\Canvas\CanvasContext
*/
public function getContext()
{
return new CanvasContext($this, $this->edges);
}
/**
* Renders the canvas as a PNG data stream.
*
* @param array $keywords Keywords to be written to the PNG stream.
* See https://www.w3.org/TR/PNG/#11keywords.
* @returns string
*/
public function toPng($keywords = array())
{
$colorRanges = array();
Rasterizer::rasterize(
$colorRanges, $this->edges,
$this->width, $this->height);
$backColor = ColorUtils::parse($this->backColor);
if (ColorUtils::alpha($backColor) > 0) {
$isColor = false;
foreach ($colorRanges as & $value) {
if ($isColor) {
$value = ColorUtils::over($value, $backColor);
$isColor = false;
} else {
$isColor = true;
}
}
unset($value);
}
$palette = new PngPalette($colorRanges);
$png = new PngEncoder();
$png->writeImageHeader($this->width, $this->height, $palette->isValid ?
PngEncoder::INDEXED_COLOR : PngEncoder::TRUE_COLOR_WITH_ALPHA);
$png->writeImageGamma();
foreach ($keywords as $key => $value) {
$png->writeTextualData($key, $value);
}
if ($palette && $palette->isValid) {
$png->writePalette($palette);
$png->writeTransparency($palette);
$png->writeIndexed($colorRanges, $palette,
$this->width, $this->height);
} else {
$png->writeTrueColorWithAlpha($colorRanges,
$this->width, $this->height);
}
$png->writeImageEnd();
return $png->getBuffer();
}
}

View File

@@ -0,0 +1,408 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas;
use Jdenticon\Canvas\ColorUtils;
use Jdenticon\Canvas\CanvasState;
use Jdenticon\Canvas\Rasterization\EdgeTable;
use Jdenticon\Canvas\Rasterization\Edge;
use Jdenticon\Canvas\Matrix;
class CanvasContext
{
private $savedStates = array();
private $edges;
private $transform;
private $paths;
private $canvas;
/**
* Creates a new canvas with the specified dimensions given in pixels.
*
* @param \Jdenticon\Canvas\Canvas $canvas The owner canvas.
* @param array $edges The owner canvas' edge buffer.
*/
public function __construct($canvas, &$edges)
{
$this->edges = $edges;
$this->canvas = $canvas;
$this->beginPath();
$this->resetTransform();
}
/**
* Specifies the fill color that is used when the fill method is called. Allowed values are:
* - 32 bit integers on the format 0xRRGGBBAA
* - strings on the format #RGB
* - strings on the format #RRGGBB
* - strings on the format #RRGGBBAA
*
* @var integer|string
*/
public $fillStyle = 0x000000ff;
/**
* Saves the current state to the state stack.
*/
public function save()
{
array_push($this->savedStates, array(
'transform' => $this->transform,
'fillStyle' => $this->fillStyle
));
}
/**
* Restores the last saved state of the CanvasContext.
*/
public function restore()
{
$state = array_pop($this->savedStates);
if ($state != NULL) {
$this->transform = $state['transform'];
$this->fillStyle = $state['fillStyle'];
}
}
/**
* Resets the internal path buffer and begins a new path.
*/
public function resetTransform()
{
$this->transform = new Matrix(1, 0, 0, 1, 0, 0);
}
/**
* Multiplies the current transformation matrix with the specified values.
*/
public function transform($a, $b, $c, $d, $e, $f)
{
if (gettype($a) != 'integer' ||
gettype($b) != 'integer' ||
gettype($c) != 'integer' ||
gettype($d) != 'integer' ||
gettype($e) != 'integer' ||
gettype($f) != 'integer'
) {
return;
}
$this->transform = $this->transform->multiply($a, $b, $c, $d, $e, $f);
}
/**
* Sets the transformation matrix to the specified matrix.
*/
public function setTransform($a, $b, $c, $d, $e, $f)
{
if (gettype($a) != 'integer' ||
gettype($b) != 'integer' ||
gettype($c) != 'integer' ||
gettype($d) != 'integer' ||
gettype($e) != 'integer' ||
gettype($f) != 'integer'
) {
return;
}
$this->transform = new Matrix($a, $b, $c, $d, $e, $f);
}
/**
* Applies a translation transformation to the CanvasContext.
*
* @param float $x Distance to move in the horizontal direction in pixels.
* @param float $y Distance to move in the vertical direction in pixels.
*/
public function translate($x, $y)
{
$this->transform = $this->transform->translate($x, $y);
}
/**
* Applies a scale transformation to the CanvasContext.
*
* @param float $x Scale in the horizontal direction. 1 means no scale.
* @param float $y Scale in the vertical direction. 1 means no scale.
*/
public function scale($x, $y)
{
$this->transform = $this->transform->scale($x, $y);
}
/**
* Applies a rotation transformation to the canvas around its current origo.
*
* @param float $angle Angle in radians measured clockwise from the
* positive x axis.
*/
public function rotate($angle)
{
$this->transform = $this->transform->rotate($angle);
}
/**
* Removes all existing subpaths and begins a new path.
*/
public function beginPath()
{
$this->paths = array();
}
/**
* Starts a new subpath that begins in the same point as the start and end
* point of the previous one.
*/
public function closePath()
{
$pathsCount = count($this->paths);
if ($pathsCount > 0) {
$path = $this->paths[$pathsCount - 1];
$pathCount = count($path);
if ($pathCount > 2) {
// Close path
if ($path[0] != $path[$pathCount - 2] ||
$path[1] != $path[$pathCount - 1]
) {
$path[] = $path[0];
$path[] = $path[1];
}
// Begin a new path
$this->paths[] = array($path[0], $path[1]);
}
}
}
/**
* Begins a new subpath by moving the cursor to the specified position.
*
* @param float $x X coordinate.
* @param float $y Y coordinate.
*/
public function moveTo($x, $y)
{
$p = $this->transform->multiplyPoint($x, $y);
$this->paths[] = array($p->x, $p->y);
}
/**
* Inserts an edge between the last and specified position.
*
* @param float $x Target X coordinate.
* @param float $y Target Y coordinate.
* @public
*/
public function lineTo($x, $y)
{
$pathsCount = count($this->paths);
if ($pathsCount == 0) {
$this->paths[] = array();
$pathsCount++;
}
$p = $this->transform->multiplyPoint($x, $y);
$path = &$this->paths[$pathsCount - 1];
$path[] = $p->x;
$path[] = $p->y;
}
/**
* Adds an arc to the current path.
*
* @param float $x X coordinate of the center of the arc.
* @param float $y Y coordinate of the center of the arc.
* @param float $radius Radius of the arc.
* @param float $startAngle The angle in radians at which the arc starts,
* measured clockwise from the positive x axis.
* @param float $endAngle The angle in radians at which the arc end,
* measured clockwise from the positive x axis.
* @param boolean $anticlockwise Specifies whether the arc will be drawn
* counter clockwise. Default is clockwise.
*/
public function arc($x, $y, $radius, $startAngle, $endAngle, $anticlockwise)
{
$TARGET_CHORD_LENGTH_PIXELS = 3;
$sectors = floor((M_PI * $radius * 2) / $TARGET_CHORD_LENGTH_PIXELS);
if ($sectors < 9) {
$sectors = 9;
}
$sectorAngle = M_PI * 2 / $sectors;
if ($startAngle == $endAngle) {
return;
}
if ($anticlockwise) {
$sectorAngle = -$sectorAngle;
if ($startAngle - $endAngle >= M_PI * 2) {
$endAngle = $startAngle - M_PI * 2;
} else {
// Normalize end angle so that the sweep angle is in the range
// (0, -2PI]
$endAngle +=
M_PI * 2 * ceil(($startAngle - $endAngle) /
(M_PI * 2) - 1);
}
} else {
if ($endAngle - $startAngle >= M_PI * 2) {
$endAngle = $startAngle + M_PI * 2;
} else {
// Normalize end angle so that the sweep angle is in the range
// (0, 2PI]
$endAngle -=
M_PI * 2 * ceil(($endAngle - $startAngle) /
(M_PI * 2) - 1);
}
}
$dx;
$dy;
$sectors = ($endAngle - $startAngle) / $sectorAngle;
$angle = $startAngle;
for ($i = 0; $i < $sectors; $i++) {
$dx = cos($angle) * $radius;
$dy = sin($angle) * $radius;
$this->lineTo($x + $dx, $y + $dy);
$angle += $sectorAngle;
}
$dx = cos($endAngle) * $radius;
$dy = sin($endAngle) * $radius;
$this->lineTo($x + $dx, $y + $dy);
}
/**
* Fills the specified rectangle with fully transparent black without
* affecting the current paths.
*
* @param float $x X coordinate of the left side of the rectangle.
* @param float $y Y coordinate of the top of the rectangle.
* @param float $width Width of the rectangle.
* @param float $height Height of the rectangle.
*/
public function clearRect($x, $y, $width, $height)
{
$fullCanvas = false;
if (!$this->transform->hasSkewing()) {
// Check if the whole canvas is cleared
$topLeft = $this->transform->multiplyPoint($x, $y);
if ($topLeft->x <= 0 && $topLeft->y <= 0) {
$bottomRight = $this->transform->multiplyPoint(
$x + $width, $y + $height);
if ($bottomRight->x >= $this->canvas->width &&
$bottomRight->y >= $this->canvas->height
) {
$fullCanvas = true;
}
}
}
if ($fullCanvas) {
$this->edges->clear();
} else {
$this->_fillRect(ColorUtils::FORCE_TRANSPARENT,
$x, $y, $width, $height);
}
}
/**
* Fills the specified rectangle without affecting the current paths.
*
* @param float $x X coordinate of the left side of the rectangle.
* @param float $y Y coordinate of the top of the rectangle.
* @param float $width Width of the rectangle.
* @param float $height Height of the rectangle.
*/
public function fillRect($x, $y, $width, $height)
{
$fillColor = ColorUtils::parse($this->fillStyle);
$this->_fillRect($fillColor, $x, $y, $width, $height);
}
private function _fillRect($fillColor, $x, $y, $width, $height)
{
$polygonId = $this->edges->getNextPolygonId();
$points = array(
$this->transform->multiplyPoint($x, $y),
$this->transform->multiplyPoint($x + $width, $y),
$this->transform->multiplyPoint($x + $width, $y + $height),
$this->transform->multiplyPoint($x, $y + $height),
$this->transform->multiplyPoint($x, $y)
);
$pointsCount = count($points);
for ($i = 1; $i < $pointsCount; $i++) {
$this->edges->add(new Edge(
$polygonId,
$points[$i - 1]->x,
$points[$i - 1]->y,
$points[$i]->x,
$points[$i]->y,
$fillColor));
}
}
/**
* Fills the defined paths.
*
* @param string $windingRule The winding rule to be used for determining
* which areas are covered by the current path. Valid values are
* "evenodd" and "nonzero". Default is "nonzero".
*/
public function fill($windingRule = "nonzero")
{
$polygonId = $this->edges->getNextPolygonId();
$fillColor = ColorUtils::parse($this->fillStyle);
foreach ($this->paths as $points) {
$pointsCount = count($points);
if ($pointsCount <= 2) {
// Nothing to fill
continue;
}
for ($i = 2; $i < $pointsCount; $i += 2) {
$this->edges->add(new Edge(
$polygonId,
$points[$i - 2],
$points[$i - 1],
$points[$i],
$points[$i + 1],
$fillColor,
$windingRule));
}
// Close path
if ($points[0] != $points[$pointsCount - 2] ||
$points[1] != $points[$pointsCount - 1]
) {
$this->edges->add(new Edge(
$polygonId,
$points[$pointsCount - 2],
$points[$pointsCount - 1],
$points[0],
$points[1],
$fillColor,
$windingRule));
}
}
}
}

View File

@@ -0,0 +1,228 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas;
class ColorUtils
{
/**
* Transparent color.
* @var integer
*/
const TRANSPARENT = 0;
/**
* Specifies a transparent color that will not blend with layers below the
* current layer.
*
* @var float
*/
const FORCE_TRANSPARENT = INF;
/**
* Creates a color on the format 0xRRGGBBAA from the specified
* color components.
*
* @return integer
*/
public static function from($a, $r, $g, $b)
{
return ($r << 24) | ($g << 16) | ($b << 8) | $a;
}
/**
* Gets the alpha component of a color.
*
* @param integer $color 32-bit color value on the format 0xRRGGBBAA.
* @return integer Alpha in the range [0, 255].
*/
public static function alpha($color)
{
return $color & 0xff;
}
/**
* Gets the red component of a color.
*
* @param integer $color 32-bit color value on the format 0xRRGGBBAA.
* @return integer Red component in the range [0, 255].
*/
public static function red($color)
{
return ($color >> 24) & 0xff;
}
/**
* Gets the green component of a color.
*
* @param integer $color 32-bit color value on the format 0xRRGGBBAA.
* @return integer Green component in the range [0, 255].
*/
public static function green($color)
{
return ($color >> 16) & 0xff;
}
/**
* Gets the blue component of a color.
*
* @param integer $color 32-bit color value on the format 0xRRGGBBAA.
* @return integer Blue component in the range [0, 255].
*/
public static function blue($color)
{
return ($color >> 8) & 0xff;
}
/**
* Formats a color as a string.
*
* @param integer $color Color to format.
* @return string
*/
public static function format($color)
{
return bin2hex(pack('N', $color));
}
/**
* Computes a mix of the two specified colors, with the proportion given
* by the specified weight.
*
* @param integer $color1 First color to mix.
* @param integer $color2 Second color to mix.
* @param float $weight Weight in the range [0,1].
* 0 gives $color1, 1 gives $color2.
* @return integer Mixed color.
*/
public static function mix($color1, $color2, $weight)
{
if ($weight < 0) {
$weight = 0;
} elseif ($weight > 1) {
$weight = 1;
}
$a = ($color1 & 0xff) * (1 - $weight) + ($color2 & 0xff) * $weight;
if ($a <= 0.1) {
return 0;
}
$r = (
($color1 >> 24) * ($color1 & 0xff) * (1 - $weight) +
($color2 >> 24) * ($color2 & 0xff) * $weight
) / $a;
$g = (
(($color1 >> 16) & 0xff) * ($color1 & 0xff) * (1 - $weight) +
(($color2 >> 16) & 0xff) * ($color2 & 0xff) * $weight
) / $a;
$b = (
(($color1 >> 8) & 0xff) * ($color1 & 0xff) * (1 - $weight) +
(($color2 >> 8) & 0xff) * ($color2 & 0xff) * $weight
) / $a;
if ($a > 255) $a = 255;
if ($r > 255) $r = 255;
if ($g > 255) $g = 255;
if ($b > 255) $b = 255;
return ((int)$r << 24) | ((int)$g << 16) | ((int)$b << 8) | (int)$a;
}
/**
* Parses a value to a 32-bit color on the format 0xRRGGBBAA.
*
* @param integer|string $color The value to parse.
* @return integer
*/
public static function parse($color)
{
if (gettype($color) == "integer") {
return $color & 0xffffffff;
}
$color = "$color";
if (preg_match('/^#?[0-9a-fA-F]+$/', $color)) {
$hexColor = $color;
if ($hexColor[0] == '#') {
$hexColor = substr($hexColor, 1);
}
switch (strlen($hexColor)) {
case 3:
$numeric = intval($hexColor, 16);
return (
(($numeric & 0xf00) << 20) |
(($numeric & 0xf00) << 16) |
(($numeric & 0x0f0) << 16) |
(($numeric & 0x0f0) << 12) |
(($numeric & 0x00f) << 12) |
(($numeric & 0x00f) << 8) |
0xff);
case 6:
return (intval($hexColor, 16) << 8) | 0xff;
case 8:
// Workaround to cope with PHP limitation of intval
$numeric =
(intval(substr($hexColor, 0, 4), 16) << 16) |
(intval(substr($hexColor, 4, 4), 16));
return $numeric;
}
}
throw new \InvalidArgumentException("Invalid color '$color'.");
}
/**
* Blends this color with another color using the over blending operation.
*
* @param integer $fore The foreground color.
* @param integer $back The background color.
* @return integer
*/
public static function over($fore, $back)
{
$foreA = ($fore & 0xff);
$backA = ($back & 0xff);
if ($foreA < 1) {
return $back;
} elseif ($foreA > 254 || $backA < 1) {
return $fore;
}
// Source:
// https://en.wikipedia.org/wiki/Alpha_compositing#Description
$forePA = $foreA * 255;
$backPA = $backA * (255 - $foreA);
$pa = ($forePA + $backPA);
$b = (int) (
($forePA * (($fore >> 8) & 0xff) + $backPA * (($back >> 8) & 0xff)) /
$pa);
$g = (int) (
($forePA * (($fore >> 16) & 0xff) + $backPA * (($back >> 16) & 0xff)) /
$pa);
$r = (int) (
($forePA * (($fore >> 24) & 0xff) + $backPA * (($back >> 24) & 0xff)) /
$pa);
$a = (int) ($pa / 255);
return ($r << 24) | ($g << 16) | ($b << 8) | $a;
}
};

View File

@@ -0,0 +1,141 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas;
use Jdenticon\Canvas\Point;
class Matrix
{
private $a;
private $b;
private $c;
private $d;
private $e;
private $f;
/**
* Creates a new transformation matrix.
*/
public function __construct($a, $b, $c, $d, $e, $f)
{
$this->a = $a;
$this->b = $b;
$this->c = $c;
$this->d = $d;
$this->e = $e;
$this->f = $f;
}
/**
* Gets a value determining if this matrix has skewing values.
*
* @return boolean
*/
public function hasSkewing()
{
return $this->b || $this->c;
}
/**
* Gets a value determining if this matrix has translation values.
*
* @return boolean
*/
public function hasTranslation()
{
return $this->e || $this->f;
}
/**
* Gets a value determining if this matrix has scaling values.
*
* @return boolean
*/
public function hasScaling()
{
return $this->a != 1 || $this->d != 1;
}
/**
* Returns a new matrix based on the current matrix multiplied with the
* specified matrix values.
*
* @return \Jdenticon\Canvas\Matrix
*/
public function multiply($a, $b, $c, $d, $e, $f)
{
return new Matrix(
$this->a * $a + $this->c * $b,
$this->b * $a + $this->d * $b,
$this->a * $c + $this->c * $d,
$this->b * $c + $this->d * $d,
$this->a * $e + $this->c * $f + $this->e,
$this->b * $e + $this->d * $f + $this->f
);
}
/**
* Multiplies the specified point with the current matrix and returns the
* resulting point.
*
* @param float $x X coordinate.
* @param float $y Y coordinate.
* @return \Jdenticon\Canvas\Point
*/
public function multiplyPoint($x, $y)
{
return new Point(
$this->a * $x + $this->c * $y + $this->e,
$this->b * $x + $this->d * $y + $this->f
);
}
/**
* Returns a new matrix based on the current matrix with a rotation
* transformation applied.
*
* @param float $angle Rotation angle in radians.
* @return \Jdenticon\Canvas\Matrix
*/
public function rotate($angle)
{
$sin = sin($angle);
$cos = cos($angle);
return $this->multiply($cos, $sin, -$sin, $cos, 0, 0);
}
/**
* Returns a new matrix based on the current matrix with a translation
* transformation applied.
*
* @param float $x Horizontal move distance.
* @param float $y Vertical move distance.
* @return \Jdenticon\Canvas\Matrix
*/
public function translate($x, $y)
{
return $this->multiply(1, 0, 0, 1, $x, $y);
}
/**
* Returns a new matrix based on the current matrix with a scaling
* transformation applied.
*
* @param float $x Horizontal scale.
* @param float $y Vertical scale.
* @return \Jdenticon\Canvas\Matrix
*/
public function scale($x, $y)
{
return $this->multiply($x, 0, 0, $y, 0, 0);
}
}

View File

@@ -0,0 +1,92 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas\Png;
class PngBuffer
{
private $buffer = '';
private $chunkPreviousBuffer = '';
/**
* Writes a string to the buffer.
*
* @param string $str String to write.
*/
public function writeString($str)
{
$this->buffer .= $str;
}
/**
* Writes a 32 bit unsigned int to the buffer in Big Endian format.
*
* @param integer $value Value to write.
*/
public function writeUInt32BE($value)
{
$this->buffer .= pack('N', $value);
}
/**
* Writes an 8 bit unsigned int to the buffer.
*
* @param integer $value Value to write.
*/
public function writeUInt8($value)
{
$this->buffer .= pack('C', $value);
}
/**
* Starts a new PNG chunk.
*
* @param string $type Name of the chunk. Must contain exactly 4
* ASCII characters.
*/
public function startChunk($type)
{
$this->chunkPreviousBuffer = $this->buffer;
$this->buffer = $type;
}
/**
* Closes the current PNG chunk.
*/
public function endChunk()
{
// Compute Crc32 for type + data
$data = $this->buffer;
$crc = crc32($data);
$this->buffer =
$this->chunkPreviousBuffer .
// Length
pack('N', strlen($data) - 4) .
// Content
$data .
// Crc32
pack('N', $crc);
}
/**
* Gets a string containing the PNG encoded data.
*
* @return string
*/
public function getBuffer()
{
return $this->buffer;
}
}

View File

@@ -0,0 +1,238 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas\Png;
use Jdenticon\Canvas\Png\PngPalette;
use Jdenticon\Canvas\Png\PngBuffer;
use Jdenticon\Canvas\ColorUtils;
class PngEncoder
{
const GRAYSCALE = 0;
const TRUE_COLOR = 2;
const INDEXED_COLOR = 3;
const GRAYSCALE_WITH_ALPHA = 4;
const TRUE_COLOR_WITH_ALPHA = 6;
private $buffer;
public function __construct()
{
$this->buffer = new PngBuffer();
$this->buffer->writeString("\x89\x50\x4e\x47\xd\xa\x1a\xa");
}
/**
* Writes an IHDR chunk to the png data stream.
*
* @param int $width Image width in pixels.
* @param int $height Image height in pixels.
* @param int $colorType Color depth, speocfy one of the constants in
* PngEncoder.
*/
public function writeImageHeader($width, $height, $colorType)
{
$this->buffer->startChunk("IHDR");
$this->buffer->writeUInt32BE($width);
$this->buffer->writeUInt32BE($height);
$this->buffer->writeUInt8(8); // Bit depth
$this->buffer->writeUInt8($colorType);
$this->buffer->writeUInt8(0); // Compression
$this->buffer->writeUInt8(0); // Filter
$this->buffer->writeUInt8(0); // Interlace
$this->buffer->endChunk();
}
/**
* Writes a gAMA chunk to the png data stream.
*
* @param int $gamma Gamma value.
*/
public function writeImageGamma($gamma = 45455)
{
$this->buffer->startChunk("gAMA");
$this->buffer->writeUInt32BE($gamma);
$this->buffer->endChunk();
}
/**
* Writes an IDAT chunk of truecolor encoded image data.
*
* @param array $colorRanges Image data on the format
* array(count0, color0, count1, color1, ...)
* @param int $width Image width in pixels.
* @param int $height Image height in pixels.
*/
public function writeTrueColorWithAlpha(
array & $colorRanges, $width, $height)
{
$this->buffer->startChunk("IDAT");
$uncompressed = '';
$count = -1;
$x = 0;
foreach ($colorRanges as $value) {
if ($count === -1) {
$count = $value;
} else {
if ($count !== 0) {
if ($x === $width) {
$x = 0;
}
if ($x === 0) {
$uncompressed .= pack('C', 0); // No filtering
}
$uncompressed .= str_repeat(pack('N', $value), $count);
$x += $count;
}
$count = -1;
}
}
$compressed = gzcompress($uncompressed, 2);
$this->buffer->writeString($compressed);
$this->buffer->endChunk();
}
/**
* Writes an IDAT chunk of indexed image data.
*
* @param array $colorRanges Image data on the format
* array(count0, color0, count1, color1, ...)
* @param \Jdenticon\Canvas\Png\PngPalette $palette Palette containing the
* indexed colors.
* @param int $width Image width in pixels.
* @param int $height Image height in pixels.
*/
public function writeIndexed(
array & $colorRanges,
PngPalette $palette,
$width, $height)
{
$this->buffer->startChunk("IDAT");
$uncompressed = '';
$count = -1;
$x = 0;
foreach ($colorRanges as $value) {
if ($count === -1) {
$count = $value;
} else {
if ($count !== 0) {
if ($x === $width) {
$x = 0;
}
if ($x === 0) {
$uncompressed .= pack('C', 0); // No filtering
}
$colorIndex = $palette->lookup[$value];
$uncompressed .= str_repeat(pack('C', $colorIndex), $count);
$x += $count;
}
$count = -1;
}
}
$compressed = gzcompress($uncompressed, 2);
$this->buffer->writeString($compressed);
$this->buffer->endChunk();
}
/**
* Writes a PLTE chunk containing the indexed colors.
*
* @param \Jdenticon\Canvas\Png\PngPalette $palette Palette containing the
* indexed colors.
*/
public function writePalette(PngPalette $palette)
{
if ($palette && $palette->isValid) {
$this->buffer->startChunk("PLTE");
foreach ($palette->colors as $color) {
$this->buffer->writeString(
pack('C', ($color >> 24) & 0xff) .
pack('C', ($color >> 16) & 0xff) .
pack('C', ($color >> 8) & 0xff));
}
$this->buffer->endChunk();
}
}
/**
* Writes a tRNS chunk containing the alpha values of indexed colors.
*
* @param \Jdenticon\Canvas\Png\PngPalette $palette Palette containing the
* indexed colors.
*/
public function writeTransparency(PngPalette $palette)
{
if ($palette && $palette->isValid && $palette->hasAlphaChannel) {
$this->buffer->startChunk("tRNS");
$alpha = '';
foreach ($palette->colors as $color) {
$alpha .= pack('C', $color & 0xff);
}
$this->buffer->writeString($alpha);
$this->buffer->endChunk();
}
}
/**
* Writes a tEXt chunk containing the specified strings.
*
* @param string $key Key, one of
* {@link https://www.w3.org/TR/2003/REC-PNG-20031110/#11keywords}
* @param string $value Value.
*/
public function writeTextualData($key, $value)
{
$this->buffer->startChunk("tEXt");
$this->buffer->writeString($key);
$this->buffer->writeUInt8(0);
$this->buffer->writeString($value);
$this->buffer->endChunk();
}
/**
* Writes an IEND chunk to the png data stream.
*/
public function writeImageEnd()
{
$this->buffer->startChunk("IEND");
$this->buffer->endChunk();
}
/**
* Gets a binary string containing the PNG data.
*
* @return string
*/
public function getBuffer()
{
return $this->buffer->getBuffer();
}
}

View File

@@ -0,0 +1,92 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas\Png;
use Jdenticon\Canvas\ColorUtils;
/**
* Contains the colors of a PNG color palette.
*/
class PngPalette
{
/**
* Creates a PNG color palette for the specified bitmap data.
*
* @param array(integer) $colorRanges Array of interleaved values on the
* format array(count0, color0, count1, color1, ...).
*/
function __construct(& $colorRanges)
{
$lookup = array();
$colors = array();
$hasAlphaChannel = false;
$colorsCount = 0;
$count = -1;
foreach ($colorRanges as $value) {
if ($count === -1) {
$count = $value;
} else {
// Ignore empty ranges and already indexed colors
if ($count > 0 && !isset($lookup[$value])) {
if (!$hasAlphaChannel && ($value & 0xff) < 255) {
$hasAlphaChannel = true;
}
$lookup[$value] = $colorsCount++;
$colors[] = $value;
if ($colorsCount > 256) {
break;
}
}
$count = -1;
}
}
$this->hasAlphaChannel = $hasAlphaChannel;
$this->colors = & $colors;
$this->lookup = & $lookup;
$this->isValid = $colorsCount <= 256;
}
/**
* Specifies if the palette is valid to be used for encoding a PNG image.
*
* @var boolean
*/
public $isValid;
/**
* Specifies if the palette has any partial or fully transparent
* colors.
*
* @var boolean
*/
public $hasAlphaChannel;
/**
* Array of colors in the palette.
*
* @var array
*/
public $colors;
/**
* Lookup table from 32-bit color value to color index.
*
* @var array
*/
public $lookup;
}

View File

@@ -0,0 +1,41 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas;
class Point
{
/**
* X coordinate.
*
* @var float
*/
public $x;
/**
* Y coordinate.
*
* @var float
*/
public $y;
/**
* Creates a new 2D point.
*
* @param float $x X coordinate.
* @param float $y Y coordinate.
*/
public function __construct($x, $y)
{
$this->x = $x;
$this->y = $y;
}
}

View File

@@ -0,0 +1,44 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas\Rasterization;
class Edge
{
public $polygonId;
public $x0;
public $x1;
public $y0;
public $y1;
public $color;
public $windingRule;
public function __construct(
$polygonId, $x0, $y0, $x1, $y1, $color, $windingRule = null)
{
$this->polygonId = $polygonId;
$this->x0 = $x0;
$this->x1 = $x1;
$this->y0 = $y0;
$this->y1 = $y1;
$this->color = $color;
$this->windingRule = $windingRule;
}
public function intersection($y)
{
$dx =
($this->x1 - $this->x0) * ($this->y0 - $y) /
($this->y0 - $this->y1);
return $this->x0 + $dx;
}
}

View File

@@ -0,0 +1,27 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas\Rasterization;
class EdgeIntersection
{
public $fromX;
public $width;
public $edge;
public function __construct($fromX, $width, $edge)
{
$this->fromX = $fromX;
$this->width = $width;
$this->edge = $edge;
}
}

View File

@@ -0,0 +1,24 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas\Rasterization;
class EdgeSuperSampleIntersection
{
public $x;
public $edge;
public function __construct($x, $edge)
{
$this->x = $x;
$this->edge = $edge;
}
}

View File

@@ -0,0 +1,158 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas\Rasterization;
class EdgeTable
{
private $scanlines;
private $nextPolygonId;
private $width;
private $height;
/**
* Keeps a list of edges per scanline.
*
* @param integer $width Clipping width.
* @param integer $height Clipping height.
*/
public function __construct($width, $height)
{
$this->width = $width;
$this->height = $height;
$this->clear();
}
/**
* Sorts the edges of each scanline in ascending x coordinates.
*/
public function clear()
{
$this->scanlines = array();
$this->nextPolygonId = 1;
}
/**
* Gets an id for the next polygon.
*
* @return int
*/
public function getNextPolygonId()
{
return $this->nextPolygonId++;
}
/**
* Gets the scaline for the specified Y coordinate, or NULL if there are
* no edges for the specified Y coordinate.
*
* @return array|null.
*/
public function getScanline($y)
{
return isset($this->scanlines[$y]) ? $this->scanlines[$y] : null;
}
/**
* Adds an edge to the table.
*
* @param \Jdenticon\Canvas\Rasterization\Edge $edge
*/
public function add(\Jdenticon\Canvas\Rasterization\Edge $edge)
{
$minY = 0;
$maxY = 0;
if ($edge->y0 == $edge->y1) {
// Skip horizontal lines
return;
} elseif ($edge->y0 < $edge->y1) {
$minY = (int)($edge->y0);
$maxY = (int)($edge->y1 + 0.996 /* 1/255 */);
} else {
$minY = (int)($edge->y1);
$maxY = (int)($edge->y0 + 0.996 /* 1/255 */);
}
if ($maxY < 0 || $minY >= $this->height) {
return;
}
if ($minY < 0) {
$minY = 0;
}
if ($maxY > $this->height) {
$maxY = $this->height;
}
if ($minY < $maxY) {
$y = $minY;
$x1 = $edge->intersection($y);
while ($y < $maxY) {
$x2 = $edge->intersection($y + 1);
$fromX;
$width;
if ($x1 < $x2) {
$fromX = (int)($x1);
$width = (int)($x2 + 0.9999) - $fromX;
} else {
$fromX = (int)($x2);
$width = (int)($x1 + 0.9999) - $fromX;
}
if ($fromX < 0) {
$width += $fromX;
$fromX = 0;
if ($width < 0) {
$width = 0;
}
}
if ($fromX < $this->width) {
if (!isset($this->scanlines[$y])) {
$this->scanlines[$y] = array();
}
$this->scanlines[$y][] = new EdgeIntersection(
$fromX, $width, $edge);
}
$x1 = $x2;
$y++;
}
}
}
private static function edge_cmp($x, $y)
{
if ($x->fromX < $y->fromX) {
return -1;
}
if ($x->fromX > $y->fromX) {
return 1;
}
return 0;
}
/**
* Sorts the edges of each scanline in ascending x coordinates.
*/
public function sort()
{
foreach ($this->scanlines as $i => &$scanline) {
usort($scanline, array(
'Jdenticon\\Canvas\\Rasterization\\EdgeTable', 'edge_cmp'));
}
}
}

View File

@@ -0,0 +1,30 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas\Rasterization;
class Layer
{
public $polygonId;
public $color;
public $winding;
public $windingRule;
public $nextLayer;
public function __construct($polygonId, $color, $winding, $windingRule)
{
$this->polygonId = $polygonId;
$this->color = $color;
$this->winding = $winding;
$this->windingRule = $windingRule;
}
}

View File

@@ -0,0 +1,150 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas\Rasterization;
use Jdenticon\Canvas\ColorUtils;
use Jdenticon\Canvas\Rasterization\Layer;
use Jdenticon\Canvas\Rasterization\Edge;
/**
* Keeps track of the z-order of the currently rendered polygons,
* and computes the final color from the stack of layers.
*/
class LayerManager
{
public $topLayer;
/**
* The current visible color.
* @var integer
*/
public $color;
public function __construct()
{
$this->color = ColorUtils::TRANSPARENT;
}
/**
* Copies all layers in this manager to another LayerManager.
*
* @param \Jdenticon\Canvas\Rasterization\LayerManager $other The
* LayerManager to copy all layers to.
*/
public function copyTo(LayerManager $other)
{
$other->color = $this->color;
$layer = $this->topLayer;
$previousCopy = null;
while ($layer !== null) {
$copy = new Layer(
$layer->polygonId,
$layer->color,
$layer->winding,
$layer->windingRule
);
if ($previousCopy === null) {
$other->topLayer = $copy;
}
else {
$previousCopy->nextLayer = $copy;
}
$previousCopy = $copy;
$layer = $layer->nextLayer;
}
}
/**
* Adds a layer for the specified edge. The z-order is defined by its id.
*
* @param \Jdenticon\Canvas\Rasterization\Edge edge
*/
public function add(Edge $edge)
{
$dwinding = $edge->y0 < $edge->y1 ? 1 : -1;
$layer = $this->topLayer;
$previousLayer = null;
while ($layer !== null) {
if ($layer->polygonId === $edge->polygonId) {
$layer->winding += $dwinding;
$inPath = $layer->windingRule == 'evenodd' ?
($layer->winding % 2 === 1) : ($layer->winding !== 0);
if (!$inPath) {
// Remove layer
if ($previousLayer === null) {
$this->topLayer = $layer->nextLayer;
}
else {
$previousLayer->nextLayer = $layer->nextLayer;
}
}
break;
} elseif ($layer->polygonId < $edge->polygonId) {
// Insert here
$newLayer = new Layer(
$edge->polygonId,
$edge->color,
$dwinding,
$edge->windingRule
);
$newLayer->nextLayer = $layer;
if ($previousLayer === null) {
$this->topLayer = $newLayer;
} else {
$previousLayer->nextLayer = $newLayer;
}
break;
}
$previousLayer = $layer;
$layer = $layer->nextLayer;
}
if ($layer === null) {
$newLayer = new Layer(
$edge->polygonId,
$edge->color,
$dwinding,
$edge->windingRule
);
if ($previousLayer === null) {
$this->topLayer = $newLayer;
} else {
$previousLayer->nextLayer = $newLayer;
}
}
// Update current color
$color = ColorUtils::TRANSPARENT;
$layer = $this->topLayer;
while ($layer !== null && ($color & 0xff) < 255) {
if ($layer->color === ColorUtils::FORCE_TRANSPARENT) {
break;
}
$color = ColorUtils::over($layer->color, $color);
}
$this->color = $color;
}
}

View File

@@ -0,0 +1,379 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas\Rasterization;
use Jdenticon\Canvas\ColorUtils;
use Jdenticon\Canvas\Rasterization\LayerManager;
use Jdenticon\Canvas\Rasterization\SuperSampleBuffer;
use Jdenticon\Canvas\Rasterization\SuperSampleRange;
use Jdenticon\Canvas\Rasterization\EdgeSuperSampleIntersection;
class Rasterizer
{
/**
* A higher number of samples per pixel horizontally does not affect the
* performance in the same way as SAMPLES_PER_PIXEL_Y, since the rasterizer
* does not scan every subpixel horizontally.
*/
const SAMPLES_PER_PIXEL_X = 10;
/**
* A higher number of samples vertically means lower performance, since
* the rasterizer does a scan for every subpixel vertically.
*/
const SAMPLES_PER_PIXEL_Y = 3;
const SAMPLE_HEIGHT = 0.33333; // 1 / self::SAMPLES_PER_PIXEL_Y
/**
* Rasterizes the edges in the edge table to a list of color ranges. No
* range will span multiple scanlines.
*/
public static function rasterize(& $colorData, $edgeTable, $width, $height)
{
$edgeTable->sort();
$superSampleBuffer = new SuperSampleBuffer(
$width, self::SAMPLES_PER_PIXEL_X);
$layers = array();
$color = 0;
// Keeps track of how many of the subpixellayers that are used for
// the currently rendered scanline. Until a range requiring
// supersampling is encountered only a single layer is needed.
$usedLayers = 0;
for ($i = 0; $i < self::SAMPLES_PER_PIXEL_Y; $i++) {
$layers[] = new LayerManager();
}
for ($ey = 0; $ey < $height; $ey++) {
$scanline = $edgeTable->getScanline($ey);
if ($scanline === null) {
$colorData[] = $width;
$colorData[] = 0;
continue;
}
$superSampleRanges = self::getSuperSampleRanges($scanline, $width);
$superSampleRangeCount = count($superSampleRanges);
foreach ($layers as $layer) {
$layer->topLayer = null;
$layer->color = ColorUtils::TRANSPARENT;
}
$usedLayers = 1;
if ($superSampleRanges[0]->fromX) {
$colorData[] = $superSampleRanges[0]->fromX;
$colorData[] = 0;
}
for (
$rangeIndex = 0;
$rangeIndex < $superSampleRangeCount;
$rangeIndex++
) {
$superSampleRange = $superSampleRanges[$rangeIndex];
$edge = $superSampleRange->edges[0];
// If there is exactly one edge in the supersample range, and it
// is crossing the entire scanline, we can perform the
// antialiasing by integrating the edge function.
if (!isset($superSampleRange->edges[1]) && (
$edge->y0 <= $ey && $edge->y1 >= $ey + 1 ||
$edge->y0 >= $ey + 1 && $edge->y1 <= $ey
)) {
// Determine the lower and upper x value where the edge
// intersects the scanline.
$xey = $edge->intersection($ey);
$xey1 = $edge->intersection($ey + 1);
if ($xey < $xey1) {
$x0 = $xey;
$x1 = $xey1;
} else {
$x0 = $xey1;
$x1 = $xey;
}
$rangeWidth = $x1 - $x0;
if ($usedLayers === 1) {
$subScanlineLayers = $layers[0];
$fromColor = $subScanlineLayers->color;
$subScanlineLayers->add($edge);
$toColor = $subScanlineLayers->color;
} else {
$fromColorR = 0;
$fromColorG = 0;
$fromColorB = 0;
$fromColorA = 0;
$toColorR = 0;
$toColorG = 0;
$toColorB = 0;
$toColorA = 0;
// Compute the average color of all subpixel layers
// before and after the edge intersection.
// The calculation is inlined for increased performance.
for ($i = 0; $i < $usedLayers; $i++) {
$subScanlineLayers = $layers[$i];
// Add to average from-color
$color = $subScanlineLayers->color;
$alpha = $color & 0xff;
if ($alpha > 0) {
$fromColorA += $alpha;
$fromColorR += (($color >> 24) & 0xff) * $alpha;
$fromColorG += (($color >> 16) & 0xff) * $alpha;
$fromColorB += (($color >> 8) & 0xff) * $alpha;
}
// Add the new layer
$subScanlineLayers->add($edge);
// Add to average to-color
$color = $subScanlineLayers->color;
$alpha = $color & 0xff;
if ($alpha > 0) {
$toColorA += $alpha;
$toColorR += (($color >> 24) & 0xff) * $alpha;
$toColorG += (($color >> 16) & 0xff) * $alpha;
$toColorB += (($color >> 8) & 0xff) * $alpha;
}
}
$fromColor = $fromColorA === 0 ? 0 : ColorUtils::from(
(int)($fromColorA / $usedLayers),
(int)($fromColorR / $fromColorA),
(int)($fromColorG / $fromColorA),
(int)($fromColorB / $fromColorA));
$toColor = $toColorA === 0 ? 0 : ColorUtils::from(
(int)($toColorA / $usedLayers),
(int)($toColorR / $toColorA),
(int)($toColorG / $toColorA),
(int)($toColorB / $toColorA));
}
// Render pixels
for (
$x = $superSampleRange->fromX;
$x < $superSampleRange->toXExcl;
$x++
) {
if ($x0 >= $x + 1) {
// Pixel not covered
$colorData[] = 1;
$colorData[] = $fromColor;
continue;
}
if ($x1 <= $x) {
// Pixel fully covered
$colorData[] = 1;
$colorData[] = $toColor;
continue;
}
// toColor coverage in the range [0.0, 1.0]
// Initialize to the fully covered range of the pixel.
$coverage = $x1 < $x + 1 ? $x + 1 - $x1 : 0;
// Compute integral for non-vertical edges
if ($rangeWidth > 0.001) {
// Range to integrate
$integralFrom = $x0 > $x ? $x0 : $x;;
$integralTo = $x1 < $x + 1 ? $x1 : $x + 1;
$coverage +=
(
(
$integralTo * $integralTo -
$integralFrom * $integralFrom
) / 2 +
$x0 * ($integralFrom - $integralTo)
) / $rangeWidth;
}
$colorData[] = 1;
$colorData[] = ColorUtils::mix(
$fromColor, $toColor, $coverage);
}
$color = $toColor;
} // /simplified antialiasing
else {
// Super sampling
$y = $ey + self::SAMPLE_HEIGHT / 2;
// Ensure all subpixel layers are initialized
while ($usedLayers < self::SAMPLES_PER_PIXEL_Y) {
$layers[0]->copyTo($layers[$usedLayers]);
$usedLayers++;
}
foreach ($layers as $subScanlineLayers) {
$color = $subScanlineLayers->color;
$intersections = self::getIntersections(
$superSampleRange->edges, $y);
foreach ($intersections as $intersection) {
$superSampleBuffer->add($color,
$intersection->x - $superSampleRange->fromX);
$subScanlineLayers->add($intersection->edge);
$color = $subScanlineLayers->color;
}
$superSampleBuffer->add(
$color, $superSampleRange->width + 1);
$superSampleBuffer->rewind();
$y += self::SAMPLE_HEIGHT;
} // /subpixel
// Blend subpixels
$color = $superSampleBuffer->colorAt(
$superSampleRange->width);
$superSampleBuffer->emptyTo(
$colorData, $superSampleRange->width);
//$color = end($colorData);
} // /super sampling
// Forward last color
if ($rangeIndex + 1 < $superSampleRangeCount) {
$count =
$superSampleRanges[$rangeIndex + 1]->fromX -
$superSampleRange->toXExcl;
if ($count > 0) {
$colorData[] = $count;
$colorData[] = $color;
}
} else {
$count = $width - $superSampleRange->toXExcl;
if ($count > 0) {
$colorData[] = $count;
$colorData[] = $color;
}
}
} // /range
}
return $colorData;
}
private static function intersection_cmp($a, $b)
{
if ($a->x < $b->x) {
return -1;
}
if ($a->x > $b->x) {
return 1;
}
return 0;
}
/**
* Determines what edges that intersect a horizontal line with the specified
* y coordinate. For each intersecting edge the intersecting x coordinate is
* returned.
*
* @param array $edges Array of edges in the current scanline.
* @param int $y Y coordinate of the current scanline.
* @return array Array containing EdgeSuperSampleIntersection. Objects
* are sorted ascending by x coordinate.
*/
private static function getIntersections($edges, $y)
{
$intersections = array();
foreach ($edges as $edge) {
if ($edge->y0 < $y && $edge->y1 >= $y ||
$edge->y0 >= $y && $edge->y1 < $y
) {
$x = $edge->x0 +
($edge->x1 - $edge->x0) * ($y - $edge->y0) /
($edge->y1 - $edge->y0);
$intersections[] = new EdgeSuperSampleIntersection($x, $edge);
}
}
usort($intersections, array(
'Jdenticon\\Canvas\\Rasterization\\Rasterizer',
'intersection_cmp'));
return $intersections;
}
/**
* Determines what ranges of a scanline that needs to be supersampled.
*
* @param array $scanline Array of edges in the current scanline.
* @return array Array of SuperSampleRange.
*/
private static function getSuperSampleRanges(&$scanline, $width)
{
$superSampleRanges = array();
$rangeIndex = 0;
$scanlineCount = count($scanline);
while ($rangeIndex < $scanlineCount) {
$range = $scanline[$rangeIndex];
if ($range->fromX >= $width) {
break;
}
$superSampleRange = new SuperSampleRange(
$range->fromX,
$range->fromX + $range->width
);
$superSampleRange->edges[] = $range->edge;
$rangeIndex++;
for ($i = $rangeIndex; $i < $scanlineCount; $i++) {
$range = $scanline[$i];
if ($range->fromX < $superSampleRange->toXExcl) {
$superSampleRange->toXExcl = max(
$superSampleRange->toXExcl,
$range->fromX + $range->width);
$superSampleRange->edges[] = $range->edge;
$rangeIndex++;
} else {
break;
}
}
$superSampleRange->toXExcl = min($superSampleRange->toXExcl, $width);
$superSampleRange->width =
$superSampleRange->toXExcl - $superSampleRange->fromX;
$superSampleRanges[] = $superSampleRange;
}
return $superSampleRanges;
}
}

View File

@@ -0,0 +1,198 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas\Rasterization;
use Jdenticon\Canvas\ColorUtils;
class SuperSampleBuffer
{
const IDX_COUNT = 0;
const IDX_A = 1;
const IDX_R = 2;
const IDX_G = 3;
const IDX_B = 4;
/**
* Creates a color buffer keeping an average color out of several
* color samples per pixel.
*
* @param integer $width Width of the buffer in pixels.
* @param integer $samplesPerPixel Number of samples to keep per pixel.
*/
public function __construct($width, $samplesPerPixel)
{
$this->samples = array();
$this->samplesPerPixel = $samplesPerPixel;
$this->pixelOffset = 0;
$this->subPixelOffset = 0;
$this->width = $width;
$this->used = -1;
}
/**
* Rewinds the cursor to the beginning of the buffer.
*/
public function rewind()
{
$this->pixelOffset = 0;
$this->subPixelOffset = 0;
}
/**
* Clears the samples in this buffer.
*/
public function clear()
{
$this->pixelOffset = 0;
$this->subPixelOffset = 0;
$this->used = -1;
}
/**
* Writes the average color of each pixel to a specified color array.
*
* @param array $colorData The average colors will be written to this
* color array.
* @param integer $count Number of pixels to write.
*/
public function emptyTo(& $colorData, $count)
{
for ($i = 0; $i < $count; $i++) {
$sampleCount = $this->samples[$i * 5 + self::IDX_COUNT];
$a = $this->samples[$i * 5 + self::IDX_A];
$color = $sampleCount == 0 || $a == 0 ? 0 :
ColorUtils::from(
(int)($a / $sampleCount),
(int)($this->samples[$i * 5 + self::IDX_R] / $a),
(int)($this->samples[$i * 5 + self::IDX_G] / $a),
(int)($this->samples[$i * 5 + self::IDX_B] / $a)
);
$colorData[] = 1;
$colorData[] = $color;
}
$this->pixelOffset = 0;
$this->subPixelOffset = 0;
$this->used = -1;
}
/**
* Gets the average color of the pixel at a specified index.
*
* @param integer $index The index of the pixel.
* @return integer
*/
public function colorAt($index)
{
$sampleCount = $this->samples[$index * 5 + self::IDX_COUNT];
$alphaSum = $this->samples[$index * 5 + self::IDX_A];
return $sampleCount == 0 || $alphaSum == 0 ? 0 :
ColorUtils::from(
(int)($alphaSum / $sampleCount),
(int)($this->samples[$index * 5 + self::IDX_R] / $alphaSum),
(int)($this->samples[$index * 5 + self::IDX_G] / $alphaSum),
(int)($this->samples[$index * 5 + self::IDX_B] / $alphaSum)
);
}
/**
* Adds a color to the current pixel in the buffer.
*
* @param integer $count Number of samples of the color to be added to
* the buffer.
*/
private function _add($count, $a, $r, $g, $b)
{
if ($this->used < $this->pixelOffset) {
$this->used = $this->pixelOffset;
$this->samples[$this->pixelOffset * 5 + self::IDX_COUNT] = $count;
$this->samples[$this->pixelOffset * 5 + self::IDX_A] = $a * $count;
$this->samples[$this->pixelOffset * 5 + self::IDX_R] = $a * $r * $count;
$this->samples[$this->pixelOffset * 5 + self::IDX_G] = $a * $g * $count;
$this->samples[$this->pixelOffset * 5 + self::IDX_B] = $a * $b * $count;
} else {
$this->samples[$this->pixelOffset * 5 + self::IDX_COUNT] += $count;
if ($a > 0) {
$this->samples[$this->pixelOffset * 5 + self::IDX_A] += $a * $count;
$this->samples[$this->pixelOffset * 5 + self::IDX_R] += $a * $r * $count;
$this->samples[$this->pixelOffset * 5 + self::IDX_G] += $a * $g * $count;
$this->samples[$this->pixelOffset * 5 + self::IDX_B] += $a * $b * $count;
}
}
}
/**
* Adds a color to the buffer up until the specified x index.
*
* @param integer $color Color to write.
* @param float $untilX Samples of the color will be added the buffer until
* the cursor reaches this coordinate.
*/
public function add($color, $untilX)
{
$samplesLeft =
(int)($untilX * $this->samplesPerPixel) -
$this->subPixelOffset -
$this->pixelOffset * $this->samplesPerPixel;
// ColorUtils methods inlined for performance reasons
$a = ($color) & 0xff;
$r = ($color >> 24) & 0xff;
$g = ($color >> 16) & 0xff;
$b = ($color >> 8) & 0xff;
// First partial pixel
if ($this->subPixelOffset > 0) {
$samples = $this->samplesPerPixel - $this->subPixelOffset;
if ($samples > $samplesLeft) {
$samples = $samplesLeft;
}
$samplesLeft -= $samples;
$this->_add($samples, $a, $r, $g, $b);
$this->subPixelOffset += $samples;
if ($this->subPixelOffset == $this->samplesPerPixel) {
$this->subPixelOffset = 0;
$this->pixelOffset++;
}
}
// Full pixels
$fullPixels = (int)($samplesLeft / $this->samplesPerPixel);
if ($fullPixels > 0) {
for ($i = 0; $i < $fullPixels; $i++) {
$this->_add($this->samplesPerPixel, $a, $r, $g, $b);
$this->pixelOffset++;
}
$samplesLeft -= $fullPixels * $this->samplesPerPixel;
}
// Last partial pixel
if ($samplesLeft > 0) {
$this->_add($samplesLeft, $a, $r, $g, $b);
$this->subPixelOffset += $samplesLeft;
if ($this->subPixelOffset == $this->samplesPerPixel) {
$this->subPixelOffset = 0;
$this->pixelOffset++;
}
}
}
}

View File

@@ -0,0 +1,27 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas\Rasterization;
class SuperSampleRange
{
public $fromX;
public $toXExcl;
public $edges;
public $width;
public function __construct($fromX, $toXExcl)
{
$this->fromX = $fromX;
$this->toXExcl = $toXExcl;
$this->edges = array();
}
}

605
vendor/jdenticon/jdenticon/src/Color.php vendored Normal file
View File

@@ -0,0 +1,605 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon;
/**
* Represents a 24-bit color with a 8-bit alpha channel.
*/
class Color
{
private static $lightnessCompensations = array(
0.55, 0.5, 0.5, 0.46, 0.6, 0.55, 0.55);
/**
* The red component of the color in the range [0, 255].
* @var int
*/
public $r;
/**
* The green component of the color in the range [0, 255].
* @var int
*/
public $g;
/**
* The blue component of the color in the range [0, 255].
* @var int
*/
public $b;
/**
* The alpha component of the color in the range [0, 255].
* @var int
*/
public $a;
// Users of the struct should use the static factory methods
// to create Color value.
private function __construct()
{
}
/**
* Creates a Color from an RGB value.
*
* @param int $alpha Alpha channel value in the range [0, 255].
* @param int $red Red component in the range [0, 255].
* @param int $green GReen component in the range [0, 255].
* @param int $blue Blue component in the range [0, 255].
*/
public static function fromRgb($red, $green, $blue, $alpha = 255)
{
$color = new Color();
$color->r = $red;
$color->g = $green;
$color->b = $blue;
$color->a = $alpha;
return $color;
}
/**
* Creates a Color instance from HSL color parameters.
*
* @param float $hue Hue in the range [0, 1]
* @param float $saturation Saturation in the range [0, 1]
* @param float $lightness Lightness in the range [0, 1]
* @param float $alpha Alpha channel value in the range [0, 1].
*/
public static function fromHsl($hue, $saturation, $lightness, $alpha = 1.0)
{
if ($hue < 0) $hue = 0;
if ($hue > 1) $hue = 1;
if ($saturation < 0) $saturation = 0;
if ($saturation > 1) $saturation = 1;
if ($lightness < 0) $lightness = 0;
if ($lightness > 1) $lightness = 1;
if ($alpha < 0) $alpha = 0;
if ($alpha > 1) $alpha = 1;
// Based on http://www.w3.org/TR/2011/REC-css3-color-20110607/#hsl-color
if ($saturation == 0) {
$value = (int)($lightness * 255);
return self::fromRgb($value, $value, $value, (int)($alpha * 255));
} else {
if ($lightness <= 0.5) {
$m2 = $lightness * ($saturation + 1);
} else {
$m2 = $lightness + $saturation - $lightness * $saturation;
}
$m1 = $lightness * 2 - $m2;
return self::fromRgb(
self::hueToRgb($m1, $m2, $hue * 6 + 2),
self::hueToRgb($m1, $m2, $hue * 6),
self::hueToRgb($m1, $m2, $hue * 6 - 2),
(int)($alpha * 255));
}
}
/**
* Creates a Color> instance from HSL color parameters and will compensate
* the lightness for hues that appear to be darker than others.
*
* @param float $hue Hue in the range [0, 1].
* @param float $saturation Saturation in the range [0, 1].
* @param float $lightness Lightness in the range [0, 1].
* @param float $alpha Alpha channel value in the range [0, 1].
*/
public static function fromHslCompensated($hue, $saturation, $lightness, $alpha = 1.0)
{
if ($hue < 0) $hue = 0;
if ($hue > 1) $hue = 1;
$lightnessCompensation = self::$lightnessCompensations[(int)($hue * 6 + 0.5)];
// Adjust the input lightness relative to the compensation
$lightness = $lightness < 0.5 ?
$lightness * $lightnessCompensation * 2 :
$lightnessCompensation + ($lightness - 0.5) * (1 - $lightnessCompensation) * 2;
return self::fromHsl($hue, $saturation, $lightness, $alpha);
}
// Helper method for FromHsl
private static function hueToRgb($m1, $m2, $h)
{
if ($h < 0) {
$h = $h + 6;
} elseif ($h > 6) {
$h = $h - 6;
}
if ($h < 1) {
$r = $m1 + ($m2 - $m1) * $h;
} elseif ($h < 3) {
$r = $m2;
} elseif ($h < 4) {
$r = $m1 + ($m2 - $m1) * (4 - $h);
} else {
$r = $m1;
}
return (int)(255 * $r);
}
/**
* Gets the argb value of this color.
*
* @return int
*/
public function toRgba()
{
return
($this->r << 24) |
($this->g << 16) |
($this->b << 8) |
($this->a);
}
/**
* Gets a hexadecimal representation of this color on the format #rrggbbaa.
*
* @return string
*/
public function __toString()
{
return '#' . bin2hex(pack('N', $this->toRgba()));
}
/**
* Gets a hexadecimal representation of this color on the format #rrggbbaa.
*
* @return string
*/
public function toHexString($length = 8)
{
if ($length === 8) {
return $this->__toString();
}
return '#' . substr(bin2hex(pack('N', $this->toRgba())), 0, 6);
}
/**
* Tries to parse a value as a Color.
*
* @param mixed $value Value to parse.
* @throws InvalidArgumentException
* @return \Jdenticon\Color
*/
public static function parse($value) {
if ($value instanceof Color) {
return $value;
}
$value = strtolower("$value");
if (preg_match('/^#?[0-9a-f]{3,8}$/', $value) &&
self::parseHexColor($value, $result)
) {
return $result;
}
if (preg_match(
'/^rgba?\\(([^,]+),([^,]+),([^,]+)(?:,([^,]+))?\\)$/',
$value, $matches) &&
self::parseRgbComponent($matches[1], $r) &&
self::parseRgbComponent($matches[2], $g) &&
self::parseRgbComponent($matches[3], $b) &&
self::parseAlpha(isset($matches[4]) ? $matches[4] : null, $a)
) {
return self::fromRgb($r, $g, $b, (int)(255 * $a));
}
if (preg_match(
'/^hsla?\\(([^,]+),([^,]+),([^,]+)(?:,([^,]+))?\\)$/',
$value, $matches) &&
self::parseHue($matches[1], $h) &&
self::parsePercent($matches[2], $s) &&
self::parsePercent($matches[3], $l) &&
self::parseAlpha(isset($matches[4]) ? $matches[4] : null, $a)
) {
return self::fromHsl($h, $s, $l, $a);
}
$result = self::parseNamedColor($value);
if ($result !== null) {
return $result;
}
throw new \InvalidArgumentException(
"Cannot parse '$value' as a color.");
}
/**
* Parses a percent value.
*
* @param string $input Input string.
* @param float $result Resulting value in range [0, 1].
*
* @return boolean
*/
private static function parsePercent($input, &$result)
{
// Detect and remove percent sign
if (preg_match('/^\\s*(\\d*(?:\\.\\d*)?)%\\s*$/', $input, $matches)) {
$result = floatval($matches[1]) / 100;
if ($result < 0) $result = 0;
if ($result > 1) $result = 1;
return true;
}
return false;
}
/**
* Parses an alpha value.
*
* @param string $input Input string.
* @param float $result Resulting alpha in range [0, 1].
*
* @return boolean
*/
private static function parseAlpha($input, &$result)
{
if ($input === null ||
$input === ''
) {
$result = 1;
return true;
}
if (preg_match('/^\\s*(\\d*(?:\\.\\d*)?)(%?)\\s*$/', $input, $matches)) {
$result = floatval($matches[1]);
// Percentage
if ($matches[2] !== '') {
$result = $result / 100;
}
if ($result < 0) $result = 0;
if ($result > 1) $result = 1;
return true;
}
return false;
}
/**
* Parses an RGB component.
*
* @param string $input Input string.
* @param float $result Hue in range [0, 255].
*
* @return boolean
*/
private static function parseRgbComponent($input, &$result)
{
if (preg_match('/^\\s*(\\d*(?:\\.\\d*)?)(%?)\\s*$/', $input, $matches)) {
$result = floatval($matches[1]);
if ($matches[2] === '%') {
$result = 255 * $result / 100;
}
$result = (int)$result;
if ($result < 0) $result = 0;
if ($result > 255) $result = 255;
return true;
}
return false;
}
/**
* Parses a hue component.
*
* @param string $input Input string.
* @param float $result Hue in range [0, 1].
*
* @return boolean
*/
private static function parseHue($input, &$result)
{
if (preg_match(
'/^\s*(\d*(?:\.\d*)?)(deg|grad|rad|turn|)\s*$/',
$input, $matches)
) {
$result = floatval($matches[1]);
// Percentage
switch ($matches[2]) {
case "grad":
// Gradians: range 0 - 400
$result = $result / 400;
break;
case "rad":
// Radians: range 0 - 2pi
$result = $result / M_PI / 2;
break;
case "turn":
// Turns: range 0 - 1
$result = $result;
break;
default:
// Degree: range 0 - 360
$result = $result / 360;
break;
}
$result = fmod($result, 1);
if ($result < 0) {
$result += 1;
}
return true;
}
return false;
}
/**
* Parses a hex color string.
*
* @param string $input Input string.
* @param float $result Hue in range [0, 1].
*
* @return boolean
*/
private static function parseHexColor($input, &$result)
{
if ($input[0] === '#') {
$input = substr($input, 1);
}
// intval does not support unsigned 32-bit integers
// so we need to parse large numbers stepwise
$numeric24bit = intval(substr($input, 0, 6), 16);
$alpha8bit = intval(substr($input, 6, 2), 16);
switch (strlen($input)) {
case 3:
$result = self::fromRgb(
(($numeric24bit & 0xf00) >> 8) |
(($numeric24bit & 0xf00) >> 4),
(($numeric24bit & 0x0f0) >> 4) |
(($numeric24bit & 0x0f0)),
(($numeric24bit & 0x00f) << 4) |
(($numeric24bit & 0x00f))
);
return true;
case 4:
$result = self::fromRgb(
(($numeric24bit & 0xf000) >> 12) |
(($numeric24bit & 0xf000) >> 8),
(($numeric24bit & 0x0f00) >> 8) |
(($numeric24bit & 0x0f00) >> 4),
(($numeric24bit & 0x00f0) >> 4) |
(($numeric24bit & 0x00f0)),
(($numeric24bit & 0x000f) << 4) |
(($numeric24bit & 0x000f))
);
return true;
case 6:
$result = self::fromRgb(
0xff & ($numeric24bit >> 16),
0xff & ($numeric24bit >> 8),
0xff & ($numeric24bit)
);
return true;
case 8:
$result = self::fromRgb(
0xff & ($numeric24bit >> 16),
0xff & ($numeric24bit >> 8),
0xff & ($numeric24bit),
0xff & ($alpha8bit)
);
return true;
}
return false;
}
/**
* Looks up a named color to a Color instance.
*
* @param string $input Input string.
*
* @return \Jdenticon\Color
*/
private static function parseNamedColor($input)
{
// Source: https://www.w3.org/TR/css-color-4/#named-colors
switch ($input) {
case 'aliceblue': return self::fromRgb(240,248,255);
case 'antiquewhite': return self::fromRgb(250,235,215);
case 'aqua': return self::fromRgb(0,255,255);
case 'aquamarine': return self::fromRgb(127,255,212);
case 'azure': return self::fromRgb(240,255,255);
case 'beige': return self::fromRgb(245,245,220);
case 'bisque': return self::fromRgb(255,228,196);
case 'black': return self::fromRgb(0,0,0);
case 'blanchedalmond': return self::fromRgb(255,235,205);
case 'blue': return self::fromRgb(0,0,255);
case 'blueviolet': return self::fromRgb(138,43,226);
case 'brown': return self::fromRgb(165,42,42);
case 'burlywood': return self::fromRgb(222,184,135);
case 'cadetblue': return self::fromRgb(95,158,160);
case 'chartreuse': return self::fromRgb(127,255,0);
case 'chocolate': return self::fromRgb(210,105,30);
case 'coral': return self::fromRgb(255,127,80);
case 'cornflowerblue': return self::fromRgb(100,149,237);
case 'cornsilk': return self::fromRgb(255,248,220);
case 'crimson': return self::fromRgb(220,20,60);
case 'cyan': return self::fromRgb(0,255,255);
case 'darkblue': return self::fromRgb(0,0,139);
case 'darkcyan': return self::fromRgb(0,139,139);
case 'darkgoldenrod': return self::fromRgb(184,134,11);
case 'darkgray': return self::fromRgb(169,169,169);
case 'darkgreen': return self::fromRgb(0,100,0);
case 'darkgrey': return self::fromRgb(169,169,169);
case 'darkkhaki': return self::fromRgb(189,183,107);
case 'darkmagenta': return self::fromRgb(139,0,139);
case 'darkolivegreen': return self::fromRgb(85,107,47);
case 'darkorange': return self::fromRgb(255,140,0);
case 'darkorchid': return self::fromRgb(153,50,204);
case 'darkred': return self::fromRgb(139,0,0);
case 'darksalmon': return self::fromRgb(233,150,122);
case 'darkseagreen': return self::fromRgb(143,188,143);
case 'darkslateblue': return self::fromRgb(72,61,139);
case 'darkslategray': return self::fromRgb(47,79,79);
case 'darkslategrey': return self::fromRgb(47,79,79);
case 'darkturquoise': return self::fromRgb(0,206,209);
case 'darkviolet': return self::fromRgb(148,0,211);
case 'deeppink': return self::fromRgb(255,20,147);
case 'deepskyblue': return self::fromRgb(0,191,255);
case 'dimgray': return self::fromRgb(105,105,105);
case 'dimgrey': return self::fromRgb(105,105,105);
case 'dodgerblue': return self::fromRgb(30,144,255);
case 'firebrick': return self::fromRgb(178,34,34);
case 'floralwhite': return self::fromRgb(255,250,240);
case 'forestgreen': return self::fromRgb(34,139,34);
case 'fuchsia': return self::fromRgb(255,0,255);
case 'gainsboro': return self::fromRgb(220,220,220);
case 'ghostwhite': return self::fromRgb(248,248,255);
case 'gold': return self::fromRgb(255,215,0);
case 'goldenrod': return self::fromRgb(218,165,32);
case 'gray': return self::fromRgb(128,128,128);
case 'green': return self::fromRgb(0,128,0);
case 'greenyellow': return self::fromRgb(173,255,47);
case 'grey': return self::fromRgb(128,128,128);
case 'honeydew': return self::fromRgb(240,255,240);
case 'hotpink': return self::fromRgb(255,105,180);
case 'indianred': return self::fromRgb(205,92,92);
case 'indigo': return self::fromRgb(75,0,130);
case 'ivory': return self::fromRgb(255,255,240);
case 'khaki': return self::fromRgb(240,230,140);
case 'lavender': return self::fromRgb(230,230,250);
case 'lavenderblush': return self::fromRgb(255,240,245);
case 'lawngreen': return self::fromRgb(124,252,0);
case 'lemonchiffon': return self::fromRgb(255,250,205);
case 'lightblue': return self::fromRgb(173,216,230);
case 'lightcoral': return self::fromRgb(240,128,128);
case 'lightcyan': return self::fromRgb(224,255,255);
case 'lightgoldenrodyellow': return self::fromRgb(250,250,210);
case 'lightgray': return self::fromRgb(211,211,211);
case 'lightgreen': return self::fromRgb(144,238,144);
case 'lightgrey': return self::fromRgb(211,211,211);
case 'lightpink': return self::fromRgb(255,182,193);
case 'lightsalmon': return self::fromRgb(255,160,122);
case 'lightseagreen': return self::fromRgb(32,178,170);
case 'lightskyblue': return self::fromRgb(135,206,250);
case 'lightslategray': return self::fromRgb(119,136,153);
case 'lightslategrey': return self::fromRgb(119,136,153);
case 'lightsteelblue': return self::fromRgb(176,196,222);
case 'lightyellow': return self::fromRgb(255,255,224);
case 'lime': return self::fromRgb(0,255,0);
case 'limegreen': return self::fromRgb(50,205,50);
case 'linen': return self::fromRgb(250,240,230);
case 'magenta': return self::fromRgb(255,0,255);
case 'maroon': return self::fromRgb(128,0,0);
case 'mediumaquamarine': return self::fromRgb(102,205,170);
case 'mediumblue': return self::fromRgb(0,0,205);
case 'mediumorchid': return self::fromRgb(186,85,211);
case 'mediumpurple': return self::fromRgb(147,112,219);
case 'mediumseagreen': return self::fromRgb(60,179,113);
case 'mediumslateblue': return self::fromRgb(123,104,238);
case 'mediumspringgreen': return self::fromRgb(0,250,154);
case 'mediumturquoise': return self::fromRgb(72,209,204);
case 'mediumvioletred': return self::fromRgb(199,21,133);
case 'midnightblue': return self::fromRgb(25,25,112);
case 'mintcream': return self::fromRgb(245,255,250);
case 'mistyrose': return self::fromRgb(255,228,225);
case 'moccasin': return self::fromRgb(255,228,181);
case 'navajowhite': return self::fromRgb(255,222,173);
case 'navy': return self::fromRgb(0,0,128);
case 'oldlace': return self::fromRgb(253,245,230);
case 'olive': return self::fromRgb(128,128,0);
case 'olivedrab': return self::fromRgb(107,142,35);
case 'orange': return self::fromRgb(255,165,0);
case 'orangered': return self::fromRgb(255,69,0);
case 'orchid': return self::fromRgb(218,112,214);
case 'palegoldenrod': return self::fromRgb(238,232,170);
case 'palegreen': return self::fromRgb(152,251,152);
case 'paleturquoise': return self::fromRgb(175,238,238);
case 'palevioletred': return self::fromRgb(219,112,147);
case 'papayawhip': return self::fromRgb(255,239,213);
case 'peachpuff': return self::fromRgb(255,218,185);
case 'peru': return self::fromRgb(205,133,63);
case 'pink': return self::fromRgb(255,192,203);
case 'plum': return self::fromRgb(221,160,221);
case 'powderblue': return self::fromRgb(176,224,230);
case 'purple': return self::fromRgb(128,0,128);
case 'rebeccapurple': return self::fromRgb(102,51,153);
case 'red': return self::fromRgb(255,0,0);
case 'rosybrown': return self::fromRgb(188,143,143);
case 'royalblue': return self::fromRgb(65,105,225);
case 'saddlebrown': return self::fromRgb(139,69,19);
case 'salmon': return self::fromRgb(250,128,114);
case 'sandybrown': return self::fromRgb(244,164,96);
case 'seagreen': return self::fromRgb(46,139,87);
case 'seashell': return self::fromRgb(255,245,238);
case 'sienna': return self::fromRgb(160,82,45);
case 'silver': return self::fromRgb(192,192,192);
case 'skyblue': return self::fromRgb(135,206,235);
case 'slateblue': return self::fromRgb(106,90,205);
case 'slategray': return self::fromRgb(112,128,144);
case 'slategrey': return self::fromRgb(112,128,144);
case 'snow': return self::fromRgb(255,250,250);
case 'springgreen': return self::fromRgb(0,255,127);
case 'steelblue': return self::fromRgb(70,130,180);
case 'tan': return self::fromRgb(210,180,140);
case 'teal': return self::fromRgb(0,128,128);
case 'thistle': return self::fromRgb(216,191,216);
case 'tomato': return self::fromRgb(255,99,71);
case 'transparent': return self::fromRgb(0,0,0,0);
case 'turquoise': return self::fromRgb(64,224,208);
case 'violet': return self::fromRgb(238,130,238);
case 'wheat': return self::fromRgb(245,222,179);
case 'white': return self::fromRgb(255,255,255);
case 'whitesmoke': return self::fromRgb(245,245,245);
case 'yellow': return self::fromRgb(255,255,0);
case 'yellowgreen': return self::fromRgb(154,205,50);
default: return null;
}
}
}

View File

@@ -0,0 +1,492 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon;
use Jdenticon\IdenticonStyle;
use Jdenticon\Rendering\Rectangle;
use Jdenticon\Rendering\RendererInterface;
use Jdenticon\Rendering\IconGenerator;
use Jdenticon\Rendering\InternalPngRenderer;
use Jdenticon\Rendering\ImagickRenderer;
use Jdenticon\Rendering\SvgRenderer;
/**
* Represents an identicon and its style. This is the entry class to Jdenticon.
*/
class Identicon
{
/**
* @var mixed
*/
private $value;
/**
* @var boolean
*/
private $valueSet = false;
/**
* Defaults to hash of an empty string.
*
* @var string
*/
private $hash = 'da39a3ee5e6b4b0d3255bfef95601890afd80709';
/**
* @var integer
*/
private $size = 100;
/**
* @var Jdenticon\Rendering\IconGenerator
*/
private $iconGenerator;
/**
* @var Jdenticon\IdenticonStyle
*/
private $style;
/**
* @var bool
*/
private $enableImageMagick;
/**
* Creates an Identicon instance with the specified hash.
*
* @param string $hash A binary string containing the hash that will be used
* as base for this icon. The hash must contain at least 6 bytes.
* @param int|float|double $size The size of the icon in pixels (the icon
* is quadratic).
*/
public function __construct($options = null)
{
$this->iconGenerator = IconGenerator::getDefaultGenerator();
if ($options !== null) {
$this->setOptions($options);
}
if ($this->style === null) {
$this->style = new IdenticonStyle();
}
}
/**
* Creates an Identicon instance from a specified hash.
*
* @param string $hash A binary string containing the hash that will be used
* as base for this icon. The hash must contain at least 6 bytes.
* @param int $size The size of the icon in pixels (the icon is quadratic).
* @return \Jdenticon\Identicon
*/
public static function fromHash($hash, $size)
{
return new Identicon(array('hash' => $hash, 'size' => $size));
}
/**
* Creates an Identicon instance from a specified value.
*
* @param mixed $value The value that will be used as base for this icon.
* The value will be converted to a UTF8 encoded string and then hashed
* using SHA1.
* @param int $size The size of the icon in pixels (the icon is quadratic).
* @return \Jdenticon\Identicon
*/
public static function fromValue($value, $size)
{
return new Identicon(array('value' => $value, 'size' => $size));
}
/**
* Gets an associative array of all options of this identicon.
*
* @return array
*/
public function getOptions()
{
$options = array();
if ($this->valueSet) {
$options['value'] = $this->getValue();
} elseif ($this->hash !== null) {
$options['hash'] = $this->getHash();
}
$options['size'] = $this->getSize();
$options['style'] = $this->getStyle()->getOptions();
if ($this->enableImageMagick !== null) {
$options['enableImageMagick'] = $this->getEnableImageMagick();
}
if ($this->iconGenerator !== IconGenerator::getDefaultGenerator()) {
$options['iconGenerator'] = $this->getIconGenerator();
}
return $options;
}
/**
* Sets options in this identicon by specifying an associative array of
* option values.
*
* @param array $options Options to set.
* @return self
*/
public function setOptions(array $options)
{
foreach ($options as $key => $value) {
$this->__set($key, $value);
}
return $this;
}
public function __get($name)
{
switch (strtolower($name)) {
case 'size':
return $this->getSize();
case 'hash':
return $this->getHash();
case 'value':
return $this->getValue();
case 'style':
return $this->getStyle();
case 'icongenerator':
return $this->getIconGenerator();
case 'enableimagemagick':
return $this->getEnableImageMagick();
default:
throw new \InvalidArgumentException(
"Unknown Identicon option '$name'.");
}
}
public function __set($name, $value)
{
switch (strtolower($name)) {
case 'size':
$this->setSize($value);
break;
case 'hash':
$this->setHash($value);
break;
case 'value':
$this->setValue($value);
break;
case 'style':
$this->setStyle($value);
break;
case 'icongenerator':
$this->setIconGenerator($value);
break;
case 'enableimagemagick':
$this->setEnableImageMagick($value);
break;
default:
throw new \InvalidArgumentException(
"Unknown Identicon option '$name'.");
}
}
/**
* Gets the size of the icon in pixels.
*/
public function getSize()
{
return $this->size;
}
/**
* Sets the size of this icon in pixels.
*
* @param int|float|double $size The width and height of the icon.
*/
public function setSize($size)
{
if (!is_numeric($size) || $size < 1) {
throw new \InvalidArgumentException(
"An invalid identicon size was specified. ".
"A numeric value >= 1 was expected. Specified value: $size.");
}
$this->size = (int)$size;
}
/**
* Gets the size of the icon in pixels.
*/
public function getEnableImageMagick()
{
// Enable ImageMagick on PHP < 7. On PHP 7 the performance increase
// is not as obvious as on PHP 5. Since the ImageMagick renderer has a
// lot of quirks, we don't want to use it unless really needed.
if ($this->enableImageMagick === null) {
return PHP_MAJOR_VERSION < 7 && extension_loaded('imagick');
}
return $this->enableImageMagick;
}
/**
* Sets whether ImageMagick should be used to generate PNG icons.
*
* @param bool $enable true to enable ImageMagick.
*/
public function setEnableImageMagick($enable)
{
if (!is_bool($enable)) {
throw new \InvalidArgumentException(
"enableImageMagick can only assume boolean values. Specified value: $enable.");
}
// Verify that the Imagick extension is installed
if ($enable && !extension_loaded('imagick')) {
throw new \Exception(
'Failed to enable ImageMagick. '.
'The Imagick PHP extension was not found on this system.');
}
$this->enableImageMagick = $enable;
}
/**
* Gets the {@see IconGenerator} used to generate icons.
*
* @return \Jdenticon\Rendering\IconGenerator
*/
public function getIconGenerator()
{
return $this->iconGenerator;
}
/**
* Sets the {@see IconGenerator} used to generate icons.
*
* @param \Jdenticon\Rendering\IconGenerator $iconGenerator Icon generator
* that will render the shapes of the identicon.
* @return \Jdenticon\Identicon
*/
public function setIconGenerator(IconGenerator $iconGenerator)
{
if ($iconGenerator === null) {
$iconGenerator = IconGenerator::getDefaultGenerator();
}
$this->iconGenerator = $iconGenerator;
return $this;
}
/**
* Gets or sets the style of the icon.
*
* @return \Jdenticon\IdenticonStyle
*/
public function getStyle()
{
return $this->style;
}
/**
* Gets or sets the style of the icon.
*
* @param array|\Jdenticon\IdenticonStyle $style The new style of the icon.
* NULL will revert the identicon to use the default style.
* @return self
*/
public function setStyle($style)
{
if ($style == null) {
$this->style = new IdenticonStyle();
} elseif ($style instanceof IdenticonStyle) {
$this->style = $style;
} elseif (is_array($style)) {
$this->style = new IdenticonStyle($style);
} else {
throw new \InvalidArgumentException(
"Invalid indenticon style was specified. ".
"Allowed values are IdenticonStyle instances and associative ".
"arrays containing IdenticonStyle options.");
}
return $this;
}
/**
* Gets a binary string containing the hash that is used as base for this
* icon.
*/
public function getHash()
{
return $this->hash;
}
/**
* Sets a binary string containing the hash that is used as base for this
* icon. The string should contain at least 6 bytes.
*
* @param string $hash Binary string containing the hash.
*/
public function setHash($hash)
{
if (!is_string($hash)) {
throw new \InvalidArgumentException(
'An invalid $hash was passed to Identicon. ' .
'A binary string was expected.');
}
if (strlen($hash) < 6) {
throw new \InvalidArgumentException(
'An invalid $hash was passed to Identicon. ' .
'The hash was expected to contain at least 6 bytes.');
}
$this->hash = $hash;
$this->value = null;
$this->valueSet = false;
return $this;
}
/**
* Gets a binary string containing the hash that is used as base for this
* icon.
*/
public function getValue()
{
return $this->value;
}
/**
* Sets a value that will be used as base for this icon. The value will
* be converted to a string and then hashed using SHA1.
*
* @param mixed $value Value that will be hashed.
*/
public function setValue($value)
{
$this->hash = sha1("$value");
$this->value = $value;
$this->valueSet = true;
return $this;
}
/**
* Gets the bounds of the icon excluding its padding.
*
* @return \Jdenticon\Rendering\Rectangle
*/
public function getIconBounds()
{
// Round padding to nearest integer
$padding = (int)($this->style->getPadding() * $this->size + 0.5);
return new Rectangle(
$padding, $padding,
$this->size - $padding * 2,
$this->size - $padding * 2);
}
private function getRenderer($imageFormat)
{
switch (strtolower($imageFormat)) {
case 'svg':
return new SvgRenderer($this->size, $this->size);
default:
return $this->getEnableImageMagick() ?
new ImagickRenderer($this->size, $this->size) :
new InternalPngRenderer($this->size, $this->size);
}
}
/**
* Draws this icon using a specified renderer.
*
* This method is only intended for usage with custom renderers. A custom
* renderer could as an example render an Identicon in a file format not
* natively supported by Jdenticon. To implement a new file format,
* implement {@see \Jdenticon\Rendering\RendererInterface}.
*
* @param \Jdenticon\Rendering\RendererInterface $renderer The renderer used
* to render this icon.
* @param \Jdenticon\Rendering\Rectangle $rect The bounds of the rendered
* icon. No padding will be applied to the rectangle. If the parameter
* is omitted, the rectangle is calculated from the current icon
* size and padding.
*/
public function draw(
\Jdenticon\Rendering\RendererInterface $renderer,
\Jdenticon\Rendering\Rectangle $rect = null)
{
if ($rect === null) {
$rect = $this->getIconBounds();
}
$this->iconGenerator->generate(
$renderer, $rect, $this->style, $this->hash);
}
/**
* Renders the icon directly to the page output.
*
* The method will set the 'Content-Type' HTTP header. You are recommended
* to set an appropriate 'Cache-Control' header before calling this method
* to ensure the icon is cached client side.
*
* @param string $imageFormat The image format of the output.
* Supported values are 'png' and 'svg'.
*/
public function displayImage($imageFormat = 'png')
{
$renderer = $this->getRenderer($imageFormat);
$this->draw($renderer, $this->getIconBounds());
$mimeType = $renderer->getMimeType();
$data = $renderer->getData();
header("Content-Type: $mimeType");
echo $data;
}
/**
* Renders the icon to a binary string.
*
* @param string $imageFormat The image format of the output string.
* Supported values are 'png' and 'svg'.
* @return string
*/
public function getImageData($imageFormat = 'png')
{
$renderer = $this->getRenderer($imageFormat);
$this->draw($renderer, $this->getIconBounds());
return $renderer->getData();
}
/**
* Renders the icon as a data URI. It is recommended to avoid using this
* method unless really necessary, since it will effectively disable client
* caching of generated icons, and will also cause the same icon to be
* rendered multiple times, when used multiple times on a single page.
*
* @param string $imageFormat The image format of the data URI.
* Supported values are 'png' and 'svg'.
* @return string
*/
public function getImageDataUri($imageFormat = 'png')
{
$renderer = $this->getRenderer($imageFormat);
$this->draw($renderer, $this->getIconBounds());
$mimeType = $renderer->getMimeType();
$base64 = base64_encode($renderer->getData());
return "data:$mimeType;base64,$base64";
}
}

View File

@@ -0,0 +1,460 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon;
use Jdenticon\Color;
/**
* Specifies the color style of an identicon.
*/
class IdenticonStyle
{
/**
* @var \Jdenticon\Color
*/
private $backgroundColor;
/**
* @var float
*/
private $padding;
/**
* @var float
*/
private $colorSaturation;
/**
* @var float
*/
private $grayscaleSaturation;
/**
* @var array(float)
*/
private $colorLightness;
/**
* @var array(float)
*/
private $grayscaleLightness;
/**
* @var array(integer)
*/
private $hues;
public function __construct(array $options = null)
{
$this->backgroundColor = self::getDefaultBackgroundColor();
$this->padding = self::getDefaultPadding();
$this->colorSaturation = self::getDefaultColorSaturation();
$this->grayscaleSaturation = self::getDefaultGrayscaleSaturation();
$this->colorLightness = self::getDefaultColorLightness();
$this->grayscaleLightness = self::getDefaultGrayscaleLightness();
if ($options !== null) {
$this->setOptions($options);
}
}
/**
* Gets an associative array of all options of this style.
*
* @return array
*/
public function getOptions()
{
$options = array();
$options['backgroundColor'] = $this->getBackgroundColor()->__toString();
$options['padding'] = $this->getPadding();
$options['colorSaturation'] = $this->getColorSaturation();
$options['grayscaleSaturation'] = $this->getGrayscaleSaturation();
$options['colorLightness'] = $this->getColorLightness();
$options['grayscaleLightness'] = $this->getGrayscaleLightness();
if ($this->hues !== null) {
$options['hues'] = $this->getHues();
}
return $options;
}
/**
* Sets options in this style by specifying an associative array of option
* values.
*
* @param array $options Options to set.
* @return self
*/
public function setOptions(array $options)
{
foreach ($options as $key => $value) {
$this->__set($key, $value);
}
return $this;
}
public function __get($name)
{
switch (strtolower($name)) {
case 'backgroundcolor':
return $this->getBackgroundColor();
case 'padding':
return $this->getPadding();
case 'colorsaturation':
return $this->getColorSaturation();
case 'grayscalesaturation':
return $this->getGrayscaleSaturation();
case 'colorlightness':
return $this->getColorLightness();
case 'grayscalelightness':
return $this->getGrayscaleLightness();
case 'hues':
return $this->getHues();
default:
throw new \InvalidArgumentException(
"Unknown IdenticonStyle option '$name'.");
}
}
public function __set($name, $value)
{
switch (strtolower($name)) {
case 'backgroundcolor':
$this->setBackgroundColor($value);
break;
case 'padding':
$this->setPadding($value);
break;
case 'colorsaturation':
$this->setColorSaturation($value);
break;
case 'grayscalesaturation':
$this->setGrayscaleSaturation($value);
break;
case 'colorlightness':
$this->setColorLightness($value);
break;
case 'grayscalelightness':
$this->setGrayscaleLightness($value);
break;
case 'hues':
$this->setHues($value);
break;
default:
throw new \InvalidArgumentException(
"Unknown IdenticonStyle option '$name'.");
}
}
/**
* Normalizes a hue to the first turn [0, 360).
*
* @param mixed $hue
* @return integer
*/
private static function normalizeHue($hue)
{
if (!is_numeric($hue)) {
throw new \InvalidArgumentException(
"'$hue' is not a valid hue.");
}
$hue = $hue % 360;
if ($hue < 0) {
$hue += 360;
}
return $hue;
}
/**
* Gets an array of allowed hues, or null if there are no restrictions.
*
* @return array(int)|null
*/
public function getHues()
{
return $this->hues;
}
/**
* Sets the allowed hues of generated icons.
*
* @param array(integer)|integer|null $value A hue specified in degrees,
* or an array of hues specified in degrees. If set to null, the hue
* list is cleared.
* @return self
*/
public function setHues($value)
{
$hues = array();
if ($value !== null) {
if (is_array($value)) {
foreach ($value as $hue) {
$hues[] = self::normalizeHue($hue);
}
} else {
$hues[] = self::normalizeHue($value);
}
}
$this->hues = empty($hues) ? null : $hues;
return $this;
}
/**
* Gets the padding of an icon in percents in the range [0.0, 0.4].
*
* @return float
*/
public function getPadding()
{
return $this->padding;
}
/**
* Sets the padding of an icon in percents.
*
* @param float $value New padding in the range [0.0, 0.4].
* @return self
*/
public function setPadding($value)
{
if (!is_numeric($value) || $value < 0 || $value > 0.4) {
throw new \InvalidArgumentException(
"Padding '$value' out of range. ".
"Values in the range [0.0, 0.4] are allowed.");
}
$this->padding = (float)$value;
return $this;
}
/**
* Gets the color of the identicon background.
*
* @return \Jdenticon\Color
*/
public function getBackgroundColor()
{
return $this->backgroundColor;
}
/**
* Sets the color of the identicon background.
*
* @param \Jdenticon\Color|string $value New background color.
* @return \Jdenticon\IdenticonStyle
*/
public function setBackgroundColor($value)
{
if ($value instanceof Color) {
$this->backgroundColor = $value;
} else {
$this->backgroundColor = Color::parse($value);
}
return $this;
}
/**
* Gets the saturation of the originally grayscale identicon shapes.
*
* @return float Saturation in the range [0.0, 1.0].
*/
public function getGrayscaleSaturation()
{
return $this->grayscaleSaturation;
}
/**
* Sets the saturation of the originally grayscale identicon shapes.
*
* @param $value float Saturation in the range [0.0, 1.0].
* @return self
*/
public function setGrayscaleSaturation($value)
{
if (!is_numeric($value) ||
$value < 0 || $value > 1
) {
throw new \InvalidArgumentException(
"The grayscale saturation was invalid. ".
"Only values in the range [0.0, 1.0] are allowed.");
}
$this->grayscaleSaturation = (float)$value;
return $this;
}
/**
* Gets the saturation of the colored identicon shapes.
*
* @return float Saturation in the range [0.0, 1.0].
*/
public function getColorSaturation()
{
return $this->colorSaturation;
}
/**
* Sets the saturation of the colored identicon shapes.
*
* @param $value float Saturation in the range [0.0, 1.0].
* @return self
*/
public function setColorSaturation($value)
{
if (!is_numeric($value) ||
$value < 0 || $value > 1
) {
throw new \InvalidArgumentException(
"The color saturation was invalid. ".
"Only values in the range [0.0, 1.0] are allowed.");
}
$this->colorSaturation = (float)$value;
return $this;
}
/**
* Gets the value of the ColorLightness property.
*
* @return array(float, float)
*/
public function getColorLightness()
{
return $this->colorLightness;
}
/**
* Sets the value of the ColorLightness property.
*
* @param $value array(float, float) Lightness range.
* @return self
*/
public function setColorLightness($value)
{
if (!is_array($value) ||
!array_key_exists(0, $value) ||
!array_key_exists(1, $value) ||
!is_numeric($value[0]) ||
!is_numeric($value[1]) ||
$value[0] < 0 || $value[0] > 1 ||
$value[1] < 0 || $value[1] > 1
) {
throw new \InvalidArgumentException(
"The value passed to setColorLightness was invalid. ".
"Please check the documentation.");
}
$this->colorLightness = array((float)$value[0], (float)$value[1]);
return $this;
}
/**
* Gets the value of the GrayscaleLightness property.
*
* @return array(float, float)
*/
public function getGrayscaleLightness()
{
return $this->grayscaleLightness;
}
/**
* Sets the value of the GrayscaleLightness property.
*
* @param $value array(float, float) Lightness range.
* @return self
*/
public function setGrayscaleLightness($value)
{
if (!is_array($value) ||
!array_key_exists(0, $value) ||
!array_key_exists(1, $value) ||
!is_numeric($value[0]) ||
!is_numeric($value[1]) ||
$value[0] < 0 || $value[0] > 1 ||
$value[1] < 0 || $value[1] > 1
) {
throw new \InvalidArgumentException(
"The value passed to setGrayscaleLightness was invalid. ".
"Please check the documentation.");
}
$this->grayscaleLightness = array((float)$value[0], (float)$value[1]);
return $this;
}
/**
* Gets the default value of the BackgroundColor property. Resolves to transparent.
*
* @return \Jdenticon\Color
*/
public static function getDefaultBackgroundColor()
{
return Color::fromRgb(255, 255, 255, 255);
}
/**
* Gets the default value of the Padding property. Resolves to 0.08.
*
* @return float
*/
public static function getDefaultPadding()
{
return 0.08;
}
/**
* Gets the default value of the ColorSaturation property. Resolves to 0.5.
*
* @return float
*/
public static function getDefaultColorSaturation()
{
return 0.5;
}
/**
* Gets the default value of the GrayscaleSaturation property. Resolves to 0.
*
* @return float
*/
public static function getDefaultGrayscaleSaturation()
{
return 0;
}
/**
* Gets the default value of the ColorLightness property. Resolves to [0.4, 0.8].
*
* @return array
*/
public static function getDefaultColorLightness()
{
return array(0.4, 0.8);
}
/**
* Gets the default value of the GrayscaleLightness property. Resolves to [0.3, 0.9].
*
* @return array
*/
public static function getDefaultGrayscaleLightness()
{
return array(0.3, 0.9);
}
}

View File

@@ -0,0 +1,209 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Rendering;
/**
* Base class for rendering shapes in an identicon. Implement this class to e.g.
* support a new file format that is not natively supported by Jdenticon. To
* invoke the new Renderer, pass the renderer as an argument to the
* {@see \Jdenticon\Identicon::Draw} method.
*/
abstract class AbstractRenderer implements RendererInterface
{
private $transform;
protected $backgroundColor;
public function __construct()
{
$this->transform = Transform::getEmpty();
}
/**
* Sets the current transform that will be applied on all coordinates before
* being rendered to the target image.
*
* @param \Jdenticon\Rendering\Transform $transform The transform to set.
* If NULL is specified any existing transform is removed.
*/
public function setTransform(\Jdenticon\Rendering\Transform $transform)
{
$this->transform = $transform === null ?
Transform::getEmpty() : $transform;
}
/**
* Gets the current transform that will be applied on all coordinates before
* being rendered to the target image.
*
* @return \Jdenticon\Rendering\Transform
*/
public function getTransform()
{
return $this->transform;
}
/**
* Adds a polygon without translating its coordinates.
*
* @param array $points An array of the points that the polygon consists of.
*/
abstract protected function addPolygonNoTransform($points);
/**
* Adds a circle without translating its coordinates.
*
* @param float $x The x-coordinate of the bounding rectangle
* upper-left corner.
* @param float $y The y-coordinate of the bounding rectangle
* upper-left corner.
* @param float $size The size of the bounding rectangle.
* @param bool $counterClockwise If true the circle will be drawn
* counter clockwise.
*/
abstract protected function addCircleNoTransform($x, $y, $size, $counterClockwise);
/**
* Sets the background color of the image.
*
* @param \Jdenticon\Color $color The image background color.
*/
public function setBackgroundColor(\Jdenticon\Color $color)
{
$this->backgroundColor = $color;
}
/**
* Gets the background color of the image.
*
* @return \Jdenticon\Color
*/
public function getBackgroundColor()
{
return $this->backgroundColor;
}
private function addPolygonCore(array $points, $invert)
{
$transformedPoints = array();
foreach ($points as $point) {
$transformedPoints[] =
$this->transform->transformPoint($point->x, $point->y);
}
if ($invert) {
$transformedPoints = array_reverse($transformedPoints);
}
//var_dump($transformedPoints);
$this->addPolygonNoTransform($transformedPoints);
}
/**
* Adds a rectangle to the image.
*
* @param float $x The x-coordinate of the rectangle upper-left corner.
* @param float $y The y-coordinate of the rectangle upper-left corner.
* @param float $width The width of the rectangle.
* @param float $height The height of the rectangle.
* @param bool $invert If true the area of the rectangle will be removed
* from the filled area.
*/
public function addRectangle($x, $y, $width, $height, $invert = false)
{
$this->addPolygonCore(array(
new Point($x, $y),
new Point($x + $width, $y),
new Point($x + $width, $y + $height),
new Point($x, $y + $height),
), $invert);
}
/**
* Adds a circle to the image.
*
* @param float $x The x-coordinate of the bounding rectangle
* upper-left corner.
* @param float $y The y-coordinate of the bounding rectangle
* upper-left corner.
* @param float $size The size of the bounding rectangle.
* @param bool $invert If true the area of the circle will be removed
* from the filled area.
*/
public function addCircle($x, $y, $size, $invert = false)
{
$northWest = $this->transform->transformPoint($x, $y, $size, $size);
$this->addCircleNoTransform($northWest->x, $northWest->y, $size, $invert);
}
/**
* Adds a polygon to the image.
*
* @param array $points Array of points that the polygon consists of.
* @param bool $invert If true the area of the polygon will be removed
* from the filled area.
*/
public function addPolygon($points, $invert = false)
{
$this->addPolygonCore($points, $invert);
}
/**
* Adds a triangle to the image.
*
* @param float $x The x-coordinate of the bounding rectangle
* upper-left corner.
* @param float $y The y-coordinate of the bounding rectangle
* upper-left corner.
* @param float $width The width of the bounding rectangle.
* @param float $height The height of the bounding rectangle.
* @param float $direction The direction of the 90 degree corner of the
* triangle.
* @param bool $invert If true the area of the triangle will be removed
* from the filled area.
*/
public function addTriangle($x, $y, $width, $height, $direction, $invert = false)
{
$points = array(
new Point($x + $width, $y),
new Point($x + $width, $y + $height),
new Point($x, $y + $height),
new Point($x, $y)
);
array_splice($points, $direction, 1);
$this->addPolygonCore($points, $invert);
}
/**
* Adds a rhombus to the image.
*
* @param float $x The x-coordinate of the bounding rectangle
* upper-left corner.
* @param float $y The y-coordinate of the bounding rectangle
* upper-left corner.
* @param float $width The width of the bounding rectangle.
* @param float $height The height of the bounding rectangle.
* @param bool $invert If true the area of the rhombus will be removed
* from the filled area.
*/
public function addRhombus($x, $y, $width, $height, $invert = false)
{
$this->addPolygonCore(array(
new Point($x + $width / 2, $y),
new Point($x + $width, $y + $height / 2),
new Point($x + $width / 2, $y + $height),
new Point($x, $y + $height / 2),
), $invert);
}
}

View File

@@ -0,0 +1,84 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Rendering;
use Jdenticon\Color;
/**
* Specifies the colors to be used in an identicon.
*/
class ColorTheme
{
private $darkGray;
private $midColor;
private $lightGray;
private $lightColor;
private $darkColor;
/**
* Creates a new ColorTheme.
*
* @param float $hue The hue of the colored shapes in the range [0, 1].
* @param \Jdenticon\IdenticonStyle $style The style that specifies the
* lightness and saturation of the icon.
*/
public function __construct($hue, \Jdenticon\IdenticonStyle $style)
{
$grayscaleLightness = $style->getGrayscaleLightness();
$colorLightness = $style->getColorLightness();
$hues = $style->getHues();
if ($hues !== null) {
// $hue is in the range [0, 1]
// Multiply with 0.999 to change the range to [0, 1)
$hueIndex = (int)($hue * 0.999 * count($hues));
$hue = (float)$hues[$hueIndex] / 360;
}
$this->darkGray = Color::fromHslCompensated(
$hue, $style->getGrayscaleSaturation(), $grayscaleLightness[0]);
$this->midColor = Color::fromHslCompensated(
$hue, $style->getColorSaturation(), ($colorLightness[0] + $colorLightness[1]) / 2);
$this->lightGray = Color::fromHslCompensated(
$hue, $style->getGrayscaleSaturation(), $grayscaleLightness[1]);
$this->lightColor = Color::fromHslCompensated(
$hue, $style->getColorSaturation(), $colorLightness[1]);
$this->darkColor = Color::fromHslCompensated(
$hue, $style->getColorSaturation(), $colorLightness[0]);
}
/**
* Gets a color from this color theme by index.
*
* @param int $index Color index in the range [0, getCount()).
* @return Jdenticon\Color
*/
public function getByIndex($index)
{
if ($index === 0) return $this->darkGray;
if ($index === 1) return $this->midColor;
if ($index === 2) return $this->lightGray;
if ($index === 3) return $this->lightColor;
if ($index === 4) return $this->darkColor;
return null;
}
/**
* Gets the number of available colors in this theme.
*
* @return int
*/
public function getCount()
{
return 5;
}
}

View File

@@ -0,0 +1,290 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Rendering;
use Jdenticon\Shapes\Shape;
use Jdenticon\Shapes\ShapeCategory;
use Jdenticon\Shapes\ShapeDefinitions;
/**
* Generates identicons and render them to a
* {@link \Jdenticon\Rendering\RendererInterface}. This class dictates what
* shapes will be used in the generated icons. If you intend to customize the
* appearance of generated icons you probably wants to either subclass or modify
* this class.
*/
class IconGenerator
{
private $defaultShapes;
private static $instance;
protected function __construct()
{
$this->defaultShapes = array(
// Sides
new ShapeCategory(
/*$colorIndex=*/ 8,
/*$shapes=*/ ShapeDefinitions::getOuterShapes(),
/*$shapeIndex=*/ 2,
/*$rotationIndex=*/ 3,
/*$positions=*/ array(1,0, 2,0, 2,3, 1,3, 0,1, 3,1, 3,2, 0,2)
),
// Corners
new ShapeCategory(
/*$colorIndex=*/ 9,
/*$shapes=*/ ShapeDefinitions::getOuterShapes(),
/*$shapeIndex=*/ 4,
/*$rotationIndex=*/ 5,
/*$positions=*/ array(0,0, 3,0, 3,3, 0,3)
),
// Center
new ShapeCategory(
/*$colorIndex=*/ 10,
/*$shapes=*/ ShapeDefinitions::getCenterShapes(),
/*$shapeIndex=*/ 1,
/*$rotationIndex=*/ null,
/*$positions=*/ array(1,1, 2,1, 2,2, 1,2)
)
);
}
public static function getDefaultGenerator()
{
if (self::$instance === null) {
self::$instance = new IconGenerator();
}
return self::$instance;
}
/**
* Gets the number of cells in each direction of the icons generated by
* this IconGenerator.
*
* @return int
*/
public function getCellCount()
{
return 4;
}
/**
* Determines the hue to be used in an icon for the specified hash.
*
* @return float Hue in the range [0, 1].
*/
protected static function getHue($hash)
{
$value = hexdec(substr($hash, -7));
return $value / 0xfffffff;
}
/**
* Determines whether $newValue is duplicated in $source if all values
* in $duplicateValues are determined to be equal.
*
* @return bool
*/
private static function isDuplicate(
array $source, $newValue,
array $duplicateValues)
{
if (in_array($newValue, $duplicateValues, true)) {
foreach ($duplicateValues as $value) {
if (in_array($value, $source, true)) {
return true;
}
}
}
return false;
}
/**
* Gets the specified octet from a byte array.
*
* @param string $hash The hexstring from which the octet will be retrieved.
* @param int $index The zero-based index of the octet to be returned.
* @return int
*/
protected static function getOctet($hash, $index)
{
return hexdec($hash[$index]);
}
/**
* Gets an array of the shape categories to be rendered in icons generated
* by this IconGenerator.
*
* @return array
*/
protected function getCategories()
{
return $this->defaultShapes;
}
/**
* Gets an enumeration of individual shapes to be rendered in an icon for a
* specific hash.
*
* @param \Jdenticon\Rendering\ColorTheme $colorTheme A color theme
* specifying the colors to be used in the icon.
* @param string $hash The hash for which the shapes will be returned.
* @return array(Jdenticon\Shapes\Shape)
*/
protected function getShapes($colorTheme, $hash)
{
$usedColorThemeIndexes = array();
$categories = self::getCategories();
$shapes = array();
$colorCount = $colorTheme->getCount();
foreach ($categories as $category) {
$colorThemeIndex =
self::getOctet($hash, $category->colorIndex) % $colorCount;
if (self::isDuplicate(
// Disallow dark gray and dark color combo
$usedColorThemeIndexes, $colorThemeIndex, array(0, 4)) ||
self::isDuplicate(
// Disallow light gray and light color combo
$usedColorThemeIndexes, $colorThemeIndex, array(2, 3))
) {
$colorThemeIndex = 1;
}
$usedColorThemeIndexes[] = $colorThemeIndex;
$startRotationIndex = $category->rotationIndex === null ?
0 : self::getOctet($hash, $category->rotationIndex);
$shapeIndex =
self::getOctet($hash, $category->shapeIndex) %
count($category->shapes);
$shape = $category->shapes[$shapeIndex];
$shapes[] = new Shape(
/*$definition=*/ $shape,
/*$color=*/ $colorTheme->getByIndex($colorThemeIndex),
/*$positions=*/ $category->positions,
/*$startRotationIndex=*/ $startRotationIndex
);
}
return $shapes;
}
/**
* Creates a quadratic copy of the specified
* {@link \Jdenticon\Rendering\Rectangle} with a multiple of the cell count
* as size.
*
* @param \Jdenticon\Rendering\Rectangle $rect The rectangle to be
* normalized.
*/
protected function normalizeRectangle(\Jdenticon\Rendering\Rectangle $rect)
{
$size = (int)min($rect->width, $rect->height);
// Make size a multiple of the cell count
$size -= $size % $this->getCellCount();
return new Rectangle(
(int)($rect->x + ($rect->width - $size) / 2),
(int)($rect->y + ($rect->height - $size) / 2),
$size,
$size);
}
/**
* Renders the background of an icon.
*
* @param \Jdenticon\Rendering\RendererInterface $renderer The renderer to
* be used for rendering the icon on the target surface.
* @param \Jdenticon\Rendering\Rectangle $rect The outer bounds of the icon.
* @param \Jdenticon\IdenticonStyle $style The style of the icon.
* @param \Jdenticon\Rendering\ColorTheme $colorTheme A color theme
* specifying the colors to be used in the icon.
* @param string $hash The hash to be used as basis for the generated icon.
*/
protected function renderBackground(
\Jdenticon\Rendering\RendererInterface $renderer,
\Jdenticon\Rendering\Rectangle $rect,
\Jdenticon\IdenticonStyle $style,
\Jdenticon\Rendering\ColorTheme $colorTheme,
$hash)
{
$renderer->setBackgroundColor($style->getBackgroundColor());
}
/**
* Renders the foreground of an icon.
*
* @param \Jdenticon\Rendering\RendererInterface $renderer The renderer to
* be used for rendering the icon on the target surface.
* @param \Jdenticon\Rendering\Rectangle $rect The outer bounds of the icon.
* @param \Jdenticon\IdenticonStyle $style The style of the icon.
* @param \Jdenticon\Rendering\ColorTheme $colorTheme A color theme
* specifying the colors to be used in the icon.
* @param string $hash The hash to be used as basis for the generated icon.
*/
protected function renderForeground(
\Jdenticon\Rendering\RendererInterface $renderer,
\Jdenticon\Rendering\Rectangle $rect,
\Jdenticon\IdenticonStyle $style,
\Jdenticon\Rendering\ColorTheme $colorTheme,
$hash)
{
// Ensure rect is quadratic and a multiple of the cell count
$normalizedRect = $this->normalizeRectangle($rect);
$cellSize = $normalizedRect->width / $this->getCellCount();
foreach ($this->getShapes($colorTheme, $hash) as $shape) {
$rotation = $shape->startRotationIndex;
$renderer->beginShape($shape->color);
$positionCount = count($shape->positions);
for ($i = 0; $i + 1 < $positionCount; $i += 2) {
$renderer->setTransform(new Transform(
$normalizedRect->x + $shape->positions[$i + 0] * $cellSize,
$normalizedRect->y + $shape->positions[$i + 1] * $cellSize,
$cellSize, $rotation++ % 4));
$shape->definition->__invoke($renderer, $cellSize, $i / 2);
}
$renderer->endShape();
}
}
/**
* Generates an identicon for the specified hash.
*
* @param \Jdenticon\Rendering\RendererInterface $renderer The renderer to
* be used for rendering the icon on the target surface.
* @param \Jdenticon\Rendering\Rectangle $rect The outer bounds of the icon.
* @param \Jdenticon\IdenticonStyle $style The style of the icon.
* @param string $hash The hash to be used as basis for the generated icon.
*/
public function generate(
\Jdenticon\Rendering\RendererInterface $renderer,
\Jdenticon\Rendering\Rectangle $rect,
\Jdenticon\IdenticonStyle $style,
$hash)
{
$hue = self::getHue($hash);
$colorTheme = new ColorTheme($hue, $style);
$this->renderBackground($renderer, $rect, $style, $colorTheme, $hash);
$this->renderForeground($renderer, $rect, $style, $colorTheme, $hash);
}
}

View File

@@ -0,0 +1,296 @@
<?php
namespace Jdenticon\Rendering;
class ImagickRendererLine
{
/**
* Creates a new line from a vector.
*
* @param float $x0 Vector start x coordinate.
* @param float $y0 Vector start y coordinate.
* @param float $x1 Vector end x coordinate.
* @param float $y1 Vector end y coordinate.
*/
public static function fromVector($x0, $y0, $x1, $y1)
{
$line = new ImagickRendererLine();
$line->Px = $x0;
$line->Py = $y0;
$line->rx = $x1 - $x0;
$line->ry = $y1 - $y0;
return $line;
}
/**
* Moves the line to the right relative the direction vector.
*
* @param float $distance The number of pixels to move the line.
*/
public function moveRight($distance)
{
// Ortogonal direction vector
$rx = -$this->ry;
$ry = $this->rx;
$multiplier = $distance / sqrt($rx * $rx + $ry * $ry);
$this->Px += $rx * $multiplier;
$this->Py += $ry * $multiplier;
}
/**
* Computes the point at which two lines intersect.
*
* @return Point|null
*/
public static function intersection(
ImagickRendererLine $l1,
ImagickRendererLine $l2
) {
$rs = $l1->rx * $l2->ry - $l1->ry * $l2->rx;
if ($rs == 0) {
return null;
}
$u = (($l2->Px - $l1->Px) * $l1->ry - ($l2->Py - $l1->Py) * $l1->rx) / $rs;
return new Point(
$l2->Px + $u * $l2->rx,
$l2->Py + $u * $l2->ry
);
}
/**
* X coordiate of a point on the line.
* @var float
*/
public $Px;
/**
* Y coordiate of a point on the line.
* @var float
*/
public $Py;
/**
* X component of the direction vector.
* @var float
*/
public $rx;
/**
* Y component of the direction vector.
* @var float
*/
public $ry;
}
/**
* Renders icons as PNG using ImageMagick.
*
* Unfortunately the ImageMagick vector renderer has a lot of quirks that
* we need to accomodate. The most obvious is that the renderer always render
* all polygons 1/2 pixel too large. This behavior is documented here:
* http://www.imagemagick.org/Usage/draw/#bounds
*
* To prevent this we shrink all polygons with 1/2 pixels before passing them
* to ImageMagick.
*
* Another quirk is that if the polygon including the 1/2 pixel invisible border
* align perfectly to the pixel grid, white pixels will appear near edge
* intersections. Paths containing arcs will sometimes appear with horizontal
* lines drawn to the right side of the image.
*
* To prevent this (in most cases) we add 0.00013 to all coordinates.
*
*/
class ImagickRenderer extends AbstractRenderer
{
private $draw;
private $polygon;
private $width;
private $height;
/**
* This constant is added to all coordinates to avoid white pixels
* that sometimes appear near edge intersections when a polygon including
* its 1/2 invisible border is perfectly aligned to the pixel grid.
*/
const PREVENT_WHITE_PIXELS_OFFSET = -0.00013;
/**
* Creates an instance of the class ImagickRenderer.
*
* @param int $width The width of the icon in pixels.
* @param int $height The height of the icon in pixels.
*/
public function __construct($width, $height)
{
parent::__construct();
$this->draw = new \ImagickDraw();
$this->draw->setStrokeWidth(1);
$this->width = $width;
$this->height = $height;
}
/**
* Gets the MIME type of the renderer output.
*
* @return string
*/
public function getMimeType()
{
return 'image/png';
}
/**
* Adds a circle without translating its coordinates.
*
* @param float $x The x-coordinate of the bounding rectangle
* upper-left corner.
* @param float $y The y-coordinate of the bounding rectangle
* upper-left corner.
* @param float $size The size of the bounding rectangle.
* @param bool $counterClockwise If true the circle will be drawn
* counter clockwise.
*/
protected function addCircleNoTransform($x, $y, $size, $counterClockwise)
{
if ($counterClockwise) {
$x -= $size + 0.5;
$y -= 1;
} else {
$size -= 1;
$y -= 0.5;
}
$radius = $size / 2;
$this->draw->pathMoveToAbsolute(
$x + $size + self::PREVENT_WHITE_PIXELS_OFFSET,
$y + $radius + self::PREVENT_WHITE_PIXELS_OFFSET);
$this->draw->pathEllipticArcRelative($radius, $radius,
M_PI * 2, true, $counterClockwise, 0, 1);
$this->draw->pathClose();
}
/**
* Adds a polygon without translating its coordinates.
*
* @param array $points An array of the points that the polygon consists of.
*/
protected function addPolygonNoTransform($points)
{
$firstPoint = $points[0];
$lastPoint = end($points);
// Ensure polygon is closed
if ($firstPoint->x != $lastPoint->x ||
$firstPoint->y != $lastPoint->y
) {
$points[] = $firstPoint;
}
// Determine if polygon is an outer ring
// (source: https://stackoverflow.com/a/1165943)
$sum = 0;
$previousPoint = null;
foreach ($points as $point) {
if ($previousPoint !== null) {
$sum += ($point->x - $previousPoint->x) * ($point->y + $previousPoint->y);
}
$previousPoint = $point;
}
$isOuterRing = $sum < 0;
// ImageMagick draws all polygons 1 pixel too large. To prevent this,
// shrink polygons by 1 pixel.
$lines = array();
$previousPoint = null;
// Transform all edges to lines.
foreach ($points as $point) {
if ($previousPoint !== null) {
$lines[] = $line = ImagickRendererLine::fromVector(
$previousPoint->x, $previousPoint->y,
$point->x, $point->y
);
// ImageMagick draws a 1px border along the outer ring. To
// prevent the border overlaps inner rings too close to the
// outer ring, only inflate inner rings by 1/4 pixel.
$line->moveRight($isOuterRing ? 0.5 : 0.25);
}
$previousPoint = $point;
}
$first = true;
$previousLine = end($lines);
// Reconstruct point array from line intersections and draw the polygon
foreach ($lines as $line) {
$points[] = $point = ImagickRendererLine::intersection($previousLine, $line);
// Subtract 1/2 pixel to align the shapes to the pixel grid.
if ($first) {
$this->draw->pathMoveToAbsolute(
$point->x - 0.5 + self::PREVENT_WHITE_PIXELS_OFFSET,
$point->y - 0.5 + self::PREVENT_WHITE_PIXELS_OFFSET);
$first = false;
} else {
$this->draw->pathLineToAbsolute(
$point->x - 0.5 + self::PREVENT_WHITE_PIXELS_OFFSET,
$point->y - 0.5 + self::PREVENT_WHITE_PIXELS_OFFSET);
}
$previousLine = $line;
}
$this->draw->pathClose();
}
/**
* Begins a new shape. The shape should be ended with a call to endShape.
*
* @param \Jdenticon\Color $color The color of the shape.
*/
public function beginShape(\Jdenticon\Color $color)
{
$this->draw->setFillColor($color->__toString());
$this->draw->pathStart();
}
/**
* Ends the currently drawn shape.
*/
public function endShape()
{
$this->draw->pathFinish();
}
/**
* Renders this image as a PNG data stream.
*
* @return string
*/
public function getData()
{
$imagick = new \Imagick();
$imagick->newImage($this->width, $this->height, $this->backgroundColor->__toString());
$imagick->setImageFormat('png');
if (method_exists($imagick, 'setImageProperty')) {
$imagick->setImageProperty('Software', 'Jdenticon');
} else {
$imagick->setImageAttribute('Software', 'Jdenticon');
}
$imagick->drawImage($this->draw);
return $imagick->getImageBlob();
}
}

Some files were not shown because too many files have changed in this diff Show More