diff --git a/examples/config/config.yaml b/examples/config/config.yaml deleted file mode 100644 index 570a7c6..0000000 --- a/examples/config/config.yaml +++ /dev/null @@ -1,35 +0,0 @@ -system: - timezone: US/Eastern - base_path: examples - routes_path: examples/config - -logging: - destination: \Neuron\Log\Destination\File - format: \Neuron\Log\Format\PlainText - file: test.log - level: debug - -cache: - enabled: true - storage: file - path: cache/views - ttl: 3600 - html: true - markdown: true - json: false - xml: false - # Garbage collection settings (optional, defaults shown) - # gc_probability: 0.01 # 1% chance to run GC on cache write - # gc_divisor: 100 # Used with probability for fine-tuning - -views: - path: views - -site: - name: 'Neuron' - url: 'https://neuron.example.com' - description: 'A modern blogging platform built with Neuron.' - -blog: - path: 'blog' - title: 'Neuron Blog' diff --git a/examples/config/neuron.yaml b/examples/config/neuron.yaml new file mode 100644 index 0000000..154004f --- /dev/null +++ b/examples/config/neuron.yaml @@ -0,0 +1,57 @@ +system: + timezone: US/Eastern + base_path: examples + routes_path: examples/config + +logging: + destination: \Neuron\Log\Destination\File + format: \Neuron\Log\Format\PlainText + file: test.log + level: debug + +cache: + enabled: true + storage: file + path: cache/views + ttl: 3600 + html: true + markdown: true + json: false + xml: false + # Garbage collection settings (optional, defaults shown) + # gc_probability: 0.01 # 1% chance to run GC on cache write + # gc_divisor: 100 # Used with probability for fine-tuning + +views: + path: views + +site: + name: 'Neuron' + url: 'https://neuron.example.com' + description: 'A modern blogging platform built with Neuron.' + +blog: + path: 'blog' + title: 'Neuron Blog' + +# Member Registration and Management Configuration +member: + # Enable or disable user registration + # Set to false to prevent new users from signing up + registration_enabled: true + + # Default role assigned to new users + # Options: admin, editor, author, subscriber + default_role: subscriber + + # Require email verification before users can log in + # When true, users must verify their email before accessing member areas + require_email_verification: true + + # Email verification URL path + # This is appended to the site URL to create the verification link + verification_url: /verify-email + + # Email verification token expiration time (in minutes) + # Tokens older than this will be considered invalid + verification_token_expiration_minutes: 60 diff --git a/readme.md b/readme.md index 7445484..03c1300 100644 --- a/readme.md +++ b/readme.md @@ -68,7 +68,7 @@ The installer will: 1. Create the complete directory structure (app/, config/, db/, public/, resources/, storage/) 2. Publish all view templates (admin panel, blog, auth, layouts) 3. Publish application initializers -4. Create configuration files (config.yaml, routes.yaml, auth.yaml, event-listeners.yaml) +4. Create configuration files (neuron.yaml, routes.yaml, auth.yaml, event-listeners.yaml) 5. Generate the front controller (public/index.php) 6. Set up database migrations 7. Optionally run migrations to create database tables @@ -88,7 +88,8 @@ your-project/ │ ├── Initializers/ # Application initializers │ │ ├── AuthInitializer.php │ │ ├── MaintenanceInitializer.php -│ │ └── PasswordResetInitializer.php +│ │ ├── PasswordResetInitializer.php +│ │ └── ViewDataInitializer.php │ ├── Jobs/ # Background jobs │ ├── Listeners/ # Event listeners │ ├── Models/ # Domain models @@ -97,7 +98,7 @@ your-project/ │ ├── config/ │ ├── auth.yaml # Authentication configuration -│ ├── config.yaml # Main application config +│ ├── neuron.yaml # Main application config │ ├── event-listeners.yaml # Event listener configuration │ └── routes.yaml # Route definitions │ @@ -156,7 +157,7 @@ Log in with the admin credentials you created during installation. ### Optional Configuration -If you need to customize settings, edit `config/config.yaml`: +If you need to customize settings, edit `config/neuron.yaml`: ```yaml site: @@ -265,7 +266,7 @@ You can customize routes by editing `config/routes.yaml`. ### Email -Configure email in `config/config.yaml`: +Configure email in `config/neuron.yaml`: ```yaml email: diff --git a/resources/app/Initializers/RegistrationInitializer.php b/resources/app/Initializers/RegistrationInitializer.php new file mode 100644 index 0000000..faeeddc --- /dev/null +++ b/resources/app/Initializers/RegistrationInitializer.php @@ -0,0 +1,128 @@ +get( 'Settings' ); + + if( !$settings || !$settings instanceof \Neuron\Data\Setting\SettingManager ) + { + Log::error( "Settings not found in Registry, skipping registration initialization" ); + return null; + } + + // Get Application from Registry + $app = Registry::getInstance()->get( 'App' ); + + if( !$app || !$app instanceof \Neuron\Mvc\Application ) + { + Log::error( "Application not found in Registry, skipping registration initialization" ); + return null; + } + + // Get AuthManager from Registry (must be initialized first by AuthInitializer) + $authManager = Registry::getInstance()->get( 'AuthManager' ); + + if( !$authManager ) + { + Log::error( "AuthManager not found in Registry, skipping registration initialization" ); + return null; + } + + // Check if database is configured + try + { + $settingNames = $settings->getSectionSettingNames( 'database' ); + + if( !empty( $settingNames ) ) + { + // Initialize registration components + $userRepository = new DatabaseUserRepository( $settings ); + $tokenRepository = new DatabaseEmailVerificationTokenRepository( $settings ); + $passwordHasher = new PasswordHasher(); + + // Get base path for email templates + $basePath = $settings->get( 'system', 'base_path' ) ?? getcwd(); + + // Get verification URL from settings + $siteUrl = $settings->get( 'site', 'url' ) ?? 'http://localhost:8000'; + $verificationPath = $settings->get( 'member', 'verification_url' ) ?? '/verify-email'; + $verificationUrl = rtrim( $siteUrl, '/' ) . '/' . ltrim( $verificationPath, '/' ); + + // Create EmailVerificationManager + $verificationManager = new EmailVerificationManager( + $tokenRepository, + $userRepository, + $settings, + $basePath, + $verificationUrl + ); + + // Set token expiration from settings + $tokenExpiration = $settings->get( 'member', 'verification_token_expiration_minutes' ) ?? 60; + $verificationManager->setTokenExpirationMinutes( $tokenExpiration ); + + // Get event emitter if available + $emitter = Registry::getInstance()->get( 'EventEmitter' ); + + // Create RegistrationService + $registrationService = new RegistrationService( + $userRepository, + $passwordHasher, + $verificationManager, + $settings, + $emitter + ); + + // Create member authentication filter + $requireVerification = $settings->get( 'member', 'require_email_verification' ) ?? true; + $memberFilter = new MemberAuthenticationFilter( + $authManager, + '/login', + $requireVerification + ); + + // Register the member filter with the Router + $app->getRouter()->registerFilter( 'member', $memberFilter ); + + // Store services in Registry for easy access + Registry::getInstance()->set( 'EmailVerificationManager', $verificationManager ); + Registry::getInstance()->set( 'RegistrationService', $registrationService ); + + Log::info( "Registration system initialized successfully" ); + } + } + catch( \Exception $e ) + { + Log::error( "Failed to initialize registration system: " . $e->getMessage() ); + } + + return null; + } +} diff --git a/resources/app/Initializers/ViewDataInitializer.php b/resources/app/Initializers/ViewDataInitializer.php new file mode 100644 index 0000000..845a6eb --- /dev/null +++ b/resources/app/Initializers/ViewDataInitializer.php @@ -0,0 +1,74 @@ +share( 'siteName', function() use ( $registry ) { + return $registry->get( 'name' ) ?? 'Neuron CMS'; + }); + + // Application version + $provider->share( 'appVersion', function() use ( $registry ) { + return $registry->get( 'version' ) ?? ''; + }); + + // Current authenticated user + $provider->share( 'currentUser', function() use ( $registry ) { + return $registry->get( 'Auth.User' ); + }); + + // Theme from settings with sensible defaults per context + $provider->share( 'theme', function() use ( $registry ) { + $settings = $registry->get( 'Settings' ); + return $settings?->get( 'theme', 'default' ) ?? 'sandstone'; + }); + + // Current year (useful for copyright notices) + $provider->share( 'currentYear', fn() => date( 'Y' ) ); + + // Check if user is authenticated (convenience helper) + $provider->share( 'isAuthenticated', function() use ( $registry ) { + $authManager = $registry->get( 'AuthManager' ); + return $authManager && $authManager->isAuthenticated(); + }); + + return null; + } +} diff --git a/resources/config/auth.yaml b/resources/config/auth.yaml index 8eeeff4..3752d9e 100644 --- a/resources/config/auth.yaml +++ b/resources/config/auth.yaml @@ -38,3 +38,15 @@ auth: storage: type: file # file or database (future) path: storage/users # Path for file-based storage + +# Member Portal Configuration +member: + # Registration settings + registration_enabled: true # Enable/disable public user registration + require_email_verification: true # Require email verification before account activation + default_role: subscriber # Default role for new members + + # Email verification settings + verification_token_expiration_minutes: 60 # Token validity in minutes + verification_url: /verify-email # Verification endpoint URL + resend_throttle_seconds: 60 # Minimum time between resend requests diff --git a/resources/config/database.yaml.example b/resources/config/database.yaml.example index f708280..1dea521 100644 --- a/resources/config/database.yaml.example +++ b/resources/config/database.yaml.example @@ -1,7 +1,7 @@ # Database Configuration # # This file provides database configuration for the CMS component. -# Copy sections to your config/config.yaml +# Copy sections to your config/neuron.yaml database: # Database adapter (mysql, pgsql, sqlite) diff --git a/resources/config/maintenance.yaml.example b/resources/config/maintenance.yaml.example index 7beecf5..03d9db5 100644 --- a/resources/config/maintenance.yaml.example +++ b/resources/config/maintenance.yaml.example @@ -1,7 +1,7 @@ # Maintenance Mode Configuration # # This file provides configuration defaults for the CMS maintenance mode system. -# Copy this file to your config directory and rename to config.yaml (or merge with existing config.yaml) +# Copy this file to your config directory and rename to neuron.yaml (or merge with existing neuron.yaml) maintenance: # Default maintenance message shown to users diff --git a/resources/config/neuron.yaml b/resources/config/neuron.yaml new file mode 100644 index 0000000..a8e27d5 --- /dev/null +++ b/resources/config/neuron.yaml @@ -0,0 +1,69 @@ +# Neuron CMS Configuration +# Main configuration file for the Neuron CMS application + +system: + timezone: UTC + base_path: resources + routes_path: resources/config + +logging: + destination: \Neuron\Log\Destination\File + format: \Neuron\Log\Format\PlainText + file: storage/logs/app.log + level: info + +cache: + enabled: false + storage: file + path: storage/cache/views + ttl: 3600 + html: true + markdown: true + json: false + xml: false + +views: + path: resources/views + +site: + name: 'Neuron CMS' + url: 'https://localhost' + description: 'A modern content management system built with Neuron.' + title: 'Neuron CMS' + +# Theme Configuration +# Controls the Bootswatch theme for different portals +# Available themes: cerulean, cosmo, cyborg, darkly, flatly, journal, litera, +# lumen, lux, materia, minty, morph, pulse, quartz, sandstone, simplex, sketchy, +# slate, solar, spacelab, superhero, united, vapor, yeti, zephyr +theme: + # Admin portal theme (for admin role users) + admin: vapor + + # Member portal theme (for subscriber, editor, author roles) + subscriber: sandstone + + # Guest theme (for public website and login/registration pages) + guest: sandstone + +# Member Registration and Management Configuration +member: + # Enable or disable user registration + # Set to false to prevent new users from signing up + registration_enabled: true + + # Default role assigned to new users + # Options: admin, editor, author, subscriber + default_role: subscriber + + # Require email verification before users can log in + # When true, users must verify their email before accessing member areas + require_email_verification: true + + # Email verification URL path + # This is appended to the site URL to create the verification link + verification_url: /verify-email + + # Email verification token expiration time (in minutes) + # Tokens older than this will be considered invalid + verification_token_expiration_minutes: 60 diff --git a/resources/config/neuron.yaml.example b/resources/config/neuron.yaml.example new file mode 100644 index 0000000..047afbf --- /dev/null +++ b/resources/config/neuron.yaml.example @@ -0,0 +1,78 @@ +# Neuron CMS Configuration Example +# Copy this file to neuron.yaml and customize for your site +# Main configuration file for the Neuron CMS application + +system: + timezone: UTC # Server timezone (e.g., America/New_York, Europe/London, UTC) + base_path: resources # Base path for resources + routes_path: resources/config # Path to routes configuration + +logging: + destination: \Neuron\Log\Destination\File # Log destination class + format: \Neuron\Log\Format\PlainText # Log format class + file: storage/logs/app.log # Log file path + level: info # Log level: debug, info, warning, error + +cache: + enabled: true # Enable view caching + storage: file # Cache storage type + path: storage/cache/views # Cache storage path + ttl: 3600 # Cache time-to-live in seconds + html: true # Cache HTML views + markdown: true # Cache Markdown views + json: false # Cache JSON views + xml: false # Cache XML views + +views: + path: resources/views # Path to view templates + +site: + name: 'Neuron CMS' # Site name + url: 'https://example.com' # Site URL (without trailing slash) + description: 'A modern content management system built with Neuron.' # Site description + title: 'Neuron CMS' # Default page title + +# Theme Configuration +# Controls the Bootswatch theme for different portals +# Themes are role-based: admin, subscriber (member), and guest (public/auth) +# +# Available Bootswatch 5.3+ themes: +# - cerulean: A calm blue sky theme +# - cosmo: An ode to Metro +# - cyborg: Jet black and electric blue +# - darkly: Flatly in night mode +# - flatly: Flat and modern +# - journal: Crisp like a new sheet of paper +# - litera: The medium is the message +# - lumen: Light and shadow +# - lux: A touch of class +# - materia: Material is the metaphor +# - minty: A fresh feel +# - morph: A neumorphic design +# - pulse: A trace of purple +# - quartz: A glassmorphism design +# - sandstone: A touch of warmth +# - simplex: Mini and minimalist +# - sketchy: A hand-drawn look +# - slate: Shades of gunmetal gray +# - solar: A spin on Solarized +# - spacelab: Silvery and sleek +# - superhero: The brave and the blue +# - united: Ubuntu orange and unique font +# - vapor: A cyberpunk aesthetic +# - yeti: A friendly foundation +# - zephyr: Breezy and beautiful +# +# Preview themes at: https://bootswatch.com/ +theme: + # Admin portal theme (for users with admin role) + # Recommended: vapor (dark), darkly, cyborg, slate + admin: vapor + + # Member portal theme (for users with subscriber, editor, author roles) + # Recommended: sandstone, flatly, cosmo, lux, materia + subscriber: sandstone + + # Guest theme (for public website and login/registration pages) + # Recommended: sandstone, flatly, cosmo, litera, lumen, minty + guest: sandstone diff --git a/resources/config/routes.yaml b/resources/config/routes.yaml index a7d1575..68655a0 100644 --- a/resources/config/routes.yaml +++ b/resources/config/routes.yaml @@ -250,3 +250,55 @@ routes: method: GET route: /rss controller: Neuron\Cms\Controllers\Blog@feed + + # Member Registration Routes + register: + method: GET + route: /register + controller: Neuron\Cms\Controllers\Member\Registration@showRegistrationForm + + register_post: + method: POST + route: /register + controller: Neuron\Cms\Controllers\Member\Registration@processRegistration + + verify_email: + method: GET + route: /verify-email + controller: Neuron\Cms\Controllers\Member\Registration@verify + + verify_email_sent: + method: GET + route: /verify-email-sent + controller: Neuron\Cms\Controllers\Member\Registration@showVerificationSent + + resend_verification: + method: POST + route: /resend-verification + controller: Neuron\Cms\Controllers\Member\Registration@resendVerification + + # Member Dashboard Routes + member: + method: GET + route: /member + controller: Neuron\Cms\Controllers\Member\Dashboard@index + filter: member + + member_dashboard: + method: GET + route: /member/dashboard + controller: Neuron\Cms\Controllers\Member\Dashboard@index + filter: member + + # Member Profile Routes + member_profile: + method: GET + route: /member/profile + controller: Neuron\Cms\Controllers\Member\Profile@edit + filter: member + + member_profile_update: + method: PUT + route: /member/profile + controller: Neuron\Cms\Controllers\Member\Profile@update + filter: member diff --git a/resources/database/migrate/20250111000000_create_users_table.php b/resources/database/migrate/20250111000000_create_users_table.php new file mode 100644 index 0000000..70d2774 --- /dev/null +++ b/resources/database/migrate/20250111000000_create_users_table.php @@ -0,0 +1,50 @@ +table( 'users' ); + + $usersTable->addColumn( 'username', 'string', [ 'limit' => 255 ] ) + ->addColumn( 'email', 'string', [ 'limit' => 255 ] ) + ->addColumn( 'password_hash', 'string', [ 'limit' => 255 ] ) + ->addColumn( 'role', 'string', [ 'limit' => 50, 'default' => 'subscriber' ] ) + ->addColumn( 'status', 'string', [ 'limit' => 50, 'default' => 'active' ] ) + ->addColumn( 'email_verified', 'boolean', [ 'default' => false ] ) + ->addColumn( 'two_factor_secret', 'string', [ 'limit' => 255, 'null' => true ] ) + ->addColumn( 'remember_token', 'string', [ 'limit' => 255, 'null' => true ] ) + ->addColumn( 'failed_login_attempts', 'integer', [ 'default' => 0 ] ) + ->addColumn( 'locked_until', 'timestamp', [ 'null' => true ] ) + ->addColumn( 'last_login_at', 'timestamp', [ 'null' => true ] ) + ->addColumn( 'timezone', 'string', [ 'limit' => 50, 'default' => 'UTC' ] ) + ->addColumn( 'created_at', 'timestamp', [ 'default' => 'CURRENT_TIMESTAMP' ] ) + ->addColumn( 'updated_at', 'timestamp', [ 'default' => 'CURRENT_TIMESTAMP', 'update' => 'CURRENT_TIMESTAMP' ] ) + ->addIndex( [ 'username' ], [ 'unique' => true ] ) + ->addIndex( [ 'email' ], [ 'unique' => true ] ) + ->addIndex( [ 'remember_token' ] ) + ->addIndex( [ 'status' ] ) + ->create(); + + // Create password_reset_tokens table + $tokensTable = $this->table( 'password_reset_tokens' ); + + $tokensTable->addColumn( 'email', 'string', [ 'limit' => 255 ] ) + ->addColumn( 'token', 'string', [ 'limit' => 64 ] ) + ->addColumn( 'created_at', 'timestamp', [ 'default' => 'CURRENT_TIMESTAMP' ] ) + ->addColumn( 'expires_at', 'timestamp', [ 'null' => false ] ) + ->addIndex( [ 'email' ] ) + ->addIndex( [ 'token' ] ) + ->addIndex( [ 'expires_at' ] ) + ->create(); + } +} diff --git a/resources/database/migrate/20250112000000_create_email_verification_tokens_table.php b/resources/database/migrate/20250112000000_create_email_verification_tokens_table.php new file mode 100644 index 0000000..2e5a77a --- /dev/null +++ b/resources/database/migrate/20250112000000_create_email_verification_tokens_table.php @@ -0,0 +1,27 @@ +table( 'email_verification_tokens' ); + + $table->addColumn( 'user_id', 'integer', [ 'null' => false ] ) + ->addColumn( 'token', 'string', [ 'limit' => 64 ] ) + ->addColumn( 'created_at', 'timestamp', [ 'default' => 'CURRENT_TIMESTAMP' ] ) + ->addColumn( 'expires_at', 'timestamp', [ 'null' => false ] ) + ->addIndex( [ 'user_id' ] ) + ->addIndex( [ 'token' ], [ 'unique' => true ] ) + ->addIndex( [ 'expires_at' ] ) + ->addForeignKey( 'user_id', 'users', 'id', [ 'delete' => 'CASCADE', 'update' => 'NO_ACTION' ] ) + ->create(); + } +} diff --git a/resources/views/emails/email-verification.php b/resources/views/emails/email-verification.php new file mode 100644 index 0000000..d568efe --- /dev/null +++ b/resources/views/emails/email-verification.php @@ -0,0 +1,183 @@ + + +
+ + +Hello= isset($Username) ? ', ' . htmlspecialchars($Username) : '' ?>!
+ ++ Thank you for registering with = htmlspecialchars($SiteName) ?>. + To complete your registration and activate your account, please verify your email address by clicking the button below: +
+ + + ++ Or copy and paste this link into your browser: +
+ + + ++ Important: This verification link will expire in = htmlspecialchars($ExpirationMinutes) ?> minutes. + If you did not create an account with us, please ignore this email. +
++ Once you verify your email, you'll be able to log in and access all member features. +
+ ++ If you're having trouble clicking the verification button, copy and paste the URL above into your web browser. +
+ +
+ Welcome aboard!
+ The = htmlspecialchars($SiteName) ?> Team
+
Create and manage blog posts, categories, and tags with an intuitive admin interface.
+Fully functional blog with posts, categories, and tags.
Manage users with role-based access control and secure authentication.
+Customizable member section with profile and timezone support.
Built with PHP 8.4+ using modern practices and the Neuron MVC framework.
+Manage users and blog posts in an easy-to-use back end.