Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f3ad65a
fixes duplicate method issue.
ljonesfl Nov 9, 2025
379e30a
refactors for a more service like architecture.
ljonesfl Nov 11, 2025
fdbee69
Initial plan
Copilot Nov 11, 2025
d1b2b0e
Initial plan
Copilot Nov 11, 2025
ccd9512
Initial plan
Copilot Nov 11, 2025
3bbcb4d
Initial plan
Copilot Nov 11, 2025
91dd710
Fix authentication filter to prevent stale session security issue
Copilot Nov 11, 2025
9194a39
Fix authorization checks to compare IDs instead of object with string
Copilot Nov 11, 2025
b79f4f6
Initial plan
Copilot Nov 11, 2025
c1cd742
Initial plan
Copilot Nov 11, 2025
5617a57
Fix authorization check type mismatch in PostController
Copilot Nov 11, 2025
ed6e2c2
Fix authorization check type mismatch in PostController
Copilot Nov 11, 2025
b9f0e5d
Add error handling for repository persistence failures in Publisher s…
Copilot Nov 11, 2025
35d1d3d
Add publishedAt auto-setting to Updater service
Copilot Nov 11, 2025
7d263d7
Merge pull request #7 from Neuron-PHP/copilot/sub-pr-6
ljonesfl Nov 11, 2025
b5cd6cb
Merge pull request #8 from Neuron-PHP/copilot/sub-pr-6-again
ljonesfl Nov 11, 2025
c3a877e
Merge pull request #9 from Neuron-PHP/copilot/sub-pr-6-another-one
ljonesfl Nov 11, 2025
332e8a7
Merge pull request #10 from Neuron-PHP/copilot/sub-pr-6-yet-again
ljonesfl Nov 11, 2025
a049252
Merge pull request #12 from Neuron-PHP/copilot/sub-pr-6-please-work
ljonesfl Nov 11, 2025
8a50ee3
Fix PublisherTest mocks to return true for update() calls
Copilot Nov 11, 2025
709fe70
Merge pull request #11 from Neuron-PHP/copilot/sub-pr-6-one-more-time
ljonesfl Nov 11, 2025
d76bcbf
Update src/Cms/Database/ConnectionFactory.php
ljonesfl Nov 11, 2025
76b26fa
Initial plan
Copilot Nov 11, 2025
0e8620a
Fix boolean casting for email_verified field to support PostgreSQL
Copilot Nov 11, 2025
190117c
Merge pull request #13 from Neuron-PHP/copilot/sub-pr-6
ljonesfl Nov 11, 2025
3188ef0
Update tests/Cms/Services/Post/PublisherTest.php
ljonesfl Nov 11, 2025
b923652
updated readme.
ljonesfl Nov 11, 2025
fd74233
Merge branch 'feature/service-architecture' of github.com:Neuron-PHP/…
ljonesfl Nov 11, 2025
31605d6
fixes profile update failure handling.
ljonesfl Nov 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,16 @@ All view templates are in `resources/views/` and can be customized:
- `blog/show.php` - Individual post
- `admin/*` - Admin panel templates

### Scheduling Jobs

Start the scheduler:

```bash
php neuron jobs:schedule
```

This will process scheduled jobs.

### Running Background Jobs

Start the queue worker:
Expand Down
51 changes: 16 additions & 35 deletions resources/app/Initializers/AuthInitializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,58 +20,39 @@ class AuthInitializer implements IRunnable
{
/**
* Run the initializer
* @param array $argv
* @return mixed
* @throws \Exception
*/
public function run( array $Argv = [] ): mixed
public function run( array $argv = [] ): mixed
{
// Get Settings from Registry
$Settings = Registry::getInstance()->get( 'Settings' );
$settings = Registry::getInstance()->get( 'Settings' );

if( !$Settings || !$Settings instanceof \Neuron\Data\Setting\SettingManager )
if( !$settings || !$settings instanceof \Neuron\Data\Setting\SettingManager )
{
Log::error( "Settings not found in Registry, skipping auth initialization" );
return null;
}

// Get Application from Registry
$App = Registry::getInstance()->get( 'App' );
$app = Registry::getInstance()->get( 'App' );

if( !$App || !$App instanceof \Neuron\Mvc\Application )
if( !$app || !$app instanceof \Neuron\Mvc\Application )
{
Log::error( "Application not found in Registry, skipping auth initialization" );
return null;
}

// Get database configuration
$dbConfig = [];
// Check if database is configured
try
{
$settingNames = $Settings->getSectionSettingNames( 'database' );
$settingNames = $settings->getSectionSettingNames( 'database' );

if( !empty( $settingNames ) )
{
foreach( $settingNames as $name )
{
$value = $Settings->get( 'database', $name );
if( $value !== null )
{
// Convert string values to appropriate types
$dbConfig[$name] = ( $name === 'port' ) ? (int)$value : $value;
}
}
}
}
catch( \Exception $e )
{
Log::error( "Failed to load database config: " . $e->getMessage() );
}

// Only register auth filter if database is configured
if( !empty( $dbConfig ) )
{
try
{
// Initialize authentication components
$userRepository = new DatabaseUserRepository( $dbConfig );
$userRepository = new DatabaseUserRepository( $settings );
$sessionManager = new SessionManager();
$passwordHasher = new PasswordHasher();
$authManager = new AuthManager( $userRepository, $sessionManager, $passwordHasher );
Expand All @@ -80,15 +61,15 @@ public function run( array $Argv = [] ): mixed
$authFilter = new AuthenticationFilter( $authManager, '/login' );

// Register the auth filter with the Router
$App->getRouter()->registerFilter( 'auth', $authFilter );
$app->getRouter()->registerFilter( 'auth', $authFilter );

// Store AuthManager in Registry for easy access
Registry::getInstance()->set( 'AuthManager', $authManager );
}
catch( \Exception $e )
{
Log::error( "Failed to register auth filter: " . $e->getMessage() );
}
}
catch( \Exception $e )
{
Log::error( "Failed to register auth filter: " . $e->getMessage() );
}

return null;
Expand Down
21 changes: 12 additions & 9 deletions resources/app/Initializers/MaintenanceInitializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,34 +19,37 @@ class MaintenanceInitializer implements IRunnable
{
/**
* Run the initializer
* @param array $argv
* @return mixed
* @throws \Exception
*/
public function run( array $Argv = [] ): mixed
public function run( array $argv = [] ): mixed
{
// Get Application from Registry
$App = Registry::getInstance()->get( 'App' );
$app = Registry::getInstance()->get( 'App' );

if( !$App || !$App instanceof \Neuron\Mvc\Application )
if( !$app || !$app instanceof \Neuron\Mvc\Application )
{
Log::error( "Application not found in Registry, skipping maintenance initialization" );
return null;
}

// Get base path for maintenance file (use application base path, not cwd)
$basePath = $App->getBasePath();
$basePath = $app->getBasePath();

// Create maintenance manager
$manager = new MaintenanceManager( $basePath );

// Get configuration (if available)
$config = null;
$Settings = Registry::getInstance()->get( 'Settings' );
$settings = Registry::getInstance()->get( 'Settings' );

if( $Settings && $Settings instanceof \Neuron\Data\Setting\SettingManager )
if( $settings && $settings instanceof \Neuron\Data\Setting\SettingManager )
{
try
{
// Get the source from the SettingManager
$source = $Settings->getSource();
$source = $settings->getSource();
$config = MaintenanceConfig::fromSettings( $source );
}
catch( \Exception $e )
Expand All @@ -62,8 +65,8 @@ public function run( array $Argv = [] ): mixed
);

// Register and apply filter globally to all routes
$App->getRouter()->registerFilter( 'maintenance', $filter );
$App->getRouter()->addFilter( 'maintenance' );
$app->getRouter()->registerFilter( 'maintenance', $filter );
$app->getRouter()->addFilter( 'maintenance' );

// Store manager in registry for CLI commands
Registry::getInstance()->set( 'maintenance.manager', $manager );
Expand Down
59 changes: 19 additions & 40 deletions resources/app/Initializers/PasswordResetInitializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,61 +20,42 @@ class PasswordResetInitializer implements IRunnable
{
/**
* Run the initializer
* @param array $argv
* @return mixed
* @throws \Exception
*/
public function run( array $Argv = [] ): mixed
public function run( array $argv = [] ): mixed
{
// Get Settings from Registry
$Settings = Registry::getInstance()->get( 'Settings' );
$settings = Registry::getInstance()->get( 'Settings' );

if( !$Settings || !$Settings instanceof SettingManager )
if( !$settings || !$settings instanceof SettingManager )
{
Log::error( "Settings not found in Registry, skipping password reset initialization" );
return null;
}

// Get database configuration
$dbConfig = [];
// Check if database is configured
try
{
$settingNames = $Settings->getSectionSettingNames( 'database' );
$settingNames = $settings->getSectionSettingNames( 'database' );

if( !empty( $settingNames ) )
{
foreach( $settingNames as $name )
{
$value = $Settings->get( 'database', $name );
if( $value !== null )
{
// Convert string values to appropriate types
$dbConfig[$name] = ( $name === 'port' ) ? (int)$value : $value;
}
}
}
}
catch( \Exception $e )
{
Log::error( "Failed to load database config: " . $e->getMessage() );
}

// Only initialize if database is configured
if( !empty( $dbConfig ) )
{
try
{
// Get site configuration
$siteUrl = $Settings->get( 'site', 'url' ) ?? 'http://localhost:8000';
$siteName = $Settings->get( 'site', 'name' ) ?? 'Neuron CMS';
$siteUrl = $settings->get( 'site', 'url' ) ?? 'http://localhost:8000';
$siteName = $settings->get( 'site', 'name' ) ?? 'Neuron CMS';

// Get email configuration (with fallbacks)
$fromEmail = $Settings->get( 'mail', 'from_email' ) ?? 'noreply@localhost';
$fromName = $Settings->get( 'mail', 'from_name' ) ?? $siteName;
$fromEmail = $settings->get( 'mail', 'from_email' ) ?? 'noreply@localhost';
$fromName = $settings->get( 'mail', 'from_name' ) ?? $siteName;

// Get token expiration (default 60 minutes)
$tokenExpiration = $Settings->get( 'auth', 'password_reset_expiration' ) ?? 60;
$tokenExpiration = $settings->get( 'auth', 'password_reset_expiration' ) ?? 60;

// Initialize components
$tokenRepository = new DatabasePasswordResetTokenRepository( $dbConfig );
$userRepository = new DatabaseUserRepository( $dbConfig );
$tokenRepository = new DatabasePasswordResetTokenRepository( $settings );
$userRepository = new DatabaseUserRepository( $settings );
$passwordHasher = new PasswordHasher();

// Create password reset URL
Expand All @@ -95,14 +76,12 @@ public function run( array $Argv = [] ): mixed

// Store PasswordResetManager in Registry for easy access
Registry::getInstance()->set( 'PasswordResetManager', $resetManager );

Log::debug( "Password reset system initialized" );
}
catch( \Exception $e )
{
Log::error( "Failed to initialize password reset: " . $e->getMessage() );
}
}
catch( \Exception $e )
{
Log::error( "Failed to initialize password reset: " . $e->getMessage() );
}

return null;
}
Expand Down
4 changes: 2 additions & 2 deletions resources/public/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@

error_reporting( E_ALL );

$App = boot( '../config' );
$app = boot( '../config' );

dispatch( $App );
dispatch( $app );
2 changes: 1 addition & 1 deletion resources/views/admin/posts/edit.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

<div class="mb-3">
<label for="content" class="form-label">Content</label>
<textarea class="form-control" id="content" name="content" rows="15" required><?= htmlspecialchars( $post->getContent() ) ?></textarea>
<textarea class="form-control" id="content" name="content" rows="15" required><?= htmlspecialchars( $post->getBody() ) ?></textarea>
<small class="form-text text-muted">Markdown supported</small>
</div>

Expand Down
4 changes: 2 additions & 2 deletions resources/views/admin/posts/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
<tr>
<td><?= $post->getId() ?></td>
<td><?= htmlspecialchars( $post->getTitle() ) ?></td>
<td><?= htmlspecialchars( $post->getAuthor() ) ?></td>
<td><?= $post->getAuthor() ? htmlspecialchars( $post->getAuthor()->getUsername() ) : 'Unknown' ?></td>
<td><span class="badge bg-<?= $post->getStatus() === 'published' ? 'success' : 'secondary' ?>"><?= htmlspecialchars( $post->getStatus() ) ?></span></td>
<td><?= $post->getViews() ?></td>
<td><?= $post->getViewCount() ?></td>
<td><?= $post->getCreatedAt() ? $post->getCreatedAt()->format( 'Y-m-d H:i' ) : 'N/A' ?></td>
<td>
<a href="/blog/post/<?= htmlspecialchars( $post->getSlug() ) ?>" class="btn btn-sm btn-outline-secondary" target="_blank">View</a>
Expand Down
2 changes: 1 addition & 1 deletion resources/views/blog/category.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
</h2>
<p class="text-muted">
<small>
By <?= htmlspecialchars( $post->getAuthor() ) ?>
By <?= $post->getAuthor() ? htmlspecialchars( $post->getAuthor()->getUsername() ) : 'Unknown' ?>
on <?= $post->getCreatedAt() ? $post->getCreatedAt()->format( 'F j, Y' ) : '' ?>
</small>
</p>
Expand Down
2 changes: 1 addition & 1 deletion resources/views/blog/tag.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
</h2>
<p class="text-muted">
<small>
By <?= htmlspecialchars( $post->getAuthor() ) ?>
By <?= $post->getAuthor() ? htmlspecialchars( $post->getAuthor()->getUsername() ) : 'Unknown' ?>
on <?= $post->getCreatedAt() ? $post->getCreatedAt()->format( 'F j, Y' ) : '' ?>
</small>
</p>
Expand Down
8 changes: 4 additions & 4 deletions src/Bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,18 @@

/**
* Bootstrap and initialize a Neuron CMS application.
*
*
* This function initializes a complete CMS application with all necessary
* components including routing, content management, blog functionality,
* and site configuration. It delegates to the MVC boot function but
* provides CMS-specific context and naming.
*
* @param string $ConfigPath Path to the CMS configuration directory
* @param string $configPath Path to the CMS configuration directory
* @return Application Fully configured CMS application instance
* @throws \Exception If configuration loading or application initialization fails
*/

function boot( string $ConfigPath ) : Application
function boot( string $configPath ) : Application
{
return \Neuron\Mvc\boot( $ConfigPath );
return \Neuron\Mvc\boot( $configPath );
}
Loading