101 Commits

Author SHA1 Message Date
El RIDO
0288d94a68 regenerate composer.lock with PHP 7.2 for Scrutinizer 2022-11-06 09:39:29 +01:00
El RIDO
94aab6d64b apply StyleCI patch 2022-11-06 09:12:42 +01:00
El RIDO
a799351db3 re-use logic from _getExpiredPastes()
Scrutinizer pointed out that the dieerr() function isn't available in this
class. Code does work when invoked by migrate script, but this way it would
also work in other contexts.
2022-11-06 09:09:50 +01:00
El RIDO
8ede84f000 disable test when PHP < 7.2
It started failing after we removed the cache from the Database class,
but the behaviour is still correct (exception when something goes wrong
during comment storing).
2022-11-06 08:21:32 +01:00
El RIDO
6caf1143df add a verification step for investigating failures in tests below PHP 7.2 2022-11-06 08:13:07 +01:00
El RIDO
669c98550c add a version check, the third argument in getopt requires PHP >= 7.1 2022-11-06 08:05:41 +01:00
El RIDO
14075cea78 trying a different approach to get that exception 70 triggered reliably 2022-11-06 07:53:43 +01:00
El RIDO
bde7a19971 apply StyleCI patch 2022-11-06 07:43:19 +01:00
El RIDO
5195adfdb9 simple migration test 2022-11-06 07:41:05 +01:00
El RIDO
3028c22c20 be more efficient 2022-11-06 07:40:39 +01:00
El RIDO
f4eed668e7 seems impacted by removal of cache, let's see if we can adjust the test 2022-11-05 10:00:12 +01:00
El RIDO
9a1f3aeca5 update Jdenticon library 2022-11-05 09:44:35 +01:00
El RIDO
07ad9ad0f4 typo, found by Scrutinizer 2022-11-05 09:37:24 +01:00
El RIDO
833cf93209 address Scrutinizer warning
> The variable $bucket does not seem to be defined for all execution paths leading up to this point.
2022-11-05 09:35:19 +01:00
El RIDO
62bb68344c move all scripts into one location
- standardize includes, namings
- made migrate executable
- updated ConfigurationCombinationsTest generator to work with current persistance API
2022-11-05 09:32:30 +01:00
El RIDO
8389c2a2d6 minor optimization, let the PDO driver do that for us 2022-11-05 08:46:42 +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
116 changed files with 7499 additions and 722 deletions

2
.gitattributes vendored
View File

@@ -1,3 +1,5 @@
bin/configuration-test-generator export-ignore
bin/icon-test export-ignore
doc/ export-ignore
tst/ export-ignore
img/browserstack.svg export-ignore

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

View File

@@ -1,8 +1,10 @@
# PrivateBin version history
* **1.4.1 (not yet released)**
* ADDED: script for data storage backend migrations (#1012)
* ADDED: Translations for Turkish, Slovak and Greek
* 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)

View File

@@ -31,6 +31,7 @@
* 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

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

View File

