administration 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. #!/usr/bin/env php
  2. <?php
  3. /**
  4. * PrivateBin
  5. *
  6. * a zero-knowledge paste bin
  7. *
  8. * @link https://github.com/PrivateBin/PrivateBin
  9. * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  10. * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
  11. * @version 1.6.1
  12. */
  13. namespace PrivateBin;
  14. use PrivateBin\Configuration;
  15. use PrivateBin\Data\AbstractData;
  16. use PrivateBin\Model\Paste;
  17. define('PATH', dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR);
  18. require PATH . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
  19. /**
  20. * Administration
  21. *
  22. * Command line utility for administrative tasks.
  23. */
  24. class Administration
  25. {
  26. /**
  27. * configuration
  28. *
  29. * @access private
  30. * @var Configuration
  31. */
  32. private $_conf;
  33. /**
  34. * options, parsed from the command line arguments
  35. *
  36. * @access private
  37. * @var array
  38. */
  39. private $_opts = array();
  40. /**
  41. * data storage model
  42. *
  43. * @access private
  44. * @var AbstractData
  45. */
  46. private $_store;
  47. /**
  48. * deletes the requested paste ID, if a valid ID and it exists
  49. *
  50. * @access private
  51. * @param string $pasteId
  52. */
  53. private function _delete($pasteId)
  54. {
  55. if (!Paste::isValidId($pasteId)) {
  56. self::_error('given ID is not a valid paste ID (16 hexadecimal digits)', 5);
  57. }
  58. if (!$this->_store->exists($pasteId)) {
  59. self::_error('given ID does not exist, has expired or was already deleted', 6);
  60. }
  61. $this->_store->delete($pasteId);
  62. if ($this->_store->exists($pasteId)) {
  63. self::_error('paste ID exists after deletion, permission problem?', 7);
  64. }
  65. exit("paste $pasteId successfully deleted" . PHP_EOL);
  66. }
  67. /**
  68. * removes empty directories, if current storage model uses Filesystem
  69. *
  70. * @access private
  71. */
  72. private function _empty_dirs()
  73. {
  74. if ($this->_conf->getKey('class', 'model') !== 'Filesystem') {
  75. self::_error('instance not using Filesystem storage, no directories to empty', 4);
  76. }
  77. $dir = $this->_conf->getKey('dir', 'model_options');
  78. passthru("find $dir -type d -empty -delete", $code);
  79. exit($code);
  80. }
  81. /**
  82. * display a message on STDERR and exits
  83. *
  84. * @access private
  85. * @static
  86. * @param string $message
  87. * @param int $code optional, defaults to 1
  88. */
  89. private static function _error($message, $code = 1)
  90. {
  91. self::_error_echo($message);
  92. exit($code);
  93. }
  94. /**
  95. * display a message on STDERR
  96. *
  97. * @access private
  98. * @static
  99. * @param string $message
  100. */
  101. private static function _error_echo($message)
  102. {
  103. fwrite(STDERR, 'Error: ' . $message . PHP_EOL);
  104. }
  105. /**
  106. * display usage help on STDOUT and exits
  107. *
  108. * @access private
  109. * @static
  110. * @param int $code optional, defaults to 0
  111. */
  112. private static function _help($code = 0)
  113. {
  114. echo <<<'EOT'
  115. Usage:
  116. administration [--delete <paste id> | --empty-dirs | --help | --purge | --statistics]
  117. Options:
  118. -d, --delete deletes the requested paste ID
  119. -e, --empty-dirs removes empty directories (only if Filesystem storage is
  120. configured)
  121. -h, --help displays this help message
  122. -p, --purge purge all expired pastes
  123. -s, --statistics reads all stored pastes and comments and reports statistics
  124. EOT, PHP_EOL;
  125. exit($code);
  126. }
  127. /**
  128. * return option for given short or long keyname, if it got set
  129. *
  130. * @access private
  131. * @static
  132. * @param string $short
  133. * @param string $long
  134. * @return string|null
  135. */
  136. private function _option($short, $long)
  137. {
  138. foreach (array($short, $long) as $key) {
  139. if (array_key_exists($key, $this->_opts)) {
  140. return $this->_opts[$key];
  141. }
  142. }
  143. return null;
  144. }
  145. /**
  146. * initialize options from given argument array
  147. *
  148. * @access private
  149. * @static
  150. * @param array $arguments
  151. */
  152. private function _options_initialize($arguments)
  153. {
  154. if ($arguments > 3) {
  155. self::_error_echo('too many arguments given');
  156. echo PHP_EOL;
  157. self::_help(1);
  158. }
  159. if ($arguments < 2) {
  160. self::_error_echo('missing arguments');
  161. echo PHP_EOL;
  162. self::_help(2);
  163. }
  164. $this->_opts = getopt('hd:eps', array('help', 'delete:', 'empty-dirs', 'purge', 'statistics'));
  165. if (!$this->_opts) {
  166. self::_error_echo('unsupported arguments given');
  167. echo PHP_EOL;
  168. self::_help(3);
  169. }
  170. }
  171. /**
  172. * reads all stored pastes and comments and reports statistics
  173. *
  174. * @access public
  175. */
  176. private function _statistics()
  177. {
  178. $counters = array(
  179. 'burn' => 0,
  180. 'discussion' => 0,
  181. 'expired' => 0,
  182. 'md' => 0,
  183. 'percent' => 1,
  184. 'plain' => 0,
  185. 'progress' => 0,
  186. 'syntax' => 0,
  187. 'total' => 0,
  188. 'unknown' => 0,
  189. );
  190. $time = time();
  191. $ids = $this->_store->getAllPastes();
  192. $counters['total'] = count($ids);
  193. $dots = $counters['total'] < 100 ? 10 : (
  194. $counters['total'] < 1000 ? 50 : 100
  195. );
  196. $percentages = $counters['total'] < 100 ? 0 : (
  197. $counters['total'] < 1000 ? 4 : 10
  198. );
  199. echo "Total:\t\t\t{$counters['total']}", PHP_EOL;
  200. foreach ($ids as $pasteid) {
  201. $paste = $this->_store->read($pasteid);
  202. ++$counters['progress'];
  203. if (
  204. array_key_exists('expire_date', $paste['meta']) &&
  205. $paste['meta']['expire_date'] < $time
  206. ) {
  207. ++$counters['expired'];
  208. }
  209. if (array_key_exists('adata', $paste)) {
  210. $format = $paste['adata'][1];
  211. $discussion = $paste['adata'][2];
  212. $burn = $paste['adata'][3];
  213. } else {
  214. $format = array_key_exists('formatter', $paste['meta']) ? $paste['meta']['formatter'] : 'plaintext';
  215. $discussion = array_key_exists('opendiscussion', $paste['meta']) ? $paste['meta']['opendiscussion'] : false;
  216. $burn = array_key_exists('burnafterreading', $paste['meta']) ? $paste['meta']['burnafterreading'] : false;
  217. }
  218. if ($format === 'plaintext') {
  219. ++$counters['plain'];
  220. } elseif ($format === 'syntaxhighlighting') {
  221. ++$counters['syntax'];
  222. } elseif ($format === 'markdown') {
  223. ++$counters['md'];
  224. } else {
  225. ++$counters['unknown'];
  226. }
  227. $counters['discussion'] += (int) $discussion;
  228. $counters['burn'] += (int) $burn;
  229. // display progress
  230. if ($counters['progress'] % $dots === 0) {
  231. echo '.';
  232. if ($percentages) {
  233. $progress = $percentages / $counters['total'] * $counters['progress'];
  234. if ($progress >= $counters['percent']) {
  235. printf(' %d%% ', 100 / $percentages * $progress);
  236. ++$counters['percent'];
  237. }
  238. }
  239. }
  240. }
  241. echo PHP_EOL, <<<EOT
  242. Expired:\t\t{$counters['expired']}
  243. Burn after reading:\t{$counters['burn']}
  244. Discussions:\t\t{$counters['discussion']}
  245. Plain Text:\t\t{$counters['plain']}
  246. Source Code:\t\t{$counters['syntax']}
  247. Markdown:\t\t{$counters['md']}
  248. EOT, PHP_EOL;
  249. if ($counters['unknown'] > 0) {
  250. echo "Unknown format:\t\t{$counters['unknown']}", PHP_EOL;
  251. }
  252. }
  253. /**
  254. * constructor
  255. *
  256. * initializes and runs administrative tasks
  257. *
  258. * @access public
  259. */
  260. public function __construct()
  261. {
  262. $this->_options_initialize($_SERVER['argc']);
  263. if ($this->_option('h', 'help') !== null) {
  264. self::_help();
  265. }
  266. $this->_conf = new Configuration;
  267. if ($this->_option('e', 'empty-dirs') !== null) {
  268. $this->_empty_dirs();
  269. }
  270. $class = 'PrivateBin\\Data\\' . $this->_conf->getKey('class', 'model');
  271. $this->_store = new $class($this->_conf->getSection('model_options'));
  272. if (($pasteId = $this->_option('d', 'delete')) !== null) {
  273. $this->_delete($pasteId);
  274. }
  275. if ($this->_option('p', 'purge') !== null) {
  276. $this->_store->purge(PHP_INT_MAX);
  277. exit('purging of expired pastes concluded' . PHP_EOL);
  278. }
  279. if ($this->_option('s', 'statistics') !== null) {
  280. $this->_statistics();
  281. }
  282. }
  283. }
  284. new Administration();