├── CHANGELOG.md
├── CONTRIBUTING.md
├── Classes
├── Controller
│ ├── AbstractController.php
│ ├── BackendController.php
│ ├── BlogController.php
│ ├── CommentController.php
│ └── PostController.php
├── Domain
│ ├── Model
│ │ ├── Administrator.php
│ │ ├── Blog.php
│ │ ├── Comment.php
│ │ ├── FrontendUser.php
│ │ ├── FrontendUserGroup.php
│ │ ├── Info.php
│ │ ├── Person.php
│ │ ├── Post.php
│ │ └── Tag.php
│ ├── Repository
│ │ ├── AdministratorRepository.php
│ │ ├── BlogRepository.php
│ │ ├── CommentRepository.php
│ │ ├── PersonRepository.php
│ │ └── PostRepository.php
│ └── Validator
│ │ ├── BlogValidator.php
│ │ ├── PostValidator.php
│ │ └── TitleValidator.php
├── Exception
│ └── NoBlogAdminAccessException.php
├── ExpressionLanguage
│ └── ExtensionConfigurationProvider.php
├── PageTitle
│ └── BlogPageTitleProvider.php
├── Property
│ └── TypeConverters
│ │ └── HiddenCommentConverter.php
├── Service
│ ├── BlogFactory.php
│ ├── BlogValidationService.php
│ └── PostValidationService.php
├── Upgrades
│ ├── MigratePluginsToContentElementsUpgradeWizard.php
│ └── PluginUpgradeWizard.php
└── ViewHelpers
│ └── GravatarViewHelper.php
├── Configuration
├── Backend
│ └── Modules.php
├── ExpressionLanguage.php
├── Extbase
│ └── Persistence
│ │ └── Classes.php
├── FlexForms
│ └── PluginSettings.xml
├── Icons.php
├── Services.yaml
├── Sets
│ ├── BlogExample
│ │ ├── config.yaml
│ │ ├── settings.definitions.yaml
│ │ └── setup.typoscript
│ ├── DefaultStyles
│ │ ├── config.yaml
│ │ └── setup.typoscript
│ └── RssFeed
│ │ ├── config.yaml
│ │ ├── constants.typoscript
│ │ ├── settings.definitions.yaml
│ │ └── setup.typoscript
├── TCA
│ ├── Overrides
│ │ ├── fe_users.php
│ │ └── tt_content.php
│ ├── tx_blogexample_domain_model_blog.php
│ ├── tx_blogexample_domain_model_comment.php
│ ├── tx_blogexample_domain_model_info.php
│ ├── tx_blogexample_domain_model_person.php
│ ├── tx_blogexample_domain_model_post.php
│ └── tx_blogexample_domain_model_tag.php
└── page.tsconfig
├── Makefile
├── README.md
├── Resources
├── Private
│ ├── Backend
│ │ └── Templates
│ │ │ ├── Index.html
│ │ │ ├── ShowAllComments.html
│ │ │ ├── ShowBlog.html
│ │ │ └── ShowPost.html
│ ├── Language
│ │ ├── Module
│ │ │ └── locallang_mod.xlf
│ │ ├── de.locallang.xlf
│ │ ├── locallang.xlf
│ │ └── locallang_db.xlf
│ ├── Layouts
│ │ └── Default.html
│ ├── Partials
│ │ ├── BlogForm.html
│ │ ├── CommentForm.html
│ │ ├── FormErrors.html
│ │ ├── Pagination.html
│ │ ├── PostForm.html
│ │ ├── PostMetaData.html
│ │ └── PostTags.html
│ └── Templates
│ │ ├── Blog
│ │ ├── Edit.html
│ │ ├── Index.html
│ │ └── New.html
│ │ └── Post
│ │ ├── DisplayRssList.html
│ │ ├── Edit.html
│ │ ├── Index.html
│ │ ├── Index.txt
│ │ ├── New.html
│ │ └── Show.html
└── Public
│ ├── Css
│ └── BlogExample.css
│ └── Icons
│ ├── Extension.svg
│ ├── FlashMessages
│ ├── error.png
│ ├── information.png
│ ├── notice.png
│ ├── ok.png
│ └── warning.png
│ ├── default_gravatar.gif
│ ├── icon_close.gif
│ ├── icon_delete.gif
│ ├── icon_edit.gif
│ ├── icon_new.gif
│ ├── icon_next.gif
│ ├── icon_plaintext.gif
│ ├── icon_populate.gif
│ ├── icon_previous.gif
│ ├── icon_relation.gif
│ ├── icon_tx_blogexample_domain_model_blog.gif
│ ├── icon_tx_blogexample_domain_model_comment.gif
│ ├── icon_tx_blogexample_domain_model_person.gif
│ ├── icon_tx_blogexample_domain_model_post.gif
│ ├── icon_tx_blogexample_domain_model_tag.gif
│ └── module-blog.svg
├── composer.json
├── ext_conf_template.txt
├── ext_emconf.php
├── ext_localconf.php
└── ext_tables.sql
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change log
2 |
3 | All notable changes to this project will be documented in this file.
4 | This project adheres to [Semantic Versioning](https://semver.org/).
5 |
6 |
7 | ## Unreleased
8 |
9 | ### Fixed
10 | - Add return type to `ModuleController->initializeAction()` (#74)
11 |
12 | ## 12.1.0 - 2024-04-15
13 |
14 | ### Added
15 | - Add page title provider for blog post indexAction and showAction (#43)
16 |
17 | ### Fixed
18 | - Update locallang_db.xlf - missing field names added (#76)
19 | - Correct the integration of categories (#77)
20 | - Invoke addFlashMessage() correctly when deleting a post or a blog (#83, #86)
21 |
22 | ## 12.0.4 - 2023-09-16
23 |
24 | ### Fixed
25 | - Remove superfluous CSH language file (#57)
26 | - Streamline XLIFF files (#58)
27 |
28 | ## 12.0.3 - 2023-08-07
29 |
30 | ### Fixed
31 | - Substitute ExtensionManagementUtility::getFileFieldTCAConfig() with TCA file type (#48)
32 |
33 | ## 12.0.2 - 2022-10-09
34 |
35 | ### Changed
36 | - The main branch was renamed from `master` to `main`
37 |
38 | ### Fixed
39 | - Fix `Services.yaml` for type converter example (#41)
40 |
41 | ## 12.0.1 - 2022-10-07
42 |
43 | ### Added
44 | - Add TypeConverter examples (#34)
45 |
46 | ### Changed
47 | - Changed the vendor to t3docs (#40)
48 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contribute to this extension
2 |
3 | For help about contributing to the documentation in general, see https://docs.typo3.org/typo3cms/HowToDocument/
4 |
5 | You are welcome to contribute to this example extension. Please note that the code is used in different manuals
6 | to demonstrate certain features of TYPO3 and Extbase.
7 |
8 | Talk to us on Slack, channel #typo3-documentation or open an [issue](https://github.com/TYPO3-Documentation/blog_example/issues) to be sure
9 | that your intended changes are fitting the didactics of these manuals.
10 |
11 | ## Run tests and apply coding guidelines
12 |
13 | The blog-example comes with a simple demo set of tests. It relies
14 | on the runTests.sh script which is a simplified version of a similar script from the TYPO3 core.
15 | Find detailed usage examples by executing `Build/Scripts/runTests.sh -h` and have a look at
16 | `.github/workflows/tests.yml` to see how this is used in CI.
17 |
18 | Install the requirements:
19 |
20 | ```
21 | make install
22 | ```
23 |
24 | Apply rector and automatic coding guideline fixes:
25 |
26 | ```
27 | make fix
28 | ```
29 |
30 | Run tests:
31 |
32 | ```
33 | make test
34 | ```
35 |
36 | See help:
37 |
38 | ```
39 | make
40 | ```
41 |
42 | If you have no `make` installed or which for finer control, you can run the tests directly:
43 |
44 | ```
45 | Build/Scripts/runTests.sh -s h
46 | ```
47 |
48 | # General TYPO3 Support
49 |
50 | If you have some general TYPO3 support questions or need help with TYPO3, please see https://typo3.org/help.
51 |
--------------------------------------------------------------------------------
/Classes/Controller/AbstractController.php:
--------------------------------------------------------------------------------
1 | request->getControllerName(),
39 | $this->actionMethodName,
40 | );
41 | return $this->translate($locallangKey, $defaultFlashMessage);
42 | }
43 |
44 | protected function hasBlogAdminAccess(): bool
45 | {
46 | // TODO access protection
47 | return true;
48 | }
49 |
50 | /**
51 | * @throws NoBlogAdminAccessException
52 | */
53 | protected function checkBlogAdminAccess(): void
54 | {
55 | if (!$this->hasBlogAdminAccess()) {
56 | throw new NoBlogAdminAccessException();
57 | }
58 | }
59 |
60 | /**
61 | * helper function to render localized flashmessages
62 | */
63 | public function addLocalizedFlashMessage(
64 | string $action,
65 | ContextualFeedbackSeverity $severity = ContextualFeedbackSeverity::OK,
66 | ): void {
67 | $messageLocallangKey = sprintf(
68 | 'flashmessage.%s.%s',
69 | $this->request->getControllerName(),
70 | $action,
71 | );
72 | $localizedMessage = $this->translate(
73 | $messageLocallangKey,
74 | '[' . $messageLocallangKey . ']',
75 | );
76 | $titleLocallangKey = sprintf('%s.title', $messageLocallangKey);
77 | $localizedTitle = $this->translate(
78 | $titleLocallangKey,
79 | '[' . $titleLocallangKey . ']',
80 | );
81 | $this->addFlashMessage($localizedMessage, $localizedTitle, $severity);
82 | }
83 |
84 | /**
85 | * helper function to use localized strings in BlogExample controllers
86 | */
87 | protected function translate(
88 | string $key,
89 | string $defaultMessage = '',
90 | ): string {
91 | $message = LocalizationUtility::translate($key, 'BlogExample');
92 | if ($message === null) {
93 | $message = $defaultMessage;
94 | }
95 | return $message;
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/Classes/Controller/BlogController.php:
--------------------------------------------------------------------------------
1 | blogRepository->findAll();
54 | $paginator = new QueryResultPaginator(
55 | $allAvailableBlogs,
56 | $currentPage,
57 | (int)($this->settings['itemsPerPage'] ?? 3),
58 | );
59 | $pagination = new SimplePagination($paginator);
60 | $this->view->assignMultiple([
61 | 'blogs' => $allAvailableBlogs,
62 | 'paginator' => $paginator,
63 | 'pagination' => $pagination,
64 | 'pages' => range(1, $pagination->getLastPageNumber()),
65 | ]);
66 | return $this->htmlResponse();
67 | }
68 |
69 | /**
70 | * Output
Hello World!
71 | */
72 | public function helloWorldAction(): ResponseInterface
73 | {
74 | return $this->htmlResponse('Hello World! ');
75 | }
76 |
77 | /**
78 | * Displays a form for creating a new blog
79 | */
80 | #[IgnoreValidation(['value' => 'newBlog'])]
81 | public function newAction(?Blog $newBlog = null): ResponseInterface
82 | {
83 | $this->view->assignMultiple([
84 | 'newBlog' => $newBlog,
85 | 'administrators' => $this->administratorRepository->findAll(),
86 | ]);
87 | return $this->htmlResponse();
88 | }
89 |
90 | /**
91 | * Creates a new blog
92 | *
93 | * $blog is a fresh Blog object which has not yet been added to the
94 | * repository
95 | */
96 | #[Validate(['param' => 'newBlog', 'validator' => BlogValidator::class])]
97 | public function createAction(Blog $newBlog): ResponseInterface
98 | {
99 | $this->checkBlogAdminAccess();
100 | $this->blogRepository->add($newBlog);
101 | $this->addFlashMessage('created');
102 | return $this->redirect('index');
103 | }
104 |
105 | /**
106 | * Displays a form for editing an existing blog
107 | *
108 | * $blog might also be a clone of the original blog already containing
109 | * modifications if the edit form has been submitted, contained errors and
110 | * therefore ended up in this action again.
111 | */
112 | #[IgnoreValidation(['value' => 'blog'])]
113 | public function editAction(Blog $blog): ResponseInterface
114 | {
115 | $this->view->assignMultiple([
116 | 'blog' => $blog,
117 | 'administrators' => $this->administratorRepository->findAll(),
118 | ]);
119 | return $this->htmlResponse();
120 | }
121 |
122 | /**
123 | * Updates an existing blog
124 | *
125 | * $blog is a not yet persisted clone of the original blog containing
126 | * the modifications
127 | *
128 | * @throws NoBlogAdminAccessException
129 | */
130 | #[Validate(['param' => 'blog', 'validator' => BlogValidator::class])]
131 | public function updateAction(Blog $blog): ResponseInterface
132 | {
133 | $this->checkBlogAdminAccess();
134 | $this->blogRepository->update($blog);
135 | $this->addFlashMessage('updated');
136 | return $this->redirect('index');
137 | }
138 |
139 | /**
140 | * Deletes an existing blog
141 | * @throws NoBlogAdminAccessException
142 | */
143 | public function deleteAction(Blog $blog): ResponseInterface
144 | {
145 | $this->checkBlogAdminAccess();
146 | $this->blogRepository->remove($blog);
147 | $this->addFlashMessage('The blog has been deleted.', 'deleted', ContextualFeedbackSeverity::INFO);
148 | return $this->redirect('index');
149 | }
150 |
151 | /**
152 | * Deletes an existing blog
153 | * @throws NoBlogAdminAccessException
154 | */
155 | public function deleteAllAction(): ResponseInterface
156 | {
157 | $this->checkBlogAdminAccess();
158 | $this->blogRepository->removeAll();
159 | return $this->redirect('index');
160 | }
161 |
162 | /**
163 | * Creates a several new blogs
164 | * @throws NoBlogAdminAccessException
165 | */
166 | public function populateAction(): ResponseInterface
167 | {
168 | $this->checkBlogAdminAccess();
169 | $numberOfExistingBlogs = $this->blogRepository->countAll();
170 | for ($blogNumber = $numberOfExistingBlogs + 1; $blogNumber < ($numberOfExistingBlogs + 5); $blogNumber++) {
171 | $blog = $this->blogFactory->createBlog($blogNumber);
172 | $this->blogRepository->add($blog);
173 | }
174 | $this->addFlashMessage('populated');
175 | return $this->redirect('index');
176 | }
177 |
178 | public function showBlogAjaxAction(Blog $blog): ResponseInterface
179 | {
180 | $jsonOutput = json_encode($blog);
181 | return $this->jsonResponse($jsonOutput);
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/Classes/Controller/CommentController.php:
--------------------------------------------------------------------------------
1 | addComment($newComment);
54 | $this->postRepository->update($post);
55 | $this->addFlashMessage('created');
56 | return $this->redirect('show', 'Post', null, ['post' => $post]);
57 | }
58 |
59 | /**
60 | * Deletes an existing comment
61 | * @throws NoBlogAdminAccessException
62 | */
63 | public function deleteAction(
64 | Post $post,
65 | Comment $comment,
66 | ): ResponseInterface {
67 | $this->checkBlogAdminAccess();
68 | $post->removeComment($comment);
69 | $this->postRepository->update($post);
70 | $this->addFlashMessage('deleted', '', ContextualFeedbackSeverity::INFO);
71 | return $this->redirect('show', 'Post', null, ['post' => $post]);
72 | }
73 |
74 | /**
75 | * @throws NoSuchArgumentException
76 | */
77 | public function initializePublishAction(): void
78 | {
79 | $this->arguments->getArgument('comment')
80 | ->getPropertyMappingConfiguration()
81 | ->setTypeConverter($this->hiddenCommentConverter);
82 | }
83 |
84 | public function publishAction(
85 | Post $post,
86 | Comment $comment,
87 | ): ResponseInterface {
88 | $this->checkBlogAdminAccess();
89 | $comment->setHidden(false);
90 | $this->commentRepository->update($comment);
91 | $this->addFlashMessage('published', '', ContextualFeedbackSeverity::INFO);
92 | return $this->redirect('show', 'Post', null, ['post' => $post]);
93 | }
94 |
95 | /**
96 | * Deletes all comments of the given post
97 | * @throws NoBlogAdminAccessException
98 | */
99 | public function deleteAllAction(Post $post): ResponseInterface
100 | {
101 | $this->checkBlogAdminAccess();
102 | $post->removeAllComments();
103 | $this->postRepository->update($post);
104 | $this->addFlashMessage('deletedAll', '', ContextualFeedbackSeverity::INFO);
105 | return $this->redirect(
106 | 'edit',
107 | 'Post',
108 | null,
109 | ['post' => $post, 'blog' => $post->getBlog()],
110 | );
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/Classes/Controller/PostController.php:
--------------------------------------------------------------------------------
1 | $tagString,
65 | ];
66 | return $this->propertyMapper->convert(
67 | $input,
68 | Tag::class,
69 | );
70 | }
71 |
72 | /**
73 | * This method demonstrates property mapping to an integer
74 | * @throws Exception
75 | */
76 | protected function mapIntegerFromString(string $numberString = '42'): int
77 | {
78 | return $output = $this->propertyMapper->convert($numberString, 'integer');
79 | }
80 |
81 | /**
82 | * Displays a list of posts. If $tag is set only posts matching this tag are shown
83 | */
84 | public function indexAction(
85 | ?Blog $blog = null,
86 | string $tag = '',
87 | int $currentPage = 1,
88 | ): ResponseInterface {
89 | if ($blog == null) {
90 | return (new ForwardResponse('index'))
91 | ->withControllerName('Blog')
92 | ->withExtensionName('blog_example')
93 | ->withArguments(['currentPage' => $currentPage]);
94 | }
95 | $this->blogPageTitleProvider->setTitle($blog->getTitle());
96 | if (empty($tag)) {
97 | $posts = $this->postRepository->findBy(['blog' => $blog]);
98 | } else {
99 | $tag = urldecode($tag);
100 | $posts = $this->postRepository->findByTagAndBlog($tag, $blog);
101 | $this->view->assign('tag', $tag);
102 | }
103 | $paginator = new QueryResultPaginator(
104 | $posts,
105 | $currentPage,
106 | (int)($this->settings['itemsPerPage'] ?? 3),
107 | );
108 | $pagination = new SimplePagination($paginator);
109 | $this->view->assignMultiple([
110 | 'paginator' => $paginator,
111 | 'pagination' => $pagination,
112 | 'pages' => range(1, $pagination->getLastPageNumber()),
113 | 'blog' => $blog,
114 | 'posts' => $posts,
115 | ]);
116 | return $this->htmlResponse();
117 | }
118 |
119 | /**
120 | * Displays a list of posts as RSS feed
121 | */
122 | public function displayRssListAction(): ResponseInterface
123 | {
124 | $defaultBlog = $this->settings['defaultBlog'] ?? 0;
125 | if ($defaultBlog > 0) {
126 | $blog = $this->blogRepository->findByUid((int)$defaultBlog);
127 | } else {
128 | $blog = $this->blogRepository->findAll()->getFirst();
129 | }
130 | $this->view->assign('blog', $blog);
131 | return $this->responseFactory->createResponse()
132 | ->withHeader('Content-Type', 'text/xml; charset=utf-8')
133 | ->withBody($this->streamFactory->createStream($this->view->render()));
134 | }
135 |
136 | /**
137 | * Displays one single post
138 | */
139 | #[IgnoreValidation(['value' => 'newComment'])]
140 | public function showAction(
141 | Post $post,
142 | ?Comment $newComment = null,
143 | ): ResponseInterface {
144 | $this->blogPageTitleProvider->setTitle($post->getTitle());
145 | $this->view->assignMultiple([
146 | 'post' => $post,
147 | 'newComment' => $newComment,
148 | ]);
149 | return $this->htmlResponse();
150 | }
151 |
152 | /**
153 | * Displays a form for creating a new post
154 | *
155 | * $newPost is a fresh post object taken as a basis for the rendering
156 | */
157 | #[IgnoreValidation(['value' => 'newPost'])]
158 | public function newAction(
159 | Blog $blog,
160 | ?Post $newPost = null,
161 | ): ResponseInterface {
162 | $this->view->assignMultiple([
163 | 'authors' => $this->personRepository->findAll(),
164 | 'blog' => $blog,
165 | 'newPost' => $newPost,
166 | 'remainingPosts' => $this->postRepository->findBy(['blog' => $blog]),
167 | ]);
168 |
169 | return $this->htmlResponse();
170 | }
171 |
172 | /**
173 | * Creates a new post
174 | * @throws NoBlogAdminAccessException|IllegalObjectTypeException
175 | */
176 | public function createAction(
177 | Blog $blog,
178 | Post $newPost,
179 | ): ResponseInterface {
180 | $this->checkBlogAdminAccess();
181 | $blog->addPost($newPost);
182 | $newPost->setBlog($blog);
183 | $this->postRepository->add($newPost);
184 | $this->addFlashMessage('created');
185 | return $this->redirect('index', null, null, ['blog' => $blog]);
186 | }
187 |
188 | /**
189 | * Displays a form to edit an existing post
190 | */
191 | #[IgnoreValidation(['value' => 'post'])]
192 | public function editAction(Blog $blog, Post $post): ResponseInterface
193 | {
194 | $this->view->assignMultiple([
195 | 'authors' => $this->personRepository->findAll(),
196 | 'blog' => $blog,
197 | 'post' => $post,
198 | 'remainingPosts' => $this->postRepository->findRemaining($post),
199 | ]);
200 | return $this->htmlResponse();
201 | }
202 |
203 | /**
204 | * Updates an existing post
205 | *
206 | * $post is a clone of the original post with the updated values already applied
207 | *
208 | * @throws NoBlogAdminAccessException
209 | */
210 | public function updateAction(
211 | Blog $blog,
212 | Post $post,
213 | ): ResponseInterface {
214 | $this->checkBlogAdminAccess();
215 | $this->postRepository->update($post);
216 | $this->addFlashMessage('updated');
217 | return $this->redirect(
218 | 'show',
219 | null,
220 | null,
221 | ['post' => $post, 'blog' => $blog],
222 | );
223 | }
224 |
225 | /**
226 | * Deletes an existing post
227 | * @throws NoBlogAdminAccessException
228 | */
229 | public function deleteAction(
230 | Blog $blog,
231 | Post $post,
232 | ): ResponseInterface {
233 | $this->checkBlogAdminAccess();
234 | $this->postRepository->remove($post);
235 | $this->addFlashMessage('The post has been deleted.', 'Deleted', ContextualFeedbackSeverity::INFO);
236 | return $this->redirect('index', null, null, ['blog' => $blog]);
237 | }
238 | }
239 |
--------------------------------------------------------------------------------
/Classes/Domain/Model/Administrator.php:
--------------------------------------------------------------------------------
1 | TitleValidator::class])]
34 | public string $title = '';
35 |
36 | #[Validate(['validator' => 'StringLength', 'options' => ['minimum' => 5, 'maximum' => 80]])]
37 | public string|null $subtitle = null;
38 |
39 | /**
40 | * A short description of the blog
41 | */
42 | #[Validate(['validator' => 'StringLength', 'options' => ['maximum' => 150]])]
43 | public string $description = '';
44 |
45 | /**
46 | * @var ?ObjectStorage
47 | */
48 | public ?ObjectStorage $logo = null;
49 |
50 | /**
51 | * @var ?ObjectStorage
52 | */
53 | #[Lazy()]
54 | #[Cascade(['value' => 'remove'])]
55 | public ?ObjectStorage $posts = null;
56 |
57 | /**
58 | * @var ?ObjectStorage
59 | */
60 | public ?ObjectStorage $categories = null;
61 |
62 | public ?Administrator $administrator = null;
63 |
64 | public function __construct()
65 | {
66 | $this->initializeObject();
67 | }
68 |
69 | /**
70 | * Initializes all ObjectStorage properties when model is reconstructed from DB (where __construct is not called)
71 | */
72 | public function initializeObject(): void
73 | {
74 | $this->posts ??= new ObjectStorage();
75 | $this->categories ??= new ObjectStorage();
76 | }
77 |
78 | /**
79 | * Adds a post to this blog
80 | */
81 | public function addPost(Post $post): void
82 | {
83 | $this->posts?->attach($post);
84 | }
85 |
86 | /**
87 | * Remove a post from this blog
88 | */
89 | public function removePost(Post $postToRemove): void
90 | {
91 | $this->posts?->detach($postToRemove);
92 | }
93 |
94 | /**
95 | * Remove all posts from this blog
96 | */
97 | public function removeAllPosts(): void
98 | {
99 | $this->posts = new ObjectStorage();
100 | }
101 |
102 | /**
103 | * Returns all posts in this blog
104 | *
105 | * @return ObjectStorage
106 | */
107 | public function getPosts(): ObjectStorage
108 | {
109 | return $this->posts;
110 | }
111 |
112 | /**
113 | * @param ObjectStorage $posts
114 | */
115 | public function setPosts(ObjectStorage $posts): void
116 | {
117 | $this->posts = $posts;
118 | }
119 |
120 | /**
121 | * Add category to a blog
122 | *
123 | * @param Category $category
124 | */
125 | public function addCategory(Category $category): void
126 | {
127 | $this->categories?->attach($category);
128 | }
129 |
130 | /**
131 | * @param ObjectStorage $categories
132 | */
133 | public function setCategories(ObjectStorage $categories): void
134 | {
135 | $this->categories = $categories;
136 | }
137 |
138 | /**
139 | * @return ObjectStorage|null
140 | */
141 | public function getCategories(): ?ObjectStorage
142 | {
143 | return $this->categories;
144 | }
145 |
146 | /**
147 | * Remove category from blog
148 | */
149 | public function removeCategory(Category $category): void
150 | {
151 | $this->categories?->detach($category);
152 | }
153 |
154 | public function getTitle(): string
155 | {
156 | return $this->title;
157 | }
158 |
159 | public function setTitle(string $title): void
160 | {
161 | $this->title = $title;
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/Classes/Domain/Model/Comment.php:
--------------------------------------------------------------------------------
1 | 'NotEmpty'])]
30 | protected string $author = '';
31 |
32 | #[Validate(['validator' => 'EmailAddress'])]
33 | protected string $email = '';
34 |
35 | #[Validate(['validator' => 'StringLength', 'options' => ['maximum' => 500]])]
36 | protected string $content = '';
37 |
38 | protected bool $hidden = true;
39 |
40 | public function __construct()
41 | {
42 | $this->date = new \DateTime();
43 | }
44 |
45 | /**
46 | * @return \DateTime
47 | */
48 | public function getDate(): \DateTime
49 | {
50 | return $this->date;
51 | }
52 |
53 | public function setDate(\DateTime $date): void
54 | {
55 | $this->date = $date;
56 | }
57 |
58 | /**
59 | * @return string
60 | */
61 | public function getAuthor(): string
62 | {
63 | return $this->author;
64 | }
65 |
66 | public function setAuthor(string $author): void
67 | {
68 | $this->author = $author;
69 | }
70 |
71 | /**
72 | * @return string
73 | */
74 | public function getEmail(): string
75 | {
76 | return $this->email;
77 | }
78 |
79 | /**
80 | * @param string $email
81 | * Sets the authors email for this comment
82 | */
83 | public function setEmail(string $email): void
84 | {
85 | $this->email = $email;
86 | }
87 |
88 | /**
89 | * @return string
90 | */
91 | public function getContent(): string
92 | {
93 | return $this->content;
94 | }
95 |
96 | public function setContent(string $content): void
97 | {
98 | $this->content = $content;
99 | }
100 |
101 | public function isHidden(): bool
102 | {
103 | return $this->hidden;
104 | }
105 |
106 | public function setHidden(bool $hidden): void
107 | {
108 | $this->hidden = $hidden;
109 | }
110 |
111 | /**
112 | * Returns this comment as a formatted string
113 | */
114 | public function __toString(): string
115 | {
116 | return $this->author . ' (' . $this->email . ') said on ' . $this->date->format('Y-m-d') . ':' . chr(10) .
117 | $this->content . chr(10);
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/Classes/Domain/Model/FrontendUser.php:
--------------------------------------------------------------------------------
1 | |null
29 | */
30 | protected ObjectStorage|null $usergroup = null;
31 | public string $name = '';
32 | public string $email = '';
33 | public \DateTime|null $lastlogin = null;
34 |
35 | public function __construct()
36 | {
37 | $this->initializeObject();
38 | }
39 |
40 | /**
41 | * Initializes all ObjectStorage properties when model is reconstructed from DB (where __construct is not called)
42 | */
43 | public function initializeObject(): void
44 | {
45 | $this->usergroup ??= new ObjectStorage();
46 | }
47 |
48 | /**
49 | * Sets the usergroups. Keep in mind that the property is called "usergroup"
50 | * although it can hold several usergroups.
51 | *
52 | * @param ObjectStorage $usergroup
53 | */
54 | public function setUsergroup(ObjectStorage $usergroup): void
55 | {
56 | $this->usergroup = $usergroup;
57 | }
58 |
59 | /**
60 | * Adds a usergroup to the frontend user
61 | */
62 | public function addUsergroup(FrontendUserGroup $usergroup): void
63 | {
64 | $this->usergroup?->attach($usergroup);
65 | }
66 |
67 | /**
68 | * Removes a usergroup from the frontend user
69 | */
70 | public function removeUsergroup(FrontendUserGroup $usergroup): void
71 | {
72 | $this->usergroup?->detach($usergroup);
73 | }
74 |
75 | /**
76 | * Returns the usergroups. Keep in mind that the property is called "usergroup"
77 | * although it can hold several usergroups.
78 | *
79 | * @return ObjectStorage An object storage containing the usergroup
80 | */
81 | public function getUsergroup(): ObjectStorage
82 | {
83 | $this->usergroup ??= new ObjectStorage();
84 | return $this->usergroup;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/Classes/Domain/Model/FrontendUserGroup.php:
--------------------------------------------------------------------------------
1 |
34 | */
35 | protected ?ObjectStorage $subgroup = null;
36 |
37 | /**
38 | * Constructs a new Frontend User Group
39 | */
40 | public function __construct(string $title = '')
41 | {
42 | $this->setTitle($title);
43 | $this->initializeObject();
44 | }
45 |
46 | /**
47 | * Initializes all ObjectStorage properties when model is reconstructed from DB (where __construct is not called)
48 | */
49 | public function initializeObject(): void
50 | {
51 | $this->subgroup ??= new ObjectStorage();
52 | }
53 |
54 | public function getTitle(): string
55 | {
56 | return $this->title;
57 | }
58 |
59 | public function setTitle(string $title): void
60 | {
61 | $this->title = $title;
62 | }
63 |
64 | public function getDescription(): string
65 | {
66 | return $this->description;
67 | }
68 |
69 | public function setDescription(string $description): void
70 | {
71 | $this->description = $description;
72 | }
73 |
74 | /**
75 | * Sets the subgroups. Keep in mind that the property is called "subgroup"
76 | * although it can hold several subgroups.
77 | *
78 | * @param ObjectStorage $subgroup
79 | */
80 | public function setSubgroup(ObjectStorage $subgroup): void
81 | {
82 | $this->subgroup = $subgroup;
83 | }
84 |
85 | /**
86 | * Adds a subgroup to the frontend user
87 | */
88 | public function addSubgroup(FrontendUserGroup $subgroup): void
89 | {
90 | $this->subgroup?->attach($subgroup);
91 | }
92 |
93 | /**
94 | * Removes a subgroup from the frontend user group
95 | */
96 | public function removeSubgroup(FrontendUserGroup $subgroup): void
97 | {
98 | $this->subgroup?->detach($subgroup);
99 | }
100 |
101 | /**
102 | * Returns the subgroups. Keep in mind that the property is called "subgroup"
103 | * although it can hold several subgroups.
104 | *
105 | * @return ?ObjectStorage
106 | */
107 | public function getSubgroup(): ?ObjectStorage
108 | {
109 | return $this->subgroup;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/Classes/Domain/Model/Info.php:
--------------------------------------------------------------------------------
1 | name;
34 | }
35 |
36 | public function setName(string $name): void
37 | {
38 | $this->name = $name;
39 | }
40 |
41 | public function getBodytext(): string
42 | {
43 | return $this->bodytext;
44 | }
45 |
46 | public function setBodytext(string $bodytext): void
47 | {
48 | $this->bodytext = $bodytext;
49 | }
50 |
51 | public function getCombinedString(): string
52 | {
53 | return $this->name . ': ' . $this->bodytext;
54 | }
55 |
56 | public function __toString(): string
57 | {
58 | return $this->name;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/Classes/Domain/Model/Person.php:
--------------------------------------------------------------------------------
1 | 'StringLength', 'options' => ['maximum' => 80]])]
31 | protected string $firstname = '';
32 |
33 | #[Validate(['validator' => 'StringLength', 'options' => ['minimum' => 2, 'maximum' => 80]])]
34 | protected string $lastname = '';
35 |
36 | #[Transient()]
37 | protected string $fullname = '';
38 |
39 | #[Validate(['validator' => 'EmailAddress'])]
40 | protected string $email = '';
41 |
42 | /**
43 | * @var ?ObjectStorage
44 | */
45 | protected ?ObjectStorage $tags = null;
46 |
47 | /**
48 | * @var ?ObjectStorage
49 | */
50 | protected ?ObjectStorage $tagsSpecial = null;
51 |
52 | /**
53 | * Constructs a new Person
54 | */
55 | public function __construct(string $firstname, string $lastname, string $email)
56 | {
57 | $this->setFirstname($firstname);
58 | $this->setLastname($lastname);
59 | $this->setEmail($email);
60 |
61 | $this->initializeObject();
62 | }
63 |
64 | /**
65 | * Initializes all ObjectStorage properties when model is reconstructed from DB (where __construct is not called)
66 | */
67 | public function initializeObject(): void
68 | {
69 | $this->tags ??= new ObjectStorage();
70 | $this->tagsSpecial ??= new ObjectStorage();
71 | }
72 |
73 | public function getFirstname(): string
74 | {
75 | return $this->firstname;
76 | }
77 |
78 | public function setFirstname(string $firstname): void
79 | {
80 | $this->firstname = $firstname;
81 | }
82 |
83 | public function getLastname(): string
84 | {
85 | return $this->lastname;
86 | }
87 |
88 | public function setLastname(string $lastname): void
89 | {
90 | $this->lastname = $lastname;
91 | }
92 |
93 | public function getEmail(): string
94 | {
95 | return $this->email;
96 | }
97 |
98 | public function setEmail(string $email): void
99 | {
100 | $this->email = $email;
101 | }
102 |
103 | public function getFullName(): string
104 | {
105 | $this->fullname = $this->firstname . ' ' . $this->lastname;
106 | return $this->fullname;
107 | }
108 |
109 | /**
110 | * @return ObjectStorage
111 | */
112 | public function getTags(): ObjectStorage
113 | {
114 | return $this->tags;
115 | }
116 |
117 | /**
118 | * @param ObjectStorage $tags
119 | */
120 | public function setTags(ObjectStorage $tags): void
121 | {
122 | $this->tags = $tags;
123 | }
124 |
125 | public function addTag(Tag $tag): void
126 | {
127 | $this->tags->attach($tag);
128 | }
129 |
130 | public function removeTag(Tag $tag): void
131 | {
132 | $this->tags->detach($tag);
133 | }
134 |
135 | /**
136 | * @return ObjectStorage
137 | */
138 | public function getTagsSpecial(): ObjectStorage
139 | {
140 | return $this->tagsSpecial;
141 | }
142 |
143 | /**
144 | * @param ObjectStorage $tagsSpecial
145 | */
146 | public function setTagsSpecial(ObjectStorage $tagsSpecial): void
147 | {
148 | $this->tagsSpecial = $tagsSpecial;
149 | }
150 |
151 | public function addTagSpecial(Tag $tag): void
152 | {
153 | $this->tagsSpecial->attach($tag);
154 | }
155 |
156 | public function removeTagSpecial(Tag $tag): void
157 | {
158 | $this->tagsSpecial->detach($tag);
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/Classes/Domain/Model/Post.php:
--------------------------------------------------------------------------------
1 | 'StringLength', 'options' => ['minimum' => 3, 'maximum' => 50]])]
34 | protected string $title = '';
35 |
36 | /**
37 | * @var \DateTime
38 | */
39 | protected ?\DateTime $date = null;
40 |
41 | /**
42 | * @var Person
43 | */
44 | protected ?Person $author = null;
45 | protected ?Person $secondAuthor = null;
46 | protected ?Person $reviewer = null;
47 |
48 | #[Validate(['validator' => 'StringLength', 'options' => ['minimum' => 3]])]
49 | protected string $content = '';
50 |
51 | /**
52 | * @var ?ObjectStorage
53 | */
54 | public ?ObjectStorage $tags = null;
55 |
56 | /**
57 | * @var ?ObjectStorage
58 | */
59 | public ?ObjectStorage $categories = null;
60 |
61 | /**
62 | * @var ?ObjectStorage
63 | */
64 | #[Lazy()]
65 | #[Cascade(['value' => 'remove'])]
66 | public ?ObjectStorage $comments = null;
67 |
68 | /**
69 | * @var ?ObjectStorage
70 | */
71 | #[Lazy()]
72 | public ?ObjectStorage $relatedPosts = null;
73 |
74 | /**
75 | * 1:1 optional relation
76 | */
77 | #[Cascade(['value' => 'remove'])]
78 | public ?Info $additionalName = null;
79 |
80 | /**
81 | * 1:1 optional relation
82 | */
83 | #[Cascade(['value' => 'remove'])]
84 | protected ?Info $additionalInfo = null;
85 |
86 | /**
87 | * 1:n relation stored as CSV value
88 | * @var ?ObjectStorage
89 | */
90 | #[Lazy()]
91 | public ?ObjectStorage $additionalComments = null;
92 |
93 | public function __construct()
94 | {
95 | $this->initializeObject();
96 | }
97 |
98 | /**
99 | * Initializes all ObjectStorage properties when model is reconstructed from DB (where __construct is not called)
100 | */
101 | public function initializeObject(): void
102 | {
103 | $this->tags ??= new ObjectStorage();
104 | $this->categories ??= new ObjectStorage();
105 | $this->comments ??= new ObjectStorage();
106 | $this->relatedPosts ??= new ObjectStorage();
107 | $this->additionalComments ??= new ObjectStorage();
108 | }
109 |
110 | /**
111 | * Set one or more Tag objects
112 | *
113 | * @param ObjectStorage $tags
114 | */
115 | public function setTags(ObjectStorage $tags): void
116 | {
117 | $this->tags = $tags;
118 | }
119 |
120 | public function addTag(Tag $tag): void
121 | {
122 | $this->tags?->attach($tag);
123 | }
124 |
125 | public function removeTag(Tag $tag): void
126 | {
127 | $this->tags?->detach($tag);
128 | }
129 |
130 | public function removeAllTags(): void
131 | {
132 | $this->tags = new ObjectStorage();
133 | }
134 |
135 | /**
136 | * Getter for tags, A storage holding objects
137 | * Note: We return a clone of the tags because they must not be modified as they are Value Objects
138 | *
139 | * @return ObjectStorage
140 | */
141 | public function getTags(): ?ObjectStorage
142 | {
143 | return clone $this->tags;
144 | }
145 |
146 | /**
147 | * Add category to a post
148 | */
149 | public function addCategory(Category $category): void
150 | {
151 | $this->categories?->attach($category);
152 | }
153 |
154 | /**
155 | * Set categories
156 | *
157 | * @param ObjectStorage $categories
158 | */
159 | public function setCategories(ObjectStorage $categories): void
160 | {
161 | $this->categories = $categories;
162 | }
163 |
164 | /**
165 | * Get categories
166 | *
167 | * @return ?ObjectStorage
168 | */
169 | public function getCategories(): ?ObjectStorage
170 | {
171 | return $this->categories;
172 | }
173 |
174 | /**
175 | * Remove category from post
176 | */
177 | public function removeCategory(Category $category): void
178 | {
179 | $this->categories?->detach($category);
180 | }
181 |
182 | /**
183 | * Sets the author for this post
184 | */
185 | public function setAuthor(Person $author): void
186 | {
187 | $this->author = $author;
188 | }
189 |
190 | /**
191 | * Getter for author
192 | */
193 | public function getAuthor(): ?Person
194 | {
195 | return $this->author;
196 | }
197 |
198 | public function getSecondAuthor(): ?Person
199 | {
200 | return $this->secondAuthor;
201 | }
202 |
203 | public function setSecondAuthor(Person $secondAuthor): void
204 | {
205 | $this->secondAuthor = $secondAuthor;
206 | }
207 |
208 | public function getReviewer(): ?Person
209 | {
210 | return $this->reviewer;
211 | }
212 |
213 | public function setReviewer(Person $reviewer): void
214 | {
215 | $this->reviewer = $reviewer;
216 | }
217 |
218 | /**
219 | * Set the comments to this post, an Object Storage of related Comment instances
220 | *
221 | * @param ObjectStorage $comments
222 | */
223 | public function setComments(ObjectStorage $comments): void
224 | {
225 | $this->comments = $comments;
226 | }
227 |
228 | /**
229 | * Adds a comment to this post
230 | */
231 | public function addComment(Comment $commentToAdd): void
232 | {
233 | $this->comments?->attach($commentToAdd);
234 | }
235 |
236 | /**
237 | * Removes Comment from this post and deletes it due to annotation `@Cascade("remove")`
238 | */
239 | public function removeComment(Comment $commentToDelete): void
240 | {
241 | $this->comments?->detach($commentToDelete);
242 | }
243 |
244 | /**
245 | * @TODO: explain what's done in the method an why
246 | */
247 | public function removeAllComments(): void
248 | {
249 | $comments = clone $this->comments;
250 | $this->comments->removeAll($comments);
251 | }
252 |
253 | /**
254 | * Returns the comments to this post
255 | *
256 | * @return ObjectStorage
257 | */
258 | public function getComments(): ?ObjectStorage
259 | {
260 | return $this->comments;
261 | }
262 |
263 | /**
264 | * Set the related posts with ObjectStorage containing the instances of relatedPosts
265 | *
266 | * @param ObjectStorage $relatedPosts
267 | */
268 | public function setRelatedPosts(ObjectStorage $relatedPosts): void
269 | {
270 | $this->relatedPosts = $relatedPosts;
271 | }
272 |
273 | public function addRelatedPost(Post $post): void
274 | {
275 | $this->relatedPosts?->attach($post);
276 | }
277 |
278 | public function removeAllRelatedPosts(): void
279 | {
280 | $relatedPosts = clone $this->relatedPosts;
281 | $this->relatedPosts->removeAll($relatedPosts);
282 | }
283 |
284 | /**
285 | * Returns the related posts
286 | *
287 | * @return ObjectStorage
288 | */
289 | public function getRelatedPosts(): ?ObjectStorage
290 | {
291 | return $this->relatedPosts;
292 | }
293 |
294 | /**
295 | * @return ObjectStorage
296 | */
297 | public function getAdditionalComments(): ?ObjectStorage
298 | {
299 | return $this->additionalComments;
300 | }
301 |
302 | /**
303 | * @param ObjectStorage $additionalComments
304 | */
305 | public function setAdditionalComments(ObjectStorage $additionalComments): void
306 | {
307 | $this->additionalComments = $additionalComments;
308 | }
309 |
310 | public function addAdditionalComment(Comment $comment): void
311 | {
312 | $this->additionalComments?->attach($comment);
313 | }
314 |
315 | /**
316 | * Remove all additional Comments
317 | */
318 | public function removeAllAdditionalComments(): void
319 | {
320 | $comments = clone $this->additionalComments;
321 | $this->additionalComments->removeAll($comments);
322 | }
323 |
324 | public function removeAdditionalComment(Comment $comment): void
325 | {
326 | $this->additionalComments?->detach($comment);
327 | }
328 |
329 | /**
330 | * @return Blog
331 | */
332 | public function getBlog(): Blog
333 | {
334 | return $this->blog;
335 | }
336 |
337 | public function setBlog(Blog $blog): void
338 | {
339 | $this->blog = $blog;
340 | }
341 |
342 | /**
343 | * @return string
344 | */
345 | public function getTitle(): string
346 | {
347 | return $this->title;
348 | }
349 |
350 | public function setTitle(string $title): void
351 | {
352 | $this->title = $title;
353 | }
354 |
355 | public function getDate(): ?\DateTime
356 | {
357 | return $this->date;
358 | }
359 |
360 | public function setDate(\DateTime $date): void
361 | {
362 | $this->date = $date;
363 | }
364 |
365 | /**
366 | * @return string
367 | */
368 | public function getContent(): string
369 | {
370 | return $this->content;
371 | }
372 |
373 | public function setContent(string $content): void
374 | {
375 | $this->content = $content;
376 | }
377 |
378 | /**
379 | * @return Info|null
380 | */
381 | public function getAdditionalName(): ?Info
382 | {
383 | return $this->additionalName;
384 | }
385 |
386 | public function setAdditionalName(?Info $additionalName): void
387 | {
388 | $this->additionalName = $additionalName;
389 | }
390 |
391 | public function getAdditionalInfo(): ?Info
392 | {
393 | return $this->additionalInfo;
394 | }
395 |
396 | public function setAdditionalInfo(?Info $additionalInfo): void
397 | {
398 | $this->additionalInfo = $additionalInfo;
399 | }
400 |
401 | /**
402 | * Returns this post as a formatted string
403 | */
404 | public function __toString(): string
405 | {
406 | return $this->title . chr(10) .
407 | ' written on ' . $this->date->format('Y-m-d') . chr(10) .
408 | ' by ' . $this->author->getFullName() . chr(10) .
409 | wordwrap($this->content, 70, chr(10)) . chr(10) .
410 | implode(', ', $this->tags->toArray());
411 | }
412 | }
413 |
--------------------------------------------------------------------------------
/Classes/Domain/Model/Tag.php:
--------------------------------------------------------------------------------
1 | name;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Classes/Domain/Repository/AdministratorRepository.php:
--------------------------------------------------------------------------------
1 |
26 | */
27 | class AdministratorRepository extends Repository {}
28 |
--------------------------------------------------------------------------------
/Classes/Domain/Repository/BlogRepository.php:
--------------------------------------------------------------------------------
1 |
27 | */
28 | class BlogRepository extends Repository
29 | {
30 | protected $defaultOrderings = [
31 | 'crdate' => QueryInterface::ORDER_DESCENDING,
32 | 'uid' => QueryInterface::ORDER_DESCENDING,
33 | ];
34 | }
35 |
--------------------------------------------------------------------------------
/Classes/Domain/Repository/CommentRepository.php:
--------------------------------------------------------------------------------
1 |
27 | */
28 | class CommentRepository extends Repository
29 | {
30 | protected $defaultOrderings = ['date' => QueryInterface::ORDER_DESCENDING];
31 |
32 | public function initializeObject(): void
33 | {
34 | $querySettings = $this->createQuery()->getQuerySettings();
35 | // Show comments from all pages
36 | $querySettings->setRespectStoragePage(false);
37 | $this->setDefaultQuerySettings($querySettings);
38 | }
39 |
40 | /**
41 | * @return QueryResultInterface
42 | */
43 | public function findAllIgnoreEnableFields(): QueryResultInterface
44 | {
45 | $query = $this->createQuery();
46 | $query->getQuerySettings()->setIgnoreEnableFields(true);
47 | return $query->execute();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Classes/Domain/Repository/PersonRepository.php:
--------------------------------------------------------------------------------
1 |
26 | */
27 | class PersonRepository extends Repository
28 | {
29 | protected $defaultOrderings = ['lastname' => QueryInterface::ORDER_ASCENDING];
30 | }
31 |
--------------------------------------------------------------------------------
/Classes/Domain/Repository/PostRepository.php:
--------------------------------------------------------------------------------
1 |
30 | *
31 | * @method Post findByUid($uid)
32 | */
33 | class PostRepository extends Repository
34 | {
35 | protected $defaultOrderings = ['date' => QueryInterface::ORDER_DESCENDING];
36 |
37 | /**
38 | * Finds all posts by the specified blog
39 | *
40 | * @return QueryResultInterface
41 | */
42 | public function findAllByBlog(Blog $blog): QueryResultInterface
43 | {
44 | $query = $this->createQuery();
45 | return $query
46 | ->matching(
47 | $query->equals('blog', $blog),
48 | )
49 | ->execute();
50 | }
51 |
52 | /**
53 | * Finds posts by the specified tag and blog
54 | *
55 | * @param Blog $blog The blog the post must refer to
56 | * @return QueryResultInterface
57 | */
58 | public function findByTagAndBlog(
59 | string $tag,
60 | Blog $blog,
61 | ): QueryResultInterface {
62 | $query = $this->createQuery();
63 | return $query
64 | ->matching(
65 | $query->logicalAnd(
66 | $query->equals('blog', $blog),
67 | $query->equals('tags.name', $tag),
68 | ),
69 | )
70 | ->execute();
71 | }
72 |
73 | /**
74 | * Finds all remaining posts of the blog
75 | *
76 | * @param Post $post The reference post
77 | * @return QueryResultInterface
78 | */
79 | public function findRemaining(Post $post): QueryResultInterface
80 | {
81 | $blog = $post->getBlog();
82 | $query = $this->createQuery();
83 | return $query
84 | ->matching(
85 | $query->logicalAnd(
86 | $query->equals('blog', $blog),
87 | $query->logicalNot(
88 | $query->equals('uid', $post->getUid()),
89 | ),
90 | ),
91 | )
92 | ->execute();
93 | }
94 |
95 | /**
96 | * Finds the previous of the given post
97 | *
98 | * @param Post $post The reference post
99 | */
100 | public function findPrevious(Post $post): ?Post
101 | {
102 | $query = $this->createQuery();
103 | return $query
104 | ->matching(
105 | $query->lessThan('date', $post->getDate()),
106 | )
107 | ->execute()
108 | ->getFirst();
109 | }
110 |
111 | /**
112 | * Finds the post next to the given post
113 | *
114 | * @param Post $post The reference post
115 | */
116 | public function findNext(Post $post): ?Post
117 | {
118 | $query = $this->createQuery();
119 | return $query
120 | ->matching(
121 | $query->greaterThan('date', $post->getDate()),
122 | )
123 | ->execute()
124 | ->getFirst();
125 | }
126 |
127 | /**
128 | * Finds most recent posts by the specified blog
129 | *
130 | * @param Blog $blog The blog the post must refer to
131 | * @param int $limit The number of posts to return at max
132 | * @return QueryResultInterface
133 | */
134 | public function findRecentByBlog(
135 | Blog $blog,
136 | int $limit = 5,
137 | ): QueryResultInterface {
138 | $query = $this->createQuery();
139 | return $query
140 | ->matching(
141 | $query->equals('blog', $blog),
142 | )
143 | ->setLimit((int)$limit)
144 | ->execute();
145 | }
146 |
147 | /**
148 | * @return QueryResultInterface
149 | * @throws InvalidQueryException
150 | */
151 | public function findByCategory(int $categoryUid): QueryResultInterface
152 | {
153 | $query = $this->createQuery();
154 | return $query
155 | ->matching(
156 | $query->contains('categories', $categoryUid),
157 | )
158 | ->execute();
159 | }
160 |
161 | /**
162 | * @param int[] $uids
163 | * @return QueryResultInterface
164 | * @throws InvalidQueryException
165 | */
166 | public function findAllSortedByCategory(array $uids): QueryResultInterface
167 | {
168 | $q = $this->createQuery();
169 | $q->matching($q->in('uid', $uids));
170 | $q->setOrderings([
171 | 'categories.title' => QueryInterface::ORDER_ASCENDING,
172 | 'uid' => QueryInterface::ORDER_ASCENDING,
173 | ]);
174 | return $q->execute();
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/Classes/Domain/Validator/BlogValidator.php:
--------------------------------------------------------------------------------
1 | addError($errorString, 1297418975);
39 | }
40 | if (!$this->blogValidationService->isBlogCategoryCountValid($value)) {
41 | $errorString = LocalizationUtility::translate(
42 | 'error.Blog.tooManyCategories',
43 | 'BlogExample',
44 | );
45 | // Add the error to the property if it is specific to one property
46 | $this->addErrorForProperty('categories', $errorString, 1297418976);
47 | }
48 | if (!$this->blogValidationService->isBlogSubtitleValid($value)) {
49 | $errorString = LocalizationUtility::translate(
50 | 'error.Blog.invalidSubTitle',
51 | 'BlogExample',
52 | );
53 | // Add the error directly if it takes several properties into account
54 | $this->addError($errorString, 1297418974);
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Classes/Domain/Validator/PostValidator.php:
--------------------------------------------------------------------------------
1 | postValidationService->isTitleValid($value)) {
41 | $error = new Error('Title custom validation failed', 1480872650);
42 | $this->result->forProperty('title')->addError($error);
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Classes/Domain/Validator/TitleValidator.php:
--------------------------------------------------------------------------------
1 | addError($errorString, 1297418976);
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Classes/Exception/NoBlogAdminAccessException.php:
--------------------------------------------------------------------------------
1 | expressionLanguageVariables = [
20 | 'blogConfiguration' => $configuration->get('blog_example'),
21 | ];
22 | } catch (ExtensionConfigurationExtensionNotConfiguredException | ExtensionConfigurationPathDoesNotExistException) {
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Classes/PageTitle/BlogPageTitleProvider.php:
--------------------------------------------------------------------------------
1 | title = $title;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Classes/Property/TypeConverters/HiddenCommentConverter.php:
--------------------------------------------------------------------------------
1 | persistenceManager->createQueryForType($targetType);
35 | $query
36 | ->getQuerySettings()
37 | ->setRespectStoragePage(false)
38 | ->setIgnoreEnableFields(true)
39 | ->setEnableFieldsToBeIgnored(['disabled']);
40 | $object = $query->matching($query->equals('uid', $identity))->execute()->getFirst();
41 | } else {
42 | throw new InvalidSourceException(
43 | 'The identity property "' . $identity . '" is no UID.',
44 | 1641904861,
45 | );
46 | }
47 |
48 | if ($object === null) {
49 | throw new TargetNotFoundException(
50 | sprintf('Object of type %s with identity "%s" not found.', $targetType, print_r($identity, true)),
51 | 1641904896,
52 | );
53 | }
54 |
55 | return $object;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Classes/Service/BlogFactory.php:
--------------------------------------------------------------------------------
1 | setTitle('Blog #' . $blogNumber);
45 | $blog->description = 'A blog about TYPO3 extension development.';
46 |
47 | // create author
48 | $author = new Person('Stephen', 'Smith', 'foo.bar@example.com');
49 |
50 | // create administrator
51 | $administrator = new Administrator();
52 | $administrator->name = 'John Doe';
53 | $administrator->email = 'john.doe@example.com';
54 | $blog->administrator = $administrator;
55 |
56 | // create sample posts
57 | for ($postNumber = 1; $postNumber < 6; $postNumber++) {
58 | // create post
59 | $post = new Post();
60 | $post->setTitle('The ' . $postNumber . '. post of blog #' . $blogNumber);
61 | $post->setAuthor($author);
62 | $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.');
63 |
64 | // create comments
65 | $comment = new Comment();
66 | $comment->setDate(new \DateTime());
67 | $comment->setAuthor('Peter Pan');
68 | $comment->setEmail('peter.pan@example.com');
69 | $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.');
70 | $post->addComment($comment);
71 |
72 | $comment = new Comment();
73 | $comment->setDate(new \DateTime('2009-03-19 23:44'));
74 | $comment->setAuthor('John Smith');
75 | $comment->setEmail('john@matrix.org');
76 | $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.');
77 | $post->addComment($comment);
78 |
79 | // create some random tags
80 | if (random_int(0, 1) > 0) {
81 | $tag = new Tag('MVC');
82 | $post->addTag($tag);
83 | }
84 | if (random_int(0, 1) > 0) {
85 | $tag = new Tag('Domain Driven Design');
86 | $post->addTag($tag);
87 | }
88 | if (random_int(0, 1) > 0) {
89 | $tag = new Tag('TYPO3');
90 | $post->addTag($tag);
91 | }
92 | // add the post to the current blog
93 | $blog->addPost($post);
94 | $post->setBlog($blog);
95 | }
96 | return $blog;
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/Classes/Service/BlogValidationService.php:
--------------------------------------------------------------------------------
1 | maxCategoryCount = $maxCategoryCount;
29 | }
30 |
31 | public function isBlogCategoryCountValid(Blog $blog): bool
32 | {
33 | return $blog->getCategories()->count() <= $this->maxCategoryCount;
34 | }
35 |
36 | public function isBlogSubtitleValid(Blog $blog): bool
37 | {
38 | return strtolower($blog->getTitle()) !== strtolower($blog->subtitle ?? '');
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Classes/Service/PostValidationService.php:
--------------------------------------------------------------------------------
1 | getTitle(), $this->forbiddenTitles);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Classes/Upgrades/MigratePluginsToContentElementsUpgradeWizard.php:
--------------------------------------------------------------------------------
1 | getQueryBuilder();
36 |
37 | $queryBuilder
38 | ->update('tt_content')
39 | ->set('CType', $pluginName)
40 | ->set('list_type', '')
41 | ->where(
42 | $queryBuilder->expr()->eq(
43 | 'CType',
44 | $queryBuilder->createNamedParameter('list'),
45 | ),
46 | $queryBuilder->expr()->eq(
47 | 'list_type',
48 | $queryBuilder->createNamedParameter($pluginName),
49 | ),
50 | )
51 | ->executeStatement();
52 | }
53 |
54 | return true;
55 | }
56 |
57 | public function updateNecessary(): bool
58 | {
59 | $queryBuilder = $this->getQueryBuilder();
60 |
61 | $orConstraints = [];
62 | foreach (self::PLUGINS as $pluginName) {
63 | $orConstraints[] = $queryBuilder->expr()->eq(
64 | 'list_type',
65 | $queryBuilder->createNamedParameter($pluginName),
66 | );
67 | }
68 |
69 | return (bool)$queryBuilder
70 | ->count('*')
71 | ->from('tt_content')
72 | ->where(
73 | $queryBuilder->expr()->eq(
74 | 'CType',
75 | $queryBuilder->createNamedParameter('list'),
76 | ),
77 | $queryBuilder->expr()->or(...$orConstraints),
78 | )
79 | ->executeQuery()
80 | ->fetchOne();
81 | }
82 |
83 | private function getQueryBuilder(): QueryBuilder
84 | {
85 | $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
86 | ->getQueryBuilderForTable('tt_content');
87 |
88 | $queryBuilder
89 | ->getRestrictions()
90 | ->removeAll()
91 | ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
92 |
93 | return $queryBuilder;
94 | }
95 |
96 | public function getPrerequisites(): array
97 | {
98 | return [];
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/Classes/Upgrades/PluginUpgradeWizard.php:
--------------------------------------------------------------------------------
1 | connectionPool->getQueryBuilderForTable('tt_content');
31 | $result = $queryBuilder
32 | ->update('tt_content')
33 | ->where(
34 | $queryBuilder->expr()->eq('list_type', $queryBuilder->createNamedParameter(self::OLD_LIST_TYPE)),
35 | )
36 | ->set('list_type', self::NEW_LIST_TYPE)
37 | ->executeStatement();
38 | return $result > 0;
39 | }
40 |
41 | public function updateNecessary(): bool
42 | {
43 | $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tt_content');
44 | $count = $queryBuilder
45 | ->count('uid')
46 | ->from('tt_content')
47 | ->where(
48 | $queryBuilder->expr()->eq('list_type', $queryBuilder->createNamedParameter(self::OLD_LIST_TYPE)),
49 | )
50 | ->executeQuery()
51 | ->fetchOne();
52 | return is_int($count) && $count > 0;
53 | }
54 |
55 | public function getPrerequisites(): array
56 | {
57 | return [];
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Classes/ViewHelpers/GravatarViewHelper.php:
--------------------------------------------------------------------------------
1 |
30 | *
35 | *
36 | *
37 | *
38 | *
39 | */
40 | class GravatarViewHelper extends AbstractTagBasedViewHelper
41 | {
42 | /**
43 | * @var string
44 | */
45 | protected $tagName = 'img';
46 | public function __construct(private readonly UriFactory $uriFactory)
47 | {
48 | parent::__construct();
49 | }
50 |
51 | public function initializeArguments(): void
52 | {
53 | $this->registerArgument('emailAddress', 'string', '', true);
54 | $this->registerArgument('defaultImageUri', 'string', '');
55 | $this->registerArgument('size', 'int', '');
56 | }
57 |
58 | public function render(): string
59 | {
60 | $gravatarUri = $this->uriFactory->createUri(
61 | 'https://gravatar.com/avatar/' . md5($this->arguments['emailAddress']),
62 | );
63 |
64 | $queryArguments = [];
65 | if ($this->arguments['defaultImageUri'] !== null) {
66 | $queryArguments['d'] = urlencode($this->arguments['defaultImageUri']);
67 | }
68 |
69 | if ($this->arguments['size'] !== null) {
70 | $queryArguments['s'] = MathUtility::forceIntegerInRange((int)$this->arguments['size'], 1, 2048);
71 | }
72 |
73 | $this->tag->addAttribute(
74 | 'src',
75 | (string)$gravatarUri->withQuery(HttpUtility::buildQueryString($queryArguments, '', true)),
76 | );
77 |
78 | return $this->tag->render();
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/Configuration/Backend/Modules.php:
--------------------------------------------------------------------------------
1 | [
10 | 'parent' => 'web',
11 | 'position' => ['after' => 'web_info'],
12 | 'access' => 'user',
13 | 'workspaces' => 'live',
14 | 'path' => '/module/page/blog_example',
15 | 'labels' => 'LLL:EXT:blog_example/Resources/Private/Language/Module/locallang_mod.xlf',
16 | 'extensionName' => 'BlogExample',
17 | 'controllerActions' => [
18 | BackendController::class => [
19 | 'index',
20 | 'deleteAll',
21 | 'deleteBlog',
22 | 'populate',
23 | 'showBlog',
24 | 'showPost',
25 | 'showAllComments',
26 | ],
27 | ],
28 | ],
29 | ];
30 |
--------------------------------------------------------------------------------
/Configuration/ExpressionLanguage.php:
--------------------------------------------------------------------------------
1 | [
11 | ExtensionConfigurationProvider::class,
12 | ],
13 | ];
14 |
--------------------------------------------------------------------------------
/Configuration/Extbase/Persistence/Classes.php:
--------------------------------------------------------------------------------
1 | [
12 | 'tableName' => 'fe_users',
13 | 'recordType' => Administrator::class,
14 | 'properties' => [
15 | 'administratorName' => [
16 | 'fieldName' => 'username',
17 | ],
18 | ],
19 | ],
20 | FrontendUserGroup::class => [
21 | 'tableName' => 'fe_groups',
22 | ],
23 | Blog::class => [
24 | 'tableName' => 'tx_blogexample_domain_model_blog',
25 | 'properties' => [
26 | 'categories' => [
27 | 'fieldName' => 'category',
28 | ],
29 | ],
30 | ],
31 | Post::class => [
32 | 'tableName' => 'tx_blogexample_domain_model_post',
33 | 'properties' => [
34 | 'categories' => [
35 | 'fieldName' => 'category',
36 | ],
37 | ],
38 | ],
39 | ];
40 |
--------------------------------------------------------------------------------
/Configuration/FlexForms/PluginSettings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Options
6 | array
7 |
8 |
9 | Max. number of items to display per page
10 |
11 | number
12 | 2
13 | 3
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/Configuration/Icons.php:
--------------------------------------------------------------------------------
1 | [
9 | // icon provider class
10 | 'provider' => SvgIconProvider::class,
11 | // the source SVG for the SvgIconProvider
12 | 'source' => 'EXT:blog_example/Resources/Public/Icons/Extension.svg',
13 | ],
14 | 'blogexample_blog' => [
15 | 'provider' => BitmapIconProvider::class,
16 | 'source' => 'EXT:blog_example/Resources/Public/Icons/icon_tx_blogexample_domain_model_blog.gif',
17 | ],
18 | 'blogexample_comment' => [
19 | 'provider' => BitmapIconProvider::class,
20 | 'source' => 'EXT:blog_example/Resources/Public/Icons/icon_tx_blogexample_domain_model_comment.gif',
21 | ],
22 | 'blogexample_person' => [
23 | 'provider' => BitmapIconProvider::class,
24 | 'source' => 'EXT:blog_example/Resources/Public/Icons/icon_tx_blogexample_domain_model_person.gif',
25 | ],
26 | 'blogexample_post' => [
27 | 'provider' => BitmapIconProvider::class,
28 | 'source' => 'EXT:blog_example/Resources/Public/Icons/icon_tx_blogexample_domain_model_post.gif',
29 | ],
30 | 'blogexample_tag' => [
31 | 'provider' => BitmapIconProvider::class,
32 | 'source' => 'EXT:blog_example/Resources/Public/Icons/icon_tx_blogexample_domain_model_tag.gif',
33 | ],
34 | ];
35 |
--------------------------------------------------------------------------------
/Configuration/Services.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | _defaults:
3 | autowire: true
4 | autoconfigure: true
5 | public: false
6 |
7 | T3docs\BlogExample\:
8 | resource: '../Classes/*'
9 | exclude: '../Classes/Domain/Model/*'
10 | T3docs\BlogExample\Property\TypeConverters\HiddenCommentConverter:
11 | tags:
12 | - name: extbase.type_converter
13 | priority: 2
14 | target: T3docs\BlogExample\Domain\Model\Comment
15 | sources: int,string
16 |
--------------------------------------------------------------------------------
/Configuration/Sets/BlogExample/config.yaml:
--------------------------------------------------------------------------------
1 | name: t3docs/blog-example
2 | label: Blog example set
3 |
--------------------------------------------------------------------------------
/Configuration/Sets/BlogExample/settings.definitions.yaml:
--------------------------------------------------------------------------------
1 | categories:
2 | BlogExample:
3 | label: 'Blog Example'
4 | BlogExample.templates:
5 | label: 'Templates'
6 | parent: BlogExample
7 | BlogExample.pages:
8 | label: 'Pages'
9 | parent: BlogExample
10 | BlogExample.layout:
11 | label: 'Layout'
12 | parent: BlogExample
13 | BlogExample.settings:
14 | label: 'Settings'
15 | parent: BlogExample
16 |
17 | settings:
18 | blogExample.templateRootPath:
19 | label: 'Templates'
20 | category: BlogExample.templates
21 | description: 'Path to template root'
22 | type: string
23 | default: 'EXT:blog_example/Resources/Private/Templates/'
24 | blogExample.partialRootPath:
25 | label: 'Partials'
26 | category: BlogExample.templates
27 | description: 'Path to partial root'
28 | type: string
29 | default: 'EXT:blog_example/Resources/Private/Partials/'
30 | blogExample.layoutRootPath:
31 | label: 'Layouts'
32 | category: BlogExample.templates
33 | description: 'Path to layout root'
34 | type: string
35 | default: 'EXT:blog_example/Resources/Private/Layouts/'
36 | blogExample.storagePid:
37 | label: 'Default storage PID'
38 | category: BlogExample.pages
39 | description: 'Folder that stores all blogs, blog-posts and comments'
40 | type: int
41 | default: 0
42 | blogExample.editorUsergroupUid:
43 | label: 'Frontend admin user group UID'
44 | category: BlogExample.settings
45 | description: 'Enter the uid of the front end user group that should be allowed to edit blogs and post in the frontend in the admin plugin. '
46 | type: int
47 | default: 0
48 | blogExample.postsPerPage:
49 | label: 'Posts per page'
50 | category: BlogExample.settings
51 | description: 'How many posts should be displayed per page of pagination?'
52 | type: int
53 | default: 3
54 | blogExample.defaultGravator:
55 | label: 'Default gravator icon'
56 | category: BlogExample.layout
57 | description: 'This icon is displayed for a comment if the email address of the commentary is not registered. '
58 | type: string
59 | default: 'EXT:blog_example/Resources/Public/Icons/default_gravatar.gif'
60 |
--------------------------------------------------------------------------------
/Configuration/Sets/BlogExample/setup.typoscript:
--------------------------------------------------------------------------------
1 | # Plugin configuration
2 | plugin.tx_blogexample {
3 | settings {
4 | postsPerPage = {$blogExample.postsPerPage}
5 | editorUsergroupUid = {$blogExample.editorUsergroupUid}
6 | defaultGravator = {$blogExample.defaultGravator}
7 | }
8 |
9 | persistence {
10 | storagePid = {$blogExample.storagePid}
11 | }
12 |
13 | view {
14 | templateRootPaths.10 = {$blogExample.templateRootPath}
15 | partialRootPaths.10 = {$blogExample.partialRootPath}
16 | layoutRootPaths.10 = {$blogExample.layoutRootPath}
17 | defaultPid = auto
18 | }
19 |
20 | # This is an example how to modify the translation
21 | _LOCAL_LANG {
22 | default {
23 | someUnusedKey = foo
24 | }
25 | }
26 | }
27 |
28 | # Make postlist available as lib.blog_example_post_list
29 |
30 | lib.blog_example_post_list < tt_content.list.20.blogexample_postlist
31 |
32 | # Configure page title provider for blog post indexAction and showAction
33 | config {
34 | pageTitleProviders {
35 | blogExample {
36 | provider = T3docs\BlogExample\PageTitle\BlogPageTitleProvider
37 | before = record
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Configuration/Sets/DefaultStyles/config.yaml:
--------------------------------------------------------------------------------
1 | name: t3docs/blog-example-styles
2 | label: Blog example default styles
3 | dependencies:
4 | - t3docs/blog-example
5 |
--------------------------------------------------------------------------------
/Configuration/Sets/DefaultStyles/setup.typoscript:
--------------------------------------------------------------------------------
1 | # Include BlogExample default styles
2 |
3 | page.includeCSS.tx_blogexample = EXT:blog_example/Resources/Public/Css/BlogExample.css
4 |
--------------------------------------------------------------------------------
/Configuration/Sets/RssFeed/config.yaml:
--------------------------------------------------------------------------------
1 | name: t3docs/blog-example-rss
2 | label: Blog example RSS feed
3 | dependencies:
4 | - t3docs/blog-example
5 |
--------------------------------------------------------------------------------
/Configuration/Sets/RssFeed/constants.typoscript:
--------------------------------------------------------------------------------
1 | plugin.tx_blogexample {
2 | settings {
3 | # cat=plugin.tx_blogexample/a; type=int+; label=Rss page type:If the default RSS page typenum (778) conflicts with your setup, you can override this setting here
4 | rssPageType = 778
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Configuration/Sets/RssFeed/settings.definitions.yaml:
--------------------------------------------------------------------------------
1 | categories:
2 | BlogExample.rss:
3 | label: 'RSS feed'
4 | parent: BlogExample
5 |
6 | settings:
7 | blogExampleRss.page_type:
8 | label: 'RSS page type'
9 | category: BlogExample.rss
10 | description: 'If the default RSS page type number (778) conflicts with your setup, you can override this setting here. '
11 | type: int
12 | default: 778
13 | blogExampleRss.title:
14 | label: 'Title of the RSS feed'
15 | category: BlogExample.rss
16 | description: 'Path to template root'
17 | type: string
18 | default: 'The RSS feed'
19 |
--------------------------------------------------------------------------------
/Configuration/Sets/RssFeed/setup.typoscript:
--------------------------------------------------------------------------------
1 | plugin.tx_blogexample {
2 | settings {
3 | rss {
4 | channel {
5 | typeNum = {$blogExampleRss.page_type}
6 | title = {$blogExampleRss.title}
7 | }
8 | }
9 | }
10 | }
11 | # RSS rendering
12 | tx_blogexample_rss = PAGE
13 | tx_blogexample_rss {
14 | typeNum = {$blogExampleRss.page_type}
15 | 10 < tt_content.blogexample_postlistrss
16 |
17 | config {
18 | disableAllHeaderCode = 1
19 | xhtml_cleaning = none
20 | admPanel = 0
21 | debug = 0
22 | disablePrefixComment = 1
23 | metaCharset = utf-8
24 | additionalHeaders.10.header = Content-Type:application/rss+xml;charset=utf-8
25 | linkVars >
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Configuration/TCA/Overrides/fe_users.php:
--------------------------------------------------------------------------------
1 | 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:fe_users.tx_extbase_type.administrator',
13 | 'value' => Administrator::class,
14 | ];
15 | }
16 |
--------------------------------------------------------------------------------
/Configuration/TCA/Overrides/tt_content.php:
--------------------------------------------------------------------------------
1 | [
5 | 'title' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_blog',
6 | 'label' => 'title',
7 | 'tstamp' => 'tstamp',
8 | 'crdate' => 'crdate',
9 | 'versioningWS' => true,
10 | 'origUid' => 't3_origuid',
11 | 'transOrigPointerField' => 'l10n_parent',
12 | 'transOrigDiffSourceField' => 'l10n_diffsource',
13 | 'languageField' => 'sys_language_uid',
14 | 'translationSource' => 'l10n_source',
15 | 'delete' => 'deleted',
16 | 'enablecolumns' => [
17 | 'disabled' => 'hidden',
18 | 'fe_group' => 'fe_group',
19 | 'starttime' => 'starttime',
20 | 'endtime' => 'endtime',
21 | ],
22 | 'iconfile' => 'EXT:blog_example/Resources/Public/Icons/icon_tx_blogexample_domain_model_blog.gif',
23 | ],
24 | 'columns' => [
25 | 'title' => [
26 | 'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_blog.title',
27 | 'config' => [
28 | 'type' => 'input',
29 | 'size' => 20,
30 | 'eval' => 'trim',
31 | 'required' => true,
32 | 'max' => 256,
33 | ],
34 | ],
35 | 'subtitle' => [
36 | 'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_blog.subtitle',
37 | 'config' => [
38 | 'type' => 'input',
39 | 'size' => 20,
40 | 'eval' => 'trim',
41 | 'max' => 256,
42 | ],
43 | ],
44 | 'description' => [
45 | 'exclude' => true,
46 | 'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_blog.description',
47 | 'config' => [
48 | 'type' => 'text',
49 | 'enableRichtext' => true,
50 | 'required' => true,
51 | ],
52 | ],
53 | 'logo' => [
54 | 'exclude' => true,
55 | 'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_blog.logo',
56 | 'config' => [
57 | 'type' => 'file',
58 | 'allowed' => 'common-image-types',
59 | 'appearance' => [
60 | 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference',
61 | ],
62 | ],
63 | ],
64 | 'posts' => [
65 | 'exclude' => true,
66 | 'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_blog.posts',
67 | 'config' => [
68 | 'type' => 'inline',
69 | 'foreign_table' => 'tx_blogexample_domain_model_post',
70 | 'foreign_field' => 'blog',
71 | 'foreign_sortby' => 'sorting',
72 | 'appearance' => [
73 | 'collapseAll' => 1,
74 | 'expandSingle' => 1,
75 | ],
76 | ],
77 | ],
78 | 'administrator' => [
79 | 'exclude' => true,
80 | 'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_blog.administrator',
81 | 'description' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_blog.administrator.description',
82 | 'config' => [
83 | 'type' => 'select',
84 | 'renderType' => 'selectSingle',
85 | 'foreign_table' => 'fe_users',
86 | 'foreign_table_where' => "AND fe_users.tx_extbase_type='T3docs\\\\BlogExample\\\\Domain\\\\Model\\\\Administrator'",
87 | 'items' => [
88 | ['label' => '--none--', 'value' => 0],
89 | ],
90 | 'default' => 0,
91 | 'fieldControl' => [
92 | 'editPopup' => [
93 | 'disabled' => false,
94 | ],
95 | 'addRecord' => [
96 | 'disabled' => false,
97 | 'options' => [
98 | 'setValue' => 'prepend',
99 | ],
100 | ],
101 | ],
102 | ],
103 | ],
104 | 'category' => [
105 | 'config' => [
106 | 'type' => 'category',
107 | ],
108 | ],
109 | ],
110 | 'types' => [
111 | '1' => ['showitem' => '
112 | --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:general,
113 | title, description, logo,
114 | --div--;LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_blog.posts,
115 | posts,
116 | --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:categories,
117 | category,
118 | --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access,
119 | administrator,
120 | --palette--;;paletteHidden,
121 | --palette--;;paletteAccess,
122 | --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language,
123 | --palette--;;paletteLanguage,
124 | --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:extended,
125 | '],
126 | ],
127 | 'palettes' => [
128 | 'paletteHidden' => [
129 | 'showitem' => 'hidden',
130 | ],
131 | 'paletteAccess' => [
132 | 'showitem' => '
133 | starttime, endtime,
134 | --linebreak--,
135 | fe_group',
136 | ],
137 | 'paletteLanguage' => [
138 | 'showitem' => 'sys_language_uid, l10n_parent',
139 | ],
140 | ],
141 | ];
142 |
--------------------------------------------------------------------------------
/Configuration/TCA/tx_blogexample_domain_model_comment.php:
--------------------------------------------------------------------------------
1 | [
5 | 'title' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_comment',
6 | 'label' => 'date',
7 | 'label_alt' => 'author',
8 | 'label_alt_force' => true,
9 | 'origUid' => 't3_origuid',
10 | 'tstamp' => 'tstamp',
11 | 'crdate' => 'crdate',
12 | 'delete' => 'deleted',
13 | 'enablecolumns' => [
14 | 'disabled' => 'hidden',
15 | ],
16 | 'iconfile' => 'EXT:blog_example/Resources/Public/Icons/icon_tx_blogexample_domain_model_comment.gif',
17 | ],
18 | 'columns' => [
19 | 'date' => [
20 | 'exclude' => true,
21 | 'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_comment.date',
22 | 'config' => [
23 | 'type' => 'datetime',
24 | 'dbType' => 'datetime',
25 | 'size' => 12,
26 | 'eval' => 'datetime',
27 | 'required' => true,
28 | 'default' => time(),
29 | ],
30 | ],
31 | 'author' => [
32 | 'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_comment.author',
33 | 'config' => [
34 | 'type' => 'input',
35 | 'size' => 20,
36 | 'eval' => 'trim',
37 | 'required' => true,
38 | 'max' => 256,
39 | ],
40 | ],
41 | 'email' => [
42 | 'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_comment.email',
43 | 'config' => [
44 | 'type' => 'input',
45 | 'size' => 20,
46 | 'eval' => 'trim',
47 | 'required' => true,
48 | 'max' => 256,
49 | ],
50 | ],
51 | 'content' => [
52 | 'exclude' => true,
53 | 'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_comment.content',
54 | 'config' => [
55 | 'type' => 'text',
56 | 'rows' => 30,
57 | 'cols' => 80,
58 | ],
59 | ],
60 | 'post' => [
61 | 'config' => [
62 | 'type' => 'passthrough',
63 | ],
64 | ],
65 | ],
66 | 'types' => [
67 | '1' => ['showitem' => 'hidden, date, author, email, content, '],
68 | ],
69 | 'palettes' => [
70 | '1' => ['showitem' => ''],
71 | ],
72 | ];
73 |
--------------------------------------------------------------------------------
/Configuration/TCA/tx_blogexample_domain_model_info.php:
--------------------------------------------------------------------------------
1 | [
5 | 'title' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_info',
6 | 'label' => 'name',
7 | 'tstamp' => 'tstamp',
8 | 'crdate' => 'crdate',
9 | 'versioningWS' => true,
10 | 'transOrigPointerField' => 'l10n_parent',
11 | 'transOrigDiffSourceField' => 'l10n_diffsource',
12 | 'languageField' => 'sys_language_uid',
13 | 'translationSource' => 'l10n_source',
14 | 'origUid' => 't3_origuid',
15 | 'delete' => 'deleted',
16 | 'sortby' => 'sorting',
17 | 'enablecolumns' => [
18 | 'disabled' => 'hidden',
19 | ],
20 | 'iconfile' => 'EXT:blog_example/Resources/Public/Icons/icon_tx_blogexample_domain_model_tag.gif',
21 | ],
22 | 'columns' => [
23 | 'name' => [
24 | 'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_info.name',
25 | 'config' => [
26 | 'type' => 'input',
27 | 'size' => 20,
28 | 'eval' => 'trim',
29 | 'required' => true,
30 | 'max' => 256,
31 | ],
32 | ],
33 | 'bodytext' => [
34 | 'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_info.bodytext',
35 | 'config' => [
36 | 'type' => 'text',
37 | 'enableRichtext' => true,
38 | ],
39 | ],
40 | 'post' => [
41 | 'config' => [
42 | 'type' => 'passthrough',
43 | ],
44 | ],
45 | ],
46 | 'types' => [
47 | 0 => ['showitem' => '
48 | --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:general,
49 | name, bodytext,
50 | --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access,
51 | hidden,
52 | --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language,
53 | --palette--;;paletteLanguage,
54 | --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:extended,
55 | '],
56 | ],
57 | 'palettes' => [
58 | 'paletteLanguage' => [
59 | 'showitem' => 'sys_language_uid, l10n_parent',
60 | ],
61 | ],
62 | ];
63 |
--------------------------------------------------------------------------------
/Configuration/TCA/tx_blogexample_domain_model_person.php:
--------------------------------------------------------------------------------
1 | [
5 | 'title' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_person',
6 | 'label' => 'lastname',
7 | 'label_alt' => 'firstname',
8 | 'label_alt_force' => true,
9 | 'tstamp' => 'tstamp',
10 | 'crdate' => 'crdate',
11 | 'versioningWS' => true,
12 | 'transOrigPointerField' => 'l10n_parent',
13 | 'transOrigDiffSourceField' => 'l10n_diffsource',
14 | 'languageField' => 'sys_language_uid',
15 | 'translationSource' => 'l10n_source',
16 | 'origUid' => 't3_origuid',
17 | 'prependAtCopy' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.prependAtCopy',
18 | 'delete' => 'deleted',
19 | 'enablecolumns' => [
20 | 'disabled' => 'hidden',
21 | ],
22 | 'iconfile' => 'EXT:blog_example/Resources/Public/Icons/icon_tx_blogexample_domain_model_person.gif',
23 | ],
24 | 'columns' => [
25 | 'firstname' => [
26 | 'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_person.firstname',
27 | 'config' => [
28 | 'type' => 'input',
29 | 'size' => 20,
30 | 'eval' => 'trim',
31 | 'required' => true,
32 | 'max' => 256,
33 | ],
34 | ],
35 | 'lastname' => [
36 | 'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_person.lastname',
37 | 'config' => [
38 | 'type' => 'input',
39 | 'size' => 20,
40 | 'eval' => 'trim',
41 | 'required' => true,
42 | 'max' => 256,
43 | ],
44 | ],
45 | 'email' => [
46 | 'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_person.email',
47 | 'config' => [
48 | 'type' => 'input',
49 | 'size' => 20,
50 | 'eval' => 'trim',
51 | 'required' => true,
52 | 'max' => 256,
53 | ],
54 | ],
55 | 'tags' => [
56 | 'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_person.tags',
57 | 'config' => [
58 | 'type' => 'inline',
59 | 'foreign_table' => 'tx_blogexample_domain_model_tag', // needed by Extbase
60 | 'MM' => 'tx_blogexample_domain_model_tag_mm',
61 | 'MM_match_fields' => [
62 | 'fieldname' => 'tags',
63 | ],
64 | 'appearance' => [
65 | 'useCombination' => 1,
66 | 'useSortable' => 1,
67 | 'collapseAll' => 1,
68 | 'expandSingle' => 1,
69 | ],
70 | ],
71 | ],
72 | 'tags_special' => [
73 | 'exclude' => true,
74 | 'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_person.tags_special',
75 | 'config' => [
76 | 'type' => 'inline',
77 | 'foreign_table' => 'tx_blogexample_domain_model_tag', // needed by Extbase
78 | 'MM' => 'tx_blogexample_domain_model_tag_mm',
79 | 'MM_match_fields' => [
80 | 'fieldname' => 'tags_special',
81 | ],
82 | 'appearance' => [
83 | 'useCombination' => 1,
84 | 'useSortable' => 1,
85 | 'collapseAll' => 1,
86 | 'expandSingle' => 1,
87 | ],
88 | ],
89 | ],
90 | ],
91 | 'types' => [
92 | '1' => ['showitem' => '
93 | --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:general,
94 | firstname, lastname, email, avatar, tags, tags_special,
95 | --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access,
96 | hidden,
97 | --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language,
98 | --palette--;;paletteLanguage,
99 | --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:extended,
100 | '],
101 | ],
102 | 'palettes' => [
103 | 'paletteLanguage' => [
104 | 'showitem' => 'sys_language_uid, l10n_parent',
105 | ],
106 | ],
107 | ];
108 |
--------------------------------------------------------------------------------
/Configuration/TCA/tx_blogexample_domain_model_post.php:
--------------------------------------------------------------------------------
1 | [
5 | 'title' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_post',
6 | 'label' => 'title',
7 | 'label_alt' => 'author',
8 | 'label_alt_force' => true,
9 | 'tstamp' => 'tstamp',
10 | 'crdate' => 'crdate',
11 | 'versioningWS' => true,
12 | 'origUid' => 't3_origuid',
13 | 'transOrigPointerField' => 'l10n_parent',
14 | 'transOrigDiffSourceField' => 'l10n_diffsource',
15 | 'languageField' => 'sys_language_uid',
16 | 'translationSource' => 'l10n_source',
17 | 'delete' => 'deleted',
18 | 'sortby' => 'sorting',
19 | 'enablecolumns' => [
20 | 'disabled' => 'hidden',
21 | ],
22 | 'iconfile' => 'EXT:blog_example/Resources/Public/Icons/icon_tx_blogexample_domain_model_post.gif',
23 | ],
24 | 'interface' => [
25 | 'maxDBListItems' => 100,
26 | 'maxSingleDBListItems' => 500,
27 | ],
28 | 'columns' => [
29 | 'blog' => [
30 | 'exclude' => true,
31 | 'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_post.blog',
32 | 'config' => [
33 | 'type' => 'select',
34 | 'renderType' => 'selectSingle',
35 | 'foreign_table' => 'tx_blogexample_domain_model_blog',
36 | 'maxitems' => 1,
37 | ],
38 | ],
39 | 'title' => [
40 | 'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_post.title',
41 | 'config' => [
42 | 'type' => 'input',
43 | 'size' => 20,
44 | 'eval' => 'trim',
45 | 'required' => true,
46 | 'max' => 256,
47 | ],
48 | ],
49 | 'date' => [
50 | 'exclude' => true,
51 | 'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_post.date',
52 | 'config' => [
53 | 'type' => 'datetime',
54 | 'eval' => 'datetime',
55 | ],
56 | ],
57 | 'author' => [
58 | 'exclude' => true,
59 | 'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_post.author',
60 | 'config' => [
61 | 'type' => 'select',
62 | 'renderType' => 'selectSingle',
63 | 'foreign_table' => 'tx_blogexample_domain_model_person',
64 | 'fieldControl' => [
65 | 'editPopup' => [
66 | 'disabled' => false,
67 | ],
68 | 'addRecord' => [
69 | 'disabled' => false,
70 | 'options' => [
71 | 'setValue' => 'prepend',
72 | ],
73 | ],
74 | ],
75 | ],
76 | ],
77 | 'second_author' => [
78 | 'exclude' => true,
79 | 'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_post.second_author',
80 | 'config' => [
81 | 'type' => 'group',
82 | 'allowed' => 'tx_blogexample_domain_model_person',
83 | 'foreign_table' => 'tx_blogexample_domain_model_person',
84 | 'maxitems' => 1,
85 | 'default' => 0,
86 | 'fieldControl' => [
87 | 'editPopup' => [
88 | 'disabled' => false,
89 | ],
90 | 'addRecord' => [
91 | 'disabled' => false,
92 | ],
93 | 'listModule' => [
94 | 'disabled' => false,
95 | ],
96 | ],
97 | ],
98 | ],
99 | 'reviewer' => [
100 | 'exclude' => true,
101 | 'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_post.reviewer',
102 | 'config' => [
103 | 'type' => 'select',
104 | 'renderType' => 'selectSingle',
105 | 'foreign_table' => 'tx_blogexample_domain_model_person',
106 | 'fieldControl' => [
107 | 'editPopup' => [
108 | 'disabled' => false,
109 | ],
110 | 'addRecord' => [
111 | 'disabled' => false,
112 | 'options' => [
113 | 'setValue' => 'prepend',
114 | ],
115 | ],
116 | ],
117 | ],
118 | ],
119 | 'content' => [
120 | 'exclude' => true,
121 | 'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_post.content',
122 | 'config' => [
123 | 'type' => 'text',
124 | 'enableRichtext' => true,
125 | ],
126 | ],
127 | 'tags' => [
128 | 'exclude' => true,
129 | 'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_post.tags',
130 | 'config' => [
131 | 'type' => 'inline',
132 | 'foreign_table' => 'tx_blogexample_domain_model_tag',
133 | 'MM' => 'tx_blogexample_post_tag_mm',
134 | 'appearance' => [
135 | 'useCombination' => 1,
136 | 'useSortable' => 1,
137 | 'collapseAll' => 1,
138 | 'expandSingle' => 1,
139 | ],
140 | ],
141 | ],
142 | 'comments' => [
143 | 'exclude' => true,
144 | 'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_post.comments',
145 | 'config' => [
146 | 'type' => 'inline',
147 | 'foreign_table' => 'tx_blogexample_domain_model_comment',
148 | 'foreign_field' => 'post',
149 | 'size' => 10,
150 | 'autoSizeMax' => 30,
151 | 'multiple' => 0,
152 | 'appearance' => [
153 | 'collapseAll' => 1,
154 | 'expandSingle' => 1,
155 | ],
156 | ],
157 | ],
158 | 'related_posts' => [
159 | 'exclude' => true,
160 | 'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_post.related',
161 | 'config' => [
162 | 'type' => 'select',
163 | 'renderType' => 'selectMultipleSideBySide',
164 | 'size' => 10,
165 | 'autoSizeMax' => 30,
166 | 'multiple' => 0,
167 | 'foreign_table' => 'tx_blogexample_domain_model_post',
168 | 'foreign_table_where' => 'AND ###THIS_UID### != tx_blogexample_domain_model_post.uid',
169 | 'MM' => 'tx_blogexample_post_post_mm',
170 | // @see https://forge.typo3.org/issues/98323
171 | // 'MM_opposite_field' => 'related_posts',
172 | ],
173 | ],
174 | 'additional_name' => [
175 | 'exclude' => true,
176 | 'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_post.additional_name',
177 | 'config' => [
178 | 'type' => 'inline', // this will store the info uid in the additional_name field (CSV)
179 | 'foreign_table' => 'tx_blogexample_domain_model_info',
180 | 'minitems' => 0,
181 | 'maxitems' => 1,
182 | ],
183 | ],
184 | 'additional_info' => [
185 | 'exclude' => true,
186 | 'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_post.additional_info',
187 | 'config' => [
188 | 'type' => 'inline', // this will store the post uid in the post field of the info table
189 | 'foreign_table' => 'tx_blogexample_domain_model_info',
190 | 'foreign_field' => 'post',
191 | 'minitems' => 0,
192 | 'maxitems' => 1,
193 | ],
194 | ],
195 | 'additional_comments' => [
196 | 'exclude' => true,
197 | 'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_post.additional_comments',
198 | 'config' => [
199 | 'type' => 'inline', // this will store the comments uids in the additional_comments field (CSV)
200 | 'foreign_table' => 'tx_blogexample_domain_model_comment',
201 | 'minitems' => 0,
202 | 'maxitems' => 200,
203 | ],
204 | ],
205 | 'category' => [
206 | 'config' => [
207 | 'type' => 'category',
208 | ],
209 | ],
210 | ],
211 | 'types' => [
212 | '1' => ['showitem' => '
213 | --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:general,
214 | blog, title, date, author, second_author, content, tags, comments, related_posts, additional_name, additional_info, additional_comments,
215 | --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:categories,
216 | category,
217 | --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access,
218 | hidden,
219 | --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language,
220 | --palette--;;paletteLanguage,
221 | --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:extended,
222 | '],
223 | ],
224 | 'palettes' => [
225 | 'paletteLanguage' => [
226 | 'showitem' => 'sys_language_uid, l10n_parent',
227 | ],
228 | ],
229 | ];
230 |
--------------------------------------------------------------------------------
/Configuration/TCA/tx_blogexample_domain_model_tag.php:
--------------------------------------------------------------------------------
1 | [
5 | 'title' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_tag',
6 | 'label' => 'name',
7 | 'tstamp' => 'tstamp',
8 | 'crdate' => 'crdate',
9 | 'versioningWS' => true,
10 | 'origUid' => 't3_origuid',
11 | 'transOrigPointerField' => 'l10n_parent',
12 | 'transOrigDiffSourceField' => 'l10n_diffsource',
13 | 'languageField' => 'sys_language_uid',
14 | 'translationSource' => 'l10n_source',
15 | 'delete' => 'deleted',
16 | 'enablecolumns' => [
17 | 'disabled' => 'hidden',
18 | ],
19 | 'iconfile' => 'EXT:blog_example/Resources/Public/Icons/icon_tx_blogexample_domain_model_tag.gif',
20 | ],
21 | 'columns' => [
22 | 'name' => [
23 | 'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_tag.name',
24 | 'config' => [
25 | 'type' => 'input',
26 | 'size' => 20,
27 | 'eval' => 'trim',
28 | 'required' => true,
29 | 'max' => 256,
30 | ],
31 | ],
32 | 'priority' => [
33 | 'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_tag.priority',
34 | 'config' => [
35 | 'type' => 'number',
36 | 'default' => 0,
37 | ],
38 | ],
39 | 'posts' => [
40 | 'exclude' => true,
41 | 'label' => 'LLL:EXT:blog_example/Resources/Private/Language/locallang_db.xlf:tx_blogexample_domain_model_tag.posts',
42 | 'config' => [
43 | 'type' => 'select',
44 | 'renderType' => 'selectMultipleSideBySide',
45 | 'size' => 10,
46 | 'minitems' => 0,
47 | 'autoSizeMax' => 30,
48 | 'multiple' => 0,
49 | 'foreign_table' => 'tx_blogexample_domain_model_post',
50 | 'MM' => 'tx_blogexample_post_tag_mm',
51 | 'MM_opposite_field' => 'tags',
52 | ],
53 | ],
54 | ],
55 | 'types' => [
56 | '1' => ['showitem' => '
57 | --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:general,
58 | name, priority, posts,
59 | --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access,
60 | hidden,
61 | --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language,
62 | --palette--;;paletteLanguage,
63 | --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:extended,
64 | '],
65 | ],
66 | 'palettes' => [
67 | 'paletteLanguage' => [
68 | 'showitem' => 'sys_language_uid, l10n_parent',
69 | ],
70 | ],
71 | ];
72 |
--------------------------------------------------------------------------------
/Configuration/page.tsconfig:
--------------------------------------------------------------------------------
1 | templates.t3docs/blog-example.100 = t3docs/blog-example:Resources/Private/Backend/
2 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: help
2 | help: ## Displays this list of targets with descriptions
3 | @echo "The following commands are available:\n"
4 | @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[32m%-30s\033[0m %s\n", $$1, $$2}'
5 |
6 | .PHONY: install
7 | install: ## Run rector
8 | Build/Scripts/runTests.sh -s composerUpdate
9 |
10 | .PHONY: rector
11 | rector: ## Run rector
12 | Build/Scripts/runTests.sh -s rector
13 |
14 | .PHONY: fix-cs
15 | fix-cs: ## Fix PHP coding styles
16 | Build/Scripts/runTests.sh -s cgl
17 |
18 | .PHONY: fix
19 | fix: rector fix-cs## Run rector and cgl fixes
20 |
21 | .PHONY: phpstan
22 | phpstan: ## Run phpstan tests
23 | Build/Scripts/runTests.sh -s phpstan
24 |
25 | .PHONY: phpstan-baseline
26 | phpstan-baseline: ## Update the phpstan baseline
27 | Build/Scripts/runTests.sh -s phpstanBaseline
28 |
29 | .PHONY: test
30 | test: fix-cs phpstan test-unit test-functional## Run all tests
31 |
32 | .PHONY: test-unit
33 | test-unit: ## Run unit tests
34 | Build/Scripts/runTests.sh -s unit
35 |
36 | .PHONY: test-functional
37 | test-functional: ## Run functional tests
38 | Build/Scripts/runTests.sh -s functional -d mysql
39 |
40 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://extensions.typo3.org/extension/blog_example/)
2 | [](https://get.typo3.org/version/13)
3 | [](https://get.typo3.org/version/12)
4 | [](https://packagist.org/packages/t3docs/blog-example)
5 | [](https://packagist.org/packages/t3docs/blog-example)
6 | 
7 |
8 | # TYPO3 extension ``blog_example``
9 |
10 | **Installation:** Can be installed via Composer:
11 | ``composer req t3docs/blog-example``
12 |
13 | This example provides the code examples for [Extbase reference] (https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ExtensionArchitecture/Extbase/Reference/Index.html#extbase-reference)
14 | of the TYPO3 project.
15 |
16 | The code examples are automatically extracted with the TYPO3 documentation
17 | code-snippet tool (https://github.com/TYPO3-Documentation/t3docs-codesnippets)
18 |
19 | After changes to the code the snippets in the TYPO3-Reference-CoreApi
20 | have to be regenerated.
21 |
22 | ```
23 | ddev exec vendor/bin/typo3 restructured_api_tools:php_domain public/fileadmin/TYPO3CMS-Reference-CoreApi/Documentation/CodeSnippets/
24 | ```
25 |
26 | It was originally written by Sebastian Kurfuerst and Jochen Rau (Thanks!) and
27 | adjusted over time to reflect current development in the TYPO3 project.
28 |
29 | | | URL |
30 | |------------------|------------------------------------------------------|
31 | | **Repository:** | https://github.com/TYPO3-Documentation/blog_example |
32 | | **TER:** | https://extensions.typo3.org/extension/blog_example/ |
33 |
34 | # Running tests
35 |
36 | Please see [CONTRIBUTING.md](CONTRIBUTING.md)
37 |
38 |
39 | # Tagging and releasing
40 |
41 | [packagist.org](https://packagist.org/packages/t3docs/blog-example) is enabled via the casual GitHub hook.
42 | TER releases are created by the "publish.yml" GitHub workflow when tagging versions.
43 | The commit message of the tagged commit is used as TER upload comment.
44 |
--------------------------------------------------------------------------------
/Resources/Private/Backend/Templates/Index.html:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Here is a list of blogs:
12 |
13 |
14 |
15 |
16 | Blog
17 | Description
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | {blog.title}
27 | ({blog.posts -> f:count()})
28 |
29 |
30 | {blog.description}
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | Delete all Blogs [!!!]
67 |
68 |
69 |
70 |
71 |
72 | Create example data
73 |
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/Resources/Private/Backend/Templates/ShowAllComments.html:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/Resources/Private/Backend/Templates/ShowBlog.html:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 | Posts of Blog "{blog.title}"
10 | {blog.description}
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | {post.title}
21 |
22 |
23 |
24 | {post.content}
25 |
26 |
27 |
28 |
29 |
30 |
31 | This blog currently doesn't contain any posts.
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/Resources/Private/Backend/Templates/ShowPost.html:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
{post.date}
12 |
13 | {post.title}
14 |
15 |
16 | {post.content}
17 |
18 |
By: {post.author.fullName}
19 |
20 |
21 | Related posts:
22 |
23 |
24 |
25 | {relatedPost.title}
26 |
27 |
28 |
29 |
30 |
31 | Back
32 |
33 |
34 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/Resources/Private/Language/Module/locallang_mod.xlf:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Blogs
8 |
9 |
10 | View/Modify Blogs
11 |
12 |
13 | Blogs
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/Resources/Private/Language/locallang.xlf:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
9 |
12 |
13 | back
14 |
15 |
16 | cancel
17 |
18 |
19 | submit
20 |
21 |
22 | Log in with a FE user that belongs to group #%d (configurable by settings.editorUsergroupUid) in order to add/modify blogs and posts in FE mode!
23 |
24 |
25 | published on
26 |
27 |
28 | by
29 |
30 |
31 | posted on
32 |
33 |
36 |
37 | This extension serves as a demonstration for basic development techniques used in Extbase applications.
38 |
39 |
40 | Here is a list of %d blogs
41 |
42 |
43 | %d posts
44 |
45 |
46 | There are currently no blogs
47 |
48 |
49 | Edit this blog
50 |
51 |
52 | Delete this blog
53 |
54 |
55 | Create your first blog
56 |
57 |
58 | Create another blog
59 |
60 |
61 | Create sample data
62 |
63 |
64 | Delete all blogs
65 |
66 |
67 | Are you sure that you want to delete all blogs?
68 |
69 |
72 |
73 | Edit the information about your blog below
74 |
75 |
78 |
79 | Edit the information about the new blog below
80 |
81 |
84 |
85 | If you have any questions, feel free to contact the administrator %s.
86 |
87 |
88 | There is currently no administrator assigned to this Blog
89 |
90 |
91 | This blog contains %d posts
92 |
93 |
94 | This blog contains %d posts tagged with "%s"
95 |
96 |
97 | There are currently no posts in this blog
98 |
99 |
100 | Create your first post
101 |
102 |
103 | Create another post
104 |
105 |
106 | Show all posts of this blog
107 |
108 |
109 | Edit this post
110 |
111 |
112 | Delete this post
113 |
114 |
115 | read more
116 |
117 |
118 | related posts
119 |
120 |
123 |
126 |
129 |
132 |
133 | Show posts as plaintext
134 |
135 |
138 |
139 | title
140 |
141 |
142 | description
143 |
144 |
145 | administrator
146 |
147 |
148 | content
149 |
150 |
151 | author
152 |
153 |
154 | email
155 |
156 |
157 | message
158 |
159 |
160 | related posts
161 |
162 |
163 | Blog added
164 |
165 |
166 | A new blog was created
167 |
168 |
169 | Blog updated
170 |
171 |
172 | The blog was updated
173 |
174 |
175 | Blog deleted
176 |
177 |
178 | The blog has been removed
179 |
180 |
181 | Sample data created
182 |
183 |
184 | Some exemplary blogs and posts have been created
185 |
186 |
187 | Post added
188 |
189 |
190 | A new post was created
191 |
192 |
193 | Post updated
194 |
195 |
196 | The blog post was updated
197 |
198 |
199 | Post deleted
200 |
201 |
202 | The blog post has been removed
203 |
204 |
207 |
210 |
213 |
216 |
219 |
222 |
223 | Y-m-d H:i\h
224 |
225 |
226 | Y-m-d
227 |
228 |
229 | Following errors occurred
230 |
231 |
232 | Could not update blog
233 |
234 |
235 | Could not create blog
236 |
237 |
238 | "Extbase" can't be used as a blog title
239 |
240 |
241 | Could not update post
242 |
243 |
244 | Could not create post
245 |
246 |
249 |
250 | The given <strong>%s</strong> is either too long or too short
251 |
252 |
253 | The given <strong>%s</strong> is too short
254 |
255 |
256 | The given <strong>%s</strong> is too long
257 |
258 |
259 | The given <strong>%s</strong> is not a valid email address
260 |
261 |
262 | The <strong>%s</strong> must not be empty
263 |
264 |
265 |
266 |
267 |
--------------------------------------------------------------------------------
/Resources/Private/Language/locallang_db.xlf:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Blog
8 |
9 |
10 |
11 | Blog
12 |
13 |
14 | Blog title
15 |
16 |
17 | Short description
18 |
19 |
20 | Logo
21 |
22 |
23 | Posts
24 |
25 |
26 | Administrator
27 |
28 |
29 | This frontend user may edit the blog and all its posts.
30 |
31 |
32 | Post
33 |
34 |
35 | Related to
36 |
37 |
38 | Title
39 |
40 |
41 | Date
42 |
43 |
44 | Author
45 |
46 |
47 | Content
48 |
49 |
50 | Votes
51 |
52 |
53 | Published
54 |
55 |
56 | Tags
57 |
58 |
59 | Comments
60 |
61 |
62 | Related posts
63 |
64 |
65 | Second author
66 |
67 |
68 | Additional name
69 |
70 |
71 | Additional info
72 |
73 |
74 | Additional comments
75 |
76 |
77 | Person
78 |
79 |
80 | Firstname
81 |
82 |
83 | Lastname
84 |
85 |
86 | E-Mail
87 |
88 |
89 | Avatar
90 |
91 |
92 | Comment
93 |
94 |
95 | Date
96 |
97 |
98 | Author
99 |
100 |
101 | Email
102 |
103 |
104 | Content
105 |
106 |
107 | Tag
108 |
109 |
110 | Name
111 |
112 |
113 | Priority
114 |
115 |
116 | Related posts
117 |
118 |
119 | Blog Admin (BlogExample)
120 |
121 |
122 | Bloginfo
123 |
124 |
125 | Name
126 |
127 |
128 | Text
129 |
130 |
131 |
132 |
133 |
--------------------------------------------------------------------------------
/Resources/Private/Layouts/Default.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
© TYPO3 Association
11 |
12 |
13 |
--------------------------------------------------------------------------------
/Resources/Private/Partials/BlogForm.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 | [title]
9 | :
10 |
11 |
12 |
13 |
14 |
15 |
16 | [description]
17 | :
18 |
19 |
20 |
21 |
22 |
23 |
24 | [administrator]
25 | :
26 |
27 |
28 |
29 | dummy
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/Resources/Private/Partials/CommentForm.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
38 |
39 |
--------------------------------------------------------------------------------
/Resources/Private/Partials/FormErrors.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 | [following errors occurred:]
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | [{errorDetail.message} ({errorDetail.code})]
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | {errorDetail.message}
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/Resources/Private/Partials/Pagination.html:
--------------------------------------------------------------------------------
1 |
5 |
57 |
58 |
--------------------------------------------------------------------------------
/Resources/Private/Partials/PostForm.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | [author]
10 | :
11 |
12 |
13 |
14 | dummy
15 |
16 |
17 |
18 |
19 | [title]
20 | :
21 |
22 |
23 |
24 |
25 |
26 |
27 | [content]
28 | :
29 |
30 |
31 |
32 |
33 |
34 |
35 | [related posts]
36 | :
37 |
38 |
39 |
40 | dummy
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/Resources/Private/Partials/PostMetaData.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | {post.date -> f:format.date(format: '{f:translate(key: \'culture.date.formatShort\')}')}
7 |
8 | {post.author.fullName}
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Resources/Private/Partials/PostTags.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 | {tag.name}
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Resources/Private/Templates/Blog/Edit.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 | [cancel]
9 |
10 |
11 | [edit blog]
12 |
13 |
14 | [edit blog below]
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Resources/Private/Templates/Blog/Index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 | [Blog header]
9 |
10 |
11 | [introduction]
12 |
13 |
14 |
15 |
16 | [list of blogs]
17 | :
18 |
19 |
20 |
21 |
22 |
23 |
24 | {blog.title} ({f:translate(key: 'blog.numberOfPosts', arguments: '{numberOfPosts: \'{blog.posts -> f:count()}\'}')})
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | {blog.description}
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | [create another blog]
48 |
49 |
50 |
51 |
52 | [create example data]
53 |
54 |
55 |
56 |
58 | [delete all blogs]
59 |
60 |
61 |
62 |
63 |
64 |
65 | [no blogs]
66 |
67 |
68 |
69 |
70 |
71 |
72 | [create first blog]
73 |
74 |
75 |
76 |
77 | [create example data]
78 |
79 |
80 |
81 |
82 |
83 |
84 | [log in to create your first blog]
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/Resources/Private/Templates/Blog/New.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 | [cancel]
9 |
10 |
11 | [create blog]
12 |
13 |
14 | [create blog below]
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Resources/Private/Templates/Post/DisplayRssList.html:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 | {settings.rss.channel.title}
12 | {settings.rss.channel.link}
13 |
14 |
15 |
16 |
17 |
18 | -
19 |
news-{post.uid}
20 | {post.datetime}
21 | {post.title -> f:format.htmlspecialchars()}
22 |
23 | {post.content -> f:format.stripTags() -> f:format.htmlspecialchars()}
24 | {post.content}
25 |
26 |
27 | {postCategory.title -> f:format.htmlspecialchars()}
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/Resources/Private/Templates/Post/Edit.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 | [cancel]
9 |
10 |
11 | [edit post]
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/Resources/Private/Templates/Post/Index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | [Post header]
10 |
11 |
12 |
13 |
14 |
15 |
16 | [introduction (administrator)]
17 |
18 |
19 |
20 |
21 |
22 |
23 | [introduction (no administrator)]
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | [list of posts (tag)]
33 | :
34 |
35 |
36 | [show all posts]
37 |
38 |
39 |
40 |
41 | [list of posts]
42 | :
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | {post.title}
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | {post.content}
65 |
66 | [read more]
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | [create another post]
80 |
81 |
82 |
83 |
84 | [list posts as plaintext]
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | [no entries]
93 |
94 |
95 |
96 |
97 |
98 | [create first post]
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 | {content}
114 |
115 |
116 |
117 | {content}
118 |
119 |
120 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/Resources/Private/Templates/Post/Index.txt:
--------------------------------------------------------------------------------
1 | {blog.title}:
2 | ===============================================================
3 |
4 | {post.title}
5 | ---------------------------------------------------------------
6 | {post.content}
7 |
8 | Published on {post.date -> f:format.date(format: '{f:translate(key: \'culture.date.formatShort\')}')} by {post.author.fullName}
9 | Tags: [{tag.name}]
10 |
11 |
12 |
13 |
14 | No Posts available.
15 |
16 |
--------------------------------------------------------------------------------
/Resources/Private/Templates/Post/New.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 | [cancel]
9 |
10 |
11 | [create post]
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/Resources/Private/Templates/Post/Show.html:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 | [back]
10 |
11 | {post.title}
12 |
13 | {post.content}
14 |
15 |
16 |
17 |
18 |
19 |
20 | [related posts]
21 | :
22 |
23 |
24 |
25 |
26 | {relatedPost.title}
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/Resources/Public/Css/BlogExample.css:
--------------------------------------------------------------------------------
1 | /* Tags */
2 |
3 | .tx-blogexample h1, .tx-blogexample h2, .tx-blogexample h3 {
4 | margin: 10px 0 10px 0;
5 | padding: 0;
6 | clear: both;
7 | font-size: 17px;
8 | font-weight: bold;
9 | }
10 |
11 | .tx-blogexample h2 {
12 | font-size: 16px;
13 | }
14 |
15 | .tx-blogexample h3 {
16 | margin: 15px 0;
17 | font-size: 14px;
18 | }
19 |
20 | .tx-blogexample ul, .tx-blogexample ol {
21 | margin: 0;
22 | padding: 0;
23 | list-style: none;
24 | }
25 |
26 | .tx-blogexample ul li, .tx-blogexample ol li {
27 | margin: 0;
28 | padding: 0;
29 | list-style: none;
30 | }
31 |
32 | .tx-blogexample strong {
33 | font-weight: bold;
34 | }
35 |
36 | /* Classes */
37 |
38 | .tx-blogexample .tx-blogexample-blog-list h3, .tx-blogexample .tx-blogexample-post-list h3 {
39 | margin: 10px 0 0 0;
40 | float: left;
41 | }
42 |
43 | .tx-blogexample .tx-blogexample-description, .tx-blogexample .tx-blogexample-content {
44 | margin: 0 0 15px 0;
45 | clear: both;
46 | }
47 |
48 | .tx-blogexample .tx-blogexample-options {
49 | margin: 10px 0 0 5px;
50 | float: left;
51 | clear: right;
52 | }
53 |
54 | .tx-blogexample .tx-blogexample-linksection {
55 | margin: 20px 0;
56 | padding: 3px;
57 | background: #f5f5f5;
58 | border: 1px solid #e0e0e0;
59 | height: 16px;
60 | }
61 |
62 | .tx-blogexample .tx-blogexample-metadata {
63 | font-size: 0.8em;
64 | color: #777;
65 | }
66 |
67 | .tx-blogexample .tx-blogexample-tags {
68 | margin: 5px 0;
69 | }
70 |
71 | .tx-blogexample .tx-blogexample-tags li {
72 | display: inline;
73 | }
74 |
75 | .tx-blogexample .tx-blogexample-comments {
76 | padding-top: 20px;
77 | clear: left;
78 | }
79 |
80 | .tx-blogexample .tx-blogexample-comments li {
81 | margin: 10px 0;
82 | }
83 |
84 | .tx-blogexample .tx-blogexample-comments p {
85 | font-size: .9em;
86 | }
87 |
88 | .tx-blogexample .tx-blogexample-comments .tx-blogexample-options {
89 | float: none;
90 | display: inline;
91 | }
92 |
93 | .tx-blogexample .tx-blogexample-gravatar {
94 | margin: 2px 5px 5px 0;
95 | float: left;
96 | }
97 |
98 | /* Icons */
99 |
100 | .tx-blogexample .tag {
101 | margin: 0 2px 0 0;
102 | padding: 2px 3px 2px 21px;
103 | background: #c0e6ff url('../Icons/icon_tx_blogexample_domain_model_tag.gif') 1px 1px no-repeat;
104 | border: 1px solid #96acbb;
105 | font-size: 0.7em;
106 | text-decoration: none;
107 | text-transform: uppercase;
108 | }
109 |
110 | .tx-blogexample a.tag:hover {
111 | background-color: #d8f0ff;
112 | border-color: #a6bfcf;
113 | }
114 |
115 | .tx-blogexample .icon, .tx-blogexample .textIcon {
116 | display: block;
117 | margin: 0 5px 0 0;
118 | padding: 0;
119 | background-position: 0 0;
120 | background-repeat: no-repeat;
121 | }
122 |
123 | .tx-blogexample .icon {
124 | float: left;
125 | text-indent: -9999px;
126 | width: 16px;
127 | height: 16px;
128 | }
129 |
130 | .tx-blogexample .textIcon {
131 | padding-left: 18px;
132 | }
133 |
134 | .tx-blogexample .icon.edit, .tx-blogexample .textIcon.edit {
135 | background-image: url('../Icons/icon_edit.gif');
136 | }
137 |
138 | .tx-blogexample .icon.delete, .tx-blogexample .textIcon.delete {
139 | background-image: url('../Icons/icon_delete.gif');
140 | }
141 |
142 | .tx-blogexample .icon.new, .tx-blogexample .textIcon.new {
143 | background-image: url('../Icons/icon_new.gif');
144 | }
145 |
146 | .tx-blogexample .icon.populate, .tx-blogexample .textIcon.populate {
147 | background-image: url('../Icons/icon_populate.gif');
148 | }
149 |
150 | .tx-blogexample .icon.cancel, .tx-blogexample .textIcon.cancel {
151 | background-image: url('../Icons/icon_close.gif');
152 | }
153 |
154 | .tx-blogexample .icon.plaintext, .tx-blogexample .textIcon.plaintext {
155 | background-image: url('../Icons/icon_plaintext.gif');
156 | }
157 |
158 | /* Flashmessages */
159 |
160 | .tx-blogexample .typo3-message {
161 | margin: 1em 0;
162 | background-position: 0.5em 0.7em;
163 | background-repeat: no-repeat;
164 | border: 1px solid;
165 | color: #000000;
166 | padding: 0.6em 0.6em 0.6em 2.6em;
167 | }
168 |
169 | .tx-blogexample .typo3-message a {
170 | text-decoration: underline;
171 | }
172 |
173 | .tx-blogexample .typo3-message .message-header {
174 | font-weight: bold;
175 | }
176 |
177 | .tx-blogexample .typo3-message h4 {
178 | margin-top: 0;
179 | }
180 |
181 | .tx-blogexample .message-notice {
182 | background-color: #F6F7FA;
183 | background-image: url('../Icons/FlashMessages/notice.png');
184 | border-color: #C2CBCF;
185 | }
186 |
187 | .tx-blogexample .message-information {
188 | background-color: #DDEEF9;
189 | background-image: url('../Icons/FlashMessages/information.png');
190 | border-color: #8AAFC4;
191 | }
192 |
193 | .tx-blogexample .message-ok {
194 | background-color: #CDEACA;
195 | background-image: url('../Icons/FlashMessages/ok.png');
196 | border-color: #58B548;
197 | }
198 |
199 | .tx-blogexample .message-warning {
200 | background-color: #FBFFB3;
201 | background-image: url('../Icons/FlashMessages/warning.png');
202 | border-color: #C4B70D;
203 | }
204 |
205 | .tx-blogexample .message-error {
206 | background-color: #FBB19B;
207 | background-image: url('../Icons/FlashMessages/error.png');
208 | border-color: #DC4C42;
209 | }
210 |
211 | .tx-blogexample input.f3-form-error, .tx-blogexample textarea.f3-form-error {
212 | background-color: #FBB19B;
213 | border: 1px #DC4C42 solid;
214 | }
215 |
216 |
217 | /* Errors */
218 |
219 | .tx-blogexample .errors {
220 | margin: 10px 0;
221 | padding: 10px;
222 | background-color: #FBB19B;
223 | border: 1px #DC4C42 solid;
224 | }
225 |
226 |
227 | /* Pagination */
228 |
229 | .tx-blogexample .f3-widget-paginator {
230 | margin: 20px 0;
231 | padding: 0;
232 | }
233 |
234 | .tx-blogexample .f3-widget-paginator li {
235 | margin: 0;
236 | padding: 0;
237 | display: inline;
238 | }
239 |
240 | .tx-blogexample .f3-widget-paginator li a {
241 | text-decoration: none;
242 | }
243 |
244 | .tx-blogexample .f3-widget-paginator li.active a {
245 | text-decoration: underline;
246 | color: black;
247 | }
248 |
249 |
250 | .tx-blogexample .f3-widget-paginator .icon {
251 | text-indent: 0;
252 | float: none;
253 | display: inline-block;
254 | }
255 |
--------------------------------------------------------------------------------
/Resources/Public/Icons/Extension.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/Resources/Public/Icons/FlashMessages/error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TYPO3-Documentation/blog_example/c549e10d31655da380464fb4d22b815e7103dcf8/Resources/Public/Icons/FlashMessages/error.png
--------------------------------------------------------------------------------
/Resources/Public/Icons/FlashMessages/information.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TYPO3-Documentation/blog_example/c549e10d31655da380464fb4d22b815e7103dcf8/Resources/Public/Icons/FlashMessages/information.png
--------------------------------------------------------------------------------
/Resources/Public/Icons/FlashMessages/notice.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TYPO3-Documentation/blog_example/c549e10d31655da380464fb4d22b815e7103dcf8/Resources/Public/Icons/FlashMessages/notice.png
--------------------------------------------------------------------------------
/Resources/Public/Icons/FlashMessages/ok.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TYPO3-Documentation/blog_example/c549e10d31655da380464fb4d22b815e7103dcf8/Resources/Public/Icons/FlashMessages/ok.png
--------------------------------------------------------------------------------
/Resources/Public/Icons/FlashMessages/warning.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TYPO3-Documentation/blog_example/c549e10d31655da380464fb4d22b815e7103dcf8/Resources/Public/Icons/FlashMessages/warning.png
--------------------------------------------------------------------------------
/Resources/Public/Icons/default_gravatar.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TYPO3-Documentation/blog_example/c549e10d31655da380464fb4d22b815e7103dcf8/Resources/Public/Icons/default_gravatar.gif
--------------------------------------------------------------------------------
/Resources/Public/Icons/icon_close.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TYPO3-Documentation/blog_example/c549e10d31655da380464fb4d22b815e7103dcf8/Resources/Public/Icons/icon_close.gif
--------------------------------------------------------------------------------
/Resources/Public/Icons/icon_delete.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TYPO3-Documentation/blog_example/c549e10d31655da380464fb4d22b815e7103dcf8/Resources/Public/Icons/icon_delete.gif
--------------------------------------------------------------------------------
/Resources/Public/Icons/icon_edit.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TYPO3-Documentation/blog_example/c549e10d31655da380464fb4d22b815e7103dcf8/Resources/Public/Icons/icon_edit.gif
--------------------------------------------------------------------------------
/Resources/Public/Icons/icon_new.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TYPO3-Documentation/blog_example/c549e10d31655da380464fb4d22b815e7103dcf8/Resources/Public/Icons/icon_new.gif
--------------------------------------------------------------------------------
/Resources/Public/Icons/icon_next.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TYPO3-Documentation/blog_example/c549e10d31655da380464fb4d22b815e7103dcf8/Resources/Public/Icons/icon_next.gif
--------------------------------------------------------------------------------
/Resources/Public/Icons/icon_plaintext.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TYPO3-Documentation/blog_example/c549e10d31655da380464fb4d22b815e7103dcf8/Resources/Public/Icons/icon_plaintext.gif
--------------------------------------------------------------------------------
/Resources/Public/Icons/icon_populate.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TYPO3-Documentation/blog_example/c549e10d31655da380464fb4d22b815e7103dcf8/Resources/Public/Icons/icon_populate.gif
--------------------------------------------------------------------------------
/Resources/Public/Icons/icon_previous.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TYPO3-Documentation/blog_example/c549e10d31655da380464fb4d22b815e7103dcf8/Resources/Public/Icons/icon_previous.gif
--------------------------------------------------------------------------------
/Resources/Public/Icons/icon_relation.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TYPO3-Documentation/blog_example/c549e10d31655da380464fb4d22b815e7103dcf8/Resources/Public/Icons/icon_relation.gif
--------------------------------------------------------------------------------
/Resources/Public/Icons/icon_tx_blogexample_domain_model_blog.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TYPO3-Documentation/blog_example/c549e10d31655da380464fb4d22b815e7103dcf8/Resources/Public/Icons/icon_tx_blogexample_domain_model_blog.gif
--------------------------------------------------------------------------------
/Resources/Public/Icons/icon_tx_blogexample_domain_model_comment.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TYPO3-Documentation/blog_example/c549e10d31655da380464fb4d22b815e7103dcf8/Resources/Public/Icons/icon_tx_blogexample_domain_model_comment.gif
--------------------------------------------------------------------------------
/Resources/Public/Icons/icon_tx_blogexample_domain_model_person.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TYPO3-Documentation/blog_example/c549e10d31655da380464fb4d22b815e7103dcf8/Resources/Public/Icons/icon_tx_blogexample_domain_model_person.gif
--------------------------------------------------------------------------------
/Resources/Public/Icons/icon_tx_blogexample_domain_model_post.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TYPO3-Documentation/blog_example/c549e10d31655da380464fb4d22b815e7103dcf8/Resources/Public/Icons/icon_tx_blogexample_domain_model_post.gif
--------------------------------------------------------------------------------
/Resources/Public/Icons/icon_tx_blogexample_domain_model_tag.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TYPO3-Documentation/blog_example/c549e10d31655da380464fb4d22b815e7103dcf8/Resources/Public/Icons/icon_tx_blogexample_domain_model_tag.gif
--------------------------------------------------------------------------------
/Resources/Public/Icons/module-blog.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "t3docs/blog-example",
3 | "description": "This extension contains code examples used in TYPO3 explained to describe the use of Extbase.",
4 | "license": "GPL-2.0-or-later",
5 | "type": "typo3-cms-extension",
6 | "authors": [
7 | {
8 | "name": "TYPO3 Documentation Team and contributors",
9 | "role": "Developer"
10 | }
11 | ],
12 | "homepage": "https://extensions.typo3.org/extension/blog_example/",
13 | "support": {
14 | "issues": "https://github.com/TYPO3-Documentation/blog_example/issues",
15 | "source": "https://github.com/TYPO3-Documentation/blog_example"
16 | },
17 | "require": {
18 | "typo3/cms-backend": "^13.3 || dev-main",
19 | "typo3/cms-core": "^13.3 || dev-main",
20 | "typo3/cms-extbase": "^13.3 || dev-main",
21 | "typo3/cms-fluid": "^13.3 || dev-main"
22 | },
23 | "require-dev": {
24 | "ergebnis/composer-normalize": "~2.42.0",
25 | "friendsofphp/php-cs-fixer": "^3.52",
26 | "phpstan/phpstan": "^1.10",
27 | "phpunit/phpunit": "^11.0.3",
28 | "typo3/cms-install": "^13.3 || dev-main",
29 | "typo3/testing-framework": "dev-main"
30 | },
31 | "minimum-stability": "dev",
32 | "prefer-stable": true,
33 | "autoload": {
34 | "psr-4": {
35 | "T3docs\\BlogExample\\": "Classes/"
36 | }
37 | },
38 | "autoload-dev": {
39 | "psr-4": {
40 | "T3docs\\BlogExample\\Tests\\": "Tests/"
41 | }
42 | },
43 | "config": {
44 | "allow-plugins": {
45 | "ergebnis/composer-normalize": true,
46 | "typo3/class-alias-loader": true,
47 | "typo3/cms-composer-installers": true
48 | },
49 | "bin-dir": ".Build/bin",
50 | "sort-packages": true,
51 | "vendor-dir": ".Build/vendor"
52 | },
53 | "extra": {
54 | "branch-alias": {
55 | "dev-main": "13.0.x-dev"
56 | },
57 | "typo3/cms": {
58 | "extension-key": "blog_example",
59 | "web-dir": ".Build/web"
60 | }
61 | },
62 | "scripts": {
63 | "prepare-release": [
64 | "rm -rf .github",
65 | "rm .gitignore",
66 | "rm .editorconfig"
67 | ]
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/ext_conf_template.txt:
--------------------------------------------------------------------------------
1 | # cat=basic; type=string; label=Example entry: This is just an example for ExtensionConfigurationProvider to check, if this value is available in Expression Language
2 | foo = bar
3 |
--------------------------------------------------------------------------------
/ext_emconf.php:
--------------------------------------------------------------------------------
1 | 'A Blog Example for the Extbase Framework',
5 | 'description' => 'This extension contains code examples used in TYPO3 explained to describe the use of Extbase',
6 | 'version' => '13.0.0',
7 | 'category' => 'example',
8 | 'author' => 'TYPO3 Documentation Team and contributors',
9 | 'author_company' => '',
10 | 'author_email' => '',
11 | 'state' => 'stable',
12 | 'constraints' => [
13 | 'depends' => [
14 | 'typo3' => '13.3.0-13.99.99',
15 | ],
16 | 'conflicts' => [],
17 | 'suggests' => [],
18 | ],
19 | ];
20 |
--------------------------------------------------------------------------------
/ext_localconf.php:
--------------------------------------------------------------------------------
1 | 'index',
19 | PostController::class => 'index, show',
20 | CommentController::class => 'create',
21 | ],
22 | [
23 | CommentController::class => 'create',
24 | ],
25 | ExtensionUtility::PLUGIN_TYPE_CONTENT_ELEMENT,
26 | );
27 |
28 | // RSS Feed
29 | ExtensionUtility::configurePlugin(
30 | $extensionName,
31 | 'PostListRss',
32 | // Cache-able Controller-Actions
33 | [
34 | PostController::class => 'displayRssList',
35 | ],
36 | [],
37 | ExtensionUtility::PLUGIN_TYPE_CONTENT_ELEMENT,
38 | );
39 |
40 | // admin plugins
41 | ExtensionUtility::configurePlugin(
42 | $extensionName,
43 | 'BlogAdmin',
44 | // Cache-able Controller-Actions
45 | [
46 | BlogController::class => 'new,create,delete,deleteAll,edit,update,populate',
47 | PostController::class => 'new,create,delete,edit,update',
48 | CommentController::class => 'delete',
49 | ],
50 | // Non-Cache-able Controller-Actions
51 | [
52 | BlogController::class => 'create,delete,deleteAll,update,populate',
53 | PostController::class => 'create,delete,update',
54 | CommentController::class => 'delete',
55 | ],
56 | ExtensionUtility::PLUGIN_TYPE_CONTENT_ELEMENT,
57 | );
58 | })('BlogExample');
59 |
--------------------------------------------------------------------------------
/ext_tables.sql:
--------------------------------------------------------------------------------
1 | #
2 | # Table structure for table 'tx_blogexample_domain_model_blog'
3 | #
4 | CREATE TABLE tx_blogexample_domain_model_blog (
5 | title varchar(255) DEFAULT '' NOT NULL,
6 | subtitle varchar(255) DEFAULT '',
7 | );
8 |
9 | #
10 | # Table structure for table 'tx_blogexample_domain_model_post'
11 | #
12 | CREATE TABLE tx_blogexample_domain_model_post (
13 | title varchar(255) DEFAULT '' NOT NULL,
14 | );
15 |
16 | #
17 | # Table structure for table 'tx_blogexample_domain_model_comment'
18 | #
19 | CREATE TABLE tx_blogexample_domain_model_comment (
20 | author varchar(255) DEFAULT '' NOT NULL,
21 | email varchar(255) DEFAULT '' NOT NULL,
22 | );
23 |
24 | #
25 | # Table structure for table 'tx_blogexample_domain_model_person'
26 | #
27 | CREATE TABLE tx_blogexample_domain_model_person (
28 | firstname varchar(255) DEFAULT '' NOT NULL,
29 | lastname varchar(255) DEFAULT '' NOT NULL,
30 | email varchar(255) DEFAULT '' NOT NULL,
31 | );
32 |
33 | #
34 | # Table structure for table 'tx_blogexample_domain_model_tag'
35 | #
36 | CREATE TABLE tx_blogexample_domain_model_tag (
37 | name varchar(255) DEFAULT '' NOT NULL,
38 | );
39 |
40 |
41 | #
42 | # Table structure for table 'tx_blogexample_domain_model_info'
43 | #
44 | CREATE TABLE tx_blogexample_domain_model_info (
45 | name varchar(255) DEFAULT '' NOT NULL,
46 | );
47 |
48 | #
49 | # Table structure for table 'tx_blogexample_domain_model_tag_mm'
50 | # @TODO fix tx_blogexample_domain_model_person to create and recognize this mm-table
51 | # with the field `fieldname` automatically, remove this entry
52 | # @see https://forge.typo3.org/issues/98322
53 | #
54 | CREATE TABLE `tx_blogexample_domain_model_tag_mm` (
55 | `fieldname` varchar(63) DEFAULT '' NOT NULL
56 | );
57 |
58 |
--------------------------------------------------------------------------------
List of all comments
11 |26 |
This blog currently doesn't contain any comments.
31 |