diff --git a/CHANGELOG.md b/CHANGELOG.md index f7977c0d..8002155b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,9 @@ # PrivateBin version history ## 2.0.4 (not yet released) -* CHANGED: Deduplicate JSON error message translations. +* CHANGED: Deduplicate JSON error message translations +* CHANGED: Refactored translation of exception messages +* FIXED: Some exceptions not getting translated ## 1.7.9 (2025-11-13) * CHANGED: Upgrading libraries to: base-x 5.0.1, bootstrap 5.3.8, DOMpurify 3.2.7, ip-lib 1.21.0 & kjua 0.10.0 diff --git a/composer.lock b/composer.lock index dac377f9..a7a66ba0 100644 --- a/composer.lock +++ b/composer.lock @@ -397,16 +397,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.6.1", + "version": "v5.6.2", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2" + "reference": "3a454ca033b9e06b63282ce19562e892747449bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", - "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb", + "reference": "3a454ca033b9e06b63282ce19562e892747449bb", "shasum": "" }, "require": { @@ -449,9 +449,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2" }, - "time": "2025-08-13T20:13:15+00:00" + "time": "2025-10-21T19:32:17+00:00" }, { "name": "phar-io/manifest", @@ -2014,16 +2014,16 @@ }, { "name": "theseer/tokenizer", - "version": "1.2.3", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + "reference": "d74205c497bfbca49f34d4bc4c19c17e22db4ebb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/d74205c497bfbca49f34d4bc4c19c17e22db4ebb", + "reference": "d74205c497bfbca49f34d4bc4c19c17e22db4ebb", "shasum": "" }, "require": { @@ -2052,7 +2052,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + "source": "https://github.com/theseer/tokenizer/tree/1.3.0" }, "funding": [ { @@ -2060,7 +2060,7 @@ "type": "github" } ], - "time": "2024-03-03T12:36:25+00:00" + "time": "2025-11-13T13:44:09+00:00" } ], "aliases": [], diff --git a/lib/Configuration.php b/lib/Configuration.php index 2cccc342..b02a72e4 100644 --- a/lib/Configuration.php +++ b/lib/Configuration.php @@ -12,6 +12,7 @@ namespace PrivateBin; use Exception; +use PrivateBin\Exception\TranslatedException; /** * Configuration @@ -131,7 +132,7 @@ class Configuration /** * parse configuration file and ensure default configuration values are present * - * @throws Exception + * @throws TranslatedException */ public function __construct() { @@ -148,7 +149,8 @@ class Configuration $config = parse_ini_file($configFile, true); foreach (array('main', 'model', 'model_options') as $section) { if (!array_key_exists($section, $config)) { - throw new Exception(I18n::_('PrivateBin requires configuration section [%s] to be present in configuration file.', $section), 2); + $name = $config['main']['name'] ?? self::getDefaults()['main']['name']; + throw new TranslatedException(array('%s requires configuration section [%s] to be present in configuration file.', I18n::_($name), $section), 2); } } break; @@ -158,7 +160,7 @@ class Configuration $opts = '_options'; foreach (self::getDefaults() as $section => $values) { // fill missing sections with default values - if (!array_key_exists($section, $config) || count($config[$section]) == 0) { + if (!array_key_exists($section, $config) || count($config[$section]) === 0) { $this->_configuration[$section] = $values; if (array_key_exists('dir', $this->_configuration[$section])) { $this->_configuration[$section]['dir'] = PATH . $this->_configuration[$section]['dir']; @@ -167,7 +169,7 @@ class Configuration } // provide different defaults for database model elseif ( - $section == 'model_options' && + $section === 'model_options' && $this->_configuration['model']['class'] === 'Database' ) { $values = array( @@ -178,7 +180,7 @@ class Configuration 'opt' => array(), ); } elseif ( - $section == 'model_options' && + $section === 'model_options' && $this->_configuration['model']['class'] === 'GoogleCloudStorage' ) { $values = array( @@ -187,7 +189,7 @@ class Configuration 'uniformacl' => false, ); } elseif ( - $section == 'model_options' && + $section === 'model_options' && $this->_configuration['model']['class'] === 'S3Storage' ) { $values = array( @@ -216,11 +218,11 @@ class Configuration // check for missing keys and set defaults if necessary else { // preserve configured SRI hashes - if ($section == 'sri' && array_key_exists($section, $config)) { + if ($section === 'sri' && array_key_exists($section, $config)) { $this->_configuration[$section] = $config[$section]; } foreach ($values as $key => $val) { - if ($key == 'dir') { + if ($key === 'dir') { $val = PATH . $val; } $result = $val; @@ -304,13 +306,13 @@ class Configuration * get a section from the configuration, must exist * * @param string $section - * @throws Exception + * @throws TranslatedException * @return mixed */ public function getSection($section) { if (!array_key_exists($section, $this->_configuration)) { - throw new Exception(I18n::_('%s requires configuration section [%s] to be present in configuration file.', I18n::_($this->getKey('name')), $section), 3); + throw new TranslatedException(array('%s requires configuration section [%s] to be present in configuration file.', I18n::_($this->getKey('name')), $section), 3); } return $this->_configuration[$section]; } diff --git a/lib/Controller.php b/lib/Controller.php index bb161907..cef82a61 100644 --- a/lib/Controller.php +++ b/lib/Controller.php @@ -12,6 +12,8 @@ namespace PrivateBin; use Exception; +use PrivateBin\Exception\JsonException; +use PrivateBin\Exception\TranslatedException; use PrivateBin\Persistence\ServerSalt; use PrivateBin\Persistence\TrafficLimiter; use PrivateBin\Proxy\AbstractProxy; @@ -195,13 +197,14 @@ class Controller * Set default language * * @access private + * @throws Exception */ private function _setDefaultLanguage() { $lang = $this->_conf->getKey('languagedefault'); I18n::setLanguageFallback($lang); // force default language, if language selection is disabled and a default is set - if (!$this->_conf->getKey('languageselection') && strlen($lang) == 2) { + if (!$this->_conf->getKey('languageselection') && strlen($lang) === 2) { $_COOKIE['lang'] = $lang; setcookie('lang', $lang, array('SameSite' => 'Lax', 'Secure' => true)); } @@ -211,6 +214,7 @@ class Controller * Set default template * * @access private + * @throws Exception */ private function _setDefaultTemplate() { @@ -260,6 +264,7 @@ class Controller * pasteid (optional) = in discussions, which paste this comment belongs to. * * @access private + * @throws Exception * @return string */ private function _create() @@ -270,8 +275,7 @@ class Controller TrafficLimiter::setStore($this->_model->getStore()); try { TrafficLimiter::canPass(); - } catch (Exception $e) { - // traffic limiter exceptions come translated + } catch (TranslatedException $e) { $this->_json_error($e->getMessage()); return; } @@ -305,12 +309,10 @@ class Controller $comment = $paste->getComment($data['parentid']); $comment->setData($data); $comment->store(); + $this->_json_result($comment->getId()); } catch (Exception $e) { - // comment exceptions need translation - $this->_json_error(I18n::_($e->getMessage())); - return; + $this->_json_error($e->getMessage()); } - $this->_json_result($comment->getId()); } else { $this->_json_error(I18n::_('Invalid data.')); } @@ -319,22 +321,13 @@ class Controller else { try { $this->_model->purge(); - } catch (Exception $e) { - error_log('Error purging documents: ' . $e->getMessage() . PHP_EOL . - 'Use the administration scripts statistics to find ' . - 'damaged paste IDs and either delete them or restore them ' . - 'from backup.'); - } - $paste = $this->_model->getPaste(); - try { + $paste = $this->_model->getPaste(); $paste->setData($data); $paste->store(); + $this->_json_result($paste->getId(), array('deletetoken' => $paste->getDeleteToken())); } catch (Exception $e) { - // paste exceptions need translation - $this->_json_error(I18n::_($e->getMessage())); - return; + $this->_json_error($e->getMessage()); } - $this->_json_result($paste->getId(), array('deletetoken' => $paste->getDeleteToken())); } } @@ -364,7 +357,7 @@ class Controller } else { $this->_error = self::GENERIC_ERROR; } - } catch (Exception $e) { + } catch (TranslatedException $e) { $this->_error = $e->getMessage(); } if ($this->_request->isJsonApiCall()) { @@ -399,9 +392,8 @@ class Controller } else { $this->_json_error(I18n::_(self::GENERIC_ERROR)); } - } catch (Exception $e) { - // paste exceptions need translation - $this->_json_error(I18n::_($e->getMessage())); + } catch (TranslatedException $e) { + $this->_json_error($e->getMessage()); } } @@ -409,6 +401,7 @@ class Controller * Display frontend. * * @access private + * @throws Exception */ private function _view() { @@ -428,7 +421,7 @@ class Controller // label all the expiration options $expire = array(); foreach ($this->_conf->getSection('expire_options') as $time => $seconds) { - $expire[$time] = ($seconds == 0) ? I18n::_(ucfirst($time)) : Filter::formatHumanReadableTime($time); + $expire[$time] = ($seconds === 0) ? I18n::_(ucfirst($time)) : Filter::formatHumanReadableTime($time); } // translate all the formatter options @@ -469,7 +462,7 @@ class Controller } $page->assign('BASEPATH', I18n::_($this->_conf->getKey('basepath'))); $page->assign('STATUS', I18n::_($this->_status)); - $page->assign('ISDELETED', I18n::_(json_encode($this->_is_deleted))); + $page->assign('ISDELETED', $this->_is_deleted); $page->assign('VERSION', self::VERSION); $page->assign('DISCUSSION', $this->_conf->getKey('discussion')); $page->assign('OPENDISCUSSION', $this->_conf->getKey('opendiscussion')); @@ -545,6 +538,7 @@ class Controller * * @access private * @param string $error + * @throws JsonException */ private function _json_error($error) { @@ -561,6 +555,7 @@ class Controller * @access private * @param string $dataid * @param array $other + * @throws JsonException */ private function _json_result($dataid, $other = array()) { diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 0fd42dcc..a574c13a 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -15,6 +15,7 @@ use Exception; use PDO; use PDOException; use PrivateBin\Controller; +use PrivateBin\Exception\JsonException; use PrivateBin\Json; /** @@ -179,18 +180,24 @@ class Database extends AbstractData 'SELECT * FROM "' . $this->_sanitizeIdentifier('paste') . '" WHERE "dataid" = ?', array($pasteid), true ); - } catch (Exception $e) { + } catch (PDOException $e) { $row = false; } if ($row === false) { return false; } // create array - $paste = Json::decode($row['data']); + try { + $paste = Json::decode($row['data']); + } catch (JsonException $e) { + error_log('Error while reading a paste from the database: ' . $e->getMessage()); + $paste = array(); + } try { $paste['meta'] = Json::decode($row['meta']); - } catch (Exception $e) { + } catch (JsonException $e) { + error_log('Error while reading a paste from the database: ' . $e->getMessage()); $paste['meta'] = array(); } $expire_date = (int) $row['expiredate']; @@ -233,7 +240,7 @@ class Database extends AbstractData 'SELECT "dataid" FROM "' . $this->_sanitizeIdentifier('paste') . '" WHERE "dataid" = ?', array($pasteid), true ); - } catch (Exception $e) { + } catch (PDOException $e) { return false; } return (bool) $row; @@ -253,7 +260,7 @@ class Database extends AbstractData { try { $data = Json::encode($comment); - } catch (Exception $e) { + } catch (JsonException $e) { error_log('Error while attempting to insert a comment into the database: ' . $e->getMessage()); return false; } @@ -274,7 +281,7 @@ class Database extends AbstractData $meta['created'], ) ); - } catch (Exception $e) { + } catch (PDOException $e) { error_log('Error while attempting to insert a comment into the database: ' . $e->getMessage()); return false; } @@ -298,8 +305,14 @@ class Database extends AbstractData $comments = array(); if (count($rows)) { foreach ($rows as $row) { + try { + $data = Json::decode($row['data']); + } catch (JsonException $e) { + error_log('Error while reading a comment from the database: ' . $e->getMessage()); + $data = array(); + } $i = $this->getOpenSlot($comments, (int) $row['postdate']); - $comments[$i] = Json::decode($row['data']); + $comments[$i] = $data; $comments[$i]['id'] = $row['dataid']; $comments[$i]['parentid'] = $row['parentid']; $comments[$i]['meta'] = array('created' => (int) $row['postdate']); @@ -329,7 +342,7 @@ class Database extends AbstractData '" WHERE "pasteid" = ? AND "parentid" = ? AND "dataid" = ?', array($pasteid, $parentid, $commentid), true ); - } catch (Exception $e) { + } catch (PDOException $e) { return false; } } @@ -349,7 +362,8 @@ class Database extends AbstractData $this->_last_cache[$key] = $value; try { $value = Json::encode($this->_last_cache); - } catch (Exception $e) { + } catch (JsonException $e) { + error_log('Error encoding JSON for table "config", row "traffic_limiter": ' . $e->getMessage()); return false; } } @@ -386,14 +400,17 @@ class Database extends AbstractData $fs = new Filesystem(array('dir' => 'data')); $value = $fs->getValue('salt'); $this->setValue($value, 'salt'); - unlink($file); + if (!unlink($file)) { + error_log('Error deleting migrated salt: ' . $file); + } return $value; } } if ($value && $namespace === 'traffic_limiter') { try { $this->_last_cache = Json::decode($value); - } catch (Exception $e) { + } catch (JsonException $e) { + error_log('Error decoding JSON from table "config", row "traffic_limiter": ' . $e->getMessage()); $this->_last_cache = array(); } if (array_key_exists($key, $this->_last_cache)) { @@ -412,13 +429,18 @@ class Database extends AbstractData */ protected function _getExpiredPastes($batchsize) { - $statement = $this->_db->prepare( - 'SELECT "dataid" FROM "' . $this->_sanitizeIdentifier('paste') . - '" WHERE "expiredate" < ? AND "expiredate" != ? ' . - ($this->_type === 'oci' ? 'FETCH NEXT ? ROWS ONLY' : 'LIMIT ?') - ); - $statement->execute(array(time(), 0, $batchsize)); - return $statement->fetchAll(PDO::FETCH_COLUMN, 0); + try { + $statement = $this->_db->prepare( + 'SELECT "dataid" FROM "' . $this->_sanitizeIdentifier('paste') . + '" WHERE "expiredate" < ? AND "expiredate" != ? ' . + ($this->_type === 'oci' ? 'FETCH NEXT ? ROWS ONLY' : 'LIMIT ?') + ); + $statement->execute(array(time(), 0, $batchsize)); + return $statement->fetchAll(PDO::FETCH_COLUMN, 0); + } catch (PDOException $e) { + error_log('Error while attempting to find expired pastes in the database: ' . $e->getMessage()); + return array(); + } } /** @@ -552,6 +574,7 @@ class Database extends AbstractData '" WHERE "id" = ?', array($key), true ); } catch (PDOException $e) { + error_log('Error while attempting to fetch configuration key "' . $key . '" in the database: ' . $e->getMessage()); return ''; } return $row ? $row['value'] : ''; diff --git a/lib/Data/Filesystem.php b/lib/Data/Filesystem.php index e4377a9f..8dbaa9d2 100644 --- a/lib/Data/Filesystem.php +++ b/lib/Data/Filesystem.php @@ -11,8 +11,9 @@ namespace PrivateBin\Data; -use Exception; +use DirectoryIterator; use GlobIterator; +use PrivateBin\Exception\JsonException; use PrivateBin\Json; /** @@ -104,13 +105,10 @@ class Filesystem extends AbstractData */ public function read($pasteid) { - if ( - !$this->exists($pasteid) || - !$paste = $this->_get($this->_dataid2path($pasteid) . $pasteid . '.php') - ) { - return false; + if ($this->exists($pasteid)) { + return $this->_get($this->_dataid2path($pasteid) . $pasteid . '.php'); } - return $paste; + return false; } /** @@ -124,21 +122,24 @@ class Filesystem extends AbstractData $pastedir = $this->_dataid2path($pasteid); if (is_dir($pastedir)) { // Delete the paste itself. - if (is_file($pastedir . $pasteid . '.php')) { - unlink($pastedir . $pasteid . '.php'); + $pastefile = $pastedir . $pasteid . '.php'; + if (is_file($pastefile)) { + if (!unlink($pastefile)) { + error_log('Error deleting paste: ' . $pastefile); + } } // Delete discussion if it exists. $discdir = $this->_dataid2discussionpath($pasteid); if (is_dir($discdir)) { // Delete all files in discussion directory - $dir = dir($discdir); - while (false !== ($filename = $dir->read())) { - if (is_file($discdir . $filename)) { - unlink($discdir . $filename); + foreach (new DirectoryIterator($discdir) as $file) { + if ($file->isFile()) { + if (!unlink($file->getPathname())) { + error_log('Error deleting comment: ' . $file->getPathname()); + } } } - $dir->close(); rmdir($discdir); } } @@ -162,14 +163,11 @@ class Filesystem extends AbstractData // convert comments, too $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'; - $this->_prependRename($discdir . $filename, $commentFilename); + foreach (new DirectoryIterator($discdir) as $file) { + if ($file->getExtension() !== 'php' && strlen($file->getFilename()) >= 16) { + $this->_prependRename($file->getPathname(), $file->getPathname() . '.php'); } } - $dir->close(); } } return is_readable($pastePath); @@ -210,15 +208,14 @@ class Filesystem extends AbstractData $comments = array(); $discdir = $this->_dataid2discussionpath($pasteid); if (is_dir($discdir)) { - $dir = dir($discdir); - while (false !== ($filename = $dir->read())) { + foreach (new DirectoryIterator($discdir) as $file) { // Filename is in the form pasteid.commentid.parentid.php: // - pasteid is the paste this reply belongs to. // - commentid is the comment identifier itself. // - parentid is the comment this comment replies to (It can be pasteid) - if (is_file($discdir . $filename)) { - $comment = $this->_get($discdir . $filename); - $items = explode('.', $filename); + if ($file->isFile()) { + $comment = $this->_get($file->getPathname()); + $items = explode('.', $file->getBasename('.php')); // Add some meta information not contained in file. $comment['id'] = $items[1]; $comment['parentid'] = $items[2]; @@ -231,7 +228,6 @@ class Filesystem extends AbstractData $comments[$key] = $comment; } } - $dir->close(); // Sort comments by date, oldest first. ksort($comments); @@ -312,7 +308,7 @@ class Filesystem extends AbstractData $file = $this->_path . DIRECTORY_SEPARATOR . 'salt.php'; if (is_readable($file)) { $items = explode('|', file_get_contents($file)); - if (count($items) == 3) { + if (count($items) === 3) { return $items[1]; } } @@ -346,7 +342,12 @@ class Filesystem extends AbstractData file_get_contents($filename), strlen(self::PROTECTION_LINE . PHP_EOL) ); - return Json::decode($data); + try { + return Json::decode($data); + } catch (JsonException $e) { + error_log('Error decoding JSON from "' . $filename . '": ' . $e->getMessage()); + return false; + } } /** @@ -368,10 +369,7 @@ class Filesystem extends AbstractData foreach ($files as $pasteid) { if ($this->exists($pasteid)) { $data = $this->read($pasteid); - if ( - array_key_exists('expire_date', $data['meta']) && - $data['meta']['expire_date'] < $time - ) { + if (($data['meta']['expire_date'] ?? $time) < $time) { $pastes[] = $pasteid; if (++$count >= $batchsize) { break; @@ -450,7 +448,7 @@ class Filesystem extends AbstractData $filename, self::PROTECTION_LINE . PHP_EOL . Json::encode($data) ); - } catch (Exception $e) { + } catch (JsonException $e) { error_log('Error while trying to store data to the filesystem at path "' . $filename . '": ' . $e->getMessage()); return false; } @@ -523,6 +521,8 @@ class Filesystem extends AbstractData file_put_contents($destFile, $handle, FILE_APPEND); fclose($handle); } - unlink($srcFile); + if (!unlink($srcFile)) { + error_log('Error deleting converted document: ' . $srcFile); + } } } diff --git a/lib/Data/GoogleCloudStorage.php b/lib/Data/GoogleCloudStorage.php index 9f64abe5..971825c5 100644 --- a/lib/Data/GoogleCloudStorage.php +++ b/lib/Data/GoogleCloudStorage.php @@ -15,6 +15,7 @@ use Exception; use Google\Cloud\Core\Exception\NotFoundException; use Google\Cloud\Storage\Bucket; use Google\Cloud\Storage\StorageClient; +use PrivateBin\Exception\JsonException; use PrivateBin\Json; class GoogleCloudStorage extends AbstractData @@ -89,7 +90,7 @@ class GoogleCloudStorage extends AbstractData */ private function _getKey($pasteid) { - if ($this->_prefix != '') { + if (!empty($this->_prefix)) { return $this->_prefix . '/' . $pasteid; } return $pasteid; @@ -106,7 +107,7 @@ class GoogleCloudStorage extends AbstractData */ private function _upload($key, &$payload) { - $metadata = array_key_exists('meta', $payload) ? $payload['meta'] : array(); + $metadata = $payload['meta'] ?? array(); unset($metadata['salt']); foreach ($metadata as $k => $v) { $metadata[$k] = strval($v); @@ -219,7 +220,12 @@ class GoogleCloudStorage extends AbstractData try { foreach ($this->_bucket->objects(array('prefix' => $prefix)) as $key) { $data = $this->_bucket->object($key->name())->downloadAsString(); - $comment = Json::decode($data); + try { + $comment = Json::decode($data); + } catch (JsonException $e) { + error_log('failed to read comment from ' . $key->name() . ', ' . $e->getMessage()); + $comment = array(); + } $comment['id'] = basename($key->name()); $slot = $this->getOpenSlot($comments, (int) $comment['meta']['created']); $comments[$slot] = $comment; @@ -252,15 +258,12 @@ class GoogleCloudStorage extends AbstractData if (strlen($name) > strlen($path) && substr($name, strlen($path), 1) !== '/') { continue; } - $info = $object->info(); - if (key_exists('metadata', $info) && key_exists('value', $info['metadata'])) { - $value = $info['metadata']['value']; - if (is_numeric($value) && intval($value) < $time) { - try { - $object->delete(); - } catch (NotFoundException $e) { - // deleted by another instance. - } + $value = $object->info()['metadata']['value'] ?? ''; + if (is_numeric($value) && intval($value) < $time) { + try { + $object->delete(); + } catch (NotFoundException $e) { + // deleted by another instance. } } } @@ -276,14 +279,14 @@ class GoogleCloudStorage extends AbstractData */ public function setValue($value, $namespace, $key = '') { - if ($key === '') { + if (empty($key)) { $key = 'config/' . $namespace; } else { $key = 'config/' . $namespace . '/' . $key; } $metadata = array('namespace' => $namespace); - if ($namespace != 'salt') { + if ($namespace !== 'salt') { $metadata['value'] = strval($value); } try { @@ -334,17 +337,14 @@ class GoogleCloudStorage extends AbstractData $now = time(); $prefix = $this->_prefix; - if ($prefix != '') { + if (!empty($prefix)) { $prefix .= '/'; } try { 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']); - if ($expire_at != 0 && $expire_at < $now) { - array_push($expired, basename($object->name())); - } + $expire_at = $object->info()['metadata']['expire_date'] ?? ''; + if (is_numeric($expire_at) && intval($expire_at) < $now) { + array_push($expired, basename($object->name())); } if (count($expired) > $batchsize) { @@ -364,7 +364,7 @@ class GoogleCloudStorage extends AbstractData { $pastes = array(); $prefix = $this->_prefix; - if ($prefix != '') { + if (!empty($prefix)) { $prefix .= '/'; } diff --git a/lib/Data/S3Storage.php b/lib/Data/S3Storage.php index 526a9ec2..ccaf2526 100644 --- a/lib/Data/S3Storage.php +++ b/lib/Data/S3Storage.php @@ -37,6 +37,7 @@ namespace PrivateBin\Data; use Aws\S3\Exception\S3Exception; use Aws\S3\S3Client; +use PrivateBin\Exception\JsonException; use PrivateBin\Json; class S3Storage extends AbstractData @@ -147,7 +148,7 @@ class S3Storage extends AbstractData */ private function _getKey($pasteid) { - if ($this->_prefix != '') { + if (!empty($this->_prefix)) { return $this->_prefix . '/' . $pasteid; } return $pasteid; @@ -164,7 +165,7 @@ class S3Storage extends AbstractData */ private function _upload($key, &$payload) { - $metadata = array_key_exists('meta', $payload) ? $payload['meta'] : array(); + $metadata = $payload['meta'] ?? array(); unset($metadata['salt']); foreach ($metadata as $k => $v) { $metadata[$k] = strval($v); @@ -177,12 +178,14 @@ class S3Storage extends AbstractData 'ContentType' => 'application/json', 'Metadata' => $metadata, )); + return true; } catch (S3Exception $e) { error_log('failed to upload ' . $key . ' to ' . $this->_bucket . ', ' . trim(preg_replace('/\s\s+/', ' ', $e->getMessage()))); - return false; + } catch (JsonException $e) { + error_log('failed to JSON encode ' . $key . ', ' . $e->getMessage()); } - return true; + return false; } /** @@ -212,8 +215,10 @@ class S3Storage extends AbstractData } catch (S3Exception $e) { error_log('failed to read ' . $pasteid . ' from ' . $this->_bucket . ', ' . trim(preg_replace('/\s\s+/', ' ', $e->getMessage()))); - return false; + } catch (JsonException $e) { + error_log('failed to JSON decode ' . $pasteid . ', ' . $e->getMessage()); } + return false; } /** @@ -312,7 +317,7 @@ class S3Storage extends AbstractData public function purgeValues($namespace, $time) { $path = $this->_prefix; - if ($path != '') { + if (!empty($path)) { $path .= '/'; } $path .= 'config/' . $namespace; @@ -327,17 +332,15 @@ class S3Storage extends AbstractData 'Bucket' => $this->_bucket, 'Key' => $name, )); - if ($head->get('Metadata') != null && array_key_exists('value', $head->get('Metadata'))) { - $value = $head->get('Metadata')['value']; - if (is_numeric($value) && intval($value) < $time) { - try { - $this->_client->deleteObject(array( - 'Bucket' => $this->_bucket, - 'Key' => $name, - )); - } catch (S3Exception $e) { - // deleted by another instance. - } + $value = $head->get('Metadata')['value'] ?? ''; + if (is_numeric($value) && intval($value) < $time) { + try { + $this->_client->deleteObject(array( + 'Bucket' => $this->_bucket, + 'Key' => $name, + )); + } catch (S3Exception $e) { + // deleted by another instance. } } } @@ -354,7 +357,7 @@ class S3Storage extends AbstractData public function setValue($value, $namespace, $key = '') { $prefix = $this->_prefix; - if ($prefix != '') { + if (!empty($prefix)) { $prefix .= '/'; } @@ -365,7 +368,7 @@ class S3Storage extends AbstractData } $metadata = array('namespace' => $namespace); - if ($namespace != 'salt') { + if ($namespace !== 'salt') { $metadata['value'] = strval($value); } try { @@ -390,7 +393,7 @@ class S3Storage extends AbstractData public function getValue($namespace, $key = '') { $prefix = $this->_prefix; - if ($prefix != '') { + if (!empty($prefix)) { $prefix .= '/'; } @@ -419,7 +422,7 @@ class S3Storage extends AbstractData $expired = array(); $now = time(); $prefix = $this->_prefix; - if ($prefix != '') { + if (!empty($prefix)) { $prefix .= '/'; } @@ -429,11 +432,9 @@ class S3Storage extends AbstractData 'Bucket' => $this->_bucket, 'Key' => $object['Key'], )); - if ($head->get('Metadata') != null && array_key_exists('expire_date', $head->get('Metadata'))) { - $expire_at = intval($head->get('Metadata')['expire_date']); - if ($expire_at != 0 && $expire_at < $now) { - array_push($expired, $object['Key']); - } + $expire_at = $head->get('Metadata')['expire_date'] ?? ''; + if (is_numeric($expire_at) && intval($expire_at) < $now) { + array_push($expired, $object['Key']); } if (count($expired) > $batchsize) { @@ -453,7 +454,7 @@ class S3Storage extends AbstractData { $pastes = array(); $prefix = $this->_prefix; - if ($prefix != '') { + if (!empty($prefix)) { $prefix .= '/'; } diff --git a/lib/Exception/JsonException.php b/lib/Exception/JsonException.php new file mode 100644 index 00000000..dea5c97f --- /dev/null +++ b/lib/Exception/JsonException.php @@ -0,0 +1,38 @@ +append(new GlobIterator(self::_getPath('??.json'))); $languageIterator->append(new GlobIterator(self::_getPath('???.json'))); // for jbo foreach ($languageIterator as $file) { $language = $file->getBasename('.json'); - if ($language != 'en') { + if ($language !== 'en') { self::$_availableLanguages[] = $language; } } @@ -270,16 +270,17 @@ class I18n * @access public * @static * @param array $languages + * @throws JsonException * @return array */ public static function getLanguageLabels($languages = array()) { $file = self::_getPath('languages.json'); - if (count(self::$_languageLabels) == 0 && is_readable($file)) { + if (count(self::$_languageLabels) === 0 && is_readable($file)) { $data = file_get_contents($file); self::$_languageLabels = Json::decode($data); } - if (count($languages) == 0) { + if (count($languages) === 0) { return self::$_languageLabels; } return array_intersect_key(self::$_languageLabels, array_flip($languages)); @@ -366,7 +367,7 @@ class I18n return $n === 1 ? 0 : (($n === 0 || ($n % 100 > 0 && $n % 100 < 20)) ? 1 : 2); case 'ru': case 'uk': - return $n % 10 === 1 && $n % 100 != 11 ? 0 : ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2); + return $n % 10 === 1 && $n % 100 !== 11 ? 0 : ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2); case 'sl': return $n % 100 === 1 ? 1 : ($n % 100 === 2 ? 2 : ($n % 100 === 3 || $n % 100 === 4 ? 3 : 0)); default: diff --git a/lib/Json.php b/lib/Json.php index cc405575..15453cfa 100644 --- a/lib/Json.php +++ b/lib/Json.php @@ -11,7 +11,7 @@ namespace PrivateBin; -use Exception; +use PrivateBin\Exception\JsonException; /** * Json @@ -26,7 +26,7 @@ class Json * @access public * @static * @param mixed $input - * @throws Exception + * @throws JsonException * @return string */ public static function encode(&$input) @@ -42,7 +42,7 @@ class Json * @access public * @static * @param string $input - * @throws Exception + * @throws JsonException * @return mixed */ public static function decode(&$input) @@ -57,21 +57,14 @@ class Json * * @access private * @static - * @throws Exception + * @throws JsonException * @return void */ private static function _detectError() { $errorCode = json_last_error(); - if ($errorCode === JSON_ERROR_NONE) { - return; + if ($errorCode !== JSON_ERROR_NONE) { + throw new JsonException($errorCode); } - - $message = 'A JSON error occurred'; - if (function_exists('json_last_error_msg')) { - $message .= ': ' . json_last_error_msg(); - } - $message .= ' (' . $errorCode . ')'; - throw new Exception($message, 90); } } diff --git a/lib/Model/AbstractModel.php b/lib/Model/AbstractModel.php index d7b5102f..51c11dfa 100644 --- a/lib/Model/AbstractModel.php +++ b/lib/Model/AbstractModel.php @@ -11,9 +11,9 @@ namespace PrivateBin\Model; -use Exception; use PrivateBin\Configuration; use PrivateBin\Data\AbstractData; +use PrivateBin\Exception\TranslatedException; /** * AbstractModel @@ -22,6 +22,20 @@ use PrivateBin\Data\AbstractData; */ abstract class AbstractModel { + /** + * show the same error message if the data is invalid + * + * @const string + */ + const INVALID_DATA_ERROR = 'Invalid data.'; + + /** + * show the same error message if the document ID already exists + * + * @const string + */ + const COLLISION_ERROR = 'You are unlucky. Try again.'; + /** * Instance ID. * @@ -83,12 +97,12 @@ abstract class AbstractModel * * @access public * @param string $id - * @throws Exception + * @throws TranslatedException */ public function setId($id) { if (!self::isValidId($id)) { - throw new Exception('Invalid document ID.', 60); + throw new TranslatedException('Invalid document ID.', 60); } $this->_id = $id; } @@ -98,7 +112,7 @@ abstract class AbstractModel * * @access public * @param array $data - * @throws Exception + * @throws TranslatedException */ public function setData(array &$data) { @@ -125,7 +139,7 @@ abstract class AbstractModel * Store the instance's data. * * @access public - * @throws Exception + * @throws TranslatedException */ abstract public function store(); @@ -163,7 +177,7 @@ abstract class AbstractModel * * @access protected * @param array $data - * @throws Exception + * @throws TranslatedException */ protected function _validate(array &$data) { diff --git a/lib/Model/Comment.php b/lib/Model/Comment.php index 3c0b2b20..2a55b588 100644 --- a/lib/Model/Comment.php +++ b/lib/Model/Comment.php @@ -11,9 +11,9 @@ namespace PrivateBin\Model; -use Exception; use Identicon\Identicon; use Jdenticon\Identicon as Jdenticon; +use PrivateBin\Exception\TranslatedException; use PrivateBin\Persistence\TrafficLimiter; use PrivateBin\Vizhash16x16; @@ -36,24 +36,24 @@ class Comment extends AbstractModel * Store the comment's data. * * @access public - * @throws Exception + * @throws TranslatedException */ public function store() { // Make sure paste exists. $pasteid = $this->getPaste()->getId(); if (!$this->getPaste()->exists()) { - throw new Exception('Invalid data.', 67); + throw new TranslatedException(self::INVALID_DATA_ERROR, 67); } // Make sure the discussion is opened in this paste and allowed in the configuration. if (!$this->getPaste()->isOpendiscussion() || !$this->_conf->getKey('discussion')) { - throw new Exception('Invalid data.', 68); + throw new TranslatedException(self::INVALID_DATA_ERROR, 68); } // Check for improbable collision. if ($this->exists()) { - throw new Exception('You are unlucky. Try again.', 69); + throw new TranslatedException(self::COLLISION_ERROR, 69); } $this->_data['meta']['created'] = time(); @@ -67,7 +67,7 @@ class Comment extends AbstractModel $this->_data ) === false ) { - throw new Exception('Error saving comment. Sorry.', 70); + throw new TranslatedException('Error saving comment. Sorry.', 70); } } @@ -91,7 +91,6 @@ class Comment extends AbstractModel * * @access public * @param Paste $paste - * @throws Exception */ public function setPaste(Paste &$paste) { @@ -115,12 +114,12 @@ class Comment extends AbstractModel * * @access public * @param string $id - * @throws Exception + * @throws TranslatedException */ public function setParentId($id) { if (!self::isValidId($id)) { - throw new Exception('Invalid document ID.', 65); + throw new TranslatedException('Invalid document ID.', 65); } $this->_data['parentid'] = $id; } @@ -149,13 +148,13 @@ class Comment extends AbstractModel { // we generate an icon based on a SHA512 HMAC of the users IP, if configured $icon = $this->_conf->getKey('icon'); - if ($icon != 'none') { + if ($icon !== 'none') { $pngdata = ''; $hmac = TrafficLimiter::getHash(); - if ($icon == 'identicon') { + if ($icon === 'identicon') { $identicon = new Identicon(); $pngdata = $identicon->getImageDataUri($hmac, 16); - } elseif ($icon == 'jdenticon') { + } elseif ($icon === 'jdenticon') { $jdenticon = new Jdenticon(array( 'hash' => $hmac, 'size' => 16, @@ -165,13 +164,13 @@ class Comment extends AbstractModel ), )); $pngdata = $jdenticon->getImageDataUri('png'); - } elseif ($icon == 'vizhash') { + } elseif ($icon === 'vizhash') { $vh = new Vizhash16x16(); $pngdata = 'data:image/png;base64,' . base64_encode( $vh->generate($hmac) ); } - if ($pngdata != '') { + if (!empty($pngdata)) { if (!array_key_exists('meta', $data)) { $data['meta'] = array(); } diff --git a/lib/Model/Paste.php b/lib/Model/Paste.php index a42ab35e..884b5fb1 100644 --- a/lib/Model/Paste.php +++ b/lib/Model/Paste.php @@ -11,8 +11,8 @@ namespace PrivateBin\Model; -use Exception; use PrivateBin\Controller; +use PrivateBin\Exception\TranslatedException; use PrivateBin\Persistence\ServerSalt; /** @@ -47,14 +47,14 @@ class Paste extends AbstractModel * Get paste data. * * @access public - * @throws Exception + * @throws TranslatedException * @return array */ public function get() { $data = $this->_store->read($this->getId()); if ($data === false) { - throw new Exception(Controller::GENERIC_ERROR, 64); + throw new TranslatedException(Controller::GENERIC_ERROR, 64); } // check if paste has expired and delete it if necessary. @@ -62,7 +62,7 @@ class Paste extends AbstractModel $now = time(); if ($data['meta']['expire_date'] < $now) { $this->delete(); - throw new Exception(Controller::GENERIC_ERROR, 63); + throw new TranslatedException(Controller::GENERIC_ERROR, 63); } // We kindly provide the remaining time before expiration (in seconds) $data['meta']['time_to_live'] = $data['meta']['expire_date'] - $now; @@ -73,10 +73,7 @@ class Paste extends AbstractModel } // check if non-expired burn after reading paste needs to be deleted - if ( - array_key_exists('adata', $data) && - $data['adata'][self::ADATA_BURN_AFTER_READING] === 1 - ) { + if (($data['adata'][self::ADATA_BURN_AFTER_READING] ?? 0) === 1) { $this->delete(); } @@ -93,13 +90,13 @@ class Paste extends AbstractModel * Store the paste's data. * * @access public - * @throws Exception + * @throws TranslatedException */ public function store() { // Check for improbable collision. if ($this->exists()) { - throw new Exception('You are unlucky. Try again.', 75); + throw new TranslatedException(self::COLLISION_ERROR, 75); } $this->_data['meta']['salt'] = ServerSalt::generate(); @@ -111,7 +108,7 @@ class Paste extends AbstractModel $this->_data ) === false ) { - throw new Exception('Error saving document. Sorry.', 76); + throw new TranslatedException('Error saving document. Sorry.', 76); } } @@ -119,7 +116,6 @@ class Paste extends AbstractModel * Delete the paste. * * @access public - * @throws Exception */ public function delete() { @@ -143,18 +139,18 @@ class Paste extends AbstractModel * @access public * @param string $parentId * @param string $commentId - * @throws Exception + * @throws TranslatedException * @return Comment */ public function getComment($parentId, $commentId = '') { if (!$this->exists()) { - throw new Exception('Invalid data.', 62); + throw new TranslatedException(self::INVALID_DATA_ERROR, 62); } $comment = new Comment($this->_conf, $this->_store); $comment->setPaste($this); $comment->setParentId($parentId); - if ($commentId !== '') { + if (!empty($commentId)) { $comment->setId($commentId); } return $comment; @@ -201,7 +197,6 @@ class Paste extends AbstractModel * Check if paste has discussions enabled. * * @access public - * @throws Exception * @return bool */ public function isOpendiscussion() @@ -209,8 +204,7 @@ class Paste extends AbstractModel if (!array_key_exists('adata', $this->_data) && !array_key_exists('data', $this->_data)) { $this->get(); } - return array_key_exists('adata', $this->_data) && - $this->_data['adata'][self::ADATA_OPEN_DISCUSSION] === 1; + return ($this->_data['adata'][self::ADATA_OPEN_DISCUSSION] ?? 0) === 1; } /** @@ -224,12 +218,9 @@ class Paste extends AbstractModel $expiration = $data['meta']['expire'] ?? 0; unset($data['meta']['expire']); $expire_options = $this->_conf->getSection('expire_options'); - if (array_key_exists($expiration, $expire_options)) { - $expire = $expire_options[$expiration]; - } else { - // using getKey() to ensure a default value is present - $expire = $this->_conf->getKey($this->_conf->getKey('default', 'expire'), 'expire_options'); - } + // using getKey() to ensure a default value is present + $expire = $expire_options[$expiration] ?? + $this->_conf->getKey($this->_conf->getKey('default', 'expire'), 'expire_options'); if ($expire > 0) { $data['meta']['expire_date'] = time() + $expire; } @@ -240,13 +231,13 @@ class Paste extends AbstractModel * * @access protected * @param array $data - * @throws Exception + * @throws TranslatedException */ protected function _validate(array &$data) { // reject invalid or disabled formatters if (!array_key_exists($data['adata'][self::ADATA_FORMATTER], $this->_conf->getSection('formatter_options'))) { - throw new Exception('Invalid data.', 75); + throw new TranslatedException(self::INVALID_DATA_ERROR, 75); } // discussion requested, but disabled in config or burn after reading requested as well, or invalid integer @@ -257,7 +248,7 @@ class Paste extends AbstractModel )) || ($data['adata'][self::ADATA_OPEN_DISCUSSION] !== 0 && $data['adata'][self::ADATA_OPEN_DISCUSSION] !== 1) ) { - throw new Exception('Invalid data.', 74); + throw new TranslatedException(self::INVALID_DATA_ERROR, 74); } // reject invalid burn after reading @@ -265,7 +256,7 @@ class Paste extends AbstractModel $data['adata'][self::ADATA_BURN_AFTER_READING] !== 0 && $data['adata'][self::ADATA_BURN_AFTER_READING] !== 1 ) { - throw new Exception('Invalid data.', 73); + throw new TranslatedException(self::INVALID_DATA_ERROR, 73); } } } diff --git a/lib/Persistence/TrafficLimiter.php b/lib/Persistence/TrafficLimiter.php index 60977f5d..2c937205 100644 --- a/lib/Persistence/TrafficLimiter.php +++ b/lib/Persistence/TrafficLimiter.php @@ -12,11 +12,10 @@ namespace PrivateBin\Persistence; -use Exception; use IPLib\Factory; use IPLib\ParseStringFlag; use PrivateBin\Configuration; -use PrivateBin\I18n; +use PrivateBin\Exception\TranslatedException; /** * TrafficLimiter @@ -74,7 +73,7 @@ class TrafficLimiter extends AbstractPersistence self::setExempted($conf->getKey('exempted', 'traffic')); self::setLimit($conf->getKey('limit', 'traffic')); - if (($option = $conf->getKey('header', 'traffic')) !== '') { + if (!empty($option = $conf->getKey('header', 'traffic'))) { $httpHeader = 'HTTP_' . $option; if (array_key_exists($httpHeader, $_SERVER) && !empty($_SERVER[$httpHeader])) { self::$_ipKey = $httpHeader; @@ -167,7 +166,7 @@ class TrafficLimiter extends AbstractPersistence * * @access public * @static - * @throws Exception + * @throws TranslatedException * @return true */ public static function canPass() @@ -181,7 +180,7 @@ class TrafficLimiter extends AbstractPersistence return true; } } - throw new Exception(I18n::_('Your IP is not authorized to create documents.')); + throw new TranslatedException('Your IP is not authorized to create documents.'); } // disable limits if set to less then 1 @@ -210,9 +209,9 @@ class TrafficLimiter extends AbstractPersistence } return true; } - throw new Exception(I18n::_( + throw new TranslatedException(array( 'Please wait %d seconds between each post.', - self::$_limit + self::$_limit, )); } } diff --git a/lib/Proxy/AbstractProxy.php b/lib/Proxy/AbstractProxy.php index e45f5377..b09c609d 100644 --- a/lib/Proxy/AbstractProxy.php +++ b/lib/Proxy/AbstractProxy.php @@ -11,8 +11,8 @@ namespace PrivateBin\Proxy; -use Exception; use PrivateBin\Configuration; +use PrivateBin\Exception\JsonException; use PrivateBin\Json; /** @@ -55,7 +55,7 @@ abstract class AbstractProxy } if (!str_starts_with($link, $conf->getKey('basepath') . '?') || - parse_url($link, PHP_URL_HOST) != parse_url($conf->getKey('basepath'), PHP_URL_HOST) + parse_url($link, PHP_URL_HOST) !== parse_url($conf->getKey('basepath'), PHP_URL_HOST) ) { $this->_error = 'Trying to shorten a URL that isn\'t pointing at our instance.'; return; @@ -90,7 +90,7 @@ abstract class AbstractProxy try { $jsonData = Json::decode($data); - } catch (Exception $e) { + } catch (JsonException $e) { $this->_error = 'Proxy error: Error parsing proxy response. This can be a configuration issue, like wrong or missing config keys.'; $this->logErrorWithClassName('Error calling proxy: ' . $e->getMessage()); return; diff --git a/lib/Proxy/ShlinkProxy.php b/lib/Proxy/ShlinkProxy.php index ee4507cf..93c3be27 100644 --- a/lib/Proxy/ShlinkProxy.php +++ b/lib/Proxy/ShlinkProxy.php @@ -12,6 +12,7 @@ namespace PrivateBin\Proxy; use PrivateBin\Configuration; +use PrivateBin\Exception\JsonException; use PrivateBin\Json; /** @@ -48,12 +49,17 @@ class ShlinkProxy extends AbstractProxy 'longUrl' => $link, ); - return array( - 'method' => 'POST', - 'header' => "Content-Type: application/json\r\n" . - 'X-Api-Key: ' . $shlink_api_key . "\r\n", - 'content' => Json::encode($body), - ); + try { + return array( + 'method' => 'POST', + 'header' => "Content-Type: application/json\r\n" . + 'X-Api-Key: ' . $shlink_api_key . "\r\n", + 'content' => Json::encode($body), + ); + } catch (JsonException $e) { + error_log('[' . get_class($this) . '] Error encoding body: ' . $e->getMessage()); + return array(); + } } /** @@ -65,11 +71,6 @@ class ShlinkProxy extends AbstractProxy */ protected function _extractShortUrl(array $data): ?string { - if ( - array_key_exists('shortUrl', $data) - ) { - return $data['shortUrl']; - } - return null; + return $data['shortUrl'] ?? null; } } diff --git a/lib/Proxy/YourlsProxy.php b/lib/Proxy/YourlsProxy.php index ae9e11e7..f1762e57 100644 --- a/lib/Proxy/YourlsProxy.php +++ b/lib/Proxy/YourlsProxy.php @@ -65,12 +65,8 @@ class YourlsProxy extends AbstractProxy */ protected function _extractShortUrl(array $data): ?string { - if ( - array_key_exists('statusCode', $data) && - $data['statusCode'] == 200 && - array_key_exists('shorturl', $data) - ) { - return $data['shorturl']; + if (($data['statusCode'] ?? 0) === 200) { + return $data['shorturl'] ?? 0; } return null; } diff --git a/lib/Request.php b/lib/Request.php index 24a083be..2e386e6f 100644 --- a/lib/Request.php +++ b/lib/Request.php @@ -11,7 +11,7 @@ namespace PrivateBin; -use Exception; +use PrivateBin\Exception\JsonException; use PrivateBin\Model\Paste; /** @@ -104,7 +104,7 @@ class Request $this->_isJsonApi = $this->_detectJsonRequest(); // parse parameters, depending on request type - switch (array_key_exists('REQUEST_METHOD', $_SERVER) ? $_SERVER['REQUEST_METHOD'] : 'GET') { + switch ($_SERVER['REQUEST_METHOD'] ?? 'GET') { case 'DELETE': case 'PUT': case 'POST': @@ -113,7 +113,7 @@ class Request try { $data = file_get_contents(self::$_inputStream); $this->_params = Json::decode($data); - } catch (Exception $e) { + } catch (JsonException $e) { // ignore error, $this->_params will remain empty } break; @@ -141,7 +141,7 @@ class Request if (array_key_exists('pasteid', $this->_params) && !empty($this->_params['pasteid'])) { if (array_key_exists('deletetoken', $this->_params) && !empty($this->_params['deletetoken'])) { $this->_operation = 'delete'; - } elseif ($this->_operation != 'create') { + } elseif ($this->_operation !== 'create') { $this->_operation = 'read'; } } elseif (array_key_exists('jsonld', $this->_params) && !empty($this->_params['jsonld'])) { @@ -187,7 +187,7 @@ class Request $data['meta'] = $meta; } foreach ($required_keys as $key) { - $data[$key] = $this->getParam($key, $key == 'v' ? 1 : ''); + $data[$key] = $this->getParam($key, $key === 'v' ? 1 : ''); } // forcing a cast to int or float $data['v'] = $data['v'] + 0; @@ -204,8 +204,7 @@ class Request */ public function getParam($param, $default = '') { - return array_key_exists($param, $this->_params) ? - $this->_params[$param] : $default; + return $this->_params[$param] ?? $default; } /** @@ -263,23 +262,22 @@ class Request */ private function _detectJsonRequest() { - $hasAcceptHeader = array_key_exists('HTTP_ACCEPT', $_SERVER); - $acceptHeader = $hasAcceptHeader ? $_SERVER['HTTP_ACCEPT'] : ''; + $acceptHeader = $_SERVER['HTTP_ACCEPT'] ?? ''; // simple cases if ( - (array_key_exists('HTTP_X_REQUESTED_WITH', $_SERVER) && - $_SERVER['HTTP_X_REQUESTED_WITH'] == 'JSONHttpRequest') || - ($hasAcceptHeader && + ($_SERVER['HTTP_X_REQUESTED_WITH'] ?? '') === 'JSONHttpRequest' || + ( str_contains($acceptHeader, self::MIME_JSON) && !str_contains($acceptHeader, self::MIME_HTML) && - !str_contains($acceptHeader, self::MIME_XHTML)) + !str_contains($acceptHeader, self::MIME_XHTML) + ) ) { return true; } // advanced case: media type negotiation - if ($hasAcceptHeader) { + if (!empty($acceptHeader)) { $mediaTypes = array(); foreach (explode(',', trim($acceptHeader)) as $mediaTypeRange) { if (preg_match( diff --git a/lib/Vizhash16x16.php b/lib/Vizhash16x16.php index 12c2efcc..8ac03be7 100644 --- a/lib/Vizhash16x16.php +++ b/lib/Vizhash16x16.php @@ -103,7 +103,7 @@ class Vizhash16x16 // First, create an image with a specific gradient background. $op = 'v'; - if (($this->getInt() % 2) == 0) { + if (($this->getInt() % 2) === 0) { $op = 'h'; } $image = $this->degrade($image, $op, array($r0, $g0, $b0), array(0, 0, 0)); @@ -179,7 +179,7 @@ class Vizhash16x16 */ private function degrade($img, $direction, $color1, $color2) { - if ($direction == 'h') { + if ($direction === 'h') { $size = imagesx($img); $sizeinv = imagesy($img); } else { @@ -195,7 +195,7 @@ class Vizhash16x16 $r = $color1[0] + ((int) $diffs[0] * $i); $g = $color1[1] + ((int) $diffs[1] * $i); $b = $color1[2] + ((int) $diffs[2] * $i); - if ($direction == 'h') { + if ($direction === 'h') { imageline($img, $i, 0, $i, $sizeinv, imagecolorallocate($img, $r, $g, $b)); } else { imageline($img, 0, $i, $sizeinv, $i, imagecolorallocate($img, $r, $g, $b)); diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php index e842065d..eb759ae4 100644 --- a/tpl/bootstrap.php +++ b/tpl/bootstrap.php @@ -249,7 +249,7 @@ endif; foreach ($EXPIRE as $key => $value) : ?> @@ -327,7 +327,7 @@ if ($isCpct) : foreach ($FORMATTER as $key => $value) : ?> @@ -412,7 +412,7 @@ if (!$isCpct) : foreach ($FORMATTER as $key => $value) : ?> @@ -513,16 +513,19 @@ if ($FILEUPLOAD) : -