Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 81 additions & 10 deletions Classes/Controller/BackendController.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,13 @@

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use T3docs\BlogExample\Domain\Model\Administrator;
use T3docs\BlogExample\Domain\Model\Blog;
use T3docs\BlogExample\Domain\Model\Post;
use T3docs\BlogExample\Domain\Repository\AdministratorRepository;
use T3docs\BlogExample\Domain\Repository\BlogRepository;
use T3docs\BlogExample\Domain\Repository\CommentRepository;
use T3docs\BlogExample\Domain\Repository\PersonRepository;
use T3docs\BlogExample\Domain\Repository\PostRepository;
use T3docs\BlogExample\Service\BlogFactory;
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
Expand All @@ -31,6 +34,7 @@
use TYPO3\CMS\Backend\Template\ModuleTemplate;
use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Imaging\IconSize;
use TYPO3\CMS\Core\Localization\LanguageService;
Expand All @@ -56,12 +60,22 @@ public function __construct(
protected readonly BlogFactory $blogFactory,
protected readonly PostRepository $postRepository,
protected readonly CommentRepository $commentRepository,
protected readonly PersonRepository $personRepository,
protected readonly AdministratorRepository $administratorRepository,
protected readonly ModuleTemplateFactory $moduleTemplateFactory,
private readonly IconFactory $iconFactory,
private readonly LanguageServiceFactory $languageServiceFactory,
protected readonly ComponentFactory $componentFactory,
) {}

/**
* Function will be called before every other action
*/
protected function initializeAction(): void
{
$this->pageUid = (int)($this->request->getQueryParams()['id'] ?? 0);
}

public function addPopulateButton(ButtonBar $buttonBar): void
{
$populateButton = $this->componentFactory->createLinkButton()
Expand Down Expand Up @@ -91,20 +105,19 @@ public function getMetaInformation(): array|false
);
}

/**
* Function will be called before every other action
*/
protected function initializeAction(): void
{
$this->pageUid = (int)($this->request->getQueryParams()['id'] ?? 0);
}

