PageRenderTime 27ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/core/modules/comment/src/CommentStorage.php

https://gitlab.com/geeta7/drupal
PHP | 340 lines | 182 code | 31 blank | 127 comment | 15 complexity | e288bd7a8f7a19ccb2fdc120341721e6 MD5 | raw file
  1. <?php
  2. /**
  3. * @file
  4. * Contains \Drupal\comment\CommentStorage.
  5. */
  6. namespace Drupal\comment;
  7. use Drupal\Core\Cache\CacheBackendInterface;
  8. use Drupal\Core\Database\Connection;
  9. use Drupal\Core\Entity\EntityManagerInterface;
  10. use Drupal\Core\Entity\EntityTypeInterface;
  11. use Drupal\Core\Entity\EntityInterface;
  12. use Drupal\Core\Entity\FieldableEntityInterface;
  13. use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
  14. use Drupal\Core\Session\AccountInterface;
  15. use Drupal\Core\Language\LanguageManagerInterface;
  16. use Symfony\Component\DependencyInjection\ContainerInterface;
  17. /**
  18. * Defines the controller class for comments.
  19. *
  20. * This extends the Drupal\Core\Entity\Sql\SqlContentEntityStorage class,
  21. * adding required special handling for comment entities.
  22. */
  23. class CommentStorage extends SqlContentEntityStorage implements CommentStorageInterface {
  24. /**
  25. * The current user.
  26. *
  27. * @var \Drupal\Core\Session\AccountInterface
  28. */
  29. protected $currentUser;
  30. /**
  31. * Constructs a CommentStorage object.
  32. *
  33. * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
  34. * An array of entity info for the entity type.
  35. * @param \Drupal\Core\Database\Connection $database
  36. * The database connection to be used.
  37. * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
  38. * The entity manager.
  39. * @param \Drupal\Core\Session\AccountInterface $current_user
  40. * The current user.
  41. * @param \Drupal\Core\Cache\CacheBackendInterface $cache
  42. * Cache backend instance to use.
  43. * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
  44. * The language manager.
  45. */
  46. public function __construct(EntityTypeInterface $entity_info, Connection $database, EntityManagerInterface $entity_manager, AccountInterface $current_user, CacheBackendInterface $cache, LanguageManagerInterface $language_manager) {
  47. parent::__construct($entity_info, $database, $entity_manager, $cache, $language_manager);
  48. $this->currentUser = $current_user;
  49. }
  50. /**
  51. * {@inheritdoc}
  52. */
  53. public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info) {
  54. return new static(
  55. $entity_info,
  56. $container->get('database'),
  57. $container->get('entity.manager'),
  58. $container->get('current_user'),
  59. $container->get('cache.entity'),
  60. $container->get('language_manager')
  61. );
  62. }
  63. /**
  64. * {@inheritdoc}
  65. */
  66. public function getMaxThread(CommentInterface $comment) {
  67. $query = $this->database->select('comment_field_data', 'c')
  68. ->condition('entity_id', $comment->getCommentedEntityId())
  69. ->condition('field_name', $comment->getFieldName())
  70. ->condition('entity_type', $comment->getCommentedEntityTypeId())
  71. ->condition('default_langcode', 1);
  72. $query->addExpression('MAX(thread)', 'thread');
  73. return $query->execute()
  74. ->fetchField();
  75. }
  76. /**
  77. * {@inheritdoc}
  78. */
  79. public function getMaxThreadPerThread(CommentInterface $comment) {
  80. $query = $this->database->select('comment_field_data', 'c')
  81. ->condition('entity_id', $comment->getCommentedEntityId())
  82. ->condition('field_name', $comment->getFieldName())
  83. ->condition('entity_type', $comment->getCommentedEntityTypeId())
  84. ->condition('thread', $comment->getParentComment()->getThread() . '.%', 'LIKE')
  85. ->condition('default_langcode', 1);
  86. $query->addExpression('MAX(thread)', 'thread');
  87. return $query->execute()
  88. ->fetchField();
  89. }
  90. /**
  91. * {@inheritdoc}
  92. */
  93. public function getDisplayOrdinal(CommentInterface $comment, $comment_mode, $divisor = 1) {
  94. // Count how many comments (c1) are before $comment (c2) in display order.
  95. // This is the 0-based display ordinal.
  96. $query = $this->database->select('comment_field_data', 'c1');
  97. $query->innerJoin('comment_field_data', 'c2', 'c2.entity_id = c1.entity_id AND c2.entity_type = c1.entity_type AND c2.field_name = c1.field_name');
  98. $query->addExpression('COUNT(*)', 'count');
  99. $query->condition('c2.cid', $comment->id());
  100. if (!$this->currentUser->hasPermission('administer comments')) {
  101. $query->condition('c1.status', CommentInterface::PUBLISHED);
  102. }
  103. if ($comment_mode == CommentManagerInterface::COMMENT_MODE_FLAT) {
  104. // For rendering flat comments, cid is used for ordering comments due to
  105. // unpredictable behavior with timestamp, so we make the same assumption
  106. // here.
  107. $query->condition('c1.cid', $comment->id(), '<');
  108. }
  109. else {
  110. // For threaded comments, the c.thread column is used for ordering. We can
  111. // use the sorting code for comparison, but must remove the trailing
  112. // slash.
  113. $query->where('SUBSTRING(c1.thread, 1, (LENGTH(c1.thread) - 1)) < SUBSTRING(c2.thread, 1, (LENGTH(c2.thread) - 1))');
  114. }
  115. $query->condition('c1.default_langcode', 1);
  116. $query->condition('c2.default_langcode', 1);
  117. $ordinal = $query->execute()->fetchField();
  118. return ($divisor > 1) ? floor($ordinal / $divisor) : $ordinal;
  119. }
  120. /**
  121. * {@inheritdoc}
  122. */
  123. public function getNewCommentPageNumber($total_comments, $new_comments, FieldableEntityInterface $entity, $field_name) {
  124. $field = $entity->getFieldDefinition($field_name);
  125. $comments_per_page = $field->getSetting('per_page');
  126. if ($total_comments <= $comments_per_page) {
  127. // Only one page of comments.
  128. $count = 0;
  129. }
  130. elseif ($field->getSetting('default_mode') == CommentManagerInterface::COMMENT_MODE_FLAT) {
  131. // Flat comments.
  132. $count = $total_comments - $new_comments;
  133. }
  134. else {
  135. // Threaded comments.
  136. // 1. Find all the threads with a new comment.
  137. $unread_threads_query = $this->database->select('comment_field_data', 'comment')
  138. ->fields('comment', array('thread'))
  139. ->condition('entity_id', $entity->id())
  140. ->condition('entity_type', $entity->getEntityTypeId())
  141. ->condition('field_name', $field_name)
  142. ->condition('status', CommentInterface::PUBLISHED)
  143. ->condition('default_langcode', 1)
  144. ->orderBy('created', 'DESC')
  145. ->orderBy('cid', 'DESC')
  146. ->range(0, $new_comments);
  147. // 2. Find the first thread.
  148. $first_thread_query = $this->database->select($unread_threads_query, 'thread');
  149. $first_thread_query->addExpression('SUBSTRING(thread, 1, (LENGTH(thread) - 1))', 'torder');
  150. $first_thread = $first_thread_query
  151. ->fields('thread', array('thread'))
  152. ->orderBy('torder')
  153. ->range(0, 1)
  154. ->execute()
  155. ->fetchField();
  156. // Remove the final '/'.
  157. $first_thread = substr($first_thread, 0, -1);
  158. // Find the number of the first comment of the first unread thread.
  159. $count = $this->database->query('SELECT COUNT(*) FROM {comment_field_data} WHERE entity_id = :entity_id
  160. AND entity_type = :entity_type
  161. AND field_name = :field_name
  162. AND status = :status
  163. AND SUBSTRING(thread, 1, (LENGTH(thread) - 1)) < :thread
  164. AND default_langcode = 1', array(
  165. ':status' => CommentInterface::PUBLISHED,
  166. ':entity_id' => $entity->id(),
  167. ':field_name' => $field_name,
  168. ':entity_type' => $entity->getEntityTypeId(),
  169. ':thread' => $first_thread,
  170. ))->fetchField();
  171. }
  172. return $comments_per_page > 0 ? (int) ($count / $comments_per_page) : 0;
  173. }
  174. /**
  175. * {@inheritdoc}
  176. */
  177. public function getChildCids(array $comments) {
  178. return $this->database->select('comment_field_data', 'c')
  179. ->fields('c', array('cid'))
  180. ->condition('pid', array_keys($comments), 'IN')
  181. ->condition('default_langcode', 1)
  182. ->execute()
  183. ->fetchCol();
  184. }
  185. /**
  186. * {@inheritdoc}
  187. *
  188. * To display threaded comments in the correct order we keep a 'thread' field
  189. * and order by that value. This field keeps this data in
  190. * a way which is easy to update and convenient to use.
  191. *
  192. * A "thread" value starts at "1". If we add a child (A) to this comment,
  193. * we assign it a "thread" = "1.1". A child of (A) will have "1.1.1". Next
  194. * brother of (A) will get "1.2". Next brother of the parent of (A) will get
  195. * "2" and so on.
  196. *
  197. * First of all note that the thread field stores the depth of the comment:
  198. * depth 0 will be "X", depth 1 "X.X", depth 2 "X.X.X", etc.
  199. *
  200. * Now to get the ordering right, consider this example:
  201. *
  202. * 1
  203. * 1.1
  204. * 1.1.1
  205. * 1.2
  206. * 2
  207. *
  208. * If we "ORDER BY thread ASC" we get the above result, and this is the
  209. * natural order sorted by time. However, if we "ORDER BY thread DESC"
  210. * we get:
  211. *
  212. * 2
  213. * 1.2
  214. * 1.1.1
  215. * 1.1
  216. * 1
  217. *
  218. * Clearly, this is not a natural way to see a thread, and users will get
  219. * confused. The natural order to show a thread by time desc would be:
  220. *
  221. * 2
  222. * 1
  223. * 1.2
  224. * 1.1
  225. * 1.1.1
  226. *
  227. * which is what we already did before the standard pager patch. To achieve
  228. * this we simply add a "/" at the end of each "thread" value. This way, the
  229. * thread fields will look like this:
  230. *
  231. * 1/
  232. * 1.1/
  233. * 1.1.1/
  234. * 1.2/
  235. * 2/
  236. *
  237. * we add "/" since this char is, in ASCII, higher than every number, so if
  238. * now we "ORDER BY thread DESC" we get the correct order. However this would
  239. * spoil the reverse ordering, "ORDER BY thread ASC" -- here, we do not need
  240. * to consider the trailing "/" so we use a substring only.
  241. */
  242. public function loadThread(EntityInterface $entity, $field_name, $mode, $comments_per_page = 0, $pager_id = 0) {
  243. $query = $this->database->select('comment_field_data', 'c');
  244. $query->addField('c', 'cid');
  245. $query
  246. ->condition('c.entity_id', $entity->id())
  247. ->condition('c.entity_type', $entity->getEntityTypeId())
  248. ->condition('c.field_name', $field_name)
  249. ->condition('c.default_langcode', 1)
  250. ->addTag('entity_access')
  251. ->addTag('comment_filter')
  252. ->addMetaData('base_table', 'comment')
  253. ->addMetaData('entity', $entity)
  254. ->addMetaData('field_name', $field_name);
  255. if ($comments_per_page) {
  256. $query = $query->extend('Drupal\Core\Database\Query\PagerSelectExtender')
  257. ->limit($comments_per_page);
  258. if ($pager_id) {
  259. $query->element($pager_id);
  260. }
  261. $count_query = $this->database->select('comment_field_data', 'c');
  262. $count_query->addExpression('COUNT(*)');
  263. $count_query
  264. ->condition('c.entity_id', $entity->id())
  265. ->condition('c.entity_type', $entity->getEntityTypeId())
  266. ->condition('c.field_name', $field_name)
  267. ->condition('c.default_langcode', 1)
  268. ->addTag('entity_access')
  269. ->addTag('comment_filter')
  270. ->addMetaData('base_table', 'comment')
  271. ->addMetaData('entity', $entity)
  272. ->addMetaData('field_name', $field_name);
  273. $query->setCountQuery($count_query);
  274. }
  275. if (!$this->currentUser->hasPermission('administer comments')) {
  276. $query->condition('c.status', CommentInterface::PUBLISHED);
  277. if ($comments_per_page) {
  278. $count_query->condition('c.status', CommentInterface::PUBLISHED);
  279. }
  280. }
  281. if ($mode == CommentManagerInterface::COMMENT_MODE_FLAT) {
  282. $query->orderBy('c.cid', 'ASC');
  283. }
  284. else {
  285. // See comment above. Analysis reveals that this doesn't cost too
  286. // much. It scales much much better than having the whole comment
  287. // structure.
  288. $query->addExpression('SUBSTRING(c.thread, 1, (LENGTH(c.thread) - 1))', 'torder');
  289. $query->orderBy('torder', 'ASC');
  290. }
  291. $cids = $query->execute()->fetchCol();
  292. $comments = array();
  293. if ($cids) {
  294. $comments = $this->loadMultiple($cids);
  295. }
  296. return $comments;
  297. }
  298. /**
  299. * {@inheritdoc}
  300. */
  301. public function getUnapprovedCount() {
  302. return $this->database->select('comment_field_data', 'c')
  303. ->condition('status', CommentInterface::NOT_PUBLISHED, '=')
  304. ->condition('default_langcode', 1)
  305. ->countQuery()
  306. ->execute()
  307. ->fetchField();
  308. }
  309. }