memcache_ui.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  1. <?php
  2. /**
  3. * Wrapper around php tidy class.
  4. *
  5. * @param string $html
  6. *
  7. * @return void
  8. */
  9. function applyTidy (&$html) {
  10. $config = array(
  11. 'indent' => TRUE,
  12. 'output-xhtml' => TRUE,
  13. 'sort-attributes' => 'alpha',
  14. 'wrap' => 80,
  15. );
  16. $tidy = new tidy();
  17. $tidy->parseString($html, $config, 'utf8');
  18. $tidy->cleanRepair();
  19. $html = (string) $tidy;
  20. }
  21. class Context {
  22. protected $logLevelClasses = NULL;
  23. /**
  24. * Base URL for the script.
  25. *
  26. * @var string
  27. */
  28. protected $base;
  29. /**
  30. * Graphics context
  31. *
  32. * @var GraphicsContext
  33. */
  34. protected $gc = NULL;
  35. /**
  36. * Logging level, as per RFC5424
  37. *
  38. * @link http://php.net/network.constants.php
  39. *
  40. * @var integer
  41. */
  42. protected $logLevel = NULL;
  43. /**
  44. * Messages for display.
  45. *
  46. * @var array
  47. */
  48. protected $messages = array();
  49. /**
  50. * Requested path: <$PHP_SELF>?q=a/b/c
  51. *
  52. * @var string
  53. */
  54. protected $path = NULL;
  55. /**
  56. * Tidy the output ?
  57. *
  58. * @var boolean
  59. */
  60. protected $tidy = NULL;
  61. /**
  62. * User information: logged or not ?
  63. *
  64. * @var boolean
  65. */
  66. protected $user = FALSE;
  67. function __construct() {
  68. $this->getTidy(); // Needed to check extension
  69. }
  70. function __destruct() {
  71. if (!empty($this->messages)) {
  72. $ret = (string) new Element('pre', array('class' => array('messages')),
  73. implode("\n", $this->getMessage(TRUE)));
  74. echo $ret;
  75. }
  76. }
  77. function __toString() {
  78. $ret = '<pre>' . print_r($this, TRUE) . '</pre>';
  79. return $ret;
  80. }
  81. /**
  82. * Get the base path where the script is located.
  83. *
  84. * This is helpful to locate other files using paths relative to the install.
  85. */
  86. public function getBase() {
  87. if (!isset($this->base)) {
  88. $this->base = dirname($_SERVER['SCRIPT_NAME']);
  89. if ($this->base == '/') {
  90. $this->base = '';
  91. }
  92. }
  93. return $this->base;
  94. }
  95. public function getLogLevel() {
  96. if (!isset($this->logLevel)) {
  97. $usLogLevel = NULL;
  98. foreach ($_GET as $key => $value) {
  99. if (strtolower($key) === 'loglevel') {
  100. $usLogLevel = (int) $value;
  101. break;
  102. }
  103. }
  104. if (!isset($usLogLevel)) {
  105. $usLogLevel = LOG_NOTICE;
  106. }
  107. if ($usLogLevel < LOG_EMERG) {
  108. $this->logLevel = LOG_EMERG;
  109. }
  110. elseif ($usLogLevel > LOG_DEBUG) {
  111. $this->logLevel = LOG_DEBUG;
  112. }
  113. else {
  114. $this->logLevel = $usLogLevel; // We now know it to be safe
  115. }
  116. }
  117. return $this->logLevel;
  118. }
  119. public function getLogLevelClass($logLevel) {
  120. if (!isset($this->logLevelClasses)) {
  121. $this->logLevelClasses = array(
  122. LOG_EMERG => 'error',
  123. LOG_ALERT => 'error',
  124. LOG_CRIT => 'error',
  125. LOG_ERR => 'error',
  126. LOG_WARNING => 'warning',
  127. LOG_NOTICE => 'warning',
  128. LOG_INFO => 'status',
  129. LOG_DEBUG => 'status',
  130. );
  131. }
  132. if ($logLevel < LOG_EMERG) {
  133. $logLevel = LOG_EMERG;
  134. }
  135. elseif ($logLevel > LOG_DEBUG) {
  136. $logLevel = LOG_DEBUG;
  137. }
  138. return $this->logLevelClasses[$logLevel];
  139. }
  140. public function getMessage($clear = FALSE) {
  141. $ret = $this->messages;
  142. if ($clear) {
  143. $this->messages = array();
  144. }
  145. return $ret;
  146. }
  147. /**
  148. * Return the requested path.
  149. *
  150. * @param string $path
  151. */
  152. public function getPath() {
  153. if (!isset($this->path)) {
  154. $this->path = empty($_GET['q']) ? '' : $_GET['q'];
  155. }
  156. return $this->path;
  157. }
  158. /**
  159. * Return the "tidy" status.
  160. *
  161. * Will only be TRUE if requested (possibly by default) and extension is
  162. * loaded. A warning will be generated if tidy is requested but extension is
  163. * not loaded.
  164. */
  165. public function getTidy() {
  166. static $notified = FALSE;
  167. if (!isset($this->tidy)) {
  168. $this->tidy = TRUE;
  169. foreach ($_GET as $key => $value) {
  170. if (strtolower($key) === 'tidy') {
  171. $this->tidy = !!$value;
  172. break;
  173. }
  174. }
  175. if (!$notified && $this->tidy && !extension_loaded('tidy')) {
  176. $this->setMessage(strtr('Extension @tidy requested but missing: skipping', array(
  177. '@tidy' => 'tidy',
  178. )), LOG_WARNING);
  179. $notified = TRUE;
  180. $this->tidy = FALSE;
  181. }
  182. }
  183. return $this->tidy;
  184. }
  185. /**
  186. * Add a message to the messages list if it is above the current logging level.
  187. *
  188. * @param string $text
  189. * @param integer $logLevel
  190. *
  191. * @return void
  192. */
  193. public function setMessage($text, $logLevel = LOG_NOTICE) {
  194. if ($logLevel <= $this->getlogLevel()) {
  195. if (is_array($text) || (is_object($text) && !method_exists($text, '__toString'))) {
  196. $this->messages[] = array('<pre>' . print_r($text, TRUE) . '</pre>', $logLevel);
  197. }
  198. else {
  199. $this->messages[] = array((string) $text, $logLevel);
  200. }
  201. }
  202. }
  203. }
  204. /**
  205. * A wrapper for XML elements.
  206. */
  207. class Element {
  208. public $attributes = array();
  209. public $name = NULL;
  210. public $new_line; // Add a new line after element
  211. public $value = NULL;
  212. public function __construct($name, $attr = NULL, $value = NULL) {
  213. $this->name = $name;
  214. $this->attributes = $attr;
  215. $this->value = $value;
  216. }
  217. /**
  218. * @link drupal7/includes/common.inc#check_plain()
  219. */
  220. public static function check_plain($text) {
  221. return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
  222. }
  223. /**
  224. * @link drupal7/includes/common.inc#drupal_attributes()
  225. */
  226. public static function getSerializedAttributes(array $attributes = array()) {
  227. foreach ($attributes as $attribute => &$data) {
  228. $data = implode(' ', (array) $data);
  229. $data = $attribute . '="' . self::check_plain($data) . '"';
  230. }
  231. return $attributes ? ' ' . implode(' ', $attributes) : '';
  232. }
  233. public function __toString() {
  234. $ret = '<'. $this->name;
  235. if (!empty($this->attributes) && is_array($this->attributes)) {
  236. $ret .= self::getSerializedAttributes($this->attributes);
  237. }
  238. if (empty($this->value)) {
  239. $ret .= ' />';
  240. }
  241. else {
  242. $ret .= '>';
  243. if ($this->value instanceof Element) {
  244. $ret .= (string) $this->value; // force __toString()
  245. }
  246. elseif (is_array($this->value)) {
  247. $ret .= implode('', $this->value);
  248. }
  249. else {
  250. $ret .= $this->value;
  251. }
  252. $ret .= "</$this->name>";
  253. }
  254. return $ret;
  255. }
  256. }
  257. class GraphicsContext {
  258. protected $palette = array();
  259. }
  260. class Page extends Element {
  261. /**
  262. * The HTML body element of the page.
  263. *
  264. * @var array
  265. */
  266. protected $body;
  267. /**
  268. * @var Context
  269. */
  270. protected $context;
  271. /**
  272. * Ths HTML head element of the page.
  273. *
  274. * @var array
  275. */
  276. protected $head;
  277. /**
  278. * @var array
  279. */
  280. protected $finalized = array();
  281. /**
  282. * @var array
  283. */
  284. protected $styles;
  285. /**
  286. * Page constructor.
  287. *
  288. * @param Context $context
  289. * @param array $item
  290. * A router info array.
  291. *
  292. * @see Router::getInfo()
  293. */
  294. public function __construct(Context $context, array $item) {
  295. parent::__construct('html');
  296. $this->context = $context;
  297. $this->initializeHead();
  298. $this->initializeBody();
  299. }
  300. public function finalizeBody() {
  301. if (isset($this->finalized['body'])) {
  302. throw new Exception('Attempt to finalize already finalized body');
  303. }
  304. if ($message = $this->context->getMessage(TRUE)) {
  305. $items = array();
  306. foreach ($message as $row) {
  307. $items[] = new Element('li', array(
  308. 'class' => array($this->context->getLogLevelClass($row[1])),
  309. ), $row[0]);
  310. }
  311. $this->setBody(new Element('div', array('class' => array('messages')), $items), 'messages');
  312. }
  313. foreach ($this->getRegions() as $region) {
  314. $this->body[$region] = implode('', $this->body[$region]);
  315. }
  316. $this->finalized['body'] = TRUE;
  317. }
  318. public function finalizeHead() {
  319. if (isset($this->finalized['head'])) {
  320. throw new Exception('Attempt to finalize already finalized head');
  321. }
  322. $cssLink = new Element('link', array(
  323. 'rel' => 'stylesheet',
  324. 'type' => 'text/css',
  325. 'href' => $this->context->getBase() .'/memcache_ui.css',
  326. ));
  327. $this->setHead($cssLink);
  328. $this->finalized['head'] = TRUE;
  329. }
  330. public function getBody() {
  331. $this->finalizeBody();
  332. $body = new Element('body', NULL, $this->body);
  333. $ret = (string) $body;
  334. return $ret;
  335. }
  336. public function getHead() {
  337. $this->finalizeHead();
  338. $head = new Element('head', NULL, $this->head);
  339. $ret = (string) $head;
  340. return $ret;
  341. }
  342. public function getHeader($name) {
  343. }
  344. public function getRegions() {
  345. $ret = array(
  346. 'header',
  347. 'first sidebar',
  348. 'content top',
  349. 'messages',
  350. 'help',
  351. 'content',
  352. 'content bottom',
  353. 'second sidebar',
  354. 'footer',
  355. );
  356. return $ret;
  357. }
  358. public function initializeBody() {
  359. foreach ($this->getRegions() as $region) {
  360. $this->body[$region] = array();
  361. }
  362. }
  363. public function initializeHead() {
  364. $this->setHead(new Element('title', NULL, 'Memcache info'));
  365. }
  366. public function render() {
  367. $html = new Element('html', NULL, $this->getHead() . $this->getBody());
  368. return (string) $html;
  369. }
  370. public function setBody($fragment, $region = 'content') {
  371. if (!in_array($region, $this->getRegions())) {
  372. $this->context->setMessage(strtr('Attempted to insert data in nonexistent region @region', array(
  373. '@region' => self::check_plain($region),
  374. )), LOG_WARNING);
  375. }
  376. $this->body[$region][] = $fragment;
  377. }
  378. public function setHead($item) {
  379. $this->head[] = $item;
  380. }
  381. public function setHeader($name, $value) {
  382. }
  383. }
  384. class Page_Main extends Page {
  385. function finalizeBody() {
  386. $hello = new Element('p', NULL, 'Hello world');
  387. $this->setBody($hello);
  388. parent::finalizeBody();
  389. }
  390. }
  391. class Page_Server_Flush extends Page {
  392. public function __construct(Context $context, $item) {
  393. parent::__construct($context, $item);
  394. $context->setMessage($item, LOG_DEBUG);
  395. }
  396. function finalizeBody() {
  397. $hello = new Element('p', NULL, 'Flush server');
  398. $this->setBody($hello);
  399. parent::finalizeBody();
  400. }
  401. }
  402. class Page_Slab_Overview extends Page {
  403. function finalizeBody() {
  404. $hello = new Element('p', NULL, 'Slabs');
  405. $this->setBody($hello);
  406. parent::finalizeBody();
  407. }
  408. }
  409. class Router {
  410. function __construct(Context $context) {
  411. $this->context = $context;
  412. }
  413. function getInfo() {
  414. $ret = array(
  415. '^server/(\w+)/flush/(\w+)$' => array(
  416. 'page class' => 'page_server_flush',
  417. 'page arguments' => array('$1', '$2'),
  418. 'title callback' => 'title_server',
  419. 'title arguments' => 'page arguments',
  420. ),
  421. '^server/(\w+)/flush$' => array(
  422. 'page class' => 'Page_server_Flush',
  423. 'page arguments' => array('$1'),
  424. 'title callback' => 'title_server',
  425. 'title arguments' => 'page arguments',
  426. ),
  427. '^server/(\w+)/slab/(\d+)$' => array(
  428. 'page class' => 'page_slab_view',
  429. 'page arguments' => array('$1', '$2'),
  430. 'title callback' => 'title_slab',
  431. 'title arguments' => 'page arguments',
  432. ),
  433. '^server/(\w+)/key/(.+)/delete/(\w+)$' => array(
  434. 'page class' => 'page_variable_delete_confirm',
  435. 'page arguments' => array('$1', '$2', '$3'),
  436. 'title callback' => 'title_variable',
  437. 'title arguments' => 'page arguments',
  438. ),
  439. '^server/(\w+)/key/(.+)/delete$' => array(
  440. 'page class' => 'page_variable_delete_form',
  441. 'page arguments' => array('$1', '$2'),
  442. 'title callback' => 'title_variable',
  443. 'title arguments' => 'page arguments',
  444. ),
  445. '^server/(\w+)/key/(.+)/dump$' => array(
  446. 'page class' => 'page_variable_view_php',
  447. 'page arguments' => array('$1', '$2'),
  448. 'title callback' => 'title_variable',
  449. 'title arguments' => 'page arguments',
  450. ),
  451. '^server/(\w+)/key/(.+)/php$' => array(
  452. 'page class' => 'page_variable_view_php',
  453. 'page arguments' => array('$1', '$2'),
  454. 'title callback' => 'title_variable',
  455. 'title arguments' => 'page arguments',
  456. ),
  457. '^server/(\w+)/key/(.+)$' => array(
  458. 'page class' => 'page_variable_view_text',
  459. 'page arguments' => array('$1', '$2'),
  460. 'title callback' => 'title_variable',
  461. 'title arguments' => 'page arguments',
  462. ),
  463. '^slabs$' => array(
  464. 'page class' => 'Page_Slab_Overview',
  465. 'title' => 'Slabs per server',
  466. ),
  467. '' => array( // Catch-all regex
  468. 'page class' => 'Page_Main',
  469. 'title' => 'Overview',
  470. ),
  471. );
  472. return $ret;
  473. }
  474. function getRoute() {
  475. $found = FALSE;
  476. $path = $this->context->getPath();
  477. $matches = array();
  478. $infoDefaults = array(
  479. 'page arguments' => array(),
  480. );
  481. foreach ($this->getInfo() as $regex => $info) {
  482. $regex = "@$regex@";
  483. $count = preg_match($regex, $path, $matches);
  484. if ($count) {
  485. $found = TRUE;
  486. break;
  487. }
  488. }
  489. if ($found) {
  490. $this->context->setMessage("Menu hit on $regex", LOG_DEBUG);
  491. $info = array_merge($infoDefaults, $info);
  492. $this->context->setMessage("Info: ". print_r($info, TRUE), LOG_DEBUG);
  493. if (!empty($info['page arguments'])) {
  494. $regexes = array_fill(0, count($info['page arguments']), $regex);
  495. $paths = array_fill(0, count($info['page arguments']), $path);
  496. $info['page arguments'] = preg_replace($regexes, $info['page arguments'], $paths);
  497. }
  498. if ($info['title arguments'] == 'page arguments') {
  499. $info['title arguments'] = &$info['page arguments'];
  500. }
  501. $this->context->setMessage("Info resolved: ". print_r($info, TRUE), LOG_DEBUG);
  502. }
  503. else {
  504. $info = NULL;
  505. }
  506. return $info;
  507. }
  508. }
  509. function main() {
  510. try {
  511. ob_start();
  512. $context = new Context();
  513. $context->setMessage("Dirname: [". $context->getBase() . "]", LOG_DEBUG);
  514. $context->setMessage("Path: [". $context->getPath() . "]", LOG_DEBUG);
  515. $router = new Router($context);
  516. $item = $router->getRoute();
  517. $page = new $item['page class']($context, $item);
  518. // echo '<pre>'; var_dump($page);
  519. echo $page->render();
  520. $html = ob_get_clean();
  521. if ($context->getTidy()) {
  522. applyTidy($html);
  523. }
  524. echo $html;
  525. }
  526. catch (Exception $e) {
  527. echo '<pre>';
  528. echo $e->getMessage() . PHP_EOL;
  529. echo $e->getTraceAsString();
  530. echo "</pre>";
  531. }
  532. }
  533. main();