164 Commits

Author SHA1 Message Date
El RIDO
2fa58b9198 Merge pull request #1015 from PrivateBin/thai
enable and credit Thai translation
2022-11-07 07:33:53 +01:00
El RIDO
e970fbce8d Merge pull request #1013 from PrivateBin/crowdin-translation
New Crowdin updates
2022-11-07 07:13:29 +01:00
El RIDO
89df4a54ec enable and credit Thai translation 2022-11-07 07:12:40 +01:00
PrivateBin Translator Bot
b0c61cc208 New translations en.json (Thai) 2022-11-07 00:58:25 +01:00
PrivateBin Translator Bot
d4d4687464 New translations en.json (Thai) 2022-11-07 00:01:51 +01:00
PrivateBin Translator Bot
9f1e95f588 New translations en.json (Thai) 2022-11-06 03:21:05 +01:00
PrivateBin Translator Bot
3e3d93c9c2 New translations en.json (Thai) 2022-11-06 02:18:55 +01:00
PrivateBin Translator Bot
d46398fc22 New translations en.json (Thai) 2022-11-05 23:30:32 +01:00
PrivateBin Translator Bot
15ce736bc8 New translations en.json (Thai) 2022-11-05 20:25:29 +01:00
El RIDO
05f77e45bc Merge branch 'felixjogris-migrate' 2022-11-05 08:29:40 +01:00
El RIDO
a33721e3ab Merge branch 'migrate' of https://github.com/felixjogris/PrivateBin into felixjogris-migrate 2022-11-05 08:27:40 +01:00
Felix J. Ogris
10013ad092 syntax bot 2022-11-04 21:27:27 +01:00
Felix J. Ogris
75d28ef423 _sanitizeClob touches no instance variables 2022-11-04 21:25:53 +01:00
Felix J. Ogris
604c931875 remove cache from database backend 2022-11-04 21:19:47 +01:00
Felix J. Ogris
3d485ecd7f let GCS backends talk to the same "storage account" during testing 2022-11-04 21:04:18 +01:00
Felix J. Ogris
726f54ce9e typos 2022-11-04 20:19:41 +01:00
El RIDO
66600e5eb3 Merge pull request #1003 from PrivateBin/yourls-cleanup
improve configuration wording, adjust self check
2022-11-03 19:54:56 +01:00
El RIDO
987ead2719 ensure the basepath ends in a slash, if one is set 2022-11-03 07:47:50 +01:00
Felix J. Ogris
bde5802a3a syntax fix, changelog 2022-11-01 16:38:06 +01:00
Felix J. Ogris
9a61e8fd48 started script for storage backend migrations
todo: GCS

added GCS, no GLOBALS, two methods for saving pastes and comments

use GLOBALS for verbosity again

added getAllPastes() to all storage providers

moved to bin, added --delete options, make use of $store->getAllPastes()

added --delete-* options to help

longopts without -- *sigh*

fixed arguments

drop singleton behaviour to allow multiple backends of the same type simultaneously

remove singleton from Model, collapse loop in migrate.php

comments is not indexed

tests without data singleton

fix

exit if scandir() fails

