From d148911af38fce6543a31dc899ab2cc8daf391ff Mon Sep 17 00:00:00 2001 From: philipp lang Date: Thu, 28 Oct 2021 22:35:15 +0200 Subject: [PATCH] Initial Commit --- .gitignore | 1 + Plugin.php | 123 +++++ classes/FacebookSyncService.php | 170 +++++++ components/FacebookPageFeed.php | 51 ++ components/facebookpagefeed/default.htm | 39 ++ composer.json | 12 + composer.lock | 461 ++++++++++++++++++ console/SocialSync.php | 61 +++ formwidgets/FacebookLogin.php | 119 +++++ formwidgets/InstagramLogin.php | 139 ++++++ .../assets/css/facebooklogin.css | 5 + .../facebooklogin/assets/js/facebooklogin.js | 5 + .../facebooklogin/partials/_facebooklogin.htm | 10 + .../assets/css/facebooklogin.css | 5 + .../instagramlogin/assets/js/facebooklogin.js | 5 + .../partials/_instagramlogin.htm | 1 + models/Account.php | 72 +++ models/Attachment.php | 82 ++++ models/InstagramUser.php | 75 +++ models/Page.php | 91 ++++ models/Post.php | 95 ++++ models/Setting.php | 31 ++ models/account/columns.yaml | 8 + models/account/fields.yaml | 8 + models/instagramuser/columns.yaml | 8 + models/instagramuser/fields.yaml | 8 + models/setting/fields.yaml | 42 ++ updates/.version.yaml.swp | Bin 0 -> 12288 bytes updates/create_accounts_table.php | 28 ++ updates/create_attachments_table.php | 26 + updates/create_instagram_users_table.php | 28 ++ updates/create_pages_table.php | 28 ++ updates/create_posts_table.php | 26 + updates/version.yaml | 6 + 34 files changed, 1869 insertions(+) create mode 100644 .gitignore create mode 100644 Plugin.php create mode 100644 classes/FacebookSyncService.php create mode 100644 components/FacebookPageFeed.php create mode 100644 components/facebookpagefeed/default.htm create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 console/SocialSync.php create mode 100644 formwidgets/FacebookLogin.php create mode 100644 formwidgets/InstagramLogin.php create mode 100644 formwidgets/facebooklogin/assets/css/facebooklogin.css create mode 100644 formwidgets/facebooklogin/assets/js/facebooklogin.js create mode 100644 formwidgets/facebooklogin/partials/_facebooklogin.htm create mode 100644 formwidgets/instagramlogin/assets/css/facebooklogin.css create mode 100644 formwidgets/instagramlogin/assets/js/facebooklogin.js create mode 100644 formwidgets/instagramlogin/partials/_instagramlogin.htm create mode 100644 models/Account.php create mode 100644 models/Attachment.php create mode 100644 models/InstagramUser.php create mode 100644 models/Page.php create mode 100644 models/Post.php create mode 100644 models/Setting.php create mode 100644 models/account/columns.yaml create mode 100644 models/account/fields.yaml create mode 100644 models/instagramuser/columns.yaml create mode 100644 models/instagramuser/fields.yaml create mode 100644 models/setting/fields.yaml create mode 100644 updates/.version.yaml.swp create mode 100644 updates/create_accounts_table.php create mode 100644 updates/create_attachments_table.php create mode 100644 updates/create_instagram_users_table.php create mode 100644 updates/create_pages_table.php create mode 100644 updates/create_posts_table.php create mode 100644 updates/version.yaml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..57872d0 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/vendor/ diff --git a/Plugin.php b/Plugin.php new file mode 100644 index 0000000..b12b649 --- /dev/null +++ b/Plugin.php @@ -0,0 +1,123 @@ + 'social', + 'description' => 'No description provided yet...', + 'author' => 'zoomyboy', + 'icon' => 'icon-leaf' + ]; + } + + /** + * Register method, called when the plugin is first registered. + * + * @return void + */ + public function register() + { + $this->registerConsoleCommand('social-sync', SocialSync::class); + } + + /** + * Boot method, called right before the request route. + * + * @return array + */ + public function boot() + { + + } + + /** + * Registers any front-end components implemented in this plugin. + * + * @return array + */ + public function registerComponents() + { + return [ + 'Zoomyboy\Social\Components\FacebookPageFeed' => 'facebookpagefeed', + ]; + } + + /** + * Registers any back-end permissions used by this plugin. + * + * @return array + */ + public function registerPermissions() + { + return [ + 'zoomyboy.social.settings' => [ + 'tab' => 'social', + 'label' => 'Social Settings' + ], + ]; + } + + /** + * Registers back-end navigation items for this plugin. + * + * @return array + */ + public function registerNavigation() + { + return []; // Remove this line to activate + + return [ + 'social' => [ + 'label' => 'social', + 'url' => Backend::url('zoomyboy/social/mycontroller'), + 'icon' => 'icon-leaf', + 'permissions' => ['zoomyboy.social.*'], + 'order' => 500, + ], + ]; + } + + public function registerSettings() { + return [ + 'settings' => [ + 'label' => 'Social Media', + 'description' => 'Social media Einstellungen', + 'category' => 'Plugins', + 'icon' => 'icon-facebook', + 'class' => Setting::class, + 'order' => 500, + 'keywords' => 'facebook twitter social', + 'permissions' => ['zoomyboy.social.settings'] + ] + ]; + } + + public function registerFormWidgets() { + return [ + FacebookLogin::class => 'zoomyboy_social_facebook_login', + InstagramLogin::class => 'zoomyboy_social_instagram_login', + ]; + } + + public function registerSchedule($schedule) { + $schedule->command('social:sync')->hourly(); + } + +} diff --git a/classes/FacebookSyncService.php b/classes/FacebookSyncService.php new file mode 100644 index 0000000..2eff92a --- /dev/null +++ b/classes/FacebookSyncService.php @@ -0,0 +1,170 @@ +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 posts(): array + { + return $this->get( + "{$this->page->remote_id}/published_posts", + ['fields' => 'id,created_time,full_picture,message,attachments'], + 'data', + ); + } + + 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']) ]); + } + + public function saveAttachments(Post $post, $data) { + foreach($data as $i => $attachment) { + $fid = null; + + switch($attachment['type']) { + case 'photo': + case 'video_inline': + $payload = [ 'href' => $this->saveUrl(data_get($attachment, 'media.image.src')) ]; break; + case 'share': + case 'event': + $fid = $post->id.'-attachment-'.$i; + if (!data_get($attachment, 'media.image.src')) { + continue 2; + } + $payload = [ 'href' => $this->saveUrl(data_get($attachment, 'media.image.src'), $fid.'.jpg') ]; + break; + case 'album': + $payload = [ 'href' => $this->saveUrl(data_get($attachment, 'subattachments.data.0.media.image.src')) ]; break; + case 'multi_share': + $payload = [ 'href' => $this->saveUrl(data_get($attachment, 'subattachments.data.0.media.image.src')) ]; break; + case 'native_templates': continue 2; + default: + throw new \Exception('I dont know how to parse attachment of type '.$attachment['type']); + } + + $fid = $fid ?: data_get($attachment, 'target.id', ''); + $post->attachments()->updateOrCreate(['facebook_id' => $fid], array_merge($payload, [ + 'facebook_id' => $fid, + 'type' => $attachment['type'], + ])); + } + } + + public function sync(): void + { + $this->saveCover(); + + foreach ($this->posts() as $post) { + if (!data_get($post, 'message')) { + continue; + } + + if (!data_get($post, 'attachments')) { + continue; + } + + $payload = [ + 'message' => $post['message'], + 'facebook_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(); + + if ($existing) { + $existing->update($payload); + } else { + $existing = $this->page->posts()->create($payload); + } + + $this->saveAttachments($existing, data_get($post, 'attachments.data')); + } + + if (!is_numeric(Setting::get('max_posts'))) { + return; + } + + while ($this->page->posts()->get()->count() > 0 && $this->page->posts()->get()->count() > Setting::get('max_posts')) { + $this->page->posts()->orderBy('created_at')->first()->delete(); + } + } + + // -------------------------------- 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]), + ]); + + $data = json_decode((string) $response->getBody(), true); + + return $return === null + ? $data + : data_get($data, $return); + } + +} diff --git a/components/FacebookPageFeed.php b/components/FacebookPageFeed.php new file mode 100644 index 0000000..81691a3 --- /dev/null +++ b/components/FacebookPageFeed.php @@ -0,0 +1,51 @@ + 'FacebookFeed', + 'description' => 'No description provided yet...', + ]; + } + + public function onRender(): void + { + $this->posts = Post::where('page_id', $this->property('pageid')) + ->select('*') + ->withIntro()->with('attachments')->latest()->limit(20) + ->get(); + $this->logo = $this->property('logo'); + } + + public function defineProperties(): array + { + return [ + 'pageid' => [ + 'type' => 'dropdown', + 'label' => 'Seite', + ], + 'logo' => [ + 'label' => 'Logo', + ], + ]; + } + + public function getPageidOptions(): array + { + return Page::get()->pluck('slug', 'id')->toArray(); + } + +} diff --git a/components/facebookpagefeed/default.htm b/components/facebookpagefeed/default.htm new file mode 100644 index 0000000..ab18301 --- /dev/null +++ b/components/facebookpagefeed/default.htm @@ -0,0 +1,39 @@ +
+ + + +
+ + {{'chevron' | sprite('w-6 h-6 text-white cursor-pointer fill-current')}} + +
+ +
+ + {{'chevron' | sprite('w-6 h-6 text-white cursor-pointer fill-current rotate-180 transform')}} + +
+ +
+ +
+ diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..85c4a4b --- /dev/null +++ b/composer.json @@ -0,0 +1,12 @@ +{ + "name": "zoomyboy/social", + "require": { + "guzzlehttp/guzzle": "^7.3" + }, + "authors": [ + { + "name": "Philipp Lang", + "email": "philipp.lang@dpsg-koeln.de" + } + ] +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..83b4525 --- /dev/null +++ b/composer.lock @@ -0,0 +1,461 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "26fa4b11f736129b464084d6e21ab516", + "packages": [ + { + "name": "guzzlehttp/guzzle", + "version": "7.3.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "7008573787b430c1c1f650e3722d9bba59967628" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7008573787b430c1c1f650e3722d9bba59967628", + "reference": "7008573787b430c1c1f650e3722d9bba59967628", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.4", + "guzzlehttp/psr7": "^1.7 || ^2.0", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "ext-curl": "*", + "php-http/client-integration-tests": "^3.0", + "phpunit/phpunit": "^8.5.5 || ^9.3.5", + "psr/log": "^1.1" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.3-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.3.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://github.com/alexeyshockov", + "type": "github" + }, + { + "url": "https://github.com/gmponos", + "type": "github" + } + ], + "time": "2021-03-23T11:33:13+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/8e7d04f1f6450fef59366c399cfad4b9383aa30d", + "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/1.4.1" + }, + "time": "2021-03-07T09:25:29+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "1dc8d9cba3897165e16d12bb13d813afb1eb3fe7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/1dc8d9cba3897165e16d12bb13d813afb1eb3fe7", + "reference": "1dc8d9cba3897165e16d12bb13d813afb1eb3fe7", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.8 || ^9.3.10" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.0.0" + }, + "time": "2021-06-30T20:03:07+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client/tree/master" + }, + "time": "2020-06-29T06:28:15+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.0.0" +} diff --git a/console/SocialSync.php b/console/SocialSync.php new file mode 100644 index 0000000..8b23d37 --- /dev/null +++ b/console/SocialSync.php @@ -0,0 +1,61 @@ +option('clear', false)) { + return FacebookSyncService::clearAll(); + } + + if ($this->option('full', false)) { + FacebookSyncService::clearAll(); + FacebookSyncService::syncAll(); + return; + } + + return FacebookSyncService::syncAll(); + } + + /** + * Get the console command arguments. + * @return array + */ + protected function getArguments() + { + return []; + } + + /** + * Get the console command options. + * @return array + */ + protected function getOptions() + { + return [ + ['clear'], + ['full'] + ]; + } +} diff --git a/formwidgets/FacebookLogin.php b/formwidgets/FacebookLogin.php new file mode 100644 index 0000000..c7f10ce --- /dev/null +++ b/formwidgets/FacebookLogin.php @@ -0,0 +1,119 @@ +has('code') && request()->query('state') == Session::get('facebook_auth_state')) { + $this->authenticate(); + $this->storePages(); + } + } + + public function authenticate(): void + { + $client = $this->client(); + $response = $client->get('/v11.0/oauth/access_token', ['query' => [ + 'client_id' => Setting::get('facebook_client_id'), + 'redirect_uri' => $this->redirectUri(), + 'client_secret' => Setting::get('facebook_client_secret'), + 'code' => request()->query('code') + ]]); + $accessToken = json_decode((string) $response->getBody())->access_token; + + $response = $client->get('/v11.0/oauth/access_token', ['query' => [ + 'grant_type' => 'fb_exchange_token', + 'client_id' => Setting::get('facebook_client_id'), + 'client_secret' => Setting::get('facebook_client_secret'), + 'fb_exchange_token' => $accessToken, + ]]); + $accessToken = json_decode((string) $response->getBody())->access_token; + + Setting::set('facebook_access_token', $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], + ); + } + } + + public function render() + { + $this->prepareVars(); + return $this->makePartial('facebooklogin'); + } + + public function redirectUri(): string + { + return env('FACEBOOK_REDIRECT_URI', url()->current()); + } + + /** + * Prepares the form widget view data + */ + public function prepareVars() + { + $state = str_random('20'); + Session::put('facebook_auth_state', $state); + $this->vars['name'] = $this->formField->getName(); + $this->vars['value'] = $this->getLoadValue(); + $this->vars['model'] = $this->model; + $this->vars['client_id'] = Setting::get('facebook_client_id'); + $this->vars['redirect_url'] = $this->redirectUri();; + $this->vars['state'] = $state; + } + + public function loadAssets() + { + $this->addCss('css/facebooklogin.css', 'zoomyboy.social'); + $this->addJs('js/facebooklogin.js', 'zoomyboy.social'); + } + + public function getSaveValue($value) + { + return $value; + } + + private function client(bool $auth = false): Client + { + $query = $auth + ? ['access_token' => Setting::get('facebook_access_token')] + : []; + + return new Client([ + 'base_uri' => 'https://graph.facebook.com', + 'query' => $query, + ]); + } + +} diff --git a/formwidgets/InstagramLogin.php b/formwidgets/InstagramLogin.php new file mode 100644 index 0000000..a4fe2a3 --- /dev/null +++ b/formwidgets/InstagramLogin.php @@ -0,0 +1,139 @@ +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], + ); + } + } + + public function render() + { + $this->prepareVars(); + return $this->makePartial('instagramlogin'); + } + + public function redirectUri(): string + { + return env('INSTAGRAM_REDIRECT_URI', url()->current()); + } + + /** + * Prepares the form widget view data + */ + public function prepareVars() + { + $state = str_random('20'); + Session::put('instagram_auth_state', $state); + $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['state'] = $state; + } + + public function loadAssets() + { + $this->addCss('css/facebooklogin.css', 'zoomyboy.social'); + $this->addJs('js/facebooklogin.js', 'zoomyboy.social'); + } + + public function getSaveValue($value) + { + 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()); + + $this->storeUser($accessToken); + InstagramUser::create([ + 'access_token' => $accessToken, + 'name' => $data->username, + 'id' => $data->id, + ]); + } + +} diff --git a/formwidgets/facebooklogin/assets/css/facebooklogin.css b/formwidgets/facebooklogin/assets/css/facebooklogin.css new file mode 100644 index 0000000..c82045f --- /dev/null +++ b/formwidgets/facebooklogin/assets/css/facebooklogin.css @@ -0,0 +1,5 @@ +/* + * This is a sample StyleSheet file used by FacebookLogin + * + * You can delete this file if you want + */ diff --git a/formwidgets/facebooklogin/assets/js/facebooklogin.js b/formwidgets/facebooklogin/assets/js/facebooklogin.js new file mode 100644 index 0000000..e36a556 --- /dev/null +++ b/formwidgets/facebooklogin/assets/js/facebooklogin.js @@ -0,0 +1,5 @@ +/* + * This is a sample JavaScript file used by FacebookLogin + * + * You can delete this file if you want + */ diff --git a/formwidgets/facebooklogin/partials/_facebooklogin.htm b/formwidgets/facebooklogin/partials/_facebooklogin.htm new file mode 100644 index 0000000..ca837c2 --- /dev/null +++ b/formwidgets/facebooklogin/partials/_facebooklogin.htm @@ -0,0 +1,10 @@ +previewMode): ?> + +
+ +
+ + + +Mit facebook anmelden + diff --git a/formwidgets/instagramlogin/assets/css/facebooklogin.css b/formwidgets/instagramlogin/assets/css/facebooklogin.css new file mode 100644 index 0000000..c82045f --- /dev/null +++ b/formwidgets/instagramlogin/assets/css/facebooklogin.css @@ -0,0 +1,5 @@ +/* + * This is a sample StyleSheet file used by FacebookLogin + * + * You can delete this file if you want + */ diff --git a/formwidgets/instagramlogin/assets/js/facebooklogin.js b/formwidgets/instagramlogin/assets/js/facebooklogin.js new file mode 100644 index 0000000..e36a556 --- /dev/null +++ b/formwidgets/instagramlogin/assets/js/facebooklogin.js @@ -0,0 +1,5 @@ +/* + * This is a sample JavaScript file used by FacebookLogin + * + * You can delete this file if you want + */ diff --git a/formwidgets/instagramlogin/partials/_instagramlogin.htm b/formwidgets/instagramlogin/partials/_instagramlogin.htm new file mode 100644 index 0000000..c1cf065 --- /dev/null +++ b/formwidgets/instagramlogin/partials/_instagramlogin.htm @@ -0,0 +1 @@ +Neuen Account hinzufügen diff --git a/models/Account.php b/models/Account.php new file mode 100644 index 0000000..5a5df87 --- /dev/null +++ b/models/Account.php @@ -0,0 +1,72 @@ + [Post::class, 'post_id'] + ]; + public $belongsToMany = []; + public $morphTo = []; + public $morphOne = []; + public $morphMany = []; + public $attachOne = []; + public $attachMany = []; + + public function beforeDelete() + { + MediaLibrary::instance()->deleteFiles([$this->href]); + Event::fire('media.file.delete', [null, $this->href, null]); + } +} diff --git a/models/InstagramUser.php b/models/InstagramUser.php new file mode 100644 index 0000000..d6a9e81 --- /dev/null +++ b/models/InstagramUser.php @@ -0,0 +1,75 @@ + '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 new file mode 100644 index 0000000..bfdd0b5 --- /dev/null +++ b/models/Page.php @@ -0,0 +1,91 @@ + 'name']; + + public static array $pageTypes = [ + 'facebook' => 'Facebook', + 'instagram' => 'Instagram', + ]; + + /** + * @var array Guarded fields + */ + protected $guarded = ['*']; + + /** + * @var array Fillable fields + */ + protected $fillable = ['name', 'type', 'cover', 'slug', 'access_token', 'remote_id']; + + /** + * @var array Validation rules for attributes + */ + public $rules = []; + + /** + * @var array Attributes to be cast to native types + */ + protected $casts = []; + + /** + * @var array Attributes to be cast to JSON + */ + protected $jsonable = []; + + /** + * @var array Attributes to be appended to the API representation of the model (ex. toArray()) + */ + protected $appends = []; + + /** + * @var array Attributes to be removed from the API representation of the model (ex. toArray()) + */ + protected $hidden = []; + + /** + * @var array Attributes to be cast to Argon (Carbon) instances + */ + protected $dates = [ + 'created_at', + 'updated_at' + ]; + + public $hasMany = [ + 'posts' => [ Post::class, 'page_id' ] + ]; + + public function getMediaPathAttribute() { + return 'social/facebook/'.$this->slug.'/'; + } + + public function beforeDelete() { + $this->posts->each->delete(); + + MediaLibrary::instance()->deleteFolder($this->mediaPath); + } + + public static function select($type = null): array + { + if (is_null($type)) { + return static::get()->pluck('name', 'id')->toArray(); + } + + return static::whereType($type)->get()->pluck('name', 'id')->toArray(); + } +} diff --git a/models/Post.php b/models/Post.php new file mode 100644 index 0000000..c17f1c3 --- /dev/null +++ b/models/Post.php @@ -0,0 +1,95 @@ + [ Attachment::class, 'post_id' ] + ]; + public $belongsTo = [ + 'page' => [ Page::class, 'page_id' ] + ]; + public $belongsToMany = []; + public $morphTo = []; + public $morphOne = []; + public $morphMany = []; + public $attachOne = []; + public $attachMany = []; + + public function beforeDelete() { + $this->attachments->each->delete(); + } + + // ---------------------------------- Getters ---------------------------------- + // ***************************************************************************** + public function getFeaturedImageAttribute(): ?string + { + return optional($this->attachments->first())->href; + } + + // ---------------------------------- Scopes ----------------------------------- + // ***************************************************************************** + public function scopeWithIntro(Builder $q): Builder + { + return $q->selectSub('SELECT SUBSTRING(message, 1, 200)', 'intro'); + } +} diff --git a/models/Setting.php b/models/Setting.php new file mode 100644 index 0000000..271ce37 --- /dev/null +++ b/models/Setting.php @@ -0,0 +1,31 @@ +pluck('name', 'id')->toArray(); + } + + public static function synchedPages(): array + { + return (new static([]))->getSynchedPagesOptions(); + } + + public function getInstagramUsersOptions(): array + { + return InstagramUser::get()->pluck('name', 'id')->toArray(); + } + +} diff --git a/models/account/columns.yaml b/models/account/columns.yaml new file mode 100644 index 0000000..b11160b --- /dev/null +++ b/models/account/columns.yaml @@ -0,0 +1,8 @@ +# =================================== +# List Column Definitions +# =================================== + +columns: + id: + label: ID + searchable: true diff --git a/models/account/fields.yaml b/models/account/fields.yaml new file mode 100644 index 0000000..c611f31 --- /dev/null +++ b/models/account/fields.yaml @@ -0,0 +1,8 @@ +# =================================== +# Form Field Definitions +# =================================== + +fields: + id: + label: ID + disabled: true diff --git a/models/instagramuser/columns.yaml b/models/instagramuser/columns.yaml new file mode 100644 index 0000000..b11160b --- /dev/null +++ b/models/instagramuser/columns.yaml @@ -0,0 +1,8 @@ +# =================================== +# List Column Definitions +# =================================== + +columns: + id: + label: ID + searchable: true diff --git a/models/instagramuser/fields.yaml b/models/instagramuser/fields.yaml new file mode 100644 index 0000000..c611f31 --- /dev/null +++ b/models/instagramuser/fields.yaml @@ -0,0 +1,8 @@ +# =================================== +# Form Field Definitions +# =================================== + +fields: + id: + label: ID + disabled: true diff --git a/models/setting/fields.yaml b/models/setting/fields.yaml new file mode 100644 index 0000000..f1be57a --- /dev/null +++ b/models/setting/fields.yaml @@ -0,0 +1,42 @@ +# =================================== +# Form Field Definitions +# =================================== + +tabs: + fields: + max_posts: + tab: Allgemein + label: Maximale Posts + + facebook_client_id: + tab: Facebook + label: Facebook Client ID + + facebook_client_secret: + tab: Facebook + label: Facebook Client secret + + facebook_auth: + type: zoomyboy_social_facebook_login + tab: Facebook + + instagram_client_id: + tab: Instagram + label: Instagram Client ID + + instagram_client_secret: + tab: Instagram + label: Instagram Client secret + + instagram_users: + tab: Instagram + type: checkboxlist + + instagram_auth: + tab: Instagram + type: zoomyboy_social_instagram_login + + synched_pages: + type: checkboxlist + tab: Facebook + label: zu synchronisierende Seiten diff --git a/updates/.version.yaml.swp b/updates/.version.yaml.swp new file mode 100644 index 0000000000000000000000000000000000000000..4a8db475db1a68cfe8f64038131b5c78d1c16d45 GIT binary patch literal 12288 zcmeI&F;2rU6b9heE=&~&E?{7ele7#~BvvE_#xgKaC5;;*(%6a}q%go8y0LNyHZH=( zG1zcQP^Hj_4XNn2^kmuj~5#& zd#%11qJsbgAOHafKmY;|fB*y_009U2N^wfk=Cb+s$increments('id'); + $table->string('client_id')->nullable(); + $table->string('client_secret')->nullable(); + $table->string('access_token')->nullable(); + $table->string('page')->nullable(); + $table->timestamps(); + }); + } + + public function down() + { + Schema::dropIfExists('zoomyboy_social_accounts'); + } +} diff --git a/updates/create_attachments_table.php b/updates/create_attachments_table.php new file mode 100644 index 0000000..c9d0eac --- /dev/null +++ b/updates/create_attachments_table.php @@ -0,0 +1,26 @@ +engine = 'InnoDB'; + $table->increments('id'); + $table->string('href'); + $table->string('type'); + $table->integer('post_id'); + $table->string('facebook_id'); + $table->timestamps(); + }); + } + + public function down() + { + Schema::dropIfExists('zoomyboy_social_attachments'); + } +} diff --git a/updates/create_instagram_users_table.php b/updates/create_instagram_users_table.php new file mode 100644 index 0000000..1f376cb --- /dev/null +++ b/updates/create_instagram_users_table.php @@ -0,0 +1,28 @@ +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_pages_table.php b/updates/create_pages_table.php new file mode 100644 index 0000000..407f591 --- /dev/null +++ b/updates/create_pages_table.php @@ -0,0 +1,28 @@ +engine = 'InnoDB'; + $table->increments('id'); + $table->string('name'); + $table->string('slug'); + $table->string('access_token'); + $table->string('remote_id'); + $table->string('type'); + $table->string('cover')->nullable(); + $table->timestamps(); + }); + } + + public function down() + { + Schema::dropIfExists('zoomyboy_social_pages'); + } +} diff --git a/updates/create_posts_table.php b/updates/create_posts_table.php new file mode 100644 index 0000000..1b133fc --- /dev/null +++ b/updates/create_posts_table.php @@ -0,0 +1,26 @@ +engine = 'InnoDB'; + $table->increments('id'); + $table->text('message')->nullable(); + $table->string('facebook_id'); + $table->integer('page_id'); + $table->string('href', 500)->nullable(); + $table->timestamps(); + }); + } + + public function down() + { + Schema::dropIfExists('zoomyboy_social_posts'); + } +} diff --git a/updates/version.yaml b/updates/version.yaml new file mode 100644 index 0000000..8bf36a5 --- /dev/null +++ b/updates/version.yaml @@ -0,0 +1,6 @@ +1.0.1: + - create_posts_table.php + - create_pages_table.php + - create_attachments_table.php + - create_accounts_table.php + - create_instagram_users_table.php