diff --git a/docs/EMAIL_TEMPLATES.md b/docs/EMAIL_TEMPLATES.md new file mode 100644 index 0000000..fdfd8b4 --- /dev/null +++ b/docs/EMAIL_TEMPLATES.md @@ -0,0 +1,525 @@ +# Email Templates + +The CMS uses a template-based email system powered by **PHPMailer** and the `Email\Sender` service. All email templates are editable PHP files located in `resources/views/emails/`. + +## Quick Start + +### 1. Configure Email Settings + +Copy the example configuration: +```bash +cp resources/config/email.yaml.example resources/config/email.yaml +``` + +Edit `resources/config/email.yaml`: +```yaml +email: + test_mode: true # Enable for development + driver: mail # or 'smtp' for production + from_address: noreply@yourdomain.com + from_name: Your Site Name +``` + +### 2. Customize Email Templates + +Templates are in `resources/views/emails/`. Edit them like any PHP file: + +```php +// resources/views/emails/welcome.php +

Hi ,

+

Welcome to !

+``` + +### 3. Test in Development + +With `test_mode: true`, emails are logged instead of sent: +``` +[INFO] TEST MODE - Email not sent +[INFO] To: newuser@example.com +[INFO] Subject: Welcome to My Site! +[INFO] Body: Hi John, Welcome to My Site... +``` + +## Available Templates + +### welcome.php +**Sent when**: A new user is created +**Triggered by**: `SendWelcomeEmailListener` (listens to `user.created` event) +**Variables available**: +- `$Username` - The new user's username +- `$SiteName` - Site name from settings +- `$SiteUrl` - Site URL from settings + +**Customization ideas**: +- Add your logo (use absolute URL: `https://yoursite.com/logo.png`) +- Change gradient colors in header +- Modify greeting message +- Add onboarding steps +- Include social media links +- Add help/support contact info + +### password-reset.php +**Sent when**: A user requests a password reset +**Triggered by**: `PasswordResetManager::requestReset()` (via forgot password form) +**Variables available**: +- `$ResetLink` - The password reset URL with token +- `$ExpirationMinutes` - Token expiration time in minutes (default: 60) +- `$SiteName` - Site name from settings + +**Customization ideas**: +- Change gradient colors in header to match your brand +- Modify the security notice styling +- Add your logo +- Include support contact information +- Customize expiration warning message +- Add additional security tips + +## Email Configuration + +### Drivers + +**mail** (default) +- Uses PHP's `mail()` function +- Requires server to have mail configured +- Works on most shared hosting +- May have deliverability issues + +**sendmail** +- Uses sendmail binary +- Available on Linux/Mac servers +- Better than `mail()` for deliverability + +**smtp** (recommended for production) +- Uses SMTP server (Gmail, SendGrid, Mailgun, etc.) +- Best deliverability +- Requires SMTP credentials + +### SMTP Configuration Examples + +#### Gmail +```yaml +email: + driver: smtp + host: smtp.gmail.com + port: 587 + username: your-email@gmail.com + password: your-app-password # NOT your regular Gmail password! + encryption: tls + from_address: your-email@gmail.com + from_name: Your Site Name +``` + +**Important**: Use an [App Password](https://support.google.com/accounts/answer/185833), not your regular password! + +#### SendGrid +```yaml +email: + driver: smtp + host: smtp.sendgrid.net + port: 587 + username: apikey + password: SG.your-sendgrid-api-key-here + encryption: tls + from_address: noreply@yourdomain.com + from_name: Your Site Name +``` + +#### Mailgun +```yaml +email: + driver: smtp + host: smtp.mailgun.org + port: 587 + username: postmaster@yourdomain.com + password: your-mailgun-smtp-password + encryption: tls + from_address: noreply@yourdomain.com + from_name: Your Site Name +``` + +### Test Mode + +Enable test mode during development to log emails instead of sending: + +```yaml +email: + test_mode: true +``` + +Emails will be logged with full details: +``` +[INFO] TEST MODE - Email not sent +[INFO] To: test@example.com +[INFO] Subject: Welcome! +[INFO] Body: Hi John, Welcome to... +``` + +## Customizing Templates + +### Basic Customization + +Templates are standard PHP files with variables. Edit them directly: + +```php +// resources/views/emails/welcome.php + + + + + + + + +

