Skip to content

Commit

Permalink
Rewritten old comment system to a new approach.
Browse files Browse the repository at this point in the history
  • Loading branch information
parpalak committed Sep 23, 2024
1 parent 30e1c9a commit d23e0fe
Show file tree
Hide file tree
Showing 31 changed files with 1,745 additions and 187 deletions.
15 changes: 13 additions & 2 deletions _extensions/s2_blog/BlogUrlBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@

class BlogUrlBuilder
{
protected ?string $blogPath = null;
protected ?string $blogTagsPath = null;
private ?string $blogPath = null;
private ?string $absBlogPath = null;
private ?string $blogTagsPath = null;

public function __construct(
private readonly UrlBuilder $urlBuilder,
Expand All @@ -29,6 +30,11 @@ public function main(): string
return $this->blogPath ?? $this->blogPath = $this->urlBuilder->link($this->encodedBlogUrl() . '/');
}

public function absMain(): string
{
return $this->absBlogPath ?? $this->absBlogPath = $this->urlBuilder->absLink($this->encodedBlogUrl() . '/');
}

public function favorite(): string
{
return $this->main() . rawurlencode($this->favoriteUrl) . '/';
Expand Down Expand Up @@ -74,6 +80,11 @@ public function postFromTimestamp(int $createTime, string $url): string
return $this->main() . date('Y/m/d/', $createTime) . rawurlencode($url);
}

public function absPostFromTimestamp(int $createTime, string $url): string
{
return $this->absMain() . date('Y/m/d/', $createTime) . rawurlencode($url);
}

public function postFromTimestampWithoutPrefix(int $createTime, string $url): string
{
return $this->encodedBlogUrl() . date('/Y/m/d', $createTime) . '/' . rawurlencode($url);
Expand Down
2 changes: 1 addition & 1 deletion _extensions/s2_blog/Controller/PostPageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ private function get_post(Request $request, HtmlTemplate $template, int $year, i
$template
->putInPlaceholder('meta_description', self::extractMetaDescriptions($row['text']))
->putInPlaceholder('text', $this->viewer->render('post', $row, 's2_blog'))
->putInPlaceholder('id', $post_id)
->putInPlaceholder('id', md5('s2_blog_post_' . $post_id))
->putInPlaceholder('head_title', s2_htmlencode($row['title']))
;

Expand Down
129 changes: 112 additions & 17 deletions _extensions/s2_blog/Extension.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,18 @@

namespace s2_extensions\s2_blog;

use Psr\Log\LoggerInterface;
use S2\Cms\Asset\AssetPack;
use S2\Cms\Config\DynamicConfigProvider;
use S2\Cms\Controller\CommentController;
use S2\Cms\Framework\Container;
use S2\Cms\Framework\ExtensionInterface;
use S2\Cms\Model\Article\ArticleRenderedEvent;
use S2\Cms\Model\ArticleProvider;
use S2\Cms\Model\AuthProvider;
use S2\Cms\Model\Comment\CommentStrategyInterface;
use S2\Cms\Model\UrlBuilder;
use S2\Cms\Model\User\UserProvider;
use S2\Cms\Pdo\DbLayer;
use S2\Cms\Queue\QueueHandlerInterface;
use S2\Cms\Queue\QueuePublisher;
Expand All @@ -37,6 +42,7 @@
use s2_extensions\s2_blog\Controller\TagsPageController;
use s2_extensions\s2_blog\Controller\YearPageController;
use s2_extensions\s2_blog\Model\BlogCommentNotifier;
use s2_extensions\s2_blog\Model\BlogCommentStrategy;
use s2_extensions\s2_blog\Model\BlogPlaceholderProvider;
use s2_extensions\s2_blog\Model\PostProvider;
use s2_extensions\s2_blog\Service\PostIndexer;
Expand Down Expand Up @@ -256,6 +262,29 @@ public function buildContainer(Container $container): void
);
});

