From 288b6cf5b36d952e4e38be54cbda4696a00f3653 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Tue, 11 Nov 2025 20:41:45 -0600 Subject: [PATCH 1/4] orm conversion. --- composer.json | 1 + resources/views/admin/dashboard/index.php | 2 +- resources/views/admin/posts/index.php | 2 +- resources/views/admin/profile/edit.php | 38 ++++++++ resources/views/admin/users/index.php | 2 +- resources/views/blog/category.php | 2 +- resources/views/blog/index.php | 2 +- resources/views/blog/show.php | 2 +- resources/views/blog/tag.php | 2 +- src/Bootstrap.php | 25 ++++- .../Cli/Commands/Install/InstallCommand.php | 1 + .../Controllers/Admin/ProfileController.php | 8 +- src/Cms/Models/Category.php | 13 ++- src/Cms/Models/Post.php | 16 +++- src/Cms/Models/Tag.php | 13 ++- src/Cms/Models/User.php | 37 ++++++- .../Repositories/DatabaseUserRepository.php | 8 +- src/Cms/Services/User/Updater.php | 11 ++- src/Cms/View/helpers.php | 96 +++++++++++++++++++ tests/Cms/Auth/AuthManagerTest.php | 1 + .../DatabaseUserRepositoryTest.php | 1 + 21 files changed, 256 insertions(+), 27 deletions(-) diff --git a/composer.json b/composer.json index 5e99ee4..38daa31 100644 --- a/composer.json +++ b/composer.json @@ -15,6 +15,7 @@ "neuron-php/mvc": "0.8.*", "neuron-php/cli": "0.8.*", "neuron-php/jobs": "0.2.*", + "neuron-php/orm": "0.1.*", "phpmailer/phpmailer": "^6.9" }, "require-dev": { diff --git a/resources/views/admin/dashboard/index.php b/resources/views/admin/dashboard/index.php index b3c7992..02e1989 100644 --- a/resources/views/admin/dashboard/index.php +++ b/resources/views/admin/dashboard/index.php @@ -29,7 +29,7 @@

Role: getRole()) ?>

Account Status: getStatus()) ?>

getLastLoginAt()): ?> -

Last Login: getLastLoginAt()->format('F j, Y g:i A') ?>

+

Last Login: getLastLoginAt(), 'F j, Y g:i A') ?>

diff --git a/resources/views/admin/posts/index.php b/resources/views/admin/posts/index.php index 7a7124e..fb7c690 100644 --- a/resources/views/admin/posts/index.php +++ b/resources/views/admin/posts/index.php @@ -27,7 +27,7 @@ getAuthor() ? htmlspecialchars( $post->getAuthor()->getUsername() ) : 'Unknown' ?> getStatus() ) ?> getViewCount() ?> - getCreatedAt() ? $post->getCreatedAt()->format( 'Y-m-d H:i' ) : 'N/A' ?> + getCreatedAt() ) ?> View Edit diff --git a/resources/views/admin/profile/edit.php b/resources/views/admin/profile/edit.php index 2a1eff1..eb7219c 100644 --- a/resources/views/admin/profile/edit.php +++ b/resources/views/admin/profile/edit.php @@ -46,6 +46,44 @@ +
+ + + All dates and times will be displayed in your selected timezone +
+
diff --git a/resources/views/admin/users/index.php b/resources/views/admin/users/index.php index dbacf93..0701139 100644 --- a/resources/views/admin/users/index.php +++ b/resources/views/admin/users/index.php @@ -27,7 +27,7 @@ getEmail() ) ?> getRole() ) ?> getStatus() ) ?> - getCreatedAt() ? $user->getCreatedAt()->format( 'Y-m-d H:i' ) : 'N/A' ?> + getCreatedAt() ) ?> Edit getId() !== $user->getId() ): ?> diff --git a/resources/views/blog/category.php b/resources/views/blog/category.php index 8cff7df..e7f2890 100644 --- a/resources/views/blog/category.php +++ b/resources/views/blog/category.php @@ -24,7 +24,7 @@

By getAuthor() ? htmlspecialchars( $post->getAuthor()->getUsername() ) : 'Unknown' ?> - on getCreatedAt() ? $post->getCreatedAt()->format( 'F j, Y' ) : '' ?> + on getCreatedAt(), 'F j, Y' ) ?>

