From a5aec87b86c5ac621846bcbca7559342b951f5a7 Mon Sep 17 00:00:00 2001 From: philipp lang Date: Sat, 30 Oct 2021 12:16:27 +0200 Subject: [PATCH] Add abstract class --- Plugin.php | 3 + ...ookSyncService.php => FacebookService.php} | 68 +++------- classes/InstagramService.php | 117 ++++++++++++++++++ classes/SocialService.php | 65 ++++++++++ console/SocialRefresh.php | 49 ++++++++ console/SocialSync.php | 29 +++-- formwidgets/InstagramLogin.php | 83 ++----------- models/Attachment.php | 6 +- models/InstagramUser.php | 75 ----------- models/Page.php | 2 +- models/Post.php | 2 +- models/Setting.php | 8 +- models/setting/fields.yaml | 2 +- updates/create_attachments_table.php | 4 +- updates/create_instagram_users_table.php | 28 ----- updates/create_posts_table.php | 2 +- updates/version.yaml | 1 - 17 files changed, 291 insertions(+), 253 deletions(-) rename classes/{FacebookSyncService.php => FacebookService.php} (65%) create mode 100644 classes/InstagramService.php create mode 100644 classes/SocialService.php create mode 100644 console/SocialRefresh.php delete mode 100644 models/InstagramUser.php delete mode 100644 updates/create_instagram_users_table.php diff --git a/Plugin.php b/Plugin.php index b12b649..8887f53 100644 --- a/Plugin.php +++ b/Plugin.php @@ -2,6 +2,7 @@ use Backend; use System\Classes\PluginBase; +use Zoomyboy\Social\Console\SocialRefresh; use Zoomyboy\Social\Console\SocialSync; use Zoomyboy\Social\FormWidgets\FacebookLogin; use Zoomyboy\Social\FormWidgets\InstagramLogin; @@ -35,6 +36,7 @@ class Plugin extends PluginBase public function register() { $this->registerConsoleCommand('social-sync', SocialSync::class); + $this->registerConsoleCommand('social-refresh', SocialRefresh::class); } /** @@ -118,6 +120,7 @@ class Plugin extends PluginBase public function registerSchedule($schedule) { $schedule->command('social:sync')->hourly(); + $schedule->command('social:refresh')->monthly(); } } diff --git a/classes/FacebookSyncService.php b/classes/FacebookService.php similarity index 65% rename from classes/FacebookSyncService.php rename to classes/FacebookService.php index 2eff92a..874ce0f 100644 --- a/classes/FacebookSyncService.php +++ b/classes/FacebookService.php @@ -4,34 +4,27 @@ namespace Zoomyboy\Social\Classes; use Carbon\Carbon; use GuzzleHttp\Client; -use Event; -use Media\Classes\MediaLibrary; use Zoomyboy\Social\Models\Page; use Zoomyboy\Social\Models\Post; use Zoomyboy\Social\Models\Setting; -class FacebookSyncService { +class FacebookService extends SocialService { - private static $baseUri = 'https://graph.facebook.com'; + private $baseUri = 'https://graph.facebook.com'; protected $client; protected $page; - protected $token; protected $version = 'v11.0'; protected $media; - public static function page($page, $token) { - return new static($page, $token); + public function __construct() { + parent::__construct(); + + $this->client = new Client([ 'http_errors' => false, 'base_uri' => $this->baseUri ]); } - private function __construct(Page $page) { - $this->media = MediaLibrary::instance(); - $this->page = $page; - $this->token = $page->access_token; - $this->client = new Client([ 'http_errors' => false, 'base_uri' => static::$baseUri ]); - } - - public function clear() { - $this->page->delete(); + public function getType(): string + { + return 'facebook'; } public function posts(): array @@ -43,19 +36,6 @@ class FacebookSyncService { ); } - public function saveUrl(string $source, ?string $filename = null): string - { - $filename = $filename ?: pathinfo(parse_url($source, PHP_URL_PATH), PATHINFO_BASENAME); - $file = $this->page->mediaPath.$filename; - - if (!$this->media->exists($file)) { - $this->media->put($file, file_get_contents($source)); - Event::fire('media.file.upload', [null, $file, null]); - } - - return $file; - } - public function saveCover() { $cover = $this->get($this->page->remote_id, ['fields' => 'cover'], 'cover'); $this->page->update([ 'cover' => $this->saveUrl($cover['source']) ]); @@ -87,8 +67,8 @@ class FacebookSyncService { } $fid = $fid ?: data_get($attachment, 'target.id', ''); - $post->attachments()->updateOrCreate(['facebook_id' => $fid], array_merge($payload, [ - 'facebook_id' => $fid, + $post->attachments()->updateOrCreate(['remote_id' => $fid], array_merge($payload, [ + 'remote_id' => $fid, 'type' => $attachment['type'], ])); } @@ -109,12 +89,12 @@ class FacebookSyncService { $payload = [ 'message' => $post['message'], - 'facebook_id' => $post['id'], + 'remote_id' => $post['id'], 'href' => data_get($post, 'attachments.data.0.target.url'), 'created_at' => Carbon::parse($post['created_time']), ]; - $existing = $this->page->posts()->where('facebook_id', $post['id'])->first(); + $existing = $this->page->posts()->where('remote_id', $post['id'])->first(); if ($existing) { $existing->update($payload); @@ -134,30 +114,10 @@ class FacebookSyncService { } } - // -------------------------------- static api --------------------------------- - // ***************************************************************************** - public static function clearAll(): void - { - foreach (Page::where('type', 'facebook')->get() as $page) { - $service = new static($page); - $service->clear(); - } - } - - public static function syncAll(): void - { - foreach (Page::where('type', 'facebook')->get() as $page) { - $service = new static($page); - $service->sync(); - } - } - - // -------------------------------- Guzzle Api --------------------------------- - // ***************************************************************************** private function get(string $url, array $query = [], ?string $return = null): array { $response = $this->client->get("/{$this->version}/$url", [ - 'query' => array_merge($query, ['access_token' => $this->token]), + 'query' => array_merge($query, ['access_token' => $this->page->access_token]), ]); $data = json_decode((string) $response->getBody(), true); diff --git a/classes/InstagramService.php b/classes/InstagramService.php new file mode 100644 index 0000000..0fdb70d --- /dev/null +++ b/classes/InstagramService.php @@ -0,0 +1,117 @@ +client()->get('/refresh_access_token', ['query' => [ + 'grant_type' => 'ig_refresh_token', + 'access_token' => $page->access_token, + ]]); + + $page->update([ + 'access_token' => json_decode((string) $response->getBody())->access_token + ]); + } + + public function client() + { + return new Client([ + 'base_uri' => 'https://graph.instagram.com', + ]); + } + + public function authClient() + { + return new Client([ + 'base_uri' => 'https://api.instagram.com', + ]); + } + + public function authenticate(): string + { + $response = $this->authClient()->post('/oauth/access_token', [ + 'headers' => ['Content-Type' => 'application/x-www-form-urlencoded'], + 'form_params' => [ + 'client_id' => Setting::get('instagram_client_id'), + 'redirect_uri' => $this->redirectUri(), + 'client_secret' => Setting::get('instagram_client_secret'), + 'grant_type' => 'authorization_code', + 'code' => request()->query('code') + ] + ]); + $accessToken = json_decode((string) $response->getBody())->access_token; + + $response = $this->client()->get('/access_token', ['query' => [ + 'grant_type' => 'ig_exchange_token', + 'client_secret' => Setting::get('instagram_client_secret'), + 'access_token' => $accessToken, + ]]); + return json_decode((string) $response->getBody())->access_token; + } + + public function redirectUri(): string + { + return env('INSTAGRAM_REDIRECT_URI', url()->current()); + } + + public function me(string $accessToken): array + { + $response = $this->client()->get('/me', ['query' => [ + 'access_token' => $accessToken, + 'fields' => 'id,username', + ]]); + + return json_decode((string) $response->getBody(), true); + } + + public function clientId(): string + { + return Setting::get('instagram_client_id'); + } + + public function posts() + { + $response = $this->client()->get('/me/media', ['query' => ['access_token' => $this->page->access_token, 'fields' => 'caption,id,media_url,permalink,timestamp']]); + + return json_decode((string) $response->getBody(), true); + } + + public function sync(): array + { + foreach ($this->posts()['data'] as $image) { + $payload = [ + 'message' => $image['caption'], + 'remote_id' => $image['id'], + 'href' => $image['permalink'], + 'created_at' => Carbon::parse($image['timestamp']), + ]; + + $existing = $this->page->posts()->where('remote_id', $image['id'])->first(); + + if ($existing) { + $existing->update($payload); + } else { + $existing = $this->page->posts()->create($payload); + } + $existing->attachments()->updateOrCreate(['remote_id' => $image['id']], [ + 'remote_id' => $image['id'], + 'href' => $this->saveUrl($image['media_url']), + 'type' => 'image', + ]); + } + } + +} diff --git a/classes/SocialService.php b/classes/SocialService.php new file mode 100644 index 0000000..e32e9ea --- /dev/null +++ b/classes/SocialService.php @@ -0,0 +1,65 @@ +media = MediaLibrary::instance(); + } + + protected function setPage(Page $page): self + { + $this->page = $page; + + return $this; + } + + public function clearAll(): void + { + foreach ($this->pages() as $page) { + $this->setPage($page)->clear(); + } + } + + public function syncAll(): void + { + foreach ($this->pages() as $page) { + $this->setPage($page)->sync(); + } + } + + protected function pages(): Collection + { + return Page::where('type', $this->getType())->get(); + } + + public function clear() { + $this->page->delete(); + } + + public function saveUrl(string $source, ?string $filename = null): string + { + $filename = $filename ?: pathinfo(parse_url($source, PHP_URL_PATH), PATHINFO_BASENAME); + $file = $this->page->mediaPath.$filename; + + if (!$this->media->exists($file)) { + $this->media->put($file, file_get_contents($source)); + Event::fire('media.file.upload', [null, $file, null]); + } + + return $file; + } + +} diff --git a/console/SocialRefresh.php b/console/SocialRefresh.php new file mode 100644 index 0000000..9faaa72 --- /dev/null +++ b/console/SocialRefresh.php @@ -0,0 +1,49 @@ +each(function($account) { + app(InstagramService::class)->refresh($account); + }); + } + + /** + * getArguments get the console command arguments + */ + protected function getArguments() + { + return []; + } + + /** + * getOptions get the console command options + */ + protected function getOptions() + { + return []; + } +} diff --git a/console/SocialSync.php b/console/SocialSync.php index 8b23d37..d38f54e 100644 --- a/console/SocialSync.php +++ b/console/SocialSync.php @@ -4,11 +4,18 @@ use Illuminate\Console\Command; use Storage; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; -use Zoomyboy\Social\Classes\FacebookSyncService; +use Zoomyboy\Social\Classes\FacebookService; +use Zoomyboy\Social\Classes\InstagramService; use Zoomyboy\Social\Models\Setting; class SocialSync extends Command { + + private $services = [ + FacebookService::class, + InstagramService::class, + ]; + /** * @var string The console command name. */ @@ -25,17 +32,19 @@ class SocialSync extends Command */ public function handle() { - if ($this->option('clear', false)) { - return FacebookSyncService::clearAll(); - } + foreach ($this->services as $service) { + if ($this->option('clear', false)) { + return app($service)->clearAll(); + } - if ($this->option('full', false)) { - FacebookSyncService::clearAll(); - FacebookSyncService::syncAll(); - return; - } + if ($this->option('full', false)) { + app($service)->clearAll(); + app($service)->syncAll(); + return; + } - return FacebookSyncService::syncAll(); + app($service)->syncAll(); + } } /** diff --git a/formwidgets/InstagramLogin.php b/formwidgets/InstagramLogin.php index 4bb2704..b0fddd3 100644 --- a/formwidgets/InstagramLogin.php +++ b/formwidgets/InstagramLogin.php @@ -2,7 +2,7 @@ use \Session; use Backend\Classes\FormWidgetBase; -use GuzzleHttp\Client; +use Zoomyboy\Social\Classes\InstagramService; use Zoomyboy\Social\Models\InstagramUser; use Zoomyboy\Social\Models\Page; use Zoomyboy\Social\Models\Setting; @@ -23,49 +23,8 @@ class InstagramLogin extends FormWidgetBase public function init() { if (request()->has('code') && request()->query('state') == Session::get('instagram_auth_state')) { - $this->authenticate(); - } - } - - public function authenticate(): void - { - $response = $this->authClient()->post('/oauth/access_token', [ - 'headers' => ['Content-Type' => 'application/x-www-form-urlencoded'], - 'form_params' => [ - 'client_id' => Setting::get('instagram_client_id'), - 'redirect_uri' => $this->redirectUri(), - 'client_secret' => Setting::get('instagram_client_secret'), - 'grant_type' => 'authorization_code', - 'code' => request()->query('code') - ] - ]); - $accessToken = json_decode((string) $response->getBody())->access_token; - - $response = $this->client()->get('/access_token', ['query' => [ - 'grant_type' => 'ig_exchange_token', - 'client_secret' => Setting::get('instagram_client_secret'), - 'access_token' => $accessToken, - ]]); - $accessToken = json_decode((string) $response->getBody())->access_token; - - $this->storeUser($accessToken); - } - - public function storePages(): void - { - $client = $this->client($auth = true); - - $response = $client->get('/me'); - $userId = json_decode((string) $response->getBody())->id; - - $response = $client->get('/v11.0/'.$userId.'/accounts'); - $response = json_decode((string) $response->getBody()); - - foreach ($response->data as $page) { - Page::updateOrCreate( - ['remote_id' => $page->id], - ['type' => 'facebook', 'remote_id' => $page->id, 'access_token' => $page->access_token, 'name' => $page->name], - ); + $accessToken = app(InstagramService::class)->authenticate(); + $this->storeUser($accessToken); } } @@ -75,11 +34,6 @@ class InstagramLogin extends FormWidgetBase return $this->makePartial('instagramlogin'); } - public function redirectUri(): string - { - return env('INSTAGRAM_REDIRECT_URI', url()->current()); - } - /** * Prepares the form widget view data */ @@ -90,8 +44,8 @@ class InstagramLogin extends FormWidgetBase $this->vars['name'] = $this->formField->getName(); $this->vars['value'] = $this->getLoadValue(); $this->vars['model'] = $this->model; - $this->vars['client_id'] = Setting::get('instagram_client_id'); - $this->vars['redirect_url'] = $this->redirectUri(); + $this->vars['client_id'] = app(InstagramService::class)->clientId(); + $this->vars['redirect_url'] = app(InstagramService::class)->redirectUri(); $this->vars['state'] = $state; } @@ -106,32 +60,15 @@ class InstagramLogin extends FormWidgetBase return $value; } - private function client(bool $auth = false): Client - { - return new Client([ - 'base_uri' => 'https://graph.instagram.com', - ]); - } - - private function authClient(): Client - { - return new Client([ - 'base_uri' => 'https://api.instagram.com', - ]); - } - private function storeUser(string $accessToken): void { - $response = $this->client()->get('/me', ['query' => [ - 'access_token' => $accessToken, - 'fields' => 'id,username', - ]]); - $data = json_decode((string) $response->getBody()); + $me = app(InstagramService::class)->me($accessToken); - InstagramUser::create([ + Page::create([ 'access_token' => $accessToken, - 'name' => $data->username, - 'user_id' => $data->id, + 'type' => app(InstagramService::class)->getType(), + 'name' => $me['username'], + 'remote_id' => $me['id'], ]); } diff --git a/models/Attachment.php b/models/Attachment.php index 1b317c3..cc3233a 100644 --- a/models/Attachment.php +++ b/models/Attachment.php @@ -1,8 +1,8 @@ 'name']; - - /** - * @var array guarded attributes aren't mass assignable - */ - protected $guarded = []; - - /** - * @var array fillable attributes are mass assignable - */ - protected $fillable = []; - - /** - * @var array rules for validation - */ - public $rules = []; - - /** - * @var array Attributes to be cast to native types - */ - protected $casts = []; - - /** - * @var array jsonable attribute names that are json encoded and decoded from the database - */ - protected $jsonable = []; - - /** - * @var array appends attributes to the API representation of the model (ex. toArray()) - */ - protected $appends = []; - - /** - * @var array hidden attributes removed from the API representation of the model (ex. toArray()) - */ - protected $hidden = []; - - /** - * @var array dates attributes that should be mutated to dates - */ - protected $dates = [ - 'created_at', - 'updated_at' - ]; - - /** - * @var array hasOne and other relations - */ - public $hasOne = []; - public $hasMany = []; - public $belongsTo = []; - public $belongsToMany = []; - public $morphTo = []; - public $morphOne = []; - public $morphMany = []; - public $attachOne = []; - public $attachMany = []; -} diff --git a/models/Page.php b/models/Page.php index bfdd0b5..8d0246c 100644 --- a/models/Page.php +++ b/models/Page.php @@ -71,7 +71,7 @@ class Page extends Model ]; public function getMediaPathAttribute() { - return 'social/facebook/'.$this->slug.'/'; + return 'social/'.$this->slug.'/'; } public function beforeDelete() { diff --git a/models/Post.php b/models/Post.php index c17f1c3..60b4046 100644 --- a/models/Post.php +++ b/models/Post.php @@ -23,7 +23,7 @@ class Post extends Model /** * @var array Fillable fields */ - protected $fillable = ['message', 'href', 'facebook_id', 'created_at', 'updated_at']; + protected $fillable = ['message', 'href', 'remote_id', 'created_at', 'updated_at']; /** * @var array Validation rules for attributes diff --git a/models/Setting.php b/models/Setting.php index 271ce37..c692d05 100644 --- a/models/Setting.php +++ b/models/Setting.php @@ -1,6 +1,8 @@ pluck('name', 'id')->toArray(); + return Page::where('type', app(FacebookService::class)->getType())->pluck('name', 'id')->toArray(); } public static function synchedPages(): array @@ -25,7 +27,7 @@ class Setting extends Model public function getInstagramUsersOptions(): array { - return InstagramUser::get()->pluck('name', 'id')->toArray(); + return Page::where('type', app(InstagramService::class)->getType())->pluck('name', 'id')->toArray(); } } diff --git a/models/setting/fields.yaml b/models/setting/fields.yaml index f1be57a..ef10d29 100644 --- a/models/setting/fields.yaml +++ b/models/setting/fields.yaml @@ -36,7 +36,7 @@ tabs: tab: Instagram type: zoomyboy_social_instagram_login - synched_pages: + facebook_pages: type: checkboxlist tab: Facebook label: zu synchronisierende Seiten diff --git a/updates/create_attachments_table.php b/updates/create_attachments_table.php index c9d0eac..9c65373 100644 --- a/updates/create_attachments_table.php +++ b/updates/create_attachments_table.php @@ -1,8 +1,8 @@ string('href'); $table->string('type'); $table->integer('post_id'); - $table->string('facebook_id'); + $table->string('remote_id'); $table->timestamps(); }); } diff --git a/updates/create_instagram_users_table.php b/updates/create_instagram_users_table.php deleted file mode 100644 index 1f376cb..0000000 --- a/updates/create_instagram_users_table.php +++ /dev/null @@ -1,28 +0,0 @@ -increments('id'); - $table->string('user_id'); - $table->string('name'); - $table->string('slug'); - $table->string('access_token', 500); - $table->timestamps(); - }); - } - - public function down() - { - Schema::dropIfExists('zoomyboy_social_instagram_users'); - } -} diff --git a/updates/create_posts_table.php b/updates/create_posts_table.php index 1b133fc..dbf0275 100644 --- a/updates/create_posts_table.php +++ b/updates/create_posts_table.php @@ -12,7 +12,7 @@ class CreatePostsTable extends Migration $table->engine = 'InnoDB'; $table->increments('id'); $table->text('message')->nullable(); - $table->string('facebook_id'); + $table->string('remote_id'); $table->integer('page_id'); $table->string('href', 500)->nullable(); $table->timestamps(); diff --git a/updates/version.yaml b/updates/version.yaml index 8bf36a5..53642ff 100644 --- a/updates/version.yaml +++ b/updates/version.yaml @@ -3,4 +3,3 @@ - create_pages_table.php - create_attachments_table.php - create_accounts_table.php - - create_instagram_users_table.php