@@ -9,7 +9,9 @@
* DANGER: Too many options/settings and too high max iteration setting may trigger
* a fork bomb. Please save your work before executing this script.
*/
include 'Bootstrap.php';
define('PATH', dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR);
include PATH . 'tst' . DIRECTORY_SEPARATOR . 'Bootstrap.php';
$vd = array('view', 'delete');
$vcd = array('view', 'create', 'delete');
@@ -392,7 +394,7 @@ class ConfigurationTestGenerator
}
}
$code .= '}' . PHP_EOL;
file_put_contents('ConfigurationCombinationsTest.php', $code);
file_put_contents(dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'tst' . DIRECTORY_SEPARATOR . 'ConfigurationCombinationsTest.php', $code);
}
/**
@@ -427,7 +429,9 @@ 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));
ServerSalt::setStore($this->_model);
TrafficLimiter::setStore($this->_model);
$this->reset();
}

199
bin/migrate Executable file
View File

@@ -0,0 +1,199 @@
#!/usr/bin/env php
<?php
// change this, if your php files and data is outside of your webservers document root
define('PATH', dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR);
define('PUBLIC_PATH', __DIR__ . DIRECTORY_SEPARATOR);
require PATH . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
use PrivateBin\Configuration;
use PrivateBin\Model;
// third argument in getopt requires PHP >= 7.1
if (version_compare(PHP_VERSION, '7.1.0') < 0) {
dieerr('migrate requires php 7.1 or above to work. Sorry.');
}
$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:
migrate [--delete-after] [--delete-during] [-f] [-n] [-v] srcconfdir
[<dstconfdir>]
migrate [-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
@@ -229,18 +231,18 @@ dir = PATH "data"
;secretkey = "secret access key"
[yourls]
; don't mix this up with "urlshortener" config item:
; - when using a standard configuration, "urlshortener" will point to the YOURLS
; API, including access credentials, and will be part of the PrivateBin public
; web page (insecure!)
; - when using the parameters in this section ("signature" and "apiurl"),
; "urlshortener" will point to a fixed PrivateBin page
; ("$basepath/shortenviayourls?link=") and that URL will in turn call YOURLS
; server-side, using the URL from "apiurl" and the "access signature" from the
; "signature" parameters below.
; 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 = ""
; apiurl = "https://yourls.example.com/yourls-api.php"

View File

@@ -27,7 +27,8 @@
"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",

223
composer.lock generated
View File

@@ -4,8 +4,52 @@
"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"
],
"time": "2022-10-30T17:15:02+00:00"
},
{
"name": "mlocati/ip-lib",
"version": "1.18.0",
@@ -61,16 +105,6 @@
"range",
"subnet"
],
"funding": [
{
"url": "https://github.com/sponsors/mlocati",
"type": "github"
},
{
"url": "https://paypal.me/mlocati",
"type": "other"
}
],
"time": "2022-01-13T18:05:33+00:00"
},
{
@@ -179,29 +213,30 @@
"packages-dev": [
{
"name": "doctrine/instantiator",
"version": "1.4.0",
"version": "1.4.1",
"source": {
"type": "git",
"url": "https://github.com/doctrine/instantiator.git",
"reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b"
"reference": "10dcfce151b967d20fde1b34ae6640712c3891bc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b",
"reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b",
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc",
"reference": "10dcfce151b967d20fde1b34ae6640712c3891bc",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0"
},
"require-dev": {
"doctrine/coding-standard": "^8.0",
"doctrine/coding-standard": "^9",
"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"
"phpbench/phpbench": "^0.16 || ^1",
"phpstan/phpstan": "^1.4",
"phpstan/phpstan-phpunit": "^1",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
"vimeo/psalm": "^4.22"
},
"type": "library",
"autoload": {
@@ -226,46 +261,33 @@
"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"
"time": "2022-03-03T08:28:38+00:00"
},
{
"name": "myclabs/deep-copy",
"version": "1.10.2",
"version": "1.11.0",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
"reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220"
"reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220",
"reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614",
"reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0"
},
"replace": {
"myclabs/deep-copy": "self.version"
"conflict": {
"doctrine/collections": "<1.6.8",
"doctrine/common": "<2.13.3 || >=3,<3.2.2"
},
"require-dev": {
"doctrine/collections": "^1.0",
"doctrine/common": "^2.6",
"phpunit/phpunit": "^7.1"
"doctrine/collections": "^1.6.8",
"doctrine/common": "^2.13.3 || ^3.2.2",
"phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
},
"type": "library",
"autoload": {
@@ -288,13 +310,7 @@
"object",
"object graph"
],
"funding": [
{
"url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
"type": "tidelift"
}
],
"time": "2020-11-13T09:40:50+00:00"
"time": "2022-03-03T13:19:32+00:00"
},
{
"name": "phpdocumentor/reflection-common",
@@ -400,16 +416,16 @@
},
{
"name": "phpdocumentor/type-resolver",
"version": "1.6.0",
"version": "1.6.1",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/TypeResolver.git",
"reference": "93ebd0014cab80c4ea9f5e297ea48672f1b87706"
"reference": "77a32518733312af16a44300404e945338981de3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/93ebd0014cab80c4ea9f5e297ea48672f1b87706",
"reference": "93ebd0014cab80c4ea9f5e297ea48672f1b87706",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3",
"reference": "77a32518733312af16a44300404e945338981de3",
"shasum": ""
},
"require": {
@@ -442,7 +458,7 @@
}
],
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
"time": "2022-01-04T19:58:01+00:00"
"time": "2022-03-15T21:29:03+00:00"
},
{
"name": "phpspec/prophecy",
@@ -509,35 +525,35 @@
},
{
"name": "phpunit/php-code-coverage",
"version": "4.0.8",
"version": "4.0.6",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d"
"reference": "ca060f645beeddebedb1885c97bf163e93264c35"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef7b2f56815df854e66ceaee8ebe9393ae36a40d",
"reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca060f645beeddebedb1885c97bf163e93264c35",
"reference": "ca060f645beeddebedb1885c97bf163e93264c35",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-xmlwriter": "*",
"php": "^5.6 || ^7.0",
"phpunit/php-file-iterator": "^1.3",
"phpunit/php-text-template": "^1.2",
"phpunit/php-file-iterator": "~1.3",
"phpunit/php-text-template": "~1.2",
"phpunit/php-token-stream": "^1.4.2 || ^2.0",
"sebastian/code-unit-reverse-lookup": "^1.0",
"sebastian/code-unit-reverse-lookup": "~1.0",
"sebastian/environment": "^1.3.2 || ^2.0",
"sebastian/version": "^1.0 || ^2.0"
"sebastian/version": "~1.0|~2.0"
},
"require-dev": {
"ext-xdebug": "^2.1.4",
"phpunit/phpunit": "^5.7"
"ext-xdebug": ">=2.1.4",
"phpunit/phpunit": "^5.4"
},
"suggest": {
"ext-xdebug": "^2.5.1"
"ext-dom": "*",
"ext-xdebug": ">=2.4.0",
"ext-xmlwriter": "*"
},
"type": "library",
"extra": {
@@ -568,7 +584,7 @@
"testing",
"xunit"
],
"time": "2017-04-02T07:44:40+00:00"
"time": "2017-02-23T07:38:02+00:00"
},
{
"name": "phpunit/php-file-iterator",
@@ -942,12 +958,6 @@
],
"description": "Looks up which function or method a line of code belongs to",
"homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2020-11-30T08:15:22+00:00"
},
{
@@ -1420,16 +1430,16 @@
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.24.0",
"version": "v1.26.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "30885182c981ab175d4d034db0f6f469898070ab"
"reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4"
},
"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/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
"reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
"shasum": ""
},
"require": {
@@ -1444,7 +1454,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.23-dev"
"dev-main": "1.26-dev"
},
"thanks": {
"name": "symfony/polyfill",
@@ -1452,12 +1462,12 @@
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
},
"files": [
"bootstrap.php"
]
],
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@@ -1481,34 +1491,20 @@
"polyfill",
"portable"
],
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2021-10-20T20:35:02+00:00"
"time": "2022-05-24T11:49:31+00:00"
},
{
"name": "symfony/yaml",
"version": "v4.4.37",
"version": "v4.4.45",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "d7f637cc0f0cc14beb0984f2bb50da560b271311"
"reference": "aeccc4dc52a9e634f1d1eebeb21eacfdcff1053d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/d7f637cc0f0cc14beb0984f2bb50da560b271311",
"reference": "d7f637cc0f0cc14beb0984f2bb50da560b271311",
"url": "https://api.github.com/repos/symfony/yaml/zipball/aeccc4dc52a9e634f1d1eebeb21eacfdcff1053d",
"reference": "aeccc4dc52a9e634f1d1eebeb21eacfdcff1053d",
"shasum": ""
},
"require": {
@@ -1549,21 +1545,7 @@
],
"description": "Loads and dumps YAML files",
"homepage": "https://symfony.com",
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2022-01-24T20:11:01+00:00"
"time": "2022-08-02T15:47:23+00:00"
},
{
"name": "webmozart/assert",
@@ -1628,6 +1610,5 @@
"platform": {
"php": "^5.6.0 || ^7.0 || ^8.0"
},
"platform-dev": [],
"plugin-api-version": "1.1.0"
"platform-dev": []
}

View File

@@ -182,7 +182,7 @@
"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",

View File

@@ -182,7 +182,7 @@
"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",

View File

@@ -182,7 +182,7 @@
"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",

View File

@@ -182,12 +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.",
"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."
"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,7 +182,7 @@
"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",

View File

@@ -182,12 +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.",
"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."
"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

@@ -182,7 +182,7 @@
"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": "Αποθήκευση επικόλλησης",

View File

@@ -182,7 +182,7 @@
"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",

View File

@@ -182,7 +182,7 @@
"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\"",

View File

@@ -182,7 +182,7 @@
"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",

View File

@@ -182,7 +182,7 @@
"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",

View File

@@ -182,7 +182,7 @@
"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",

View File

@@ -182,7 +182,7 @@
"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",

View File

@@ -182,7 +182,7 @@
"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",

View File

@@ -182,7 +182,7 @@
"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",

View File

@@ -182,7 +182,7 @@
"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",

View File

@@ -182,7 +182,7 @@
"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",

View File

@@ -182,7 +182,7 @@
"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",

View File

@@ -182,7 +182,7 @@
"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",

View File

@@ -182,7 +182,7 @@
"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",

View File

@@ -182,7 +182,7 @@
"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",

View File

@@ -182,7 +182,7 @@
"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ą",

View File

@@ -182,7 +182,7 @@
"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",

View File

@@ -182,7 +182,7 @@
"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",

View File

@@ -182,7 +182,7 @@
"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",

View File

@@ -182,7 +182,7 @@
"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",

View File

@@ -182,7 +182,7 @@
"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",

View File

@@ -182,7 +182,7 @@
"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": "Сохранить запись",

View File

@@ -182,12 +182,12 @@
"Use Current Timezone": "Použiť aktuálne časové pásmo",
"Convert To UTC": "Previesť na UTC",
"Close": "Zavrieť",
"Encrypted note on PrivateBin": "Zašifrovaná poznámka na PrivateBin",
"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.": "Trying to shorten a URL that isn't pointing at our instance.",
"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,7 +182,7 @@
"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",

View File

@@ -182,7 +182,7 @@
"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",

View File

@@ -182,7 +182,7 @@
"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",

View File

@@ -182,7 +182,7 @@
"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.",
"Save paste": "Зберегти вставку",

View File

@@ -182,7 +182,7 @@
"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": "保存内容",

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

@@ -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 "dataid" FROM "' . $this->_sanitizeIdentifier('paste') .
'" WHERE "dataid" = ?', array($pasteid), true
);
} catch (Exception $e) {
return 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;
@@ -520,34 +486,37 @@ class Database extends AbstractData
*/
protected function _getExpiredPastes($batchsize)
{
$pastes = array();
$rows = self::_select(
'SELECT "dataid" FROM "' . self::_sanitizeIdentifier('paste') .
$statement = $this->_db->prepare(
'SELECT "dataid" FROM "' . $this->_sanitizeIdentifier('paste') .
'" WHERE "expiredate" < ? AND "expiredate" != ? ' .
(self::$_type === 'oci' ? 'FETCH NEXT ? ROWS ONLY' : 'LIMIT ?'),
array(time(), 0, $batchsize)
($this->_type === 'oci' ? 'FETCH NEXT ? ROWS ONLY' : 'LIMIT ?')
);
if (is_array($rows) && count($rows)) {
foreach ($rows as $row) {
$pastes[] = $row['dataid'];
}
}
return $pastes;
$statement->execute(array(time(), 0, $batchsize));
return $statement->fetchAll(PDO::FETCH_COLUMN, 0);
}
/**
* @inheritDoc
*/
public function getAllPastes()
{
return $this->_db->query(
'SELECT "dataid" FROM "' . $this->_sanitizeIdentifier('paste') . '"'
)->fetchAll(PDO::FETCH_COLUMN, 0);
}
/**
* 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 +536,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 +571,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 +586,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 +641,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 +661,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 +685,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 +705,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 +725,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 +741,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 +766,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 +781,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 +797,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 +809,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 +848,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,55 @@ class Filesystem extends AbstractData
return $pastes;
}
/**
* @inheritDoc
*/
public function getAllPastes()
{
$pastes = array();
$firstLevel = array_filter(
scandir($this->_path),
'PrivateBin\Data\Filesystem::_isFirstLevelDir'
);
if (count($firstLevel) > 0) {
foreach ($firstLevel as $firstKey) {
$secondLevel = array_filter(
scandir($this->_path . DIRECTORY_SEPARATOR . $firstKey),
'PrivateBin\Data\Filesystem::_isSecondLevelDir'
);
// skip this folder
if (count($secondLevel) == 0) {
continue;
}
foreach ($secondLevel as $secondKey) {
$path = $this->_path . DIRECTORY_SEPARATOR . $firstKey .
DIRECTORY_SEPARATOR . $secondKey;
if (!is_dir($path)) {
continue;
}
$thirdLevel = array_filter(
array_map(
function ($filename) {
return strlen($filename) >= 20 ?
substr($filename, 0, -4) :
$filename;
},
scandir($path)
),
'PrivateBin\\Model\\Paste::isValidId'
);
if (count($thirdLevel) == 0) {
continue;
}
$pastes += $thirdLevel;
}
}
}
return $pastes;
}
/**
* Convert paste id to storage path.
*
@@ -423,13 +464,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 +480,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 +493,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 +518,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 +538,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 +588,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,54 +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';
/**
* bucket acl type
*
* @access private
* @static
* @var bool
*/
private static $_uniformacl = false;
private $_uniformacl = false;
/**
* returns a Google Cloud Storage data backend.
* 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');
}
@@ -69,24 +58,22 @@ 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)) {
self::$_uniformacl = $options['uniformacl'];
$this->_uniformacl = $options['uniformacl'];
}
if (empty(self::$_client)) {
self::$_client = class_exists('StorageClientStub', false) ?
new \StorageClientStub(array()) :
new StorageClient(array('suppressKeyFileNotice' => true));
$this->_client = class_exists('StorageClientStub', false) ?
new \StorageClientStub(array()) :
new StorageClient(array('suppressKeyFileNotice' => true));
if (isset($bucket)) {
$this->_bucket = $this->_client->bucket($bucket);
}
self::$_bucket = self::$_client->bucket($bucket);
return self::$_instance;
}
/**
* 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
@@ -94,14 +81,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.
@@ -126,12 +113,12 @@ class GoogleCloudStorage extends AbstractData
'metadata' => $metadata,
),
);
if (!self::$_uniformacl) {
if (!$this->_uniformacl) {
$data['predefinedAcl'] = 'private';
}
self::$_bucket->upload(Json::encode($payload), $data);
$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;
}
@@ -156,13 +143,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;
}
@@ -176,9 +163,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.
}
@@ -188,7 +175,7 @@ class GoogleCloudStorage extends AbstractData
}
try {
self::$_bucket->object($name)->delete();
$this->_bucket->object($name)->delete();
} catch (NotFoundException $e) {
// ignore if already deleted
}
@@ -199,7 +186,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();
}
@@ -223,8 +210,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;
@@ -241,7 +228,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();
}
@@ -252,7 +239,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;
@@ -300,12 +287,12 @@ class GoogleCloudStorage extends AbstractData
'metadata' => $metadata,
),
);
if (!self::$_uniformacl) {
if (!$this->_uniformacl) {
$data['predefinedAcl'] = 'private';
}
self::$_bucket->upload($value, $data);
$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;
}
@@ -323,7 +310,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 '';
@@ -338,12 +325,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']);
@@ -361,4 +348,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;
}
}

View File

@@ -45,86 +45,71 @@ class S3Storage extends AbstractData
* S3 client
*
* @access private
* @static
* @var S3Client
*/
private static $_client = null;
private $_client = null;
/**
* S3 client options
*
* @access private
* @static
* @var array
*/
private static $_options = array();
private $_options = array();
/**
* S3 bucket
*
* @access private
* @static
* @var string
*/
private static $_bucket = null;
private $_bucket = null;
/**
* S3 prefix for all PrivateBin data in this bucket
*
* @access private
* @static
* @var string
*/
private static $_prefix = '';
private $_prefix = '';
/**
* returns an S3 data backend.
* instantiates a new S3 data backend.
*
* @access public
* @static
* @param array $options
* @return S3Storage
* @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;
}
self::$_options = array();
self::$_options['credentials'] = array();
$this->_options['credentials'] = array();
if (is_array($options) && array_key_exists('region', $options)) {
self::$_options['region'] = $options['region'];
$this->_options['region'] = $options['region'];
}
if (is_array($options) && array_key_exists('version', $options)) {
self::$_options['version'] = $options['version'];
$this->_options['version'] = $options['version'];
}
if (is_array($options) && array_key_exists('endpoint', $options)) {
self::$_options['endpoint'] = $options['endpoint'];
$this->_options['endpoint'] = $options['endpoint'];
}
if (is_array($options) && array_key_exists('accesskey', $options)) {
self::$_options['credentials']['key'] = $options['accesskey'];
$this->_options['credentials']['key'] = $options['accesskey'];
}
if (is_array($options) && array_key_exists('secretkey', $options)) {
self::$_options['credentials']['secret'] = $options['secretkey'];
$this->_options['credentials']['secret'] = $options['secretkey'];
}
if (is_array($options) && array_key_exists('use_path_style_endpoint', $options)) {
self::$_options['use_path_style_endpoint'] = filter_var($options['use_path_style_endpoint'], FILTER_VALIDATE_BOOLEAN);
$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)) {
self::$_bucket = $options['bucket'];
$this->_bucket = $options['bucket'];
}
if (is_array($options) && array_key_exists('prefix', $options)) {
self::$_prefix = $options['prefix'];
$this->_prefix = $options['prefix'];
}
if (empty(self::$_client)) {
self::$_client = new S3Client(self::$_options);
}
return self::$_instance;
$this->_client = new S3Client($this->_options);
}
/**
@@ -138,12 +123,12 @@ class S3Storage extends AbstractData
{
$allObjects = array();
$options = array(
'Bucket' => self::$_bucket,
'Bucket' => $this->_bucket,
'Prefix' => $prefix,
);
do {
$objectsListResponse = self::$_client->listObjects($options);
$objectsListResponse = $this->_client->listObjects($options);
$objects = $objectsListResponse['Contents'] ?? array();
foreach ($objects as $object) {
$allObjects[] = $object;
@@ -155,7 +140,7 @@ class S3Storage extends AbstractData
}
/**
* returns the S3 storage object key for $pasteid in self::$_bucket.
* returns the S3 storage object key for $pasteid in $this->_bucket.
*
* @access private
* @param $pasteid string to get the key for
@@ -163,14 +148,14 @@ class S3Storage 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 S3 object's metadata except for the fields attachment, attachmentname
* and salt.
@@ -187,15 +172,15 @@ class S3Storage extends AbstractData
$metadata[$k] = strval($v);
}
try {
self::$_client->putObject(array(
'Bucket' => self::$_bucket,
$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 ' . self::$_bucket . ', ' .
error_log('failed to upload ' . $key . ' to ' . $this->_bucket . ', ' .
trim(preg_replace('/\s\s+/', ' ', $e->getMessage())));
return false;
}
@@ -220,14 +205,14 @@ class S3Storage extends AbstractData
public function read($pasteid)
{
try {
$object = self::$_client->getObject(array(
'Bucket' => self::$_bucket,
$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 ' . self::$_bucket . ', ' .
error_log('failed to read ' . $pasteid . ' from ' . $this->_bucket . ', ' .
trim(preg_replace('/\s\s+/', ' ', $e->getMessage())));
return false;
}
@@ -244,8 +229,8 @@ class S3Storage extends AbstractData
$comments = $this->_listAllObjects($name . '/discussion/');
foreach ($comments as $comment) {
try {
self::$_client->deleteObject(array(
'Bucket' => self::$_bucket,
$this->_client->deleteObject(array(
'Bucket' => $this->_bucket,
'Key' => $comment['Key'],
));
} catch (S3Exception $e) {
@@ -257,8 +242,8 @@ class S3Storage extends AbstractData
}
try {
self::$_client->deleteObject(array(
'Bucket' => self::$_bucket,
$this->_client->deleteObject(array(
'Bucket' => $this->_bucket,
'Key' => $name,
));
} catch (S3Exception $e) {
@@ -271,7 +256,7 @@ class S3Storage extends AbstractData
*/
public function exists($pasteid)
{
return self::$_client->doesObjectExistV2(self::$_bucket, $this->_getKey($pasteid));
return $this->_client->doesObjectExistV2($this->_bucket, $this->_getKey($pasteid));
}
/**
@@ -296,8 +281,8 @@ class S3Storage extends AbstractData
try {
$entries = $this->_listAllObjects($prefix);
foreach ($entries as $entry) {
$object = self::$_client->getObject(array(
'Bucket' => self::$_bucket,
$object = $this->_client->getObject(array(
'Bucket' => $this->_bucket,
'Key' => $entry['Key'],
));
$body = JSON::decode($object['Body']->getContents());
@@ -319,7 +304,7 @@ class S3Storage extends AbstractData
public function existsComment($pasteid, $parentid, $commentid)
{
$name = $this->_getKey($pasteid) . '/discussion/' . $parentid . '/' . $commentid;
return self::$_client->doesObjectExistV2(self::$_bucket, $name);
return $this->_client->doesObjectExistV2($this->_bucket, $name);
}
/**
@@ -327,7 +312,7 @@ class S3Storage extends AbstractData
*/
public function purgeValues($namespace, $time)
{
$path = self::$_prefix;
$path = $this->_prefix;
if ($path != '') {
$path .= '/';
}
@@ -339,16 +324,16 @@ class S3Storage extends AbstractData
if (strlen($name) > strlen($path) && substr($name, strlen($path), 1) !== '/') {
continue;
}
$head = self::$_client->headObject(array(
'Bucket' => self::$_bucket,
$head = $this->_client->headObject(array(
'Bucket' => $this->_bucket,
'Key' => $name,
));
if (array_key_exists('Metadata', $head) && array_key_exists('value', $head['Metadata'])) {
$value = $head['Metadata']['value'];
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 {
self::$_client->deleteObject(array(
'Bucket' => self::$_bucket,
$this->_client->deleteObject(array(
'Bucket' => $this->_bucket,
'Key' => $name,
));
} catch (S3Exception $e) {
@@ -369,7 +354,7 @@ class S3Storage extends AbstractData
*/
public function setValue($value, $namespace, $key = '')
{
$prefix = self::$_prefix;
$prefix = $this->_prefix;
if ($prefix != '') {
$prefix .= '/';
}
@@ -385,15 +370,15 @@ class S3Storage extends AbstractData
$metadata['value'] = strval($value);
}
try {
self::$_client->putObject(array(
'Bucket' => self::$_bucket,
$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 ' . self::$_bucket . ', ' .
error_log('failed to set key ' . $key . ' to ' . $this->_bucket . ', ' .
trim(preg_replace('/\s\s+/', ' ', $e->getMessage())));
return false;
}
@@ -405,7 +390,7 @@ class S3Storage extends AbstractData
*/
public function getValue($namespace, $key = '')
{
$prefix = self::$_prefix;
$prefix = $this->_prefix;
if ($prefix != '') {
$prefix .= '/';
}
@@ -417,8 +402,8 @@ class S3Storage extends AbstractData
}
try {
$object = self::$_client->getObject(array(
'Bucket' => self::$_bucket,
$object = $this->_client->getObject(array(
'Bucket' => $this->_bucket,
'Key' => $key,
));
return $object['Body']->getContents();
@@ -434,19 +419,19 @@ class S3Storage extends AbstractData
{
$expired = array();
$now = time();
$prefix = self::$_prefix;
$prefix = $this->_prefix;
if ($prefix != '') {
$prefix .= '/';
}
try {
foreach ($this->_listAllObjects($prefix) as $object) {
$head = self::$_client->headObject(array(
'Bucket' => self::$_bucket,
$head = $this->_client->headObject(array(
'Bucket' => $this->_bucket,
'Key' => $object['Key'],
));
if (array_key_exists('Metadata', $head) && array_key_exists('expire_date', $head['Metadata'])) {
$expire_at = intval($head['Metadata']['expire_date']);
if ($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']);
}
@@ -461,4 +446,28 @@ class S3Storage extends AbstractData
}
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());
}
/**

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

@@ -48,7 +48,7 @@ class YourlsProxy
*/
public function __construct(Configuration $conf, $link)
{
if (strpos($link, $conf->getKey('basepath') . '/?') === false) {
if (strpos($link, $conf->getKey('basepath') . '?') === false) {
$this->_error = 'Trying to shorten a URL that isn\'t pointing at our instance.';
return;
}

View File

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

@@ -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); ?>" />

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

@@ -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();

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

@@ -18,7 +18,7 @@ class JsonApiTest extends PHPUnit_Framework_TestCase
if (!is_dir($this->_path)) {
mkdir($this->_path);
}
$this->_model = Filesystem::getInstance(array('dir' => $this->_path));
$this->_model = new Filesystem(array('dir' => $this->_path));
ServerSalt::setStore($this->_model);
$_POST = array();
@@ -272,7 +272,7 @@ class JsonApiTest extends PHPUnit_Framework_TestCase
{
$mock_yourls_service = $this->_path . DIRECTORY_SEPARATOR . 'yourls.json';
$options = parse_ini_file(CONF, true);
$options['main']['basepath'] = 'https://example.com/path';
$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);

85
tst/MigrateTest.php Normal file
View File

@@ -0,0 +1,85 @@
<?php
use PrivateBin\Data\Database;
use PrivateBin\Data\Filesystem;
class MigrateTest extends PHPUnit_Framework_TestCase
{
protected $_model_1;
protected $_model_2;
protected $_path;
protected $_path_instance_1;
protected $_path_instance_2;
public function setUp()
{
/* Setup Routine */
$this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data';
$this->_path_instance_1 = $this->_path . DIRECTORY_SEPARATOR . 'instance_1';
$this->_path_instance_2 = $this->_path . DIRECTORY_SEPARATOR . 'instance_2';
if (!is_dir($this->_path)) {
mkdir($this->_path);
}
mkdir($this->_path_instance_1);
mkdir($this->_path_instance_1 . DIRECTORY_SEPARATOR . 'cfg');
mkdir($this->_path_instance_2);
mkdir($this->_path_instance_2 . DIRECTORY_SEPARATOR . 'cfg');
$options = parse_ini_file(CONF_SAMPLE, true);
$options['purge']['limit'] = 0;
$options['model_options']['dir'] = $this->_path_instance_1 . DIRECTORY_SEPARATOR . 'data';
$this->_model_1 = new Filesystem($options['model_options']);
Helper::createIniFile($this->_path_instance_1 . DIRECTORY_SEPARATOR . 'cfg' . DIRECTORY_SEPARATOR . 'conf.php', $options);
$options['model'] = array(
'class' => 'Database',
);
$options['model_options'] = array(
'dsn' => 'sqlite:' . $this->_path_instance_2 . DIRECTORY_SEPARATOR . 'test.sq3',
'usr' => null,
'pwd' => null,
'opt' => array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION),
);
$this->_model_2 = new Database($options['model_options']);
Helper::createIniFile($this->_path_instance_2 . DIRECTORY_SEPARATOR . 'cfg' . DIRECTORY_SEPARATOR . 'conf.php', $options);
}
public function tearDown()
{
/* Tear Down Routine */
Helper::rmDir($this->_path);
}
public function testMigrate()
{
if (version_compare(PHP_VERSION, '7.1.0') < 0) {
return; // skip test on unsupported PHP versions
}
$this->_model_1->delete(Helper::getPasteId());
$this->_model_2->delete(Helper::getPasteId());
// storing paste & comment
$this->_model_1->create(Helper::getPasteId(), Helper::getPaste());
$this->_model_1->createComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId(), Helper::getComment());
// migrate files to database
$output = null;
$exit_code = 255;
exec('php ' . PATH . 'bin' . DIRECTORY_SEPARATOR . 'migrate --delete-after ' . $this->_path_instance_1 . DIRECTORY_SEPARATOR . 'cfg ' . $this->_path_instance_2 . DIRECTORY_SEPARATOR . 'cfg', $output, $exit_code);
$this->assertEquals(0, $exit_code, 'migrate script exits 0');
$this->assertFalse($this->_model_1->exists(Helper::getPasteId()), 'paste removed after migrating it');
$this->assertFalse($this->_model_1->existsComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId()), 'comment removed after migrating it');
$this->assertTrue($this->_model_2->exists(Helper::getPasteId()), 'paste migrated');
$this->assertTrue($this->_model_2->existsComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId()), 'comment migrated');
// migrate back to files
$exit_code = 255;
exec('php ' . PATH . 'bin' . DIRECTORY_SEPARATOR . 'migrate ' . $this->_path_instance_2 . DIRECTORY_SEPARATOR . 'cfg ' . $this->_path_instance_1 . DIRECTORY_SEPARATOR . 'cfg', $output, $exit_code);
$this->assertEquals(0, $exit_code, 'migrate script exits 0');
$this->assertTrue($this->_model_1->exists(Helper::getPasteId()), 'paste migrated back');
$this->assertTrue($this->_model_1->existsComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId()), 'comment migrated back');
}
}

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')
)
);
@@ -259,7 +259,10 @@ class ModelTest extends PHPUnit_Framework_TestCase
$paste = $model->getPaste();
$paste->setData($pasteData);
$paste->store();
$paste->exists();
$this->assertTrue($paste->exists(), 'paste exists before creating comment');
$comment = $paste->getComment(Helper::getPasteId());
$comment->setData($commentData);
$db = new PDO(
$options['model_options']['dsn'],
@@ -271,8 +274,9 @@ class ModelTest extends PHPUnit_Framework_TestCase
$statement->execute();
$statement->closeCursor();
$comment = $paste->getComment(Helper::getPasteId());
$comment->setData($commentData);
if (version_compare(PHP_VERSION, '7.2.0') < 0) {
throw new Exception('For some reason, this test stopped working in PHP < 7.2', 70);
}
$comment->store();
}
@@ -314,8 +318,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 +456,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

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

View File

@@ -20,7 +20,7 @@ class YourlsProxyTest extends PHPUnit_Framework_TestCase
}
$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']['basepath'] = 'https://example.com/';
$options['main']['urlshortener'] = 'https://example.com/shortenviayourls?link=';
$options['yourls']['apiurl'] = $this->_mock_yourls_service;
Helper::confBackup();

View File

@@ -1,7 +1,6 @@
<phpunit bootstrap="Bootstrap.php" colors="true">
<testsuite name="PrivateBin Test Suite">
<directory suffix=".php">./</directory>
<exclude>ConfigurationTestGenerator.php</exclude>
</testsuite>
<filter>
<whitelist>

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

@@ -28,12 +28,50 @@ 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\\ImagickRendererLine' => $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

@@ -13,9 +13,6 @@ class ComposerAutoloaderInitDontChange
}
}
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader()
{
if (null !== self::$loader) {

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',
@@ -60,12 +68,50 @@ 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\\ImagickRendererLine' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/ImagickRenderer.php',
'Jdenticon\\Rendering\\InternalPngRenderer' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/InternalPngRenderer.php',
'Jdenticon\\Rendering\\Point' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/Point.php',
'Jdenticon\\Rendering\\Rectangle' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/Rectangle.php',
'Jdenticon\\Rendering\\RendererInterface' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/RendererInterface.php',
'Jdenticon\\Rendering\\SvgPath' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/SvgPath.php',
'Jdenticon\\Rendering\\SvgRenderer' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/SvgRenderer.php',
'Jdenticon\\Rendering\\Transform' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/Transform.php',
'Jdenticon\\Rendering\\TriangleDirection' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/TriangleDirection.php',
'Jdenticon\\Shapes\\Shape' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Shapes/Shape.php',
'Jdenticon\\Shapes\\ShapeCategory' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Shapes/ShapeCategory.php',
'Jdenticon\\Shapes\\ShapeDefinitions' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Shapes/ShapeDefinitions.php',
'Jdenticon\\Shapes\\ShapePosition' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Shapes/ShapePosition.php',
'PrivateBin\\Configuration' => __DIR__ . '/../..' . '/lib/Configuration.php',
'PrivateBin\\Controller' => __DIR__ . '/../..' . '/lib/Controller.php',
'PrivateBin\\Data\\AbstractData' => __DIR__ . '/../..' . '/lib/Data/AbstractData.php',
'PrivateBin\\Data\\Database' => __DIR__ . '/../..' . '/lib/Data/Database.php',
'PrivateBin\\Data\\Filesystem' => __DIR__ . '/../..' . '/lib/Data/Filesystem.php',
'PrivateBin\\Data\\GoogleCloudStorage' => __DIR__ . '/../..' . '/lib/Data/GoogleCloudStorage.php',
'PrivateBin\\Data\\S3Storage' => __DIR__ . '/../..' . '/lib/Data/S3Storage.php',
'PrivateBin\\Filter' => __DIR__ . '/../..' . '/lib/Filter.php',
'PrivateBin\\FormatV2' => __DIR__ . '/../..' . '/lib/FormatV2.php',
'PrivateBin\\I18n' => __DIR__ . '/../..' . '/lib/I18n.php',
@@ -81,6 +127,7 @@ class ComposerStaticInitDontChange
'PrivateBin\\Request' => __DIR__ . '/../..' . '/lib/Request.php',
'PrivateBin\\View' => __DIR__ . '/../..' . '/lib/View.php',
'PrivateBin\\Vizhash16x16' => __DIR__ . '/../..' . '/lib/Vizhash16x16.php',
'PrivateBin\\YourlsProxy' => __DIR__ . '/../..' . '/lib/YourlsProxy.php',
);
public static function getInitializer(ClassLoader $loader)

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

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