getExcerpt() ): ?> diff --git a/resources/views/blog/index.php b/resources/views/blog/index.php index a265a50..9654aea 100644 --- a/resources/views/blog/index.php +++ b/resources/views/blog/index.php @@ -17,7 +17,7 @@ getAuthor(); ?> By getUsername() : 'Unknown' ) ?> - on getCreatedAt() ? $Post->getCreatedAt()->format( 'F j, Y' ) : '' ?> + on getCreatedAt(), 'F j, Y' ) ?> getViewCount() > 0 ): ?> · getViewCount() ?> views diff --git a/resources/views/blog/show.php b/resources/views/blog/show.php index e239e93..fb0b3a7 100644 --- a/resources/views/blog/show.php +++ b/resources/views/blog/show.php @@ -8,7 +8,7 @@

getAuthor(); ?> By getUsername() : 'Unknown' ) ?> - on getCreatedAt() ? $Post->getCreatedAt()->format( 'F j, Y' ) : '' ?> + on getCreatedAt(), 'F j, Y' ) ?> getViewCount() > 0 ): ?> · getViewCount() ?> views diff --git a/resources/views/blog/tag.php b/resources/views/blog/tag.php index 31157a7..6ddd5fb 100644 --- a/resources/views/blog/tag.php +++ b/resources/views/blog/tag.php @@ -21,7 +21,7 @@

By getAuthor() ? htmlspecialchars( $post->getAuthor()->getUsername() ) : 'Unknown' ?> - on getCreatedAt() ? $post->getCreatedAt()->format( 'F j, Y' ) : '' ?> + on getCreatedAt(), 'F j, Y' ) ?>