Hey ! 👋

+ + +

Thanks for signing up! We're excited to have you.

+ + + + Get Started Now + + + +``` + +### Adding Images + +Use absolute URLs for images (relative paths don't work in emails): + +```php + +Logo + + +Logo +``` + +### Inline CSS + +Always use inline CSS or ` + + + +``` + +### Variables and Logic + +Templates support full PHP: + +```php + + +

You've earned a bonus!

+ + + + +
  • + + + +

    Year:

    +

    URL:

    +``` + +### Email-Safe HTML + +Follow these best practices: + +1. **Use tables for layout** (not divs) for better email client support +2. **Inline CSS** - Styles must be inline or in ` + + +
    +

    +

    Hi ,

    +

    +
    + + +``` + +### 2. Send Email with Template + +```php +use Neuron\Cms\Services\Email\Sender; + +$sender = new Sender( $settings, $basePath ); +$sender + ->to( 'user@example.com', 'John Doe' ) + ->subject( 'Your Subject' ) + ->template( 'emails/your-template', [ + 'Username' => 'John', + 'Subject' => 'Hello', + 'Message' => 'This is a test' + ]) + ->send(); +``` + +### 3. Create Listener (Optional) + +To send automatically on events: + +```php +namespace Neuron\Cms\Listeners; + +use Neuron\Events\IListener; +use Neuron\Cms\Services\Email\Sender; + +class YourEmailListener implements IListener +{ + public function event( $event ): void + { + $settings = Registry::getInstance()->get( 'Settings' ); + $basePath = Registry::getInstance()->get( 'Base.Path' ); + + $sender = new Sender( $settings, $basePath ); + $sender + ->to( $event->user->getEmail() ) + ->subject( 'Your Subject' ) + ->template( 'emails/your-template', [ + 'Username' => $event->user->getUsername() + ]) + ->send(); + } +} +``` + +Register in `resources/config/event-listeners.yaml`: +```yaml +events: + your.event: + class: 'Neuron\Cms\Events\YourEvent' + listeners: + - 'Neuron\Cms\Listeners\YourEmailListener' +``` + +## Advanced Features + +### Multiple Recipients + +```php +$sender + ->to( 'user1@example.com', 'User One' ) + ->to( 'user2@example.com', 'User Two' ) + ->cc( 'manager@example.com' ) + ->bcc( 'admin@example.com' ) + ->send(); +``` + +### Attachments + +```php +$sender + ->to( 'user@example.com' ) + ->subject( 'Invoice' ) + ->template( 'emails/invoice', $data ) + ->attach( '/path/to/invoice.pdf', 'Invoice.pdf' ) + ->send(); +``` + +### Reply-To Address + +```php +$sender + ->to( 'user@example.com' ) + ->subject( 'Contact Form' ) + ->replyTo( 'support@yoursite.com', 'Support Team' ) + ->template( 'emails/contact-form', $data ) + ->send(); +``` + +### Plain Text Body + +```php +$sender + ->to( 'user@example.com' ) + ->subject( 'Hello' ) + ->body( 'Plain text message', false ) // false = plain text + ->send(); +``` + +## Troubleshooting + +### Email not sending? + +1. **Check logs** - Look for error messages in log files +2. **Enable test mode** - Verify template renders correctly +3. **Check mail server** - Ensure `mail()` works: `php -r "mail('you@example.com', 'Test', 'Test');"` +4. **Try SMTP** - More reliable than `mail()` +5. **Check spam folder** - Emails might be filtered + +### Template not found? + +``` +RuntimeException: Failed to render email template: emails/welcome +``` + +**Solutions**: +- Verify file exists at `resources/views/emails/welcome.php` +- Check file permissions (must be readable) +- Ensure correct base path in Registry + +### Variables not showing? + +Template receives: `$Username`, `$SiteName`, `$SiteUrl` + +Check your template data array: +```php +$templateData = [ + 'Username' => $user->getUsername(), // ✅ PascalCase + 'SiteName' => $siteName, // ✅ PascalCase + 'SiteUrl' => $siteUrl // ✅ PascalCase +]; +``` + +### SMTP authentication failed? + +**Gmail**: Use App Password, not regular password +**SendGrid**: Username must be `apikey` +**Mailgun**: Use SMTP password, not API key + +### Email goes to spam? + +1. **Use SMTP** instead of `mail()` +2. **Configure SPF/DKIM** for your domain +3. **Warm up IP** if using dedicated IP +4. **Avoid spam trigger words** (FREE, ACT NOW, etc.) +5. **Test with** [Mail-Tester.com](https://www.mail-tester.com/) + +## Email Client Testing + +Test templates in popular email clients: + +- **Gmail** (Desktop & Mobile) +- **Outlook** (Desktop & Web) +- **Apple Mail** (Mac & iOS) +- **Yahoo Mail** +- **Outlook.com / Hotmail** + +Tools: +- [Litmus](https://litmus.com/) - Paid, comprehensive +- [Email on Acid](https://www.emailonacid.com/) - Paid +- [Mailtrap](https://mailtrap.io/) - Free tier for development + +## Best Practices + +1. **Keep it simple** - Complex layouts break in email clients +2. **Mobile first** - 60%+ of emails opened on mobile +3. **Inline CSS** - External styles don't work +4. **Absolute URLs** - For all images and links +5. **Test thoroughly** - Check multiple clients +6. **Use test mode** - During development +7. **Personalize** - Use recipient name +8. **Clear CTA** - One primary call-to-action +9. **Unsubscribe link** - For marketing emails +10. **Monitor deliverability** - Track bounce rates + +## Security + +### XSS Prevention + +**Always escape variables**: +```php + +

    + + +

    +``` + +### Template Injection + +Templates are PHP files. **Never** load user-supplied template names: +```php +// ❌ Dangerous - template injection +$sender->template( $_POST['template'], $data ); + +// ✅ Safe - whitelist templates +$templates = ['welcome', 'password-reset']; +if( in_array( $_POST['template'], $templates ) ) { + $sender->template( "emails/{$_POST['template']}", $data ); +} +``` + +## Examples + +### Welcome Email with Logo + +```php + + + +
    + Logo +

    Welcome, !

    +

    Thanks for joining .

    + + Get Started + +
    + + +``` + +### Two-Column Layout + +```php + + + + + +
    +

    Feature 1

    +

    Description...

    +
    +

    Feature 2

    +

    Description...

    +
    +``` + +## Further Reading + +- [PHPMailer Documentation](https://github.com/PHPMailer/PHPMailer) +- [Email Design Best Practices](https://www.campaignmonitor.com/resources/guides/email-design-best-practices/) +- [HTML Email Coding Guide](https://templates.mailchimp.com/getting-started/html-email-basics/) +- [Can I email...](https://www.caniemail.com/) - Email client support reference diff --git a/docs/EVENTS.md b/docs/EVENTS.md new file mode 100644 index 0000000..de204aa --- /dev/null +++ b/docs/EVENTS.md @@ -0,0 +1,266 @@ +# Event System + +The CMS uses an event-driven architecture to decouple business logic from side effects like sending emails and clearing caches. + +## Available Events + +### User Events +- **UserCreatedEvent** - Fired when a new user is created +- **UserUpdatedEvent** - Fired when a user is updated +- **UserDeletedEvent** - Fired when a user is deleted + +### Post Events +- **PostCreatedEvent** - Fired when a new post is created +- **PostPublishedEvent** - Fired when a post is published +- **PostDeletedEvent** - Fired when a post is deleted + +### Category Events +- **CategoryCreatedEvent** - Fired when a new category is created +- **CategoryUpdatedEvent** - Fired when a category is updated +- **CategoryDeletedEvent** - Fired when a category is deleted + +## Built-in Listeners + +### SendWelcomeEmailListener + +Sends a professional HTML welcome email to newly registered users using the **editable template** at `resources/views/emails/welcome.php`. + +**Configuration:** +```yaml +# resources/config/email.yaml +email: + test_mode: false # Set true to log instead of send + driver: smtp # or 'mail', 'sendmail' + from_address: noreply@yourdomain.com + from_name: Your Site Name + # SMTP settings (if driver is 'smtp') + host: smtp.gmail.com + port: 587 + username: your-email@gmail.com + password: your-app-password + encryption: tls +``` + +**Template Location:** +`resources/views/emails/welcome.php` - Fully editable PHP template + +**Template Variables:** +- `$Username` - New user's username +- `$SiteName` - Site name from settings +- `$SiteUrl` - Site URL from settings + +**Features:** +- **Editable template** - Customize design, colors, text without code changes +- **PHPMailer integration** - Professional email delivery with SMTP support +- **Test mode** - Logs emails during development instead of sending +- **Responsive design** - Works on desktop and mobile email clients +- **Inline CSS** - Styles work across all email clients +- **XSS protection** - All variables properly escaped + +**Customization:** +Simply edit `resources/views/emails/welcome.php` to: +- Change gradient colors +- Add your logo +- Modify greeting message +- Update call-to-action button +- Customize footer +- Add social media links + +See [EMAIL_TEMPLATES.md](EMAIL_TEMPLATES.md) for complete customization guide. + +### ClearCacheListener + +Automatically clears view cache when content changes to ensure users see fresh content. + +**Triggers:** +- Post published +- Post deleted +- Category updated + +**How it Works:** +1. Listener receives content change event +2. Retrieves ViewCache instance from Registry +3. Calls `ViewCache::clear()` to invalidate all cached views +4. Logs success/failure for debugging + +**Requirements:** +- ViewCache must be registered in Registry as 'ViewCache' +- Cache storage must be configured (File or Redis) + +### LogUserActivityListener + +Logs all user activity for audit trail purposes. + +**Logged Activities:** +- User created: "User created: username (ID: 1)" +- User updated: "User updated: username (ID: 1)" +- User deleted: "User deleted: ID 1" + +**Log Level:** INFO + +## Event Configuration + +Events and their listeners are configured in `resources/config/event-listeners.yaml`: + +```yaml +events: + user.created: + class: 'Neuron\Cms\Events\UserCreatedEvent' + listeners: + - 'Neuron\Cms\Listeners\SendWelcomeEmailListener' + - 'Neuron\Cms\Listeners\LogUserActivityListener' + + user.updated: + class: 'Neuron\Cms\Events\UserUpdatedEvent' + listeners: + - 'Neuron\Cms\Listeners\LogUserActivityListener' + + user.deleted: + class: 'Neuron\Cms\Events\UserDeletedEvent' + listeners: + - 'Neuron\Cms\Listeners\LogUserActivityListener' + + post.published: + class: 'Neuron\Cms\Events\PostPublishedEvent' + listeners: + - 'Neuron\Cms\Listeners\ClearCacheListener' + + post.deleted: + class: 'Neuron\Cms\Events\PostDeletedEvent' + listeners: + - 'Neuron\Cms\Listeners\ClearCacheListener' + + category.updated: + class: 'Neuron\Cms\Events\CategoryUpdatedEvent' + listeners: + - 'Neuron\Cms\Listeners\ClearCacheListener' +``` + +## Creating Custom Listeners + +### Step 1: Create Listener Class + +```php +user; + + // Your custom logic here + Log::info( "Custom action for user: {$user->getUsername()}" ); + } +} +``` + +### Step 2: Register in Configuration + +Add your listener to `resources/config/event-listeners.yaml`: + +```yaml +events: + user.created: + class: 'Neuron\Cms\Events\UserCreatedEvent' + listeners: + - 'Neuron\Cms\Listeners\SendWelcomeEmailListener' + - 'Neuron\Cms\Listeners\LogUserActivityListener' + - 'Neuron\Cms\Listeners\MyCustomListener' # Your listener +``` + +## How Events are Emitted + +Events are emitted from service classes, not controllers. This keeps controllers thin and business logic encapsulated. + +**Example from User\Creator service:** + +```php +public function create( /* ... */ ): User +{ + // Create user + $user = new User(); + // ... set user properties + $user = $this->_userRepository->create( $user ); + + // Emit event + $emitter = Registry::getInstance()->get( 'EventEmitter' ); + if( $emitter ) + { + $emitter->emit( new UserCreatedEvent( $user ) ); + } + + return $user; +} +``` + +## Benefits of Event-Driven Architecture + +1. **Decoupling** - Services don't need to know about emails, caching, logging, etc. +2. **Extensibility** - Add new listeners without modifying existing code +3. **Testability** - Easy to test services without side effects +4. **Flexibility** - Enable/disable features by adding/removing listeners +5. **Single Responsibility** - Each component does one thing well + +## Debugging Events + +Enable debug logging to see event flow: + +```php +Log::setLevel( Log::DEBUG ); +``` + +You'll see logs like: +``` +[INFO] User created: testuser (ID: 5) +[DEBUG] Settings not available - welcome email skipped for: test@example.com +[INFO] Cache cleared successfully: Post published: My New Post +``` + +## Testing Listeners + +Listeners can be tested in isolation: + +```php +public function testSendWelcomeEmailListener(): void +{ + $user = new User(); + $user->setEmail( 'test@example.com' ); + + $event = new UserCreatedEvent( $user ); + $listener = new SendWelcomeEmailListener(); + + // Should not throw exception + $listener->event( $event ); + + $this->assertTrue( true ); +} +``` + +## Performance Considerations + +- Listeners run synchronously during the request +- Keep listener logic fast (< 100ms) +- For slow operations (external APIs), use a job queue instead +- Cache clearing is fast (< 10ms typically) +- Email sending uses PHP's mail() which is non-blocking + +## Future Enhancements + +Potential improvements to the event system: + +1. **Async Listeners** - Queue slow listeners for background processing +2. **Event Priorities** - Control listener execution order +3. **Conditional Listeners** - Only fire based on conditions +4. **Event Middleware** - Transform events before listeners receive them +5. **Dead Letter Queue** - Capture failed listener executions diff --git a/resources/app/Initializers/AuthInitializer.php b/resources/app/Initializers/AuthInitializer.php index a2085d5..8cfb18f 100644 --- a/resources/app/Initializers/AuthInitializer.php +++ b/resources/app/Initializers/AuthInitializer.php @@ -12,7 +12,7 @@ use Neuron\Patterns\IRunnable; /** - * Initialize authentication system + * Initialize the authentication system * * Registers the auth filter with the Router for protecting routes */ diff --git a/resources/app/Initializers/MaintenanceInitializer.php b/resources/app/Initializers/MaintenanceInitializer.php index fc44426..b6e711c 100644 --- a/resources/app/Initializers/MaintenanceInitializer.php +++ b/resources/app/Initializers/MaintenanceInitializer.php @@ -10,7 +10,7 @@ use Neuron\Patterns\IRunnable; /** - * Initialize maintenance mode filter + * Initialize the maintenance mode filter * * Registers the maintenance mode filter with the Router to intercept * requests when maintenance mode is enabled. diff --git a/resources/app/Initializers/PasswordResetInitializer.php b/resources/app/Initializers/PasswordResetInitializer.php index 88a2245..50d61ce 100644 --- a/resources/app/Initializers/PasswordResetInitializer.php +++ b/resources/app/Initializers/PasswordResetInitializer.php @@ -12,7 +12,7 @@ use Neuron\Patterns\IRunnable; /** - * Initialize password reset system + * Initialize the password reset system * * Registers the PasswordResetManager in the Registry */ @@ -44,15 +44,13 @@ public function run( array $argv = [] ): mixed { // Get site configuration $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; // Get token expiration (default 60 minutes) $tokenExpiration = $settings->get( 'auth', 'password_reset_expiration' ) ?? 60; + // Get base path from Registry + $basePath = Registry::getInstance()->get( 'Base.Path' ) ?? getcwd(); + // Initialize components $tokenRepository = new DatabasePasswordResetTokenRepository( $settings ); $userRepository = new DatabaseUserRepository( $settings ); @@ -66,9 +64,9 @@ public function run( array $argv = [] ): mixed $tokenRepository, $userRepository, $passwordHasher, - $resetUrl, - $fromEmail, - $fromName + $settings, + $basePath, + $resetUrl ); // Set token expiration if configured diff --git a/resources/config/email.yaml.example b/resources/config/email.yaml.example new file mode 100644 index 0000000..405a8f5 --- /dev/null +++ b/resources/config/email.yaml.example @@ -0,0 +1,68 @@ +# Email Configuration Example +# +# Copy this file to email.yaml and configure for your environment +# +# This configuration is used by the SendWelcomeEmailListener and other +# email-sending features of the CMS powered by PHPMailer. + +email: + # Test mode - logs emails instead of sending (useful for development) + # When enabled, emails are logged to the log file instead of being sent + test_mode: false + + # Email driver: mail, sendmail, or smtp + # - mail: Uses PHP's mail() function (default, requires server mail setup) + # - sendmail: Uses sendmail binary (Linux/Mac) + # - smtp: Uses SMTP server (recommended for production) + driver: mail + + # From address and name for system emails + from_address: noreply@yourdomain.com + from_name: Your Site Name + + # SMTP Configuration (only required if driver is 'smtp') + # Examples for popular email services: + # + # Gmail: + # host: smtp.gmail.com + # port: 587 + # encryption: tls + # username: your-email@gmail.com + # password: your-app-password (not your regular password!) + # + # SendGrid: + # host: smtp.sendgrid.net + # port: 587 + # encryption: tls + # username: apikey + # password: your-sendgrid-api-key + # + # Mailgun: + # host: smtp.mailgun.org + # port: 587 + # encryption: tls + # username: postmaster@yourdomain.com + # password: your-mailgun-smtp-password + + # host: smtp.gmail.com + # port: 587 + # username: your-email@gmail.com + # password: your-app-password + # encryption: tls # or 'ssl' for port 465 + +# Email Template Customization +# +# Templates are located in: resources/views/emails/ +# +# Available templates: +# - welcome.php - Welcome email sent to new users +# +# To customize, edit the template files directly. They use standard PHP +# templating with variables like $Username, $SiteName, $SiteUrl. +# +# Example customization in welcome.php: +# - Change colors in the + + +
    + +
    +

    Password Reset Request

    +
    + + +
    +

    Hello,

    + +

    + You have requested to reset your password for . + Click the button below to create a new password: +

    + + + +

    + Or copy and paste this link into your browser: +

    + + + +
    +

    + Security Notice: This link will expire in minutes. + If you did not request a password reset, please ignore this email and your password will remain unchanged. +

    +
    + +
    + +

    + If you're having trouble clicking the reset button, copy and paste the URL above into your web browser. +

    + +

    + Best regards,
    + The Team +

    +
    + + + +
    + + diff --git a/resources/views/emails/welcome.php b/resources/views/emails/welcome.php new file mode 100644 index 0000000..3dffa68 --- /dev/null +++ b/resources/views/emails/welcome.php @@ -0,0 +1,146 @@ + + + + + + Welcome to <?= htmlspecialchars($SiteName) ?> + + + +
    + +
    +

    Welcome to !

    +
    + + +
    +

    Hi ,

    + +

    + Thank you for joining ! + We're thrilled to have you as part of our community. +

    + +

    + Your account has been successfully created and you're all set to get started. + Explore our platform, connect with others, and make the most of your membership. +

    + +
    + Visit +
    + +
    + +

    + If you have any questions or need assistance, don't hesitate to reach out. + We're here to help you get the most out of your experience. +

    + +

    + Best regards,
    + The Team +

    +
    + + + +
    + + diff --git a/resources/views/home/index.php b/resources/views/home/index.php new file mode 100644 index 0000000..f75fe14 --- /dev/null +++ b/resources/views/home/index.php @@ -0,0 +1,45 @@ +
    +
    +
    +

    + + +

    + +

    A modern, database-backed CMS built on the Neuron framework

    + + + + +
    +
    +
    +
    +

    Content Management

    +

    Create and manage blog posts, categories, and tags with an intuitive admin interface.

    +
    +
    +
    +
    +
    +
    +

    User Management

    +

    Manage users with role-based access control and secure authentication.

    +
    +
    +
    +
    +
    +
    +

    Modern & Fast

    +

    Built with PHP 8.4+ using modern practices and the Neuron MVC framework.

    +
    +
    +
    +
    +
    +
    +
    diff --git a/resources/views/layouts/admin.php b/resources/views/layouts/admin.php index df15306..b0939c0 100644 --- a/resources/views/layouts/admin.php +++ b/resources/views/layouts/admin.php @@ -18,7 +18,7 @@