memcache_ui.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  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' => 200,
  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("\n", $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. abstract class Page extends Element {
  261. /**
  262. * The HTML body element of the page.
  263. *
  264. * @var array
  265. */
  266. protected $body;
  267. /**
  268. * The MIME content type with charset
  269. *
  270. * @var string
  271. */
  272. protected $contentType = 'text/html; charset=utf8';
  273. /**
  274. * @var Context
  275. */
  276. protected $context;
  277. /**
  278. * The HTML head element of the page.
  279. *
  280. * @var array
  281. */
  282. protected $head;
  283. /**
  284. * The array of HTTP headers
  285. *
  286. * @var array
  287. */
  288. protected $headers = array();
  289. /**
  290. * @var array
  291. */
  292. protected $finalized = array();
  293. /**
  294. * @var array
  295. */
  296. protected $styles;
  297. /**
  298. * Page constructor.
  299. *
  300. * @param Context $context
  301. * @param array $item
  302. * A router info array.
  303. *
  304. * @see Router::getInfo()
  305. */
  306. public function __construct(Context $context, array $item) {
  307. parent::__construct('html');
  308. $context->setMessage($item, LOG_DEBUG);
  309. $this->context = $context;
  310. $this->initializeHead();
  311. $this->initializeBody();
  312. // Will fail if not reimplemented as a concrete method in a child class
  313. $this->build();
  314. }
  315. public function __toString() {
  316. $this->finalizeHeader();
  317. $html = new Element('html', NULL, $this->getHead() . $this->getBody());
  318. return (string) $html;
  319. }
  320. public abstract function build();
  321. public function emitHeaders() {
  322. $this->finalizeHeader();
  323. foreach ($this->headers as $name => $value) {
  324. header("$name: $value");
  325. }
  326. }
  327. public function finalizeBody() {
  328. if (isset($this->finalized['body'])) {
  329. throw new Exception('Attempt to finalize already finalized body');
  330. }
  331. if ($message = $this->context->getMessage(TRUE)) {
  332. $items = array();
  333. foreach ($message as $row) {
  334. $items[] = new Element('li', array(
  335. 'class' => array($this->context->getLogLevelClass($row[1])),
  336. ), $row[0]);
  337. }
  338. $this->setBody(new Element('div', array('class' => array('messages')), $items), 'messages');
  339. }
  340. foreach ($this->getRegions() as $region) {
  341. $this->body[$region] = implode('', $this->body[$region]);
  342. }
  343. $this->finalized['body'] = TRUE;
  344. }
  345. public function finalizeHead() {
  346. if (isset($this->finalized['head'])) {
  347. throw new Exception('Attempt to finalize already finalized head');
  348. }
  349. $cssLink = new Element('link', array(
  350. 'rel' => 'stylesheet',
  351. 'type' => 'text/css',
  352. 'href' => $this->context->getBase() .'/memcache_ui.css',
  353. ));
  354. $this->setHead($cssLink);
  355. $this->finalized['head'] = TRUE;
  356. }
  357. public function finalizeHeader() {
  358. $contentType = $this->getHeader('content-type');
  359. if (empty($contentType)) {
  360. $this->setHeader('content-type', $this->contentType);
  361. }
  362. }
  363. public function getBody() {
  364. $this->finalizeBody();
  365. $body = new Element('body', NULL, $this->body);
  366. $ret = (string) $body;
  367. return $ret;
  368. }
  369. public function getHead() {
  370. $this->finalizeHead();
  371. $head = new Element('head', NULL, $this->head);
  372. $ret = (string) $head;
  373. return $ret;
  374. }
  375. public function getHeader($name) {
  376. }
  377. public function getRegions() {
  378. $ret = array(
  379. 'header',
  380. 'first sidebar',
  381. 'content top',
  382. 'messages',
  383. 'help',
  384. 'content',
  385. 'content bottom',
  386. 'second sidebar',
  387. 'footer',
  388. );
  389. return $ret;
  390. }
  391. public function initializeBody() {
  392. foreach ($this->getRegions() as $region) {
  393. $this->body[$region] = array();
  394. }
  395. }
  396. public function initializeHead() {
  397. $this->setHead(new Element('title', NULL, 'Memcache info'));
  398. }
  399. public function setBody($fragment, $region = 'content') {
  400. if (!in_array($region, $this->getRegions())) {
  401. $this->context->setMessage(strtr('Attempted to insert data in nonexistent region @region', array(
  402. '@region' => self::check_plain($region),
  403. )), LOG_WARNING);
  404. }
  405. $this->body[$region][] = $fragment;
  406. }
  407. public function setHead($item) {
  408. $this->head[] = $item;
  409. }
  410. public function setHeader($name, $value) {
  411. $this->headers[$name] = $value;
  412. }
  413. }
  414. /**
  415. * Provide common layout elements to child classes.
  416. *
  417. * - header
  418. */
  419. abstract class Page_Memcache_Base extends Page {
  420. function build() {
  421. $this->setBody(
  422. new Element('div', array('class' => array('head')),
  423. new Element('h1', array('class' => array('memcache')), array(
  424. new Element('span', array('class' => array('logo')),
  425. new Element('a', array('href' => 'http://pecl.php.net/package/memcache'), 'memcache')
  426. ),
  427. new Element('span', array('class' => array('nameinfo')), 'memcache_ui.php from an idea by <a href="http://livebookmark.net">Harun Yayli</a>'),
  428. ))),
  429. 'header'
  430. );
  431. }
  432. }
  433. class Page_Memcache_Main extends Page_Memcache_Base {
  434. function build() {
  435. parent::build();
  436. $this->setBody(new Element('p', NULL, 'Hello world'));
  437. }
  438. }
  439. class Page_Memcache_Server_Flush extends Page_Memcache_Base {
  440. function build() {
  441. parent::build();
  442. $this->setBody(new Element('p', NULL, 'Flush server'));
  443. }
  444. }
  445. class Page_Memcache_Slab_Overview extends Page_Memcache_Base {
  446. function build() {
  447. parent::build();
  448. $this->setBody(new Element('p', NULL, 'Slabs'));
  449. }
  450. }
  451. class Router {
  452. function __construct(Context $context) {
  453. $this->context = $context;
  454. }
  455. function getInfo() {
  456. $ret = array(
  457. '^server/(\w+)/flush/(\w+)$' => array(
  458. 'page class' => 'page_server_flush',
  459. 'page arguments' => array('$1', '$2'),
  460. 'title callback' => 'title_server',
  461. 'title arguments' => 'page arguments',
  462. ),
  463. '^server/(\w+)/flush$' => array(
  464. 'page class' => 'Page_Memcache_Server_Flush',
  465. 'page arguments' => array('$1'),
  466. 'title callback' => 'title_server',
  467. 'title arguments' => 'page arguments',
  468. ),
  469. '^server/(\w+)/slab/(\d+)$' => array(
  470. 'page class' => 'page_slab_view',
  471. 'page arguments' => array('$1', '$2'),
  472. 'title callback' => 'title_slab',
  473. 'title arguments' => 'page arguments',
  474. ),
  475. '^server/(\w+)/key/(.+)/delete/(\w+)$' => array(
  476. 'page class' => 'page_variable_delete_confirm',
  477. 'page arguments' => array('$1', '$2', '$3'),
  478. 'title callback' => 'title_variable',
  479. 'title arguments' => 'page arguments',
  480. ),
  481. '^server/(\w+)/key/(.+)/delete$' => array(
  482. 'page class' => 'page_variable_delete_form',
  483. 'page arguments' => array('$1', '$2'),
  484. 'title callback' => 'title_variable',
  485. 'title arguments' => 'page arguments',
  486. ),
  487. '^server/(\w+)/key/(.+)/dump$' => array(
  488. 'page class' => 'page_variable_view_php',
  489. 'page arguments' => array('$1', '$2'),
  490. 'title callback' => 'title_variable',
  491. 'title arguments' => 'page arguments',
  492. ),
  493. '^server/(\w+)/key/(.+)/php$' => array(
  494. 'page class' => 'page_variable_view_php',
  495. 'page arguments' => array('$1', '$2'),
  496. 'title callback' => 'title_variable',
  497. 'title arguments' => 'page arguments',
  498. ),
  499. '^server/(\w+)/key/(.+)$' => array(
  500. 'page class' => 'page_variable_view_text',
  501. 'page arguments' => array('$1', '$2'),
  502. 'title callback' => 'title_variable',
  503. 'title arguments' => 'page arguments',
  504. ),
  505. '^slabs$' => array(
  506. 'page class' => 'Page_Memcache_Slab_Overview',
  507. 'title' => 'Slabs per server',
  508. ),
  509. '' => array( // Catch-all regex
  510. 'page class' => 'Page_Memcache_Main',
  511. 'title' => 'Overview',
  512. ),
  513. );
  514. return $ret;
  515. }
  516. function getRoute() {
  517. $found = FALSE;
  518. $path = $this->context->getPath();
  519. $matches = array();
  520. $infoDefaults = array(
  521. 'page arguments' => array(),
  522. );
  523. foreach ($this->getInfo() as $regex => $info) {
  524. $regex = "@$regex@";
  525. $count = preg_match($regex, $path, $matches);
  526. if ($count) {
  527. $found = TRUE;
  528. break;
  529. }
  530. }
  531. if ($found) {
  532. $this->context->setMessage("Menu hit on $regex", LOG_DEBUG);
  533. $info = array_merge($infoDefaults, $info);
  534. $this->context->setMessage("Info: ". print_r($info, TRUE), LOG_DEBUG);
  535. if (!empty($info['page arguments'])) {
  536. $regexes = array_fill(0, count($info['page arguments']), $regex);
  537. $paths = array_fill(0, count($info['page arguments']), $path);
  538. $info['page arguments'] = preg_replace($regexes, $info['page arguments'], $paths);
  539. }
  540. if ($info['title arguments'] == 'page arguments') {
  541. $info['title arguments'] = &$info['page arguments'];
  542. }
  543. $this->context->setMessage("Info resolved: ". print_r($info, TRUE), LOG_DEBUG);
  544. }
  545. else {
  546. $info = NULL;
  547. }
  548. return $info;
  549. }
  550. }
  551. function main() {
  552. try {
  553. ob_start();
  554. $context = new Context();
  555. $context->setMessage("Dirname: [". $context->getBase() . "]", LOG_DEBUG);
  556. $context->setMessage("Path: [". $context->getPath() . "]", LOG_DEBUG);
  557. $router = new Router($context);
  558. $item = $router->getRoute();
  559. $page = new $item['page class']($context, $item);
  560. $page->emitHeaders();
  561. echo $page;
  562. $html = ob_get_clean();
  563. if ($context->getTidy()) {
  564. applyTidy($html);
  565. }
  566. echo $html;
  567. }
  568. catch (Exception $e) {
  569. echo '<pre>';
  570. echo $e->getMessage() . PHP_EOL;
  571. echo $e->getTraceAsString();
  572. echo "</pre>";
  573. }
  574. }
  575. main();