Commit 3dfe432c authored by webchick's avatar webchick

Issue #1685170 by chx, bdragon: Fixed Possibility of {comment}.thread duplication in the same node.

parent 1a3136c1
......@@ -1917,15 +1917,6 @@ function comment_alphadecimal_to_int($c = '00') {
return base_convert(substr($c, 1), 36, 10);
}
/**
* Increments a sorting code to the next value.
*
* @see comment_int_to_alphadecimal()
*/
function comment_increment_alphadecimal($c = '00') {
return comment_int_to_alphadecimal(comment_alphadecimal_to_int($c) + 1);
}
/**
* Implements hook_action_info().
*/
......
......@@ -9,6 +9,7 @@
use Drupal\entity\EntityInterface;
use Drupal\entity\DatabaseStorageController;
use LogicException;
/**
* Defines the controller class for comments.
......@@ -17,6 +18,11 @@
* required special handling for comment entities.
*/
class CommentStorageController extends DatabaseStorageController {
/**
* The thread for which a lock was acquired.
*/
protected $threadLock = '';
/**
* Overrides Drupal\entity\DatabaseStorageController::buildQuery().
......@@ -50,7 +56,7 @@ protected function attachLoad(&$comments, $revision_id = FALSE) {
* Overrides Drupal\entity\DatabaseStorageController::preSave().
*
* @see comment_int_to_alphadecimal()
* @see comment_increment_alphadecimal()
* @see comment_alphadecimal_to_int()
*/
protected function preSave(EntityInterface $comment) {
global $user;
......@@ -70,46 +76,60 @@ protected function preSave(EntityInterface $comment) {
// Allow calling code to set thread itself.
$thread = $comment->thread;
}
elseif ($comment->pid == 0) {
// This is a comment with no parent comment (depth 0): we start
// by retrieving the maximum thread level.
$max = db_query('SELECT MAX(thread) FROM {comment} WHERE nid = :nid', array(':nid' => $comment->nid))->fetchField();
// Strip the "/" from the end of the thread.
$max = rtrim($max, '/');
// We need to get the value at the correct depth.
$parts = explode('.', $max);
$firstsegment = $parts[0];
// Finally, build the thread field for this new comment.
$thread = comment_increment_alphadecimal($firstsegment) .'/';
}
else {
// This is a comment with a parent comment, so increase the part of
// the thread value at the proper depth.
// Get the parent comment:
$parent = comment_load($comment->pid);
// Strip the "/" from the end of the parent thread.
$parent->thread = (string) rtrim((string) $parent->thread, '/');
// Get the max value in *this* thread.
$max = db_query("SELECT MAX(thread) FROM {comment} WHERE thread LIKE :thread AND nid = :nid", array(
':thread' => $parent->thread . '.%',
':nid' => $comment->nid,
))->fetchField();
if ($max == '') {
// First child of this parent.
$thread = $parent->thread . '.' . comment_int_to_alphadecimal(0) . '/';
if ($this->threadLock) {
// As preSave() is protected, this can only happen when this class
// is extended in a faulty manner.
throw new LogicException('preSave is called again without calling postSave() or releaseThreadLock()');
}
else {
// Strip the "/" at the end of the thread.
if ($comment->pid == 0) {
// This is a comment with no parent comment (depth 0): we start
// by retrieving the maximum thread level.
$max = db_query('SELECT MAX(thread) FROM {comment} WHERE nid = :nid', array(':nid' => $comment->nid))->fetchField();
// Strip the "/" from the end of the thread.
$max = rtrim($max, '/');
// Get the value at the correct depth.
// We need to get the value at the correct depth.
$parts = explode('.', $max);
$parent_depth = count(explode('.', $parent->thread));
$last = $parts[$parent_depth];
// Finally, build the thread field for this new comment.
$thread = $parent->thread . '.' . comment_increment_alphadecimal($last) . '/';
$n = comment_alphadecimal_to_int($parts[0]);
$prefix = '';
}
else {
// This is a comment with a parent comment, so increase the part of
// the thread value at the proper depth.
// Get the parent comment:
$parent = comment_load($comment->pid);
// Strip the "/" from the end of the parent thread.
$parent->thread = (string) rtrim((string) $parent->thread, '/');
$prefix = $parent->thread . '.';
// Get the max value in *this* thread.
$max = db_query("SELECT MAX(thread) FROM {comment} WHERE thread LIKE :thread AND nid = :nid", array(
':thread' => $parent->thread . '.%',
':nid' => $comment->nid,
))->fetchField();
if ($max == '') {
// First child of this parent. As the other two cases do an
// increment of the thread number before creating the thread
// string set this to -1 so it requires an increment too.
$n = -1;
}
else {
// Strip the "/" at the end of the thread.
$max = rtrim($max, '/');
// Get the value at the correct depth.
$parts = explode('.', $max);
$parent_depth = count(explode('.', $parent->thread));
$n = comment_alphadecimal_to_int($parts[$parent_depth]);
}
}
// Finally, build the thread field for this new comment. To avoid
// race conditions, get a lock on the thread. If aother process already
// has the lock, just move to the next integer.
do {
$thread = $prefix . comment_int_to_alphadecimal(++$n) . '/';
} while (!lock_acquire("comment:$comment->nid:$thread"));
$this->threadLock = $thread;
}
if (empty($comment->created)) {
$comment->created = REQUEST_TIME;
......@@ -132,6 +152,7 @@ protected function preSave(EntityInterface $comment) {
* Overrides Drupal\entity\DatabaseStorageController::postSave().
*/
protected function postSave(EntityInterface $comment, $update) {
$this->releaseThreadLock();
// Update the {node_comment_statistics} table prior to executing the hook.
$this->updateNodeStatistics($comment->nid);
if ($comment->status == COMMENT_PUBLISHED) {
......@@ -215,4 +236,14 @@ protected function updateNodeStatistics($nid) {
->execute();
}
}
/**
* Release the lock acquired for the thread in preSave().
*/
protected function releaseThreadLock() {
if ($this->threadLock) {
lock_release($this->threadLock);
$this->threadLock = '';
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment