Skip to content

Conversation

@ljonesfl
Copy link
Member

@ljonesfl ljonesfl commented Nov 29, 2025

Upgrades the blog editor to be a block based editor.
A ton of refactoring.

Summary by CodeRabbit

  • New Features

    • Rich text editor for post creation and editing with multiple formatting tools
    • Event tracking for user authentication activities (login, logout, password reset, email verification)
    • Maintenance mode event notifications
  • Style

    • Enhanced post content rendering and error page styling
  • Chores

    • Removed email template generation CLI tool
    • Removed queue system installation CLI tool

✏️ Tip: You can customize this high-level summary in your review settings.

@ljonesfl
Copy link
Member Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 29, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 29, 2025

Walkthrough

This PR modernizes the CMS by integrating EditorJS as the rich-text editor for posts, implementing an event-driven architecture for authentication and maintenance workflows, updating package dependencies, and migrating namespace references from Neuron\Data\Setting to Neuron\Data\Settings. Additionally, CLI commands for email and queue generation are removed, and various services are updated to emit events on user login, logout, password resets, and maintenance mode changes.

Changes

Cohort / File(s) Summary
Dependency Updates
composer.json
Updated neuron-php/mvc constraint to ^0.9.5 → 0.9.\, added neuron-php/data 0.9.\, added neuron-php/scaffolding 0.8.\*, removed "extra" section with neuron CLI provider config
Namespace Migrations (Settings)
src/Bootstrap.php, src/Cms/Cli/Commands/Install/InstallCommand.php, src/Cms/Cli/Commands/Maintenance/EnableCommand.php, src/Cms/Controllers/Admin/Categories.php, src/Cms/Controllers/Admin/Profile.php, src/Cms/Controllers/Admin/Users.php, src/Cms/Database/ConnectionFactory.php, src/Cms/Email/helpers.php, src/Cms/Maintenance/MaintenanceConfig.php, src/Cms/Repositories/Database*.php, src/Cms/Services/Auth/Authentication.php, src/Cms/Services/Auth/EmailVerifier.php, src/Cms/Services/Auth/PasswordResetter.php, src/Cms/Services/Email/Sender.php, src/Cms/Services/Member/RegistrationService.php, resources/app/Initializers/*.php
Migrated import paths from Neuron\Data\SettingNeuron\Data\Settings and Neuron\Data\Object\VersionNeuron\Data\Objects\Version across 25+ files
Namespace Migrations (Tests)
tests/Cms/**/*.php
Updated test imports from Neuron\Data\SettingNeuron\Data\Settings and related source paths across 9 test files
New Event Classes
src/Cms/Events/UserLoginEvent.php, src/Cms/Events/UserLoginFailedEvent.php, src/Cms/Events/UserLogoutEvent.php, src/Cms/Events/EmailVerifiedEvent.php, src/Cms/Events/PasswordResetRequestedEvent.php, src/Cms/Events/PasswordResetCompletedEvent.php, src/Cms/Events/MaintenanceModeEnabledEvent.php, src/Cms/Events/MaintenanceModeDisabledEvent.php
Added 8 new event classes implementing IEvent for tracking authentication lifecycle, email verification, password resets, and maintenance mode changes
EditorJS Content Integration
src/Cms/Models/Post.php, src/Cms/Services/Post/Creator.php, src/Cms/Services/Post/Updater.php, resources/views/admin/posts/create.php, resources/views/admin/posts/edit.php, resources/database/migrate/20251119224525_add_content_raw_to_posts.php
Integrated EditorJS editor: added _contentRaw property and setContent(), getContent(), setContentArray(), getContentRaw() methods to Post model; renamed service parameters from $body to $content; added hidden content-json field and EditorJS initialization in post creation/editing views; added database migration to create content_raw column
Blog Content Rendering
src/Cms/Controllers/Blog.php, resources/views/blog/show.php
Integrated EditorJsRenderer, ShortcodeParser, and WidgetRenderer services in Blog controller to process and render post content; added comprehensive CSS styling for rendered content in blog view; replaced renderedContent fallback logic
Authentication Event Emissions
src/Cms/Services/Auth/Authentication.php, src/Cms/Services/Auth/EmailVerifier.php, src/Cms/Services/Auth/PasswordResetter.php, src/Cms/Maintenance/MaintenanceManager.php
Added event emissions for login/logout/failure scenarios, email verification, password reset requests/completions, and maintenance mode enable/disable; updated MaintenanceManager.disable() signature to include optional $disabledBy parameter
CLI Command Removals
src/Cms/Cli/Commands/Generate/EmailCommand.php, src/Cms/Cli/Commands/Generate/stubs/email.stub, src/Cms/Cli/Commands/Queue/InstallCommand.php, src/Cms/Cli/Provider.php
Removed EmailCommand and Queue\InstallCommand CLI commands and their registrations from Provider
Content Controller & Factories
src/Cms/Controllers/Content.php
Updated Version class loading to use factory method Factories\Version::fromFile(...) instead of direct instantiation; added default page parameter handling with null-coalescing operator
Error Page
resources/views/http_codes/500.php
Added static 500 error page with centered error message and inline styling
Test Updates (EditorJS Content)
tests/Cms/Services/Post/CreatorTest.php, tests/Cms/Services/Post/UpdaterTest.php
Updated post creation/update tests to use EditorJS JSON content format instead of plain text; added assertions for getContentRaw() verification

Sequence Diagram

sequenceDiagram
    participant User as User (Browser)
    participant Editor as EditorJS Instance
    participant Form as Post Form
    participant Server as Server/Controller
    participant Model as Post Model
    participant DB as Database
    participant Renderer as EditorJsRenderer
    participant View as Blog View

    User->>Editor: Edit/Create Post Content
    Editor->>Editor: Capture JSON blocks
    Editor->>Form: Update hidden field (content-json)
    User->>Form: Submit Form
    Form->>Server: POST with content-json
    Server->>Model: setContent(jsonString)
    Model->>Model: Decode JSON & extract plain text
    Model->>Model: Store _contentRaw & _body
    Server->>DB: Save Post
    DB-->>Server: Saved
    
    rect rgb(200, 220, 240)
    Note over Server,View: Content Display Flow
    Server->>Renderer: render(contentRaw)
    Renderer->>Renderer: Parse EditorJS blocks
    Renderer->>Renderer: Apply ShortcodeParser
    Renderer->>Renderer: Apply WidgetRenderer
    Renderer-->>Server: Rendered HTML
    Server->>View: Pass renderedContent
    View->>View: Apply CSS styling
    View-->>User: Display formatted post
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring extra attention:

  • EditorJS Integration: Verify content serialization/deserialization logic in Post model methods (setContent(), getContent(), extractPlainText()), and ensure Editor.js initialization in both create and edit views handles existing content correctly
  • Event Emission Logic: Review authentication event emissions in Authentication.php for correct event construction, IP address handling, and session duration tracking; verify MaintenanceManager event emissions occur in correct success paths
  • Database Migration: Confirm the migration creates the content_raw column with correct defaults and placement, and verify backward compatibility with existing posts
  • Renderer Service Integration: Ensure EditorJsRenderer, ShortcodeParser, and WidgetRenderer are correctly chained in Blog controller and produce expected HTML output
  • CLI Command Removals: Assess if any external code or documentation references the removed mail:generate and queue:install commands
  • Namespace Migration Coverage: Verify all ~30 import path updates are consistent and no mixed references to old/new namespace paths remain