extended meta doc
2022-11-01 16:02:17 +01:00
El RIDO
449dbb8377 Merge pull request #1010 from PrivateBin/dependabot/composer/phpunit/phpunit-5.7.27
Bump phpunit/phpunit from 5.6.3 to 5.7.27
2022-10-31 17:18:50 +01:00
El RIDO
606d70863e Merge pull request #1009 from PrivateBin/dependabot/composer/jdenticon/jdenticon-1.0.2
Bump jdenticon/jdenticon from 1.0.1 to 1.0.2
2022-10-31 17:07:00 +01:00
dependabot[bot]
b63de431a7 Bump phpunit/phpunit from 5.6.3 to 5.7.27
Bumps [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) from 5.6.3 to 5.7.27.
- [Release notes](https://github.com/sebastianbergmann/phpunit/releases)
- [Changelog](https://github.com/sebastianbergmann/phpunit/blob/5.7.27/ChangeLog-5.7.md)
- [Commits](https://github.com/sebastianbergmann/phpunit/compare/5.6.3...5.7.27)

---
updated-dependencies:
- dependency-name: phpunit/phpunit
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-31 11:33:10 +00:00
dependabot[bot]
be1989b117 Bump jdenticon/jdenticon from 1.0.1 to 1.0.2
Bumps [jdenticon/jdenticon](https://github.com/dmester/jdenticon-php) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/dmester/jdenticon-php/releases)
- [Commits](https://github.com/dmester/jdenticon-php/compare/1.0.1...1.0.2)

---
updated-dependencies:
- dependency-name: jdenticon/jdenticon
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-31 11:32:59 +00: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
432d3e71d3 improve configuration wording, adjust self check 2022-10-29 07:58:40 +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
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
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
122 changed files with 9253 additions and 908 deletions

View File

@@ -65,14 +65,13 @@ 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
@@ -104,7 +103,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: '12'
node-version: '16'
cache: 'npm'
cache-dependency-path: 'js/package-lock.json'
@@ -116,6 +115,6 @@ jobs:
working-directory: js
- name: Run unit tests
run: mocha
run: npm test
working-directory: js

View File

@@ -1,10 +1,15 @@
# PrivateBin version history
* **1.4.1 (not yet released)**
* ADDED: Translations for Turkish
* ADDED: script for data storage backend migrations (#1012)
* ADDED: Translations for Turkish, Slovak, Greek and Thai
* ADDED: S3 Storage backend (#994)
* CHANGED: Switched to Jdenticons as the default 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,9 @@
* 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
* Felix J. Ogris - script for data backend migrations, dropped singleton behaviour of data backends
## Translations
* Hexalyse - French
@@ -57,3 +60,6 @@
* Patriccollu di Santa Maria è Sichè - Corsican
* Markus Mikkonen - Finnish
* Emir Ensar Rahmanlar - Turkish
* Stevo984 - Slovak
* Christos Karamolegkos - Greek
* jaideejung007 - Thai

View File

@@ -38,10 +38,10 @@ install and configure PrivateBin on your server. It's available on
### Changing the Path
In the index.php you can define a different `PATH`. This is useful to secure
your installation. You can move the configuration, data files, templates and PHP
libraries (directories cfg, doc, data, lib, tpl, tst and vendor) outside of your
document root. This new location must still be accessible to your webserver and
PHP process (see also
your installation. You can move the utilities, configuration, data files,
templates and PHP libraries (directories bin, cfg, doc, data, lib, tpl, tst and
vendor) outside of your document root. This new location must still be
accessible to your webserver and PHP process (see also
[open_basedir setting](https://secure.php.net/manual/en/ini.core.php#ini.open-basedir)).
> #### PATH Example
@@ -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"
```

194
bin/migrate.php Normal file
View File

@@ -0,0 +1,194 @@
<?php
// change this, if your php files and data is outside of your webservers document root
define('PATH', '..' . DIRECTORY_SEPARATOR);
define('PUBLIC_PATH', __DIR__ . DIRECTORY_SEPARATOR);
require PATH . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
use PrivateBin\Configuration;
use PrivateBin\Model;
$longopts = array(
"delete-after",
"delete-during"
);
$opts_arr = getopt("fhnv", $longopts, $rest);
if ($opts_arr === false) {
dieerr("Erroneous command line options. Please use -h");
}
if (array_key_exists("h", $opts_arr)) {
helpexit();
}
$delete_after = array_key_exists("delete-after", $opts_arr);
$delete_during = array_key_exists("delete-during", $opts_arr);
$force_overwrite = array_key_exists("f", $opts_arr);
$dryrun = array_key_exists("n", $opts_arr);
$verbose = array_key_exists("v", $opts_arr);
if ($rest >= $argc) {
dieerr("Missing source configuration directory");
}
if ($delete_after && $delete_during) {
dieerr("--delete-after and --delete-during are mutually exclusive");
}
$srcconf = getConfig("source", $argv[$rest]);
$rest++;
$dstconf = getConfig("destination", ($rest < $argc ? $argv[$rest] : ""));
if (($srcconf->getSection("model") == $dstconf->getSection("model")) &&
($srcconf->getSection("model_options") == $dstconf->getSection("model_options"))) {
dieerr("Source and destination storage configurations are identical");
}
$srcmodel = new Model($srcconf);
$srcstore = $srcmodel->getStore();
$dstmodel = new Model($dstconf);
$dststore = $dstmodel->getStore();
$ids = $srcstore->getAllPastes();
foreach ($ids as $id) {
debug("Reading paste id " . $id);
$paste = $srcstore->read($id);
$comments = $srcstore->readComments($id);
savePaste($force_overwrite, $dryrun, $id, $paste, $dststore);
foreach ($comments as $comment) {
saveComment($force_overwrite, $dryrun, $id, $comment, $dststore);
}
if ($delete_during) {
deletePaste($dryrun, $id, $srcstore);
}
}
if ($delete_after) {
foreach ($ids as $id) {
deletePaste($dryrun, $id, $srcstore);
}
}
debug("Done.");
function deletePaste($dryrun, $pasteid, $srcstore)
{
if (!$dryrun) {
debug("Deleting paste id " . $pasteid);
$srcstore->delete($pasteid);
} else {
debug("Would delete paste id " . $pasteid);
}
}
function saveComment ($force_overwrite, $dryrun, $pasteid, $comment, $dststore)
{
$parentid = $comment["parentid"];
$commentid = $comment["id"];
if (!$dststore->existsComment($pasteid, $parentid, $commentid)) {
if (!$dryrun) {
debug("Saving paste id " . $pasteid . ", parent id " .
$parentid . ", comment id " . $commentid);
$dststore->createComment($pasteid, $parentid, $commentid, $comment);
} else {
debug("Would save paste id " . $pasteid . ", parent id " .
$parentid . ", comment id " . $commentid);
}
} else if ($force_overwrite) {
if (!$dryrun) {
debug("Overwriting paste id " . $pasteid . ", parent id " .
$parentid . ", comment id " . $commentid);
$dststore->createComment($pasteid, $parentid, $commentid, $comment);
} else {
debug("Would overwrite paste id " . $pasteid . ", parent id " .
$parentid . ", comment id " . $commentid);
}
} else {
if (!$dryrun) {
dieerr("Not overwriting paste id " . $pasteid . ", parent id " .
$parentid . ", comment id " . $commentid);
} else {
dieerr("Would not overwrite paste id " . $pasteid . ", parent id " .
$parentid . ", comment id " . $commentid);
}
}
}
function savePaste ($force_overwrite, $dryrun, $pasteid, $paste, $dststore)
{
if (!$dststore->exists($pasteid)) {
if (!$dryrun) {
debug("Saving paste id " . $pasteid);
$dststore->create($pasteid, $paste);
} else {
debug("Would save paste id " . $pasteid);
}
} else if ($force_overwrite) {
if (!$dryrun) {
debug("Overwriting paste id " . $pasteid);
$dststore->create($pasteid, $paste);
} else {
debug("Would overwrite paste id " . $pasteid);
}
} else {
if (!$dryrun) {
dieerr("Not overwriting paste id " . $pasteid);
} else {
dieerr("Would not overwrite paste id " . $pasteid);
}
}
}
function getConfig ($target, $confdir)
{
debug("Trying to load " . $target . " conf.php" .
($confdir === "" ? "" : " from " . $confdir));
putenv("CONFIG_PATH=" . $confdir);
$conf = new Configuration;
putenv("CONFIG_PATH=");
return $conf;
}
function dieerr ($text)
{
fprintf(STDERR, "ERROR: %s" . PHP_EOL, $text);
die(1);
}
function debug ($text) {
if ($GLOBALS["verbose"]) {
printf("DEBUG: %s" . PHP_EOL, $text);
}
}
function helpexit ()
{
print("migrate.php - Copy data between PrivateBin backends
Usage:
php migrate.php [--delete-after] [--delete-during] [-f] [-n] [-v] srcconfdir
[<dstconfdir>]
php migrate.php [-h]
Options:
--delete-after delete data from source after all pastes and comments have
successfully been copied to the destination
--delete-during delete data from source after the current paste and its
comments have successfully been copied to the destination
-f forcefully overwrite data which already exists at the
destination
-n dry run, do not copy data
-v be verbose
<srcconfdir> use storage backend configration from conf.php found in
this directory as source
<dstconfdir> optionally, use storage backend configration from conf.php
found in this directory as destination; defaults to:
" . PATH . "cfg" . DIRECTORY_SEPARATOR . "conf.php
");
exit();
}

View File

@@ -7,9 +7,10 @@
; (optional) set a project name to be displayed on the website
; name = "PrivateBin"
; The full URL, with the domain name and directories that point to the PrivateBin files
; This URL is essential to allow Opengraph images to be displayed on social networks
; basepath = ""
; The full URL, with the domain name and directories that point to the
; PrivateBin files, including an ending slash (/). This URL is essential to
; allow Opengraph images to be displayed on social networks.
; basepath = "https://privatebin.example.com/"
; enable or disable the discussion feature, defaults to true
discussion = true
@@ -55,9 +56,9 @@ languageselection = false
; if this is set and language selection is disabled, this will be the only language
; languagedefault = "en"
; (optional) URL shortener address to offer after a new paste is created
; it is suggested to only use this with self-hosted shorteners as this will leak
; the pastes encryption key
; (optional) URL shortener address to offer after a new paste is created.
; It is suggested to only use this with self-hosted shorteners as this will leak
; the pastes encryption key.
; urlshortener = "https://shortener.example.com/api?link="
; (optional) Let users create a QR code for sharing the paste URL with one click.
@@ -65,10 +66,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" / "vizhash" / "identicon" / "jdenticon" (default).
; icon = "none"
; Content Security Policy headers allow a website to restrict what sources are
@@ -175,6 +177,7 @@ dir = PATH "data"
;[model_options]
;bucket = "my-private-bin"
;prefix = "pastes"
;uniformacl = false
;[model]
; example of DB configuration for MySQL
@@ -204,3 +207,42 @@ dir = PATH "data"
;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]
; When using YOURLS as a "urlshortener" config item:
; - By default, "urlshortener" will point to the YOURLS API URL, with or without
; credentials, and will be visible in public on the PrivateBin web page.
; Only use this if you allow short URL creation without credentials.
; - Alternatively, using the parameters in this section ("signature" and
; "apiurl"), "urlshortener" needs to point to the base URL of your PrivateBin
; instance with "shortenviayourls?link=" appended. For example:
; urlshortener = "${basepath}shortenviayourls?link="
; This URL will in turn call YOURLS on the 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 = "https://yourls.example.com/yourls-api.php"

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"

397
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.2",
"source": {
"type": "git",
"url": "https://github.com/dmester/jdenticon-php.git",
"reference": "cabb7a44c413c318392a341c5d3ca30fcdd57a6f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dmester/jdenticon-php/zipball/cabb7a44c413c318392a341c5d3ca30fcdd57a6f",
"reference": "cabb7a44c413c318392a341c5d3ca30fcdd57a6f",
"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-10-30T17:15:02+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,8 +836,12 @@
"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",
@@ -837,6 +923,10 @@
"testing",
"xunit"
],
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"source": "https://github.com/sebastianbergmann/phpunit/tree/5.7.27"
},
"time": "2018-02-01T05:50:59+00:00"
},
{
@@ -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,6 +1221,10 @@
"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"
},
{
@@ -1181,6 +1292,10 @@
"export",
"exporter"
],
"support": {
"issues": "https://github.com/sebastianbergmann/exporter/issues",
"source": "https://github.com/sebastianbergmann/exporter/tree/master"
},
"time": "2016-11-19T08:54:04+00:00"
},
{
@@ -1232,6 +1347,10 @@
"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"
},
{
@@ -1278,6 +1397,10 @@
],
"description": "Traverses array structures and object graphs to enumerate all referenced objects",
"homepage": "https://github.com/sebastianbergmann/object-enumerator/",
"support": {
"issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
"source": "https://github.com/sebastianbergmann/object-enumerator/tree/master"
},
"time": "2017-02-18T15:18:39+00:00"
},
{
@@ -1331,6 +1454,10 @@
],
"description": "Provides functionality to recursively process PHP variables",
"homepage": "http://www.github.com/sebastianbergmann/recursion-context",
"support": {
"issues": "https://github.com/sebastianbergmann/recursion-context/issues",
"source": "https://github.com/sebastianbergmann/recursion-context/tree/master"
},
"time": "2016-11-19T07:33:16+00:00"
},
{
@@ -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

@@ -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

@@ -26,7 +26,7 @@
"%s requires a modern browser to work.": "%s vereist een moderne browser om te kunnen werken ",
"New": "Nieuw",
"Send": "Verzenden",
"Clone": "Klonen",
"Clone": "Clonen",
"Raw text": "Onbewerkte tekst",
"Expires": "Verloopt",
"Burn after reading": "Vernietig na lezen",
@@ -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 (heeft 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",
@@ -170,7 +170,7 @@
"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",
"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…",
@@ -182,9 +182,12 @@
"Use Current Timezone": "Gebruik huidige tijdzone",
"Convert To UTC": "Omzetten naar UTC",
"Close": "Sluiten",
"Encrypted note on PrivateBin": "Versleutelde notitie op PrivateBin",
"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."
"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."
}

193
i18n/th.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 เป็น pastebin ออนไลน์แบบโอเพ่นซอร์สที่มีสไตล์แบบมินิมัลลิสท์ เซิร์ฟเวอร์ไม่สามารถรู้ได้ว่าข้อมูลโค้ดที่มาฝากนั้นเป็นข้อมูลอะไร โดยจะถูก %sเข้ารหัส/ถอดรหัส%s ด้วยกระบวนการ AES จำนวน 256 บิตผ่านเบราว์เซอร์",
"More information on the <a href=\"https://privatebin.info/\">project page</a>.": "ข้อมูลเพิ่มเติม ดูได้ที่<a href=\"https://privatebin.info/\">หน้าโครงการ</a>",
"Because ignorance is bliss": "ไม่รู้ไม่ชี้ดีที่สุด",
"en": "th",
"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.": [
"กรุณาเว้นระยะเวลาการส่งข้อมูลอย่างน้อย %d วินาที",
"กรุณาเว้นระยะเวลาการส่งข้อมูลอย่างน้อย %d วินาที",
"กรุณาเว้นระยะเวลาการส่งข้อมูลอย่างน้อย %d วินาที",
"กรุณาเว้นระยะเวลาการส่งข้อมูลอย่างน้อย %d วินาที"
],
"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.": "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 ต้องใช้เบราว์เซอร์สมัยใหม่ถึงจะสามารถใช้งานได้",
"New": "ใหม่",
"Send": "ส่ง",
"Clone": "โคลน",
"Raw text": "ข้อความล้วน",
"Expires": "หมดอายุ",
"Burn after reading": "ลบทันทีเมื่อเปิดอ่าน",
"Open discussion": "แสดงความคิดเห็นได้",
"Password (recommended)": "รหัสผ่าน (แนะนำให้ใส่)",
"Discussion": "ความคิดเห็น",
"Toggle navigation": "สลับเปิดปิดการนำทาง",
"%d seconds": [
"%d วินาที",
"%d วินาที",
"%d วินาที",
"%d วินาที"
],
"%d minutes": [
"%d นาที",
"%d นาที",
"%d นาที",
"%d นาที"
],
"%d hours": [
"%d ชั่วโมง",
"%d ชั่วโมง",
"%d ชั่วโมง",
"%d ชั่วโมง"
],
"%d days": [
"%d วัน",
"%d วัน",
"%d วัน",
"%d วัน"
],
"%d weeks": [
"%d สัปดาห์",
"%d สัปดาห์",
"%d สัปดาห์",
"%d สัปดาห์"
],
"%d months": [
"%d เดือน",
"%d เดือน",
"%d เดือน",
"%d เดือน"
],
"%d years": [
"%d ปี",
"%d ปี",
"%d ปี",
"%d ปี"
],
"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.": [
"เอกสารนี้จะหมดอายุใน %d วินาที",
"เอกสารนี้จะหมดอายุใน %d วินาที",
"เอกสารนี้จะหมดอายุใน %d วินาที",
"เอกสารนี้จะหมดอายุใน %d วินาที"
],
"This document will expire in %d minutes.": [
"เอกสารนี้จะหมดอายุใน %d นาที",
"เอกสารนี้จะหมดอายุใน %d นาที",
"เอกสารนี้จะหมดอายุใน %d นาที",
"เอกสารนี้จะหมดอายุใน %d นาที"
],
"This document will expire in %d hours.": [
"เอกสารนี้จะหมดอายุใน %d ชั่วโมง",
"เอกสารนี้จะหมดอายุใน %d ชั่วโมง",
"เอกสารนี้จะหมดอายุใน %d ชั่วโมง",
"เอกสารนี้จะหมดอายุใน %d ชั่วโมง"
],
"This document will expire in %d days.": [
"เอกสารนี้จะหมดอายุใน %d วัน",
"เอกสารนี้จะหมดอายุใน %d วัน",
"เอกสารนี้จะหมดอายุใน %d วัน",
"เอกสารนี้จะหมดอายุใน %d วัน"
],
"This document will expire in %d months.": [
"เอกสารนี้จะหมดอายุใน %d เดือน",
"เอกสารนี้จะหมดอายุใน %d เดือน",
"เอกสารนี้จะหมดอายุใน %d เดือน",
"เอกสารนี้จะหมดอายุใน %d เดือน"
],
"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": "อวาตารสร้างมาจากไอพี",
"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?)": "ไม่สามารถถอดรหัสข้อมูลการฝากโค้ดได้: คีย์ถอดรหัสที่อยู่ใน URL หายไป (คุณได้ใช้ตัวเปลี่ยนเส้นทางหรือตัวย่อ URL ที่มีการตัดส่วนของ URL ออกหรือไม่)",
"B": "B",
"KiB": "KiB",
"MiB": "MiB",
"GiB": "GiB",
"TiB": "TiB",
"PiB": "PiB",
"EiB": "EiB",
"ZiB": "ZiB",
"YiB": "YiB",
"Format": "รูปแบบ",
"Plain Text": "ข้อความล้วน",
"Source Code": "ซอร์สโค้ด",
"Markdown": "Markdown",
"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": "หรือสามารถลากและวางไฟล์หรือวางรูปภาพจากคลิปบอร์ดได้",
"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.": "เบราว์เซอร์ของคุณไม่สนับสนุนการอัปโหลดไฟล์แบบเข้ารหัสได้ กรุณาใช้เบราว์เซอร์ที่ใหม่กว่า",
"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": "คิวอาร์โค้ด",
"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 เพื่อสนับสนุน API แบบ WebCrypto ลอง<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 นี้ให้ใครก็ได้เพื่อให้สามารถเข้าถึงบันทึกย่อได้",
"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.": "กำลังพยายามใช้เครื่องมือสร้างลิงก์ย่อ ที่ไม่ได้ชี้ไปที่อินสแตนซ์ของเรา",
"Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "เกิดข้อผิดพลาดในการเรียก YOURLS อาจเป็นปัญหามาจากการกำหนดค่า เช่น \"apiurl\" หรือ \"signature\" ไม่ถูกต้องหรือขาดหายไป",
"Error parsing YOURLS response.": "เกิดข้อผิดพลาดในการแยกวิเคราะห์การตอบสนองของ YOURLS"
}

View File

@@ -60,7 +60,7 @@
],
"%d weeks": [
"%d hafta",
"%d hafta",
"%d haftalar",
"%d hafta",
"%d hafta"
],
@@ -79,34 +79,34 @@
"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.": [
"Bu belge %s saniyede silinecektir.",
"Bu belge %s saniyede silinecektir.",
"Bu belge %s saniyede silinecektir.",
"Bu belge %s saniyede silinecektir."
"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.": [
"Bu belge %s dakikada silinecektir.",
"Bu belge %s dakikada silinecektir.",
"Bu belge %s dakikada silinecektir.",
"Bu belge %s dakikada silinecektir."
"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.": [
"Bu belge %s saatte silinecektir.",
"Bu belge %s saatte silinecektir.",
"Bu belge %s saatte silinecektir.",
"Bu belge %s saatte silinecektir.."
"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.": [
"Bu belge %s günde silinecektir.",
"Bu belge %s günde silinecektir.",
"Bu belge %s günde silinecektir.",
"Bu belge %s günde silinecektir.(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.": [
"Bu belge %s ayda silinecektir.",
"Bu belge %s ayda silinecektir",
"Bu belge %s ayda silinecektir",
"Bu belge %s ayda silinecektir"
"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:": "Lütfen bu yazı için şifrenizi girin:",
"Could not decrypt data (Wrong key?)": "Şifre çözülemedi (Yanlış anahtar mı kullandınız?)",
@@ -117,9 +117,9 @@
"Anonymous": "Anonim",
"Avatar generated from IP address": "IP adresinden oluşturulmuş avatar",
"Add comment": "Yorum ekle",
"Optional nickname…": "İsteğe bağlı takma isim...",
"Optional nickname…": "İsteğe bağlı takma isim",
"Post comment": "Yorumu gönder",
"Sending comment…": "Yorum gönderiliyor...",
"Sending comment…": "Yorum gönderiliyor",
"Comment posted.": "Yorum gönderildi.",
"Could not refresh display: %s": "Görüntü yenilenemedi: %s",
"unknown status": "bilinmeyen durum",
@@ -147,7 +147,7 @@
"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.z",
"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.",
@@ -160,8 +160,8 @@
"Decrypt": "Şifreyi çöz",
"Enter password": "Şifreyi girin",
"Loading…": "Yükleniyor…",
"Decrypting paste…": "Yazı şifresi çözülüyor...",
"Preparing new paste…": "Yeni yazı hazırlanıyor...",
"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": "Yazı verisi alınamıyor: %s",
@@ -173,7 +173,7 @@
"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.": "Dosya şifresi çözülemedi, doğru şifreyi kullandığınıza emin misiniz? Üstteki buton ile tekrar deneyin.",
"Retry": "Yeniden Dene",
"Showing raw text…": "Açık yazı gösteriliyor...",
"Showing raw text…": "Açık yazı gösteriliyor",
"Notice:": "Bildirim:",
"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.",
@@ -182,9 +182,12 @@
"Use Current Timezone": "Şuanki zaman dilimini kullan",
"Convert To UTC": "UTC zaman dilimine çevir",
"Close": "Kapat",
"Encrypted note on PrivateBin": "PrivateBin üzerinde şifrelenmiş not",
"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."
"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.": "您的 IP 无权创建粘贴。"
"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;

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', 'tr', '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', 'th', 'tr', 'uk', 'zh'];
/**
* built in language
@@ -803,7 +803,8 @@ 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':
@@ -814,17 +815,18 @@ jQuery.PrivateBin = (function($, RawDeflate) {
return n === 1 ? 0 : (n === 2 ? 1 : ((n < 0 || n > 10) && (n % 10 === 0) ? 2 : 3));
case 'id':
case 'jbo':
case 'th':
return 0;
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

@@ -53,7 +53,7 @@ class Configuration
'languagedefault' => '',
'urlshortener' => '',
'qrcode' => true,
'icon' => 'identicon',
'icon' => 'jdenticon',
'cspheader' => 'default-src \'none\'; base-uri \'self\'; form-action \'none\'; manifest-src \'self\'; connect-src * blob:; script-src \'self\' \'unsafe-eval\'; style-src \'self\'; font-src \'self\'; frame-ancestors \'none\'; img-src \'self\' data: blob:; media-src blob:; object-src blob:; sandbox allow-same-origin allow-scripts allow-forms allow-popups allow-modals allow-downloads',
'zerobincompatibility' => false,
'httpwarning' => true,
@@ -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' => '',
);
}
@@ -215,6 +236,14 @@ class Configuration
if (!array_key_exists($this->_configuration['expire']['default'], $this->_configuration['expire_options'])) {
$this->_configuration['expire']['default'] = key($this->_configuration['expire_options']);
}
// ensure the basepath ends in a slash, if one is set
if (
strlen($this->_configuration['main']['basepath']) &&
substr_compare($this->_configuration['main']['basepath'], '/', -1) !== 0
) {
$this->_configuration['main']['basepath'] .= '/';
}
}
/**

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,7 +344,10 @@ 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');
// 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');
@@ -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

@@ -15,61 +15,17 @@ namespace PrivateBin\Data;
/**
* AbstractData
*
* Abstract model for data access, implemented as a singleton.
* Abstract model for data access
*/
abstract class AbstractData
{
/**
* Singleton instance
*
* @access protected
* @static
* @var AbstractData
*/
protected static $_instance = null;
/**
* cache for the traffic limiter
*
* @access private
* @static
* @access protected
* @var array
*/
protected static $_last_cache = array();
/**
* Enforce singleton, disable constructor
*
* Instantiate using {@link getInstance()}, this object implements the singleton pattern.
*
* @access protected
*/
protected function __construct()
{
}
/**
* Enforce singleton, disable cloning
*
* Instantiate using {@link getInstance()}, this object implements the singleton pattern.
*
* @access private
*/
private function __clone()
{
}
/**
* Get instance of singleton
*
* @access public
* @static
* @param array $options
* @return AbstractData
*/
public static function getInstance(array $options)
{
}
protected $_last_cache = array();
/**
* Create a paste.
@@ -150,9 +106,9 @@ abstract class AbstractData
public function purgeValues($namespace, $time)
{
if ($namespace === 'traffic_limiter') {
foreach (self::$_last_cache as $key => $last_submission) {
foreach ($this->_last_cache as $key => $last_submission) {
if ($last_submission <= $time) {
unset(self::$_last_cache[$key]);
unset($this->_last_cache[$key]);
}
}
}
@@ -207,6 +163,14 @@ abstract class AbstractData
}
}
/**
* Returns all paste ids
*
* @access public
* @return array
*/
abstract public function getAllPastes();
/**
* Get next free slot for comment from postdate.
*

View File

@@ -25,59 +25,43 @@ use PrivateBin\Json;
*/
class Database extends AbstractData
{
/**
* cache for select queries
*
* @var array
*/
private static $_cache = array();
/**
* instance of database connection
*
* @access private
* @static
* @var PDO
*/
private static $_db;
private $_db;
/**
* table prefix
*
* @access private
* @static
* @var string
*/
private static $_prefix = '';
private $_prefix = '';
/**
* database type
*
* @access private
* @static
* @var string
*/
private static $_type = '';
private $_type = '';
/**
* get instance of singleton
* instantiates a new Database data backend
*
* @access public
* @static
* @param array $options
* @throws Exception
* @return Database
* @return
*/
public static function getInstance(array $options)
public function __construct(array $options)
{
// if needed initialize the singleton
if (!(self::$_instance instanceof self)) {
self::$_instance = new self;
}
// set table prefix if given
if (array_key_exists('tbl', $options)) {
self::$_prefix = $options['tbl'];
$this->_prefix = $options['tbl'];
}
// initialize the db connection with new options
@@ -94,16 +78,16 @@ class Database extends AbstractData
$db_tables_exist = true;
// setup type and dabase connection
self::$_type = strtolower(
$this->_type = strtolower(
substr($options['dsn'], 0, strpos($options['dsn'], ':'))
);
// 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')) {
if ($this->_type === 'mysql' && defined('PDO::MYSQL_ATTR_INIT_COMMAND')) {
$options['opt'][PDO::MYSQL_ATTR_INIT_COMMAND] = "SET SESSION sql_mode='ANSI_QUOTES'";
}
$tableQuery = self::_getTableQuery(self::$_type);
self::$_db = new PDO(
$tableQuery = $this->_getTableQuery($this->_type);
$this->_db = new PDO(
$options['dsn'],
$options['usr'],
$options['pwd'],
@@ -111,43 +95,41 @@ class Database extends AbstractData
);
// check if the database contains the required tables
$tables = self::$_db->query($tableQuery)->fetchAll(PDO::FETCH_COLUMN, 0);
$tables = $this->_db->query($tableQuery)->fetchAll(PDO::FETCH_COLUMN, 0);
// create paste table if necessary
if (!in_array(self::_sanitizeIdentifier('paste'), $tables)) {
self::_createPasteTable();
if (!in_array($this->_sanitizeIdentifier('paste'), $tables)) {
$this->_createPasteTable();
$db_tables_exist = false;
}
// create comment table if necessary
if (!in_array(self::_sanitizeIdentifier('comment'), $tables)) {
self::_createCommentTable();
if (!in_array($this->_sanitizeIdentifier('comment'), $tables)) {
$this->_createCommentTable();
$db_tables_exist = false;
}
// create config table if necessary
$db_version = Controller::VERSION;
if (!in_array(self::_sanitizeIdentifier('config'), $tables)) {
self::_createConfigTable();
if (!in_array($this->_sanitizeIdentifier('config'), $tables)) {
$this->_createConfigTable();
// if we only needed to create the config table, the DB is older then 0.22
if ($db_tables_exist) {
$db_version = '0.21';
}
} else {
$db_version = self::_getConfig('VERSION');
$db_version = $this->_getConfig('VERSION');
}
// update database structure if necessary
if (version_compare($db_version, Controller::VERSION, '<')) {
self::_upgradeDatabase($db_version);
$this->_upgradeDatabase($db_version);
}
} else {
throw new Exception(
'Missing configuration for key dsn, usr, pwd or opt in the section model_options, please check your configuration file', 6
);
}
return self::$_instance;
}
/**
@@ -160,22 +142,12 @@ class Database extends AbstractData
*/
public function create($pasteid, array $paste)
{
if (
array_key_exists($pasteid, self::$_cache)
) {
if (false !== self::$_cache[$pasteid]) {
return false;
} else {
unset(self::$_cache[$pasteid]);
}
}
$expire_date = 0;
$opendiscussion = $burnafterreading = false;
$attachment = $attachmentname = null;
$meta = $paste['meta'];
$isVersion1 = array_key_exists('data', $paste);
list($createdKey) = self::_getVersionedKeys($isVersion1 ? 1 : 2);
list($createdKey) = $this->_getVersionedKeys($isVersion1 ? 1 : 2);
$created = (int) $meta[$createdKey];
unset($meta[$createdKey], $paste['meta']);
if (array_key_exists('expire_date', $meta)) {
@@ -204,8 +176,8 @@ class Database extends AbstractData
$burnafterreading = $paste['adata'][3];
}
try {
return self::_exec(
'INSERT INTO "' . self::_sanitizeIdentifier('paste') .
return $this->_exec(
'INSERT INTO "' . $this->_sanitizeIdentifier('paste') .
'" VALUES(?,?,?,?,?,?,?,?,?)',
array(
$pasteid,
@@ -233,64 +205,59 @@ class Database extends AbstractData
*/
public function read($pasteid)
{
if (array_key_exists($pasteid, self::$_cache)) {
return self::$_cache[$pasteid];
}
self::$_cache[$pasteid] = false;
try {
$paste = self::_select(
'SELECT * FROM "' . self::_sanitizeIdentifier('paste') .
$row = $this->_select(
'SELECT * FROM "' . $this->_sanitizeIdentifier('paste') .
'" WHERE "dataid" = ?', array($pasteid), true
);
} catch (Exception $e) {
$paste = false;
$row = false;
}
if ($paste === false) {
if ($row === false) {
return false;
}
// create array
$data = Json::decode($paste['data']);
$data = Json::decode($row['data']);
$isVersion2 = array_key_exists('v', $data) && $data['v'] >= 2;
if ($isVersion2) {
self::$_cache[$pasteid] = $data;
list($createdKey) = self::_getVersionedKeys(2);
$paste = $data;
list($createdKey) = $this->_getVersionedKeys(2);
} else {
self::$_cache[$pasteid] = array('data' => $paste['data']);
list($createdKey) = self::_getVersionedKeys(1);
$paste = array('data' => $row['data']);
list($createdKey) = $this->_getVersionedKeys(1);
}
try {
$paste['meta'] = Json::decode($paste['meta']);
$row['meta'] = Json::decode($row['meta']);
} catch (Exception $e) {
$paste['meta'] = array();
$row['meta'] = array();
}
$paste = self::upgradePreV1Format($paste);
self::$_cache[$pasteid]['meta'] = $paste['meta'];
self::$_cache[$pasteid]['meta'][$createdKey] = (int) $paste['postdate'];
$expire_date = (int) $paste['expiredate'];
$row = self::upgradePreV1Format($row);
$paste['meta'] = $row['meta'];
$paste['meta'][$createdKey] = (int) $row['postdate'];
$expire_date = (int) $row['expiredate'];
if ($expire_date > 0) {
self::$_cache[$pasteid]['meta']['expire_date'] = $expire_date;
$paste['meta']['expire_date'] = $expire_date;
}
if ($isVersion2) {
return self::$_cache[$pasteid];
return $paste;
}
// support v1 attachments
if (array_key_exists('attachment', $paste) && !empty($paste['attachment'])) {
self::$_cache[$pasteid]['attachment'] = $paste['attachment'];
if (array_key_exists('attachmentname', $paste) && !empty($paste['attachmentname'])) {
self::$_cache[$pasteid]['attachmentname'] = $paste['attachmentname'];
if (array_key_exists('attachment', $row) && !empty($row['attachment'])) {
$paste['attachment'] = $row['attachment'];
if (array_key_exists('attachmentname', $row) && !empty($row['attachmentname'])) {
$paste['attachmentname'] = $row['attachmentname'];
}
}
if ($paste['opendiscussion']) {
self::$_cache[$pasteid]['meta']['opendiscussion'] = true;
if ($row['opendiscussion']) {
$paste['meta']['opendiscussion'] = true;
}
if ($paste['burnafterreading']) {
self::$_cache[$pasteid]['meta']['burnafterreading'] = true;
if ($row['burnafterreading']) {
$paste['meta']['burnafterreading'] = true;
}
return self::$_cache[$pasteid];
return $paste;
}
/**
@@ -301,19 +268,14 @@ class Database extends AbstractData
*/
public function delete($pasteid)
{
self::_exec(
'DELETE FROM "' . self::_sanitizeIdentifier('paste') .
$this->_exec(
'DELETE FROM "' . $this->_sanitizeIdentifier('paste') .
'" WHERE "dataid" = ?', array($pasteid)
);
self::_exec(
'DELETE FROM "' . self::_sanitizeIdentifier('comment') .
$this->_exec(
'DELETE FROM "' . $this->_sanitizeIdentifier('comment') .
'" WHERE "pasteid" = ?', array($pasteid)
);
if (
array_key_exists($pasteid, self::$_cache)
) {
unset(self::$_cache[$pasteid]);
}
}
/**
@@ -325,12 +287,15 @@ class Database extends AbstractData
*/
public function exists($pasteid)
{
if (
!array_key_exists($pasteid, self::$_cache)
) {
self::$_cache[$pasteid] = $this->read($pasteid);
try {
$row = $this->_select(
'SELECT * FROM "' . $this->_sanitizeIdentifier('paste') .
'" WHERE "dataid" = ?', array($pasteid), true
);
} catch (Exception $e) {
$row = false;
}
return (bool) self::$_cache[$pasteid];
return (bool) $row;
}
/**
@@ -352,7 +317,7 @@ class Database extends AbstractData
$version = 2;
$data = Json::encode($comment);
}
list($createdKey, $iconKey) = self::_getVersionedKeys($version);
list($createdKey, $iconKey) = $this->_getVersionedKeys($version);
$meta = $comment['meta'];
unset($comment['meta']);
foreach (array('nickname', $iconKey) as $key) {
@@ -361,8 +326,8 @@ class Database extends AbstractData
}
}
try {
return self::_exec(
'INSERT INTO "' . self::_sanitizeIdentifier('comment') .
return $this->_exec(
'INSERT INTO "' . $this->_sanitizeIdentifier('comment') .
'" VALUES(?,?,?,?,?,?,?)',
array(
$commentid,
@@ -388,8 +353,8 @@ class Database extends AbstractData
*/
public function readComments($pasteid)
{
$rows = self::_select(
'SELECT * FROM "' . self::_sanitizeIdentifier('comment') .
$rows = $this->_select(
'SELECT * FROM "' . $this->_sanitizeIdentifier('comment') .
'" WHERE "pasteid" = ?', array($pasteid)
);
@@ -406,7 +371,7 @@ class Database extends AbstractData
$version = 1;
$comments[$i] = array('data' => $row['data']);
}
list($createdKey, $iconKey) = self::_getVersionedKeys($version);
list($createdKey, $iconKey) = $this->_getVersionedKeys($version);
$comments[$i]['id'] = $row['dataid'];
$comments[$i]['parentid'] = $row['parentid'];
$comments[$i]['meta'] = array($createdKey => (int) $row['postdate']);
@@ -433,8 +398,8 @@ class Database extends AbstractData
public function existsComment($pasteid, $parentid, $commentid)
{
try {
return (bool) self::_select(
'SELECT "dataid" FROM "' . self::_sanitizeIdentifier('comment') .
return (bool) $this->_select(
'SELECT "dataid" FROM "' . $this->_sanitizeIdentifier('comment') .
'" WHERE "pasteid" = ? AND "parentid" = ? AND "dataid" = ?',
array($pasteid, $parentid, $commentid), true
);
@@ -455,15 +420,15 @@ class Database extends AbstractData
public function setValue($value, $namespace, $key = '')
{
if ($namespace === 'traffic_limiter') {
self::$_last_cache[$key] = $value;
$this->_last_cache[$key] = $value;
try {
$value = Json::encode(self::$_last_cache);
$value = Json::encode($this->_last_cache);
} catch (Exception $e) {
return false;
}
}
return self::_exec(
'UPDATE "' . self::_sanitizeIdentifier('config') .
return $this->_exec(
'UPDATE "' . $this->_sanitizeIdentifier('config') .
'" SET "value" = ? WHERE "id" = ?',
array($value, strtoupper($namespace))
);
@@ -483,8 +448,8 @@ class Database extends AbstractData
$value = $this->_getConfig($configKey);
if ($value === '') {
// initialize the row, so that setValue can rely on UPDATE queries
self::_exec(
'INSERT INTO "' . self::_sanitizeIdentifier('config') .
$this->_exec(
'INSERT INTO "' . $this->_sanitizeIdentifier('config') .
'" VALUES(?,?)',
array($configKey, '')
);
@@ -492,7 +457,8 @@ class Database extends AbstractData
// migrate filesystem based salt into database
$file = 'data' . DIRECTORY_SEPARATOR . 'salt.php';
if ($namespace === 'salt' && is_readable($file)) {
$value = Filesystem::getInstance(array('dir' => 'data'))->getValue('salt');
$fs = new Filesystem(array('dir' => 'data'));
$value = $fs->getValue('salt');
$this->setValue($value, 'salt');
@unlink($file);
return $value;
@@ -500,12 +466,12 @@ class Database extends AbstractData
}
if ($value && $namespace === 'traffic_limiter') {
try {
self::$_last_cache = Json::decode($value);
$this->_last_cache = Json::decode($value);
} catch (Exception $e) {
self::$_last_cache = array();
$this->_last_cache = array();
}
if (array_key_exists($key, self::$_last_cache)) {
return self::$_last_cache[$key];
if (array_key_exists($key, $this->_last_cache)) {
return $this->_last_cache[$key];
}
}
return (string) $value;
@@ -521,10 +487,10 @@ class Database extends AbstractData
protected function _getExpiredPastes($batchsize)
{
$pastes = array();
$rows = self::_select(
'SELECT "dataid" FROM "' . self::_sanitizeIdentifier('paste') .
$rows = $this->_select(
'SELECT "dataid" FROM "' . $this->_sanitizeIdentifier('paste') .
'" WHERE "expiredate" < ? AND "expiredate" != ? ' .
(self::$_type === 'oci' ? 'FETCH NEXT ? ROWS ONLY' : 'LIMIT ?'),
($this->_type === 'oci' ? 'FETCH NEXT ? ROWS ONLY' : 'LIMIT ?'),
array(time(), 0, $batchsize)
);
if (is_array($rows) && count($rows)) {
@@ -535,19 +501,29 @@ class Database extends AbstractData
return $pastes;
}
/**
* @inheritDoc
*/
public function getAllPastes()
{
$pastes = $this->_db->_query(
'SELECT "dataid" FROM "' . $this->_sanitizeIdentifier('paste') . '"'
)->fetchAll(PDO::FETCH_COLUMN, 0);
return $pastes;
}
/**
* execute a statement
*
* @access private
* @static
* @param string $sql
* @param array $params
* @throws PDOException
* @return bool
*/
private static function _exec($sql, array $params)
private function _exec($sql, array $params)
{
$statement = self::$_db->prepare($sql);
$statement = $this->_db->prepare($sql);
foreach ($params as $key => &$parameter) {
$position = $key + 1;
if (is_int($parameter)) {
@@ -567,33 +543,32 @@ class Database extends AbstractData
* run a select statement
*
* @access private
* @static
* @param string $sql
* @param array $params
* @param bool $firstOnly if only the first row should be returned
* @throws PDOException
* @return array|false
*/
private static function _select($sql, array $params, $firstOnly = false)
private function _select($sql, array $params, $firstOnly = false)
{
$statement = self::$_db->prepare($sql);
$statement = $this->_db->prepare($sql);
$statement->execute($params);
if ($firstOnly) {
$result = $statement->fetch(PDO::FETCH_ASSOC);
} elseif (self::$_type === 'oci') {
} elseif ($this->_type === 'oci') {
// 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);
}
$statement->closeCursor();
if (self::$_type === 'oci' && is_array($result)) {
if ($this->_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;
@@ -603,11 +578,10 @@ class Database extends AbstractData
* get version dependent key names
*
* @access private
* @static
* @param int $version
* @return array
*/
private static function _getVersionedKeys($version)
private function _getVersionedKeys($version)
{
if ($version === 1) {
return array('postdate', 'vizhash');
@@ -619,12 +593,11 @@ class Database extends AbstractData
* get table list query, depending on the database type
*
* @access private
* @static
* @param string $type
* @throws Exception
* @return string
*/
private static function _getTableQuery($type)
private function _getTableQuery($type)
{
switch ($type) {
case 'ibm':
@@ -675,15 +648,14 @@ class Database extends AbstractData
* get a value by key from the config table
*
* @access private
* @static
* @param string $key
* @return string
*/
private static function _getConfig($key)
private function _getConfig($key)
{
try {
$row = self::_select(
'SELECT "value" FROM "' . self::_sanitizeIdentifier('config') .
$row = $this->_select(
'SELECT "value" FROM "' . $this->_sanitizeIdentifier('config') .
'" WHERE "id" = ?', array($key), true
);
} catch (PDOException $e) {
@@ -696,14 +668,13 @@ class Database extends AbstractData
* get the primary key clauses, depending on the database driver
*
* @access private
* @static
* @param string $key
* @return array
*/
private static function _getPrimaryKeyClauses($key = 'dataid')
private function _getPrimaryKeyClauses($key = 'dataid')
{
$main_key = $after_key = '';
switch (self::$_type) {
switch ($this->_type) {
case 'mysql':
case 'oci':
$after_key = ", PRIMARY KEY (\"$key\")";
@@ -721,12 +692,11 @@ class Database extends AbstractData
* PostgreSQL and OCI uses a different API for BLOBs then SQL, hence we use TEXT and CLOB
*
* @access private
* @static
* @return string
*/
private static function _getDataType()
private function _getDataType()
{
switch (self::$_type) {
switch ($this->_type) {
case 'oci':
return 'CLOB';
case 'pgsql':
@@ -742,12 +712,11 @@ class Database extends AbstractData
* PostgreSQL and OCI use different APIs for BLOBs then SQL, hence we use TEXT and CLOB
*
* @access private
* @static
* @return string
*/
private static function _getAttachmentType()
private function _getAttachmentType()
{
switch (self::$_type) {
switch ($this->_type) {
case 'oci':
return 'CLOB';
case 'pgsql':
@@ -763,12 +732,11 @@ class Database extends AbstractData
* OCI doesn't accept TEXT so it has to be VARCHAR2(4000)
*
* @access private
* @static
* @return string
*/
private static function _getMetaType()
private function _getMetaType()
{
switch (self::$_type) {
switch ($this->_type) {
case 'oci':
return 'VARCHAR2(4000)';
default:
@@ -780,16 +748,15 @@ class Database extends AbstractData
* create the paste table
*
* @access private
* @static
*/
private static function _createPasteTable()
private function _createPasteTable()
{
list($main_key, $after_key) = self::_getPrimaryKeyClauses();
$dataType = self::_getDataType();
$attachmentType = self::_getAttachmentType();
$metaType = self::_getMetaType();
self::$_db->exec(
'CREATE TABLE "' . self::_sanitizeIdentifier('paste') . '" ( ' .
list($main_key, $after_key) = $this->_getPrimaryKeyClauses();
$dataType = $this->_getDataType();
$attachmentType = $this->_getAttachmentType();
$metaType = $this->_getMetaType();
$this->_db->exec(
'CREATE TABLE "' . $this->_sanitizeIdentifier('paste') . '" ( ' .
"\"dataid\" CHAR(16) NOT NULL$main_key, " .
"\"data\" $attachmentType, " .
'"postdate" INT, ' .
@@ -806,14 +773,13 @@ class Database extends AbstractData
* create the paste table
*
* @access private
* @static
*/
private static function _createCommentTable()
private function _createCommentTable()
{
list($main_key, $after_key) = self::_getPrimaryKeyClauses();
$dataType = self::_getDataType();
self::$_db->exec(
'CREATE TABLE "' . self::_sanitizeIdentifier('comment') . '" ( ' .
list($main_key, $after_key) = $this->_getPrimaryKeyClauses();
$dataType = $this->_getDataType();
$this->_db->exec(
'CREATE TABLE "' . $this->_sanitizeIdentifier('comment') . '" ( ' .
"\"dataid\" CHAR(16) NOT NULL$main_key, " .
'"pasteid" CHAR(16), ' .
'"parentid" CHAR(16), ' .
@@ -822,15 +788,15 @@ class Database extends AbstractData
"\"vizhash\" $dataType, " .
"\"postdate\" INT$after_key )"
);
if (self::$_type === 'oci') {
self::$_db->exec(
if ($this->_type === 'oci') {
$this->_db->exec(
'declare
already_exists exception;
columns_indexed exception;
pragma exception_init( already_exists, -955 );
pragma exception_init(columns_indexed, -1408);
begin
execute immediate \'create index "comment_parent" on "' . self::_sanitizeIdentifier('comment') . '" ("pasteid")\';
execute immediate \'create index "comment_parent" on "' . $this->_sanitizeIdentifier('comment') . '" ("pasteid")\';
exception
when already_exists or columns_indexed then
NULL;
@@ -838,10 +804,10 @@ class Database extends AbstractData
);
} else {
// CREATE INDEX IF NOT EXISTS not supported as of Oracle MySQL <= 8.0
self::$_db->exec(
$this->_db->exec(
'CREATE INDEX "' .
self::_sanitizeIdentifier('comment_parent') . '" ON "' .
self::_sanitizeIdentifier('comment') . '" ("pasteid")'
$this->_sanitizeIdentifier('comment_parent') . '" ON "' .
$this->_sanitizeIdentifier('comment') . '" ("pasteid")'
);
}
}
@@ -850,19 +816,18 @@ class Database extends AbstractData
* create the paste table
*
* @access private
* @static
*/
private static function _createConfigTable()
private function _createConfigTable()
{
list($main_key, $after_key) = self::_getPrimaryKeyClauses('id');
$charType = self::$_type === 'oci' ? 'VARCHAR2(16)' : 'CHAR(16)';
$textType = self::_getMetaType();
self::$_db->exec(
'CREATE TABLE "' . self::_sanitizeIdentifier('config') .
list($main_key, $after_key) = $this->_getPrimaryKeyClauses('id');
$charType = $this->_type === 'oci' ? 'VARCHAR2(16)' : 'CHAR(16)';
$textType = $this->_getMetaType();
$this->_db->exec(
'CREATE TABLE "' . $this->_sanitizeIdentifier('config') .
"\" ( \"id\" $charType NOT NULL$main_key, \"value\" $textType$after_key )"
);
self::_exec(
'INSERT INTO "' . self::_sanitizeIdentifier('config') .
$this->_exec(
'INSERT INTO "' . $this->_sanitizeIdentifier('config') .
'" VALUES(?,?)',
array('VERSION', Controller::VERSION)
);
@@ -890,90 +855,88 @@ class Database extends AbstractData
* sanitizes identifiers
*
* @access private
* @static
* @param string $identifier
* @return string
*/
private static function _sanitizeIdentifier($identifier)
private function _sanitizeIdentifier($identifier)
{
return preg_replace('/[^A-Za-z0-9_]+/', '', self::$_prefix . $identifier);
return preg_replace('/[^A-Za-z0-9_]+/', '', $this->_prefix . $identifier);
}
/**
* upgrade the database schema from an old version
*
* @access private
* @static
* @param string $oldversion
*/
private static function _upgradeDatabase($oldversion)
private function _upgradeDatabase($oldversion)
{
$dataType = self::_getDataType();
$attachmentType = self::_getAttachmentType();
$dataType = $this->_getDataType();
$attachmentType = $this->_getAttachmentType();
switch ($oldversion) {
case '0.21':
// create the meta column if necessary (pre 0.21 change)
try {
self::$_db->exec(
'SELECT "meta" FROM "' . self::_sanitizeIdentifier('paste') . '" ' .
(self::$_type === 'oci' ? 'FETCH NEXT 1 ROWS ONLY' : 'LIMIT 1')
$this->_db->exec(
'SELECT "meta" FROM "' . $this->_sanitizeIdentifier('paste') . '" ' .
($this->_type === 'oci' ? 'FETCH NEXT 1 ROWS ONLY' : 'LIMIT 1')
);
} catch (PDOException $e) {
self::$_db->exec('ALTER TABLE "' . self::_sanitizeIdentifier('paste') . '" ADD COLUMN "meta" TEXT');
$this->_db->exec('ALTER TABLE "' . $this->_sanitizeIdentifier('paste') . '" ADD COLUMN "meta" TEXT');
}
// SQLite only allows one ALTER statement at a time...
self::$_db->exec(
'ALTER TABLE "' . self::_sanitizeIdentifier('paste') .
$this->_db->exec(
'ALTER TABLE "' . $this->_sanitizeIdentifier('paste') .
"\" ADD COLUMN \"attachment\" $attachmentType"
);
self::$_db->exec(
'ALTER TABLE "' . self::_sanitizeIdentifier('paste') . "\" ADD COLUMN \"attachmentname\" $dataType"
$this->_db->exec(
'ALTER TABLE "' . $this->_sanitizeIdentifier('paste') . "\" ADD COLUMN \"attachmentname\" $dataType"
);
// SQLite doesn't support MODIFY, but it allows TEXT of similar
// size as BLOB, so there is no need to change it there
if (self::$_type !== 'sqlite') {
self::$_db->exec(
'ALTER TABLE "' . self::_sanitizeIdentifier('paste') .
if ($this->_type !== 'sqlite') {
$this->_db->exec(
'ALTER TABLE "' . $this->_sanitizeIdentifier('paste') .
"\" ADD PRIMARY KEY (\"dataid\"), MODIFY COLUMN \"data\" $dataType"
);
self::$_db->exec(
'ALTER TABLE "' . self::_sanitizeIdentifier('comment') .
$this->_db->exec(
'ALTER TABLE "' . $this->_sanitizeIdentifier('comment') .
"\" ADD PRIMARY KEY (\"dataid\"), MODIFY COLUMN \"data\" $dataType, " .
"MODIFY COLUMN \"nickname\" $dataType, MODIFY COLUMN \"vizhash\" $dataType"
);
} else {
self::$_db->exec(
$this->_db->exec(
'CREATE UNIQUE INDEX IF NOT EXISTS "' .
self::_sanitizeIdentifier('paste_dataid') . '" ON "' .
self::_sanitizeIdentifier('paste') . '" ("dataid")'
$this->_sanitizeIdentifier('paste_dataid') . '" ON "' .
$this->_sanitizeIdentifier('paste') . '" ("dataid")'
);
self::$_db->exec(
$this->_db->exec(
'CREATE UNIQUE INDEX IF NOT EXISTS "' .
self::_sanitizeIdentifier('comment_dataid') . '" ON "' .
self::_sanitizeIdentifier('comment') . '" ("dataid")'
$this->_sanitizeIdentifier('comment_dataid') . '" ON "' .
$this->_sanitizeIdentifier('comment') . '" ("dataid")'
);
}
// CREATE INDEX IF NOT EXISTS not supported as of Oracle MySQL <= 8.0
self::$_db->exec(
$this->_db->exec(
'CREATE INDEX "' .
self::_sanitizeIdentifier('comment_parent') . '" ON "' .
self::_sanitizeIdentifier('comment') . '" ("pasteid")'
$this->_sanitizeIdentifier('comment_parent') . '" ON "' .
$this->_sanitizeIdentifier('comment') . '" ("pasteid")'
);
// no break, continue with updates for 0.22 and later
case '1.3':
// SQLite doesn't support MODIFY, but it allows TEXT of similar
// size as BLOB and PostgreSQL uses TEXT, so there is no need
// to change it there
if (self::$_type !== 'sqlite' && self::$_type !== 'pgsql') {
self::$_db->exec(
'ALTER TABLE "' . self::_sanitizeIdentifier('paste') .
if ($this->_type !== 'sqlite' && $this->_type !== 'pgsql') {
$this->_db->exec(
'ALTER TABLE "' . $this->_sanitizeIdentifier('paste') .
"\" MODIFY COLUMN \"data\" $attachmentType"
);
}
// no break, continue with updates for all newer versions
default:
self::_exec(
'UPDATE "' . self::_sanitizeIdentifier('config') .
$this->_exec(
'UPDATE "' . $this->_sanitizeIdentifier('config') .
'" SET "value" = ? WHERE "id" = ?',
array(Controller::VERSION, 'VERSION')
);

View File

@@ -40,33 +40,26 @@ class Filesystem extends AbstractData
* path in which to persist something
*
* @access private
* @static
* @var string
*/
private static $_path = 'data';
private $_path = 'data';
/**
* get instance of singleton
* instantiates a new Filesystem data backend
*
* @access public
* @static
* @param array $options
* @return Filesystem
* @return
*/
public static function getInstance(array $options)
public function __construct(array $options)
{
// if needed initialize the singleton
if (!(self::$_instance instanceof self)) {
self::$_instance = new self;
}
// if given update the data directory
if (
is_array($options) &&
array_key_exists('dir', $options)
) {
self::$_path = $options['dir'];
$this->_path = $options['dir'];
}
return self::$_instance;
}
/**
@@ -79,7 +72,7 @@ class Filesystem extends AbstractData
*/
public function create($pasteid, array $paste)
{
$storagedir = self::_dataid2path($pasteid);
$storagedir = $this->_dataid2path($pasteid);
$file = $storagedir . $pasteid . '.php';
if (is_file($file)) {
return false;
@@ -87,7 +80,7 @@ class Filesystem extends AbstractData
if (!is_dir($storagedir)) {
mkdir($storagedir, 0700, true);
}
return self::_store($file, $paste);
return $this->_store($file, $paste);
}
/**
@@ -101,7 +94,7 @@ class Filesystem extends AbstractData
{
if (
!$this->exists($pasteid) ||
!$paste = self::_get(self::_dataid2path($pasteid) . $pasteid . '.php')
!$paste = $this->_get($this->_dataid2path($pasteid) . $pasteid . '.php')
) {
return false;
}
@@ -116,7 +109,7 @@ class Filesystem extends AbstractData
*/
public function delete($pasteid)
{
$pastedir = self::_dataid2path($pasteid);
$pastedir = $this->_dataid2path($pasteid);
if (is_dir($pastedir)) {
// Delete the paste itself.
if (is_file($pastedir . $pasteid . '.php')) {
@@ -124,7 +117,7 @@ class Filesystem extends AbstractData
}
// Delete discussion if it exists.
$discdir = self::_dataid2discussionpath($pasteid);
$discdir = $this->_dataid2discussionpath($pasteid);
if (is_dir($discdir)) {
// Delete all files in discussion directory
$dir = dir($discdir);
@@ -148,20 +141,20 @@ class Filesystem extends AbstractData
*/
public function exists($pasteid)
{
$basePath = self::_dataid2path($pasteid) . $pasteid;
$basePath = $this->_dataid2path($pasteid) . $pasteid;
$pastePath = $basePath . '.php';
// convert to PHP protected files if needed
if (is_readable($basePath)) {
self::_prependRename($basePath, $pastePath);
$this->_prependRename($basePath, $pastePath);
// convert comments, too
$discdir = self::_dataid2discussionpath($pasteid);
$discdir = $this->_dataid2discussionpath($pasteid);
if (is_dir($discdir)) {
$dir = dir($discdir);
while (false !== ($filename = $dir->read())) {
if (substr($filename, -4) !== '.php' && strlen($filename) >= 16) {
$commentFilename = $discdir . $filename . '.php';
self::_prependRename($discdir . $filename, $commentFilename);
$this->_prependRename($discdir . $filename, $commentFilename);
}
}
$dir->close();
@@ -182,7 +175,7 @@ class Filesystem extends AbstractData
*/
public function createComment($pasteid, $parentid, $commentid, array $comment)
{
$storagedir = self::_dataid2discussionpath($pasteid);
$storagedir = $this->_dataid2discussionpath($pasteid);
$file = $storagedir . $pasteid . '.' . $commentid . '.' . $parentid . '.php';
if (is_file($file)) {
return false;
@@ -190,7 +183,7 @@ class Filesystem extends AbstractData
if (!is_dir($storagedir)) {
mkdir($storagedir, 0700, true);
}
return self::_store($file, $comment);
return $this->_store($file, $comment);
}
/**
@@ -203,7 +196,7 @@ class Filesystem extends AbstractData
public function readComments($pasteid)
{
$comments = array();
$discdir = self::_dataid2discussionpath($pasteid);
$discdir = $this->_dataid2discussionpath($pasteid);
if (is_dir($discdir)) {
$dir = dir($discdir);
while (false !== ($filename = $dir->read())) {
@@ -212,7 +205,7 @@ class Filesystem extends AbstractData
// - commentid is the comment identifier itself.
// - parentid is the comment this comment replies to (It can be pasteid)
if (is_file($discdir . $filename)) {
$comment = self::_get($discdir . $filename);
$comment = $this->_get($discdir . $filename);
$items = explode('.', $filename);
// Add some meta information not contained in file.
$comment['id'] = $items[1];
@@ -243,7 +236,7 @@ class Filesystem extends AbstractData
public function existsComment($pasteid, $parentid, $commentid)
{
return is_file(
self::_dataid2discussionpath($pasteid) .
$this->_dataid2discussionpath($pasteid) .
$pasteid . '.' . $commentid . '.' . $parentid . '.php'
);
}
@@ -261,20 +254,20 @@ class Filesystem extends AbstractData
{
switch ($namespace) {
case 'purge_limiter':
return self::_storeString(
self::$_path . DIRECTORY_SEPARATOR . 'purge_limiter.php',
return $this->_storeString(
$this->_path . DIRECTORY_SEPARATOR . 'purge_limiter.php',
'<?php' . PHP_EOL . '$GLOBALS[\'purge_limiter\'] = ' . $value . ';'
);
case 'salt':
return self::_storeString(
self::$_path . DIRECTORY_SEPARATOR . 'salt.php',
return $this->_storeString(
$this->_path . DIRECTORY_SEPARATOR . 'salt.php',
'<?php # |' . $value . '|'
);
case 'traffic_limiter':
self::$_last_cache[$key] = $value;
return self::_storeString(
self::$_path . DIRECTORY_SEPARATOR . 'traffic_limiter.php',
'<?php' . PHP_EOL . '$GLOBALS[\'traffic_limiter\'] = ' . var_export(self::$_last_cache, true) . ';'
$this->_last_cache[$key] = $value;
return $this->_storeString(
$this->_path . DIRECTORY_SEPARATOR . 'traffic_limiter.php',
'<?php' . PHP_EOL . '$GLOBALS[\'traffic_limiter\'] = ' . var_export($this->_last_cache, true) . ';'
);
}
return false;
@@ -292,14 +285,14 @@ class Filesystem extends AbstractData
{
switch ($namespace) {
case 'purge_limiter':
$file = self::$_path . DIRECTORY_SEPARATOR . 'purge_limiter.php';
$file = $this->_path . DIRECTORY_SEPARATOR . 'purge_limiter.php';
if (is_readable($file)) {
require $file;
return $GLOBALS['purge_limiter'];
}
break;
case 'salt':
$file = self::$_path . DIRECTORY_SEPARATOR . 'salt.php';
$file = $this->_path . DIRECTORY_SEPARATOR . 'salt.php';
if (is_readable($file)) {
$items = explode('|', file_get_contents($file));
if (is_array($items) && count($items) == 3) {
@@ -308,12 +301,12 @@ class Filesystem extends AbstractData
}
break;
case 'traffic_limiter':
$file = self::$_path . DIRECTORY_SEPARATOR . 'traffic_limiter.php';
$file = $this->_path . DIRECTORY_SEPARATOR . 'traffic_limiter.php';
if (is_readable($file)) {
require $file;
self::$_last_cache = $GLOBALS['traffic_limiter'];
if (array_key_exists($key, self::$_last_cache)) {
return self::$_last_cache[$key];
$this->_last_cache = $GLOBALS['traffic_limiter'];
if (array_key_exists($key, $this->_last_cache)) {
return $this->_last_cache[$key];
}
}
break;
@@ -325,11 +318,10 @@ class Filesystem extends AbstractData
* get the data
*
* @access public
* @static
* @param string $filename
* @return array|false $data
*/
private static function _get($filename)
private function _get($filename)
{
return Json::decode(
substr(
@@ -350,16 +342,16 @@ class Filesystem extends AbstractData
{
$pastes = array();
$firstLevel = array_filter(
scandir(self::$_path),
'self::_isFirstLevelDir'
scandir($this->_path),
'PrivateBin\Data\Filesystem::_isFirstLevelDir'
);
if (count($firstLevel) > 0) {
// try at most 10 times the $batchsize pastes before giving up
for ($i = 0, $max = $batchsize * 10; $i < $max; ++$i) {
$firstKey = array_rand($firstLevel);
$secondLevel = array_filter(
scandir(self::$_path . DIRECTORY_SEPARATOR . $firstLevel[$firstKey]),
'self::_isSecondLevelDir'
scandir($this->_path . DIRECTORY_SEPARATOR . $firstLevel[$firstKey]),
'PrivateBin\Data\Filesystem::_isSecondLevelDir'
);
// skip this folder in the next checks if it is empty
@@ -369,7 +361,7 @@ class Filesystem extends AbstractData
}
$secondKey = array_rand($secondLevel);
$path = self::$_path . DIRECTORY_SEPARATOR .
$path = $this->_path . DIRECTORY_SEPARATOR .
$firstLevel[$firstKey] . DIRECTORY_SEPARATOR .
$secondLevel[$secondKey];
if (!is_dir($path)) {
@@ -412,6 +404,46 @@ class Filesystem extends AbstractData
return $pastes;
}
/**
* @inheritDoc
*/
public function getAllPastes()
{
$pastes = array();
$subdirs = scandir($this->_path);
if ($subdirs === false) {
dieerr('Unable to list directory ' . $this->_path);
}
$subdirs = preg_grep('/^[^.].$/', $subdirs);
foreach ($subdirs as $subdir) {
$subpath = $this->_path . DIRECTORY_SEPARATOR . $subdir;
$subsubdirs = scandir($subpath);
if ($subsubdirs === false) {
dieerr('Unable to list directory ' . $subpath);
}
$subsubdirs = preg_grep('/^[^.].$/', $subsubdirs);
foreach ($subsubdirs as $subsubdir) {
$subsubpath = $subpath . DIRECTORY_SEPARATOR . $subsubdir;
$files = scandir($subsubpath);
if ($files === false) {
dieerr('Unable to list directory ' . $subsubpath);
}
$files = preg_grep('/\.php$/', $files);
foreach ($files as $file) {
if (substr($file, 0, 4) === $subdir . $subsubdir) {
$pastes[] = substr($file, 0, strlen($file) - 4);
}
}
}
}
return $pastes;
}
/**
* Convert paste id to storage path.
*
@@ -423,13 +455,12 @@ class Filesystem extends AbstractData
* eg. input 'e3570978f9e4aa90' --> output 'data/e3/57/'
*
* @access private
* @static
* @param string $dataid
* @return string
*/
private static function _dataid2path($dataid)
private function _dataid2path($dataid)
{
return self::$_path . DIRECTORY_SEPARATOR .
return $this->_path . DIRECTORY_SEPARATOR .
substr($dataid, 0, 2) . DIRECTORY_SEPARATOR .
substr($dataid, 2, 2) . DIRECTORY_SEPARATOR;
}
@@ -440,13 +471,12 @@ class Filesystem extends AbstractData
* eg. input 'e3570978f9e4aa90' --> output 'data/e3/57/e3570978f9e4aa90.discussion/'
*
* @access private
* @static
* @param string $dataid
* @return string
*/
private static function _dataid2discussionpath($dataid)
private function _dataid2discussionpath($dataid)
{
return self::_dataid2path($dataid) . $dataid .
return $this->_dataid2path($dataid) . $dataid .
'.discussion' . DIRECTORY_SEPARATOR;
}
@@ -454,25 +484,23 @@ class Filesystem extends AbstractData
* Check that the given element is a valid first level directory.
*
* @access private
* @static
* @param string $element
* @return bool
*/
private static function _isFirstLevelDir($element)
private function _isFirstLevelDir($element)
{
return self::_isSecondLevelDir($element) &&
is_dir(self::$_path . DIRECTORY_SEPARATOR . $element);
return $this->_isSecondLevelDir($element) &&
is_dir($this->_path . DIRECTORY_SEPARATOR . $element);
}
/**
* Check that the given element is a valid second level directory.
*
* @access private
* @static
* @param string $element
* @return bool
*/
private static function _isSecondLevelDir($element)
private function _isSecondLevelDir($element)
{
return (bool) preg_match('/^[a-f0-9]{2}$/', $element);
}
@@ -481,15 +509,14 @@ class Filesystem extends AbstractData
* store the data
*
* @access public
* @static
* @param string $filename
* @param array $data
* @return bool
*/
private static function _store($filename, array $data)
private function _store($filename, array $data)
{
try {
return self::_storeString(
return $this->_storeString(
$filename,
self::PROTECTION_LINE . PHP_EOL . Json::encode($data)
);
@@ -502,20 +529,19 @@ class Filesystem extends AbstractData
* store a string
*
* @access public
* @static
* @param string $filename
* @param string $data
* @return bool
*/
private static function _storeString($filename, $data)
private function _storeString($filename, $data)
{
// Create storage directory if it does not exist.
if (!is_dir(self::$_path)) {
if (!@mkdir(self::$_path, 0700)) {
if (!is_dir($this->_path)) {
if (!@mkdir($this->_path, 0700)) {
return false;
}
}
$file = self::$_path . DIRECTORY_SEPARATOR . '.htaccess';
$file = $this->_path . DIRECTORY_SEPARATOR . '.htaccess';
if (!is_file($file)) {
$writtenBytes = 0;
if ($fileCreated = @touch($file)) {
@@ -553,12 +579,11 @@ class Filesystem extends AbstractData
* rename a file, prepending the protection line at the beginning
*
* @access public
* @static
* @param string $srcFile
* @param string $destFile
* @return void
*/
private static function _prependRename($srcFile, $destFile)
private function _prependRename($srcFile, $destFile)
{
// don't overwrite already converted file
if (!is_readable($destFile)) {

View File

@@ -14,45 +14,43 @@ class GoogleCloudStorage extends AbstractData
* GCS client
*
* @access private
* @static
* @var StorageClient
*/
private static $_client = null;
private $_client = null;
/**
* GCS bucket
*
* @access private
* @static
* @var Bucket
*/
private static $_bucket = null;
private $_bucket = null;
/**
* object prefix
*
* @access private
* @static
* @var string
*/
private static $_prefix = 'pastes';
private $_prefix = 'pastes';
/**
* returns a Google Cloud Storage data backend.
* bucket acl type
*
* @access private
* @var bool
*/
private $_uniformacl = false;
/**
* instantiantes a new Google Cloud Storage data backend.
*
* @access public
* @static
* @param array $options
* @return GoogleCloudStorage
* @return
*/
public static function getInstance(array $options)
public function __construct(array $options)
{
// if needed initialize the singleton
if (!(self::$_instance instanceof self)) {
self::$_instance = new self;
}
$bucket = null;
if (getenv('PRIVATEBIN_GCS_BUCKET')) {
$bucket = getenv('PRIVATEBIN_GCS_BUCKET');
}
@@ -60,21 +58,20 @@ class GoogleCloudStorage extends AbstractData
$bucket = $options['bucket'];
}
if (is_array($options) && array_key_exists('prefix', $options)) {
self::$_prefix = $options['prefix'];
$this->_prefix = $options['prefix'];
}
if (is_array($options) && array_key_exists('uniformacl', $options)) {
$this->_uniformacl = $options['uniformacl'];
}
if (empty(self::$_client)) {
self::$_client = class_exists('StorageClientStub', false) ?
new \StorageClientStub(array()) :
new StorageClient(array('suppressKeyFileNotice' => true));
}
self::$_bucket = self::$_client->bucket($bucket);
return self::$_instance;
$this->_client = class_exists('StorageClientStub', false) ?
new \StorageClientStub(array()) :
new StorageClient(array('suppressKeyFileNotice' => true));
$this->_bucket = $this->_client->bucket($bucket);
}
/**
* returns the google storage object key for $pasteid in self::$_bucket.
* returns the google storage object key for $pasteid in $this->_bucket.
*
* @access private
* @param $pasteid string to get the key for
@@ -82,14 +79,14 @@ class GoogleCloudStorage extends AbstractData
*/
private function _getKey($pasteid)
{
if (self::$_prefix != '') {
return self::$_prefix . '/' . $pasteid;
if ($this->_prefix != '') {
return $this->_prefix . '/' . $pasteid;
}
return $pasteid;
}
/**
* Uploads the payload in the self::$_bucket under the specified key.
* Uploads the payload in the $this->_bucket under the specified key.
* The entire payload is stored as a JSON document. The metadata is replicated
* as the GCS object's metadata except for the fields attachment, attachmentname
* and salt.
@@ -106,17 +103,20 @@ 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 (!$this->_uniformacl) {
$data['predefinedAcl'] = 'private';
}
$this->_bucket->upload(Json::encode($payload), $data);
} catch (Exception $e) {
error_log('failed to upload ' . $key . ' to ' . self::$_bucket->name() . ', ' .
error_log('failed to upload ' . $key . ' to ' . $this->_bucket->name() . ', ' .
trim(preg_replace('/\s\s+/', ' ', $e->getMessage())));
return false;
}
@@ -141,13 +141,13 @@ class GoogleCloudStorage extends AbstractData
public function read($pasteid)
{
try {
$o = self::$_bucket->object($this->_getKey($pasteid));
$o = $this->_bucket->object($this->_getKey($pasteid));
$data = $o->downloadAsString();
return Json::decode($data);
} catch (NotFoundException $e) {
return false;
} catch (Exception $e) {
error_log('failed to read ' . $pasteid . ' from ' . self::$_bucket->name() . ', ' .
error_log('failed to read ' . $pasteid . ' from ' . $this->_bucket->name() . ', ' .
trim(preg_replace('/\s\s+/', ' ', $e->getMessage())));
return false;
}
@@ -161,9 +161,9 @@ class GoogleCloudStorage extends AbstractData
$name = $this->_getKey($pasteid);
try {
foreach (self::$_bucket->objects(array('prefix' => $name . '/discussion/')) as $comment) {
foreach ($this->_bucket->objects(array('prefix' => $name . '/discussion/')) as $comment) {
try {
self::$_bucket->object($comment->name())->delete();
$this->_bucket->object($comment->name())->delete();
} catch (NotFoundException $e) {
// ignore if already deleted.
}
@@ -173,7 +173,7 @@ class GoogleCloudStorage extends AbstractData
}
try {
self::$_bucket->object($name)->delete();
$this->_bucket->object($name)->delete();
} catch (NotFoundException $e) {
// ignore if already deleted
}
@@ -184,7 +184,7 @@ class GoogleCloudStorage extends AbstractData
*/
public function exists($pasteid)
{
$o = self::$_bucket->object($this->_getKey($pasteid));
$o = $this->_bucket->object($this->_getKey($pasteid));
return $o->exists();
}
@@ -208,8 +208,8 @@ class GoogleCloudStorage extends AbstractData
$comments = array();
$prefix = $this->_getKey($pasteid) . '/discussion/';
try {
foreach (self::$_bucket->objects(array('prefix' => $prefix)) as $key) {
$comment = JSON::decode(self::$_bucket->object($key->name())->downloadAsString());
foreach ($this->_bucket->objects(array('prefix' => $prefix)) as $key) {
$comment = JSON::decode($this->_bucket->object($key->name())->downloadAsString());
$comment['id'] = basename($key->name());
$slot = $this->getOpenSlot($comments, (int) $comment['meta']['created']);
$comments[$slot] = $comment;
@@ -226,7 +226,7 @@ class GoogleCloudStorage extends AbstractData
public function existsComment($pasteid, $parentid, $commentid)
{
$name = $this->_getKey($pasteid) . '/discussion/' . $parentid . '/' . $commentid;
$o = self::$_bucket->object($name);
$o = $this->_bucket->object($name);
return $o->exists();
}
@@ -237,7 +237,7 @@ class GoogleCloudStorage extends AbstractData
{
$path = 'config/' . $namespace;
try {
foreach (self::$_bucket->objects(array('prefix' => $path)) as $object) {
foreach ($this->_bucket->objects(array('prefix' => $path)) as $object) {
$name = $object->name();
if (strlen($name) > strlen($path) && substr($name, strlen($path), 1) !== '/') {
continue;
@@ -277,17 +277,20 @@ 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 (!$this->_uniformacl) {
$data['predefinedAcl'] = 'private';
}
$this->_bucket->upload($value, $data);
} catch (Exception $e) {
error_log('failed to set key ' . $key . ' to ' . self::$_bucket->name() . ', ' .
error_log('failed to set key ' . $key . ' to ' . $this->_bucket->name() . ', ' .
trim(preg_replace('/\s\s+/', ' ', $e->getMessage())));
return false;
}
@@ -305,7 +308,7 @@ class GoogleCloudStorage extends AbstractData
$key = 'config/' . $namespace . '/' . $key;
}
try {
$o = self::$_bucket->object($key);
$o = $this->_bucket->object($key);
return $o->downloadAsString();
} catch (NotFoundException $e) {
return '';
@@ -320,12 +323,12 @@ class GoogleCloudStorage extends AbstractData
$expired = array();
$now = time();
$prefix = self::$_prefix;
$prefix = $this->_prefix;
if ($prefix != '') {
$prefix .= '/';
}
try {
foreach (self::$_bucket->objects(array('prefix' => $prefix)) as $object) {
foreach ($this->_bucket->objects(array('prefix' => $prefix)) as $object) {
$metadata = $object->info()['metadata'];
if ($metadata != null && array_key_exists('expire_date', $metadata)) {
$expire_at = intval($metadata['expire_date']);
@@ -343,4 +346,28 @@ class GoogleCloudStorage extends AbstractData
}
return $expired;
}
/**
* @inheritDoc
*/
public function getAllPastes()
{
$pastes = array();
$prefix = $this->_prefix;
if ($prefix != '') {
$prefix .= '/';
}
try {
foreach ($this->_bucket->objects(array('prefix' => $prefix)) as $object) {
$candidate = substr($object->name(), strlen($prefix));
if (strpos($candidate, '/') === false) {
$pastes[] = $candidate;
}
}
} catch (NotFoundException $e) {
// no objects in the bucket yet
}
return $pastes;
}
}

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

@@ -0,0 +1,473 @@
<?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
* @var S3Client
*/
private $_client = null;
/**
* S3 client options
*
* @access private
* @var array
*/
private $_options = array();
/**
* S3 bucket
*
* @access private
* @var string
*/
private $_bucket = null;
/**
* S3 prefix for all PrivateBin data in this bucket
*
* @access private
* @var string
*/
private $_prefix = '';
/**
* instantiates a new S3 data backend.
*
* @access public
* @param array $options
* @return
*/
public function __construct(array $options)
{
$this->_options['credentials'] = array();
if (is_array($options) && array_key_exists('region', $options)) {
$this->_options['region'] = $options['region'];
}
if (is_array($options) && array_key_exists('version', $options)) {
$this->_options['version'] = $options['version'];
}
if (is_array($options) && array_key_exists('endpoint', $options)) {
$this->_options['endpoint'] = $options['endpoint'];
}
if (is_array($options) && array_key_exists('accesskey', $options)) {
$this->_options['credentials']['key'] = $options['accesskey'];
}
if (is_array($options) && array_key_exists('secretkey', $options)) {
$this->_options['credentials']['secret'] = $options['secretkey'];
}
if (is_array($options) && array_key_exists('use_path_style_endpoint', $options)) {
$this->_options['use_path_style_endpoint'] = filter_var($options['use_path_style_endpoint'], FILTER_VALIDATE_BOOLEAN);
}
if (is_array($options) && array_key_exists('bucket', $options)) {
$this->_bucket = $options['bucket'];
}
if (is_array($options) && array_key_exists('prefix', $options)) {
$this->_prefix = $options['prefix'];
}
$this->_client = new S3Client($this->_options);
}
/**
* 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' => $this->_bucket,
'Prefix' => $prefix,
);
do {
$objectsListResponse = $this->_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 $this->_bucket.
*
* @access private
* @param $pasteid string to get the key for
* @return string
*/
private function _getKey($pasteid)
{
if ($this->_prefix != '') {
return $this->_prefix . '/' . $pasteid;
}
return $pasteid;
}
/**
* Uploads the payload in the $this->_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 {
$this->_client->putObject(array(
'Bucket' => $this->_bucket,
'Key' => $key,
'Body' => Json::encode($payload),
'ContentType' => 'application/json',
'Metadata' => $metadata,
));
} catch (S3Exception $e) {
error_log('failed to upload ' . $key . ' to ' . $this->_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 = $this->_client->getObject(array(
'Bucket' => $this->_bucket,
'Key' => $this->_getKey($pasteid),
));
$data = $object['Body']->getContents();
return Json::decode($data);
} catch (S3Exception $e) {
error_log('failed to read ' . $pasteid . ' from ' . $this->_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 {
$this->_client->deleteObject(array(
'Bucket' => $this->_bucket,
'Key' => $comment['Key'],
));
} catch (S3Exception $e) {
// ignore if already deleted.
}
}
} catch (S3Exception $e) {
// there are no discussions associated with the paste
}
try {
$this->_client->deleteObject(array(
'Bucket' => $this->_bucket,
'Key' => $name,
));
} catch (S3Exception $e) {
// ignore if already deleted
}
}
/**
* @inheritDoc
*/
public function exists($pasteid)
{
return $this->_client->doesObjectExistV2($this->_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 = $this->_client->getObject(array(
'Bucket' => $this->_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 $this->_client->doesObjectExistV2($this->_bucket, $name);
}
/**
* @inheritDoc
*/
public function purgeValues($namespace, $time)
{
$path = $this->_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 = $this->_client->headObject(array(
'Bucket' => $this->_bucket,
'Key' => $name,
));
if ($head->get('Metadata') != null && array_key_exists('value', $head->get('Metadata'))) {
$value = $head->get('Metadata')['value'];
if (is_numeric($value) && intval($value) < $time) {
try {
$this->_client->deleteObject(array(
'Bucket' => $this->_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 = $this->_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 {
$this->_client->putObject(array(
'Bucket' => $this->_bucket,
'Key' => $key,
'Body' => $value,
'ContentType' => 'application/json',
'Metadata' => $metadata,
));
} catch (S3Exception $e) {
error_log('failed to set key ' . $key . ' to ' . $this->_bucket . ', ' .
trim(preg_replace('/\s\s+/', ' ', $e->getMessage())));
return false;
}
return true;
}
/**
* @inheritDoc
*/
public function getValue($namespace, $key = '')
{
$prefix = $this->_prefix;
if ($prefix != '') {
$prefix .= '/';
}
if ($key === '') {
$key = $prefix . 'config/' . $namespace;
} else {
$key = $prefix . 'config/' . $namespace . '/' . $key;
}
try {
$object = $this->_client->getObject(array(
'Bucket' => $this->_bucket,
'Key' => $key,
));
return $object['Body']->getContents();
} catch (S3Exception $e) {
return '';
}
}
/**
* @inheritDoc
*/
protected function _getExpiredPastes($batchsize)
{
$expired = array();
$now = time();
$prefix = $this->_prefix;
if ($prefix != '') {
$prefix .= '/';
}
try {
foreach ($this->_listAllObjects($prefix) as $object) {
$head = $this->_client->headObject(array(
'Bucket' => $this->_bucket,
'Key' => $object['Key'],
));
if ($head->get('Metadata') != null && array_key_exists('expire_date', $head->get('Metadata'))) {
$expire_at = intval($head->get('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;
}
/**
* @inheritDoc
*/
public function getAllPastes()
{
$pastes = array();
$prefix = $this->_prefix;
if ($prefix != '') {
$prefix .= '/';
}
try {
foreach ($this->_listAllObjects($prefix) as $object) {
$candidate = substr($object['Key'], strlen($prefix));
if (strpos($candidate, '/') === false) {
$pastes[] = $candidate;
}
}
} catch (S3Exception $e) {
// no objects in the bucket yet
}
return $pastes;
}
}

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,7 +316,8 @@ 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':
@@ -327,19 +328,20 @@ class I18n
return $n === 1 ? 0 : ($n === 2 ? 1 : (($n < 0 || $n > 10) && ($n % 10 === 0) ? 2 : 3));
case 'id':
case 'jbo':
case 'th':
return 0;
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

@@ -81,10 +81,8 @@ class Model
public function getStore()
{
if ($this->_store === null) {
$this->_store = forward_static_call(
'PrivateBin\\Data\\' . $this->_conf->getKey('class', 'model') . '::getInstance',
$this->_conf->getSection('model_options')
);
$class = 'PrivateBin\\Data\\' . $this->_conf->getKey('class', 'model');
$this->_store = new $class($this->_conf->getSection('model_options'));
}
return $this->_store;
}

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;
@@ -164,7 +165,17 @@ class Comment extends AbstractModel
if ($icon != 'none') {
$pngdata = '';
$hmac = TrafficLimiter::getHash();
if ($icon == 'identicon') {
if ($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 == 'identicon') {
$identicon = new Identicon();
$pngdata = $identicon->getImageDataUri($hmac, 16);
} elseif ($icon == 'vizhash') {

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-CbXFfxyGfdXnwMumt2sMdPs/Pxnk3Ahkpw8JulqRIGYZPq7O/hM0YT/xjHG9IcaigdKC0aL42uzlxoBXLI11gw==" crossorigin="anonymous"></script>
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-0he/s7UnjLf2pxLH+EIGImaUBhVzpngrL5PId/QB2FcstucrPUPOhQpqJcGvtmVPyuXckiktEwwrz4Sn4Mi3Tw==" 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-CbXFfxyGfdXnwMumt2sMdPs/Pxnk3Ahkpw8JulqRIGYZPq7O/hM0YT/xjHG9IcaigdKC0aL42uzlxoBXLI11gw==" crossorigin="anonymous"></script>
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-0he/s7UnjLf2pxLH+EIGImaUBhVzpngrL5PId/QB2FcstucrPUPOhQpqJcGvtmVPyuXckiktEwwrz4Sn4Mi3Tw==" 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

@@ -32,9 +32,9 @@ Helper::updateSubresourceIntegrity();
*/
class StorageClientStub extends StorageClient
{
private $_config = null;
private $_connection = null;
private $_buckets = array();
private $_config = null;
private $_connection = null;
private static $_buckets = array();
public function __construct(array $config = array())
{
@@ -44,11 +44,11 @@ class StorageClientStub extends StorageClient
public function bucket($name, $userProject = false)
{
if (!key_exists($name, $this->_buckets)) {
if (!key_exists($name, self::$_buckets)) {
$b = new BucketStub($this->_connection, $name, array(), $this);
$this->_buckets[$name] = $b;
self::$_buckets[$name] = $b;
}
return $this->_buckets[$name];
return self::$_buckets[$name];
}
/**
@@ -56,8 +56,8 @@ class StorageClientStub extends StorageClient
*/
public function deleteBucket($name)
{
if (key_exists($name, $this->_buckets)) {
unset($this->_buckets[$name]);
if (key_exists($name, self::$_buckets)) {
unset(self::$_buckets[$name]);
} else {
throw new NotFoundException();
}
@@ -110,11 +110,11 @@ class StorageClientStub extends StorageClient
public function createBucket($name, array $options = array())
{
if (key_exists($name, $this->_buckets)) {
if (key_exists($name, self::$_buckets)) {
throw new BadRequestException('already exists');
}
$b = new BucketStub($this->_connection, $name, array(), $this);
$this->_buckets[$name] = $b;
self::$_buckets[$name] = $b;
return $b;
}
}

View File

@@ -427,7 +427,7 @@ class ConfigurationCombinationsTest extends PHPUnit_Framework_TestCase
/* Setup Routine */
Helper::confBackup();
$this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data';
$this->_model = Filesystem::getInstance(array('dir' => $this->_path));
$this->_model = new Filesystem(array('dir' => $this->_path));
$this->reset();
}

View File

@@ -16,7 +16,7 @@ class ControllerTest extends PHPUnit_Framework_TestCase
{
/* Setup Routine */
$this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data';
$this->_data = Filesystem::getInstance(array('dir' => $this->_path));
$this->_data = new Filesystem(array('dir' => $this->_path));
ServerSalt::setStore($this->_data);
TrafficLimiter::setStore($this->_data);
$this->reset();
@@ -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

@@ -25,7 +25,7 @@ class ControllerWithDbTest extends ControllerTest
mkdir($this->_path);
}
$this->_options['dsn'] = 'sqlite:' . $this->_path . DIRECTORY_SEPARATOR . 'tst.sq3';
$this->_data = Database::getInstance($this->_options);
$this->_data = new Database($this->_options);
ServerSalt::setStore($this->_data);
TrafficLimiter::setStore($this->_data);
$this->reset();

View File

@@ -39,7 +39,7 @@ class ControllerWithGcsTest extends ControllerTest
'bucket' => self::$_bucket->name(),
'prefix' => 'pastes',
);
$this->_data = GoogleCloudStorage::getInstance($this->_options);
$this->_data = new GoogleCloudStorage($this->_options);
ServerSalt::setStore($this->_data);
TrafficLimiter::setStore($this->_data);
$this->reset();

View File

@@ -22,7 +22,7 @@ class DatabaseTest extends PHPUnit_Framework_TestCase
{
/* Setup Routine */
$this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data';
$this->_model = Database::getInstance($this->_options);
$this->_model = new Database($this->_options);
}
public function tearDown()
@@ -35,7 +35,7 @@ class DatabaseTest extends PHPUnit_Framework_TestCase
public function testSaltMigration()
{
ServerSalt::setStore(Filesystem::getInstance(array('dir' => 'data')));
ServerSalt::setStore(new Filesystem(array('dir' => 'data')));
$salt = ServerSalt::get();
$file = 'data' . DIRECTORY_SEPARATOR . 'salt.php';
$this->assertFileExists($file, 'ServerSalt got initialized and stored on disk');
@@ -141,7 +141,7 @@ class DatabaseTest extends PHPUnit_Framework_TestCase
*/
public function testGetIbmInstance()
{
Database::getInstance(array(
new Database(array(
'dsn' => 'ibm:', 'usr' => null, 'pwd' => null,
'opt' => array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION),
));
@@ -152,7 +152,7 @@ class DatabaseTest extends PHPUnit_Framework_TestCase
*/
public function testGetInformixInstance()
{
Database::getInstance(array(
new Database(array(
'dsn' => 'informix:', 'usr' => null, 'pwd' => null,
'opt' => array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION),
));
@@ -163,7 +163,7 @@ class DatabaseTest extends PHPUnit_Framework_TestCase
*/
public function testGetMssqlInstance()
{
Database::getInstance(array(
new Database(array(
'dsn' => 'mssql:', 'usr' => null, 'pwd' => null,
'opt' => array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION),
));
@@ -174,7 +174,7 @@ class DatabaseTest extends PHPUnit_Framework_TestCase
*/
public function testGetMysqlInstance()
{
Database::getInstance(array(
new Database(array(
'dsn' => 'mysql:', 'usr' => null, 'pwd' => null,
'opt' => array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION),
));
@@ -185,7 +185,7 @@ class DatabaseTest extends PHPUnit_Framework_TestCase
*/
public function testGetOciInstance()
{
Database::getInstance(array(
new Database(array(
'dsn' => 'oci:', 'usr' => null, 'pwd' => null,
'opt' => array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION),
));
@@ -196,7 +196,7 @@ class DatabaseTest extends PHPUnit_Framework_TestCase
*/
public function testGetPgsqlInstance()
{
Database::getInstance(array(
new Database(array(
'dsn' => 'pgsql:', 'usr' => null, 'pwd' => null,
'opt' => array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION),
));
@@ -208,7 +208,7 @@ class DatabaseTest extends PHPUnit_Framework_TestCase
*/
public function testGetFooInstance()
{
Database::getInstance(array(
new Database(array(
'dsn' => 'foo:', 'usr' => null, 'pwd' => null, 'opt' => null,
));
}
@@ -221,7 +221,7 @@ class DatabaseTest extends PHPUnit_Framework_TestCase
{
$options = $this->_options;
unset($options['dsn']);
Database::getInstance($options);
new Database($options);
}
/**
@@ -232,7 +232,7 @@ class DatabaseTest extends PHPUnit_Framework_TestCase
{
$options = $this->_options;
unset($options['usr']);
Database::getInstance($options);
new Database($options);
}
/**
@@ -243,7 +243,7 @@ class DatabaseTest extends PHPUnit_Framework_TestCase
{
$options = $this->_options;
unset($options['pwd']);
Database::getInstance($options);
new Database($options);
}
/**
@@ -254,7 +254,7 @@ class DatabaseTest extends PHPUnit_Framework_TestCase
{
$options = $this->_options;
unset($options['opt']);
Database::getInstance($options);
new Database($options);
}
public function testOldAttachments()
@@ -266,7 +266,7 @@ class DatabaseTest extends PHPUnit_Framework_TestCase
}
$this->_options['dsn'] = 'sqlite:' . $path;
$this->_options['tbl'] = 'bar_';
$model = Database::getInstance($this->_options);
$model = new Database($this->_options);
$original = $paste = Helper::getPasteWithAttachment(1, array('expire_date' => 1344803344));
$meta = $paste['meta'];
@@ -311,7 +311,7 @@ class DatabaseTest extends PHPUnit_Framework_TestCase
}
$this->_options['dsn'] = 'sqlite:' . $path;
$this->_options['tbl'] = 'baz_';
$model = Database::getInstance($this->_options);
$model = new Database($this->_options);
$paste = Helper::getPaste(1, array('expire_date' => 1344803344));
unset($paste['meta']['formatter'], $paste['meta']['opendiscussion'], $paste['meta']['salt']);
$model->delete(Helper::getPasteId());
@@ -378,7 +378,7 @@ class DatabaseTest extends PHPUnit_Framework_TestCase
'vizhash BLOB, ' .
'postdate INT );'
);
$this->assertInstanceOf('PrivateBin\\Data\\Database', Database::getInstance($this->_options));
$this->assertInstanceOf('PrivateBin\\Data\\Database', new Database($this->_options));
// check if version number was upgraded in created configuration table
$statement = $db->prepare('SELECT value FROM foo_config WHERE id LIKE ?');

View File

@@ -15,7 +15,7 @@ class FilesystemTest extends PHPUnit_Framework_TestCase
/* Setup Routine */
$this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data';
$this->_invalidPath = $this->_path . DIRECTORY_SEPARATOR . 'bar';
$this->_model = Filesystem::getInstance(array('dir' => $this->_path));
$this->_model = new Filesystem(array('dir' => $this->_path));
if (!is_dir($this->_path)) {
mkdir($this->_path);
}

View File

@@ -26,7 +26,7 @@ class GoogleCloudStorageTest extends PHPUnit_Framework_TestCase
public function setUp()
{
ini_set('error_log', stream_get_meta_data(tmpfile())['uri']);
$this->_model = GoogleCloudStorage::getInstance(array(
$this->_model = new GoogleCloudStorage(array(
'bucket' => self::$_bucket->name(),
'prefix' => 'pastes',
));

View File

@@ -15,7 +15,10 @@ class JsonApiTest extends PHPUnit_Framework_TestCase
{
/* Setup Routine */
$this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data';
$this->_model = Filesystem::getInstance(array('dir' => $this->_path));
if (!is_dir($this->_path)) {
mkdir($this->_path);
}
$this->_model = new Filesystem(array('dir' => $this->_path));
ServerSalt::setStore($this->_model);
$_POST = array();
@@ -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'; // missing slash gets added by Configuration constructor
$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

@@ -1,6 +1,6 @@
<?php
use Identicon\Identicon;
use Jdenticon\Identicon;
use PrivateBin\Configuration;
use PrivateBin\Data\Database;
use PrivateBin\Model;
@@ -38,7 +38,7 @@ class ModelTest extends PHPUnit_Framework_TestCase
);
Helper::confBackup();
Helper::createIniFile(CONF, $options);
ServerSalt::setStore(Database::getInstance($options['model_options']));
ServerSalt::setStore(new Database($options['model_options']));
$this->_conf = new Configuration;
$this->_model = new Model($this->_conf);
$_SERVER['REMOTE_ADDR'] = '::1';
@@ -156,10 +156,10 @@ class ModelTest extends PHPUnit_Framework_TestCase
public function testCommentDefaults()
{
$class = 'PrivateBin\\Data\\' . $this->_conf->getKey('class', 'model');
$comment = new Comment(
$this->_conf,
forward_static_call(
'PrivateBin\\Data\\' . $this->_conf->getKey('class', 'model') . '::getInstance',
new $class(
$this->_conf->getSection('model_options')
)
);
@@ -314,8 +314,15 @@ class ModelTest extends PHPUnit_Framework_TestCase
$comment->get();
$comment->store();
$identicon = new Identicon();
$pngdata = $identicon->getImageDataUri(TrafficLimiter::getHash(), 16);
$identicon = new Identicon(array(
'hash' => TrafficLimiter::getHash(),
'size' => 16,
'style' => array(
'backgroundColor' => '#fff0', // fully transparent, for dark mode
'padding' => 0,
),
));
$pngdata = $identicon->getImageDataUri('png');
$comment = current($this->_model->getPaste(Helper::getPasteId())->get()['comments']);
$this->assertEquals($pngdata, $comment['meta']['icon'], 'icon gets set');
}
@@ -445,7 +452,7 @@ class ModelTest extends PHPUnit_Framework_TestCase
public function testPurge()
{
$conf = new Configuration;
$store = Database::getInstance($conf->getSection('model_options'));
$store = new Database($conf->getSection('model_options'));
$store->delete(Helper::getPasteId());
$expired = Helper::getPaste(2, array('expire_date' => 1344803344));
$paste = Helper::getPaste(2, array('expire_date' => time() + 3600));

View File

@@ -15,7 +15,7 @@ class PurgeLimiterTest extends PHPUnit_Framework_TestCase
mkdir($this->_path);
}
PurgeLimiter::setStore(
Filesystem::getInstance(array('dir' => $this->_path))
new Filesystem(array('dir' => $this->_path))
);
}

View File

@@ -21,7 +21,7 @@ class ServerSaltTest extends PHPUnit_Framework_TestCase
mkdir($this->_path);
}
ServerSalt::setStore(
Filesystem::getInstance(array('dir' => $this->_path))
new Filesystem(array('dir' => $this->_path))
);
$this->_otherPath = $this->_path . DIRECTORY_SEPARATOR . 'foo';
@@ -44,17 +44,17 @@ class ServerSaltTest extends PHPUnit_Framework_TestCase
{
// generating new salt
ServerSalt::setStore(
Filesystem::getInstance(array('dir' => $this->_path))
new Filesystem(array('dir' => $this->_path))
);
$salt = ServerSalt::get();
// try setting a different path and resetting it
ServerSalt::setStore(
Filesystem::getInstance(array('dir' => $this->_otherPath))
new Filesystem(array('dir' => $this->_otherPath))
);
$this->assertNotEquals($salt, ServerSalt::get());
ServerSalt::setStore(
Filesystem::getInstance(array('dir' => $this->_path))
new Filesystem(array('dir' => $this->_path))
);
$this->assertEquals($salt, ServerSalt::get());
}
@@ -63,7 +63,7 @@ class ServerSaltTest extends PHPUnit_Framework_TestCase
{
// try setting an invalid path
chmod($this->_invalidPath, 0000);
$store = Filesystem::getInstance(array('dir' => $this->_invalidPath));
$store = new Filesystem(array('dir' => $this->_invalidPath));
ServerSalt::setStore($store);
$salt = ServerSalt::get();
ServerSalt::setStore($store);
@@ -76,7 +76,7 @@ class ServerSaltTest extends PHPUnit_Framework_TestCase
chmod($this->_invalidPath, 0700);
file_put_contents($this->_invalidFile, '');
chmod($this->_invalidFile, 0000);
$store = Filesystem::getInstance(array('dir' => $this->_invalidPath));
$store = new Filesystem(array('dir' => $this->_invalidPath));
ServerSalt::setStore($store);
$salt = ServerSalt::get();
ServerSalt::setStore($store);
@@ -93,7 +93,7 @@ class ServerSaltTest extends PHPUnit_Framework_TestCase
}
file_put_contents($this->_invalidPath . DIRECTORY_SEPARATOR . '.htaccess', '');
chmod($this->_invalidPath, 0500);
$store = Filesystem::getInstance(array('dir' => $this->_invalidPath));
$store = new Filesystem(array('dir' => $this->_invalidPath));
ServerSalt::setStore($store);
$salt = ServerSalt::get();
ServerSalt::setStore($store);
@@ -105,9 +105,9 @@ class ServerSaltTest extends PHPUnit_Framework_TestCase
// try creating an invalid path
chmod($this->_invalidPath, 0000);
ServerSalt::setStore(
Filesystem::getInstance(array('dir' => $this->_invalidPath . DIRECTORY_SEPARATOR . 'baz'))
new Filesystem(array('dir' => $this->_invalidPath . DIRECTORY_SEPARATOR . 'baz'))
);
$store = Filesystem::getInstance(array('dir' => $this->_invalidPath));
$store = new Filesystem(array('dir' => $this->_invalidPath));
ServerSalt::setStore($store);
$salt = ServerSalt::get();
ServerSalt::setStore($store);

View File

@@ -12,7 +12,7 @@ class TrafficLimiterTest extends PHPUnit_Framework_TestCase
{
/* Setup Routine */
$this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'trafficlimit';
$store = Filesystem::getInstance(array('dir' => $this->_path));
$store = new Filesystem(array('dir' => $this->_path));
ServerSalt::setStore($store);
TrafficLimiter::setStore($store);
}

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,

View File

@@ -18,7 +18,7 @@ class Vizhash16x16Test extends PHPUnit_Framework_TestCase
mkdir($this->_path);
}
$this->_file = $this->_path . DIRECTORY_SEPARATOR . 'vizhash.png';
ServerSalt::setStore(Filesystem::getInstance(array('dir' => $this->_path)));
ServerSalt::setStore(new Filesystem(array('dir' => $this->_path)));
}
public function tearDown()

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;
}
}

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