Message.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801
  1. <?php
  2. declare(strict_types=1);
  3. namespace PhpMyAdmin;
  4. use const ENT_COMPAT;
  5. use function array_unshift;
  6. use function count;
  7. use function htmlspecialchars;
  8. use function is_array;
  9. use function is_float;
  10. use function is_int;
  11. use function md5;
  12. use function sprintf;
  13. use function strlen;
  14. /**
  15. * a single message
  16. *
  17. * simple usage examples:
  18. * <code>
  19. * // display simple error message 'Error'
  20. * echo Message::error()->getDisplay();
  21. *
  22. * // get simple success message 'Success'
  23. * $message = Message::success();
  24. *
  25. * // get special notice
  26. * $message = Message::notice(__('This is a localized notice'));
  27. * </code>
  28. *
  29. * more advanced usage example:
  30. * <code>
  31. * // create another message, a hint, with a localized string which expects
  32. * $hint = Message::notice('Read the %smanual%s');
  33. * // replace placeholders with the following params
  34. * $hint->addParam('[doc@cfg_Example]');
  35. * $hint->addParam('[/doc]');
  36. * // add this hint as a tooltip
  37. * $hint = showHint($hint);
  38. *
  39. * // add the retrieved tooltip reference to the original message
  40. * $message->addMessage($hint);
  41. * </code>
  42. */
  43. class Message
  44. {
  45. public const SUCCESS = 1; // 0001
  46. public const NOTICE = 2; // 0010
  47. public const ERROR = 8; // 1000
  48. public const SANITIZE_NONE = 0; // 0000 0000
  49. public const SANITIZE_STRING = 16; // 0001 0000
  50. public const SANITIZE_PARAMS = 32; // 0010 0000
  51. public const SANITIZE_BOOTH = 48; // 0011 0000
  52. /**
  53. * message levels
  54. *
  55. * @var array
  56. */
  57. public static $level = [
  58. self::SUCCESS => 'success',
  59. self::NOTICE => 'notice',
  60. self::ERROR => 'error',
  61. ];
  62. /**
  63. * The message number
  64. *
  65. * @access protected
  66. * @var int
  67. */
  68. protected $number = self::NOTICE;
  69. /**
  70. * The locale string identifier
  71. *
  72. * @access protected
  73. * @var string
  74. */
  75. protected $string = '';
  76. /**
  77. * The formatted message
  78. *
  79. * @access protected
  80. * @var string
  81. */
  82. protected $message = '';
  83. /**
  84. * Whether the message was already displayed
  85. *
  86. * @access protected
  87. * @var bool
  88. */
  89. protected $isDisplayed = false;
  90. /**
  91. * Whether to use BB code when displaying.
  92. *
  93. * @access protected
  94. * @var bool
  95. */
  96. protected $useBBCode = true;
  97. /**
  98. * Unique id
  99. *
  100. * @access protected
  101. * @var string
  102. */
  103. protected $hash = null;
  104. /**
  105. * holds parameters
  106. *
  107. * @access protected
  108. * @var array
  109. */
  110. protected $params = [];
  111. /**
  112. * holds additional messages
  113. *
  114. * @access protected
  115. * @var array
  116. */
  117. protected $addedMessages = [];
  118. /**
  119. * @param string $string The message to be displayed
  120. * @param int $number A numeric representation of the type of message
  121. * @param array $params An array of parameters to use in the message
  122. * @param int $sanitize A flag to indicate what to sanitize, see
  123. * constant definitions above
  124. */
  125. public function __construct(
  126. string $string = '',
  127. int $number = self::NOTICE,
  128. array $params = [],
  129. int $sanitize = self::SANITIZE_NONE
  130. ) {
  131. $this->setString($string, $sanitize & self::SANITIZE_STRING);
  132. $this->setNumber($number);
  133. $this->setParams($params, $sanitize & self::SANITIZE_PARAMS);
  134. }
  135. /**
  136. * magic method: return string representation for this object
  137. */
  138. public function __toString(): string
  139. {
  140. return $this->getMessage();
  141. }
  142. /**
  143. * get Message of type success
  144. *
  145. * shorthand for getting a simple success message
  146. *
  147. * @param string $string A localized string
  148. * e.g. __('Your SQL query has been
  149. * executed successfully')
  150. *
  151. * @return Message
  152. *
  153. * @static
  154. */
  155. public static function success(string $string = ''): self
  156. {
  157. if (empty($string)) {
  158. $string = __('Your SQL query has been executed successfully.');
  159. }
  160. return new Message($string, self::SUCCESS);
  161. }
  162. /**
  163. * get Message of type error
  164. *
  165. * shorthand for getting a simple error message
  166. *
  167. * @param string $string A localized string e.g. __('Error')
  168. *
  169. * @return Message
  170. *
  171. * @static
  172. */
  173. public static function error(string $string = ''): self
  174. {
  175. if (empty($string)) {
  176. $string = __('Error');
  177. }
  178. return new Message($string, self::ERROR);
  179. }
  180. /**
  181. * get Message of type notice
  182. *
  183. * shorthand for getting a simple notice message
  184. *
  185. * @param string $string A localized string
  186. * e.g. __('The additional features for working with
  187. * linked tables have been deactivated. To find out
  188. * why click %shere%s.')
  189. *
  190. * @return Message
  191. *
  192. * @static
  193. */
  194. public static function notice(string $string): self
  195. {
  196. return new Message($string, self::NOTICE);
  197. }
  198. /**
  199. * get Message with customized content
  200. *
  201. * shorthand for getting a customized message
  202. *
  203. * @param string $message A localized string
  204. * @param int $type A numeric representation of the type of message
  205. *
  206. * @return Message
  207. *
  208. * @static
  209. */
  210. public static function raw(string $message, int $type = self::NOTICE): self
  211. {
  212. $r = new Message('', $type);
  213. $r->setMessage($message);
  214. $r->setBBCode(false);
  215. return $r;
  216. }
  217. /**
  218. * get Message for number of affected rows
  219. *
  220. * shorthand for getting a customized message
  221. *
  222. * @param int $rows Number of rows
  223. *
  224. * @return Message
  225. *
  226. * @static
  227. */
  228. public static function getMessageForAffectedRows(int $rows): self
  229. {
  230. $message = self::success(
  231. _ngettext('%1$d row affected.', '%1$d rows affected.', $rows)
  232. );
  233. $message->addParam($rows);
  234. return $message;
  235. }
  236. /**
  237. * get Message for number of deleted rows
  238. *
  239. * shorthand for getting a customized message
  240. *
  241. * @param int $rows Number of rows
  242. *
  243. * @return Message
  244. *
  245. * @static
  246. */
  247. public static function getMessageForDeletedRows(int $rows): self
  248. {
  249. $message = self::success(
  250. _ngettext('%1$d row deleted.', '%1$d rows deleted.', $rows)
  251. );
  252. $message->addParam($rows);
  253. return $message;
  254. }
  255. /**
  256. * get Message for number of inserted rows
  257. *
  258. * shorthand for getting a customized message
  259. *
  260. * @param int $rows Number of rows
  261. *
  262. * @return Message
  263. *
  264. * @static
  265. */
  266. public static function getMessageForInsertedRows(int $rows): self
  267. {
  268. $message = self::success(
  269. _ngettext('%1$d row inserted.', '%1$d rows inserted.', $rows)
  270. );
  271. $message->addParam($rows);
  272. return $message;
  273. }
  274. /**
  275. * get Message of type error with custom content
  276. *
  277. * shorthand for getting a customized error message
  278. *
  279. * @param string $message A localized string
  280. *
  281. * @return Message
  282. *
  283. * @static
  284. */
  285. public static function rawError(string $message): self
  286. {
  287. return self::raw($message, self::ERROR);
  288. }
  289. /**
  290. * get Message of type notice with custom content
  291. *
  292. * shorthand for getting a customized notice message
  293. *
  294. * @param string $message A localized string
  295. *
  296. * @return Message
  297. *
  298. * @static
  299. */
  300. public static function rawNotice(string $message): self
  301. {
  302. return self::raw($message, self::NOTICE);
  303. }
  304. /**
  305. * get Message of type success with custom content
  306. *
  307. * shorthand for getting a customized success message
  308. *
  309. * @param string $message A localized string
  310. *
  311. * @return Message
  312. *
  313. * @static
  314. */
  315. public static function rawSuccess(string $message): self
  316. {
  317. return self::raw($message, self::SUCCESS);
  318. }
  319. /**
  320. * returns whether this message is a success message or not
  321. * and optionally makes this message a success message
  322. *
  323. * @param bool $set Whether to make this message of SUCCESS type
  324. *
  325. * @return bool whether this is a success message or not
  326. */
  327. public function isSuccess(bool $set = false): bool
  328. {
  329. if ($set) {
  330. $this->setNumber(self::SUCCESS);
  331. }
  332. return $this->getNumber() === self::SUCCESS;
  333. }
  334. /**
  335. * returns whether this message is a notice message or not
  336. * and optionally makes this message a notice message
  337. *
  338. * @param bool $set Whether to make this message of NOTICE type
  339. *
  340. * @return bool whether this is a notice message or not
  341. */
  342. public function isNotice(bool $set = false): bool
  343. {
  344. if ($set) {
  345. $this->setNumber(self::NOTICE);
  346. }
  347. return $this->getNumber() === self::NOTICE;
  348. }
  349. /**
  350. * returns whether this message is an error message or not
  351. * and optionally makes this message an error message
  352. *
  353. * @param bool $set Whether to make this message of ERROR type
  354. *
  355. * @return bool Whether this is an error message or not
  356. */
  357. public function isError(bool $set = false): bool
  358. {
  359. if ($set) {
  360. $this->setNumber(self::ERROR);
  361. }
  362. return $this->getNumber() === self::ERROR;
  363. }
  364. /**
  365. * Set whether we should use BB Code when rendering.
  366. *
  367. * @param bool $useBBCode Use BB Code?
  368. */
  369. public function setBBCode(bool $useBBCode): void
  370. {
  371. $this->useBBCode = $useBBCode;
  372. }
  373. /**
  374. * set raw message (overrides string)
  375. *
  376. * @param string $message A localized string
  377. * @param bool $sanitize Whether to sanitize $message or not
  378. */
  379. public function setMessage(string $message, bool $sanitize = false): void
  380. {
  381. if ($sanitize) {
  382. $message = self::sanitize($message);
  383. }
  384. $this->message = $message;
  385. }
  386. /**
  387. * set string (does not take effect if raw message is set)
  388. *
  389. * @param string $string string to set
  390. * @param bool|int $sanitize whether to sanitize $string or not
  391. */
  392. public function setString(string $string, $sanitize = true): void
  393. {
  394. if ($sanitize) {
  395. $string = self::sanitize($string);
  396. }
  397. $this->string = $string;
  398. }
  399. /**
  400. * set message type number
  401. *
  402. * @param int $number message type number to set
  403. */
  404. public function setNumber(int $number): void
  405. {
  406. $this->number = $number;
  407. }
  408. /**
  409. * add string or Message parameter
  410. *
  411. * usage
  412. * <code>
  413. * $message->addParam('[em]some string[/em]');
  414. * </code>
  415. *
  416. * @param mixed $param parameter to add
  417. */
  418. public function addParam($param): void
  419. {
  420. if ($param instanceof self || is_float($param) || is_int($param)) {
  421. $this->params[] = $param;
  422. } else {
  423. $this->params[] = htmlspecialchars((string) $param, ENT_COMPAT);
  424. }
  425. }
  426. /**
  427. * add parameter as raw HTML, usually in conjunction with strings
  428. *
  429. * usage
  430. * <code>
  431. * $message->addParamHtml('<img src="img">');
  432. * </code>
  433. *
  434. * @param string $param parameter to add
  435. */
  436. public function addParamHtml(string $param): void
  437. {
  438. $this->params[] = self::notice($param);
  439. }
  440. /**
  441. * add a bunch of messages at once
  442. *
  443. * @param Message[] $messages to be added
  444. * @param string $separator to use between this and previous string/message
  445. */
  446. public function addMessages(array $messages, string $separator = ' '): void
  447. {
  448. foreach ($messages as $message) {
  449. $this->addMessage($message, $separator);
  450. }
  451. }
  452. /**
  453. * add a bunch of messages at once
  454. *
  455. * @param string[] $messages to be added
  456. * @param string $separator to use between this and previous string/message
  457. */
  458. public function addMessagesString(array $messages, string $separator = ' '): void
  459. {
  460. foreach ($messages as $message) {
  461. $this->addText($message, $separator);
  462. }
  463. }
  464. /**
  465. * Real implementation of adding message
  466. *
  467. * @param Message $message to be added
  468. * @param string $separator to use between this and previous string/message
  469. */
  470. private function addMessageToList(self $message, string $separator): void
  471. {
  472. if (! empty($separator)) {
  473. $this->addedMessages[] = $separator;
  474. }
  475. $this->addedMessages[] = $message;
  476. }
  477. /**
  478. * add another raw message to be concatenated on displaying
  479. *
  480. * @param self $message to be added
  481. * @param string $separator to use between this and previous string/message
  482. */
  483. public function addMessage(self $message, string $separator = ' '): void
  484. {
  485. $this->addMessageToList($message, $separator);
  486. }
  487. /**
  488. * add another raw message to be concatenated on displaying
  489. *
  490. * @param string $message to be added
  491. * @param string $separator to use between this and previous string/message
  492. */
  493. public function addText(string $message, string $separator = ' '): void
  494. {
  495. $this->addMessageToList(self::notice(htmlspecialchars($message)), $separator);
  496. }
  497. /**
  498. * add another html message to be concatenated on displaying
  499. *
  500. * @param string $message to be added
  501. * @param string $separator to use between this and previous string/message
  502. */
  503. public function addHtml(string $message, string $separator = ' '): void
  504. {
  505. $this->addMessageToList(self::rawNotice($message), $separator);
  506. }
  507. /**
  508. * set all params at once, usually used in conjunction with string
  509. *
  510. * @param array $params parameters to set
  511. * @param bool|int $sanitize whether to sanitize params
  512. */
  513. public function setParams(array $params, $sanitize = false): void
  514. {
  515. if ($sanitize) {
  516. $params = self::sanitize($params);
  517. }
  518. $this->params = $params;
  519. }
  520. /**
  521. * return all parameters
  522. *
  523. * @return array
  524. */
  525. public function getParams(): array
  526. {
  527. return $this->params;
  528. }
  529. /**
  530. * return all added messages
  531. *
  532. * @return array
  533. */
  534. public function getAddedMessages(): array
  535. {
  536. return $this->addedMessages;
  537. }
  538. /**
  539. * Sanitizes $message
  540. *
  541. * @param mixed $message the message(s)
  542. *
  543. * @return mixed the sanitized message(s)
  544. *
  545. * @access public
  546. * @static
  547. */
  548. public static function sanitize($message)
  549. {
  550. if (is_array($message)) {
  551. foreach ($message as $key => $val) {
  552. $message[$key] = self::sanitize($val);
  553. }
  554. return $message;
  555. }
  556. return htmlspecialchars((string) $message);
  557. }
  558. /**
  559. * decode $message, taking into account our special codes
  560. * for formatting
  561. *
  562. * @param string $message the message
  563. *
  564. * @return string the decoded message
  565. *
  566. * @access public
  567. * @static
  568. */
  569. public static function decodeBB(string $message): string
  570. {
  571. return Sanitize::sanitizeMessage($message, false, true);
  572. }
  573. /**
  574. * wrapper for sprintf()
  575. *
  576. * @param mixed[] ...$params Params
  577. *
  578. * @return string formatted
  579. */
  580. public static function format(...$params): string
  581. {
  582. if (isset($params[1]) && is_array($params[1])) {
  583. array_unshift($params[1], $params[0]);
  584. $params = $params[1];
  585. }
  586. return sprintf(...$params);
  587. }
  588. /**
  589. * returns unique Message::$hash, if not exists it will be created
  590. *
  591. * @return string Message::$hash
  592. */
  593. public function getHash(): string
  594. {
  595. if ($this->hash === null) {
  596. $this->hash = md5(
  597. $this->getNumber() .
  598. $this->string .
  599. $this->message
  600. );
  601. }
  602. return $this->hash;
  603. }
  604. /**
  605. * returns compiled message
  606. *
  607. * @return string complete message
  608. */
  609. public function getMessage(): string
  610. {
  611. $message = $this->message;
  612. if (strlen($message) === 0) {
  613. $string = $this->getString();
  614. if (strlen($string) === 0) {
  615. $message = '';
  616. } else {
  617. $message = $string;
  618. }
  619. }
  620. if ($this->isDisplayed()) {
  621. $message = $this->getMessageWithIcon($message);
  622. }
  623. if (count($this->getParams()) > 0) {
  624. $message = self::format($message, $this->getParams());
  625. }
  626. if ($this->useBBCode) {
  627. $message = self::decodeBB($message);
  628. }
  629. foreach ($this->getAddedMessages() as $add_message) {
  630. $message .= $add_message;
  631. }
  632. return $message;
  633. }
  634. /**
  635. * Returns only message string without image & other HTML.
  636. */
  637. public function getOnlyMessage(): string
  638. {
  639. return $this->message;
  640. }
  641. /**
  642. * returns Message::$string
  643. *
  644. * @return string Message::$string
  645. */
  646. public function getString(): string
  647. {
  648. return $this->string;
  649. }
  650. /**
  651. * returns Message::$number
  652. *
  653. * @return int Message::$number
  654. */
  655. public function getNumber(): int
  656. {
  657. return $this->number;
  658. }
  659. /**
  660. * returns level of message
  661. *
  662. * @return string level of message
  663. */
  664. public function getLevel(): string
  665. {
  666. return self::$level[$this->getNumber()];
  667. }
  668. /**
  669. * returns HTML code for displaying this message
  670. *
  671. * @return string whole message box
  672. */
  673. public function getDisplay(): string
  674. {
  675. $this->isDisplayed(true);
  676. $context = 'primary';
  677. $level = $this->getLevel();
  678. if ($level === 'error') {
  679. $context = 'danger';
  680. } elseif ($level === 'success') {
  681. $context = 'success';
  682. }
  683. $template = new Template();
  684. return $template->render('message', [
  685. 'context' => $context,
  686. 'message' => $this->getMessage(),
  687. ]);
  688. }
  689. /**
  690. * sets and returns whether the message was displayed or not
  691. *
  692. * @param bool $isDisplayed whether to set displayed flag
  693. *
  694. * @return bool Message::$isDisplayed
  695. */
  696. public function isDisplayed(bool $isDisplayed = false): bool
  697. {
  698. if ($isDisplayed) {
  699. $this->isDisplayed = true;
  700. }
  701. return $this->isDisplayed;
  702. }
  703. /**
  704. * Returns the message with corresponding image icon
  705. *
  706. * @param string $message the message(s)
  707. *
  708. * @return string message with icon
  709. */
  710. public function getMessageWithIcon(string $message): string
  711. {
  712. if ($this->getLevel() === 'error') {
  713. $image = 's_error';
  714. } elseif ($this->getLevel() === 'success') {
  715. $image = 's_success';
  716. } else {
  717. $image = 's_notice';
  718. }
  719. $message = self::notice(Html\Generator::getImage($image)) . ' ' . $message;
  720. return $message;
  721. }
  722. }