$container->set(BlogCommentStrategy::class, function (Container $container) {
return new BlogCommentStrategy(
$container->get(DbLayer::class),
$container->get(BlogCommentNotifier::class),
);
}, [CommentStrategyInterface::class]);
$container->set('s2_blog.comment_controller', function (Container $container) {
/** @var DynamicConfigProvider $provider */
$provider = $container->get(DynamicConfigProvider::class);
return new CommentController(
$container->get(AuthProvider::class),
$container->get(UserProvider::class),
$container->get(BlogCommentStrategy::class),
$container->get('comments_translator'),
$container->get(UrlBuilder::class),
$container->get(HtmlTemplateProvider::class),
$container->get(Viewer::class),
$container->get(LoggerInterface::class),
$provider->get('S2_ENABLED_COMMENTS') === '1',
$provider->get('S2_PREMODERATION') === '1',
);
});

$container->set(PostProvider::class, function (Container $container) {
return new PostProvider(
$container->get(DbLayer::class),
Expand All @@ -266,8 +295,8 @@ public function buildContainer(Container $container): void
$container->set(BlogCommentNotifier::class, function (Container $container) {
return new BlogCommentNotifier(
$container->get(DbLayer::class),
$container->get(UrlBuilder::class),
$container->get(BlogUrlBuilder::class),
$container->getParameter('base_url'),
);
});

Expand Down Expand Up @@ -406,23 +435,89 @@ public function registerRoutes(RouteCollection $routes, Container $container): v
$priority = 1;

if ($s2BlogUrl !== '') {
$routes->add('blog_main', new Route($s2BlogUrl . '{slash</?>}', ['_controller' => MainPageController::class, 'page' => 0], options: ['utf8' => true]), $priority);
$routes->add('blog_main', new Route(
$s2BlogUrl . '{slash</?>}',
['_controller' => MainPageController::class, 'page' => 0],
options: ['utf8' => true],
methods: ['GET'],
), $priority);
} else {
$routes->add('blog_main', new Route('/', ['_controller' => MainPageController::class, 'page' => 0, 'slash' => '/'], options: ['utf8' => true]), $priority);
$routes->add('blog_main', new Route(
'/',
['_controller' => MainPageController::class, 'page' => 0, 'slash' => '/'],
options: ['utf8' => true],
methods: ['GET'],
), $priority);
}
$routes->add('blog_main_pages', new Route($s2BlogUrl . '/skip/{page<\d+>}', ['_controller' => MainPageController::class, 'slash' => '/'], options: ['utf8' => true]), $priority);

$routes->add('blog_rss', new Route($s2BlogUrl . '/rss.xml', ['_controller' => BlogRss::class], options: ['utf8' => true]), $priority);
$routes->add('blog_sitemap', new Route($s2BlogUrl . '/sitemap.xml', ['_controller' => Sitemap::class], options: ['utf8' => true]), $priority);

$routes->add('blog_favorite', new Route($s2BlogUrl . '/' . $favoriteUrl . '{slash</?>}', ['_controller' => FavoritePageController::class], options: ['utf8' => true]), $priority);

$routes->add('blog_tags', new Route($s2BlogUrl . '/' . $tagsUrl . '{slash</?>}', ['_controller' => TagsPageController::class], options: ['utf8' => true]), $priority);
$routes->add('blog_tag', new Route($s2BlogUrl . '/' . $tagsUrl . '/{tag}{slash</?>}', ['_controller' => TagPageController::class], options: ['utf8' => true]), $priority);

$routes->add('blog_year', new Route($s2BlogUrl . '/{year<\d+>}/', ['_controller' => YearPageController::class], options: ['utf8' => true]), $priority);
$routes->add('blog_month', new Route($s2BlogUrl . '/{year<\d+>}/{month<\d+>}/', ['_controller' => MonthPageController::class], options: ['utf8' => true]), $priority);
$routes->add('blog_day', new Route($s2BlogUrl . '/{year<\d+>}/{month<\d+>}/{day<\d+>}/', ['_controller' => DayPageController::class], options: ['utf8' => true]), $priority);
$routes->add('blog_post', new Route($s2BlogUrl . '/{year<\d+>}/{month<\d+>}/{day<\d+>}/{url}', ['_controller' => PostPageController::class], options: ['utf8' => true]), $priority);
$routes->add('blog_main_pages', new Route(
$s2BlogUrl . '/skip/{page<\d+>}',
['_controller' => MainPageController::class, 'slash' => '/'],
options: ['utf8' => true],
methods: ['GET'],
), $priority);

$routes->add('blog_rss', new Route(
$s2BlogUrl . '/rss.xml',
['_controller' => BlogRss::class],
options: ['utf8' => true],
methods: ['GET'],
), $priority);
$routes->add('blog_sitemap', new Route(
$s2BlogUrl . '/sitemap.xml',
['_controller' => Sitemap::class],
options: ['utf8' => true],
methods: ['GET'],
), $priority);

$routes->add('blog_favorite', new Route(
$s2BlogUrl . '/' . $favoriteUrl . '{slash</?>}',
['_controller' => FavoritePageController::class],
options: ['utf8' => true],
methods: ['GET'],
), $priority);

$routes->add('blog_tags', new Route(
$s2BlogUrl . '/' . $tagsUrl . '{slash</?>}',
['_controller' => TagsPageController::class],
options: ['utf8' => true],
methods: ['GET'],
), $priority);
$routes->add('blog_tag', new Route(
$s2BlogUrl . '/' . $tagsUrl . '/{tag}{slash</?>}',
['_controller' => TagPageController::class],
options: ['utf8' => true],
methods: ['GET'],
), $priority);

$routes->add('blog_year', new Route(
$s2BlogUrl . '/{year<\d+>}/',
['_controller' => YearPageController::class],
options: ['utf8' => true],
methods: ['GET'],
), $priority);
$routes->add('blog_month', new Route(
$s2BlogUrl . '/{year<\d+>}/{month<\d+>}/',
['_controller' => MonthPageController::class],
options: ['utf8' => true],
methods: ['GET'],
), $priority);
$routes->add('blog_day', new Route(
$s2BlogUrl . '/{year<\d+>}/{month<\d+>}/{day<\d+>}/',
['_controller' => DayPageController::class],
options: ['utf8' => true],
methods: ['GET'],
), $priority);
$routes->add('blog_post', new Route(
$s2BlogUrl . '/{year<\d+>}/{month<\d+>}/{day<\d+>}/{url}',
['_controller' => PostPageController::class],
options: ['utf8' => true],
methods: ['GET'],
), $priority);
$routes->add('blog_comment', new Route(
$s2BlogUrl . '/{year<\d+>}/{month<\d+>}/{day<\d+>}/{url}',
['_controller' => 's2_blog.comment_controller'],
options: ['utf8' => true],
methods: ['POST'],
), $priority);
}
}
103 changes: 84 additions & 19 deletions _extensions/s2_blog/Model/BlogCommentNotifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,32 @@

namespace s2_extensions\s2_blog\Model;

use S2\Cms\Model\UrlBuilder;
use S2\Cms\Pdo\DbLayer;
use S2\Cms\Pdo\DbLayerException;
use s2_extensions\s2_blog\BlogUrlBuilder;

/**
* 1. Sends notifications on new comments:
* - Retrieves information about the comment and associated post.
* - Sends the comment to commentators who subscribed to this post.
* - Generates an unsubscribe link.
* - Marks the comment as sent.
*
* 2. Unsubscribes commentators by parameters from the unsubscribe links.
*/
readonly class BlogCommentNotifier
{
public function __construct(
private DbLayer $dbLayer,
private UrlBuilder $urlBuilder,
private BlogUrlBuilder $blogUrlBuilder,
private string $baseUrl,
) {
}

/**
* @throws DbLayerException
*/
public function notify(int $commentId): void
{
/**
Expand Down Expand Up @@ -55,41 +69,40 @@ public function notify(int $commentId): void
}

// Getting some info about the post commented
$query = [
$result = $this->dbLayer->buildAndQuery([
'SELECT' => 'title, create_time, url',
'FROM' => 's2_blog_posts',
'WHERE' => 'id = ' . $comment['post_id'] . ' AND published = 1 AND commented = 1'
];
$result = $this->dbLayer->buildAndQuery($query);
'WHERE' => 'id = :post_id AND published = 1 AND commented = 1'
], [
'post_id' => $comment['post_id']
]);

$post = $this->dbLayer->fetchAssoc($result);
if (!$post) {
return;
}

$link = $this->blogUrlBuilder->postFromTimestamp($post['create_time'], $post['url']);
$link = $this->blogUrlBuilder->absPostFromTimestamp($post['create_time'], $post['url']);

// Fetching receivers' names and addresses
$query = [
'SELECT' => 'id, nick, email, ip, time',
'FROM' => 's2_blog_comments',
'WHERE' => 'post_id = ' . $comment['post_id'] . ' AND subscribed = 1 AND shown = 1 AND email <> \'' . $this->dbLayer->escape($comment['email']) . '\''
];
$result = $this->dbLayer->buildAndQuery($query);
$allReceivers = $this->getCommentReceivers($comment['post_id'], $comment['email'], '<>');

// Group by email, taking last records
$receivers = [];
while ($receiver = $this->dbLayer->fetchAssoc($result)) {
foreach ($allReceivers as $receiver) {
$receivers[$receiver['email']] = $receiver;
}

$message = s2_bbcode_to_mail($comment['text']);

foreach ($receivers as $receiver) {
$hash = md5($receiver['id'] . $receiver['ip'] . $receiver['nick'] . $receiver['email'] . $receiver['time']);
$unsubscribeLink = $this->urlBuilder->rawAbsLink('/comment_unsubscribe', [
'mail=' . urlencode($receiver['email']),
'id=' . $comment['post_id'],
'code=' . $receiver['hash'],
]);

$unsubscribeLink = $this->baseUrl
. '/comment.php?mail=' . urlencode($receiver['email'])
. '&id=' . $comment['post_id'] . '.s2_blog'
. '&unsubscribe=' . base_convert(substr($hash, 0, 16), 16, 36);
s2_mail_comment($receiver['nick'], $receiver['email'], $comment['text'], $post['title'], $link, $comment['nick'], $unsubscribeLink);
s2_mail_comment($receiver['nick'], $receiver['email'], $message, $post['title'], $link, $comment['nick'], $unsubscribeLink);
}

// Toggle sent mark
Expand All @@ -100,4 +113,56 @@ public function notify(int $commentId): void
];
$this->dbLayer->buildAndQuery($query);
}

/**
* @throws DbLayerException
*/
public function unsubscribe(int $postId, string $email, string $code): bool
{
$receivers = $this->getCommentReceivers($postId, $email, '=');

foreach ($receivers as $receiver) {
if ($code === $receiver['hash']) {
$this->dbLayer->buildAndQuery([
'UPDATE' => 's2_blog_comments',
'SET' => 'subscribed = 0',
'WHERE' => 'post_id = :post_id and subscribed = 1 and email = :email'
], [
'post_id' => $postId,
'email' => $email,
]);

return true;
}
}

return false;
}

/**
* @throws DbLayerException
*/
private function getCommentReceivers(int $postId, string $email, string $operation): array
{
if (!\in_array($operation, ['=', '<>'], true)) {
throw new \InvalidArgumentException(sprintf('Invalid operation "%s".', $operation));
}

$result = $this->dbLayer->buildAndQuery([
'SELECT' => 'id, nick, email, ip, time',
'FROM' => 's2_blog_comments',
'WHERE' => 'post_id = :post_id AND subscribed = 1 AND shown = 1 AND email ' . $operation . ' :email'
], [
'post_id' => $postId,
'email' => $email,
]);

$receivers = $this->dbLayer->fetchAssocAll($result);
foreach ($receivers as &$receiver) {
$receiver['hash'] = substr(base_convert(md5('s2_blog_comments' . serialize($receiver)), 16, 36), 0, 13);
}
unset($receiver);

return $receivers;
}
}
Loading

0 comments on commit d23e0fe

Please sign in to comment.