Possibly related PRs

Poem

A rabbit hops through Editor blocks with glee, 🐰
Blocks and events now dance so free!
From plain text to JSON, rich and deep,
Events whisper secrets we keep,
The garden of CMS grows, hop-hop-hoo! 🌱✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feature/blog editor upgrade' clearly identifies the main change: upgrading the blog editor to use a block-based editor system, which aligns with the extensive Editor.js integration throughout the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 80.95% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/blog-editor-upgrade

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/Cms/Services/Auth/Authentication.php (1)

226-249: Fix infinite recursion on stale sessions in user() / logout()

In user():

if( !$user )
{
	// Clear stale session if user no longer exists
	$this->logout();
}

If the session contains a user_id for a user that has been deleted, this calls logout(), which in turn calls $this->check() and $this->user(), triggering the same path again and causing infinite recursion (now with additional event logic on each loop).

Instead of delegating to logout(), clear the stale session inline:

-		$user = $this->_userRepository->findById( $userId );
-
-		if( !$user )
-		{
-			// Clear stale session if user no longer exists
-			$this->logout();
-		}
-
-		return $user;
+		$user = $this->_userRepository->findById( $userId );
+
+		if( !$user )
+		{
+			// Clear stale session and remember-me cookie without recursion
+			$this->_sessionManager->destroy();
+
+			if( isset( $_COOKIE['remember_token'] ) )
+			{
+				setcookie( 'remember_token', '', time() - 3600, '/', '', true, true );
+			}
+
+			return null;
+		}
+
+		return $user;

This preserves the intended behavior (dropping an invalid session and remember-me cookie) without risking a stack overflow or runaway event emissions when the underlying user record is missing.

src/Cms/Controllers/Blog.php (1)

87-93: "Not found" post will have empty content for rendering.

When a post is not found, a dummy Post object is created with setBody() but the new rendering pipeline uses getContent() which will return the default empty EditorJS structure {"blocks":[]}. This means the "not found" message won't be displayed.

Update the "not found" post to use the new content structure:

 		if( !$post )
 		{
 			$post = new Post();
 			$post->setTitle( 'Article Not Found' );
-			$post->setBody( 'The requested article does not exist.' );
+			$post->setContent( json_encode([
+				'blocks' => [
+					[
+						'type' => 'paragraph',
+						'data' => ['text' => 'The requested article does not exist.']
+					]
+				]
+			]) );
 			$post->setSlug( $slug );
 		}
🧹 Nitpick comments (16)
resources/views/http_codes/500.php (2)

8-20: Inline styles are appropriate for error pages.

Using inline styles ensures the page displays correctly even if external stylesheets fail to load, which is a good practice for error pages.

Optional: Consider adding responsive design for smaller screens:

 	.centered {
 		text-align: center;
-		margin-top: 100px; /* Adjust this value as needed */
+		margin-top: 10vh;
+		padding: 0 15px;
 	}
 
 	.centered-text {
 		text-align: center;
 		margin-top: 20px;
 		font-size: 16px;
 		color: #666;
+		padding: 0 15px;
 	}

1-6: Consider adding accessibility attributes and user escape hatches.

The error page would benefit from:

  • ARIA landmark roles for screen readers
  • A link back to the home page or contact information
-<div class="row">
-	<div class="col">
+<div class="row" role="main">
+	<div class="col">
 		<h1 class="centered">Something went wrong on our end.</h1>
 		<p class="centered-text">The server encountered an unexpected error. Please try again later.</p>
+		<p class="centered-text"><a href="/" style="color: #0066cc;">Return to Home</a></p>
 	</div>
 </div>
src/Cms/Repositories/DatabaseCategoryRepository.php (1)

211-217: GROUP BY may break on PostgreSQL

allWithPostCount() selects c.* but groups only by c.id; PostgreSQL’s stricter GROUP BY rules will reject this query. If you intend this repository to work against PostgreSQL (per the class doc), you’ll need to either:

  • Group by all selected categories columns, or
  • Select only grouped/aggregated columns.

Please confirm whether PostgreSQL is a supported target in your deployments; if so, consider adjusting the SELECT/GROUP BY to be portable.

composer.json (1)

15-27: Dependency updates align with the Settings refactor; confirm version ranges

The move to neuron-php/mvc: "0.9.*" and addition of neuron-php/data: "0.9.*" and neuron-php/scaffolding: "0.8.*" fit the new Settings namespace and tooling story.

One nuance: "0.9.*" can allow earlier 0.9.x releases than ^0.9.5 would have; in practice Composer will usually pick the latest matching version, but if you depend on features/bugfixes introduced in later 0.9.x, you may prefer a caret or a higher minimum.

Please make sure composer.lock is updated and CI (including migrations/tests that touch Settings) passes with these new constraints. If you want to avoid accidentally downgrading, consider tightening the constraints (e.g., ^0.9.5 / ^0.9.0) before merging.

src/Cms/Repositories/DatabasePostRepository.php (1)

84-103: Verify handling of the new content_raw column in posts

The new migration adds a non‑nullable content_raw column intended for Editor.js JSON, but this repository currently:

  • Inserts/updates only body/excerpt (no content_raw in the column/value lists), and
  • Does not include content_raw in the $data array passed to Post::fromArray().

Because the DB default will populate content_raw, this won’t break inserts, but any Editor.js JSON stored on the entity may not be persisted or hydrated unless other layers handle it.

Please double‑check how the Post model exposes raw Editor.js content (e.g., getContentRaw() / setContentRaw() or similar), and consider extending:

  • the INSERT/UPDATE statements, and
  • mapRowToPost()’s $data array
    to read/write content_raw if that field is meant to be first‑class in the domain.

Also applies to: 141-168, 440-455

src/Cms/Services/Auth/PasswordResetter.php (1)

106-110: LGTM! Event emissions enhance observability.

The addition of PasswordResetRequestedEvent and PasswordResetCompletedEvent provides valuable audit trails for password reset workflows. The fallback to 'unknown' for $_SERVER['REMOTE_ADDR'] appropriately handles CLI and testing contexts.

Consider importing the event classes at the top of the file rather than using fully qualified names for improved readability:

+use Neuron\Cms\Events\PasswordResetRequestedEvent;
+use Neuron\Cms\Events\PasswordResetCompletedEvent;
+use Neuron\Application\CrossCutting\Event;

Then use:

