Comment.php 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. <?php declare(strict_types=1);
  2. /**
  3. * PrivateBin
  4. *
  5. * a zero-knowledge paste bin
  6. *
  7. * @link https://github.com/PrivateBin/PrivateBin
  8. * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  9. * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
  10. */
  11. namespace PrivateBin\Model;
  12. use Identicon\Identicon;
  13. use Jdenticon\Identicon as Jdenticon;
  14. use PrivateBin\Exception\TranslatedException;
  15. use PrivateBin\Persistence\TrafficLimiter;
  16. use PrivateBin\Vizhash16x16;
  17. /**
  18. * Comment
  19. *
  20. * Model of a PrivateBin comment.
  21. */
  22. class Comment extends AbstractModel
  23. {
  24. /**
  25. * Instance's parent.
  26. *
  27. * @access private
  28. * @var Paste
  29. */
  30. private $_paste;
  31. /**
  32. * Store the comment's data.
  33. *
  34. * @access public
  35. * @throws TranslatedException
  36. */
  37. public function store()
  38. {
  39. // Make sure paste exists.
  40. $pasteid = $this->getPaste()->getId();
  41. if (!$this->getPaste()->exists()) {
  42. throw new TranslatedException(self::INVALID_DATA_ERROR, 67);
  43. }
  44. // Make sure the discussion is opened in this paste and allowed in the configuration.
  45. if (!$this->getPaste()->isOpendiscussion() || !$this->_conf->getKey('discussion')) {
  46. throw new TranslatedException(self::INVALID_DATA_ERROR, 68);
  47. }
  48. // Check for improbable collision.
  49. if ($this->exists()) {
  50. throw new TranslatedException(self::COLLISION_ERROR, 69);
  51. }
  52. // Comment IDs must be unique within the paste, regardless of the
  53. // parent, and the parent must be either the paste or an existing comment.
  54. $commentid = $this->getId();
  55. $parentid = $this->getParentId();
  56. $parentExists = $parentid === $pasteid;
  57. if ($commentid === $pasteid) {
  58. throw new TranslatedException(self::COLLISION_ERROR, 72);
  59. }
  60. foreach ($this->_store->readComments($pasteid) as $comment) {
  61. if ($comment['id'] === $commentid) {
  62. throw new TranslatedException(self::COLLISION_ERROR, 69);
  63. }
  64. if ($comment['id'] === $parentid) {
  65. $parentExists = true;
  66. }
  67. }
  68. if (!$parentExists) {
  69. throw new TranslatedException(self::INVALID_DATA_ERROR, 71);
  70. }
  71. $this->_data['meta']['created'] = time();
  72. // store comment
  73. if (
  74. $this->_store->createComment(
  75. $pasteid,
  76. $this->getParentId(),
  77. $this->getId(),
  78. $this->_data
  79. ) === false
  80. ) {
  81. throw new TranslatedException('Error saving comment. Sorry.', 70);
  82. }
  83. }
  84. /**
  85. * Test if comment exists in store.
  86. *
  87. * @access public
  88. * @return bool
  89. */
  90. public function exists()
  91. {
  92. return $this->_store->existsComment(
  93. $this->getPaste()->getId(),
  94. $this->getParentId(),
  95. $this->getId()
  96. );
  97. }
  98. /**
  99. * Set paste.
  100. *
  101. * @access public
  102. * @param Paste $paste
  103. */
  104. public function setPaste(Paste &$paste)
  105. {
  106. $this->_paste = $paste;
  107. $this->_data['pasteid'] = $paste->getId();
  108. }
  109. /**
  110. * Get paste.
  111. *
  112. * @access public
  113. * @return Paste
  114. */
  115. public function getPaste()
  116. {
  117. return $this->_paste;
  118. }
  119. /**
  120. * Set parent ID.
  121. *
  122. * @access public
  123. * @param string $id
  124. * @throws TranslatedException
  125. */
  126. public function setParentId($id)
  127. {
  128. if (!self::isValidId($id)) {
  129. throw new TranslatedException('Invalid document ID.', 65);
  130. }
  131. $this->_data['parentid'] = $id;
  132. }
  133. /**
  134. * Get parent ID.
  135. *
  136. * @access public
  137. * @return string
  138. */
  139. public function getParentId()
  140. {
  141. if (!array_key_exists('parentid', $this->_data)) {
  142. $this->_data['parentid'] = $this->getPaste()->getId();
  143. }
  144. return $this->_data['parentid'];
  145. }
  146. /**
  147. * Validate data.
  148. *
  149. * @access protected
  150. * @param array $data
  151. * @throws TranslatedException
  152. */
  153. protected function _validate(array &$data)
  154. {
  155. // reject comments without authenticated IDs, if required
  156. if ($this->_conf->getKey('discussionrequireids') && count($data['adata']) !== 10) {
  157. throw new TranslatedException(self::INVALID_DATA_ERROR, 77);
  158. }
  159. }
  160. /**
  161. * Sanitizes data to conform with current configuration.
  162. *
  163. * @access protected
  164. * @param array $data
  165. */
  166. protected function _sanitize(array &$data)
  167. {
  168. // we generate an icon based on a SHA512 HMAC of the users IP, if configured
  169. $icon = $this->_conf->getKey('icon');
  170. if ($icon !== 'none') {
  171. $pngdata = '';
  172. $hmac = TrafficLimiter::getHash();
  173. if ($icon === 'identicon') {
  174. $identicon = new Identicon();
  175. $pngdata = $identicon->getImageDataUri($hmac, 16);
  176. } elseif ($icon === 'jdenticon') {
  177. $jdenticon = new Jdenticon([
  178. 'hash' => $hmac,
  179. 'size' => 16,
  180. 'style' => [
  181. 'backgroundColor' => '#fff0', // fully transparent, for dark mode
  182. 'padding' => 0,
  183. ],
  184. ]);
  185. $pngdata = $jdenticon->getImageDataUri('png');
  186. } elseif ($icon === 'vizhash') {
  187. $vh = new Vizhash16x16();
  188. $pngdata = 'data:image/png;base64,' . base64_encode(
  189. $vh->generate($hmac)
  190. );
  191. }
  192. if (!empty($pngdata)) {
  193. if (!array_key_exists('meta', $data)) {
  194. $data['meta'] = [];
  195. }
  196. $data['meta']['icon'] = $pngdata;
  197. }
  198. }
  199. }
  200. }