mirror of
https://github.com/PrivateBin/PrivateBin.git
synced 2024-03-22 13:10:41 +08:00
refactor administrative script into OOP style and to our code guidelines
This commit is contained in:
parent
c00b95011b
commit
5c43ab6ef8
|
@ -1,12 +1,12 @@
|
||||||
# PrivateBin version history
|
# PrivateBin version history
|
||||||
|
|
||||||
* **1.5.1 (not yet released)**
|
* **1.5.1 (not yet released)**
|
||||||
|
* ADDED: script for administrative tasks: deleting pastes (#274), removing empty directories (#277), purging expired pastes (#276) & statistics (#319)
|
||||||
* FIXED: Revert Filesystem purge to limited and randomized lookup (#1030)
|
* FIXED: Revert Filesystem purge to limited and randomized lookup (#1030)
|
||||||
* FIXED: Catch JSON decode errors when invalid data gets sent to the API (#1030)
|
* FIXED: Catch JSON decode errors when invalid data gets sent to the API (#1030)
|
||||||
* FIXED: Support sorting v1 format in mixed version comments in Filesystem backend (#1030)
|
* FIXED: Support sorting v1 format in mixed version comments in Filesystem backend (#1030)
|
||||||
* **1.5 (2022-12-11)**
|
* **1.5 (2022-12-11)**
|
||||||
* ADDED: script for data storage backend migrations (#1012)
|
* ADDED: script for data storage backend migrations (#1012)
|
||||||
* ADDED: script for administrative tasks: deleting pastes (#274), removing empty directories (#277), purging expired pastes (#276) & statistics (#319)
|
|
||||||
* ADDED: Translations for Turkish, Slovak, Greek and Thai
|
* ADDED: Translations for Turkish, Slovak, Greek and Thai
|
||||||
* ADDED: S3 Storage backend (#994)
|
* ADDED: S3 Storage backend (#994)
|
||||||
* ADDED: Jdenticons as an option for comment icons (#793)
|
* ADDED: Jdenticons as an option for comment icons (#793)
|
||||||
|
|
317
bin/administration
Executable file
317
bin/administration
Executable file
|
@ -0,0 +1,317 @@
|
||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PrivateBin
|
||||||
|
*
|
||||||
|
* a zero-knowledge paste bin
|
||||||
|
*
|
||||||
|
* @link https://github.com/PrivateBin/PrivateBin
|
||||||
|
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
|
||||||
|
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
|
||||||
|
* @version 1.5.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace PrivateBin;
|
||||||
|
|
||||||
|
use PrivateBin\Configuration;
|
||||||
|
use PrivateBin\Data\AbstractData;
|
||||||
|
use PrivateBin\Model\Paste;
|
||||||
|
|
||||||
|
define('PATH', dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR);
|
||||||
|
require PATH . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Administration
|
||||||
|
*
|
||||||
|
* Command line utility for administrative tasks.
|
||||||
|
*/
|
||||||
|
class Administration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* configuration
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
* @var Configuration
|
||||||
|
*/
|
||||||
|
private $_conf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* options, parsed from the command line arguments
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $_opts = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* data storage model
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
* @var AbstractData
|
||||||
|
*/
|
||||||
|
private $_store;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* deletes the requested paste ID, if a valid ID and it exists
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
* @param string $pasteId
|
||||||
|
*/
|
||||||
|
private function _delete($pasteId)
|
||||||
|
{
|
||||||
|
if (!Paste::isValidId($pasteId)) {
|
||||||
|
self::_error('given ID is not a valid paste ID (16 hexadecimal digits)', 5);
|
||||||
|
}
|
||||||
|
if (!$this->_store->exists($pasteId)) {
|
||||||
|
self::_error('given ID does not exist, has expired or was already deleted', 6);
|
||||||
|
}
|
||||||
|
$this->_store->delete($pasteId);
|
||||||
|
if ($this->_store->exists($pasteId)) {
|
||||||
|
self::_error('paste ID exists after deletion, permission problem?', 7);
|
||||||
|
}
|
||||||
|
exit("paste $pasteId successfully deleted" . PHP_EOL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* removes empty directories, if current storage model uses Filesystem
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
private function _empty_dirs()
|
||||||
|
{
|
||||||
|
if ($this->_conf->getKey('class', 'model') !== 'Filesystem') {
|
||||||
|
self::_error('instance not using Filesystem storage, no directories to empty', 4);
|
||||||
|
}
|
||||||
|
$dir = $this->_conf->getKey('dir', 'model_options');
|
||||||
|
passthru("find $dir -type d -empty -delete", $code);
|
||||||
|
exit($code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* display a message on STDERR and exits
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
* @static
|
||||||
|
* @param string $message
|
||||||
|
* @param int $code optional, defaults to 1
|
||||||
|
*/
|
||||||
|
private static function _error($message, $code = 1)
|
||||||
|
{
|
||||||
|
self::_error_echo($message);
|
||||||
|
exit($code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* display a message on STDERR
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
* @static
|
||||||
|
* @param string $message
|
||||||
|
*/
|
||||||
|
private static function _error_echo($message)
|
||||||
|
{
|
||||||
|
fwrite(STDERR, 'Error: ' . $message . PHP_EOL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* display usage help on STDOUT and exits
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
* @static
|
||||||
|
* @param int $code optional, defaults to 0
|
||||||
|
*/
|
||||||
|
private static function _help($code = 0)
|
||||||
|
{
|
||||||
|
echo <<<'EOT'
|
||||||
|
Usage:
|
||||||
|
administration [--delete <paste id> | --empty-dirs | --help | --statistics]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-d, --delete deletes the requested paste ID
|
||||||
|
-e, --empty-dirs removes empty directories (only if Filesystem storage is
|
||||||
|
configured)
|
||||||
|
-h, --help displays this help message
|
||||||
|
-s, --statistics reads all stored pastes and comments and reports statistics
|
||||||
|
EOT, PHP_EOL;
|
||||||
|
exit($code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return option for given short or long keyname, if it got set
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
* @static
|
||||||
|
* @param string $short
|
||||||
|
* @param string $long
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
private function _option($short, $long)
|
||||||
|
{
|
||||||
|
foreach (array($short, $long) as $key) {
|
||||||
|
if (array_key_exists($key, $this->_opts)) {
|
||||||
|
return $this->_opts[$key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* initialize options from given argument array
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
* @static
|
||||||
|
* @param array $arguments
|
||||||
|
*/
|
||||||
|
private function _options_initialize($arguments)
|
||||||
|
{
|
||||||
|
if ($arguments > 3) {
|
||||||
|
self::_error_echo('too many arguments given');
|
||||||
|
echo PHP_EOL;
|
||||||
|
self::_help(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($arguments < 2) {
|
||||||
|
self::_error_echo('missing arguments');
|
||||||
|
echo PHP_EOL;
|
||||||
|
self::_help(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_opts = getopt('hd:eps', array('help', 'delete:', 'empty-dirs', 'purge', 'statistics'));
|
||||||
|
if (!$this->_opts) {
|
||||||
|
self::_error_echo('unsupported arguments given');
|
||||||
|
echo PHP_EOL;
|
||||||
|
self::_help(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* reads all stored pastes and comments and reports statistics
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
*/
|
||||||
|
private function _statistics()
|
||||||
|
{
|
||||||
|
$counters = array(
|
||||||
|
'burn' => 0,
|
||||||
|
'discussion' => 0,
|
||||||
|
'expired' => 0,
|
||||||
|
'md' => 0,
|
||||||
|
'percent' => 1,
|
||||||
|
'plain' => 0,
|
||||||
|
'progress' => 0,
|
||||||
|
'syntax' => 0,
|
||||||
|
'total' => 0,
|
||||||
|
'unknown' => 0,
|
||||||
|
);
|
||||||
|
$time = time();
|
||||||
|
$ids = $this->_store->getAllPastes();
|
||||||
|
$counters['total'] = count($ids);
|
||||||
|
$dots = $counters['total'] < 100 ? 10 : (
|
||||||
|
$counters['total'] < 1000 ? 50 : 100
|
||||||
|
);
|
||||||
|
$percentages = $counters['total'] < 100 ? 0 : (
|
||||||
|
$counters['total'] < 1000 ? 4 : 10
|
||||||
|
);
|
||||||
|
|
||||||
|
echo "Total:\t\t\t${counters['total']}", PHP_EOL;
|
||||||
|
foreach ($ids as $pasteid) {
|
||||||
|
$paste = $this->_store->read($pasteid);
|
||||||
|
++$counters['progress'];
|
||||||
|
|
||||||
|
if (
|
||||||
|
array_key_exists('expire_date', $paste['meta']) &&
|
||||||
|
$paste['meta']['expire_date'] < $time
|
||||||
|
) {
|
||||||
|
++$counters['expired'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (array_key_exists('adata', $paste)) {
|
||||||
|
$format = $paste['adata'][1];
|
||||||
|
$discussion = $paste['adata'][2];
|
||||||
|
$burn = $paste['adata'][3];
|
||||||
|
} else {
|
||||||
|
$format = array_key_exists('formatter', $paste['meta']) ? $paste['meta']['formatter'] : 'plaintext';
|
||||||
|
$discussion = array_key_exists('opendiscussion', $paste['meta']) ? $paste['meta']['opendiscussion'] : false;
|
||||||
|
$burn = array_key_exists('burnafterreading', $paste['meta']) ? $paste['meta']['burnafterreading'] : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($format === 'plaintext') {
|
||||||
|
++$counters['plain'];
|
||||||
|
} elseif ($format === 'syntaxhighlighting') {
|
||||||
|
++$counters['syntax'];
|
||||||
|
} elseif ($format === 'markdown') {
|
||||||
|
++$counters['md'];
|
||||||
|
} else {
|
||||||
|
++$counters['unknown'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$counters['discussion'] += (int) $discussion;
|
||||||
|
$counters['burn'] += (int) $burn;
|
||||||
|
|
||||||
|
// display progress
|
||||||
|
if ($counters['progress'] % $dots === 0) {
|
||||||
|
echo '.';
|
||||||
|
if ($percentages) {
|
||||||
|
$progress = $percentages / $counters['total'] * $counters['progress'];
|
||||||
|
if ($progress >= $counters['percent']) {
|
||||||
|
printf(' %d%% ', 100 / $percentages * $progress);
|
||||||
|
++$counters['percent'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo PHP_EOL, <<<EOT
|
||||||
|
Expired:\t\t${counters['expired']}
|
||||||
|
Burn after reading:\t${counters['burn']}
|
||||||
|
Discussions:\t\t${counters['discussion']}
|
||||||
|
Plain Text:\t\t${counters['plain']}
|
||||||
|
Source Code:\t\t${counters['syntax']}
|
||||||
|
Markdown:\t\t${counters['md']}
|
||||||
|
EOT, PHP_EOL;
|
||||||
|
if ($counters['unknown'] > 0) {
|
||||||
|
echo "Unknown format:\t\t${counters['unknown']}", PHP_EOL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* constructor
|
||||||
|
*
|
||||||
|
* initializes and runs administrative tasks
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->_options_initialize($_SERVER['argc']);
|
||||||
|
|
||||||
|
if ($this->_option('h', 'help') !== null) {
|
||||||
|
self::_help();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_conf = new Configuration;
|
||||||
|
|
||||||
|
if ($this->_option('e', 'empty-dirs') !== null) {
|
||||||
|
$this->_empty_dirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
$class = 'PrivateBin\\Data\\' . $this->_conf->getKey('class', 'model');
|
||||||
|
$this->_store = new $class($this->_conf->getSection('model_options'));
|
||||||
|
|
||||||
|
if (($pasteId = $this->_option('d', 'delete')) !== null) {
|
||||||
|
$this->_delete($pasteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->_option('p', 'purge') !== null) {
|
||||||
|
$this->_store->purge(PHP_INT_MAX);
|
||||||
|
exit('purging of expired pastes concluded' . PHP_EOL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->_option('s', 'statistics') !== null) {
|
||||||
|
$this->_statistics();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new Administration();
|
|
@ -1,184 +0,0 @@
|
||||||
#!/usr/bin/env php
|
|
||||||
<?php
|
|
||||||
|
|
||||||
define('PATH', dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR);
|
|
||||||
require PATH . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
|
|
||||||
|
|
||||||
use PrivateBin\Configuration;
|
|
||||||
use PrivateBin\Model\Paste;
|
|
||||||
|
|
||||||
$options = array();
|
|
||||||
|
|
||||||
function error($message, $code = 1) {
|
|
||||||
error_echo($message);
|
|
||||||
exit($code);
|
|
||||||
}
|
|
||||||
|
|
||||||
function error_echo($message) {
|
|
||||||
fwrite(STDERR, 'Error: ' . $message . PHP_EOL);
|
|
||||||
}
|
|
||||||
|
|
||||||
function help($code = 0) {
|
|
||||||
echo <<<'EOT'
|
|
||||||
Usage:
|
|
||||||
privatebin-admin [--delete <paste id> | --empty-dirs | --help | --statistics]
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-d, --delete deletes the requested paste ID
|
|
||||||
-e, --empty-dirs removes empty directories (only if Filesystem storage is
|
|
||||||
configured)
|
|
||||||
-h, --help displays this help message
|
|
||||||
-s, --statistics reads all stored pastes and comments and reports statistics
|
|
||||||
EOT, PHP_EOL;
|
|
||||||
exit($code);
|
|
||||||
}
|
|
||||||
|
|
||||||
function option($short, $long) {
|
|
||||||
global $options;
|
|
||||||
$option = null;
|
|
||||||
foreach (array($short, $long) as $key) {
|
|
||||||
if (array_key_exists($key, $options)) {
|
|
||||||
$option = $options[$key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $option;
|
|
||||||
}
|
|
||||||
|
|
||||||
function main() {
|
|
||||||
if ($_SERVER['argc'] > 3) {
|
|
||||||
error_echo('too many arguments given');
|
|
||||||
fwrite(STDERR, PHP_EOL);
|
|
||||||
help(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($_SERVER['argc'] < 2) {
|
|
||||||
error_echo('missing arguments');
|
|
||||||
fwrite(STDERR, PHP_EOL);
|
|
||||||
help(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
global $options;
|
|
||||||
$options = getopt('hd:eps', array('help', 'delete:', 'empty-dirs', 'purge', 'statistics'));
|
|
||||||
if (!$options) {
|
|
||||||
error_echo('unsupported arguments given');
|
|
||||||
fwrite(STDERR, PHP_EOL);
|
|
||||||
help(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (option('h', 'help') !== null) {
|
|
||||||
help();
|
|
||||||
}
|
|
||||||
|
|
||||||
$conf = new Configuration;
|
|
||||||
|
|
||||||
if (option('e', 'empty-dirs') !== null) {
|
|
||||||
if ($conf->getKey('class', 'model') !== 'Filesystem') {
|
|
||||||
error('instance not using Filesystem storage, no directories to empty', 4);
|
|
||||||
}
|
|
||||||
$dir = $conf->getKey('dir', 'model_options');
|
|
||||||
passthru("find $dir -type d -empty -delete", $code);
|
|
||||||
exit($code);
|
|
||||||
}
|
|
||||||
|
|
||||||
$class = 'PrivateBin\\Data\\' . $conf->getKey('class', 'model');
|
|
||||||
$store = new $class($conf->getSection('model_options'));
|
|
||||||
|
|
||||||
if (($pasteid = option('d', 'delete')) !== null) {
|
|
||||||
if (!Paste::isValidId($pasteid)) {
|
|
||||||
error('given ID is not a valid paste ID (16 hexadecimal digits)', 5);
|
|
||||||
}
|
|
||||||
if (!$store->exists($pasteid)) {
|
|
||||||
error('given ID does not exist, has expired or was already deleted', 6);
|
|
||||||
}
|
|
||||||
$store->delete($pasteid);
|
|
||||||
if ($store->exists($pasteid)) {
|
|
||||||
error('paste ID exists after deletion, permission problem?', 7);
|
|
||||||
}
|
|
||||||
exit("paste $pasteid successfully deleted" . PHP_EOL);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (option('p', 'purge') !== null) {
|
|
||||||
$store->purge(PHP_INT_MAX);
|
|
||||||
exit('purging of expired pastes concluded' . PHP_EOL);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (option('s', 'statistics') !== null) {
|
|
||||||
$counters = array(
|
|
||||||
'burn' => 0,
|
|
||||||
'discussion' => 0,
|
|
||||||
'expired' => 0,
|
|
||||||
'md' => 0,
|
|
||||||
'percent' => 1,
|
|
||||||
'plain' => 0,
|
|
||||||
'progress' => 0,
|
|
||||||
'syntax' => 0,
|
|
||||||
'total' => 0,
|
|
||||||
'unknown' => 0,
|
|
||||||
);
|
|
||||||
$time = time();
|
|
||||||
$ids = $store->getAllPastes();
|
|
||||||
$counters['total'] = count($ids);
|
|
||||||
$dots = $counters['total'] < 100 ? 10 : (
|
|
||||||
$counters['total'] < 1000 ? 50 : 100
|
|
||||||
);
|
|
||||||
$percentages = $counters['total'] < 100 ? 0 : (
|
|
||||||
$counters['total'] < 1000 ? 4 : 10
|
|
||||||
);
|
|
||||||
echo "Total:\t\t\t${counters['total']}", PHP_EOL;
|
|
||||||
foreach ($ids as $pasteid) {
|
|
||||||
$paste = $store->read($pasteid);
|
|
||||||
++$counters['progress'];
|
|
||||||
if (
|
|
||||||
array_key_exists('expire_date', $paste['meta']) &&
|
|
||||||
$paste['meta']['expire_date'] < $time
|
|
||||||
) {
|
|
||||||
++$counters['expired'];
|
|
||||||
}
|
|
||||||
if (array_key_exists('adata', $paste)) {
|
|
||||||
$format = $paste['adata'][1];
|
|
||||||
$discussion = $paste['adata'][2];
|
|
||||||
$burn = $paste['adata'][3];
|
|
||||||
} else {
|
|
||||||
$format = array_key_exists('formatter', $paste['meta']) ? $paste['meta']['formatter'] : 'plaintext';
|
|
||||||
$discussion = array_key_exists('opendiscussion', $paste['meta']) ? $paste['meta']['opendiscussion'] : false;
|
|
||||||
$burn = array_key_exists('burnafterreading', $paste['meta']) ? $paste['meta']['burnafterreading'] : false;
|
|
||||||
}
|
|
||||||
if ($format === 'plaintext') {
|
|
||||||
++$counters['plain'];
|
|
||||||
} elseif ($format === 'syntaxhighlighting') {
|
|
||||||
++$counters['syntax'];
|
|
||||||
} elseif ($format === 'markdown') {
|
|
||||||
++$counters['md'];
|
|
||||||
} else {
|
|
||||||
++$counters['unknown'];
|
|
||||||
}
|
|
||||||
$counters['discussion'] += (int) $discussion;
|
|
||||||
$counters['burn'] += (int) $burn;
|
|
||||||
|
|
||||||
// display progress
|
|
||||||
if ($counters['progress'] % $dots === 0) {
|
|
||||||
echo '.';
|
|
||||||
if ($percentages) {
|
|
||||||
$progress = $percentages / $counters['total'] * $counters['progress'];
|
|
||||||
if ($progress >= $counters['percent']) {
|
|
||||||
printf(' %d%% ', 100 / $percentages * $progress);
|
|
||||||
++$counters['percent'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
echo PHP_EOL, <<<EOT
|
|
||||||
Expired:\t\t${counters['expired']}
|
|
||||||
Burn after reading:\t${counters['burn']}
|
|
||||||
Discussions:\t\t${counters['discussion']}
|
|
||||||
Plain Text:\t\t${counters['plain']}
|
|
||||||
Source Code:\t\t${counters['syntax']}
|
|
||||||
Markdown:\t\t${counters['md']}
|
|
||||||
EOT, PHP_EOL;
|
|
||||||
if ($counters['unknown'] > 0) {
|
|
||||||
echo "Unknown format:\t\t${counters['unknown']}", PHP_EOL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
|
Loading…
Reference in New Issue
Block a user