Add abstract class

This commit is contained in:
philipp lang 2021-10-30 12:16:27 +02:00
parent 6eff0cbd33
commit a5aec87b86
17 changed files with 291 additions and 253 deletions

View File

@ -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();
}
}

View File

@ -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);

View File

@ -0,0 +1,117 @@
<?php
namespace Zoomyboy\Social\Classes;
use Carbon\Carbon;
use GuzzleHttp\Client;
use Zoomyboy\Social\Models\Page;
use Zoomyboy\Social\Models\Setting;
class InstagramService extends SocialService {
public function getType(): string
{
return 'instagram';
}
public function refresh(Page $page): void
{
$response = $this->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',
]);
}
}
}

65
classes/SocialService.php Normal file
View File

@ -0,0 +1,65 @@
<?php
namespace Zoomyboy\Social\Classes;
use Event;
use Illuminate\Support\Collection;
use Media\Classes\MediaLibrary;
use Zoomyboy\Social\Models\Page;
abstract class SocialService {
protected $page;
protected $media;
abstract public function getType(): string;
public function __construct()
{
$this->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;
}
}

49
console/SocialRefresh.php Normal file
View File

@ -0,0 +1,49 @@
<?php namespace Zoomyboy\Social\Console;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Zoomyboy\Social\Classes\InstagramService;
use Zoomyboy\Social\Models\Page;
/**
* SocialRefresh Command
*/
class SocialRefresh extends Command
{
/**
* @var string name is the console command name
*/
protected $name = 'social:refresh';
/**
* @var string description is the console command description
*/
protected $description = 'refreshes keys for social instagram accounts';
/**
* handle executes the console command
*/
public function handle()
{
Page::get()->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 [];
}
}

View File

@ -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();
}
}
/**

View File

@ -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'],
]);
}

View File

@ -1,8 +1,8 @@
<?php namespace Zoomyboy\Social\Models;
use Model;
use Media\Classes\MediaLibrary;
use Event;
use Media\Classes\MediaLibrary;
use Model;
/**
* Attachment Model
@ -24,7 +24,7 @@ class Attachment extends Model
/**
* @var array Fillable fields
*/
protected $fillable = [ 'facebook_id', 'post_id', 'href', 'type' ];
protected $fillable = [ 'remote_id', 'post_id', 'href', 'type' ];
/**
* @var array Validation rules for attributes

View File

@ -1,75 +0,0 @@
<?php namespace Zoomyboy\Social\Models;
use Model;
/**
* InstagramUser Model
*/
class InstagramUser extends Model
{
use \October\Rain\Database\Traits\Validation;
use \October\Rain\Database\Traits\Sluggable;
/**
* @var string table associated with the model
*/
public $table = 'zoomyboy_social_instagram_users';
public $slugs = ['slug' => '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 = [];
}

View File

@ -71,7 +71,7 @@ class Page extends Model
];
public function getMediaPathAttribute() {
return 'social/facebook/'.$this->slug.'/';
return 'social/'.$this->slug.'/';
}
public function beforeDelete() {

View File

@ -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

View File

@ -1,6 +1,8 @@
<?php namespace Zoomyboy\Social\Models;
use Model;
use Zoomyboy\Social\Classes\FacebookService;
use Zoomyboy\Social\Classes\InstagramService;
/**
* Setting Model
@ -13,9 +15,9 @@ class Setting extends Model
public $settingsFields = 'fields.yaml';
public function getSynchedPagesOptions(): array
public function getFacebookPagesOptions(): array
{
return Page::get()->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();
}
}

View File

@ -36,7 +36,7 @@ tabs:
tab: Instagram
type: zoomyboy_social_instagram_login
synched_pages:
facebook_pages:
type: checkboxlist
tab: Facebook
label: zu synchronisierende Seiten

View File

@ -1,8 +1,8 @@
<?php namespace Zoomyboy\Social\Updates;
use Schema;
use October\Rain\Database\Schema\Blueprint;
use October\Rain\Database\Updates\Migration;
use Schema;
class CreateAttachmentsTable extends Migration
{
@ -14,7 +14,7 @@ class CreateAttachmentsTable extends Migration
$table->string('href');
$table->string('type');
$table->integer('post_id');
$table->string('facebook_id');
$table->string('remote_id');
$table->timestamps();
});
}

View File

@ -1,28 +0,0 @@
<?php namespace Zoomyboy\Social\Updates;
use October\Rain\Database\Schema\Blueprint;
use October\Rain\Database\Updates\Migration;
use Schema;
/**
* CreateInstagramUsersTable Migration
*/
class CreateInstagramUsersTable extends Migration
{
public function up()
{
Schema::create('zoomyboy_social_instagram_users', function (Blueprint $table) {
$table->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');
}
}

View File

@ -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();

View File

@ -3,4 +3,3 @@
- create_pages_table.php
- create_attachments_table.php
- create_accounts_table.php
- create_instagram_users_table.php