getExcerpt() ): ?> diff --git a/src/Bootstrap.php b/src/Bootstrap.php index eaf5a6e..75e83a0 100644 --- a/src/Bootstrap.php +++ b/src/Bootstrap.php @@ -4,18 +4,20 @@ use Neuron\Data\Object\Version; use Neuron\Data\Setting\Source\Yaml; use Neuron\Mvc\Application; +use Neuron\Orm\Model; +use Neuron\Cms\Database\ConnectionFactory; // Load authentication helper functions require_once __DIR__ . '/Cms/Auth/helpers.php'; /** * CMS Bootstrap Module for the Neuron Framework - * + * * This module provides bootstrap functionality for initializing Neuron CMS * applications. It serves as the entry point for CMS-specific configuration * and setup, extending the base MVC application with content management * capabilities. - * + * * @package Neuron\Cms */ @@ -27,6 +29,8 @@ * and site configuration. It delegates to the MVC boot function but * provides CMS-specific context and naming. * + * Additionally initializes the ORM with the database connection from settings. + * * @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 @@ -34,5 +38,20 @@ function boot( string $configPath ) : Application { - return \Neuron\Mvc\boot( $configPath ); + $app = \Neuron\Mvc\boot( $configPath ); + + // Initialize ORM with PDO connection from settings + try + { + $pdo = ConnectionFactory::createFromSettings( $app->getSettingManager() ); + Model::setPdo( $pdo ); + } + catch( \Exception $e ) + { + // If database configuration is missing or invalid, log but don't fail + // This allows the application to start even without database + error_log( 'CMS ORM initialization warning: ' . $e->getMessage() ); + } + + return $app; } diff --git a/src/Cms/Cli/Commands/Install/InstallCommand.php b/src/Cms/Cli/Commands/Install/InstallCommand.php index 62ce08d..a0a6f90 100644 --- a/src/Cms/Cli/Commands/Install/InstallCommand.php +++ b/src/Cms/Cli/Commands/Install/InstallCommand.php @@ -866,6 +866,7 @@ public function change() ->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 ] ) diff --git a/src/Cms/Controllers/Admin/ProfileController.php b/src/Cms/Controllers/Admin/ProfileController.php index f9c7c2e..4855bdc 100644 --- a/src/Cms/Controllers/Admin/ProfileController.php +++ b/src/Cms/Controllers/Admin/ProfileController.php @@ -59,10 +59,14 @@ public function edit( array $parameters ): string $csrfManager = new CsrfTokenManager( $this->getSessionManager() ); Registry::getInstance()->set( 'Auth.CsrfToken', $csrfManager->getToken() ); + // Get available timezones grouped by region + $timezones = \DateTimeZone::listIdentifiers(); + $viewData = [ 'Title' => 'Profile | ' . $this->getName(), 'Description' => 'Edit Your Profile', 'User' => $user, + 'timezones' => $timezones, 'success' => $this->getSessionManager()->getFlash( 'success' ), 'error' => $this->getSessionManager()->getFlash( 'error' ) ]; @@ -91,6 +95,7 @@ public function update( array $parameters ): never } $email = $_POST['email'] ?? ''; + $timezone = $_POST['timezone'] ?? ''; $currentPassword = $_POST['current_password'] ?? ''; $newPassword = $_POST['new_password'] ?? ''; $confirmPassword = $_POST['confirm_password'] ?? ''; @@ -118,7 +123,8 @@ public function update( array $parameters ): never $user->getUsername(), $email, $user->getRole(), - !empty( $newPassword ) ? $newPassword : null + !empty( $newPassword ) ? $newPassword : null, + !empty( $timezone ) ? $timezone : null ); $this->redirect( 'admin_profile', [], ['success', 'Profile updated successfully'] ); } diff --git a/src/Cms/Models/Category.php b/src/Cms/Models/Category.php index 90bf984..537e181 100644 --- a/src/Cms/Models/Category.php +++ b/src/Cms/Models/Category.php @@ -4,13 +4,16 @@ use DateTimeImmutable; use Exception; +use Neuron\Orm\Model; +use Neuron\Orm\Attributes\{Table, BelongsToMany}; /** * Category entity representing a blog post category. * * @package Neuron\Cms\Models */ -class Category +#[Table('categories')] +class Category extends Model { private ?int $_id = null; private string $_name; @@ -19,6 +22,10 @@ class Category private ?DateTimeImmutable $_createdAt = null; private ?DateTimeImmutable $_updatedAt = null; + // Relationships + #[BelongsToMany(Post::class, pivotTable: 'post_categories')] + private array $_posts = []; + public function __construct() { $this->_createdAt = new DateTimeImmutable(); @@ -130,10 +137,10 @@ public function setUpdatedAt( ?DateTimeImmutable $updatedAt ): self * Create Category from array data * * @param array $data Associative array of category data - * @return Category + * @return static * @throws Exception */ - public static function fromArray( array $data ): Category + public static function fromArray( array $data ): static { $category = new self(); diff --git a/src/Cms/Models/Post.php b/src/Cms/Models/Post.php index cb2e2f4..2b4db19 100644 --- a/src/Cms/Models/Post.php +++ b/src/Cms/Models/Post.php @@ -4,13 +4,16 @@ use DateTimeImmutable; use Exception; +use Neuron\Orm\Model; +use Neuron\Orm\Attributes\{Table, BelongsTo, BelongsToMany}; /** * Post entity representing a blog post. * * @package Neuron\Cms\Models */ -class Post +#[Table('posts')] +class Post extends Model { private ?int $_id = null; private string $_title; @@ -25,9 +28,14 @@ class Post private ?DateTimeImmutable $_createdAt = null; private ?DateTimeImmutable $_updatedAt = null; - // Relationships - these will be populated by the repository + // Relationships - now managed by ORM + #[BelongsTo(User::class, foreignKey: 'author_id')] private ?User $_author = null; + + #[BelongsToMany(Category::class, pivotTable: 'post_categories')] private array $_categories = []; + + #[BelongsToMany(Tag::class, pivotTable: 'post_tags')] private array $_tags = []; /** @@ -424,10 +432,10 @@ public function hasTag( Tag $tag ): bool * Create Post from array data * * @param array $data Associative array of post data - * @return Post + * @return static * @throws Exception */ - public static function fromArray( array $data ): Post + public static function fromArray( array $data ): static { $post = new self(); diff --git a/src/Cms/Models/Tag.php b/src/Cms/Models/Tag.php index d268d8a..784a63f 100644 --- a/src/Cms/Models/Tag.php +++ b/src/Cms/Models/Tag.php @@ -4,13 +4,16 @@ use DateTimeImmutable; use Exception; +use Neuron\Orm\Model; +use Neuron\Orm\Attributes\{Table, BelongsToMany}; /** * Tag entity representing a blog post tag. * * @package Neuron\Cms\Models */ -class Tag +#[Table('tags')] +class Tag extends Model { private ?int $_id = null; private string $_name; @@ -18,6 +21,10 @@ class Tag private ?DateTimeImmutable $_createdAt = null; private ?DateTimeImmutable $_updatedAt = null; + // Relationships + #[BelongsToMany(Post::class, pivotTable: 'post_tags')] + private array $_posts = []; + public function __construct() { $this->_createdAt = new DateTimeImmutable(); @@ -112,10 +119,10 @@ public function setUpdatedAt( ?DateTimeImmutable $updatedAt ): self * Create Tag from array data * * @param array $data Associative array of tag data - * @return Tag + * @return static * @throws Exception */ - public static function fromArray( array $data ): Tag + public static function fromArray( array $data ): static { $tag = new self(); diff --git a/src/Cms/Models/User.php b/src/Cms/Models/User.php index e81d5a6..f1b46cc 100644 --- a/src/Cms/Models/User.php +++ b/src/Cms/Models/User.php @@ -3,13 +3,16 @@ namespace Neuron\Cms\Models; use DateTimeImmutable; +use Neuron\Orm\Model; +use Neuron\Orm\Attributes\{Table, HasMany}; /** * User entity representing a CMS user. * * @package Neuron\Cms\Models */ -class User +#[Table('users')] +class User extends Model { private ?int $_id = null; private string $_username; @@ -26,6 +29,11 @@ class User private ?DateTimeImmutable $_createdAt = null; private ?DateTimeImmutable $_updatedAt = null; private ?DateTimeImmutable $_lastLoginAt = null; + private string $_timezone = 'UTC'; + + // Relationships + #[HasMany(Post::class, foreignKey: 'author_id')] + private array $_posts = []; /** * User roles @@ -382,6 +390,23 @@ public function setLastLoginAt( ?DateTimeImmutable $lastLoginAt ): self return $this; } + /** + * Get user timezone + */ + public function getTimezone(): string + { + return $this->_timezone; + } + + /** + * Set user timezone + */ + public function setTimezone( string $timezone ): self + { + $this->_timezone = $timezone; + return $this; + } + /** * Convert user to array */ @@ -402,14 +427,15 @@ public function toArray(): array 'locked_until' => $this->_lockedUntil?->format( 'Y-m-d H:i:s' ), 'created_at' => $this->_createdAt?->format( 'Y-m-d H:i:s' ), 'updated_at' => $this->_updatedAt?->format( 'Y-m-d H:i:s' ), - 'last_login_at' => $this->_lastLoginAt?->format( 'Y-m-d H:i:s' ) + 'last_login_at' => $this->_lastLoginAt?->format( 'Y-m-d H:i:s' ), + 'timezone' => $this->_timezone ]; } /** * Create user from array */ - public static function fromArray( array $data ): self + public static function fromArray( array $data ): static { $user = new self(); @@ -454,6 +480,11 @@ public static function fromArray( array $data ): self $user->setLastLoginAt( new DateTimeImmutable( $data['last_login_at'] ) ); } + if( isset( $data['timezone'] ) && $data['timezone'] ) + { + $user->setTimezone( $data['timezone'] ); + } + return $user; } } diff --git a/src/Cms/Repositories/DatabaseUserRepository.php b/src/Cms/Repositories/DatabaseUserRepository.php index 2286c85..ae8419b 100644 --- a/src/Cms/Repositories/DatabaseUserRepository.php +++ b/src/Cms/Repositories/DatabaseUserRepository.php @@ -104,8 +104,8 @@ public function create( User $user ): User "INSERT INTO users ( username, email, password_hash, role, status, email_verified, two_factor_secret, remember_token, failed_login_attempts, - locked_until, last_login_at, created_at, updated_at - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" + locked_until, last_login_at, timezone, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" ); $stmt->execute([ @@ -120,6 +120,7 @@ public function create( User $user ): User $user->getFailedLoginAttempts(), $user->getLockedUntil() ? $user->getLockedUntil()->format( 'Y-m-d H:i:s' ) : null, $user->getLastLoginAt() ? $user->getLastLoginAt()->format( 'Y-m-d H:i:s' ) : null, + $user->getTimezone(), $user->getCreatedAt()->format( 'Y-m-d H:i:s' ), (new DateTimeImmutable())->format( 'Y-m-d H:i:s' ) ]); @@ -166,6 +167,7 @@ public function update( User $user ): bool failed_login_attempts = ?, locked_until = ?, last_login_at = ?, + timezone = ?, updated_at = ? WHERE id = ?" ); @@ -182,6 +184,7 @@ public function update( User $user ): bool $user->getFailedLoginAttempts(), $user->getLockedUntil() ? $user->getLockedUntil()->format( 'Y-m-d H:i:s' ) : null, $user->getLastLoginAt() ? $user->getLastLoginAt()->format( 'Y-m-d H:i:s' ) : null, + $user->getTimezone(), (new DateTimeImmutable())->format( 'Y-m-d H:i:s' ), $user->getId() ]); @@ -250,6 +253,7 @@ private function mapRowToUser( array $row ): User 'failed_login_attempts' => (int)$row['failed_login_attempts'], 'locked_until' => $row['locked_until'] ?? null, 'last_login_at' => $row['last_login_at'] ?? null, + 'timezone' => $row['timezone'] ?? 'UTC', 'created_at' => $row['created_at'], 'updated_at' => $row['updated_at'] ?? null, ]; diff --git a/src/Cms/Services/User/Updater.php b/src/Cms/Services/User/Updater.php index fae54f7..bd99fad 100644 --- a/src/Cms/Services/User/Updater.php +++ b/src/Cms/Services/User/Updater.php @@ -37,6 +37,7 @@ public function __construct( * @param string $email Email address * @param string $role User role * @param string|null $password Optional new password (if provided, will be validated and hashed) + * @param string|null $timezone Optional user timezone * @return User * @throws \Exception If password doesn't meet requirements or update fails */ @@ -45,7 +46,8 @@ public function update( string $username, string $email, string $role, - ?string $password = null + ?string $password = null, + ?string $timezone = null ): User { // If password is provided, validate and hash it @@ -62,6 +64,13 @@ public function update( $user->setUsername( $username ); $user->setEmail( $email ); $user->setRole( $role ); + + // Update timezone if provided + if( $timezone !== null && $timezone !== '' ) + { + $user->setTimezone( $timezone ); + } + $user->setUpdatedAt( new \DateTimeImmutable() ); $this->_userRepository->update( $user ); diff --git a/src/Cms/View/helpers.php b/src/Cms/View/helpers.php index ace8ee0..4dd39cf 100644 --- a/src/Cms/View/helpers.php +++ b/src/Cms/View/helpers.php @@ -102,3 +102,99 @@ function route_url(string $routeName, array $parameters = []): string return $urlHelper->routeUrl($routeName, $parameters) ?? ''; } } + +if (!function_exists('format_user_datetime')) { + /** + * Format a DateTimeImmutable object in the authenticated user's timezone + * + * Converts a DateTime from UTC to the user's preferred timezone and formats it. + * Falls back to system timezone if no user is authenticated or user has no timezone set. + * + * @param \DateTimeImmutable|null $dateTime DateTime to format (assumed to be in UTC) + * @param string $format Date format string (default: 'Y-m-d H:i') + * @return string Formatted date/time string, or 'N/A' if dateTime is null + * + * @example + * format_user_datetime($post->getCreatedAt()) // Returns: 2024-11-10 15:30 + * format_user_datetime($post->getCreatedAt(), 'F j, Y g:i A') // Returns: November 10, 2024 3:30 PM + */ + function format_user_datetime(?\DateTimeImmutable $dateTime, string $format = 'Y-m-d H:i'): string + { + if ($dateTime === null) { + return 'N/A'; + } + + // Get authenticated user's timezone + $user = \Neuron\Patterns\Registry::getInstance()->get('Auth.User'); + $userTimezone = $user ? $user->getTimezone() : null; + + // Fall back to system timezone if user timezone not available + if (!$userTimezone) { + $settings = \Neuron\Patterns\Registry::getInstance()->get('Settings'); + $userTimezone = $settings ? $settings->get('system', 'timezone') : date_default_timezone_get(); + } + + try { + // Convert to user's timezone + $timezone = new \DateTimeZone($userTimezone); + $localDateTime = $dateTime->setTimezone($timezone); + return $localDateTime->format($format); + } catch (\Exception $e) { + // If timezone conversion fails, return original format + return $dateTime->format($format); + } + } +} + +if (!function_exists('format_user_date')) { + /** + * Format a DateTimeImmutable object as a date (no time) in the authenticated user's timezone + * + * Convenience wrapper around format_user_datetime for date-only display. + * + * @param \DateTimeImmutable|null $dateTime DateTime to format (assumed to be in UTC) + * @param string $format Date format string (default: 'Y-m-d') + * @return string Formatted date string, or 'N/A' if dateTime is null + * + * @example + * format_user_date($post->getCreatedAt()) // Returns: 2024-11-10 + * format_user_date($post->getCreatedAt(), 'F j, Y') // Returns: November 10, 2024 + */ + function format_user_date(?\DateTimeImmutable $dateTime, string $format = 'Y-m-d'): string + { + return format_user_datetime($dateTime, $format); + } +} + +if (!function_exists('get_timezones')) { + /** + * Get a list of all available timezones grouped by region + * + * Returns an associative array with regions as keys and arrays of timezones as values. + * Useful for populating timezone dropdown selects. + * + * @return array> Grouped timezones + * + * @example + * $timezones = get_timezones(); + * // Returns: ['America' => ['America/New_York', 'America/Chicago', ...], ...] + */ + function get_timezones(): array + { + $timezones = \DateTimeZone::listIdentifiers(); + $grouped = []; + + foreach ($timezones as $timezone) { + $parts = explode('/', $timezone, 2); + if (count($parts) === 2) { + $region = $parts[0]; + if (!isset($grouped[$region])) { + $grouped[$region] = []; + } + $grouped[$region][] = $timezone; + } + } + + return $grouped; + } +} diff --git a/tests/Cms/Auth/AuthManagerTest.php b/tests/Cms/Auth/AuthManagerTest.php index d84de10..a16acab 100644 --- a/tests/Cms/Auth/AuthManagerTest.php +++ b/tests/Cms/Auth/AuthManagerTest.php @@ -77,6 +77,7 @@ private function createUsersTable(): void failed_login_attempts INTEGER DEFAULT 0, locked_until TIMESTAMP NULL, last_login_at TIMESTAMP NULL, + timezone VARCHAR(50) DEFAULT 'UTC', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) diff --git a/tests/Cms/Repositories/DatabaseUserRepositoryTest.php b/tests/Cms/Repositories/DatabaseUserRepositoryTest.php index 16a2088..a02424f 100644 --- a/tests/Cms/Repositories/DatabaseUserRepositoryTest.php +++ b/tests/Cms/Repositories/DatabaseUserRepositoryTest.php @@ -63,6 +63,7 @@ private function createUsersTable(): void failed_login_attempts INTEGER DEFAULT 0, locked_until TIMESTAMP NULL, last_login_at TIMESTAMP NULL, + timezone VARCHAR(50) DEFAULT 'UTC', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) From 697b800ccd7bb3e3635c9c5f77c653b4ea7b38fa Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Tue, 11 Nov 2025 20:54:27 -0600 Subject: [PATCH 2/4] Update resources/views/admin/profile/edit.php Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- resources/views/admin/profile/edit.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/resources/views/admin/profile/edit.php b/resources/views/admin/profile/edit.php index eb7219c..88d442d 100644 --- a/resources/views/admin/profile/edit.php +++ b/resources/views/admin/profile/edit.php @@ -54,6 +54,7 @@ $grouped = []; // Group timezones by region + $grouped['Other'] = []; foreach( $timezones as $timezone ) { $parts = explode( '/', $timezone, 2 ); @@ -66,6 +67,10 @@ } $grouped[$region][] = $timezone; } + else + { + $grouped['Other'][] = $timezone; + } } // Display grouped timezones From 69a1aac4c1a77b7ffd58fcb2839086836f5bdc46 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 12 Nov 2025 02:56:09 +0000 Subject: [PATCH 3/4] Initial plan From 993d9d47b03ba8f20d612b6e40763e354e5851b3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 12 Nov 2025 02:58:46 +0000 Subject: [PATCH 4/4] Fix variable shadowing: rename $timezones to $tzList in inner loop Co-authored-by: ljonesfl <1099983+ljonesfl@users.noreply.github.com> --- resources/views/admin/profile/edit.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/views/admin/profile/edit.php b/resources/views/admin/profile/edit.php index 88d442d..ab75205 100644 --- a/resources/views/admin/profile/edit.php +++ b/resources/views/admin/profile/edit.php @@ -74,10 +74,10 @@ } // Display grouped timezones - foreach( $grouped as $region => $timezones ) + foreach( $grouped as $region => $tzList ) { echo ''; - foreach( $timezones as $timezone ) + foreach( $tzList as $timezone ) { $selected = $timezone === $currentTimezone ? ' selected' : ''; echo '';