From 1892264cf0bb19c191ca45968b9ae6462f5fc075 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 30 Oct 2022 09:04:27 +0100 Subject: [PATCH 01/36] add perf/size test for Jdenticons --- tst/IconTest | 62 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 11 deletions(-) diff --git a/tst/IconTest b/tst/IconTest index 7794e478..e39cc7be 100755 --- a/tst/IconTest +++ b/tst/IconTest @@ -9,6 +9,7 @@ use Identicon\Generator\GdGenerator; use Identicon\Generator\ImageMagickGenerator; use Identicon\Generator\SvgGenerator; use Identicon\Identicon; +use Jdenticon\Identicon as Jdenticon; use PrivateBin\Vizhash16x16; @@ -17,7 +18,18 @@ $vizhash = new Vizhash16x16(); $identiconGenerators = array( 'identicon GD' => new Identicon(new GdGenerator()), 'identicon ImageMagick' => new Identicon(new ImageMagickGenerator()), - 'identicon SVG' => new Identicon(new SvgGenerator()) + 'identicon SVG' => new Identicon(new SvgGenerator()), +); +$jdenticon = new Jdenticon(array( + 'size' => 16, + 'style' => array( + 'backgroundColor' => '#fff0', // fully transparent, for dark mode + 'padding' => 0, + ), +)); +$jdenticonGenerators = array( + 'jdenticon GD' => 'png', + 'jdenticon SVG' => 'svg', ); $results = array( 'vizhash' => array( @@ -35,21 +47,26 @@ $results = array( 'identicon SVG' => array( 'lengths' => array(), 'time' => 0 - ) + ), + 'jdenticon GD' => array( + 'lengths' => array(), + 'time' => 0 + ), + 'jdenticon SVG' => array( + 'lengths' => array(), + 'time' => 0 + ), ); $hmacs = array(); echo 'generate ', ITERATIONS, ' hmacs and pre-populate the result array, so tests wont be slowed down', PHP_EOL; for ($i = 0; $i < ITERATIONS; ++$i) { $hmacs[$i] = hash_hmac('sha512', '127.0.0.1', bin2hex(random_bytes(256))); - $results['vizhash']['lengths'][$i] = 0; - $results['identicon GD']['lengths'][$i] = 0; - $results['identicon ImageMagick']['lengths'][$i] = 0; - $results['identicon SVG']['lengths'][$i] = 0; + foreach (array_keys($results) as $test) { + $results[$test]['lengths'][$i] = 0; + } } - - echo 'run vizhash tests', PHP_EOL; $start = microtime(true); foreach ($hmacs as $i => $hmac) { @@ -60,7 +77,6 @@ foreach ($hmacs as $i => $hmac) { } $results['vizhash']['time'] = microtime(true) - $start; - foreach ($identiconGenerators as $key => $identicon) { echo 'run ', $key,' tests', PHP_EOL; $start = microtime(true); @@ -71,9 +87,30 @@ foreach ($identiconGenerators as $key => $identicon) { $results[$key]['time'] = microtime(true) - $start; } +foreach ($jdenticonGenerators as $key => $format) { + echo 'run ', $key,' tests', PHP_EOL; + $start = microtime(true); + foreach ($hmacs as $i => $hmac) { + $jdenticon->setHash($hmac); + $data = $jdenticon->getImageDataUri($format); + $results[$key]['lengths'][$i] = strlen($data); + } + $results[$key]['time'] = microtime(true) - $start; +} -define('PADDING_LENGTH', max(array_map(function ($key) { return strlen($key); }, array_keys($results))) + 1); +define( + 'PADDING_LENGTH', + max( + array_map( + function ($key) { + return strlen($key); + }, + array_keys($results) + ) + ) + 1 +); + function format_result_line($generator, $min, $max, $avg, $sec) { echo str_pad($generator, PADDING_LENGTH, ' '), "\t", str_pad($min, 4, ' ', STR_PAD_LEFT), "\t", @@ -84,7 +121,10 @@ function format_result_line($generator, $min, $max, $avg, $sec) { echo PHP_EOL; format_result_line('Generator:', 'min', 'max', 'avg', 'seconds'); -format_result_line(str_repeat('─', PADDING_LENGTH), str_repeat('─', 4), str_repeat('─', 4), str_repeat('─', 4), str_repeat('─', 7)); +format_result_line( + str_repeat('─', PADDING_LENGTH), str_repeat('─', 4), str_repeat('─', 4), + str_repeat('─', 4), str_repeat('─', 7) +); foreach ($results as $generator => $result) { sort($result['lengths']); format_result_line( From 89d575ace36406a78d47f3bb28fb4b8a70997604 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 30 Oct 2022 09:05:29 +0100 Subject: [PATCH 02/36] in light of the perf/size test results of Jdenticons, switch back to Identicons as the default --- CHANGELOG.md | 2 +- cfg/conf.sample.php | 2 +- lib/Configuration.php | 2 +- lib/Model/Comment.php | 8 ++++---- tst/ModelTest.php | 13 +++---------- 5 files changed, 10 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b6a607d..3fff7db7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ * **1.4.1 (not yet released)** * ADDED: Translations for Turkish, Slovak and Greek * ADDED: S3 Storage backend (#994) - * CHANGED: Switched to Jdenticons as the default for comment icons (#793) + * ADDED: Jdenticons as an option for comment icons (#793) * CHANGED: Avoid `SUPER` privilege for setting the `sql_mode` for MariaDB/MySQL (#919) * CHANGED: Upgrading libraries to: zlib 1.2.13 * FIXED: Revert to CREATE INDEX without IF NOT EXISTS clauses, to support MySQL (#943) diff --git a/cfg/conf.sample.php b/cfg/conf.sample.php index c972f153..e6a39d10 100644 --- a/cfg/conf.sample.php +++ b/cfg/conf.sample.php @@ -69,7 +69,7 @@ languageselection = false ; 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). +; "none" / "identicon" (default) / "jdenticon" / "vizhash". ; icon = "none" ; Content Security Policy headers allow a website to restrict what sources are diff --git a/lib/Configuration.php b/lib/Configuration.php index f4e537ef..6f3a8225 100644 --- a/lib/Configuration.php +++ b/lib/Configuration.php @@ -53,7 +53,7 @@ class Configuration 'languagedefault' => '', 'urlshortener' => '', 'qrcode' => true, - 'icon' => 'jdenticon', + 'icon' => 'identicon', '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, diff --git a/lib/Model/Comment.php b/lib/Model/Comment.php index 8e4142ee..ffb27fc4 100644 --- a/lib/Model/Comment.php +++ b/lib/Model/Comment.php @@ -165,7 +165,10 @@ class Comment extends AbstractModel if ($icon != 'none') { $pngdata = ''; $hmac = TrafficLimiter::getHash(); - if ($icon == 'jdenticon') { + if ($icon == 'identicon') { + $identicon = new Identicon(); + $pngdata = $identicon->getImageDataUri($hmac, 16); + } elseif ($icon == 'jdenticon') { $jdenticon = new Jdenticon(array( 'hash' => $hmac, 'size' => 16, @@ -175,9 +178,6 @@ class Comment extends AbstractModel ), )); $pngdata = $jdenticon->getImageDataUri('png'); - } elseif ($icon == 'identicon') { - $identicon = new Identicon(); - $pngdata = $identicon->getImageDataUri($hmac, 16); } elseif ($icon == 'vizhash') { $vh = new Vizhash16x16(); $pngdata = 'data:image/png;base64,' . base64_encode( diff --git a/tst/ModelTest.php b/tst/ModelTest.php index 49a2ae6b..a88e0296 100644 --- a/tst/ModelTest.php +++ b/tst/ModelTest.php @@ -1,6 +1,6 @@ get(); $comment->store(); - $identicon = new Identicon(array( - 'hash' => TrafficLimiter::getHash(), - 'size' => 16, - 'style' => array( - 'backgroundColor' => '#fff0', // fully transparent, for dark mode - 'padding' => 0, - ), - )); - $pngdata = $identicon->getImageDataUri('png'); + $identicon = new Identicon(); + $pngdata = $identicon->getImageDataUri(TrafficLimiter::getHash(), 16); $comment = current($this->_model->getPaste(Helper::getPasteId())->get()['comments']); $this->assertEquals($pngdata, $comment['meta']['icon'], 'icon gets set'); } From 9a61e8fd48d193a79bb5203155c8303a719d3ff1 Mon Sep 17 00:00:00 2001 From: "Felix J. Ogris" Date: Fri, 28 Oct 2022 01:01:02 +0200 Subject: [PATCH 03/36] 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 --- CREDITS.md | 1 + INSTALL.md | 8 +- bin/migrate.php | 194 ++++++++++++++ lib/Data/AbstractData.php | 62 +---- lib/Data/Database.php | 353 ++++++++++++------------- lib/Data/Filesystem.php | 159 ++++++----- lib/Data/GoogleCloudStorage.php | 111 ++++---- lib/Data/S3Storage.php | 151 ++++++----- lib/Model.php | 6 +- tst/ConfigurationTestGenerator.php | 2 +- tst/ControllerTest.php | 2 +- tst/ControllerWithDbTest.php | 2 +- tst/ControllerWithGcsTest.php | 2 +- tst/Data/DatabaseTest.php | 32 +-- tst/Data/FilesystemTest.php | 2 +- tst/Data/GoogleCloudStorageTest.php | 2 +- tst/JsonApiTest.php | 2 +- tst/ModelTest.php | 8 +- tst/Persistence/PurgeLimiterTest.php | 2 +- tst/Persistence/ServerSaltTest.php | 18 +- tst/Persistence/TrafficLimiterTest.php | 2 +- tst/Vizhash16x16Test.php | 2 +- 22 files changed, 658 insertions(+), 465 deletions(-) create mode 100644 bin/migrate.php diff --git a/CREDITS.md b/CREDITS.md index 97c11251..eb5f152e 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -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 diff --git a/INSTALL.md b/INSTALL.md index f35d68bf..57777c94 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -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 diff --git a/bin/migrate.php b/bin/migrate.php new file mode 100644 index 00000000..193430e3 --- /dev/null +++ b/bin/migrate.php @@ -0,0 +1,194 @@ += $argc) { + dieerr("Missing source configuration directory"); +} +if ($delete_after && $delete_during) { + dieerr("--delete-after and --delete-during are mutually exclusive"); +} + +$srcconf = getConfig("source", $argv[$rest]); +$rest++; +$dstconf = getConfig("destination", ($rest < $argc ? $argv[$rest] : "")); + +if (($srcconf->getSection("model") == $dstconf->getSection("model")) && + ($srcconf->getSection("model_options") == $dstconf->getSection("model_options"))) { + dieerr("Source and destination storage configurations are identical"); +} + +$srcmodel = new Model($srcconf); +$srcstore = $srcmodel->getStore(); +$dstmodel = new Model($dstconf); +$dststore = $dstmodel->getStore(); +$ids = $srcstore->getAllPastes(); + +foreach ($ids as $id) { + debug("Reading paste id " . $id); + $paste = $srcstore->read($id); + $comments = $srcstore->readComments($id); + + savePaste($force_overwrite, $dryrun, $id, $paste, $dststore); + foreach ($comments as $comment) { + saveComment($force_overwrite, $dryrun, $id, $comment, $dststore); + } + if ($delete_during) { + deletePaste($dryrun, $id, $srcstore); + } +} + +if ($delete_after) { + foreach ($ids as $id) { + deletePaste($dryrun, $id, $srcstore); + } +} + +debug("Done."); + + +function deletePaste($dryrun, $pasteid, $srcstore) +{ + if (!$dryrun) { + debug("Deleting paste id " . $pasteid); + $srcstore->delete($pasteid); + } else { + debug("Would delete paste id " . $pasteid); + } +} + +function saveComment ($force_overwrite, $dryrun, $pasteid, $comment, $dststore) +{ + $parentid = $comment["parentid"]; + $commentid = $comment["id"]; + + if (!$dststore->existsComment($pasteid, $parentid, $commentid)) { + if (!$dryrun) { + debug("Saving paste id " . $pasteid . ", parent id " . + $parentid . ", comment id " . $commentid); + $dststore->createComment($pasteid, $parentid, $commentid, $comment); + } else { + debug("Would save paste id " . $pasteid . ", parent id " . + $parentid . ", comment id " . $commentid); + } + } else if ($force_overwrite) { + if (!$dryrun) { + debug("Overwriting paste id " . $pasteid . ", parent id " . + $parentid . ", comment id " . $commentid); + $dststore->createComment($pasteid, $parentid, $commentid, $comment); + } else { + debug("Would overwrite paste id " . $pasteid . ", parent id " . + $parentid . ", comment id " . $commentid); + } + } else { + if (!$dryrun) { + dieerr("Not overwriting paste id " . $pasteid . ", parent id " . + $parentid . ", comment id " . $commentid); + } else { + dieerr("Would not overwrite paste id " . $pasteid . ", parent id " . + $parentid . ", comment id " . $commentid); + } + } +} + +function savePaste ($force_overwrite, $dryrun, $pasteid, $paste, $dststore) +{ + if (!$dststore->exists($pasteid)) { + if (!$dryrun) { + debug("Saving paste id " . $pasteid); + $dststore->create($pasteid, $paste); + } else { + debug("Would save paste id " . $pasteid); + } + } else if ($force_overwrite) { + if (!$dryrun) { + debug("Overwriting paste id " . $pasteid); + $dststore->create($pasteid, $paste); + } else { + debug("Would overwrite paste id " . $pasteid); + } + } else { + if (!$dryrun) { + dieerr("Not overwriting paste id " . $pasteid); + } else { + dieerr("Would not overwrite paste id " . $pasteid); + } + } +} + +function getConfig ($target, $confdir) +{ + debug("Trying to load " . $target . " conf.php" . + ($confdir === "" ? "" : " from " . $confdir)); + + putenv("CONFIG_PATH=" . $confdir); + $conf = new Configuration; + putenv("CONFIG_PATH="); + + return $conf; +} + +function dieerr ($text) +{ + fprintf(STDERR, "ERROR: %s" . PHP_EOL, $text); + die(1); +} + +function debug ($text) { + if ($GLOBALS["verbose"]) { + printf("DEBUG: %s" . PHP_EOL, $text); + } +} + +function helpexit () +{ + print("migrate.php - Copy data between PrivateBin backends + +Usage: + php migrate.php [--delete-after] [--delete-during] [-f] [-n] [-v] srcconfdir + [] + php migrate.php [-h] + +Options: + --delete-after delete data from source after all pastes and comments have + successfully been copied to the destination + --delete-during delete data from source after the current paste and its + comments have successfully been copied to the destination + -f forcefully overwrite data which already exists at the + destination + -n dry run, do not copy data + -v be verbose + use storage backend configration from conf.php found in + this directory as source + optionally, use storage backend configration from conf.php + found in this directory as destination; defaults to: + " . PATH . "cfg" . DIRECTORY_SEPARATOR . "conf.php +"); + exit(); +} diff --git a/lib/Data/AbstractData.php b/lib/Data/AbstractData.php index 05353ce0..eddab09b 100644 --- a/lib/Data/AbstractData.php +++ b/lib/Data/AbstractData.php @@ -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($thi->_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. * diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 3ca7b756..05952a95 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -30,54 +30,45 @@ class Database extends AbstractData * * @var array */ - private static $_cache = array(); + private $_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 +85,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 +102,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; } /** @@ -161,12 +150,12 @@ class Database extends AbstractData public function create($pasteid, array $paste) { if ( - array_key_exists($pasteid, self::$_cache) + array_key_exists($pasteid, $this->_cache) ) { - if (false !== self::$_cache[$pasteid]) { + if (false !== $this->_cache[$pasteid]) { return false; } else { - unset(self::$_cache[$pasteid]); + unset($this->_cache[$pasteid]); } } @@ -175,7 +164,7 @@ class Database extends AbstractData $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 +193,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,14 +222,14 @@ class Database extends AbstractData */ public function read($pasteid) { - if (array_key_exists($pasteid, self::$_cache)) { - return self::$_cache[$pasteid]; + if (array_key_exists($pasteid, $this->_cache)) { + return $this->_cache[$pasteid]; } - self::$_cache[$pasteid] = false; + $this->_cache[$pasteid] = false; try { - $paste = self::_select( - 'SELECT * FROM "' . self::_sanitizeIdentifier('paste') . + $paste = $this->_select( + 'SELECT * FROM "' . $this->_sanitizeIdentifier('paste') . '" WHERE "dataid" = ?', array($pasteid), true ); } catch (Exception $e) { @@ -253,11 +242,11 @@ class Database extends AbstractData $data = Json::decode($paste['data']); $isVersion2 = array_key_exists('v', $data) && $data['v'] >= 2; if ($isVersion2) { - self::$_cache[$pasteid] = $data; - list($createdKey) = self::_getVersionedKeys(2); + $this->_cache[$pasteid] = $data; + list($createdKey) = $this->_getVersionedKeys(2); } else { - self::$_cache[$pasteid] = array('data' => $paste['data']); - list($createdKey) = self::_getVersionedKeys(1); + $this->_cache[$pasteid] = array('data' => $paste['data']); + list($createdKey) = $this->_getVersionedKeys(1); } try { @@ -266,31 +255,31 @@ class Database extends AbstractData $paste['meta'] = array(); } $paste = self::upgradePreV1Format($paste); - self::$_cache[$pasteid]['meta'] = $paste['meta']; - self::$_cache[$pasteid]['meta'][$createdKey] = (int) $paste['postdate']; + $this->_cache[$pasteid]['meta'] = $paste['meta']; + $this->_cache[$pasteid]['meta'][$createdKey] = (int) $paste['postdate']; $expire_date = (int) $paste['expiredate']; if ($expire_date > 0) { - self::$_cache[$pasteid]['meta']['expire_date'] = $expire_date; + $this->_cache[$pasteid]['meta']['expire_date'] = $expire_date; } if ($isVersion2) { - return self::$_cache[$pasteid]; + return $this->_cache[$pasteid]; } // support v1 attachments if (array_key_exists('attachment', $paste) && !empty($paste['attachment'])) { - self::$_cache[$pasteid]['attachment'] = $paste['attachment']; + $this->_cache[$pasteid]['attachment'] = $paste['attachment']; if (array_key_exists('attachmentname', $paste) && !empty($paste['attachmentname'])) { - self::$_cache[$pasteid]['attachmentname'] = $paste['attachmentname']; + $this->_cache[$pasteid]['attachmentname'] = $paste['attachmentname']; } } if ($paste['opendiscussion']) { - self::$_cache[$pasteid]['meta']['opendiscussion'] = true; + $this->_cache[$pasteid]['meta']['opendiscussion'] = true; } if ($paste['burnafterreading']) { - self::$_cache[$pasteid]['meta']['burnafterreading'] = true; + $this->_cache[$pasteid]['meta']['burnafterreading'] = true; } - return self::$_cache[$pasteid]; + return $this->_cache[$pasteid]; } /** @@ -301,18 +290,18 @@ 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) + array_key_exists($pasteid, $this->_cache) ) { - unset(self::$_cache[$pasteid]); + unset($this->_cache[$pasteid]); } } @@ -326,11 +315,11 @@ class Database extends AbstractData public function exists($pasteid) { if ( - !array_key_exists($pasteid, self::$_cache) + !array_key_exists($pasteid, $this->_cache) ) { - self::$_cache[$pasteid] = $this->read($pasteid); + $this->_cache[$pasteid] = $this->read($pasteid); } - return (bool) self::$_cache[$pasteid]; + return (bool) $this->_cache[$pasteid]; } /** @@ -352,7 +341,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 +350,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 +377,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 +395,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 +422,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 +444,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 +472,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 +481,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 +490,12 @@ class Database extends AbstractData } if ($value && $namespace === 'traffic_limiter') { try { - self::$_last_cache = Json::decode($value); + $this->_last_cache = Json::decode($value); } catch (Exception $e) { - self::$_last_cache = array(); + $this->_last_cache = array(); } - if (array_key_exists($key, self::$_last_cache)) { - return self::$_last_cache[$key]; + if (array_key_exists($key, $this->_last_cache)) { + return $this->_last_cache[$key]; } } return (string) $value; @@ -521,10 +511,10 @@ class Database extends AbstractData protected function _getExpiredPastes($batchsize) { $pastes = array(); - $rows = self::_select( - 'SELECT "dataid" FROM "' . self::_sanitizeIdentifier('paste') . + $rows = $this->_select( + 'SELECT "dataid" FROM "' . $this->_sanitizeIdentifier('paste') . '" WHERE "expiredate" < ? AND "expiredate" != ? ' . - (self::$_type === 'oci' ? 'FETCH NEXT ? ROWS ONLY' : 'LIMIT ?'), + ($this->_type === 'oci' ? 'FETCH NEXT ? ROWS ONLY' : 'LIMIT ?'), array(time(), 0, $batchsize) ); if (is_array($rows) && count($rows)) { @@ -535,19 +525,36 @@ class Database extends AbstractData return $pastes; } + /** + * @inheritDoc + */ + public function getAllPastes() + { + $pastes = array(); + $rows = $this->_select( + 'SELECT "dataid" FROM "' . $this->_sanitizeIdentifier('paste') . '"', + array() + ); + if (is_array($rows) && count($rows)) { + foreach ($rows as $row) { + $pastes[] = $row['dataid']; + } + } + return $pastes; + } + /** * execute a statement * * @access private - * @static * @param string $sql * @param array $params * @throws PDOException * @return bool */ - private static function _exec($sql, array $params) + private function _exec($sql, array $params) { - $statement = self::$_db->prepare($sql); + $statement = $this->_db->prepare($sql); foreach ($params as $key => &$parameter) { $position = $key + 1; if (is_int($parameter)) { @@ -567,20 +574,19 @@ 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)) { @@ -590,7 +596,7 @@ class Database extends AbstractData $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('PrivateBin\Data\Database::_sanitizeClob', $result) : @@ -603,11 +609,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 +624,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 +679,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 +699,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 +723,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 +743,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 +763,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 +779,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 +804,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 +819,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 +835,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 +847,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) ); @@ -874,11 +870,10 @@ class Database extends AbstractData * From: https://stackoverflow.com/questions/36200534/pdo-oci-into-a-clob-field * * @access public - * @static * @param int|string|resource $value * @return int|string */ - public static function _sanitizeClob($value) + public function _sanitizeClob($value) { if (is_resource($value)) { $value = stream_get_contents($value); @@ -890,90 +885,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') ); diff --git a/lib/Data/Filesystem.php b/lib/Data/Filesystem.php index 9674513c..3dd69578 100644 --- a/lib/Data/Filesystem.php +++ b/lib/Data/Filesystem.php @@ -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', '_storeString( + $this->_path . DIRECTORY_SEPARATOR . 'salt.php', '_last_cache[$key] = $value; + return $this->_storeString( + $this->_path . DIRECTORY_SEPARATOR . 'traffic_limiter.php', + '_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,7 +342,7 @@ class Filesystem extends AbstractData { $pastes = array(); $firstLevel = array_filter( - scandir(self::$_path), + scandir($this->_path), 'PrivateBin\Data\Filesystem::_isFirstLevelDir' ); if (count($firstLevel) > 0) { @@ -358,7 +350,7 @@ class Filesystem extends AbstractData for ($i = 0, $max = $batchsize * 10; $i < $max; ++$i) { $firstKey = array_rand($firstLevel); $secondLevel = array_filter( - scandir(self::$_path . DIRECTORY_SEPARATOR . $firstLevel[$firstKey]), + scandir($this->_path . DIRECTORY_SEPARATOR . $firstLevel[$firstKey]), 'PrivateBin\Data\Filesystem::_isSecondLevelDir' ); @@ -369,7 +361,7 @@ class Filesystem extends AbstractData } $secondKey = array_rand($secondLevel); - $path = self::$_path . DIRECTORY_SEPARATOR . + $path = $this->_path . DIRECTORY_SEPARATOR . $firstLevel[$firstKey] . DIRECTORY_SEPARATOR . $secondLevel[$secondKey]; if (!is_dir($path)) { @@ -412,6 +404,46 @@ class Filesystem extends AbstractData return $pastes; } + /** + * @inheritDoc + */ + public function getAllPastes() + { + $pastes = array(); + $subdirs = scandir($this->_path); + if ($subdirs === false) { + dieerr("Unable to list directory " . $this->_path); + } + $subdirs = preg_grep("/^[^.].$/", $subdirs); + + foreach ($subdirs as $subdir) { + $subpath = $this->_path . DIRECTORY_SEPARATOR . $subdir; + + $subsubdirs = scandir($subpath); + if ($subsubdirs === false) { + dieerr("Unable to list directory " . $subpath); + } + $subsubdirs = preg_grep("/^[^.].$/", $subsubdirs); + foreach ($subsubdirs as $subsubdir) { + $subsubpath = $subpath . DIRECTORY_SEPARATOR . $subsubdir; + + $files = scandir($subsubpath); + if ($files === false) { + dieerr("Unable to list directory " . $subsubpath); + } + $files = preg_grep("/\.php$/", $files); + + foreach ($files as $file) { + if (substr($file, 0, 4) === $subdir . $subsubdir) { + $pastes[] = substr($file, 0, strlen($file) - 4); + } + } + } + } + + return $pastes; + } + /** * Convert paste id to storage path. * @@ -423,13 +455,12 @@ class Filesystem extends AbstractData * eg. input 'e3570978f9e4aa90' --> output 'data/e3/57/' * * @access private - * @static * @param string $dataid * @return string */ - private static function _dataid2path($dataid) + private function _dataid2path($dataid) { - return self::$_path . DIRECTORY_SEPARATOR . + return $this->_path . DIRECTORY_SEPARATOR . substr($dataid, 0, 2) . DIRECTORY_SEPARATOR . substr($dataid, 2, 2) . DIRECTORY_SEPARATOR; } @@ -440,13 +471,12 @@ class Filesystem extends AbstractData * eg. input 'e3570978f9e4aa90' --> output 'data/e3/57/e3570978f9e4aa90.discussion/' * * @access private - * @static * @param string $dataid * @return string */ - private static function _dataid2discussionpath($dataid) + private function _dataid2discussionpath($dataid) { - return self::_dataid2path($dataid) . $dataid . + return $this->_dataid2path($dataid) . $dataid . '.discussion' . DIRECTORY_SEPARATOR; } @@ -454,25 +484,23 @@ class Filesystem extends AbstractData * Check that the given element is a valid first level directory. * * @access private - * @static * @param string $element * @return bool */ - private static function _isFirstLevelDir($element) + private function _isFirstLevelDir($element) { - return self::_isSecondLevelDir($element) && - is_dir(self::$_path . DIRECTORY_SEPARATOR . $element); + return $this->_isSecondLevelDir($element) && + is_dir($this->_path . DIRECTORY_SEPARATOR . $element); } /** * Check that the given element is a valid second level directory. * * @access private - * @static * @param string $element * @return bool */ - private static function _isSecondLevelDir($element) + private function _isSecondLevelDir($element) { return (bool) preg_match('/^[a-f0-9]{2}$/', $element); } @@ -481,15 +509,14 @@ class Filesystem extends AbstractData * store the data * * @access public - * @static * @param string $filename * @param array $data * @return bool */ - private static function _store($filename, array $data) + private function _store($filename, array $data) { try { - return self::_storeString( + return $this->_storeString( $filename, self::PROTECTION_LINE . PHP_EOL . Json::encode($data) ); @@ -502,20 +529,19 @@ class Filesystem extends AbstractData * store a string * * @access public - * @static * @param string $filename * @param string $data * @return bool */ - private static function _storeString($filename, $data) + private function _storeString($filename, $data) { // Create storage directory if it does not exist. - if (!is_dir(self::$_path)) { - if (!@mkdir(self::$_path, 0700)) { + if (!is_dir($this->_path)) { + if (!@mkdir($this->_path, 0700)) { return false; } } - $file = self::$_path . DIRECTORY_SEPARATOR . '.htaccess'; + $file = $this->_path . DIRECTORY_SEPARATOR . '.htaccess'; if (!is_file($file)) { $writtenBytes = 0; if ($fileCreated = @touch($file)) { @@ -553,12 +579,11 @@ class Filesystem extends AbstractData * rename a file, prepending the protection line at the beginning * * @access public - * @static * @param string $srcFile * @param string $destFile * @return void */ - private static function _prependRename($srcFile, $destFile) + private function _prependRename($srcFile, $destFile) { // don't overwrite already converted file if (!is_readable($destFile)) { diff --git a/lib/Data/GoogleCloudStorage.php b/lib/Data/GoogleCloudStorage.php index e8763258..e271e2f8 100644 --- a/lib/Data/GoogleCloudStorage.php +++ b/lib/Data/GoogleCloudStorage.php @@ -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,20 @@ class GoogleCloudStorage extends AbstractData $bucket = $options['bucket']; } if (is_array($options) && array_key_exists('prefix', $options)) { - self::$_prefix = $options['prefix']; + $this->_prefix = $options['prefix']; } if (is_array($options) && array_key_exists('uniformacl', $options)) { - 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)); - } - self::$_bucket = self::$_client->bucket($bucket); - - return self::$_instance; + $this->_client = class_exists('StorageClientStub', false) ? + new \StorageClientStub(array()) : + new StorageClient(array('suppressKeyFileNotice' => true)); + $this->_bucket = $this->_client->bucket($bucket); } /** - * returns the google storage object key for $pasteid in self::$_bucket. + * returns the google storage object key for $pasteid in $this->_bucket. * * @access private * @param $pasteid string to get the key for @@ -94,14 +79,14 @@ class GoogleCloudStorage extends AbstractData */ private function _getKey($pasteid) { - if (self::$_prefix != '') { - return self::$_prefix . '/' . $pasteid; + if ($this->_prefix != '') { + return $this->_prefix . '/' . $pasteid; } return $pasteid; } /** - * Uploads the payload in the self::$_bucket under the specified key. + * Uploads the payload in the $this->_bucket under the specified key. * The entire payload is stored as a JSON document. The metadata is replicated * as the GCS object's metadata except for the fields attachment, attachmentname * and salt. @@ -126,12 +111,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 +141,13 @@ class GoogleCloudStorage extends AbstractData public function read($pasteid) { try { - $o = self::$_bucket->object($this->_getKey($pasteid)); + $o = $this->_bucket->object($this->_getKey($pasteid)); $data = $o->downloadAsString(); return Json::decode($data); } catch (NotFoundException $e) { return false; } catch (Exception $e) { - error_log('failed to read ' . $pasteid . ' from ' . self::$_bucket->name() . ', ' . + error_log('failed to read ' . $pasteid . ' from ' . $this->_bucket->name() . ', ' . trim(preg_replace('/\s\s+/', ' ', $e->getMessage()))); return false; } @@ -176,9 +161,9 @@ class GoogleCloudStorage extends AbstractData $name = $this->_getKey($pasteid); try { - foreach (self::$_bucket->objects(array('prefix' => $name . '/discussion/')) as $comment) { + foreach ($this->_bucket->objects(array('prefix' => $name . '/discussion/')) as $comment) { try { - self::$_bucket->object($comment->name())->delete(); + $this->_bucket->object($comment->name())->delete(); } catch (NotFoundException $e) { // ignore if already deleted. } @@ -188,7 +173,7 @@ class GoogleCloudStorage extends AbstractData } try { - self::$_bucket->object($name)->delete(); + $this->_bucket->object($name)->delete(); } catch (NotFoundException $e) { // ignore if already deleted } @@ -199,7 +184,7 @@ class GoogleCloudStorage extends AbstractData */ public function exists($pasteid) { - $o = self::$_bucket->object($this->_getKey($pasteid)); + $o = $this->_bucket->object($this->_getKey($pasteid)); return $o->exists(); } @@ -223,8 +208,8 @@ class GoogleCloudStorage extends AbstractData $comments = array(); $prefix = $this->_getKey($pasteid) . '/discussion/'; try { - foreach (self::$_bucket->objects(array('prefix' => $prefix)) as $key) { - $comment = JSON::decode(self::$_bucket->object($key->name())->downloadAsString()); + foreach ($this->_bucket->objects(array('prefix' => $prefix)) as $key) { + $comment = JSON::decode($this->_bucket->object($key->name())->downloadAsString()); $comment['id'] = basename($key->name()); $slot = $this->getOpenSlot($comments, (int) $comment['meta']['created']); $comments[$slot] = $comment; @@ -241,7 +226,7 @@ class GoogleCloudStorage extends AbstractData public function existsComment($pasteid, $parentid, $commentid) { $name = $this->_getKey($pasteid) . '/discussion/' . $parentid . '/' . $commentid; - $o = self::$_bucket->object($name); + $o = $this->_bucket->object($name); return $o->exists(); } @@ -252,7 +237,7 @@ class GoogleCloudStorage extends AbstractData { $path = 'config/' . $namespace; try { - foreach (self::$_bucket->objects(array('prefix' => $path)) as $object) { + foreach ($this->_bucket->objects(array('prefix' => $path)) as $object) { $name = $object->name(); if (strlen($name) > strlen($path) && substr($name, strlen($path), 1) !== '/') { continue; @@ -300,12 +285,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 +308,7 @@ class GoogleCloudStorage extends AbstractData $key = 'config/' . $namespace . '/' . $key; } try { - $o = self::$_bucket->object($key); + $o = $this->_bucket->object($key); return $o->downloadAsString(); } catch (NotFoundException $e) { return ''; @@ -338,12 +323,12 @@ class GoogleCloudStorage extends AbstractData $expired = array(); $now = time(); - $prefix = self::$_prefix; + $prefix = $this->_prefix; if ($prefix != '') { $prefix .= '/'; } try { - foreach (self::$_bucket->objects(array('prefix' => $prefix)) as $object) { + foreach ($this->_bucket->objects(array('prefix' => $prefix)) as $object) { $metadata = $object->info()['metadata']; if ($metadata != null && array_key_exists('expire_date', $metadata)) { $expire_at = intval($metadata['expire_date']); @@ -361,4 +346,28 @@ class GoogleCloudStorage extends AbstractData } return $expired; } + + /** + * @inheritDoc + */ + public function getAllPastes() + { + $pastes = array(); + $prefix = $this->_prefix; + if ($prefix != '') { + $prefix .= '/'; + } + + try { + foreach ($this->_bucket->objects(array('prefix' => $prefix)) as $object) { + $candidate = substr($object->name(), strlen($prefix)); + if (strpos($candidate, "/") === false) { + $pastes[] = $candidate; + } + } + } catch (NotFoundException $e) { + // no objects in the bucket yet + } + return $pastes; + } } diff --git a/lib/Data/S3Storage.php b/lib/Data/S3Storage.php index d741e099..c59aaaeb 100644 --- a/lib/Data/S3Storage.php +++ b/lib/Data/S3Storage.php @@ -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; + } } diff --git a/lib/Model.php b/lib/Model.php index 360cf68c..94f77007 100644 --- a/lib/Model.php +++ b/lib/Model.php @@ -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; } diff --git a/tst/ConfigurationTestGenerator.php b/tst/ConfigurationTestGenerator.php index 945fc479..a0106a3f 100755 --- a/tst/ConfigurationTestGenerator.php +++ b/tst/ConfigurationTestGenerator.php @@ -427,7 +427,7 @@ class ConfigurationCombinationsTest extends PHPUnit_Framework_TestCase /* Setup Routine */ Helper::confBackup(); $this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data'; - $this->_model = Filesystem::getInstance(array('dir' => $this->_path)); + $this->_model = new Filesystem(array('dir' => $this->_path)); $this->reset(); } diff --git a/tst/ControllerTest.php b/tst/ControllerTest.php index e1637c11..698d5f86 100644 --- a/tst/ControllerTest.php +++ b/tst/ControllerTest.php @@ -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(); diff --git a/tst/ControllerWithDbTest.php b/tst/ControllerWithDbTest.php index bc8cd7be..d5bbe534 100644 --- a/tst/ControllerWithDbTest.php +++ b/tst/ControllerWithDbTest.php @@ -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(); diff --git a/tst/ControllerWithGcsTest.php b/tst/ControllerWithGcsTest.php index 39833417..5490db8d 100644 --- a/tst/ControllerWithGcsTest.php +++ b/tst/ControllerWithGcsTest.php @@ -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(); diff --git a/tst/Data/DatabaseTest.php b/tst/Data/DatabaseTest.php index 16e6fcb8..3e65933a 100644 --- a/tst/Data/DatabaseTest.php +++ b/tst/Data/DatabaseTest.php @@ -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 ?'); diff --git a/tst/Data/FilesystemTest.php b/tst/Data/FilesystemTest.php index 684c2940..bc9dcd06 100644 --- a/tst/Data/FilesystemTest.php +++ b/tst/Data/FilesystemTest.php @@ -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); } diff --git a/tst/Data/GoogleCloudStorageTest.php b/tst/Data/GoogleCloudStorageTest.php index 3b101c40..5f1389a3 100644 --- a/tst/Data/GoogleCloudStorageTest.php +++ b/tst/Data/GoogleCloudStorageTest.php @@ -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', )); diff --git a/tst/JsonApiTest.php b/tst/JsonApiTest.php index 4ad629ee..3fa0665e 100644 --- a/tst/JsonApiTest.php +++ b/tst/JsonApiTest.php @@ -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(); diff --git a/tst/ModelTest.php b/tst/ModelTest.php index a88e0296..0d715c2a 100644 --- a/tst/ModelTest.php +++ b/tst/ModelTest.php @@ -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') ) ); @@ -445,7 +445,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)); diff --git a/tst/Persistence/PurgeLimiterTest.php b/tst/Persistence/PurgeLimiterTest.php index adb96ffd..899576fe 100644 --- a/tst/Persistence/PurgeLimiterTest.php +++ b/tst/Persistence/PurgeLimiterTest.php @@ -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)) ); } diff --git a/tst/Persistence/ServerSaltTest.php b/tst/Persistence/ServerSaltTest.php index 3db5f7d7..ce4fc681 100644 --- a/tst/Persistence/ServerSaltTest.php +++ b/tst/Persistence/ServerSaltTest.php @@ -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); diff --git a/tst/Persistence/TrafficLimiterTest.php b/tst/Persistence/TrafficLimiterTest.php index 84e0bae8..011b9eeb 100644 --- a/tst/Persistence/TrafficLimiterTest.php +++ b/tst/Persistence/TrafficLimiterTest.php @@ -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); } diff --git a/tst/Vizhash16x16Test.php b/tst/Vizhash16x16Test.php index abfb8c49..cdc0ad3d 100644 --- a/tst/Vizhash16x16Test.php +++ b/tst/Vizhash16x16Test.php @@ -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() From bde5802a3aff22c3144277defc5f0a3fed35bab0 Mon Sep 17 00:00:00 2001 From: "Felix J. Ogris" Date: Tue, 1 Nov 2022 16:38:06 +0100 Subject: [PATCH 04/36] syntax fix, changelog --- CHANGELOG.md | 1 + lib/Data/Database.php | 2 +- lib/Data/Filesystem.php | 14 +++++++------- lib/Data/GoogleCloudStorage.php | 4 ++-- lib/Data/S3Storage.php | 8 ++++---- lib/Model.php | 2 +- tst/ModelTest.php | 2 +- 7 files changed, 17 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b897b676..61d258c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # 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: Avoid `SUPER` privilege for setting the `sql_mode` for MariaDB/MySQL (#919) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 05952a95..f4318589 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -481,7 +481,7 @@ class Database extends AbstractData // migrate filesystem based salt into database $file = 'data' . DIRECTORY_SEPARATOR . 'salt.php'; if ($namespace === 'salt' && is_readable($file)) { - $fs = new Filesystem(array('dir' => 'data')); + $fs = new Filesystem(array('dir' => 'data')); $value = $fs->getValue('salt'); $this->setValue($value, 'salt'); @unlink($file); diff --git a/lib/Data/Filesystem.php b/lib/Data/Filesystem.php index 3dd69578..2d082144 100644 --- a/lib/Data/Filesystem.php +++ b/lib/Data/Filesystem.php @@ -409,29 +409,29 @@ class Filesystem extends AbstractData */ public function getAllPastes() { - $pastes = array(); + $pastes = array(); $subdirs = scandir($this->_path); if ($subdirs === false) { - dieerr("Unable to list directory " . $this->_path); + dieerr('Unable to list directory ' . $this->_path); } - $subdirs = preg_grep("/^[^.].$/", $subdirs); + $subdirs = preg_grep('/^[^.].$/', $subdirs); foreach ($subdirs as $subdir) { $subpath = $this->_path . DIRECTORY_SEPARATOR . $subdir; $subsubdirs = scandir($subpath); if ($subsubdirs === false) { - dieerr("Unable to list directory " . $subpath); + dieerr('Unable to list directory ' . $subpath); } - $subsubdirs = preg_grep("/^[^.].$/", $subsubdirs); + $subsubdirs = preg_grep('/^[^.].$/', $subsubdirs); foreach ($subsubdirs as $subsubdir) { $subsubpath = $subpath . DIRECTORY_SEPARATOR . $subsubdir; $files = scandir($subsubpath); if ($files === false) { - dieerr("Unable to list directory " . $subsubpath); + dieerr('Unable to list directory ' . $subsubpath); } - $files = preg_grep("/\.php$/", $files); + $files = preg_grep('/\.php$/', $files); foreach ($files as $file) { if (substr($file, 0, 4) === $subdir . $subsubdir) { diff --git a/lib/Data/GoogleCloudStorage.php b/lib/Data/GoogleCloudStorage.php index e271e2f8..dd2aff39 100644 --- a/lib/Data/GoogleCloudStorage.php +++ b/lib/Data/GoogleCloudStorage.php @@ -353,7 +353,7 @@ class GoogleCloudStorage extends AbstractData public function getAllPastes() { $pastes = array(); - $prefix = $this->_prefix; + $prefix = $this->_prefix; if ($prefix != '') { $prefix .= '/'; } @@ -361,7 +361,7 @@ class GoogleCloudStorage extends AbstractData try { foreach ($this->_bucket->objects(array('prefix' => $prefix)) as $object) { $candidate = substr($object->name(), strlen($prefix)); - if (strpos($candidate, "/") === false) { + if (strpos($candidate, '/') === false) { $pastes[] = $candidate; } } diff --git a/lib/Data/S3Storage.php b/lib/Data/S3Storage.php index c59aaaeb..f2746507 100644 --- a/lib/Data/S3Storage.php +++ b/lib/Data/S3Storage.php @@ -78,7 +78,7 @@ class S3Storage extends AbstractData * * @access public * @param array $options - * @return + * @return */ public function __construct(array $options) { @@ -453,15 +453,15 @@ class S3Storage extends AbstractData public function getAllPastes() { $pastes = array(); - $prefix = $this->_prefix; + $prefix = $this->_prefix; if ($prefix != '') { $prefix .= '/'; } try { foreach ($this->_listAllObjects($prefix) as $object) { - $candidate = substr($object["Key"], strlen($prefix)); - if (strpos($candidate, "/") === false) { + $candidate = substr($object['Key'], strlen($prefix)); + if (strpos($candidate, '/') === false) { $pastes[] = $candidate; } } diff --git a/lib/Model.php b/lib/Model.php index 94f77007..f7fdc232 100644 --- a/lib/Model.php +++ b/lib/Model.php @@ -81,7 +81,7 @@ class Model public function getStore() { if ($this->_store === null) { - $class = 'PrivateBin\\Data\\' . $this->_conf->getKey('class', 'model'); + $class = 'PrivateBin\\Data\\' . $this->_conf->getKey('class', 'model'); $this->_store = new $class($this->_conf->getSection('model_options')); } return $this->_store; diff --git a/tst/ModelTest.php b/tst/ModelTest.php index 0d715c2a..cf04b4db 100644 --- a/tst/ModelTest.php +++ b/tst/ModelTest.php @@ -156,7 +156,7 @@ class ModelTest extends PHPUnit_Framework_TestCase public function testCommentDefaults() { - $class = 'PrivateBin\\Data\\' . $this->_conf->getKey('class', 'model'); + $class = 'PrivateBin\\Data\\' . $this->_conf->getKey('class', 'model'); $comment = new Comment( $this->_conf, new $class( From 726f54ce9e0476cbf3c82e0a29ca13ab432e0b7e Mon Sep 17 00:00:00 2001 From: "Felix J. Ogris" Date: Fri, 4 Nov 2022 20:19:41 +0100 Subject: [PATCH 05/36] typos --- lib/Data/AbstractData.php | 2 +- lib/Data/Database.php | 13 +++---------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/lib/Data/AbstractData.php b/lib/Data/AbstractData.php index eddab09b..b28fd539 100644 --- a/lib/Data/AbstractData.php +++ b/lib/Data/AbstractData.php @@ -108,7 +108,7 @@ abstract class AbstractData if ($namespace === 'traffic_limiter') { foreach ($this->_last_cache as $key => $last_submission) { if ($last_submission <= $time) { - unset($thi->_last_cache[$key]); + unset($this->_last_cache[$key]); } } } diff --git a/lib/Data/Database.php b/lib/Data/Database.php index f4318589..5502573c 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -530,16 +530,9 @@ class Database extends AbstractData */ public function getAllPastes() { - $pastes = array(); - $rows = $this->_select( - 'SELECT "dataid" FROM "' . $this->_sanitizeIdentifier('paste') . '"', - array() - ); - if (is_array($rows) && count($rows)) { - foreach ($rows as $row) { - $pastes[] = $row['dataid']; - } - } + $pastes = $this->_db->_query( + 'SELECT "dataid" FROM "' . $this->_sanitizeIdentifier('paste') . '"' + )->fetchAll(PDO::FETCH_COLUMN, 0); return $pastes; } From 3d485ecd7f9dbc508aa1bc072fb7b0a403f0f38c Mon Sep 17 00:00:00 2001 From: "Felix J. Ogris" Date: Fri, 4 Nov 2022 21:04:18 +0100 Subject: [PATCH 06/36] let GCS backends talk to the same "storage account" during testing --- tst/Bootstrap.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tst/Bootstrap.php b/tst/Bootstrap.php index 5b6012f0..f3b77870 100644 --- a/tst/Bootstrap.php +++ b/tst/Bootstrap.php @@ -34,7 +34,7 @@ class StorageClientStub extends StorageClient { private $_config = null; private $_connection = null; - private $_buckets = array(); + 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; } } From 604c9318751f71a4922b7afcd98eaf277974170b Mon Sep 17 00:00:00 2001 From: "Felix J. Ogris" Date: Fri, 4 Nov 2022 21:19:47 +0100 Subject: [PATCH 07/36] remove cache from database backend --- lib/Data/Database.php | 90 ++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 57 deletions(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 5502573c..3aaac84d 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -25,13 +25,6 @@ use PrivateBin\Json; */ class Database extends AbstractData { - /** - * cache for select queries - * - * @var array - */ - private $_cache = array(); - /** * instance of database connection * @@ -149,16 +142,6 @@ class Database extends AbstractData */ public function create($pasteid, array $paste) { - if ( - array_key_exists($pasteid, $this->_cache) - ) { - if (false !== $this->_cache[$pasteid]) { - return false; - } else { - unset($this->_cache[$pasteid]); - } - } - $expire_date = 0; $opendiscussion = $burnafterreading = false; $attachment = $attachmentname = null; @@ -222,64 +205,59 @@ class Database extends AbstractData */ public function read($pasteid) { - if (array_key_exists($pasteid, $this->_cache)) { - return $this->_cache[$pasteid]; - } - - $this->_cache[$pasteid] = false; try { - $paste = $this->_select( + $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) { - $this->_cache[$pasteid] = $data; - list($createdKey) = $this->_getVersionedKeys(2); + $paste = $data; + list($createdKey) = $this->_getVersionedKeys(2); } else { - $this->_cache[$pasteid] = array('data' => $paste['data']); - list($createdKey) = $this->_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); - $this->_cache[$pasteid]['meta'] = $paste['meta']; - $this->_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) { - $this->_cache[$pasteid]['meta']['expire_date'] = $expire_date; + $paste['meta']['expire_date'] = $expire_date; } if ($isVersion2) { - return $this->_cache[$pasteid]; + return $paste; } // support v1 attachments - if (array_key_exists('attachment', $paste) && !empty($paste['attachment'])) { - $this->_cache[$pasteid]['attachment'] = $paste['attachment']; - if (array_key_exists('attachmentname', $paste) && !empty($paste['attachmentname'])) { - $this->_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']) { - $this->_cache[$pasteid]['meta']['opendiscussion'] = true; + if ($row['opendiscussion']) { + $paste['meta']['opendiscussion'] = true; } - if ($paste['burnafterreading']) { - $this->_cache[$pasteid]['meta']['burnafterreading'] = true; + if ($row['burnafterreading']) { + $paste['meta']['burnafterreading'] = true; } - return $this->_cache[$pasteid]; + return $paste; } /** @@ -298,11 +276,6 @@ class Database extends AbstractData 'DELETE FROM "' . $this->_sanitizeIdentifier('comment') . '" WHERE "pasteid" = ?', array($pasteid) ); - if ( - array_key_exists($pasteid, $this->_cache) - ) { - unset($this->_cache[$pasteid]); - } } /** @@ -314,12 +287,15 @@ class Database extends AbstractData */ public function exists($pasteid) { - if ( - !array_key_exists($pasteid, $this->_cache) - ) { - $this->_cache[$pasteid] = $this->read($pasteid); + try { + $row = $this->_select( + 'SELECT * FROM "' . $this->_sanitizeIdentifier('paste') . + '" WHERE "dataid" = ?', array($pasteid), true + ); + } catch (Exception $e) { + $row = false; } - return (bool) $this->_cache[$pasteid]; + return (bool) $row; } /** From 75d28ef423e77b23acb14e0b5c526bcec94a5b90 Mon Sep 17 00:00:00 2001 From: "Felix J. Ogris" Date: Fri, 4 Nov 2022 21:25:53 +0100 Subject: [PATCH 08/36] _sanitizeClob touches no instance variables --- lib/Data/Database.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 3aaac84d..3771b5d8 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -839,10 +839,11 @@ class Database extends AbstractData * From: https://stackoverflow.com/questions/36200534/pdo-oci-into-a-clob-field * * @access public + * @static * @param int|string|resource $value * @return int|string */ - public function _sanitizeClob($value) + public static function _sanitizeClob($value) { if (is_resource($value)) { $value = stream_get_contents($value); From 10013ad09209c068c69d4222ff05f4a2940d4024 Mon Sep 17 00:00:00 2001 From: "Felix J. Ogris" Date: Fri, 4 Nov 2022 21:27:27 +0100 Subject: [PATCH 09/36] syntax bot --- tst/Bootstrap.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tst/Bootstrap.php b/tst/Bootstrap.php index f3b77870..48a91cb8 100644 --- a/tst/Bootstrap.php +++ b/tst/Bootstrap.php @@ -32,9 +32,9 @@ Helper::updateSubresourceIntegrity(); */ class StorageClientStub extends StorageClient { - private $_config = null; - private $_connection = null; - private static $_buckets = array(); + private $_config = null; + private $_connection = null; + private static $_buckets = array(); public function __construct(array $config = array()) { From 8389c2a2d6f9d8085f5dabdbcb588248e58898f0 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 5 Nov 2022 08:46:42 +0100 Subject: [PATCH 10/36] minor optimization, let the PDO driver do that for us --- lib/Data/Database.php | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 3771b5d8..e0678994 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -486,19 +486,13 @@ class Database extends AbstractData */ protected function _getExpiredPastes($batchsize) { - $pastes = array(); - $rows = $this->_select( + $statement = $this->_db->prepare( 'SELECT "dataid" FROM "' . $this->_sanitizeIdentifier('paste') . '" WHERE "expiredate" < ? AND "expiredate" != ? ' . - ($this->_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); } /** @@ -506,10 +500,9 @@ class Database extends AbstractData */ public function getAllPastes() { - $pastes = $this->_db->_query( + return $this->_db->_query( 'SELECT "dataid" FROM "' . $this->_sanitizeIdentifier('paste') . '"' )->fetchAll(PDO::FETCH_COLUMN, 0); - return $pastes; } /** From 62bb68344c129d120b02a9d4b1886fd9f865f0c8 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 5 Nov 2022 09:32:30 +0100 Subject: [PATCH 11/36] move all scripts into one location - standardize includes, namings - made migrate executable - updated ConfigurationCombinationsTest generator to work with current persistance API --- .gitattributes | 2 ++ .../configuration-test-generator | 8 ++++++-- tst/IconTest => bin/icon-test | 0 bin/{migrate.php => migrate} | 9 +++++---- tst/phpunit.xml | 1 - 5 files changed, 13 insertions(+), 7 deletions(-) rename tst/ConfigurationTestGenerator.php => bin/configuration-test-generator (98%) rename tst/IconTest => bin/icon-test (100%) rename bin/{migrate.php => migrate} (96%) mode change 100644 => 100755 diff --git a/.gitattributes b/.gitattributes index 60629c04..c01ff779 100644 --- a/.gitattributes +++ b/.gitattributes @@ -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 diff --git a/tst/ConfigurationTestGenerator.php b/bin/configuration-test-generator similarity index 98% rename from tst/ConfigurationTestGenerator.php rename to bin/configuration-test-generator index a0106a3f..432a2295 100755 --- a/tst/ConfigurationTestGenerator.php +++ b/bin/configuration-test-generator @@ -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); } /** @@ -428,6 +430,8 @@ class ConfigurationCombinationsTest extends PHPUnit_Framework_TestCase Helper::confBackup(); $this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data'; $this->_model = new Filesystem(array('dir' => $this->_path)); + ServerSalt::setStore($this->_model); + TrafficLimiter::setStore($this->_model); $this->reset(); } diff --git a/tst/IconTest b/bin/icon-test similarity index 100% rename from tst/IconTest rename to bin/icon-test diff --git a/bin/migrate.php b/bin/migrate old mode 100644 new mode 100755 similarity index 96% rename from bin/migrate.php rename to bin/migrate index 193430e3..4dbe052b --- a/bin/migrate.php +++ b/bin/migrate @@ -1,7 +1,8 @@ +#!/usr/bin/env php ] - php migrate.php [-h] + migrate [--delete-after] [--delete-during] [-f] [-n] [-v] srcconfdir + [] + migrate [-h] Options: --delete-after delete data from source after all pastes and comments have diff --git a/tst/phpunit.xml b/tst/phpunit.xml index caaa67d7..20eb4d5b 100644 --- a/tst/phpunit.xml +++ b/tst/phpunit.xml @@ -1,7 +1,6 @@ ./ - ConfigurationTestGenerator.php From 833cf93209637d4e88530d08db7e8cc932d0549f Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 5 Nov 2022 09:35:06 +0100 Subject: [PATCH 12/36] address Scrutinizer warning > The variable $bucket does not seem to be defined for all execution paths leading up to this point. --- lib/Data/GoogleCloudStorage.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Data/GoogleCloudStorage.php b/lib/Data/GoogleCloudStorage.php index dd2aff39..e07e8182 100644 --- a/lib/Data/GoogleCloudStorage.php +++ b/lib/Data/GoogleCloudStorage.php @@ -67,7 +67,9 @@ class GoogleCloudStorage extends AbstractData $this->_client = class_exists('StorageClientStub', false) ? new \StorageClientStub(array()) : new StorageClient(array('suppressKeyFileNotice' => true)); - $this->_bucket = $this->_client->bucket($bucket); + if (isset($bucket)) { + $this->_bucket = $this->_client->bucket($bucket); + } } /** From 07ad9ad0f4be3cf6647c8ab8945839178321d15d Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 5 Nov 2022 09:37:24 +0100 Subject: [PATCH 13/36] typo, found by Scrutinizer --- lib/Data/Database.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index e0678994..d4933b00 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -500,7 +500,7 @@ class Database extends AbstractData */ public function getAllPastes() { - return $this->_db->_query( + return $this->_db->query( 'SELECT "dataid" FROM "' . $this->_sanitizeIdentifier('paste') . '"' )->fetchAll(PDO::FETCH_COLUMN, 0); } From 9a1f3aeca505695e54840a1861eceaeddc7bfe11 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 5 Nov 2022 09:44:35 +0100 Subject: [PATCH 14/36] update Jdenticon library --- composer.lock | 418 +++++++----------- vendor/composer/ClassLoader.php | 157 +------ vendor/composer/autoload_classmap.php | 2 +- vendor/composer/autoload_real.php | 15 +- vendor/composer/autoload_static.php | 2 +- .../Rasterization/SuperSampleBuffer.php | 9 + 6 files changed, 196 insertions(+), 407 deletions(-) diff --git a/composer.lock b/composer.lock index 8b17d339..5d13c27b 100644 --- a/composer.lock +++ b/composer.lock @@ -48,11 +48,6 @@ "identicon", "jdenticon" ], - "support": { - "docs": "https://jdenticon.com/php-api.html", - "issues": "https://github.com/dmester/jdenticon-php/issues", - "source": "https://github.com/dmester/jdenticon-php" - }, "time": "2022-10-30T17:15:02+00:00" }, { @@ -110,10 +105,6 @@ "range", "subnet" ], - "support": { - "issues": "https://github.com/mlocati/ip-lib/issues", - "source": "https://github.com/mlocati/ip-lib/tree/1.18.0" - }, "funding": [ { "url": "https://github.com/sponsors/mlocati", @@ -173,11 +164,6 @@ "pseudorandom", "random" ], - "support": { - "email": "info@paragonie.com", - "issues": "https://github.com/paragonie/random_compat/issues", - "source": "https://github.com/paragonie/random_compat" - }, "time": "2022-02-16T17:07:03+00:00" }, { @@ -230,10 +216,6 @@ "identicon", "image" ], - "support": { - "issues": "https://github.com/yzalis/Identicon/issues", - "source": "https://github.com/yzalis/Identicon/tree/master" - }, "abandoned": true, "time": "2019-10-14T09:30:57+00:00" } @@ -241,34 +223,32 @@ "packages-dev": [ { "name": "doctrine/instantiator", - "version": "1.0.5", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", "shasum": "" }, "require": { - "php": ">=5.3,<8.0-DEV" + "php": "^7.1 || ^8.0" }, "require-dev": { - "athletic/athletic": "~0.1.8", + "doctrine/coding-standard": "^9", "ext-pdo": "*", "ext-phar": "*", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~2.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", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, "autoload": { "psr-4": { "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" @@ -282,42 +262,56 @@ { "name": "Marco Pivetta", "email": "ocramius@gmail.com", - "homepage": "http://ocramius.github.com/" + "homepage": "https://ocramius.github.io/" } ], "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", "keywords": [ "constructor", "instantiate" ], - "support": { - "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/master" - }, - "time": "2015-06-14T21:17:01+00:00" + "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": "2022-03-03T08:28:38+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.7.0", + "version": "1.11.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", - "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^7.1 || ^8.0" + }, + "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": "^4.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": { @@ -340,43 +334,40 @@ "object", "object graph" ], - "support": { - "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.x" - }, - "time": "2017-10-19T19:58:43+00:00" + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2022-03-03T13:19:32+00:00" }, { "name": "phpdocumentor/reflection-common", - "version": "1.0.1", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", - "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", "shasum": "" }, "require": { - "php": ">=5.5" - }, - "require-dev": { - "phpunit/phpunit": "^4.6" + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-2.x": "2.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src" - ] + "phpDocumentor\\Reflection\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -398,94 +389,97 @@ "reflection", "static analysis" ], - "support": { - "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", - "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/master" - }, - "time": "2017-09-11T18:02:19+00:00" + "time": "2020-06-27T09:03:43+00:00" }, { "name": "phpdocumentor/reflection-docblock", - "version": "3.3.2", + "version": "5.3.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "bf329f6c1aadea3299f08ee804682b7c45b326a2" + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/bf329f6c1aadea3299f08ee804682b7c45b326a2", - "reference": "bf329f6c1aadea3299f08ee804682b7c45b326a2", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "phpdocumentor/reflection-common": "^1.0.0", - "phpdocumentor/type-resolver": "^0.4.0", - "webmozart/assert": "^1.0" + "ext-filter": "*", + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.3", + "webmozart/assert": "^1.9.1" }, "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^4.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - } - ], - "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "support": { - "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/release/3.x" - }, - "time": "2017-11-10T14:09:06+00:00" - }, - { - "name": "phpdocumentor/type-resolver", - "version": "0.4.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", - "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", - "shasum": "" - }, - "require": { - "php": "^5.5 || ^7.0", - "phpdocumentor/reflection-common": "^1.0" - }, - "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^5.2||^4.8.24" + "mockery/mockery": "~1.3.2", + "psalm/phar": "^4.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "5.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2021-10-19T17:43:47+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.6.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "48f445a408c131e38cab1c235aa6d2bb7a0bb20d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/48f445a408c131e38cab1c235aa6d2bb7a0bb20d", + "reference": "48f445a408c131e38cab1c235aa6d2bb7a0bb20d", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -498,11 +492,8 @@ "email": "me@mikevanriel.com" } ], - "support": { - "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/master" - }, - "time": "2017-07-14T14:27:02+00:00" + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "time": "2022-10-14T12:47:21+00:00" }, { "name": "phpspec/prophecy", @@ -565,10 +556,6 @@ "spy", "stub" ], - "support": { - "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/v1.10.3" - }, "time": "2020-03-05T15:02:03+00:00" }, { @@ -632,11 +619,6 @@ "testing", "xunit" ], - "support": { - "irc": "irc://irc.freenode.net/phpunit", - "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/4.0" - }, "time": "2017-04-02T07:44:40+00:00" }, { @@ -684,11 +666,6 @@ "filesystem", "iterator" ], - "support": { - "irc": "irc://irc.freenode.net/phpunit", - "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/1.4.5" - }, "time": "2017-11-27T13:52:08+00:00" }, { @@ -730,10 +707,6 @@ "keywords": [ "template" ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1" - }, "time": "2015-06-21T13:50:34+00:00" }, { @@ -783,37 +756,33 @@ "keywords": [ "timer" ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/master" - }, "time": "2017-02-26T11:10:40+00:00" }, { "name": "phpunit/php-token-stream", - "version": "1.4.12", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16" + "reference": "791198a2c6254db10131eecfe8c06670700904db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1ce90ba27c42e4e44e6d8458241466380b51fa16", - "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", + "reference": "791198a2c6254db10131eecfe8c06670700904db", "shasum": "" }, "require": { "ext-tokenizer": "*", - "php": ">=5.3.3" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "~4.2" + "phpunit/phpunit": "^6.2.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -836,12 +805,8 @@ "keywords": [ "tokenizer" ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-token-stream/issues", - "source": "https://github.com/sebastianbergmann/php-token-stream/tree/1.4" - }, "abandoned": true, - "time": "2017-12-04T08:55:13+00:00" + "time": "2017-11-27T05:48:46+00:00" }, { "name": "phpunit/phpunit", @@ -923,10 +888,6 @@ "testing", "xunit" ], - "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/5.7.27" - }, "time": "2018-02-01T05:50:59+00:00" }, { @@ -986,11 +947,6 @@ "mock", "xunit" ], - "support": { - "irc": "irc://irc.freenode.net/phpunit", - "issues": "https://github.com/sebastianbergmann/phpunit-mock-objects/issues", - "source": "https://github.com/sebastianbergmann/phpunit-mock-objects/tree/3.4" - }, "abandoned": true, "time": "2017-06-30T09:13:00+00:00" }, @@ -1037,10 +993,6 @@ ], "description": "Looks up which function or method a line of code belongs to", "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.2" - }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -1111,10 +1063,6 @@ "compare", "equality" ], - "support": { - "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/1.2" - }, "time": "2017-01-29T09:50:25+00:00" }, { @@ -1167,10 +1115,6 @@ "keywords": [ "diff" ], - "support": { - "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/1.4" - }, "time": "2017-05-22T07:24:03+00:00" }, { @@ -1221,10 +1165,6 @@ "environment", "hhvm" ], - "support": { - "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/master" - }, "time": "2016-11-26T07:53:53+00:00" }, { @@ -1292,10 +1232,6 @@ "export", "exporter" ], - "support": { - "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/master" - }, "time": "2016-11-19T08:54:04+00:00" }, { @@ -1347,10 +1283,6 @@ "keywords": [ "global state" ], - "support": { - "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/1.1.1" - }, "time": "2015-10-12T03:26:01+00:00" }, { @@ -1397,10 +1329,6 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/master" - }, "time": "2017-02-18T15:18:39+00:00" }, { @@ -1454,10 +1382,6 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "support": { - "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/master" - }, "time": "2016-11-19T07:33:16+00:00" }, { @@ -1500,10 +1424,6 @@ ], "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/master" - }, "time": "2015-07-28T20:34:47+00:00" }, { @@ -1547,28 +1467,27 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", - "support": { - "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/master" - }, "time": "2016-10-03T07:35:21+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.19.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "aed596913b70fae57be53d86faa2e9ef85a2297b" + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/aed596913b70fae57be53d86faa2e9ef85a2297b", - "reference": "aed596913b70fae57be53d86faa2e9ef85a2297b", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" }, "suggest": { "ext-ctype": "For best performance" @@ -1576,7 +1495,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.19-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1613,9 +1532,6 @@ "polyfill", "portable" ], - "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.19.0" - }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -1630,31 +1546,31 @@ "type": "tidelift" } ], - "time": "2020-10-23T09:01:57+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/yaml", - "version": "v3.4.47", + "version": "v4.4.45", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "88289caa3c166321883f67fe5130188ebbb47094" + "reference": "aeccc4dc52a9e634f1d1eebeb21eacfdcff1053d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/88289caa3c166321883f67fe5130188ebbb47094", - "reference": "88289caa3c166321883f67fe5130188ebbb47094", + "url": "https://api.github.com/repos/symfony/yaml/zipball/aeccc4dc52a9e634f1d1eebeb21eacfdcff1053d", + "reference": "aeccc4dc52a9e634f1d1eebeb21eacfdcff1053d", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", + "php": ">=7.1.3", "symfony/polyfill-ctype": "~1.8" }, "conflict": { "symfony/console": "<3.4" }, "require-dev": { - "symfony/console": "~3.4|~4.0" + "symfony/console": "^3.4|^4.0|^5.0" }, "suggest": { "symfony/console": "For validating YAML files using the lint command" @@ -1682,11 +1598,8 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Yaml Component", + "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/yaml/tree/v3.4.47" - }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -1701,34 +1614,39 @@ "type": "tidelift" } ], - "time": "2020-10-24T10:57:07+00:00" + "time": "2022-08-02T15:47:23+00:00" }, { "name": "webmozart/assert", - "version": "1.9.1", + "version": "1.11.0", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", - "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0 || ^8.0", - "symfony/polyfill-ctype": "^1.8" + "ext-ctype": "*", + "php": "^7.2 || ^8.0" }, "conflict": { "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<3.9.1" + "vimeo/psalm": "<4.6.1 || 4.6.2" }, "require-dev": { - "phpunit/phpunit": "^4.8.36 || ^7.5.13" + "phpunit/phpunit": "^8.5.13" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, "autoload": { "psr-4": { "Webmozart\\Assert\\": "src/" @@ -1750,11 +1668,7 @@ "check", "validate" ], - "support": { - "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.9.1" - }, - "time": "2020-07-08T17:02:28+00:00" + "time": "2022-06-03T18:03:27+00:00" } ], "aliases": [], @@ -1766,5 +1680,5 @@ "php": "^5.6.0 || ^7.0 || ^8.0" }, "platform-dev": [], - "plugin-api-version": "2.3.0" + "plugin-api-version": "1.1.0" } diff --git a/vendor/composer/ClassLoader.php b/vendor/composer/ClassLoader.php index afef3fa2..fce8549f 100644 --- a/vendor/composer/ClassLoader.php +++ b/vendor/composer/ClassLoader.php @@ -37,130 +37,57 @@ namespace Composer\Autoload; * * @author Fabien Potencier * @author Jordi Boggiano - * @see https://www.php-fig.org/psr/psr-0/ - * @see https://www.php-fig.org/psr/psr-4/ + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ */ class ClassLoader { - /** @var ?string */ - private $vendorDir; - // PSR-4 - /** - * @var array[] - * @psalm-var array> - */ private $prefixLengthsPsr4 = array(); - /** - * @var array[] - * @psalm-var array> - */ private $prefixDirsPsr4 = array(); - /** - * @var array[] - * @psalm-var array - */ private $fallbackDirsPsr4 = array(); // PSR-0 - /** - * @var array[] - * @psalm-var array> - */ private $prefixesPsr0 = array(); - /** - * @var array[] - * @psalm-var array - */ private $fallbackDirsPsr0 = array(); - /** @var bool */ private $useIncludePath = false; - - /** - * @var string[] - * @psalm-var array - */ private $classMap = array(); - - /** @var bool */ private $classMapAuthoritative = false; - - /** - * @var bool[] - * @psalm-var array - */ private $missingClasses = array(); - - /** @var ?string */ private $apcuPrefix; - /** - * @var self[] - */ - private static $registeredLoaders = array(); - - /** - * @param ?string $vendorDir - */ - public function __construct($vendorDir = null) - { - $this->vendorDir = $vendorDir; - } - - /** - * @return string[] - */ public function getPrefixes() { if (!empty($this->prefixesPsr0)) { - return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + return call_user_func_array('array_merge', $this->prefixesPsr0); } return array(); } - /** - * @return array[] - * @psalm-return array> - */ public function getPrefixesPsr4() { return $this->prefixDirsPsr4; } - /** - * @return array[] - * @psalm-return array - */ public function getFallbackDirs() { return $this->fallbackDirsPsr0; } - /** - * @return array[] - * @psalm-return array - */ public function getFallbackDirsPsr4() { return $this->fallbackDirsPsr4; } - /** - * @return string[] Array of classname => path - * @psalm-return array - */ public function getClassMap() { return $this->classMap; } /** - * @param string[] $classMap Class to filename map - * @psalm-param array $classMap - * - * @return void + * @param array $classMap Class to filename map */ public function addClassMap(array $classMap) { @@ -175,11 +102,9 @@ class ClassLoader * Registers a set of PSR-0 directories for a given prefix, either * appending or prepending to the ones previously set for this prefix. * - * @param string $prefix The prefix - * @param string[]|string $paths The PSR-0 root directories - * @param bool $prepend Whether to prepend the directories - * - * @return void + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories */ public function add($prefix, $paths, $prepend = false) { @@ -222,13 +147,11 @@ class ClassLoader * Registers a set of PSR-4 directories for a given namespace, either * appending or prepending to the ones previously set for this namespace. * - * @param string $prefix The prefix/namespace, with trailing '\\' - * @param string[]|string $paths The PSR-4 base directories - * @param bool $prepend Whether to prepend the directories + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories * * @throws \InvalidArgumentException - * - * @return void */ public function addPsr4($prefix, $paths, $prepend = false) { @@ -272,10 +195,8 @@ class ClassLoader * Registers a set of PSR-0 directories for a given prefix, * replacing any others previously set for this prefix. * - * @param string $prefix The prefix - * @param string[]|string $paths The PSR-0 base directories - * - * @return void + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories */ public function set($prefix, $paths) { @@ -290,12 +211,10 @@ class ClassLoader * Registers a set of PSR-4 directories for a given namespace, * replacing any others previously set for this namespace. * - * @param string $prefix The prefix/namespace, with trailing '\\' - * @param string[]|string $paths The PSR-4 base directories + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories * * @throws \InvalidArgumentException - * - * @return void */ public function setPsr4($prefix, $paths) { @@ -315,8 +234,6 @@ class ClassLoader * Turns on searching the include path for class files. * * @param bool $useIncludePath - * - * @return void */ public function setUseIncludePath($useIncludePath) { @@ -339,8 +256,6 @@ class ClassLoader * that have not been registered with the class map. * * @param bool $classMapAuthoritative - * - * @return void */ public function setClassMapAuthoritative($classMapAuthoritative) { @@ -361,8 +276,6 @@ class ClassLoader * APCu prefix to use to cache found/not-found classes, if the extension is enabled. * * @param string|null $apcuPrefix - * - * @return void */ public function setApcuPrefix($apcuPrefix) { @@ -383,44 +296,25 @@ class ClassLoader * Registers this instance as an autoloader. * * @param bool $prepend Whether to prepend the autoloader or not - * - * @return void */ public function register($prepend = false) { spl_autoload_register(array($this, 'loadClass'), true, $prepend); - - if (null === $this->vendorDir) { - return; - } - - if ($prepend) { - self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; - } else { - unset(self::$registeredLoaders[$this->vendorDir]); - self::$registeredLoaders[$this->vendorDir] = $this; - } } /** * Unregisters this instance as an autoloader. - * - * @return void */ public function unregister() { spl_autoload_unregister(array($this, 'loadClass')); - - if (null !== $this->vendorDir) { - unset(self::$registeredLoaders[$this->vendorDir]); - } } /** * Loads the given class or interface. * * @param string $class The name of the class - * @return true|null True if loaded, null otherwise + * @return bool|null True if loaded, null otherwise */ public function loadClass($class) { @@ -429,8 +323,6 @@ class ClassLoader return true; } - - return null; } /** @@ -475,21 +367,6 @@ class ClassLoader return $file; } - /** - * Returns the currently registered loaders indexed by their corresponding vendor directories. - * - * @return self[] - */ - public static function getRegisteredLoaders() - { - return self::$registeredLoaders; - } - - /** - * @param string $class - * @param string $ext - * @return string|false - */ private function findFileWithExtension($class, $ext) { // PSR-4 lookup @@ -561,10 +438,6 @@ class ClassLoader * Scope isolated include. * * Prevents access to $this/self from included files. - * - * @param string $file - * @return void - * @private */ function includeFile($file) { diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php index ef2b6e97..12566b84 100644 --- a/vendor/composer/autoload_classmap.php +++ b/vendor/composer/autoload_classmap.php @@ -6,7 +6,6 @@ $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( - 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', 'IPLib\\Address\\AddressInterface' => $vendorDir . '/mlocati/ip-lib/src/Address/AddressInterface.php', 'IPLib\\Address\\AssignedRange' => $vendorDir . '/mlocati/ip-lib/src/Address/AssignedRange.php', 'IPLib\\Address\\IPv4' => $vendorDir . '/mlocati/ip-lib/src/Address/IPv4.php', @@ -53,6 +52,7 @@ return array( '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', diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php index 5b03524d..7a6fd4c0 100644 --- a/vendor/composer/autoload_real.php +++ b/vendor/composer/autoload_real.php @@ -22,15 +22,13 @@ class ComposerAutoloaderInitDontChange return self::$loader; } - require __DIR__ . '/platform_check.php'; - spl_autoload_register(array('ComposerAutoloaderInitDontChange', 'loadClassLoader'), true, true); - self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__))); + self::$loader = $loader = new \Composer\Autoload\ClassLoader(); spl_autoload_unregister(array('ComposerAutoloaderInitDontChange', 'loadClassLoader')); $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); if ($useStaticLoader) { - require __DIR__ . '/autoload_static.php'; + require_once __DIR__ . '/autoload_static.php'; call_user_func(\Composer\Autoload\ComposerStaticInitDontChange::getInitializer($loader)); } else { @@ -65,16 +63,11 @@ class ComposerAutoloaderInitDontChange } } -/** - * @param string $fileIdentifier - * @param string $file - * @return void - */ function composerRequireDontChange($fileIdentifier, $file) { if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { - $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; - require $file; + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; } } diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index b03812eb..d6b11453 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -46,7 +46,6 @@ class ComposerStaticInitDontChange ); public static $classMap = array ( - 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 'IPLib\\Address\\AddressInterface' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/AddressInterface.php', 'IPLib\\Address\\AssignedRange' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/AssignedRange.php', 'IPLib\\Address\\IPv4' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/IPv4.php', @@ -93,6 +92,7 @@ class ComposerStaticInitDontChange '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', diff --git a/vendor/jdenticon/jdenticon/src/Canvas/Rasterization/SuperSampleBuffer.php b/vendor/jdenticon/jdenticon/src/Canvas/Rasterization/SuperSampleBuffer.php index 82860e2e..45a611de 100644 --- a/vendor/jdenticon/jdenticon/src/Canvas/Rasterization/SuperSampleBuffer.php +++ b/vendor/jdenticon/jdenticon/src/Canvas/Rasterization/SuperSampleBuffer.php @@ -21,6 +21,15 @@ class SuperSampleBuffer 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. From f4eed668e7d7e0d689957db1b04e33dc7bdfb319 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 5 Nov 2022 10:00:12 +0100 Subject: [PATCH 15/36] seems impacted by removal of cache, let's see if we can adjust the test --- tst/ModelTest.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tst/ModelTest.php b/tst/ModelTest.php index e693856c..dbbbbc22 100644 --- a/tst/ModelTest.php +++ b/tst/ModelTest.php @@ -261,6 +261,9 @@ class ModelTest extends PHPUnit_Framework_TestCase $paste->store(); $paste->exists(); + $comment = $paste->getComment(Helper::getPasteId()); + $comment->setData($commentData); + $db = new PDO( $options['model_options']['dsn'], $options['model_options']['usr'], @@ -271,8 +274,6 @@ class ModelTest extends PHPUnit_Framework_TestCase $statement->execute(); $statement->closeCursor(); - $comment = $paste->getComment(Helper::getPasteId()); - $comment->setData($commentData); $comment->store(); } From 15ce736bc87f335439dfe1c195622531d0ec72b1 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sat, 5 Nov 2022 20:25:29 +0100 Subject: [PATCH 16/36] New translations en.json (Thai) --- i18n/th.json | 193 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 i18n/th.json diff --git a/i18n/th.json b/i18n/th.json new file mode 100644 index 00000000..a4d6d350 --- /dev/null +++ b/i18n/th.json @@ -0,0 +1,193 @@ +{ + "PrivateBin": "PrivateBin", + "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.", + "More information on the project page.": "More information on the project page.", + "Because ignorance is bliss": "Because ignorance is bliss", + "en": "en", + "Paste does not exist, has expired or has been deleted.": "Paste does not exist, has expired or has been deleted.", + "%s requires php %s or above to work. Sorry.": "%s requires php %s or above to work. Sorry.", + "%s requires configuration section [%s] to be present in configuration file.": "%s requires configuration section [%s] to be present in configuration file.", + "Please wait %d seconds between each post.": [ + "Please wait %d second between each post. (singular)", + "Please wait %d seconds between each post. (1st plural)", + "Please wait %d seconds between each post. (2nd plural)", + "Please wait %d seconds between each post. (3rd plural)" + ], + "Paste is limited to %s of encrypted data.": "Paste is limited to %s of encrypted data.", + "Invalid data.": "Invalid data.", + "You are unlucky. Try again.": "You are unlucky. Try again.", + "Error saving comment. Sorry.": "Error saving comment. Sorry.", + "Error saving paste. Sorry.": "Error saving paste. Sorry.", + "Invalid paste ID.": "Invalid paste ID.", + "Paste is not of burn-after-reading type.": "Paste is not of burn-after-reading type.", + "Wrong deletion token. Paste was not deleted.": "Wrong deletion token. Paste was not deleted.", + "Paste was properly deleted.": "Paste was properly deleted.", + "JavaScript is required for %s to work. Sorry for the inconvenience.": "JavaScript is required for %s to work. Sorry for the inconvenience.", + "%s requires a modern browser to work.": "%s requires a modern browser to work.", + "New": "New", + "Send": "Send", + "Clone": "Clone", + "Raw text": "Raw text", + "Expires": "Expires", + "Burn after reading": "Burn after reading", + "Open discussion": "Open discussion", + "Password (recommended)": "Password (recommended)", + "Discussion": "Discussion", + "Toggle navigation": "Toggle navigation", + "%d seconds": [ + "%d second (singular)", + "%d seconds (1st plural)", + "%d seconds (2nd plural)", + "%d seconds (3rd plural)" + ], + "%d minutes": [ + "%d minute (singular)", + "%d minutes (1st plural)", + "%d minutes (2nd plural)", + "%d minutes (3rd plural)" + ], + "%d hours": [ + "%d hour (singular)", + "%d hours (1st plural)", + "%d hours (2nd plural)", + "%d hours (3rd plural)" + ], + "%d days": [ + "%d day (singular)", + "%d days (1st plural)", + "%d days (2nd plural)", + "%d days (3rd plural)" + ], + "%d weeks": [ + "%d week (singular)", + "%d weeks (1st plural)", + "%d weeks (2nd plural)", + "%d weeks (3rd plural)" + ], + "%d months": [ + "%d month (singular)", + "%d months (1st plural)", + "%d months (2nd plural)", + "%d months (3rd plural)" + ], + "%d years": [ + "%d year (singular)", + "%d years (1st plural)", + "%d years (2nd plural)", + "%d years (3rd plural)" + ], + "Never": "Never", + "Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.": "Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.", + "This document will expire in %d seconds.": [ + "This document will expire in %d second. (singular)", + "This document will expire in %d seconds. (1st plural)", + "This document will expire in %d seconds. (2nd plural)", + "This document will expire in %d seconds. (3rd plural)" + ], + "This document will expire in %d minutes.": [ + "This document will expire in %d minute. (singular)", + "This document will expire in %d minutes. (1st plural)", + "This document will expire in %d minutes. (2nd plural)", + "This document will expire in %d minutes. (3rd plural)" + ], + "This document will expire in %d hours.": [ + "This document will expire in %d hour. (singular)", + "This document will expire in %d hours. (1st plural)", + "This document will expire in %d hours. (2nd plural)", + "This document will expire in %d hours. (3rd plural)" + ], + "This document will expire in %d days.": [ + "This document will expire in %d day. (singular)", + "This document will expire in %d days. (1st plural)", + "This document will expire in %d days. (2nd plural)", + "This document will expire in %d days. (3rd plural)" + ], + "This document will expire in %d months.": [ + "This document will expire in %d month. (singular)", + "This document will expire in %d months. (1st plural)", + "This document will expire in %d months. (2nd plural)", + "This document will expire in %d months. (3rd plural)" + ], + "Please enter the password for this paste:": "Please enter the password for this paste:", + "Could not decrypt data (Wrong key?)": "Could not decrypt data (Wrong key?)", + "Could not delete the paste, it was not stored in burn after reading mode.": "Could not delete the paste, it was not stored in burn after reading mode.", + "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.": "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.", + "Could not decrypt comment; Wrong key?": "Could not decrypt comment; Wrong key?", + "Reply": "Reply", + "Anonymous": "Anonymous", + "Avatar generated from IP address": "Avatar generated from IP address", + "Add comment": "Add comment", + "Optional nickname…": "Optional nickname…", + "Post comment": "Post comment", + "Sending comment…": "Sending comment…", + "Comment posted.": "Comment posted.", + "Could not refresh display: %s": "Could not refresh display: %s", + "unknown status": "unknown status", + "server error or not responding": "server error or not responding", + "Could not post comment: %s": "Could not post comment: %s", + "Sending paste…": "Sending paste…", + "Your paste is %s (Hit [Ctrl]+[c] to copy)": "Your paste is %s (Hit [Ctrl]+[c] to copy)", + "Delete data": "Delete data", + "Could not create paste: %s": "Could not create paste: %s", + "Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)": "Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)", + "B": "B", + "KiB": "KiB", + "MiB": "MiB", + "GiB": "GiB", + "TiB": "TiB", + "PiB": "PiB", + "EiB": "EiB", + "ZiB": "ZiB", + "YiB": "YiB", + "Format": "Format", + "Plain Text": "Plain Text", + "Source Code": "Source Code", + "Markdown": "Markdown", + "Download attachment": "Download attachment", + "Cloned: '%s'": "Cloned: '%s'", + "The cloned file '%s' was attached to this paste.": "The cloned file '%s' was attached to this paste.", + "Attach a file": "Attach a file", + "alternatively drag & drop a file or paste an image from the clipboard": "alternatively drag & drop a file or paste an image from the clipboard", + "File too large, to display a preview. Please download the attachment.": "File too large, to display a preview. Please download the attachment.", + "Remove attachment": "Remove attachment", + "Your browser does not support uploading encrypted files. Please use a newer browser.": "Your browser does not support uploading encrypted files. Please use a newer browser.", + "Invalid attachment.": "Invalid attachment.", + "Options": "Options", + "Shorten URL": "Shorten URL", + "Editor": "Editor", + "Preview": "Preview", + "%s requires the PATH to end in a \"%s\". Please update the PATH in your index.php.": "%s requires the PATH to end in a \"%s\". Please update the PATH in your index.php.", + "Decrypt": "Decrypt", + "Enter password": "Enter password", + "Loading…": "Loading…", + "Decrypting paste…": "Decrypting paste…", + "Preparing new paste…": "Preparing new paste…", + "In case this message never disappears please have a look at this FAQ for information to troubleshoot.": "In case this message never disappears please have a look at this FAQ for information to troubleshoot.", + "+++ no paste text +++": "+++ no paste text +++", + "Could not get paste data: %s": "Could not get paste data: %s", + "QR code": "QR code", + "This website is using an insecure HTTP connection! Please use it only for testing.": "This website is using an insecure HTTP connection! Please use it only for testing.", + "For more information see this FAQ entry.": "For more information see this FAQ entry.", + "Your browser may require an HTTPS connection to support the WebCrypto API. Try switching to HTTPS.": "Your browser may require an HTTPS connection to support the WebCrypto API. Try switching to HTTPS.", + "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.": "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.", + "waiting on user to provide a password": "waiting on user to provide a password", + "Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.": "Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.", + "Retry": "Retry", + "Showing raw text…": "Showing raw text…", + "Notice:": "Notice:", + "This link will expire after %s.": "This link will expire after %s.", + "This link can only be accessed once, do not use back or refresh button in your browser.": "This link can only be accessed once, do not use back or refresh button in your browser.", + "Link:": "Link:", + "Recipient may become aware of your timezone, convert time to UTC?": "Recipient may become aware of your timezone, convert time to UTC?", + "Use Current Timezone": "Use Current Timezone", + "Convert To UTC": "Convert To UTC", + "Close": "Close", + "Encrypted note on %s": "Encrypted note on %s", + "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Save paste": "Save paste", + "Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes.", + "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." +} From d46398fc22264e196614f95b88071b29edcee5c5 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sat, 5 Nov 2022 23:30:32 +0100 Subject: [PATCH 17/36] New translations en.json (Thai) --- i18n/th.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/th.json b/i18n/th.json index a4d6d350..1bba8504 100644 --- a/i18n/th.json +++ b/i18n/th.json @@ -3,7 +3,7 @@ "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.", "More information on the project page.": "More information on the project page.", "Because ignorance is bliss": "Because ignorance is bliss", - "en": "en", + "en": "th", "Paste does not exist, has expired or has been deleted.": "Paste does not exist, has expired or has been deleted.", "%s requires php %s or above to work. Sorry.": "%s requires php %s or above to work. Sorry.", "%s requires configuration section [%s] to be present in configuration file.": "%s requires configuration section [%s] to be present in configuration file.", From 3e3d93c9c24ab996a140142d82a35b3d50447d84 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 6 Nov 2022 02:18:55 +0100 Subject: [PATCH 18/36] New translations en.json (Thai) --- i18n/th.json | 306 +++++++++++++++++++++++++-------------------------- 1 file changed, 153 insertions(+), 153 deletions(-) diff --git a/i18n/th.json b/i18n/th.json index 1bba8504..35e6209e 100644 --- a/i18n/th.json +++ b/i18n/th.json @@ -1,135 +1,135 @@ { "PrivateBin": "PrivateBin", - "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.", - "More information on the project page.": "More information on the project page.", - "Because ignorance is bliss": "Because ignorance is bliss", + "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s เป็น pastebin ออนไลน์แบบโอเพ่นซอร์สที่มีสไตล์แบบมินิมัลลิสท์ เซิร์ฟเวอร์ไม่สามารถรู้ได้ว่าข้อมูลโค้ดที่มาฝากนั้นเป็นข้อมูลอะไร โดยจะถูก %sเข้ารหัส/ถอดรหัส%s ด้วยกระบวนการ AES จำนวน 256 บิตผ่านเบราว์เซอร์", + "More information on the project page.": "ข้อมูลเพิ่มเติมในหน้าโครงการ", + "Because ignorance is bliss": "ไม่รู้ไม่ชี้ดีที่สุด", "en": "th", - "Paste does not exist, has expired or has been deleted.": "Paste does not exist, has expired or has been deleted.", - "%s requires php %s or above to work. Sorry.": "%s requires php %s or above to work. Sorry.", - "%s requires configuration section [%s] to be present in configuration file.": "%s requires configuration section [%s] to be present in configuration file.", + "Paste does not exist, has expired or has been deleted.": "การฝากโค้ดไม่มีอยู่ อาจจะหมดอายุหรือถูกลบไปแล้ว", + "%s requires php %s or above to work. Sorry.": "ขออภัย %s ต้องใช้ PHP %s ขึ้นไปจึงจะใช้งานได้", + "%s requires configuration section [%s] to be present in configuration file.": "%s จำเป็นต้องตั้งค่าตัวแปร [%s] ในไฟล์กำหนดค่า", "Please wait %d seconds between each post.": [ - "Please wait %d second between each post. (singular)", - "Please wait %d seconds between each post. (1st plural)", - "Please wait %d seconds between each post. (2nd plural)", - "Please wait %d seconds between each post. (3rd plural)" + "กรุณาเว้นระยะเวลาการส่งข้อมูลอย่างน้อย %d วินาที", + "กรุณาเว้นระยะเวลาการส่งข้อมูลอย่างน้อย %d วินาที", + "กรุณาเว้นระยะเวลาการส่งข้อมูลอย่างน้อย %d วินาที", + "กรุณาเว้นระยะเวลาการส่งข้อมูลอย่างน้อย %d วินาที" ], - "Paste is limited to %s of encrypted data.": "Paste is limited to %s of encrypted data.", - "Invalid data.": "Invalid data.", - "You are unlucky. Try again.": "You are unlucky. Try again.", - "Error saving comment. Sorry.": "Error saving comment. Sorry.", - "Error saving paste. Sorry.": "Error saving paste. Sorry.", - "Invalid paste ID.": "Invalid paste ID.", - "Paste is not of burn-after-reading type.": "Paste is not of burn-after-reading type.", - "Wrong deletion token. Paste was not deleted.": "Wrong deletion token. Paste was not deleted.", - "Paste was properly deleted.": "Paste was properly deleted.", - "JavaScript is required for %s to work. Sorry for the inconvenience.": "JavaScript is required for %s to work. Sorry for the inconvenience.", - "%s requires a modern browser to work.": "%s requires a modern browser to work.", - "New": "New", - "Send": "Send", - "Clone": "Clone", - "Raw text": "Raw text", - "Expires": "Expires", - "Burn after reading": "Burn after reading", - "Open discussion": "Open discussion", - "Password (recommended)": "Password (recommended)", - "Discussion": "Discussion", - "Toggle navigation": "Toggle navigation", + "Paste is limited to %s of encrypted data.": "การฝากโค้ดแบบเข้ารหัส ขีดจำกัดสูงสุดคือ %s", + "Invalid data.": "ข้อมูลไม่ถูกต้อง", + "You are unlucky. Try again.": "วันนี้คุณดวงไม่เฮงเลย ลองใหม่อีกครั้งนะ", + "Error saving comment. Sorry.": "ขออภัย เกิดข้อผิดพลาดในระหว่างบันทึกความคิดเห็น", + "Error saving paste. Sorry.": "ขออภัย เกิดข้อผิดพลาดในระหว่างบันทึกการฝากโค้ด", + "Invalid paste ID.": "ID การฝากโค้ดไม่ถูกต้อง", + "Paste is not of burn-after-reading type.": "ข้อมูลการฝากโค้ดนี้ไม่ได้เป็นรูปแบบลบทันทีเมื่อเปิดอ่าน", + "Wrong deletion token. Paste was not deleted.": "โทเค็นการลบไม่ถูกต้อง ข้อมูลการฝากโค้ดไม่ถูกลบ", + "Paste was properly deleted.": "ข้อมูลการฝากโค้ดถูกลบออกเรียบร้อยแล้ว", + "JavaScript is required for %s to work. Sorry for the inconvenience.": "จำเป็นต้องใช้ JavaScript เพื่อให้ %s สามารถทำงานได้ ขออภัยในความไม่สะดวก", + "%s requires a modern browser to work.": "%s ต้องใช้เบราว์เซอร์สมัยใหม่ถึงจะสามารถใช้งานได้", + "New": "ใหม่", + "Send": "ส่ง", + "Clone": "โคลน", + "Raw text": "ข้อความล้วน", + "Expires": "หมดอายุ", + "Burn after reading": "ลบทันทีเมื่อเปิดอ่าน", + "Open discussion": "แสดงความคิดเห็นได้", + "Password (recommended)": "รหัสผ่าน (แนะนำให้ใส่)", + "Discussion": "ความคิดเห็น", + "Toggle navigation": "สลับเปิดปิดการนำทาง", "%d seconds": [ - "%d second (singular)", - "%d seconds (1st plural)", - "%d seconds (2nd plural)", - "%d seconds (3rd plural)" + "%d วินาที", + "%d วินาที", + "%d วินาที", + "%d วินาที" ], "%d minutes": [ - "%d minute (singular)", - "%d minutes (1st plural)", - "%d minutes (2nd plural)", - "%d minutes (3rd plural)" + "%d นาที", + "%d นาที", + "%d นาที", + "%d นาที" ], "%d hours": [ - "%d hour (singular)", - "%d hours (1st plural)", - "%d hours (2nd plural)", - "%d hours (3rd plural)" + "%d ชั่วโมง", + "%d ชั่วโมง", + "%d ชั่วโมง", + "%d ชั่วโมง" ], "%d days": [ - "%d day (singular)", - "%d days (1st plural)", - "%d days (2nd plural)", - "%d days (3rd plural)" + "%d วัน", + "%d วัน", + "%d วัน", + "%d วัน" ], "%d weeks": [ - "%d week (singular)", - "%d weeks (1st plural)", - "%d weeks (2nd plural)", - "%d weeks (3rd plural)" + "%d สัปดาห์", + "%d สัปดาห์", + "%d สัปดาห์", + "%d สัปดาห์" ], "%d months": [ - "%d month (singular)", - "%d months (1st plural)", - "%d months (2nd plural)", - "%d months (3rd plural)" + "%d เดือน", + "%d เดือน", + "%d เดือน", + "%d เดือน" ], "%d years": [ - "%d year (singular)", - "%d years (1st plural)", - "%d years (2nd plural)", - "%d years (3rd plural)" + "%d ปี", + "%d ปี", + "%d ปี", + "%d ปี" ], - "Never": "Never", - "Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.": "Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.", + "Never": "ไม่หมดอายุ", + "Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.": "หมายเหตุ: เว็บไซต์นี่เป็นบริการเบต้าและข้อมูลอาจถูกลบได้ตลอดเวลา หากคุณใช้บริการนี้ในทางที่ผิดอาจจะทำให้ข้อมูลของคุณสูญหายอย่างถาวรได้", "This document will expire in %d seconds.": [ - "This document will expire in %d second. (singular)", - "This document will expire in %d seconds. (1st plural)", - "This document will expire in %d seconds. (2nd plural)", - "This document will expire in %d seconds. (3rd plural)" + "เอกสารนี้จะหมดอายุใน %d วินาที", + "เอกสารนี้จะหมดอายุใน %d วินาที", + "เอกสารนี้จะหมดอายุใน %d วินาที", + "เอกสารนี้จะหมดอายุใน %d วินาที" ], "This document will expire in %d minutes.": [ - "This document will expire in %d minute. (singular)", - "This document will expire in %d minutes. (1st plural)", - "This document will expire in %d minutes. (2nd plural)", - "This document will expire in %d minutes. (3rd plural)" + "เอกสารนี้จะหมดอายุใน %d นาที", + "เอกสารนี้จะหมดอายุใน %d นาที", + "เอกสารนี้จะหมดอายุใน %d นาที", + "เอกสารนี้จะหมดอายุใน %d นาที" ], "This document will expire in %d hours.": [ - "This document will expire in %d hour. (singular)", - "This document will expire in %d hours. (1st plural)", - "This document will expire in %d hours. (2nd plural)", - "This document will expire in %d hours. (3rd plural)" + "เอกสารนี้จะหมดอายุใน %d ชั่วโมง", + "เอกสารนี้จะหมดอายุใน %d ชั่วโมง", + "เอกสารนี้จะหมดอายุใน %d ชั่วโมง", + "เอกสารนี้จะหมดอายุใน %d ชั่วโมง" ], "This document will expire in %d days.": [ - "This document will expire in %d day. (singular)", - "This document will expire in %d days. (1st plural)", - "This document will expire in %d days. (2nd plural)", - "This document will expire in %d days. (3rd plural)" + "เอกสารนี้จะหมดอายุใน %d วัน", + "เอกสารนี้จะหมดอายุใน %d วัน", + "เอกสารนี้จะหมดอายุใน %d วัน", + "เอกสารนี้จะหมดอายุใน %d วัน" ], "This document will expire in %d months.": [ - "This document will expire in %d month. (singular)", - "This document will expire in %d months. (1st plural)", - "This document will expire in %d months. (2nd plural)", - "This document will expire in %d months. (3rd plural)" + "เอกสารนี้จะหมดอายุใน %d เดือน", + "เอกสารนี้จะหมดอายุใน %d เดือน", + "เอกสารนี้จะหมดอายุใน %d เดือน", + "เอกสารนี้จะหมดอายุใน %d เดือน" ], - "Please enter the password for this paste:": "Please enter the password for this paste:", - "Could not decrypt data (Wrong key?)": "Could not decrypt data (Wrong key?)", - "Could not delete the paste, it was not stored in burn after reading mode.": "Could not delete the paste, it was not stored in burn after reading mode.", - "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.": "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.", - "Could not decrypt comment; Wrong key?": "Could not decrypt comment; Wrong key?", - "Reply": "Reply", - "Anonymous": "Anonymous", - "Avatar generated from IP address": "Avatar generated from IP address", - "Add comment": "Add comment", - "Optional nickname…": "Optional nickname…", - "Post comment": "Post comment", - "Sending comment…": "Sending comment…", - "Comment posted.": "Comment posted.", - "Could not refresh display: %s": "Could not refresh display: %s", - "unknown status": "unknown status", - "server error or not responding": "server error or not responding", - "Could not post comment: %s": "Could not post comment: %s", - "Sending paste…": "Sending paste…", - "Your paste is %s (Hit [Ctrl]+[c] to copy)": "Your paste is %s (Hit [Ctrl]+[c] to copy)", - "Delete data": "Delete data", - "Could not create paste: %s": "Could not create paste: %s", - "Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)": "Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)", + "Please enter the password for this paste:": "กรุณากรอกรหัสผ่านเพื่อเปิดข้อมูลการฝากโค้ดนี้:", + "Could not decrypt data (Wrong key?)": "ไม่สามารถถอดรหัสข้อมูลได้ (คีย์ไม่ถูกต้องหรือไม่)", + "Could not delete the paste, it was not stored in burn after reading mode.": "ไม่สามารถลบการฝากโค้ดนี้ได้ เนื่องจากว่าไม่ได้ถูกเก็บไว้ในโหมดลบทันทีเมื่อเปิดอ่าน", + "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.": "เก็บไว้ดูคนเดียวนะ อย่าปิดหน้าต่างนี้ ข้อความนี้จะไม่สามารถแสดงได้อีก", + "Could not decrypt comment; Wrong key?": "ไม่สามารถถอดรหัสความคิดเห็นได้ คีย์ไม่ถูกต้องหรือไม่", + "Reply": "ตอบกลับ", + "Anonymous": "ไม่ระบุชื่อ", + "Avatar generated from IP address": "อวาตารสร้างมาจากไอพี", + "Add comment": "เพิ่มความคิดเห็น", + "Optional nickname…": "ใส่ชื่อคนให้ความคิดเห็น…", + "Post comment": "ส่งความคิดเห็น", + "Sending comment…": "กำลังส่งความคิดเห็น…", + "Comment posted.": "ส่งความคิดเห็นแล้ว", + "Could not refresh display: %s": "ไม่สามารถรีเฟรชการแสดงผลได้: %s", + "unknown status": "ไม่ทราบสถานะ", + "server error or not responding": "เซิร์ฟเวอร์มีข้อผิดพลาดหรือไม่ตอบสนอง", + "Could not post comment: %s": "ไม่สามารถส่งความคิดเห็นได้: %s", + "Sending paste…": "กำลังส่งข้อมูล…", + "Your paste is %s (Hit [Ctrl]+[c] to copy)": "การฝากโค้ดของคุณอยู่ที่ %s (กดปุ่ม [Ctrl]+[c] เพื่อคัดลอก)", + "Delete data": "ลบข้อมูล", + "Could not create paste: %s": "ไม่สามารถสร้างข้อมูลการฝากโค้ดได้: %s", + "Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)": "ไม่สามารถถอดรหัสข้อมูลการฝากโค้ดได้: คีย์ถอดรหัสที่อยู่ใน URL หายไป (คุณได้ใช้ตัวเปลี่ยนเส้นทางหรือตัวย่อ URL ที่มีการตัดส่วนของ URL ออกหรือไม่)", "B": "B", "KiB": "KiB", "MiB": "MiB", @@ -139,55 +139,55 @@ "EiB": "EiB", "ZiB": "ZiB", "YiB": "YiB", - "Format": "Format", - "Plain Text": "Plain Text", - "Source Code": "Source Code", + "Format": "รูปแบบ", + "Plain Text": "ข้อความล้วน", + "Source Code": "ซอร์สโค้ด", "Markdown": "Markdown", - "Download attachment": "Download attachment", - "Cloned: '%s'": "Cloned: '%s'", - "The cloned file '%s' was attached to this paste.": "The cloned file '%s' was attached to this paste.", - "Attach a file": "Attach a file", - "alternatively drag & drop a file or paste an image from the clipboard": "alternatively drag & drop a file or paste an image from the clipboard", - "File too large, to display a preview. Please download the attachment.": "File too large, to display a preview. Please download the attachment.", - "Remove attachment": "Remove attachment", - "Your browser does not support uploading encrypted files. Please use a newer browser.": "Your browser does not support uploading encrypted files. Please use a newer browser.", - "Invalid attachment.": "Invalid attachment.", - "Options": "Options", - "Shorten URL": "Shorten URL", - "Editor": "Editor", - "Preview": "Preview", - "%s requires the PATH to end in a \"%s\". Please update the PATH in your index.php.": "%s requires the PATH to end in a \"%s\". Please update the PATH in your index.php.", - "Decrypt": "Decrypt", - "Enter password": "Enter password", - "Loading…": "Loading…", - "Decrypting paste…": "Decrypting paste…", - "Preparing new paste…": "Preparing new paste…", - "In case this message never disappears please have a look at this FAQ for information to troubleshoot.": "In case this message never disappears please have a look at this FAQ for information to troubleshoot.", - "+++ no paste text +++": "+++ no paste text +++", - "Could not get paste data: %s": "Could not get paste data: %s", - "QR code": "QR code", - "This website is using an insecure HTTP connection! Please use it only for testing.": "This website is using an insecure HTTP connection! Please use it only for testing.", - "For more information see this FAQ entry.": "For more information see this FAQ entry.", - "Your browser may require an HTTPS connection to support the WebCrypto API. Try switching to HTTPS.": "Your browser may require an HTTPS connection to support the WebCrypto API. Try switching to HTTPS.", - "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.": "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.", - "waiting on user to provide a password": "waiting on user to provide a password", - "Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.": "Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.", - "Retry": "Retry", - "Showing raw text…": "Showing raw text…", - "Notice:": "Notice:", - "This link will expire after %s.": "This link will expire after %s.", - "This link can only be accessed once, do not use back or refresh button in your browser.": "This link can only be accessed once, do not use back or refresh button in your browser.", - "Link:": "Link:", - "Recipient may become aware of your timezone, convert time to UTC?": "Recipient may become aware of your timezone, convert time to UTC?", - "Use Current Timezone": "Use Current Timezone", - "Convert To UTC": "Convert To UTC", - "Close": "Close", - "Encrypted note on %s": "Encrypted note on %s", - "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", - "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", - "Save paste": "Save paste", - "Your IP is not authorized to create pastes.": "Your IP is not authorized to create pastes.", - "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." + "Download attachment": "ดาวน์โหลดไฟล์แนบ", + "Cloned: '%s'": "โคลนแล้ว: '%s'", + "The cloned file '%s' was attached to this paste.": "การโคลนข้อมูลการฝากโค้ด มีไฟล์ '%s' แนบมาด้วย", + "Attach a file": "แนบไฟล์", + "alternatively drag & drop a file or paste an image from the clipboard": "หรือสามารถลากและวางไฟล์หรือวางรูปภาพจากคลิปบอร์ดได้", + "File too large, to display a preview. Please download the attachment.": "ไฟล์มีขนาดใหญ่เกินไปที่จะแสดงตัวอย่าง กรุณาดาวน์โหลดเป็นไฟล์แนบแทน", + "Remove attachment": "ลบไฟล์แนบ", + "Your browser does not support uploading encrypted files. Please use a newer browser.": "เบราว์เซอร์ของคุณไม่สนับสนุนการอัปโหลดไฟล์แบบเข้ารหัสได้ กรุณาใช้เบราว์เซอร์ที่ใหม่กว่า", + "Invalid attachment.": "ไฟล์แนบไม่ถูกต้อง", + "Options": "ตัวเลือก", + "Shorten URL": "สร้างลิงก์ย่อ", + "Editor": "ตัวแก้ไข", + "Preview": "ดูตัวอย่าง", + "%s requires the PATH to end in a \"%s\". Please update the PATH in your index.php.": "%s กำหนดให้ PATH ลงท้ายด้วย \"%s\" กรุณาอัปเดต PATH ในไฟล์ index.php ของคุณ", + "Decrypt": "ถอดรหัส", + "Enter password": "กรอกรหัสผ่าน", + "Loading…": "กำลังโหลด…", + "Decrypting paste…": "กำลังถอดรหัสข้อมูลการฝากโค้ด…", + "Preparing new paste…": "กำลังเตรียมข้อมูลการฝากโค้ดใหม่…", + "In case this message never disappears please have a look at this FAQ for information to troubleshoot.": "ในกรณีที่ข้อความนี้ยังปรากฎให้เห็นอยู่ กรุณาดูคำถามที่พบบ่อยนี้เพื่อใช้แก้ไขปัญหา", + "+++ no paste text +++": "+++ ไม่มีข้อความการฝากโค้ด +++", + "Could not get paste data: %s": "ไม่สามารถดึงข้อมูลการฝากโค้ดได้: %s", + "QR code": "คิวอาร์โค้ด", + "This website is using an insecure HTTP connection! Please use it only for testing.": "เว็บไซต์นี้ใช้การเชื่อมต่อแบบ HTTP ที่ไม่ปลอดภัย! กรุณาใช้เพื่อการทดสอบเท่านั้น", + "For more information see this FAQ entry.": "สำหรับข้อมูลเพิ่มเติม กรุณาดูรายการคำถามที่พบบ่อยนี้", + "Your browser may require an HTTPS connection to support the WebCrypto API. Try switching to HTTPS.": "เบราว์เซอร์ของคุณอาจต้องใช้การเชื่อมต่อแบบ HTTPS เพื่อสนับสนุน API แบบ WebCrypto ลองเปลี่ยนเป็น HTTPS", + "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.": "เบราว์เซอร์ของคุณไม่สนับสนุน WebAssembly ที่ทำหน้าที่ในการบีบอัดข้อมูลในรูปแบบ zlib คุณยังสามารถสร้างเอกสารที่ไม่บีบอัด แต่จะไม่สามารถอ่านเอกสารที่บีบอัดได้", + "waiting on user to provide a password": "กำลังรอผู้ใช้กรอกรหัสผ่าน", + "Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.": "ไม่สามารถถอดรหัสข้อมูลได้ คุณกรอกรหัสผ่านผิดหรือเปล่า กดปุ่มลองอีกครั้งด้านบน", + "Retry": "ลองอีกครั้ง", + "Showing raw text…": "กำลังแสดงข้อความล้วน…", + "Notice:": "โปรดทราบ:", + "This link will expire after %s.": "ลิงก์นี้จะหมดอายุหลังจาก %s", + "This link can only be accessed once, do not use back or refresh button in your browser.": "ลิงก์นี้สามารถเข้าถึงได้เพียงครั้งเดียว ห้ามใช้ปุ่มย้อนกลับหรือรีเฟรชในเบราว์เซอร์ของคุณ", + "Link:": "ลิงก์:", + "Recipient may become aware of your timezone, convert time to UTC?": "ผู้รับอีเมลอาจทราบโซนเวลาของคุณได้ คุณต้องการแปลงโซนเวลาเป็น UTC หรือไม่", + "Use Current Timezone": "ใช้โซนเวลาปัจจุบัน", + "Convert To UTC": "แปลงเป็น UTC", + "Close": "ปิด", + "Encrypted note on %s": "เขารหัสบันทึกย่อบน %s", + "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "ไปที่ลิงก์นี้เพื่อดูบันทึกย่อทั้งหมด ส่ง URL นี้ให้ใครก็ได้เพื่อให้สามารถเข้าถึงโน้ตได้", + "URL shortener may expose your decrypt key in URL.": "เครื่องมือสร้างลิงก์ย่ออาจเปิดเผยคีย์ถอดรหัสของคุณใน URL ได้", + "Save paste": "ดาวน์โหลดข้อมูลการฝากโค้ด", + "Your IP is not authorized to create pastes.": "IP ของคุณไม่ได้รับอนุญาตให้สร้างการฝากโค้ด", + "Trying to shorten a URL that isn't pointing at our instance.": "กำลังพยายามใช้เครื่องมือสร้างลิงก์ย่อ ที่ไม่ได้ชี้ไปที่อินสแตนซ์ของเรา", + "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "เกิดข้อผิดพลาดในการเรียก YOURLS อาจเป็นปัญหามาจากการกำหนดค่า เช่น \"apiurl\" หรือ \"signature\" ไม่ถูกต้องหรือขาดหายไป", + "Error parsing YOURLS response.": "เกิดข้อผิดพลาดในการแยกวิเคราะห์การตอบสนองของ YOURLS" } From 9f1e95f588a2b66e52d3e2ee6b3eef3d8e54cec6 Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Sun, 6 Nov 2022 03:21:05 +0100 Subject: [PATCH 19/36] New translations en.json (Thai) --- i18n/th.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/i18n/th.json b/i18n/th.json index 35e6209e..7cb789ea 100644 --- a/i18n/th.json +++ b/i18n/th.json @@ -1,7 +1,7 @@ { "PrivateBin": "PrivateBin", "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s เป็น pastebin ออนไลน์แบบโอเพ่นซอร์สที่มีสไตล์แบบมินิมัลลิสท์ เซิร์ฟเวอร์ไม่สามารถรู้ได้ว่าข้อมูลโค้ดที่มาฝากนั้นเป็นข้อมูลอะไร โดยจะถูก %sเข้ารหัส/ถอดรหัส%s ด้วยกระบวนการ AES จำนวน 256 บิตผ่านเบราว์เซอร์", - "More information on the project page.": "ข้อมูลเพิ่มเติมในหน้าโครงการ", + "More information on the project page.": "ข้อมูลเพิ่มเติม ดูได้ที่หน้าโครงการ", "Because ignorance is bliss": "ไม่รู้ไม่ชี้ดีที่สุด", "en": "th", "Paste does not exist, has expired or has been deleted.": "การฝากโค้ดไม่มีอยู่ อาจจะหมดอายุหรือถูกลบไปแล้ว", @@ -168,7 +168,7 @@ "QR code": "คิวอาร์โค้ด", "This website is using an insecure HTTP connection! Please use it only for testing.": "เว็บไซต์นี้ใช้การเชื่อมต่อแบบ HTTP ที่ไม่ปลอดภัย! กรุณาใช้เพื่อการทดสอบเท่านั้น", "For more information see this FAQ entry.": "สำหรับข้อมูลเพิ่มเติม กรุณาดูรายการคำถามที่พบบ่อยนี้", - "Your browser may require an HTTPS connection to support the WebCrypto API. Try switching to HTTPS.": "เบราว์เซอร์ของคุณอาจต้องใช้การเชื่อมต่อแบบ HTTPS เพื่อสนับสนุน API แบบ WebCrypto ลองเปลี่ยนเป็น HTTPS", + "Your browser may require an HTTPS connection to support the WebCrypto API. Try switching to HTTPS.": "เบราว์เซอร์ของคุณอาจต้องใช้การเชื่อมต่อ HTTPS เพื่อสนับสนุน API แบบ WebCrypto ลองเปลี่ยนเป็น HTTPS", "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.": "เบราว์เซอร์ของคุณไม่สนับสนุน WebAssembly ที่ทำหน้าที่ในการบีบอัดข้อมูลในรูปแบบ zlib คุณยังสามารถสร้างเอกสารที่ไม่บีบอัด แต่จะไม่สามารถอ่านเอกสารที่บีบอัดได้", "waiting on user to provide a password": "กำลังรอผู้ใช้กรอกรหัสผ่าน", "Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.": "ไม่สามารถถอดรหัสข้อมูลได้ คุณกรอกรหัสผ่านผิดหรือเปล่า กดปุ่มลองอีกครั้งด้านบน", @@ -176,7 +176,7 @@ "Showing raw text…": "กำลังแสดงข้อความล้วน…", "Notice:": "โปรดทราบ:", "This link will expire after %s.": "ลิงก์นี้จะหมดอายุหลังจาก %s", - "This link can only be accessed once, do not use back or refresh button in your browser.": "ลิงก์นี้สามารถเข้าถึงได้เพียงครั้งเดียว ห้ามใช้ปุ่มย้อนกลับหรือรีเฟรชในเบราว์เซอร์ของคุณ", + "This link can only be accessed once, do not use back or refresh button in your browser.": "ลิงก์นี้สามารถเข้าถึงได้เพียงครั้งเดียวเท่านั้น ไม่ควรใช้ปุ่มย้อนกลับหรือรีเฟรชหน้าเว็บบนเบราว์เซอร์ของคุณ", "Link:": "ลิงก์:", "Recipient may become aware of your timezone, convert time to UTC?": "ผู้รับอีเมลอาจทราบโซนเวลาของคุณได้ คุณต้องการแปลงโซนเวลาเป็น UTC หรือไม่", "Use Current Timezone": "ใช้โซนเวลาปัจจุบัน", From 3028c22c20a1f8fa5a2ecd9389d9219e49c75860 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 6 Nov 2022 07:40:39 +0100 Subject: [PATCH 20/36] be more efficient --- lib/Data/Database.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index d4933b00..4636f3ce 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -289,11 +289,11 @@ class Database extends AbstractData { try { $row = $this->_select( - 'SELECT * FROM "' . $this->_sanitizeIdentifier('paste') . + 'SELECT "dataid" FROM "' . $this->_sanitizeIdentifier('paste') . '" WHERE "dataid" = ?', array($pasteid), true ); } catch (Exception $e) { - $row = false; + return false; } return (bool) $row; } From 5195adfdb91a4a3c87813ecbafa04500ccec24ab Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 6 Nov 2022 07:41:05 +0100 Subject: [PATCH 21/36] simple migration test --- tst/MigrateTest.php | 81 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 tst/MigrateTest.php diff --git a/tst/MigrateTest.php b/tst/MigrateTest.php new file mode 100644 index 00000000..14feca0a --- /dev/null +++ b/tst/MigrateTest.php @@ -0,0 +1,81 @@ +_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() + { + $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'); + } +} \ No newline at end of file From bde7a199712a03122e7439e431ef40a54f8cd13e Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 6 Nov 2022 07:43:19 +0100 Subject: [PATCH 22/36] apply StyleCI patch --- tst/MigrateTest.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tst/MigrateTest.php b/tst/MigrateTest.php index 14feca0a..d1fbb30e 100644 --- a/tst/MigrateTest.php +++ b/tst/MigrateTest.php @@ -17,7 +17,7 @@ class MigrateTest extends PHPUnit_Framework_TestCase public function setUp() { /* Setup Routine */ - $this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data'; + $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)) { @@ -27,10 +27,10 @@ class MigrateTest extends PHPUnit_Framework_TestCase 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 = 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']); + $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( @@ -62,9 +62,9 @@ class MigrateTest extends PHPUnit_Framework_TestCase $this->_model_1->createComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId(), Helper::getComment()); // migrate files to database - $output = null; + $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); + 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'); @@ -73,9 +73,9 @@ class MigrateTest extends PHPUnit_Framework_TestCase // 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); + 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'); } -} \ No newline at end of file +} From 14075cea78ddd569322dc7a5fa5910cbc429f4d4 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 6 Nov 2022 07:53:43 +0100 Subject: [PATCH 23/36] trying a different approach to get that exception 70 triggered reliably --- tst/ModelTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tst/ModelTest.php b/tst/ModelTest.php index dbbbbc22..5813d853 100644 --- a/tst/ModelTest.php +++ b/tst/ModelTest.php @@ -270,7 +270,7 @@ class ModelTest extends PHPUnit_Framework_TestCase $options['model_options']['pwd'], $options['model_options']['opt'] ); - $statement = $db->prepare('DROP TABLE comment'); + $statement = $db->prepare('ALTER TABLE comment DROP COLUMN data'); $statement->execute(); $statement->closeCursor(); From 669c98550c22bbe4b1a777c924cdae36c25bd939 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 6 Nov 2022 08:05:41 +0100 Subject: [PATCH 24/36] add a version check, the third argument in getopt requires PHP >= 7.1 --- bin/migrate | 4 ++++ tst/MigrateTest.php | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/bin/migrate b/bin/migrate index 4dbe052b..515a5e80 100755 --- a/bin/migrate +++ b/bin/migrate @@ -10,6 +10,10 @@ 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", diff --git a/tst/MigrateTest.php b/tst/MigrateTest.php index d1fbb30e..eccca266 100644 --- a/tst/MigrateTest.php +++ b/tst/MigrateTest.php @@ -54,6 +54,10 @@ class MigrateTest extends PHPUnit_Framework_TestCase 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()); From 6caf1143df94afe5f97a88b48087e44a10b83896 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 6 Nov 2022 08:13:07 +0100 Subject: [PATCH 25/36] add a verification step for investigating failures in tests below PHP 7.2 --- tst/ModelTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tst/ModelTest.php b/tst/ModelTest.php index 5813d853..91da5580 100644 --- a/tst/ModelTest.php +++ b/tst/ModelTest.php @@ -259,7 +259,7 @@ 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); From 8ede84f000ad16b4c2fe444057c8945337437e43 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 6 Nov 2022 08:21:28 +0100 Subject: [PATCH 26/36] 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). --- tst/ModelTest.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tst/ModelTest.php b/tst/ModelTest.php index 91da5580..00a172db 100644 --- a/tst/ModelTest.php +++ b/tst/ModelTest.php @@ -270,10 +270,13 @@ class ModelTest extends PHPUnit_Framework_TestCase $options['model_options']['pwd'], $options['model_options']['opt'] ); - $statement = $db->prepare('ALTER TABLE comment DROP COLUMN data'); + $statement = $db->prepare('DROP TABLE comment'); $statement->execute(); $statement->closeCursor(); + 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(); } From a799351db33861e0b7aeb2172ed8108f8729ef78 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 6 Nov 2022 09:09:50 +0100 Subject: [PATCH 27/36] 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. --- lib/Data/Filesystem.php | 57 ++++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/lib/Data/Filesystem.php b/lib/Data/Filesystem.php index 2d082144..abaa51db 100644 --- a/lib/Data/Filesystem.php +++ b/lib/Data/Filesystem.php @@ -410,37 +410,46 @@ class Filesystem extends AbstractData public function getAllPastes() { $pastes = array(); - $subdirs = scandir($this->_path); - if ($subdirs === false) { - dieerr('Unable to list directory ' . $this->_path); - } - $subdirs = preg_grep('/^[^.].$/', $subdirs); + $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' + ); - foreach ($subdirs as $subdir) { - $subpath = $this->_path . DIRECTORY_SEPARATOR . $subdir; - - $subsubdirs = scandir($subpath); - if ($subsubdirs === false) { - dieerr('Unable to list directory ' . $subpath); - } - $subsubdirs = preg_grep('/^[^.].$/', $subsubdirs); - foreach ($subsubdirs as $subsubdir) { - $subsubpath = $subpath . DIRECTORY_SEPARATOR . $subsubdir; - - $files = scandir($subsubpath); - if ($files === false) { - dieerr('Unable to list directory ' . $subsubpath); + // skip this folder + if (count($secondLevel) == 0) { + continue; } - $files = preg_grep('/\.php$/', $files); - foreach ($files as $file) { - if (substr($file, 0, 4) === $subdir . $subsubdir) { - $pastes[] = substr($file, 0, strlen($file) - 4); + 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; } From 94aab6d64b4913996d543b520a6ad58282f3ca67 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 6 Nov 2022 09:12:42 +0100 Subject: [PATCH 28/36] apply StyleCI patch --- lib/Data/Filesystem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Data/Filesystem.php b/lib/Data/Filesystem.php index abaa51db..966506fd 100644 --- a/lib/Data/Filesystem.php +++ b/lib/Data/Filesystem.php @@ -409,7 +409,7 @@ class Filesystem extends AbstractData */ public function getAllPastes() { - $pastes = array(); + $pastes = array(); $firstLevel = array_filter( scandir($this->_path), 'PrivateBin\Data\Filesystem::_isFirstLevelDir' From 0288d94a68c23b940a057f60b073017aa45babb8 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 6 Nov 2022 09:27:51 +0100 Subject: [PATCH 29/36] regenerate composer.lock with PHP 7.2 for Scrutinizer --- composer.lock | 128 +++++++----------------------- vendor/composer/autoload_real.php | 3 - 2 files changed, 29 insertions(+), 102 deletions(-) diff --git a/composer.lock b/composer.lock index 5d13c27b..3f4e65f0 100644 --- a/composer.lock +++ b/composer.lock @@ -105,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" }, { @@ -271,20 +261,6 @@ "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": "2022-03-03T08:28:38+00:00" }, { @@ -334,12 +310,6 @@ "object", "object graph" ], - "funding": [ - { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" - } - ], "time": "2022-03-03T13:19:32+00:00" }, { @@ -446,30 +416,25 @@ }, { "name": "phpdocumentor/type-resolver", - "version": "1.6.2", + "version": "1.6.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "48f445a408c131e38cab1c235aa6d2bb7a0bb20d" + "reference": "77a32518733312af16a44300404e945338981de3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/48f445a408c131e38cab1c235aa6d2bb7a0bb20d", - "reference": "48f445a408c131e38cab1c235aa6d2bb7a0bb20d", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3", + "reference": "77a32518733312af16a44300404e945338981de3", "shasum": "" }, "require": { - "php": "^7.4 || ^8.0", + "php": "^7.2 || ^8.0", "phpdocumentor/reflection-common": "^2.0" }, "require-dev": { "ext-tokenizer": "*", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-phpunit": "^1.1", - "phpunit/phpunit": "^9.5", - "rector/rector": "^0.13.9", - "vimeo/psalm": "^4.25" + "psalm/phar": "^4.8" }, "type": "library", "extra": { @@ -493,7 +458,7 @@ } ], "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", - "time": "2022-10-14T12:47:21+00:00" + "time": "2022-03-15T21:29:03+00:00" }, { "name": "phpspec/prophecy", @@ -560,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": { @@ -619,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", @@ -993,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" }, { @@ -1532,20 +1491,6 @@ "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": "2022-05-24T11:49:31+00:00" }, { @@ -1600,39 +1545,25 @@ ], "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-08-02T15:47:23+00:00" }, { "name": "webmozart/assert", - "version": "1.11.0", + "version": "1.10.0", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25", "shasum": "" }, "require": { - "ext-ctype": "*", - "php": "^7.2 || ^8.0" + "php": "^7.2 || ^8.0", + "symfony/polyfill-ctype": "^1.8" }, "conflict": { "phpstan/phpstan": "<0.12.20", @@ -1668,7 +1599,7 @@ "check", "validate" ], - "time": "2022-06-03T18:03:27+00:00" + "time": "2021-03-09T10:59:23+00:00" } ], "aliases": [], @@ -1679,6 +1610,5 @@ "platform": { "php": "^5.6.0 || ^7.0 || ^8.0" }, - "platform-dev": [], - "plugin-api-version": "1.1.0" + "platform-dev": [] } diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php index 7a6fd4c0..2e234b96 100644 --- a/vendor/composer/autoload_real.php +++ b/vendor/composer/autoload_real.php @@ -13,9 +13,6 @@ class ComposerAutoloaderInitDontChange } } - /** - * @return \Composer\Autoload\ClassLoader - */ public static function getLoader() { if (null !== self::$loader) { From d4d4687464b92a4cdf527ea1e1a316b92b97ef8b Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Mon, 7 Nov 2022 00:01:51 +0100 Subject: [PATCH 30/36] New translations en.json (Thai) --- i18n/th.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/th.json b/i18n/th.json index 7cb789ea..881e5aab 100644 --- a/i18n/th.json +++ b/i18n/th.json @@ -77,7 +77,7 @@ "%d ปี" ], "Never": "ไม่หมดอายุ", - "Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.": "หมายเหตุ: เว็บไซต์นี่เป็นบริการเบต้าและข้อมูลอาจถูกลบได้ตลอดเวลา หากคุณใช้บริการนี้ในทางที่ผิดอาจจะทำให้ข้อมูลของคุณสูญหายอย่างถาวรได้", + "Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.": "โปรดทราบ: เว็บไซต์นี้เป็นการให้บริการแบบเบต้า ข้อมูลอาจถูกลบได้ตลอดเวลา หากคุณใช้บริการนี้ในทางที่ผิดอาจจะทำให้ข้อมูลของคุณสูญหายอย่างถาวรได้", "This document will expire in %d seconds.": [ "เอกสารนี้จะหมดอายุใน %d วินาที", "เอกสารนี้จะหมดอายุใน %d วินาที", From b0c61cc208ee4dd9cd6273d1a52c96856f65b7ac Mon Sep 17 00:00:00 2001 From: PrivateBin Translator Bot <72346835+privatebin-translator@users.noreply.github.com> Date: Mon, 7 Nov 2022 00:58:25 +0100 Subject: [PATCH 31/36] New translations en.json (Thai) --- i18n/th.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/th.json b/i18n/th.json index 881e5aab..d3c955db 100644 --- a/i18n/th.json +++ b/i18n/th.json @@ -183,7 +183,7 @@ "Convert To UTC": "แปลงเป็น UTC", "Close": "ปิด", "Encrypted note on %s": "เขารหัสบันทึกย่อบน %s", - "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "ไปที่ลิงก์นี้เพื่อดูบันทึกย่อทั้งหมด ส่ง URL นี้ให้ใครก็ได้เพื่อให้สามารถเข้าถึงโน้ตได้", + "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "ไปที่ลิงก์นี้เพื่อดูบันทึกย่อทั้งหมด ส่ง URL นี้ให้ใครก็ได้เพื่อให้สามารถเข้าถึงบันทึกย่อได้", "URL shortener may expose your decrypt key in URL.": "เครื่องมือสร้างลิงก์ย่ออาจเปิดเผยคีย์ถอดรหัสของคุณใน URL ได้", "Save paste": "ดาวน์โหลดข้อมูลการฝากโค้ด", "Your IP is not authorized to create pastes.": "IP ของคุณไม่ได้รับอนุญาตให้สร้างการฝากโค้ด", From 89df4a54ec3b6f2d3522f0bcd87ddc50417cb336 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Mon, 7 Nov 2022 07:12:40 +0100 Subject: [PATCH 32/36] enable and credit Thai translation --- CHANGELOG.md | 2 +- CREDITS.md | 1 + js/privatebin.js | 3 ++- lib/I18n.php | 1 + tpl/bootstrap.php | 2 +- tpl/page.php | 2 +- 6 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02c39505..d8ac2cb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ * **1.4.1 (not yet released)** * ADDED: script for data storage backend migrations (#1012) - * ADDED: Translations for Turkish, Slovak and Greek + * ADDED: Translations for Turkish, Slovak, Greek and Thai * ADDED: S3 Storage backend (#994) * CHANGED: Switched to Jdenticons as the default for comment icons (#793) * CHANGED: Avoid `SUPER` privilege for setting the `sql_mode` for MariaDB/MySQL (#919) diff --git a/CREDITS.md b/CREDITS.md index eb5f152e..7a19c5fd 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -62,3 +62,4 @@ * Emir Ensar Rahmanlar - Turkish * Stevo984 - Slovak * Christos Karamolegkos - Greek +* jaideejung007 - Thai diff --git a/js/privatebin.js b/js/privatebin.js index fe326b49..5171477d 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -627,7 +627,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { * @prop {string[]} * @readonly */ - const supportedLanguages = ['bg', 'ca', 'co', 'cs', 'de', 'el', 'es', 'et', 'fi', 'fr', 'he', 'hu', 'id', 'it', 'jbo', 'lt', 'no', 'nl', 'pl', 'pt', 'oc', 'ru', 'sk', 'sl', 'tr', 'uk', 'zh']; + const supportedLanguages = ['bg', 'ca', 'co', 'cs', 'de', 'el', 'es', 'et', 'fi', 'fr', 'he', 'hu', 'id', 'it', 'jbo', 'lt', 'no', 'nl', 'pl', 'pt', 'oc', 'ru', 'sk', 'sl', 'th', 'tr', 'uk', 'zh']; /** * built in language @@ -815,6 +815,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { return n === 1 ? 0 : (n === 2 ? 1 : ((n < 0 || n > 10) && (n % 10 === 0) ? 2 : 3)); case 'id': case 'jbo': + case 'th': return 0; case 'lt': return n % 10 === 1 && n % 100 !== 11 ? 0 : ((n % 10 >= 2 && n % 100 < 10 || n % 100 >= 20) ? 1 : 2); diff --git a/lib/I18n.php b/lib/I18n.php index c3c635f7..5c469381 100644 --- a/lib/I18n.php +++ b/lib/I18n.php @@ -328,6 +328,7 @@ class I18n return $n === 1 ? 0 : ($n === 2 ? 1 : (($n < 0 || $n > 10) && ($n % 10 === 0) ? 2 : 3)); case 'id': case 'jbo': + case 'th': return 0; case 'lt': return $n % 10 === 1 && $n % 100 !== 11 ? 0 : (($n % 10 >= 2 && $n % 100 < 10 || $n % 100 >= 20) ? 1 : 2); diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php index 3dd49135..b3441a74 100644 --- a/tpl/bootstrap.php +++ b/tpl/bootstrap.php @@ -73,7 +73,7 @@ endif; ?> - + diff --git a/tpl/page.php b/tpl/page.php index f91cb7dd..42380abc 100644 --- a/tpl/page.php +++ b/tpl/page.php @@ -51,7 +51,7 @@ endif; ?> - + From c0758e7bbb921ea44dab28b743d8c96c98ec07c1 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Mon, 7 Nov 2022 19:09:16 +0100 Subject: [PATCH 33/36] correct labels, Jdenticon renders PNG or SVG, both in pure PHP --- tst/IconTest | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tst/IconTest b/tst/IconTest index e39cc7be..75dbb279 100755 --- a/tst/IconTest +++ b/tst/IconTest @@ -28,7 +28,7 @@ $jdenticon = new Jdenticon(array( ), )); $jdenticonGenerators = array( - 'jdenticon GD' => 'png', + 'jdenticon PNG' => 'png', 'jdenticon SVG' => 'svg', ); $results = array( @@ -48,7 +48,7 @@ $results = array( 'lengths' => array(), 'time' => 0 ), - 'jdenticon GD' => array( + 'jdenticon PNG' => array( 'lengths' => array(), 'time' => 0 ), From b2ef205411984f2c40a075da0774123ddfa61335 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Mon, 7 Nov 2022 19:42:20 +0100 Subject: [PATCH 34/36] extended script to test jdenticon ImageMagick and documented option to work without GD --- INSTALL.md | 2 +- tst/IconTest | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index f35d68bf..24749d34 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -26,7 +26,7 @@ install and configure PrivateBin on your server. It's available on - `open_basedir` access to `/dev/urandom` - mcrypt extension AND `open_basedir` access to `/dev/urandom` - com_dotnet extension -- GD extension +- GD extension (when using identicon or vizhash icons, jdenticon works without it) - zlib extension - some disk space or a database supported by [PDO](https://php.net/manual/book.pdo.php) - ability to create files and folders in the installation directory and the PATH diff --git a/tst/IconTest b/tst/IconTest index 75dbb279..57145e13 100755 --- a/tst/IconTest +++ b/tst/IconTest @@ -28,7 +28,8 @@ $jdenticon = new Jdenticon(array( ), )); $jdenticonGenerators = array( - 'jdenticon PNG' => 'png', + 'jdenticon' => 'png', + 'jdenticon ImageMagick' => 'png', 'jdenticon SVG' => 'svg', ); $results = array( @@ -48,7 +49,11 @@ $results = array( 'lengths' => array(), 'time' => 0 ), - 'jdenticon PNG' => array( + 'jdenticon' => array( + 'lengths' => array(), + 'time' => 0 + ), + 'jdenticon ImageMagick' => array( 'lengths' => array(), 'time' => 0 ), @@ -89,6 +94,11 @@ foreach ($identiconGenerators as $key => $identicon) { foreach ($jdenticonGenerators as $key => $format) { echo 'run ', $key,' tests', PHP_EOL; + if ($key === 'jdenticon ImageMagick') { + $jdenticon->enableImageMagick = true; + } else { + $jdenticon->enableImageMagick = false; + } $start = microtime(true); foreach ($hmacs as $i => $hmac) { $jdenticon->setHash($hmac); From b8593b1bf2bb23d1946dff96450092b8bde34453 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Thu, 10 Nov 2022 20:36:15 +0100 Subject: [PATCH 35/36] use a glob iterator to stream through as many matches as needed --- lib/Data/Filesystem.php | 148 +++++++++------------------------------- 1 file changed, 34 insertions(+), 114 deletions(-) diff --git a/lib/Data/Filesystem.php b/lib/Data/Filesystem.php index 966506fd..1f00b577 100644 --- a/lib/Data/Filesystem.php +++ b/lib/Data/Filesystem.php @@ -340,63 +340,25 @@ class Filesystem extends AbstractData */ protected function _getExpiredPastes($batchsize) { - $pastes = array(); - $firstLevel = array_filter( - 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($this->_path . DIRECTORY_SEPARATOR . $firstLevel[$firstKey]), - 'PrivateBin\Data\Filesystem::_isSecondLevelDir' - ); - - // skip this folder in the next checks if it is empty - if (count($secondLevel) == 0) { - unset($firstLevel[$firstKey]); - continue; - } - - $secondKey = array_rand($secondLevel); - $path = $this->_path . DIRECTORY_SEPARATOR . - $firstLevel[$firstKey] . DIRECTORY_SEPARATOR . - $secondLevel[$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; - } - $thirdKey = array_rand($thirdLevel); - $pasteid = $thirdLevel[$thirdKey]; - if (in_array($pasteid, $pastes)) { - continue; - } - - if ($this->exists($pasteid)) { - $data = $this->read($pasteid); - if ( - array_key_exists('expire_date', $data['meta']) && - $data['meta']['expire_date'] < time() - ) { - $pastes[] = $pasteid; - if (count($pastes) >= $batchsize) { - break; - } + $pastes = array(); + $files = $this->_getPasteIterator(); + $count = 0; + $time = time(); + foreach ($files as $file) { + if ($file->isDir()) { + continue; + } + $pasteid = $file->getBasename('.php'); + if ($this->exists($pasteid)) { + $data = $this->read($pasteid); + if ( + array_key_exists('expire_date', $data['meta']) && + $data['meta']['expire_date'] < $time + ) { + $pastes[] = $pasteid; + ++$count; + if ($count >= $batchsize) { + break; } } } @@ -409,45 +371,11 @@ class Filesystem extends AbstractData */ 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; - } + $pastes = array(); + $files = $this->_getPasteIterator(); + foreach ($files as $file) { + if ($file->isFile()) { + $pastes[] = $file->getBasename('.php'); } } return $pastes; @@ -490,28 +418,20 @@ class Filesystem extends AbstractData } /** - * Check that the given element is a valid first level directory. + * Get an iterator matching paste files. * * @access private - * @param string $element - * @return bool + * @return \GlobIterator */ - private function _isFirstLevelDir($element) + private function _getPasteIterator() { - return $this->_isSecondLevelDir($element) && - is_dir($this->_path . DIRECTORY_SEPARATOR . $element); - } - - /** - * Check that the given element is a valid second level directory. - * - * @access private - * @param string $element - * @return bool - */ - private function _isSecondLevelDir($element) - { - return (bool) preg_match('/^[a-f0-9]{2}$/', $element); + return new \GlobIterator($this->_path . DIRECTORY_SEPARATOR . + '[a-f0-9][a-f0-9]' . DIRECTORY_SEPARATOR . + '[a-f0-9][a-f0-9]' . DIRECTORY_SEPARATOR . + '[a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9]' . + '[a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9]*'); + // need to return both files with and without .php suffix, so they can + // be hardened by _prependRename(), which is hooked into exists() } /** From 97047a6ef6db420c09945a15e93b957ded30079e Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 13 Nov 2022 06:37:23 +0100 Subject: [PATCH 36/36] upgrade JS libraries --- CHANGELOG.md | 4 ++-- js/common.js | 6 +++--- js/jquery-3.6.0.js | 2 -- js/jquery-3.6.1.js | 2 ++ js/purify-2.3.6.js | 2 -- js/purify-2.4.6.js | 2 ++ js/{showdown-2.0.3.js => showdown-2.1.0.js} | 2 +- tpl/bootstrap.php | 6 +++--- tpl/page.php | 6 +++--- 9 files changed, 16 insertions(+), 16 deletions(-) delete mode 100644 js/jquery-3.6.0.js create mode 100644 js/jquery-3.6.1.js delete mode 100644 js/purify-2.3.6.js create mode 100644 js/purify-2.4.6.js rename js/{showdown-2.0.3.js => showdown-2.1.0.js} (99%) diff --git a/CHANGELOG.md b/CHANGELOG.md index edd85024..2f23d523 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,12 @@ # PrivateBin version history - * **1.4.1 (not yet released)** + * **1.5 (not yet released)** * ADDED: script for data storage backend migrations (#1012) * ADDED: Translations for Turkish, Slovak, Greek and Thai * ADDED: S3 Storage backend (#994) * ADDED: Jdenticons as an option for comment icons (#793) * CHANGED: Avoid `SUPER` privilege for setting the `sql_mode` for MariaDB/MySQL (#919) - * CHANGED: Upgrading libraries to: zlib 1.2.13 + * CHANGED: Upgrading libraries to: DOMpurify 2.4.6, jQuery 3.6.1, Showdown 2.1.0 & zlib 1.2.13 * FIXED: Revert to CREATE INDEX without IF NOT EXISTS clauses, to support MySQL (#943) * FIXED: Apply table prefix to indexes as well, to support multiple instances sharing a single database (#943) * FIXED: YOURLS integration via new proxy, storing signature in configuration (#725) diff --git a/js/common.js b/js/common.js index 12e4c882..0d1b6738 100644 --- a/js/common.js +++ b/js/common.js @@ -10,14 +10,14 @@ global.fs = require('fs'); global.WebCrypto = require('@peculiar/webcrypto').Crypto; // application libraries to test -global.$ = global.jQuery = require('./jquery-3.6.0'); +global.$ = global.jQuery = require('./jquery-3.6.1'); global.RawDeflate = require('./rawinflate-0.3').RawDeflate; global.zlib = require('./zlib-1.2.13').zlib; require('./prettify'); global.prettyPrint = window.PR.prettyPrint; global.prettyPrintOne = window.PR.prettyPrintOne; -global.showdown = require('./showdown-2.0.3'); -global.DOMPurify = require('./purify-2.3.6'); +global.showdown = require('./showdown-2.1.0'); +global.DOMPurify = require('./purify-2.4.6'); global.baseX = require('./base-x-4.0.0').baseX; global.Legacy = require('./legacy').Legacy; require('./bootstrap-3.4.1'); diff --git a/js/jquery-3.6.0.js b/js/jquery-3.6.0.js deleted file mode 100644 index c4c6022f..00000000 --- a/js/jquery-3.6.0.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ -!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&v(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!y||!y.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ve(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ye(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ve(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],y=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&y.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||y.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||y.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||y.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||y.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||y.push(".#.+[+~]"),e.querySelectorAll("\\\f"),y.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&y.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&y.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&y.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),y.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),y=y.length&&new RegExp(y.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),v=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&v(p,e)?-1:t==C||t.ownerDocument==p&&v(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!y||!y.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),v.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",v.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",v.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ye(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),v.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(v.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return B(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=_e(v.pixelPosition,function(e,t){if(t)return t=Be(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return B(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 01?n-1:0),o=1;o/gm),z=a(/^data-[\-\w.\u00B7-\uFFFF]/),B=a(/^aria-[\-\w]+$/),P=a(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),j=a(/^(?:\w+script|data):/i),G=a(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),W=a(/^html$/i),q="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};function Y(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t0&&void 0!==arguments[0]?arguments[0]:K(),n=function(t){return e(t)};if(n.version="2.3.6",n.removed=[],!t||!t.document||9!==t.document.nodeType)return n.isSupported=!1,n;var r=t.document,o=t.document,a=t.DocumentFragment,l=t.HTMLTemplateElement,c=t.Node,s=t.Element,u=t.NodeFilter,m=t.NamedNodeMap,A=void 0===m?t.NamedNodeMap||t.MozNamedAttrMap:m,$=t.HTMLFormElement,X=t.DOMParser,Z=t.trustedTypes,J=s.prototype,Q=w(J,"cloneNode"),ee=w(J,"nextSibling"),te=w(J,"childNodes"),ne=w(J,"parentNode");if("function"==typeof l){var re=o.createElement("template");re.content&&re.content.ownerDocument&&(o=re.content.ownerDocument)}var oe=V(Z,r),ie=oe?oe.createHTML(""):"",ae=o,le=ae.implementation,ce=ae.createNodeIterator,se=ae.createDocumentFragment,ue=ae.getElementsByTagName,me=r.importNode,fe={};try{fe=x(o).documentMode?o.documentMode:{}}catch(e){}var de={};n.isSupported="function"==typeof ne&&le&&void 0!==le.createHTMLDocument&&9!==fe;var pe=H,he=U,ge=z,ye=B,ve=j,be=G,Te=P,Ne=null,Ae=E({},[].concat(Y(k),Y(S),Y(_),Y(O),Y(M))),Ee=null,xe=E({},[].concat(Y(L),Y(R),Y(I),Y(F))),we=Object.seal(Object.create(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),ke=null,Se=null,_e=!0,De=!0,Oe=!1,Ce=!1,Me=!1,Le=!1,Re=!1,Ie=!1,Fe=!1,He=!1,Ue=!0,ze=!0,Be=!1,Pe={},je=null,Ge=E({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]),We=null,qe=E({},["audio","video","img","source","image","track"]),Ye=null,Ke=E({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),Ve="http://www.w3.org/1998/Math/MathML",$e="http://www.w3.org/2000/svg",Xe="http://www.w3.org/1999/xhtml",Ze=Xe,Je=!1,Qe=void 0,et=["application/xhtml+xml","text/html"],tt="text/html",nt=void 0,rt=null,ot=o.createElement("form"),it=function(e){return e instanceof RegExp||e instanceof Function},at=function(e){rt&&rt===e||(e&&"object"===(void 0===e?"undefined":q(e))||(e={}),e=x(e),Ne="ALLOWED_TAGS"in e?E({},e.ALLOWED_TAGS):Ae,Ee="ALLOWED_ATTR"in e?E({},e.ALLOWED_ATTR):xe,Ye="ADD_URI_SAFE_ATTR"in e?E(x(Ke),e.ADD_URI_SAFE_ATTR):Ke,We="ADD_DATA_URI_TAGS"in e?E(x(qe),e.ADD_DATA_URI_TAGS):qe,je="FORBID_CONTENTS"in e?E({},e.FORBID_CONTENTS):Ge,ke="FORBID_TAGS"in e?E({},e.FORBID_TAGS):{},Se="FORBID_ATTR"in e?E({},e.FORBID_ATTR):{},Pe="USE_PROFILES"in e&&e.USE_PROFILES,_e=!1!==e.ALLOW_ARIA_ATTR,De=!1!==e.ALLOW_DATA_ATTR,Oe=e.ALLOW_UNKNOWN_PROTOCOLS||!1,Ce=e.SAFE_FOR_TEMPLATES||!1,Me=e.WHOLE_DOCUMENT||!1,Ie=e.RETURN_DOM||!1,Fe=e.RETURN_DOM_FRAGMENT||!1,He=e.RETURN_TRUSTED_TYPE||!1,Re=e.FORCE_BODY||!1,Ue=!1!==e.SANITIZE_DOM,ze=!1!==e.KEEP_CONTENT,Be=e.IN_PLACE||!1,Te=e.ALLOWED_URI_REGEXP||Te,Ze=e.NAMESPACE||Xe,e.CUSTOM_ELEMENT_HANDLING&&it(e.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(we.tagNameCheck=e.CUSTOM_ELEMENT_HANDLING.tagNameCheck),e.CUSTOM_ELEMENT_HANDLING&&it(e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(we.attributeNameCheck=e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),e.CUSTOM_ELEMENT_HANDLING&&"boolean"==typeof e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements&&(we.allowCustomizedBuiltInElements=e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),Qe=Qe=-1===et.indexOf(e.PARSER_MEDIA_TYPE)?tt:e.PARSER_MEDIA_TYPE,nt="application/xhtml+xml"===Qe?function(e){return e}:h,Ce&&(De=!1),Fe&&(Ie=!0),Pe&&(Ne=E({},[].concat(Y(M))),Ee=[],!0===Pe.html&&(E(Ne,k),E(Ee,L)),!0===Pe.svg&&(E(Ne,S),E(Ee,R),E(Ee,F)),!0===Pe.svgFilters&&(E(Ne,_),E(Ee,R),E(Ee,F)),!0===Pe.mathMl&&(E(Ne,O),E(Ee,I),E(Ee,F))),e.ADD_TAGS&&(Ne===Ae&&(Ne=x(Ne)),E(Ne,e.ADD_TAGS)),e.ADD_ATTR&&(Ee===xe&&(Ee=x(Ee)),E(Ee,e.ADD_ATTR)),e.ADD_URI_SAFE_ATTR&&E(Ye,e.ADD_URI_SAFE_ATTR),e.FORBID_CONTENTS&&(je===Ge&&(je=x(je)),E(je,e.FORBID_CONTENTS)),ze&&(Ne["#text"]=!0),Me&&E(Ne,["html","head","body"]),Ne.table&&(E(Ne,["tbody"]),delete ke.tbody),i&&i(e),rt=e)},lt=E({},["mi","mo","mn","ms","mtext"]),ct=E({},["foreignobject","desc","title","annotation-xml"]),st=E({},S);E(st,_),E(st,D);var ut=E({},O);E(ut,C);var mt=function(e){var t=ne(e);t&&t.tagName||(t={namespaceURI:Xe,tagName:"template"});var n=h(e.tagName),r=h(t.tagName);if(e.namespaceURI===$e)return t.namespaceURI===Xe?"svg"===n:t.namespaceURI===Ve?"svg"===n&&("annotation-xml"===r||lt[r]):Boolean(st[n]);if(e.namespaceURI===Ve)return t.namespaceURI===Xe?"math"===n:t.namespaceURI===$e?"math"===n&&ct[r]:Boolean(ut[n]);if(e.namespaceURI===Xe){if(t.namespaceURI===$e&&!ct[r])return!1;if(t.namespaceURI===Ve&&!lt[r])return!1;var o=E({},["title","style","font","a","script"]);return!ut[n]&&(o[n]||!st[n])}return!1},ft=function(e){p(n.removed,{element:e});try{e.parentNode.removeChild(e)}catch(t){try{e.outerHTML=ie}catch(t){e.remove()}}},dt=function(e,t){try{p(n.removed,{attribute:t.getAttributeNode(e),from:t})}catch(e){p(n.removed,{attribute:null,from:t})}if(t.removeAttribute(e),"is"===e&&!Ee[e])if(Ie||Fe)try{ft(t)}catch(e){}else try{t.setAttribute(e,"")}catch(e){}},pt=function(e){var t=void 0,n=void 0;if(Re)e=""+e;else{var r=g(e,/^[\r\n\t ]+/);n=r&&r[0]}"application/xhtml+xml"===Qe&&(e=''+e+"");var i=oe?oe.createHTML(e):e;if(Ze===Xe)try{t=(new X).parseFromString(i,Qe)}catch(e){}if(!t||!t.documentElement){t=le.createDocument(Ze,"template",null);try{t.documentElement.innerHTML=Je?"":i}catch(e){}}var a=t.body||t.documentElement;return e&&n&&a.insertBefore(o.createTextNode(n),a.childNodes[0]||null),Ze===Xe?ue.call(t,Me?"html":"body")[0]:Me?t.documentElement:a},ht=function(e){return ce.call(e.ownerDocument||e,e,u.SHOW_ELEMENT|u.SHOW_COMMENT|u.SHOW_TEXT,null,!1)},gt=function(e){return e instanceof $&&("string"!=typeof e.nodeName||"string"!=typeof e.textContent||"function"!=typeof e.removeChild||!(e.attributes instanceof A)||"function"!=typeof e.removeAttribute||"function"!=typeof e.setAttribute||"string"!=typeof e.namespaceURI||"function"!=typeof e.insertBefore)},yt=function(e){return"object"===(void 0===c?"undefined":q(c))?e instanceof c:e&&"object"===(void 0===e?"undefined":q(e))&&"number"==typeof e.nodeType&&"string"==typeof e.nodeName},vt=function(e,t,r){de[e]&&f(de[e],(function(e){e.call(n,t,r,rt)}))},bt=function(e){var t=void 0;if(vt("beforeSanitizeElements",e,null),gt(e))return ft(e),!0;if(g(e.nodeName,/[\u0080-\uFFFF]/))return ft(e),!0;var r=nt(e.nodeName);if(vt("uponSanitizeElement",e,{tagName:r,allowedTags:Ne}),!yt(e.firstElementChild)&&(!yt(e.content)||!yt(e.content.firstElementChild))&&T(/<[/\w]/g,e.innerHTML)&&T(/<[/\w]/g,e.textContent))return ft(e),!0;if("select"===r&&T(/