-		\Neuron\Application\CrossCutting\Event::emit( new \Neuron\Cms\Events\PasswordResetRequestedEvent(
+		Event::emit( new PasswordResetRequestedEvent(

Also applies to: 175-179

resources/views/admin/posts/edit.php (2)

24-31: Harden handling of existing EditorJS content JSON before initialization

Current parsing only falls back on JSON syntax errors; valid-but-unexpected JSON (e.g. "null" or an object without blocks) will flow into data and may confuse EditorJS.

You can defensively ensure a valid blocks array after parsing:

-let existingContent;
-try {
-	existingContent = JSON.parse(existingContentJson);
-} catch (error) {
-	console.error('Failed to parse existing content:', error);
-	existingContent = { blocks: [] };
-}
+let existingContent;
+try {
+	existingContent = JSON.parse(existingContentJson);
+
+	// Ensure we always pass a valid Editor.js data structure
+	if( !existingContent || !Array.isArray( existingContent.blocks ) )
+	{
+		existingContent = { blocks: [] };
+	}
+} catch (error) {
+	console.error('Failed to parse existing content:', error);
+	existingContent = { blocks: [] };
+}

This keeps the editor resilient to legacy or malformed content_raw values while preserving your existing fallback behavior.

Also applies to: 93-103, 104-109


82-90: Consider pinning EditorJS CDN versions and reducing save() frequency

The EditorJS integration and submit flow look solid. Two optional robustness points:

  • The @latest CDN URLs can introduce breaking changes when a new major EditorJS (or tool) version ships. Consider pinning to a specific major/minor version (e.g. @2.x or an exact version) and upgrading deliberately.
  • onChange currently calls editor.save() on every change, which is relatively heavy. If this becomes a performance issue for long posts, you could debounce/throttle the save into #content-json or rely solely on the submit handler to serialize content.

Both are optional, but would make the editor behavior more predictable in production.

Also applies to: 145-149, 151-163

tests/Cms/Services/Post/CreatorTest.php (1)

45-69: Centralize EditorJS content JSON in a helper/fixture for test readability

The tests correctly assert both getContentRaw() and the derived getBody(). The same EditorJS JSON pattern ({"blocks":[{"type":"paragraph","data":{"text":"Body"}}]} and variants) is repeated across multiple tests, though.

To keep things DRY and easier to tweak later, consider extracting small helpers/constants, e.g.:

private function makeEditorJsContent(string $text = 'Body'): string
{
	return json_encode([
		'blocks' => [
			[
				'type' => 'paragraph',
				'data' => ['text' => $text],
			],
		],
	]);
}

Then use makeEditorJsContent('Test body content') / makeEditorJsContent() throughout the tests instead of hardcoding JSON strings.

Also applies to: 90-95, 118-123, 148-153, 177-182, 218-223, 263-273, 297-305

src/Cms/Maintenance/MaintenanceManager.php (1)

61-82: Emit maintenance events without risking core enable/disable operations

The new enabled/disabled events are wired in cleanly, and the optional $disabledBy argument keeps the public API backward compatible.

One robustness refinement to consider: if any event listener hooked into MaintenanceModeEnabledEvent / MaintenanceModeDisabledEvent throws, callers of enable() / disable() will see an exception even though the maintenance file has already been written or deleted. To keep state changes reliable, you might wrap the emits:

-		$result = $this->writeMaintenanceFile( $data );
-
-		// Emit maintenance mode enabled event
-		if( $result )
-		{
-			\Neuron\Application\CrossCutting\Event::emit( new \Neuron\Cms\Events\MaintenanceModeEnabledEvent(
-				$data['enabled_by'],
-				$message
-			) );
-		}
+		$result = $this->writeMaintenanceFile( $data );
+
+		// Emit maintenance mode enabled event (non-fatal)
+		if( $result )
+		{
+			try
+			{
+				\Neuron\Application\CrossCutting\Event::emit(
+					new \Neuron\Cms\Events\MaintenanceModeEnabledEvent(
+						$data['enabled_by'],
+						$message
+					)
+				);
+			}
+			catch( \Throwable $e )
+			{
+				// Optional: log but do not affect enable() result
+			}
+		}

and similarly in disable(). That keeps maintenance mode toggling deterministic even if downstream listeners misbehave.

Also applies to: 87-107

src/Cms/Services/Auth/Authentication.php (1)

41-59: Event emissions for login attempts look good; consider small refactors

The new UserLoginFailedEvent, UserLoginEvent, and UserLogoutEvent hooks are well-placed and give you good observability over auth flows. A couple of low-friction refinements:

  • The four failure branches emit UserLoginFailedEvent with only the reason differing. You could extract a small private helper like emitLoginFailed(string $username, string $reason): void to reduce duplication and keep changes localized if you later enrich the payload.
  • In login(), you call microtime( true ) both when setting login_time in the session and when emitting UserLoginEvent. Reusing a single timestamp would keep those values perfectly aligned:
-		// Store user ID in session
-		$this->_sessionManager->set( 'user_id', $user->getId() );
-		$this->_sessionManager->set( 'user_role', $user->getRole() );
-		$this->_sessionManager->set( 'login_time', microtime( true ) );
+		// Store user ID in session
+		$this->_sessionManager->set( 'user_id', $user->getId() );
+		$this->_sessionManager->set( 'user_role', $user->getRole() );
+		$loginTime = microtime( true );
+		$this->_sessionManager->set( 'login_time', $loginTime );
@@
-		// Emit user login event
-		\Neuron\Application\CrossCutting\Event::emit( new \Neuron\Cms\Events\UserLoginEvent(
-			$user,
-			$_SERVER['REMOTE_ADDR'] ?? 'unknown',
-			microtime( true )
-		) );
+		// Emit user login event
+		\Neuron\Application\CrossCutting\Event::emit( new \Neuron\Cms\Events\UserLoginEvent(
+			$user,
+			$_SERVER['REMOTE_ADDR'] ?? 'unknown',
+			$loginTime
+		) );

Overall the behavior is sound; these are just to keep things DRY and metrics-friendly.

Also applies to: 61-85, 87-110, 134-156, 161-201

tests/Cms/Services/Post/UpdaterTest.php (1)

39-42: Mirror CreatorTest by centralizing EditorJS JSON for updates

These tests correctly validate that Updater::update() preserves both the raw EditorJS JSON (getContentRaw()) and the derived body text (getBody()), and that other fields (slug, status, categories, tags, etc.) behave as expected.

As in CreatorTest, you might want to centralize the repeated JSON snippets into a small helper (on this class or a shared trait) so any future changes to your EditorJS block shape only need to be made in one place.

Also applies to: 54-70, 97-99, 127-128, 171-172, 218-219, 254-255, 285-286, 321-322

resources/views/admin/posts/create.php (1)

131-142: Consider potential issues with programmatic form submission.

The submit handler prevents the default form submission, saves editor content, then calls e.target.submit(). This bypasses normal HTML5 validation and could cause issues if there are other submit handlers or validation logic.

Consider using FormData to submit the form programmatically while maintaining validation:

-document.getElementById('post-form').addEventListener('submit', async (e) => {
-	e.preventDefault();
-
-	try {
-		const savedData = await editor.save();
-		document.getElementById('content-json').value = JSON.stringify(savedData);
-		e.target.submit();
-	} catch (error) {
-		console.error('Error saving editor content:', error);
-		alert('Error preparing content. Please try again.');
-	}
-});
+document.getElementById('post-form').addEventListener('submit', async (e) => {
+	// Don't prevent default initially - let HTML5 validation run first
+	if (!e.target.checkValidity()) {
+		return; // Let browser handle validation
+	}
+	
+	e.preventDefault();
+	
+	try {
+		const savedData = await editor.save();
+		document.getElementById('content-json').value = JSON.stringify(savedData);
+		e.target.submit();
+	} catch (error) {
+		console.error('Error saving editor content:', error);
+		alert('Error preparing content. Please try again.');
+	}
+});
src/Cms/Controllers/Blog.php (1)

106-108: Add error handling for content rendering.

The renderer should handle malformed JSON or rendering errors gracefully to prevent the page from breaking when post content is invalid.

Wrap the rendering in a try-catch block:

-		// Render content from Editor.js JSON
-		$content = $post->getContent();
-		$renderedContent = $this->_renderer->render( $content );
+		// Render content from Editor.js JSON
+		$content = $post->getContent();
+		try {
+			$renderedContent = $this->_renderer->render( $content );
+		} catch (\Exception $e) {
+			error_log("Error rendering post content for slug '{$slug}': " . $e->getMessage());
+			$renderedContent = '<p>Content temporarily unavailable.</p>';
+		}
src/Cms/Models/Post.php (2)

125-128: Add error handling for malformed JSON in getContent().

If _contentRaw contains invalid JSON, json_decode() will return null and the ?? operator will correctly return the default, but this happens silently. Consider logging the error for debugging.

 	public function getContent(): array
 	{
-		return json_decode( $this->_contentRaw, true ) ?? ['blocks' => []];
+		$decoded = json_decode( $this->_contentRaw, true );
+		if ($decoded === null && json_last_error() !== JSON_ERROR_NONE) {
+			error_log("Invalid JSON in Post content_raw (ID: {$this->_id}): " . json_last_error_msg());
+		}
+		return $decoded ?? ['blocks' => []];
 	}

607-646: Consider XSS risks in extractPlainText() for 'raw' block type.

Line 633 includes raw HTML from raw blocks without sanitization when extracting plain text. While strip_tags() is applied at line 640, malicious HTML could still be present in the intermediate processing.

Since this is meant to be plain text extraction, consider whether raw blocks should be included at all, or strip tags immediately:

 			$blockText = match( $block['type'] )
 			{
 				'paragraph', 'header' => $block['data']['text'] ?? '',
 				'list' => isset( $block['data']['items'] ) && is_array( $block['data']['items'] )
 					? implode( "\n", $block['data']['items'] )
 					: '',
 				'quote' => $block['data']['text'] ?? '',
 				'code' => $block['data']['code'] ?? '',
-				'raw' => $block['data']['html'] ?? '',
+				'raw' => strip_tags($block['data']['html'] ?? ''),  // Strip tags immediately for raw HTML
 				default => ''
 			};
 
 			if( $blockText !== '' )
 			{
 				// Strip HTML tags from text
 				$blockText = strip_tags( $blockText );
 				$text[] = trim( $blockText );
 			}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 67ec356 and 9ebd824.

📒 Files selected for processing (61)
  • composer.json (2 hunks)
  • resources/app/Initializers/AuthInitializer.php (1 hunks)
  • resources/app/Initializers/MaintenanceInitializer.php (1 hunks)
  • resources/app/Initializers/PasswordResetInitializer.php (1 hunks)
  • resources/app/Initializers/RegistrationInitializer.php (1 hunks)
  • resources/database/migrate/20251119224525_add_content_raw_to_posts.php (1 hunks)
  • resources/views/admin/posts/create.php (3 hunks)
  • resources/views/admin/posts/edit.php (3 hunks)
  • resources/views/blog/show.php (2 hunks)
  • resources/views/http_codes/500.php (1 hunks)
  • src/Bootstrap.php (1 hunks)
  • src/Cms/Cli/Commands/Generate/EmailCommand.php (0 hunks)
  • src/Cms/Cli/Commands/Generate/stubs/email.stub (0 hunks)
  • src/Cms/Cli/Commands/Install/InstallCommand.php (1 hunks)
  • src/Cms/Cli/Commands/Maintenance/EnableCommand.php (1 hunks)
  • src/Cms/Cli/Commands/Queue/InstallCommand.php (0 hunks)
  • src/Cms/Cli/Provider.php (0 hunks)
  • src/Cms/Controllers/Admin/Categories.php (1 hunks)
  • src/Cms/Controllers/Admin/Profile.php (1 hunks)
  • src/Cms/Controllers/Admin/Users.php (1 hunks)
  • src/Cms/Controllers/Blog.php (4 hunks)
  • src/Cms/Controllers/Content.php (4 hunks)
  • src/Cms/Database/ConnectionFactory.php (1 hunks)
  • src/Cms/Email/helpers.php (1 hunks)
  • src/Cms/Events/EmailVerifiedEvent.php (1 hunks)
  • src/Cms/Events/MaintenanceModeDisabledEvent.php (1 hunks)
  • src/Cms/Events/MaintenanceModeEnabledEvent.php (1 hunks)
  • src/Cms/Events/PasswordResetCompletedEvent.php (1 hunks)
  • src/Cms/Events/PasswordResetRequestedEvent.php (1 hunks)
  • src/Cms/Events/UserLoginEvent.php (1 hunks)
  • src/Cms/Events/UserLoginFailedEvent.php (1 hunks)
  • src/Cms/Events/UserLogoutEvent.php (1 hunks)
  • src/Cms/Maintenance/MaintenanceConfig.php (1 hunks)
  • src/Cms/Maintenance/MaintenanceManager.php (1 hunks)
  • src/Cms/Models/Post.php (5 hunks)
  • src/Cms/Repositories/DatabaseCategoryRepository.php (1 hunks)
  • src/Cms/Repositories/DatabaseEmailVerificationTokenRepository.php (1 hunks)
  • src/Cms/Repositories/DatabasePageRepository.php (1 hunks)
  • src/Cms/Repositories/DatabasePasswordResetTokenRepository.php (1 hunks)
  • src/Cms/Repositories/DatabasePostRepository.php (1 hunks)
  • src/Cms/Repositories/DatabaseTagRepository.php (1 hunks)
  • src/Cms/Repositories/DatabaseUserRepository.php (1 hunks)
  • src/Cms/Services/Auth/Authentication.php (5 hunks)
  • src/Cms/Services/Auth/EmailVerifier.php (2 hunks)
  • src/Cms/Services/Auth/PasswordResetter.php (3 hunks)
  • src/Cms/Services/Email/Sender.php (1 hunks)
  • src/Cms/Services/Member/RegistrationService.php (1 hunks)
  • src/Cms/Services/Post/Creator.php (3 hunks)
  • src/Cms/Services/Post/Updater.php (3 hunks)
  • tests/Cms/BlogControllerTest.php (1 hunks)
  • tests/Cms/Cli/Commands/Install/InstallCommandTest.php (1 hunks)
  • tests/Cms/ContentControllerTest.php (1 hunks)
  • tests/Cms/Maintenance/MaintenanceConfigTest.php (1 hunks)
  • tests/Cms/Repositories/DatabaseEmailVerificationTokenRepositoryTest.php (1 hunks)
  • tests/Cms/Repositories/DatabaseUserRepositoryTest.php (1 hunks)
  • tests/Cms/Services/AuthenticationTest.php (1 hunks)
  • tests/Cms/Services/EmailVerifierTest.php (1 hunks)
  • tests/Cms/Services/PasswordResetterTest.php (1 hunks)
  • tests/Cms/Services/Post/CreatorTest.php (9 hunks)
  • tests/Cms/Services/Post/UpdaterTest.php (10 hunks)
  • tests/Cms/Services/RegistrationServiceTest.php (1 hunks)
💤 Files with no reviewable changes (4)
  • src/Cms/Cli/Provider.php
  • src/Cms/Cli/Commands/Generate/EmailCommand.php
  • src/Cms/Cli/Commands/Queue/InstallCommand.php
  • src/Cms/Cli/Commands/Generate/stubs/email.stub
🧰 Additional context used
🧬 Code graph analysis (15)
src/Cms/Events/PasswordResetRequestedEvent.php (2)
src/Cms/Events/EmailVerifiedEvent.php (2)
  • __construct (29-33)
  • getName (35-38)
src/Cms/Events/PasswordResetCompletedEvent.php (2)
  • __construct (30-35)
  • getName (37-40)
resources/views/admin/posts/create.php (1)
src/Cms/View/helpers.php (1)
  • route_path (70-79)
src/Cms/Events/MaintenanceModeDisabledEvent.php (2)
src/Cms/Events/EmailVerifiedEvent.php (2)
  • __construct (29-33)
  • getName (35-38)
src/Cms/Events/MaintenanceModeEnabledEvent.php (2)
  • __construct (30-35)
  • getName (37-40)
src/Cms/Events/UserLoginEvent.php (3)
src/Cms/Events/EmailVerifiedEvent.php (2)
  • __construct (29-33)
  • getName (35-38)
src/Cms/Events/UserLoginFailedEvent.php (2)
  • __construct (31-38)
  • getName (40-43)
src/Cms/Events/UserLogoutEvent.php (2)
  • __construct (30-35)
  • getName (37-40)
src/Cms/Events/UserLoginFailedEvent.php (3)
src/Cms/Events/EmailVerifiedEvent.php (2)
  • __construct (29-33)
  • getName (35-38)
src/Cms/Events/UserLoginEvent.php (2)
  • __construct (31-37)
  • getName (39-42)
src/Cms/Events/UserLogoutEvent.php (2)
  • __construct (30-35)
  • getName (37-40)
src/Cms/Maintenance/MaintenanceManager.php (2)
src/Cms/Events/MaintenanceModeEnabledEvent.php (1)
  • MaintenanceModeEnabledEvent (24-41)
src/Cms/Events/MaintenanceModeDisabledEvent.php (1)
  • MaintenanceModeDisabledEvent (24-39)
tests/Cms/Services/Post/CreatorTest.php (2)
src/Cms/Controllers/Content.php (1)
  • getTitle (141-144)
src/Cms/Models/Post.php (2)
  • getTitle (74-77)
  • getContentRaw (133-136)
src/Cms/Events/MaintenanceModeEnabledEvent.php (2)
src/Cms/Events/EmailVerifiedEvent.php (2)
  • __construct (29-33)
  • getName (35-38)
src/Cms/Events/MaintenanceModeDisabledEvent.php (2)
  • __construct (29-33)
  • getName (35-38)
src/Cms/Services/Post/Creator.php (1)
src/Cms/Models/Post.php (1)
  • setContent (142-147)
src/Cms/Services/Auth/EmailVerifier.php (2)
src/Cms/Events/EmailVerifiedEvent.php (1)
  • EmailVerifiedEvent (24-39)
src/Cms/Services/Auth/Authentication.php (1)
  • user (226-249)
resources/views/admin/posts/edit.php (2)
src/Cms/View/helpers.php (1)
  • route_path (70-79)
src/Cms/Models/Post.php (2)
  • getId (57-60)
  • getContentRaw (133-136)
src/Cms/Services/Auth/Authentication.php (6)
src/Cms/Events/UserLoginFailedEvent.php (1)
  • UserLoginFailedEvent (23-44)
src/Cms/Models/User.php (2)
  • isLockedOut (332-340)
  • isActive (187-190)
src/Cms/Auth/SessionManager.php (1)
  • set (99-103)
src/Cms/Events/UserLoginEvent.php (1)
  • UserLoginEvent (24-43)
src/Cms/Controllers/Auth/Login.php (1)
  • logout (139-143)
src/Cms/Events/UserLogoutEvent.php (1)
  • UserLogoutEvent (24-41)
resources/database/migrate/20251119224525_add_content_raw_to_posts.php (1)
src/Cms/Services/Post/Updater.php (1)
  • update (48-83)
tests/Cms/Services/Post/UpdaterTest.php (2)
src/Cms/Models/Post.php (5)
  • setContent (142-147)
  • getTitle (74-77)
  • getContentRaw (133-136)
  • getBody (108-111)
  • getStatus (246-249)
src/Cms/Repositories/DatabasePostRepository.php (1)
  • update (127-187)
src/Cms/Services/Post/Updater.php (1)
src/Cms/Models/Post.php (1)
  • setContent (142-147)
🔇 Additional comments (54)
src/Cms/Maintenance/MaintenanceConfig.php (1)

5-5: Namespace migration is complete and verified.

The import change from Neuron\Data\Setting to Neuron\Data\Settings on line 5 is correct. Verification confirms no remaining old namespace references exist in the codebase, and the new namespace is consistently used across 20 files (production code and tests).

src/Cms/Cli/Commands/Maintenance/EnableCommand.php (1)

8-8: Yaml settings source namespace update looks correct

The Yaml import now targets Neuron\Data\Settings\Source\Yaml, which matches how it’s used in loadConfiguration() and aligns with the Settings namespace migration; no further changes needed here as long as the installed neuron-php/data version exposes this namespace.

src/Cms/Services/Email/Sender.php (1)

7-7: SettingManager import aligned with Settings namespace

Switching to Neuron\Data\Settings\SettingManager is consistent with the property/ctor type hints and the rest of the Settings migration; the email config access remains unchanged.

src/Cms/Services/Member/RegistrationService.php (1)

9-9: RegistrationService now correctly targets Settings\SettingManager

The import of Neuron\Data\Settings\SettingManager matches the constructor and property types and keeps the member settings lookups intact.

src/Cms/Controllers/Admin/Categories.php (1)

11-11: Categories controller import updated to Settings\SettingManager

The controller now imports Neuron\Data\Settings\SettingManager, keeping it consistent with the rest of the Settings namespace migration in the project.

tests/Cms/Maintenance/MaintenanceConfigTest.php (1)

6-6: Test Yaml source import matches new Settings namespace

Using Neuron\Data\Settings\Source\Yaml in this test is consistent with the production MaintenanceConfig usage and the broader Settings namespace move.

src/Cms/Repositories/DatabaseEmailVerificationTokenRepository.php (1)

7-7: Repository constructor now depends on Settings\SettingManager

The repository correctly imports Neuron\Data\Settings\SettingManager and passes it to ConnectionFactory::createFromSettings(), keeping database configuration behavior unchanged under the new namespace.

tests/Cms/Repositories/DatabaseEmailVerificationTokenRepositoryTest.php (1)

8-8: Test mock updated to Settings\SettingManager

The test now mocks Neuron\Data\Settings\SettingManager, matching the repository’s constructor signature and ensuring the database config wiring remains valid.

tests/Cms/ContentControllerTest.php (1)

6-6: In-memory settings source switched to Settings\Source\Memory

The test now uses Neuron\Data\Settings\Source\Memory, which is consistent with the Settings namespace refactor and how the controller reads settings via the Registry.

src/Cms/Repositories/DatabaseUserRepository.php (1)

7-7: Repository constructor now aligned with Settings namespace

The repository’s dependency on SettingManager is correctly switched to the Neuron\Data\Settings namespace, keeping the constructor type-hint and ConnectionFactory::createFromSettings() usage consistent.

Please run your user-related tests (e.g., DatabaseUserRepositoryTest, auth flows) to confirm there are no remaining references to the old Neuron\Data\Setting\SettingManager type.

src/Cms/Repositories/DatabasePageRepository.php (1)

8-8: Page repository SettingManager import migrated cleanly

The constructor’s SettingManager dependency now points at Neuron\Data\Settings\SettingManager, matching the rest of the PR’s namespace migration without altering any repository behavior.

Confirm that any code instantiating DatabasePageRepository (controllers/services) is also using the updated Settings-namespace SettingManager instance from the container/registry.

src/Cms/Repositories/DatabaseTagRepository.php (1)

7-7: Tag repository settings dependency updated to new namespace

DatabaseTagRepository now correctly imports Neuron\Data\Settings\SettingManager while keeping the constructor contract and internals the same, so behavior should be unaffected.

As part of your test run, ensure any install/seed or tag-related CLI flows still resolve the correct SettingManager implementation.

tests/Cms/Cli/Commands/Install/InstallCommandTest.php (1)

8-9: Install command test imports aligned with Settings namespace

The test now imports Neuron\Data\Settings\SettingManager and Neuron\Data\Settings\Source\Yaml, matching the production code’s updated namespaces and avoiding mismatch with the newer data/settings package layout.

If these imports are no longer needed by the test, they can be safely removed in a later cleanup pass once static analysis (e.g., phpstan/psalm) is green.

src/Cms/Controllers/Admin/Users.php (1)

13-13: Admin Users controller now consistent with Settings-based repositories

The controller’s import of Neuron\Data\Settings\SettingManager matches the updated constructor dependency of DatabaseUserRepository, keeping the Registry-provided 'Settings' instance aligned with the repository’s expected type.

Please verify that the Registry is registering an instance of the new Neuron\Data\Settings\SettingManager class under the 'Settings' key in your bootstrap/initializer code.

src/Bootstrap.php (1)

4-5: Bootstrap imports updated to new data library namespaces

Version and Yaml now import from Neuron\Data\Objects\Version and Neuron\Data\Settings\Source\Yaml, which matches the reorganized data library structure without changing the boot() logic.

After updating composer dependencies, run a full application bootstrap (and any bootstrap-related tests) to confirm there are no unresolved references to the old Neuron\Data\Object\Version or Neuron\Data\Setting\Source\Yaml classes.

src/Cms/Controllers/Admin/Profile.php (1)

10-10: Admin Profile controller settings import matches repository expectations

The profile controller’s import now targets Neuron\Data\Settings\SettingManager, aligning its dependency graph with the updated DatabaseUserRepository and the broader Settings namespace migration.

As with the Users controller, double‑check that the Registry’s 'Settings' entry is an instance of the new Settings\SettingManager to avoid subtle type mismatches at runtime.

src/Cms/Database/ConnectionFactory.php (1)

5-5: Namespace migration verified as complete

The import change to Neuron\Data\Settings\SettingManager is correct and actively used. Verification confirms:

  • No lingering references to the old Neuron\Data\Setting namespace exist
  • The type hint in createFromSettings() (line 26) properly references the new SettingManager class
  • The method implementation correctly delegates to createFromConfig() as expected

The migration is consistent and safe to merge.

src/Cms/Repositories/DatabaseCategoryRepository.php (1)

7-7: Settings namespace update is consistent

Switching to Neuron\Data\Settings\SettingManager here matches the broader Settings namespace refactor and should be a no‑op at runtime as long as the updated neuron‑php/data dependency is installed.

Please run the test suite after composer update with the new dependencies to confirm there are no autoloading or wiring issues around SettingManager.

tests/Cms/Repositories/DatabaseUserRepositoryTest.php (1)

8-8: Updated SettingManager import in tests looks good

Using Neuron\Data\Settings\SettingManager in the mocks keeps the test suite aligned with the refactored Settings namespace; behavior remains unchanged because the class is mocked.

After updating dependencies, please run this test class to ensure the new namespace resolves correctly under your autoloader.

src/Cms/Repositories/DatabasePostRepository.php (1)

10-10: Settings namespace import updated correctly

Referencing Neuron\Data\Settings\SettingManager here keeps this repository in sync with the Settings refactor and ConnectionFactory usage; the constructor contract remains the same.

After dependency updates, please run a small smoke test that instantiates DatabasePostRepository via your usual bootstrap path to confirm the new namespace resolves as expected.

src/Cms/Email/helpers.php (1)

4-4: Email helpers now reference the new Settings namespace

Updating the type to Neuron\Data\Settings\ISettingSource keeps these helpers compatible with the refactored Settings package without changing their runtime behavior.

Please ensure the updated Settings package is present in all environments where these helpers are used (including CLI contexts) and that any existing call sites still pass a compatible settings source.

resources/database/migrate/20251119224525_add_content_raw_to_posts.php (1)

13-22: Migration for content_raw is straightforward; verify adapter support for options

Adding content_raw as non‑nullable TEXT with a default empty Editor.js document is reasonable and should keep existing rows valid.

The use of 'after' => 'body' and 'comment' => 'Editor.js JSON content' is typically MySQL‑specific in Phinx; depending on how strictly you target SQLite/PostgreSQL as well, those options may be ignored or could need adapter‑specific handling.

Please confirm that this migration runs cleanly against all database engines you support (especially in CI and local dev). If you see adapter‑specific issues, you may want to conditionally set after/comment or drop them for non‑MySQL adapters.

src/Cms/Events/EmailVerifiedEvent.php (1)

24-38: EmailVerifiedEvent implementation looks solid

The event cleanly encapsulates the verified User via a readonly promoted property and exposes a clear, stable name ('email.verified') for subscribers. The design is consistent with the other event classes in this PR.

src/Cms/Events/MaintenanceModeEnabledEvent.php (1)

24-40: MaintenanceModeEnabledEvent is consistent with the event model

Readonly constructor‑promoted properties for enabledBy and message plus a stable name 'maintenance.enabled' match the existing disabled‑event pattern and give listeners all relevant context.

src/Cms/Events/UserLoginEvent.php (1)

24-42: UserLoginEvent structure is consistent and ready for consumers

The event payload (User, IP, timestamp) and name user.login align with the other event types you’ve added and give downstream listeners enough context without extra coupling. No issues spotted.

src/Cms/Controllers/Content.php (2)

3-4: Version loading via factory is a clean upgrade

Switching to Factories\Version::fromFile("../.version.json") while keeping the broad \Exception catch preserves existing behavior and centralizes version deserialization in the data library. As long as the working directory is stable for web/CLI entry points, this looks good.

Also applies to: 51-55, 85-94


201-213: Defaulting markdown page route to index improves resilience

Using $request->getRouteParameter('page') ?? 'index' avoids failures when the route omits page and matches the typical “root markdown page” expectation. Just confirm your router/front-controller has an index.md (or equivalent) to back this default.

src/Cms/Repositories/DatabasePasswordResetTokenRepository.php (1)

5-8: Settings namespace refactor is consistent

Using Neuron\Data\Settings\SettingManager here matches the broader Settings namespace migration and keeps the existing constructor contract intact. No repository behavior changes introduced.

Also applies to: 23-32

tests/Cms/Services/PasswordResetterTest.php (1)

11-13: Test settings imports now align with production namespaces

Updating to Settings\Source\Memory and Settings\SettingManager keeps the test configuration in sync with the data layer refactor. The in-memory setup logic remains the same and should continue to exercise PasswordResetter as before.

Also applies to: 34-42

tests/Cms/Services/EmailVerifierTest.php (1)

10-12: EmailVerifier tests correctly track the Settings namespace change

The move to Neuron\Data\Settings\Source\Memory and Settings\SettingManager mirrors the production code and leaves the test behavior intact (test-mode email, same keys). Looks good.

Also applies to: 31-39

resources/app/Initializers/RegistrationInitializer.php (1)

31-38: Registration initializer now guards against the correct Settings manager type

Switching the instanceof check to \Neuron\Data\Settings\SettingManager matches the new Settings namespace and preserves the early-exit behavior when configuration isn’t wired. No additional issues here.

src/Cms/Services/Auth/EmailVerifier.php (1)

10-11: I'll help you verify the review comment about email verification event emission safety. Let me start by exploring the codebase to understand how the Event::emit() method handles missing emitters and listener failures.
<function_calls>


#!/bin/bash

Find the Event class definition

rg -n "class Event" --type=php

Find emit method definitions

rg -n "function emit|public function emit" --type=php -A 5

</function_calls>

src/Cms/Services/Post/Creator.php (1)

35-40: Confirmed: API change is complete and well-implemented internally, but lacks external documentation

The migration is thorough within the repository:

  • Post model correctly implements setContent() with EditorJS JSON, auto-extracting plain text to _body for backward compatibility
  • Creator service properly uses setContent()
  • Admin form is fully updated with Editor.js frontend that serializes to EditorJS JSON
  • Database schema includes content_raw column for JSON storage
  • Tests pass EditorJS JSON correctly

However, this package is publicly distributed via Composer ("neuron-php/cms" library type), making Creator::create() a public API. The breaking change—requiring EditorJS JSON instead of rendered body strings—lacks a migration guide or changelog. No backward compatibility wrapper exists.

tests/Cms/Services/RegistrationServiceTest.php (1)

10-11: LGTM! Namespace migration applied correctly.

The import paths have been updated to use the new Neuron\Data\Settings namespace, aligning with the project-wide refactoring.

resources/app/Initializers/PasswordResetInitializer.php (1)

9-9: LGTM! Namespace migration applied correctly.

The import path has been updated to use the new Neuron\Data\Settings\SettingManager namespace.

src/Cms/Cli/Commands/Install/InstallCommand.php (1)

9-10: LGTM! Namespace migration applied correctly.

The import paths have been updated to use the new Neuron\Data\Settings namespace.

tests/Cms/Services/AuthenticationTest.php (1)

11-11: LGTM! Namespace migration applied correctly.

The import path has been updated to use the new Neuron\Data\Settings\SettingManager namespace.

tests/Cms/BlogControllerTest.php (1)

13-14: LGTM! Namespace migration applied correctly.

The import paths have been updated to use the new Neuron\Data\Settings namespace.

resources/app/Initializers/AuthInitializer.php (1)

32-32: LGTM! Type check updated for namespace migration.

The instanceof check has been updated to reference the new Neuron\Data\Settings\SettingManager namespace.

resources/views/blog/show.php (2)

51-160: LGTM! Comprehensive styling for EditorJS content.

The CSS additions provide proper styling for post content and EditorJS widgets, including headings, lists, blockquotes, code blocks, images, and custom widgets.


39-39: I'll help you verify the concerns raised in the review comment. Let me search the codebase for the Blog controller and examine how $renderedContent is set.
<function_calls>

#!/bin/bash

Find the Blog controller

fd -t f -i "blog" --type=php | head -20


</function_calls>

Let me search more specifically for the Blog controller:
<function_calls>

#!/bin/bash

Search for Blog controller class definition

rg -n "class Blog" --type=php -A 5


</function_calls>

Now let me search for where the show view is rendered:
<function_calls>

#!/bin/bash

Search for show view rendering and renderedContent variable

rg -n "renderedContent|show.php|blog/show" --type=php -B 3 -A 3


</function_calls>

Let me also check the structure of the resources/views/blog directory:
<function_calls>

#!/bin/bash

List files in blog views directory

fd -t f "show.php" && echo "---" && fd -t f "blog" resources/views/ 2>/dev/null | head -10


</function_calls>

Let me check the full repository structure to understand the layout:
<function_calls>

#!/bin/bash

Get an overview of the repository structure

git ls-files | grep -E "(controller|blog)" | head -20


</function_calls>

src/Cms/Services/Auth/PasswordResetter.php (1)

10-10: LGTM! Namespace migration applied correctly.

The import path has been updated to use the new Neuron\Data\Settings\SettingManager namespace.

resources/app/Initializers/MaintenanceInitializer.php (1)

45-59: Updated Settings manager namespace check looks correct

Switching the instanceof check to \Neuron\Data\Settings\SettingManager aligns this initializer with the new Settings namespace without changing runtime behavior. No further changes needed here.

src/Cms/Events/PasswordResetCompletedEvent.php (1)

24-40: PasswordResetCompletedEvent structure and naming are consistent

Readonly payload (User + IP) and the password.reset_completed name match the existing event style in this module. Good to go.

src/Cms/Events/PasswordResetRequestedEvent.php (1)

24-40: PasswordResetRequestedEvent aligns with the existing event model

Readonly User + IP payload and the password.reset_requested name follow the same pattern as your other events. Looks good as a foundation for security monitoring and auditing.

src/Cms/Events/UserLogoutEvent.php (1)

1-41: LGTM!

The event class is well-structured with clear documentation, follows the established event pattern, and properly uses constructor property promotion. The inclusion of sessionDuration is a valuable addition for analytics.

src/Cms/Controllers/Blog.php (1)

40-44: LGTM! Well-structured dependency initialization.

The renderer initialization properly chains dependencies (WidgetRenderer → ShortcodeParser → EditorJsRenderer), creating a clean content processing pipeline.

src/Cms/Events/UserLoginFailedEvent.php (1)

1-44: LGTM!

The event class is well-designed for security monitoring and audit trails. The inclusion of reason, ip, and timestamp provides comprehensive context for failed login attempts, which is essential for security analysis and compliance.

src/Cms/Events/MaintenanceModeDisabledEvent.php (1)

8-39: LGTM! Event structure is appropriate.

The event class is well-documented and follows the established pattern. Using a string for $disabledBy (username/identifier) is consistent with the EnabledEvent and provides flexibility.

src/Cms/Services/Post/Updater.php (1)

39-62: LGTM! Clean migration to EditorJS content handling.

The parameter rename from $body to $content and the corresponding method call change from setBody() to setContent() properly reflect the shift to EditorJS JSON format. The updated docblock clearly documents this change.

src/Cms/Models/Post.php (3)

156-169: LGTM! Good error handling for JSON encoding.

The setContentArray() method properly handles JSON encoding failures with an explicit exception, which is better than silent failures. The use of \JsonException is appropriate.


501-523: Well-designed content handling in fromArray().

The method properly handles both content_raw and content keys, and supports both string and array formats for each. This provides good flexibility for different data sources while maintaining backward compatibility.


625-635: Use match expression correctly handles EditorJS block types.

The match expression properly extracts text from various EditorJS block types. The handling of list items with implode() and the fallback to empty string for unknown types is appropriate.

resources/views/admin/posts/create.php (1)

104-108: The image upload endpoint handler for /admin/upload/image is not implemented in the codebase.

While the codebase has a robust CSRF protection system in place (CsrfFilter and CsrfToken service), the actual route handler for the /admin/upload/image endpoint referenced in the frontend editor configuration (create.php and edit.php) could not be located. The endpoint is configured in the views but no corresponding PHP route handler exists in src/Cms/Controllers/ or elsewhere.

Recommendation: Implement the /admin/upload/image route handler and ensure it is protected with the CsrfFilter to validate CSRF tokens before processing uploads.

Comment on lines +75 to +82
<script src="https://cdn.jsdelivr.net/npm/@editorjs/editorjs@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/@editorjs/header@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/@editorjs/list@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/@editorjs/image@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/@editorjs/quote@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/@editorjs/code@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/@editorjs/delimiter@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/@editorjs/raw@latest"></script>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Pin CDN script versions instead of using @latest.

Using @latest for CDN dependencies can lead to breaking changes being introduced automatically when new versions are released, potentially causing production issues without warning.

Replace the @latest tags with specific version numbers:

-<script src="https://cdn.jsdelivr.net/npm/@editorjs/editorjs@latest"></script>
-<script src="https://cdn.jsdelivr.net/npm/@editorjs/header@latest"></script>
-<script src="https://cdn.jsdelivr.net/npm/@editorjs/list@latest"></script>
-<script src="https://cdn.jsdelivr.net/npm/@editorjs/image@latest"></script>
-<script src="https://cdn.jsdelivr.net/npm/@editorjs/quote@latest"></script>
-<script src="https://cdn.jsdelivr.net/npm/@editorjs/code@latest"></script>
-<script src="https://cdn.jsdelivr.net/npm/@editorjs/delimiter@latest"></script>
-<script src="https://cdn.jsdelivr.net/npm/@editorjs/raw@latest"></script>
+<script src="https://cdn.jsdelivr.net/npm/@editorjs/editorjs@2.28.0"></script>
+<script src="https://cdn.jsdelivr.net/npm/@editorjs/header@2.7.0"></script>
+<script src="https://cdn.jsdelivr.net/npm/@editorjs/list@1.8.0"></script>
+<script src="https://cdn.jsdelivr.net/npm/@editorjs/image@2.8.1"></script>
+<script src="https://cdn.jsdelivr.net/npm/@editorjs/quote@2.5.0"></script>
+<script src="https://cdn.jsdelivr.net/npm/@editorjs/code@2.8.0"></script>
+<script src="https://cdn.jsdelivr.net/npm/@editorjs/delimiter@1.3.0"></script>
+<script src="https://cdn.jsdelivr.net/npm/@editorjs/raw@2.4.0"></script>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<script src="https://cdn.jsdelivr.net/npm/@editorjs/editorjs@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/@editorjs/header@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/@editorjs/list@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/@editorjs/image@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/@editorjs/quote@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/@editorjs/code@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/@editorjs/delimiter@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/@editorjs/raw@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/@editorjs/editorjs@2.28.0"></script>
<script src="https://cdn.jsdelivr.net/npm/@editorjs/header@2.7.0"></script>
<script src="https://cdn.jsdelivr.net/npm/@editorjs/list@1.8.0"></script>
<script src="https://cdn.jsdelivr.net/npm/@editorjs/image@2.8.1"></script>
<script src="https://cdn.jsdelivr.net/npm/@editorjs/quote@2.5.0"></script>
<script src="https://cdn.jsdelivr.net/npm/@editorjs/code@2.8.0"></script>
<script src="https://cdn.jsdelivr.net/npm/@editorjs/delimiter@1.3.0"></script>
<script src="https://cdn.jsdelivr.net/npm/@editorjs/raw@2.4.0"></script>
🤖 Prompt for AI Agents
In resources/views/admin/posts/create.php around lines 75 to 82, the CDN script
tags use @latest which risks unplanned breaking changes; replace each @latest
with a specific, tested semantic version (e.g., exact x.y.z values) for
Editor.js and its plugins, update any README or asset notes to record chosen
versions, and consider adding subresource integrity or locking via a package
manager/build tool so future updates are explicit and audited.


namespace Neuron\Cms\Events;

use Neuron\Cms\Models\User;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove unused import.

The User class is imported but not used in this event class. The $disabledBy property is a string, not a User object.

-use Neuron\Cms\Models\User;
 use Neuron\Events\IEvent;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
use Neuron\Cms\Models\User;
use Neuron\Events\IEvent;
🤖 Prompt for AI Agents
In src/Cms/Events/MaintenanceModeDisabledEvent.php around line 5, the file
imports Neuron\Cms\Models\User but never uses it (the $disabledBy property is a
string). Remove the unused import line and confirm there are no remaining
references to the User class in this file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants