From 5a12fd51c4a3be14fc0f04caec637e8279b3282e Mon Sep 17 00:00:00 2001 From: Derek Date: Sat, 9 May 2020 18:40:37 -0400 Subject: [PATCH 1/2] Allow avatar path customization Add config('telescope.avatar_driver') to control where to fetch user avatars shown in the Telescope UI. 1. 'gravatar' looks up the avatar just-in-time using the email address (current default) 2. 'custom' can use a URL from your local application. Call IncomingEntry::avatar() to register a callback that returns a URL string for logged in $user. 3. '' empty string will disable the avatar and hide it from the UI. This will prevent calls from being made to Gravatar servers on each Telescope request. --- config/telescope.php | 13 ++ resources/js/components/PreviewScreen.vue | 9 +- src/Avatar.php | 63 +++++++ src/EntryResult.php | 31 +++- src/Http/Controllers/EntryController.php | 2 +- src/Telescope.php | 12 ++ stubs/TelescopeServiceProvider.stub | 4 + tests/Http/AvatarTest.php | 196 ++++++++++++++++++++++ 8 files changed, 319 insertions(+), 11 deletions(-) create mode 100644 src/Avatar.php create mode 100644 tests/Http/AvatarTest.php diff --git a/config/telescope.php b/config/telescope.php index 1fed214ae..086252d4f 100644 --- a/config/telescope.php +++ b/config/telescope.php @@ -80,6 +80,19 @@ Authorize::class, ], + /* + |-------------------------------------------------------------------------- + | Telescope Avatar Service + |-------------------------------------------------------------------------- + | + | This option may be used to control how to show an avatar for authorized + | users. 'gravatar' will lookup an avatar by the user's email address. + | 'custom' requires registering a callback via Telescope::avatar(). + | + */ + + 'avatar_driver' => 'gravatar', + /* |-------------------------------------------------------------------------- | Ignored Paths & Commands diff --git a/resources/js/components/PreviewScreen.vue b/resources/js/components/PreviewScreen.vue index 3a8fb66bb..a8a767d19 100644 --- a/resources/js/components/PreviewScreen.vue +++ b/resources/js/components/PreviewScreen.vue @@ -63,13 +63,6 @@ command() { return _.find(this.batch, {type: 'command'}) }, - - gravatarUrl() { - if (this.entry.content.user.email) { - const md5 = require('md5') - return 'https://www.gravatar.com/avatar/' + md5(this.entry.content.user.email.toLowerCase()) + '?s=200' - } - } }, @@ -242,7 +235,7 @@ Name - + {{entry.content.user.name}} diff --git a/src/Avatar.php b/src/Avatar.php new file mode 100644 index 000000000..a6c0a8d59 --- /dev/null +++ b/src/Avatar.php @@ -0,0 +1,63 @@ +familyHash = $familyHash; } + /** + * Set the URL to the entry user's avatar. + * + * @return $this + */ + public function generateAvatar() + { + $this->avatar = Avatar::url($this->content['user'] ?? []); + + return $this; + } + /** * Get the array representation of the entry. * @@ -93,7 +112,7 @@ public function __construct($id, $sequence, string $batchId, string $type, ?stri */ public function jsonSerialize() { - return [ + return collect([ 'id' => $this->id, 'sequence' => $this->sequence, 'batch_id' => $this->batchId, @@ -102,6 +121,14 @@ public function jsonSerialize() 'tags' => $this->tags, 'family_hash' => $this->familyHash, 'created_at' => $this->createdAt->toDateTimeString(), - ]; + ])->when($this->avatar, function ($items) { + return $items->mergeRecursive([ + 'content' => [ + 'user' => [ + 'avatar' => $this->avatar, + ], + ], + ]); + })->all(); } } diff --git a/src/Http/Controllers/EntryController.php b/src/Http/Controllers/EntryController.php index 32dc09e71..84fd53519 100644 --- a/src/Http/Controllers/EntryController.php +++ b/src/Http/Controllers/EntryController.php @@ -50,7 +50,7 @@ public function index(Request $request, EntriesRepository $storage) */ public function show(EntriesRepository $storage, $id) { - $entry = $storage->find($id); + $entry = $storage->find($id)->generateAvatar(); return response()->json([ 'entry' => $entry, diff --git a/src/Telescope.php b/src/Telescope.php index 05aaa3328..0d715c9b3 100644 --- a/src/Telescope.php +++ b/src/Telescope.php @@ -684,6 +684,18 @@ public static function night() return new static; } + /** + * Register the Telescope user avatar callback. + * + * @param \Closure $callback + */ + public static function avatar(Closure $callback) + { + if (config('telescope.avatar_driver') === 'custom') { + Avatar::register($callback); + } + } + /** * Get the default JavaScript variables for Telescope. * diff --git a/stubs/TelescopeServiceProvider.stub b/stubs/TelescopeServiceProvider.stub index a9ddad224..f444a92bb 100644 --- a/stubs/TelescopeServiceProvider.stub +++ b/stubs/TelescopeServiceProvider.stub @@ -31,6 +31,10 @@ class TelescopeServiceProvider extends TelescopeApplicationServiceProvider $entry->isScheduledTask() || $entry->hasMonitoredTag(); }); + + Telescope::avatar(function ($id, $email) { + // + }); } /** diff --git a/tests/Http/AvatarTest.php b/tests/Http/AvatarTest.php new file mode 100644 index 000000000..e332d4ec9 --- /dev/null +++ b/tests/Http/AvatarTest.php @@ -0,0 +1,196 @@ +withoutMiddleware(Authorize::class); + } + + protected function getEnvironmentSetUp($app) + { + parent::getEnvironmentSetUp($app); + + $app->get('config')->set('logging.default', 'syslog'); + + $app->get('config')->set('telescope.watchers', [ + LogWatcher::class => true, + ]); + } + + /** + * @test + */ + public function it_can_register_custom_avatar_path() + { + $user = null; + + Telescope::withoutRecording(function () use (&$user) { + $this->loadLaravelMigrations(); + + $user = UserEloquent::create([ + 'id' => 1, + 'name' => 'Telescope', + 'email' => 'telescope@laravel.com', + 'password' => 'secret', + ]); + }); + + $this->app->get('config')->set('telescope.avatar_driver', 'custom'); + + Telescope::avatar(function ($id) { + return "/images/{$id}.jpg"; + }); + + $this->actingAs($user); + + $this->app->get(LoggerInterface::class)->error('Avatar path will be generated.', [ + 'exception' => 'Some error message', + ]); + + $entry = $this->loadTelescopeEntries()->first(); + + $this->get("/telescope/telescope-api/logs/{$entry->uuid}") + ->assertOk() + ->assertJson([ + 'entry' => [ + 'content' => [ + 'user' => [ + 'avatar' => '/images/1.jpg', + ], + ], + ], + ]); + } + + /** + * @test + */ + public function it_will_not_register_custom_avatar_path_when_not_configured() + { + $user = null; + + Telescope::withoutRecording(function () use (&$user) { + $this->loadLaravelMigrations(); + + $user = UserEloquent::create([ + 'id' => 1, + 'name' => 'Telescope', + 'email' => 'telescope@laravel.com', + 'password' => 'secret', + ]); + }); + + Telescope::avatar(function ($id) { + return "/images/{$id}.jpg"; + }); + + $this->actingAs($user); + + $this->app->get(LoggerInterface::class)->error('Avatar path will default to Gravatar.', [ + 'exception' => 'Some error message', + ]); + + $entry = $this->loadTelescopeEntries()->first(); + + $this->get("/telescope/telescope-api/logs/{$entry->uuid}") + ->assertOk() + ->assertJson([ + 'entry' => [ + 'content' => [ + 'user' => [ + 'avatar' => 'https://www.gravatar.com/avatar/dac001a0dfeebe3b320cefa9f3a7d813?s=200', + ], + ], + ], + ]); + } + + /** + * @test + */ + public function it_will_not_set_avatar_path_when_the_configuration_is_empty() + { + $user = null; + + Telescope::withoutRecording(function () use (&$user) { + $this->loadLaravelMigrations(); + + $user = UserEloquent::create([ + 'id' => 1, + 'name' => 'Telescope', + 'email' => 'telescope@laravel.com', + 'password' => 'secret', + ]); + }); + + $this->app->get('config')->set('telescope.avatar_driver', ''); + + Telescope::avatar(function ($id) { + return "/images/{$id}.jpg"; + }); + + $this->actingAs($user); + + $this->app->get(LoggerInterface::class)->error('Avatar path will not be generated.', [ + 'exception' => 'Some error message', + ]); + + $entry = $this->loadTelescopeEntries()->first(); + + $json = $this->get("/telescope/telescope-api/logs/{$entry->uuid}") + ->assertOk() + ->json(); + + $this->assertArrayNotHasKey('avatar', $json['entry']['content']['user']); + } +} + +class UserEloquent extends Model implements Authenticatable +{ + protected $table = 'users'; + + protected $guarded = []; + + public function getAuthIdentifierName() + { + return $this->email; + } + + public function getAuthIdentifier() + { + return $this->id; + } + + public function getAuthPassword() + { + return $this->password; + } + + public function getRememberToken() + { + return 'i-am-telescope'; + } + + public function setRememberToken($value) + { + // + } + + public function getRememberTokenName() + { + // + } +} From f967dd8ce5f7421480a88bb85ca37362e9890c44 Mon Sep 17 00:00:00 2001 From: Derek Date: Sun, 10 May 2020 09:58:14 -0400 Subject: [PATCH 2/2] npm remove md5 Gravatar md5() calls are now done in PHP rather than within Vue component . --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index c2f3f5632..15466b819 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,6 @@ "jquery": "^3.5", "laravel-mix": "^4.0.7", "lodash": "^4.17.13", - "md5": "^2.2.1", "moment": "^2.10.6", "moment-timezone": "^0.5.21", "popper.js": "^1.12",