From dad985903321d9b6b700ed8d39a34dbc85313260 Mon Sep 17 00:00:00 2001 From: sergotail Date: Wed, 7 Apr 2021 18:49:04 +0300 Subject: [PATCH 01/17] Added .history folder to .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 27e8277..575c8b9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ .idea vendor composer.lock + +.history/ From a14022a98645470c5452f804a3f15993ec0368d5 Mon Sep 17 00:00:00 2001 From: sergotail Date: Thu, 8 Apr 2021 20:15:38 +0300 Subject: [PATCH 02/17] Added geoip timezone info retrieval and events handling customization --- src/LaravelTimezoneServiceProvider.php | 23 ++-- src/Listeners/Auth/UpdateUsersTimezone.php | 119 +++++++++------------ src/Traits/RetrievesGeoIpTimezone.php | 16 +++ src/config/timezone.php | 43 +++++++- 4 files changed, 119 insertions(+), 82 deletions(-) create mode 100644 src/Traits/RetrievesGeoIpTimezone.php diff --git a/src/LaravelTimezoneServiceProvider.php b/src/LaravelTimezoneServiceProvider.php index e20f239..5f291fb 100644 --- a/src/LaravelTimezoneServiceProvider.php +++ b/src/LaravelTimezoneServiceProvider.php @@ -17,6 +17,16 @@ class LaravelTimezoneServiceProvider extends ServiceProvider */ protected $defer = false; + private function registerEventListener(): void + { + Event::listen( + config('timezone.timezone_check.events', null) ?? [ + \Illuminate\Auth\Events\Login::class, + \Laravel\Passport\Events\AccessTokenCreated::class, + ], + config('timezone.timezone_check.listener', null) ?? UpdateUsersTimezone::class + ); + } /** * Perform post-registration booting of services. @@ -76,17 +86,4 @@ public function register() 'timezone' ); } - - /** - * - */ - private function registerEventListener(): void - { - $events = [ - \Illuminate\Auth\Events\Login::class, - \Laravel\Passport\Events\AccessTokenCreated::class, - ]; - - Event::listen($events, UpdateUsersTimezone::class); - } } diff --git a/src/Listeners/Auth/UpdateUsersTimezone.php b/src/Listeners/Auth/UpdateUsersTimezone.php index 7e3eac1..a745a3a 100644 --- a/src/Listeners/Auth/UpdateUsersTimezone.php +++ b/src/Listeners/Auth/UpdateUsersTimezone.php @@ -4,72 +4,21 @@ use Illuminate\Auth\Events\Login; use Illuminate\Support\Facades\Auth; +use JamesMills\LaravelTimezone\Traits\RetrievesGeoIpTimezone; use Laravel\Passport\Events\AccessTokenCreated; -use Torann\GeoIP\Location; class UpdateUsersTimezone { + use RetrievesGeoIpTimezone; - /** - * Handle the event. - * - * @return void - */ - public function handle($event) - { - $user = null; - - /** - * If the event is AccessTokenCreated, - * we logged the user and return, - * stopping the execution. - * - * The Auth::loginUsingId dispatches a Login event, - * making this listener be called again. - */ - if ($event instanceof AccessTokenCreated) { - Auth::loginUsingId($event->userId); - - return; - } - - /** - * If the event is Login, we get the user from the web guard. - */ - if ($event instanceof Login) { - $user = Auth::user(); - } - - /** - * If no user is found, we just return. Nothing to do here. - */ - if (is_null($user)) { - return; - } - - $ip = $this->getFromLookup(); - $geoip_info = geoip()->getLocation($ip); - - if ($user->timezone != $geoip_info['timezone']) { - if (config('timezone.overwrite') == true || $user->timezone == null) { - $user->timezone = $geoip_info['timezone']; - $user->save(); - - $this->notify($geoip_info); - } - } - } - - /** - * @param Location $geoip_info - */ - private function notify(Location $geoip_info) + private function notify(array $info): void { if (config('timezone.flash') == 'off') { return; } - $message = 'We have set your timezone to ' . $geoip_info['timezone']; + // TODO(sergotail): move to config, separate null and default timezone message case + $message = 'We have set your timezone to ' . $info['timezone']; if (config('timezone.flash') == 'laravel') { request()->session()->flash('success', $message); @@ -102,10 +51,7 @@ private function notify(Location $geoip_info) } } - /** - * @return mixed - */ - private function getFromLookup() + private function getFromLookup(): ?string { $result = null; @@ -124,12 +70,7 @@ private function getFromLookup() return $result; } - /** - * @param $type - * @param $keys - * @return string|null - */ - private function lookup($type, $keys) + private function lookup($type, $keys): ?string { $value = null; @@ -142,4 +83,50 @@ private function lookup($type, $keys) return $value; } + + public function handle($event): void + { + $user = null; + + /** + * If the event is AccessTokenCreated, + * we logged the user and return, + * stopping the execution. + * + * The Auth::loginUsingId dispatches a Login event, + * making this listener be called again. + */ + if ($event instanceof AccessTokenCreated) { + Auth::loginUsingId($event->userId); + + return; + } + + /** + * If the event is Login, we get the user from the web guard. + */ + if ($event instanceof Login) { + $user = Auth::user(); + } + + /** + * If no user is found, we just return. Nothing to do here. + */ + if (is_null($user)) { + return; + } + + if ($user->timezone === null || config('timezone.overwrite', false) === true) { + $info = $this->getGeoIpTimezone($this->getFromLookup()); + + if ($user->timezone === null || $user->timezone != $info['timezone']) { + if ($info['timezone'] !== null) { + $user->timezone = $info['timezone']; + $user->save(); + } + + $this->notify($info); + } + } + } } diff --git a/src/Traits/RetrievesGeoIpTimezone.php b/src/Traits/RetrievesGeoIpTimezone.php new file mode 100644 index 0000000..8ff1c96 --- /dev/null +++ b/src/Traits/RetrievesGeoIpTimezone.php @@ -0,0 +1,16 @@ +getLocation($ip); + + return [ + 'timezone' => $info['timezone'] ?? ($info['time_zone'] ?? [])['name'] ?? null, + 'default' => $info['default'] ?? false, + ]; + } +} diff --git a/src/config/timezone.php b/src/config/timezone.php index 7b992b0..ea79374 100644 --- a/src/config/timezone.php +++ b/src/config/timezone.php @@ -15,6 +15,45 @@ 'flash' => 'laravel', + 'timezone_check' => [ + /* + |-------------------------------------------------------------------------- + | Timezone check events + |-------------------------------------------------------------------------- + | + | Here you may configure which events will be listen for user timezone check. + | If null specified, default Laravel Login and AccessTokenCreated events will be listen. + | This option is useful only if custom event handler for custom events specified. + | Examples: + | 1) null + | 2) \App\Events\MyLoginEvent::class + | 3) [ + | \App\Events\MyLoginEvent1::class, + | \App\Events\MyLoginEvent2::class, + | ] + | + */ + + 'events' => null, + + /* + |-------------------------------------------------------------------------- + | Timezone check event listener + |-------------------------------------------------------------------------- + | + | Here you may configure which event handler will be used for user timezone check. + | If null specified, package default event listener will be used. + | This option is useful only if you are using custom login events or want to + | override default event listener, e.g. RetrievesGeoIpTimezone trait. + | Examples: + | null + | \App\EventListeners\MyTimezoneCheckEventListener::class + | + */ + + 'listener' => null, + ], + /* |-------------------------------------------------------------------------- | Overwrite Existing Timezone @@ -54,9 +93,7 @@ 'server' => [ 'REMOTE_ADDR', ], - 'headers' => [ - - ], + 'headers' => [], ], ]; From 1c91415abb856b09bba2f0c59e408cd857f56085 Mon Sep 17 00:00:00 2001 From: sergotail Date: Thu, 8 Apr 2021 22:23:40 +0300 Subject: [PATCH 03/17] Added flash messages basic customization --- src/Listeners/Auth/UpdateUsersTimezone.php | 98 +++++++++++++--------- src/Traits/FlashesMessage.php | 31 +++++++ src/config/timezone.php | 26 ++++++ 3 files changed, 114 insertions(+), 41 deletions(-) create mode 100644 src/Traits/FlashesMessage.php diff --git a/src/Listeners/Auth/UpdateUsersTimezone.php b/src/Listeners/Auth/UpdateUsersTimezone.php index a745a3a..4cb7319 100644 --- a/src/Listeners/Auth/UpdateUsersTimezone.php +++ b/src/Listeners/Auth/UpdateUsersTimezone.php @@ -4,52 +4,15 @@ use Illuminate\Auth\Events\Login; use Illuminate\Support\Facades\Auth; -use JamesMills\LaravelTimezone\Traits\RetrievesGeoIpTimezone; + use Laravel\Passport\Events\AccessTokenCreated; +use JamesMills\LaravelTimezone\Traits\FlashesMessage; +use JamesMills\LaravelTimezone\Traits\RetrievesGeoIpTimezone; class UpdateUsersTimezone { use RetrievesGeoIpTimezone; - - private function notify(array $info): void - { - if (config('timezone.flash') == 'off') { - return; - } - - // TODO(sergotail): move to config, separate null and default timezone message case - $message = 'We have set your timezone to ' . $info['timezone']; - - if (config('timezone.flash') == 'laravel') { - request()->session()->flash('success', $message); - - return; - } - - if (config('timezone.flash') == 'laracasts') { - flash()->success($message); - - return; - } - - if (config('timezone.flash') == 'mercuryseries') { - flashy()->success($message); - - return; - } - - if (config('timezone.flash') == 'spatie') { - flash()->success($message); - - return; - } - - if (config('timezone.flash') == 'mckenziearts') { - notify()->success($message); - - return; - } - } + use FlashesMessage; private function getFromLookup(): ?string { @@ -84,6 +47,59 @@ private function lookup($type, $keys): ?string return $value; } + protected function notify(array $info): void + { + if (config('timezone.flash') === 'off') { + return; + } + + if ($info['timezone'] === null) { + $key = config('timezone.messages.fail.key', 'error'); + $message = config('timezone.messages.fail.message'); + } else { + if ($info['default']) { + $key = config('timezone.messages.default.key', 'warning'); + $message = config('timezone.messages.default.message'); + } else { + $key = config('timezone.messages.success.key', 'info'); + $message = config('timezone.messages.success.message'); + } + + if ($message !== null) { + $message = sprintf($message, $info['timezone']); + } + } + + if ($message === null) { + return; + } + + if (config('timezone.flash') === 'laravel') { + $this->flashLaravelMessage($key, $message); + return; + } + + if (config('timezone.flash') === 'laracasts') { + $this->flashLaracastsMessage($key, $message); + return; + } + + if (config('timezone.flash') === 'mercuryseries') { + $this->flashMercuryseriesMessage($key, $message); + return; + } + + if (config('timezone.flash') === 'spatie') { + $this->flashSpatieMessage($key, $message); + return; + } + + if (config('timezone.flash') === 'mckenziearts') { + $this->flashMckenzieartsMessage($key, $message); + return; + } + } + public function handle($event): void { $user = null; diff --git a/src/Traits/FlashesMessage.php b/src/Traits/FlashesMessage.php new file mode 100644 index 0000000..ca0b99b --- /dev/null +++ b/src/Traits/FlashesMessage.php @@ -0,0 +1,31 @@ +session()->flash($key, $message); + } + + protected function flashLaracastsMessage(string $key, string $message): void + { + flash()->success($message); + } + + protected function flashMercuryseriesMessage(string $key, string $message): void + { + flashy()->success($message); + } + + protected function flashSpatieMessage(string $key, string $message): void + { + flash()->success($message); + } + + protected function flashMckenzieartsMessage(string $key, string $message): void + { + notify()->success($message); + } +} diff --git a/src/config/timezone.php b/src/config/timezone.php index ea79374..566edcc 100644 --- a/src/config/timezone.php +++ b/src/config/timezone.php @@ -96,4 +96,30 @@ 'headers' => [], ], + /* + |-------------------------------------------------------------------------- + | Lookup Array + |-------------------------------------------------------------------------- + | + | Here you may configure flash messages for timezone setup results. + | If null specified, no message will be flashed for current case. + | + */ + + 'messages' => [ + 'fail' => [ + 'key' => 'error', + 'message' => 'We cannot determine your timezone', + ], + + 'default' => [ + 'key' => 'warning', + 'message' => 'We cannot determine your timezone and have set it to default: %s', + ], + + 'success' => [ + 'key' => 'info', + 'message' => 'We have set your timezone to %s', + ], + ], ]; From 1497519894170e36824b195edbe7794d15af8df5 Mon Sep 17 00:00:00 2001 From: sergotail Date: Fri, 9 Apr 2021 01:20:09 +0300 Subject: [PATCH 04/17] Fixed geoip timezone retrieval logic --- src/Traits/RetrievesGeoIpTimezone.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Traits/RetrievesGeoIpTimezone.php b/src/Traits/RetrievesGeoIpTimezone.php index 8ff1c96..98a6466 100644 --- a/src/Traits/RetrievesGeoIpTimezone.php +++ b/src/Traits/RetrievesGeoIpTimezone.php @@ -9,7 +9,7 @@ protected function getGeoIpTimezone(?string $ip): array $info = geoip()->getLocation($ip); return [ - 'timezone' => $info['timezone'] ?? ($info['time_zone'] ?? [])['name'] ?? null, + 'timezone' => ($info['time_zone'] ?? [])['name'] ?? ($info['timezone'] ?? null), 'default' => $info['default'] ?? false, ]; } From 323412f91f893accdeda5c9c5a97009d15a8892c Mon Sep 17 00:00:00 2001 From: sergotail Date: Fri, 9 Apr 2021 01:34:13 +0300 Subject: [PATCH 05/17] Added empty date string customization, done minor code style fixes --- src/Timezone.php | 41 ++++++++++++++++++++++------------------- src/config/timezone.php | 19 ++++++++++++++++--- 2 files changed, 38 insertions(+), 22 deletions(-) diff --git a/src/Timezone.php b/src/Timezone.php index bc2b6d1..bcf8bdc 100644 --- a/src/Timezone.php +++ b/src/Timezone.php @@ -6,16 +6,35 @@ class Timezone { + /** + * @param Carbon\Carbon $date + * @return string + */ + private function formatTimezone(Carbon $date): string + { + $timezone = $date->format('e'); + $parts = explode('/', $timezone); + + if (count($parts) > 1) { + return str_replace('_', ' ', $parts[1]) . ', ' . $parts[0]; + } + + return str_replace('_', ' ', $parts[0]); + } + /** * @param Carbon\Carbon|null $date * @param null $format * @param bool $format_timezone * @return string */ - public function convertToLocal(?Carbon $date, $format = null, $format_timezone = false) : string - { + public function convertToLocal( + ?Carbon $date, + $format = null, + $format_timezone = false + ): string { if (is_null($date)) { - return 'Empty'; + return config('timezone.empty_date', 'Empty'); } $timezone = (auth()->user()->timezone) ?? config('app.timezone'); @@ -43,20 +62,4 @@ public function convertFromLocal($date) : Carbon { return Carbon::parse($date, auth()->user()->timezone)->setTimezone('UTC'); } - - /** - * @param Carbon\Carbon $date - * @return string - */ - private function formatTimezone(Carbon $date) : string - { - $timezone = $date->format('e'); - $parts = explode('/', $timezone); - - if (count($parts) > 1) { - return str_replace('_', ' ', $parts[1]) . ', ' . $parts[0]; - } - - return str_replace('_', ' ', $parts[0]); - } } diff --git a/src/config/timezone.php b/src/config/timezone.php index 566edcc..03a79be 100644 --- a/src/config/timezone.php +++ b/src/config/timezone.php @@ -18,7 +18,7 @@ 'timezone_check' => [ /* |-------------------------------------------------------------------------- - | Timezone check events + | Timezone Check Events |-------------------------------------------------------------------------- | | Here you may configure which events will be listen for user timezone check. @@ -38,7 +38,7 @@ /* |-------------------------------------------------------------------------- - | Timezone check event listener + | Timezone Check Event Listener |-------------------------------------------------------------------------- | | Here you may configure which event handler will be used for user timezone check. @@ -79,12 +79,25 @@ 'format' => 'jS F Y g:i:a', + /* + |-------------------------------------------------------------------------- + | Empty Date + |-------------------------------------------------------------------------- + | + | Here you may configure string to show when date is null. + | If null specified, string 'Empty' will be used. + | + */ + + 'empty_date' => null, + /* |-------------------------------------------------------------------------- | Lookup Array |-------------------------------------------------------------------------- | - | Here you may configure the lookup array whom it will be used to fetch the user remote address. + | Here you may configure the lookup array whom it will be used to fetch + | the user remote address. | When a key is found inside the lookup array that key it will be used. | */ From 7feb2c50560840d83fcbd509c39fe2aa8ddc14e7 Mon Sep 17 00:00:00 2001 From: sergotail Date: Fri, 9 Apr 2021 02:18:43 +0300 Subject: [PATCH 06/17] Added toLocal Timezone method to further work with Carbon, added default client-side timezone --- src/Timezone.php | 57 +++++++++++++++++++---------------------- src/config/timezone.php | 12 +++++++++ 2 files changed, 38 insertions(+), 31 deletions(-) diff --git a/src/Timezone.php b/src/Timezone.php index bcf8bdc..c1c28e4 100644 --- a/src/Timezone.php +++ b/src/Timezone.php @@ -6,11 +6,7 @@ class Timezone { - /** - * @param Carbon\Carbon $date - * @return string - */ - private function formatTimezone(Carbon $date): string + protected function formatTimezone(Carbon $date): string { $timezone = $date->format('e'); $parts = explode('/', $timezone); @@ -22,43 +18,42 @@ private function formatTimezone(Carbon $date): string return str_replace('_', ' ', $parts[0]); } - /** - * @param Carbon\Carbon|null $date - * @param null $format - * @param bool $format_timezone - * @return string - */ - public function convertToLocal( - ?Carbon $date, - $format = null, - $format_timezone = false - ): string { - if (is_null($date)) { - return config('timezone.empty_date', 'Empty'); + public function toLocal(?Carbon $date): ?Carbon + { + if ($date === null) { + return null; } - $timezone = (auth()->user()->timezone) ?? config('app.timezone'); + // TODO(sergotail): use geoip timezone suggestion for non-authorized users too + // (make it configurable) + $timezone = auth()->user()->timezone ?? + config('timezone.default', null) ?? + config('app.timezone'); - $date->setTimezone($timezone); + return $date->copy()->setTimezone($timezone); + } - if (is_null($format)) { - return $date->format(config('timezone.format')); + public function convertToLocal( + ?Carbon $date, + ?string $format = null, + bool $displayTimezone = false + ): string { + $date = $this->toLocal($date); + + if ($date === null) { + return config('timezone.empty_date', 'Empty'); } - $formatted_date_time = $date->format($format); + $formatted = $date->format($format ?? config('timezone.format')); - if ($format_timezone) { - return $formatted_date_time . ' ' . $this->formatTimezone($date); + if ($displayTimezone) { + return $formatted . ' ' . $this->formatTimezone($date); } - return $formatted_date_time; + return $formatted; } - /** - * @param $date - * @return Carbon\Carbon - */ - public function convertFromLocal($date) : Carbon + public function convertFromLocal($date): Carbon { return Carbon::parse($date, auth()->user()->timezone)->setTimezone('UTC'); } diff --git a/src/config/timezone.php b/src/config/timezone.php index 03a79be..accf6b9 100644 --- a/src/config/timezone.php +++ b/src/config/timezone.php @@ -91,6 +91,18 @@ 'empty_date' => null, + /* + |-------------------------------------------------------------------------- + | Default Timezone + |-------------------------------------------------------------------------- + | + | Here you may configure default timezone for requests with no auth user info. + | If null specified, app.timezone config value will be used. + | + */ + + 'default' => null, + /* |-------------------------------------------------------------------------- | Lookup Array From 5548203ed33853706482b55b79f7c67ee9c5ccd4 Mon Sep 17 00:00:00 2001 From: sergotail Date: Fri, 9 Apr 2021 02:21:46 +0300 Subject: [PATCH 07/17] Minor code style fixes --- src/Listeners/Auth/UpdateUsersTimezone.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Listeners/Auth/UpdateUsersTimezone.php b/src/Listeners/Auth/UpdateUsersTimezone.php index 4cb7319..d55264d 100644 --- a/src/Listeners/Auth/UpdateUsersTimezone.php +++ b/src/Listeners/Auth/UpdateUsersTimezone.php @@ -25,7 +25,7 @@ private function getFromLookup(): ?string $result = $this->lookup($type, $keys); - if (is_null($result)) { + if ($result === null) { continue; } } @@ -128,7 +128,7 @@ public function handle($event): void /** * If no user is found, we just return. Nothing to do here. */ - if (is_null($user)) { + if ($user === null) { return; } From fe1c3463af7485ce6bc12f44f8439ec088816296 Mon Sep 17 00:00:00 2001 From: sergotail Date: Fri, 9 Apr 2021 02:33:37 +0300 Subject: [PATCH 08/17] Don't do anything if user has no timezone attribute --- src/Listeners/Auth/UpdateUsersTimezone.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Listeners/Auth/UpdateUsersTimezone.php b/src/Listeners/Auth/UpdateUsersTimezone.php index d55264d..af41825 100644 --- a/src/Listeners/Auth/UpdateUsersTimezone.php +++ b/src/Listeners/Auth/UpdateUsersTimezone.php @@ -126,9 +126,9 @@ public function handle($event): void } /** - * If no user is found, we just return. Nothing to do here. + * If no user is found or it has no timezone attribute, we just return. */ - if ($user === null) { + if ($user === null || !property_exists($user, 'timezone')) { return; } From 6ebf736959a92b0f048c087476da1ce98571602a Mon Sep 17 00:00:00 2001 From: sergotail Date: Fri, 9 Apr 2021 02:58:02 +0300 Subject: [PATCH 09/17] Added per-user timezone overwrite flag --- src/LaravelTimezoneServiceProvider.php | 4 ++-- src/Listeners/Auth/UpdateUsersTimezone.php | 7 +++++-- src/config/timezone.php | 1 + ...ub => add_timezone_columns_to_users_table.php.stub} | 10 +++++++++- 4 files changed, 17 insertions(+), 5 deletions(-) rename src/database/migrations/{add_timezone_column_to_users_table.php.stub => add_timezone_columns_to_users_table.php.stub} (63%) mode change 100755 => 100644 diff --git a/src/LaravelTimezoneServiceProvider.php b/src/LaravelTimezoneServiceProvider.php index 5f291fb..ef068d9 100644 --- a/src/LaravelTimezoneServiceProvider.php +++ b/src/LaravelTimezoneServiceProvider.php @@ -36,9 +36,9 @@ private function registerEventListener(): void public function boot() { // Allow migrations publish - if (! class_exists('AddTimezoneColumnToUsersTable')) { + if (!class_exists('AddTimezoneColumnsToUsersTable')) { $this->publishes([ - __DIR__ . '/database/migrations/add_timezone_column_to_users_table.php.stub' => database_path('/migrations/' . date('Y_m_d_His') . '_add_timezone_column_to_users_table.php'), + __DIR__ . '/database/migrations/add_timezone_columns_to_users_table.php.stub' => database_path('/migrations/' . date('Y_m_d_His') . '_add_timezone_columns_to_users_table.php'), ], 'migrations'); } diff --git a/src/Listeners/Auth/UpdateUsersTimezone.php b/src/Listeners/Auth/UpdateUsersTimezone.php index af41825..3e98d3b 100644 --- a/src/Listeners/Auth/UpdateUsersTimezone.php +++ b/src/Listeners/Auth/UpdateUsersTimezone.php @@ -4,6 +4,7 @@ use Illuminate\Auth\Events\Login; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Schema; use Laravel\Passport\Events\AccessTokenCreated; use JamesMills\LaravelTimezone\Traits\FlashesMessage; @@ -128,11 +129,13 @@ public function handle($event): void /** * If no user is found or it has no timezone attribute, we just return. */ - if ($user === null || !property_exists($user, 'timezone')) { + if ($user === null || !Schema::hasColumn($user->getTable(), 'timezone')) { return; } - if ($user->timezone === null || config('timezone.overwrite', false) === true) { + $overwrite = $user->detect_timezone ?? config('timezone.overwrite', false); + + if ($user->timezone === null || $overwrite === true) { $info = $this->getGeoIpTimezone($this->getFromLookup()); if ($user->timezone === null || $user->timezone != $info['timezone']) { diff --git a/src/config/timezone.php b/src/config/timezone.php index accf6b9..cd6a120 100644 --- a/src/config/timezone.php +++ b/src/config/timezone.php @@ -61,6 +61,7 @@ | | Here you may configure if you would like to overwrite existing | timezones if they have been already set in the database. + | Note that this is default value, per-user value checked for existence first. | options [true, false] | */ diff --git a/src/database/migrations/add_timezone_column_to_users_table.php.stub b/src/database/migrations/add_timezone_columns_to_users_table.php.stub old mode 100755 new mode 100644 similarity index 63% rename from src/database/migrations/add_timezone_column_to_users_table.php.stub rename to src/database/migrations/add_timezone_columns_to_users_table.php.stub index 9799032..dcd6c54 --- a/src/database/migrations/add_timezone_column_to_users_table.php.stub +++ b/src/database/migrations/add_timezone_columns_to_users_table.php.stub @@ -2,8 +2,9 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; -class AddTimezoneColumnToUsersTable extends Migration +class AddTimezoneColumnsToUsersTable extends Migration { /** * Run the migrations. @@ -17,6 +18,12 @@ class AddTimezoneColumnToUsersTable extends Migration $table->string('timezone')->after('remember_token')->nullable(); }); } + + if (!Schema::hasColumn('users', 'detect_timezone')) { + Schema::table('users', function (Blueprint $table) { + $table->boolean('detect_timezone')->after('timezone')->default(false); + }); + } } /** @@ -28,6 +35,7 @@ class AddTimezoneColumnToUsersTable extends Migration { Schema::table('users', function (Blueprint $table) { $table->dropColumn('timezone'); + $table->dropColumn('detect_timezone'); }); } } From 1d0bf3887bd0eee7061c1492ddaebcf3b4858145 Mon Sep 17 00:00:00 2001 From: sergotail Date: Fri, 9 Apr 2021 15:23:42 +0300 Subject: [PATCH 10/17] Added HasTimezone trait for User model to customize timezone attributes storage source --- src/Listeners/Auth/UpdateUsersTimezone.php | 19 ++++++---- src/Timezone.php | 4 +-- src/Traits/HasTimezone.php | 40 ++++++++++++++++++++++ 3 files changed, 55 insertions(+), 8 deletions(-) create mode 100644 src/Traits/HasTimezone.php diff --git a/src/Listeners/Auth/UpdateUsersTimezone.php b/src/Listeners/Auth/UpdateUsersTimezone.php index 3e98d3b..0e5fa66 100644 --- a/src/Listeners/Auth/UpdateUsersTimezone.php +++ b/src/Listeners/Auth/UpdateUsersTimezone.php @@ -127,20 +127,27 @@ public function handle($event): void } /** - * If no user is found or it has no timezone attribute, we just return. + * If no user is found or it has no timezone-related methods, return. */ - if ($user === null || !Schema::hasColumn($user->getTable(), 'timezone')) { + if ( + $user === null || + !method_exists($user, 'getTimezone') || + !method_exists($user, 'getDetectTimezone') || + !method_exists($user, 'setTimezone') || + !method_exists($user, 'setDetectTimezone') + ) { return; } - $overwrite = $user->detect_timezone ?? config('timezone.overwrite', false); + $overwrite = $user->getDetectTimezone() ?? config('timezone.overwrite', true); + $timezone = $user->getTimezone(); - if ($user->timezone === null || $overwrite === true) { + if ($timezone === null || $overwrite === true) { $info = $this->getGeoIpTimezone($this->getFromLookup()); - if ($user->timezone === null || $user->timezone != $info['timezone']) { + if ($timezone === null || $timezone !== $info['timezone']) { if ($info['timezone'] !== null) { - $user->timezone = $info['timezone']; + $user->setTimezone($info['timezone']); $user->save(); } diff --git a/src/Timezone.php b/src/Timezone.php index c1c28e4..b783b20 100644 --- a/src/Timezone.php +++ b/src/Timezone.php @@ -26,7 +26,7 @@ public function toLocal(?Carbon $date): ?Carbon // TODO(sergotail): use geoip timezone suggestion for non-authorized users too // (make it configurable) - $timezone = auth()->user()->timezone ?? + $timezone = auth()->user()->getTimezone() ?? config('timezone.default', null) ?? config('app.timezone'); @@ -55,6 +55,6 @@ public function convertToLocal( public function convertFromLocal($date): Carbon { - return Carbon::parse($date, auth()->user()->timezone)->setTimezone('UTC'); + return Carbon::parse($date, auth()->user()->getTimezone())->setTimezone('UTC'); } } diff --git a/src/Traits/HasTimezone.php b/src/Traits/HasTimezone.php new file mode 100644 index 0000000..ff7f260 --- /dev/null +++ b/src/Traits/HasTimezone.php @@ -0,0 +1,40 @@ +timezone; + if ($timezone === null) { + return null; + } + + return (string) $timezone; + } + + public function getDetectTimezone(): ?bool + { + $detectTimezone = $this->detect_timezone; + if ($detectTimezone === null) { + return null; + } + + return (bool) $detectTimezone; + } + + public function setTimezone(?string $timezone) + { + $this->timezone = $timezone; + + return $this; + } + + public function setDetectTimezone(?bool $detectTimezone) + { + $this->detect_timezone = $detectTimezone; + + return $this; + } +} From c8a0ed7376ef5ad453503b3ae6423ad9bb1d260e Mon Sep 17 00:00:00 2001 From: sergotail Date: Fri, 9 Apr 2021 15:25:44 +0300 Subject: [PATCH 11/17] Updated readme --- README.md | 49 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 77c62b7..834ed4f 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ An easy way to set a timezone for a user in your application and then show date/ ## How does it work -This package listens for the `\Illuminate\Auth\Events\Login` event and will then automatically set a `timezone` on your `user` model (stored in the database). +This package listens for the `\Illuminate\Auth\Events\Login` event and will then automatically set a `timezone` on your `user` model (stored in the database). It decides whether to update user timezone or not according to user `detect_timezone` attribute, or, if not set, according to default value in config. For non-authorized routes, where auth user info is not accessible, package will use default timezone from its config. This package uses the [torann/geoip](http://lyften.com/projects/laravel-geoip/doc/) package which looks up the users location based on their IP address. The package also returns information like the users currency and users timezone. [You can configure this package separately if you require](#custom-configuration). @@ -34,24 +34,53 @@ Or use our nice blade directive ## Installation -Pull in the package using Composer +### Pull in the package using Composer ``` composer require jamesmills/laravel-timezone ``` -Publish database migrations - +### Publish database migrations + ``` php artisan vendor:publish --provider="JamesMills\LaravelTimezone\LaravelTimezoneServiceProvider" --tag=migrations ``` -Run the database migrations. This will add a `timezone` column to your `users` table. +Run the database migrations. This will add `timezone` and `detect_timezone` columns to your `users` table. Note that migration will be placed to default Laravel migrations folder, so if you use custom folder, you should move migration file to appropriate location. ``` php artisan migrate ``` +### Update User model + +Add `JamesMills\LaravelTimezone\Traits\HasTimezone` trait to your `user` model: + +```php +use HasTimezone; +``` + +If you use package default attributes setup, `User` model has attribute `detect_timezone`. +If you wish to work with it, you can add boolean cast for your `User` model: + +```php +protected $casts = [ + 'detect_timezone' => 'boolean', +]; +``` + +If you wish to use per-user timezone overwriting, you can add `detect_timezone` attribute to your `User` model fillable property: + +```php +protected $fillable = [ + 'detect_timezone', + ]; +``` + +### Custom timezone attributes location + +If you wish to use different location for `timezone` and `detect_timezone` attributes, e.g. `Profile` model, you should override `HasTimezone` trait and use overriden one in your `User` model. + ## Examples ### Showing date/time to the user in their timezone @@ -72,6 +101,14 @@ If you wish you can set a custom format and also include a nice version of the t // 2018-07-04 3:32 New York, America ``` +If you wish to further work with converted Carbon instance, you can use toLocal method: + +```php +{{ Timezone::toLocal($post->created_at)->diffForHumans() }} + +// diff calculated relative to datetime with user-end timezone +``` + ### Using blade directive Making your life easier one small step at a time @@ -125,7 +162,7 @@ To override this configuration, you just need to change the `flash` property ins ### Overwrite existing timezones in the database -By default, the timezone will be overwritten at each login with the current user timezone. This behavior can be restricted to only update the timezone if it is blank by setting the `'overwrite' => false,` config option. +User timezone will be overwritten at each login with the current user timezone if `detect_timezone` is set to true for this user. If this attribute is not set, by default, the timezone will be overwritten. This behavior can be restricted to only update the timezone if it is blank by setting the `'overwrite' => false,` config option. ### Default Format From a21f2cec701ac24dddaf8b8951a503838343ec69 Mon Sep 17 00:00:00 2001 From: sergotail Date: Fri, 9 Apr 2021 15:59:21 +0300 Subject: [PATCH 12/17] Removed unneeded Schema usage --- src/Listeners/Auth/UpdateUsersTimezone.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Listeners/Auth/UpdateUsersTimezone.php b/src/Listeners/Auth/UpdateUsersTimezone.php index 0e5fa66..54774e1 100644 --- a/src/Listeners/Auth/UpdateUsersTimezone.php +++ b/src/Listeners/Auth/UpdateUsersTimezone.php @@ -4,7 +4,6 @@ use Illuminate\Auth\Events\Login; use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\Schema; use Laravel\Passport\Events\AccessTokenCreated; use JamesMills\LaravelTimezone\Traits\FlashesMessage; From d144dcf995f4a64baf379271579edbdb2d3c183a Mon Sep 17 00:00:00 2001 From: sergotail Date: Fri, 9 Apr 2021 17:27:16 +0300 Subject: [PATCH 13/17] Set default values if no config provided --- src/Listeners/Auth/UpdateUsersTimezone.php | 4 ++-- src/Timezone.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Listeners/Auth/UpdateUsersTimezone.php b/src/Listeners/Auth/UpdateUsersTimezone.php index 54774e1..558e93f 100644 --- a/src/Listeners/Auth/UpdateUsersTimezone.php +++ b/src/Listeners/Auth/UpdateUsersTimezone.php @@ -18,7 +18,7 @@ private function getFromLookup(): ?string { $result = null; - foreach (config('timezone.lookup') as $type => $keys) { + foreach (config('timezone.lookup', []) as $type => $keys) { if (empty($keys)) { continue; } @@ -49,7 +49,7 @@ private function lookup($type, $keys): ?string protected function notify(array $info): void { - if (config('timezone.flash') === 'off') { + if (config('timezone.flash', 'off') === 'off') { return; } diff --git a/src/Timezone.php b/src/Timezone.php index b783b20..b18eec4 100644 --- a/src/Timezone.php +++ b/src/Timezone.php @@ -44,7 +44,7 @@ public function convertToLocal( return config('timezone.empty_date', 'Empty'); } - $formatted = $date->format($format ?? config('timezone.format')); + $formatted = $date->format($format ?? config('timezone.format', 'jS F Y g:i:a')); if ($displayTimezone) { return $formatted . ' ' . $this->formatTimezone($date); From 7b9c5e7694e209bef02cf6a736b2eb7172fc0d3c Mon Sep 17 00:00:00 2001 From: sergotail Date: Fri, 9 Apr 2021 17:27:40 +0300 Subject: [PATCH 14/17] Refactored config file --- src/config/timezone.php | 134 ++++++++++++++++++++-------------------- 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/src/config/timezone.php b/src/config/timezone.php index cd6a120..fe99974 100644 --- a/src/config/timezone.php +++ b/src/config/timezone.php @@ -4,59 +4,19 @@ /* |-------------------------------------------------------------------------- - | Flash messages + | Default Timezone |-------------------------------------------------------------------------- | - | Here you may configure if to use the laracasts/flash package for flash - | notifications when a users timezone is set. - | options [off, laravel, laracasts, mercuryseries, spatie, mckenziearts] + | Here you may configure default timezone for requests with no auth user info. + | If null specified, app.timezone config value will be used. | */ - 'flash' => 'laravel', - - 'timezone_check' => [ - /* - |-------------------------------------------------------------------------- - | Timezone Check Events - |-------------------------------------------------------------------------- - | - | Here you may configure which events will be listen for user timezone check. - | If null specified, default Laravel Login and AccessTokenCreated events will be listen. - | This option is useful only if custom event handler for custom events specified. - | Examples: - | 1) null - | 2) \App\Events\MyLoginEvent::class - | 3) [ - | \App\Events\MyLoginEvent1::class, - | \App\Events\MyLoginEvent2::class, - | ] - | - */ - - 'events' => null, - - /* - |-------------------------------------------------------------------------- - | Timezone Check Event Listener - |-------------------------------------------------------------------------- - | - | Here you may configure which event handler will be used for user timezone check. - | If null specified, package default event listener will be used. - | This option is useful only if you are using custom login events or want to - | override default event listener, e.g. RetrievesGeoIpTimezone trait. - | Examples: - | null - | \App\EventListeners\MyTimezoneCheckEventListener::class - | - */ - - 'listener' => null, - ], + 'default' => null, /* |-------------------------------------------------------------------------- - | Overwrite Existing Timezone + | Overwrite Existing Timezone Default Value |-------------------------------------------------------------------------- | | Here you may configure if you would like to overwrite existing @@ -70,57 +30,58 @@ /* |-------------------------------------------------------------------------- - | Overwrite Default Format + | Lookup Array |-------------------------------------------------------------------------- | - | Here you may configure if you would like to overwrite the - | default format. + | Here you may configure the lookup array whom it will be used to fetch + | the user remote address. + | When a key is found inside the lookup array that key it will be used. | */ - 'format' => 'jS F Y g:i:a', + 'lookup' => [ + 'server' => [ + 'REMOTE_ADDR', + ], + 'headers' => [], + ], /* |-------------------------------------------------------------------------- - | Empty Date + | DateTime Default Format |-------------------------------------------------------------------------- | - | Here you may configure string to show when date is null. - | If null specified, string 'Empty' will be used. + | Here you may configure if you would like to overwrite the + | default format. | */ - 'empty_date' => null, + 'format' => 'jS F Y g:i:a', /* |-------------------------------------------------------------------------- - | Default Timezone + | Empty DateTime String |-------------------------------------------------------------------------- | - | Here you may configure default timezone for requests with no auth user info. - | If null specified, app.timezone config value will be used. + | Here you may configure string to show when date is null. + | If null specified, string 'Empty' will be used. | */ - 'default' => null, + 'empty_date' => null, /* |-------------------------------------------------------------------------- - | Lookup Array + | Flash messages |-------------------------------------------------------------------------- | - | Here you may configure the lookup array whom it will be used to fetch - | the user remote address. - | When a key is found inside the lookup array that key it will be used. + | Here you may configure if to use the laracasts/flash package for flash + | notifications when a users timezone is set. + | options [off, laravel, laracasts, mercuryseries, spatie, mckenziearts] | */ - 'lookup' => [ - 'server' => [ - 'REMOTE_ADDR', - ], - 'headers' => [], - ], + 'flash' => 'laravel', /* |-------------------------------------------------------------------------- @@ -148,4 +109,43 @@ 'message' => 'We have set your timezone to %s', ], ], + + 'timezone_check' => [ + /* + |-------------------------------------------------------------------------- + | Timezone Check Events + |-------------------------------------------------------------------------- + | + | Here you may configure which events will be listen for user timezone check. + | If null specified, default Laravel Login and AccessTokenCreated events will be listen. + | This option is useful only if custom event handler for custom events specified. + | Examples: + | 1) null + | 2) \App\Events\MyLoginEvent::class + | 3) [ + | \App\Events\MyLoginEvent1::class, + | \App\Events\MyLoginEvent2::class, + | ] + | + */ + + 'events' => null, + + /* + |-------------------------------------------------------------------------- + | Timezone Check Event Listener + |-------------------------------------------------------------------------- + | + | Here you may configure which event handler will be used for user timezone check. + | If null specified, package default event listener will be used. + | This option is useful only if you are using custom login events or want to + | override default event listener, e.g. RetrievesGeoIpTimezone trait. + | Examples: + | null + | \App\EventListeners\MyTimezoneCheckEventListener::class + | + */ + + 'listener' => null, + ], ]; From 2bc439c9a86b0133e57c35f24172a0fb230aeade Mon Sep 17 00:00:00 2001 From: sergotail Date: Fri, 9 Apr 2021 17:28:04 +0300 Subject: [PATCH 15/17] Fixed migration to make detect_timezone nullable with no default value --- .../migrations/add_timezone_columns_to_users_table.php.stub | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/database/migrations/add_timezone_columns_to_users_table.php.stub b/src/database/migrations/add_timezone_columns_to_users_table.php.stub index dcd6c54..dbf4a8b 100644 --- a/src/database/migrations/add_timezone_columns_to_users_table.php.stub +++ b/src/database/migrations/add_timezone_columns_to_users_table.php.stub @@ -21,7 +21,7 @@ class AddTimezoneColumnsToUsersTable extends Migration if (!Schema::hasColumn('users', 'detect_timezone')) { Schema::table('users', function (Blueprint $table) { - $table->boolean('detect_timezone')->after('timezone')->default(false); + $table->boolean('detect_timezone')->after('timezone')->nullable(); }); } } From f6515f6670a526920d41ff15a15db933e001ef39 Mon Sep 17 00:00:00 2001 From: sergotail Date: Fri, 9 Apr 2021 17:30:15 +0300 Subject: [PATCH 16/17] Updated readme --- README.md | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 834ed4f..4fe09a6 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ This package listens for the `\Illuminate\Auth\Events\Login` event and will then This package uses the [torann/geoip](http://lyften.com/projects/laravel-geoip/doc/) package which looks up the users location based on their IP address. The package also returns information like the users currency and users timezone. [You can configure this package separately if you require](#custom-configuration). - ## How to use +## How to use You can show dates to your user in their timezone by using @@ -36,23 +36,27 @@ Or use our nice blade directive ### Pull in the package using Composer -``` +```bash composer require jamesmills/laravel-timezone ``` -### Publish database migrations +### Default timezone attributes location -``` +By default, timezone attributes placed into `users` table. If you wish to use package with this default, see instructions below. + +#### Publish database migrations + +```bash php artisan vendor:publish --provider="JamesMills\LaravelTimezone\LaravelTimezoneServiceProvider" --tag=migrations ``` Run the database migrations. This will add `timezone` and `detect_timezone` columns to your `users` table. Note that migration will be placed to default Laravel migrations folder, so if you use custom folder, you should move migration file to appropriate location. -``` +```bash php artisan migrate ``` -### Update User model +#### Update User model Add `JamesMills\LaravelTimezone\Traits\HasTimezone` trait to your `user` model: @@ -60,8 +64,7 @@ Add `JamesMills\LaravelTimezone\Traits\HasTimezone` trait to your `user` model: use HasTimezone; ``` -If you use package default attributes setup, `User` model has attribute `detect_timezone`. -If you wish to work with it, you can add boolean cast for your `User` model: +If you wish to work with `detect_timezone` attribute directly, you can add boolean cast for your `User` model: ```php protected $casts = [ @@ -69,11 +72,11 @@ protected $casts = [ ]; ``` -If you wish to use per-user timezone overwriting, you can add `detect_timezone` attribute to your `User` model fillable property: +If you wish to set per-user timezone overwriting at user creation time, you can add `detect_timezone` attribute to your `User` model fillable property: ```php protected $fillable = [ - 'detect_timezone', + 'detect_timezone', ]; ``` From b949000c1d8d68a927771fd0e246a5c09dec71cc Mon Sep 17 00:00:00 2001 From: sergotail Date: Fri, 9 Apr 2021 20:20:07 +0300 Subject: [PATCH 17/17] StyleCI fix --- src/config/timezone.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/timezone.php b/src/config/timezone.php index fe99974..b0f870e 100644 --- a/src/config/timezone.php +++ b/src/config/timezone.php @@ -33,7 +33,7 @@ | Lookup Array |-------------------------------------------------------------------------- | - | Here you may configure the lookup array whom it will be used to fetch + | Here you may configure the lookup array whom it will be used to fetch | the user remote address. | When a key is found inside the lookup array that key it will be used. |