/**
* Index action for this controller. Displays a list of blogs.
*/
public function indexAction(int $currentPage = 1): ResponseInterface
{
$view = $this->initializeModuleTemplate($this->request);
if (!$this->isCurrentPageSysfolder()) {

$view->assignMultiple([
'error' => true,
]);
return $view->renderResponse('Index');
}
$allAvailableBlogs = $this->blogRepository->findAll();
$paginator = new QueryResultPaginator(
$allAvailableBlogs,
Expand All @@ -124,18 +137,29 @@ public function indexAction(int $currentPage = 1): ResponseInterface
}

/**
* Deletes all blogs
* Deletes all blog data from the current backend page
*/
public function deleteAllAction(): ResponseInterface
{
if (!$this->isCurrentPageSysfolder()) {
return $this->redirect('index');
}
// Deleting a blog deletes the posts and comments by cascade delete
$this->blogRepository->removeAll();
// Persons and administrators must be deleted explicitly
$this->personRepository->removeAll();
$this->administratorRepository->removeAll();
return $this->redirect('index');
}

/**
* Deletes a blog
*/
public function deleteBlogAction(Blog $blog): ResponseInterface
{
if (!$this->isCurrentPageSysfolder()) {
return $this->redirect('index');
}
$this->blogRepository->remove($blog);
$this->addFlashMessage(sprintf('Blog "%s" was deleted. ', $blog->title), 'Blog was deleted');
return $this->redirect('index');
Expand All @@ -146,9 +170,22 @@ public function deleteBlogAction(Blog $blog): ResponseInterface
*/
public function populateAction(): ResponseInterface
{
if (!$this->isCurrentPageSysfolder()) {
return $this->redirect('index');
}
$numberOfExistingBlogs = $this->blogRepository->countAll();

// fetch administrator if they exist
$administrator = $this->administratorRepository->findOneBy(['email' => 'john.doe@example.com']);
if ($administrator === null) {
// create administrator
$administrator = new Administrator();
$administrator->name = 'John Doe';
$administrator->email = 'john.doe@example.com';
$this->administratorRepository->add($administrator);
}
for ($blogNumber = $numberOfExistingBlogs + 1; $blogNumber < ($numberOfExistingBlogs + 5); $blogNumber++) {
$blog = $this->blogFactory->createBlog($blogNumber);
$blog = $this->blogFactory->createBlog($administrator, $blogNumber, new \DateTimeImmutable());
$this->blogRepository->add($blog);
}
$this->addFlashMessage('populated');
Expand All @@ -163,6 +200,9 @@ public function showBlogAction(
string $tag = '',
int $currentPage = 1,
): ResponseInterface {
if (!$this->isCurrentPageSysfolder()) {
return $this->redirect('index');
}
$view = $this->initializeModuleTemplate($this->request);
if ($blog === null) {
$this->addFlashMessage('Blog was not found', 'Warning', ContextualFeedbackSeverity::WARNING);
Expand Down Expand Up @@ -193,13 +233,19 @@ public function showBlogAction(
*/
public function showPostAction(Post $post): ResponseInterface
{
if (!$this->isCurrentPageSysfolder()) {
return $this->redirect('index');
}
$view = $this->initializeModuleTemplate($this->request);
$view->assign('post', $post);
return $view->renderResponse('ShowPost');
}

public function showAllCommentsAction(): ResponseInterface
{
if (!$this->isCurrentPageSysfolder()) {
return $this->redirect('index');
}
$view = $this->initializeModuleTemplate($this->request);
$comments = $this->commentRepository->findAll();
$view->assign('comments', $comments);
Expand Down Expand Up @@ -300,4 +346,29 @@ private function buildMenu(ModuleTemplate $view, string &$context): Menu
}
return $menu;
}

protected function isCurrentPageSysfolder(): bool
{
if ($this->pageUid <= 0) {
$this->addFlashMessage(
'Please open the module on a storage folder (sysfolder).',
'Missing page context',
ContextualFeedbackSeverity::ERROR,
);
return false;
}

$page = BackendUtility::readPageAccess($this->pageUid, $GLOBALS['BE_USER']->getPagePermsClause(1));

// sysfolder check
if (!is_array($page) || (int)$page['doktype'] !== PageRepository::DOKTYPE_SYSFOLDER) {
$this->addFlashMessage(
'This module must be used on a storage folder (sysfolder). Please select a folder in the page tree.',
'Wrong page type',
ContextualFeedbackSeverity::ERROR,
);
return false;
}
return true;
}
}
2 changes: 1 addition & 1 deletion Classes/Controller/BlogController.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ public function populateAction(): ResponseInterface
$this->checkBlogAdminAccess();
$numberOfExistingBlogs = $this->blogRepository->countAll();
for ($blogNumber = $numberOfExistingBlogs + 1; $blogNumber < ($numberOfExistingBlogs + 5); $blogNumber++) {
$blog = $this->blogFactory->createBlog($blogNumber);
$blog = $this->blogFactory->createBlog(null, $blogNumber, new \DateTimeImmutable());
$this->blogRepository->add($blog);
}
$this->addFlashMessage('populated');
Expand Down
37 changes: 17 additions & 20 deletions Classes/Service/BlogFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@
use T3docs\BlogExample\Domain\Model\Administrator;
use T3docs\BlogExample\Domain\Model\Blog;
use T3docs\BlogExample\Domain\Model\Comment;
use T3docs\BlogExample\Domain\Model\Person;
use T3docs\BlogExample\Domain\Model\Post;
use TYPO3\CMS\Core\SingletonInterface;

/*
* This file is part of the TYPO3 CMS project.
Expand All @@ -27,51 +25,50 @@
/**
* A simple blog factory to create sample data
*/
class BlogFactory implements SingletonInterface
readonly class BlogFactory
{
/**
* Returns a sample blog populated with generic data
* It is also an example how to handle objects and repositories in general
*
*
* @return Blog
*/
public function createBlog(int $blogNumber = 1): Blog
public function createBlog(?Administrator $administrator = null, int $blogNumber = 1, ?\DateTimeImmutable $baseDate = null): Blog
{
$baseDate ??= new \DateTimeImmutable('2026-01-01 12:00:00');
// initialize blog
$blog = new Blog();
$blog->setTitle('Blog #' . $blogNumber);
$blog->description = 'A blog about TYPO3 extension development.';

// create author
$author = new Person('Stephen', 'Smith', 'foo.bar@example.com');

// create administrator
$administrator = new Administrator();
$administrator->name = 'John Doe';
$administrator->email = 'john.doe@example.com';
$blog->administrator = $administrator;
if ($administrator !== null) {
$blog->administrator = $administrator;
}

// create sample posts
for ($postNumber = 1; $postNumber < 6; $postNumber++) {
// create post
$post = new Post();
$post->setTitle('The ' . $postNumber . '. post of blog #' . $blogNumber);
$post->setAuthor($author);
$post->setContent('Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.');

// create comments
$comment = new Comment();
$comment->setDate(new \DateTime());

$comment->setDate(\DateTime::createFromImmutable(
$baseDate->modify(sprintf('+%d days', $postNumber)),
));
$comment->setAuthor('Peter Pan');
$comment->setEmail('peter.pan@example.com');
$comment->setEmail(sprintf('peter.pan%d@example.com', $blogNumber));
$comment->setContent('Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.');
$comment->setHidden(false);
$post->addComment($comment);

$comment = new Comment();
$comment->setDate(new \DateTime('2009-03-19 23:44'));
$comment->setDate(\DateTime::createFromImmutable(
$baseDate->modify(sprintf('+%d days', $postNumber + 1)),
));
$comment->setAuthor('John Smith');
$comment->setEmail('john@matrix.org');
$comment->setEmail(sprintf('john.smith%d@example.com', $blogNumber));
$comment->setHidden(false);
$comment->setContent('Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.');
$post->addComment($comment);

Expand Down
74 changes: 74 additions & 0 deletions Tests/Functional/Service/BlogFactoryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

declare(strict_types=1);

namespace T3docs\BlogExample\Tests\Service;

/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/

use PHPUnit\Framework\Attributes\Test;
use T3docs\BlogExample\Domain\Model\Administrator;
use T3docs\BlogExample\Service\BlogFactory;
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;

final class BlogFactoryTest extends FunctionalTestCase
{
protected array $testExtensionsToLoad = [
't3docs/blog-example',
];

#[Test]
public function createBlogIsDeterministicAndUniquePerBlogNumber(): void
{
$factory = new BlogFactory();

$administrator = new Administrator();
$administrator->name = 'John Doe';
$administrator->email = 'john.doe@example.com';

$baseDate = new \DateTimeImmutable('2026-01-01 12:00:00');

$blog1 = $factory->createBlog($administrator, 1, $baseDate);
$blog2 = $factory->createBlog($administrator, 2, $baseDate);

self::assertSame('Blog #1', $blog1->getTitle());
self::assertSame('Blog #2', $blog2->getTitle());

// Administrator is the same object on both blogs (by design)
self::assertSame('john.doe@example.com', $blog1->administrator?->email);
self::assertSame('john.doe@example.com', $blog2->administrator?->email);

// Safer: rewind storages before current()
$posts1 = $blog1->getPosts();
$posts1->rewind();
$post1Blog1 = $posts1->current();

$posts2 = $blog2->getPosts();
$posts2->rewind();
$post1Blog2 = $posts2->current();

$comments1 = $post1Blog1->getComments();
$comments1->rewind();
$firstComment1 = $comments1->current();

$comments2 = $post1Blog2->getComments();
$comments2->rewind();
$firstComment2 = $comments2->current();

self::assertNotSame($firstComment1->getEmail(), $firstComment2->getEmail());

$firstCommentDate = $firstComment1->getDate();
self::assertSame('2026-01-02 12:00:00', $firstCommentDate->format('Y-m-d H:i:s'));
}
}