Initial Commit

This commit is contained in:
philipp lang 2021-10-28 22:35:15 +02:00
commit d148911af3
34 changed files with 1869 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/vendor/

123
Plugin.php Normal file
View File

@ -0,0 +1,123 @@
<?php namespace Zoomyboy\Social;
use Backend;
use System\Classes\PluginBase;
use Zoomyboy\Social\Console\SocialSync;
use Zoomyboy\Social\FormWidgets\FacebookLogin;
use Zoomyboy\Social\FormWidgets\InstagramLogin;
use Zoomyboy\Social\Models\Setting;
/**
* social Plugin Information File
*/
class Plugin extends PluginBase
{
/**
* Returns information about this plugin.
*
* @return array
*/
public function pluginDetails()
{
return [
'name' => '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();
}
}

View File

@ -0,0 +1,170 @@
<?php
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 {
private static $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);
}
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 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);
}
}

View File

@ -0,0 +1,51 @@
<?php
namespace Zoomyboy\Social\Components;
use Cms\Classes\ComponentBase;
use Illuminate\Support\Collection;
use Zoomyboy\Social\Models\Page;
use Zoomyboy\Social\Models\Post;
class FacebookPageFeed extends ComponentBase
{
public Collection $posts;
public string $logo;
public function componentDetails(): array
{
return [
'name' => '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();
}
}

View File

@ -0,0 +1,39 @@
<div class="relative -mx-6">
<div class="flex pb-16 pt-8 slides-1 sm_slides-2 lg_slides-3" data-glider-prev="#glider-prev" data-glider-next="#glider-next" data-glider-dots="#glider-dots" data-glider-autostart data-glider-interval="7000" data-glider-lazyload data-glider>
{% for post in __SELF__.posts %}
<div class="px-3">
<a href="{{post.href}}" class="flex flex-col h-full box shadow-lg hover:shadow-2xl rounded-lg overflow-hidden" target="_BLANK">
{% if post.featuredImage %} <img {{ post.featuredImage | resize('box', 'calc(100vw - 1.5rem)|640:calc(50vw - 1.5rem)|1024:calc(33vw - 1.5rem)|1152:360px', {'lazy': true}) }} class="object-cover w-full h-48"> {% endif %}
<div class="flex items-end justify-between px-6 py-3 bg-gray-100">
{% if __SELF__.logo %} <img data-src="{{__SELF__.logo|media}}" class="w-8"> {% endif %}
<div class="text-xs text-gray-500">{{post.created_at | human}}</div>
</div>
<div class="flex-grow p-6 pt-0 text-sm text-gray-600 bg-gray-100">
{{post.intro}} …
</div>
<div class="flex px-6 py-3 bg-gray-100 items-center border border-t">
{{'facebook-square'|sprite('w-6 h-6 mr-3 fill-current text-primary-500')}}
<span class="text-sm text-gray-600">Beitrag anschauen</span>
</div>
</a>
</div>
{% endfor %}
</div>
<div class="absolute top-0 left-0 flex items-center h-full ml-3">
<span id="glider-prev" class="flex items-center justify-center w-10 h-10 bg-black-t400">
{{'chevron' | sprite('w-6 h-6 text-white cursor-pointer fill-current')}}
</span>
</div>
<div class="absolute top-0 right-0 flex items-center h-full mr-3">
<span id="glider-next" class="flex items-center justify-center w-10 h-10 bg-black-t400">
{{'chevron' | sprite('w-6 h-6 text-white cursor-pointer fill-current rotate-180 transform')}}
</span>
</div>
</div>
<div class="-mt-10 relative" id="glider-dots"></div>

12
composer.json Normal file
View File

@ -0,0 +1,12 @@
{
"name": "zoomyboy/social",
"require": {
"guzzlehttp/guzzle": "^7.3"
},
"authors": [
{
"name": "Philipp Lang",
"email": "philipp.lang@dpsg-koeln.de"
}
]
}

461
composer.lock generated Normal file
View File

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

61
console/SocialSync.php Normal file
View File

@ -0,0 +1,61 @@
<?php namespace Zoomyboy\Social\Console;
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\Models\Setting;
class SocialSync extends Command
{
/**
* @var string The console command name.
*/
protected $name = 'social:sync';
/**
* @var string The console command description.
*/
protected $description = 'Synchs social posts';
/**
* Execute the console command.
* @return void
*/
public function handle()
{
if ($this->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']
];
}
}

View File

@ -0,0 +1,119 @@
<?php namespace Zoomyboy\Social\FormWidgets;
use \Session;
use Backend\Classes\FormWidgetBase;
use GuzzleHttp\Client;
use Zoomyboy\Social\Models\Page;
use Zoomyboy\Social\Models\Setting;
/**
* FacebookLogin Form Widget
*/
class FacebookLogin extends FormWidgetBase
{
/**
* @inheritDoc
*/
protected $defaultAlias = 'zoomyboy_social_facebook_login';
/**
* @inheritDoc
*/
public function init()
{
if (request()->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,
]);
}
}

View File

@ -0,0 +1,139 @@
<?php namespace Zoomyboy\Social\FormWidgets;
use \Session;
use Backend\Classes\FormWidgetBase;
use GuzzleHttp\Client;
use Zoomyboy\Social\Models\InstagramUser;
use Zoomyboy\Social\Models\Page;
use Zoomyboy\Social\Models\Setting;
/**
* InstagramLogin Form Widget
*/
class InstagramLogin extends FormWidgetBase
{
/**
* @inheritDoc
*/
protected $defaultAlias = 'zoomyboy_social_instagram_login';
/**
* @inheritDoc
*/
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],
);
}
}
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,
]);
}
}

View File

@ -0,0 +1,5 @@
/*
* This is a sample StyleSheet file used by FacebookLogin
*
* You can delete this file if you want
*/

View File

@ -0,0 +1,5 @@
/*
* This is a sample JavaScript file used by FacebookLogin
*
* You can delete this file if you want
*/

View File

@ -0,0 +1,10 @@
<?php if ($this->previewMode): ?>
<div class="form-control">
<?= $value ?>
</div>
<?php else: ?>
<a href="https://www.facebook.com/v11.0/dialog/oauth?client_id=<?php echo $client_id; ?>&redirect_uri=<?php echo $redirect_url; ?>&state=<?php echo $state; ?>&response_type=code&scope=pages_show_list pages_read_engagement">Mit facebook anmelden</a>
<?php endif ?>

View File

@ -0,0 +1,5 @@
/*
* This is a sample StyleSheet file used by FacebookLogin
*
* You can delete this file if you want
*/

View File

@ -0,0 +1,5 @@
/*
* This is a sample JavaScript file used by FacebookLogin
*
* You can delete this file if you want
*/

View File

@ -0,0 +1 @@
<a href="https://api.instagram.com/oauth/authorize?client_id=<?php echo $client_id; ?>&redirect_uri=<?php echo rawurlencode($redirect_url); ?>&scope=user_media,user_profile&response_type=code&state=<?php echo $state; ?>">Neuen Account hinzufügen</a>

72
models/Account.php Normal file
View File

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

82
models/Attachment.php Normal file
View File

@ -0,0 +1,82 @@
<?php namespace Zoomyboy\Social\Models;
use Model;
use Media\Classes\MediaLibrary;
use Event;
/**
* Attachment Model
*/
class Attachment extends Model
{
use \October\Rain\Database\Traits\Validation;
/**
* @var string The database table used by the model.
*/
public $table = 'zoomyboy_social_attachments';
/**
* @var array Guarded fields
*/
protected $guarded = ['*'];
/**
* @var array Fillable fields
*/
protected $fillable = [ 'facebook_id', 'post_id', 'href', 'type' ];
/**
* @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'
];
/**
* @var array Relations
*/
public $hasOne = [];
public $hasMany = [];
public $belongsTo = [
'post' => [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]);
}
}

75
models/InstagramUser.php Normal file
View File

@ -0,0 +1,75 @@
<?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 = [];
}

91
models/Page.php Normal file
View File

@ -0,0 +1,91 @@
<?php namespace Zoomyboy\Social\Models;
use Model;
use System\Classes\MediaLibrary;
/**
* Page Model
*/
class Page extends Model
{
use \October\Rain\Database\Traits\Validation;
use \October\Rain\Database\Traits\Sluggable;
/**
* @var string The database table used by the model.
*/
public $table = 'zoomyboy_social_pages';
public $slugs = ['slug' => '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();
}
}

95
models/Post.php Normal file
View File

@ -0,0 +1,95 @@
<?php namespace Zoomyboy\Social\Models;
use Illuminate\Database\Eloquent\Builder;
use Model;
/**
* Post Model
*/
class Post extends Model
{
use \October\Rain\Database\Traits\Validation;
/**
* @var string The database table used by the model.
*/
public $table = 'zoomyboy_social_posts';
/**
* @var array Guarded fields
*/
protected $guarded = ['*'];
/**
* @var array Fillable fields
*/
protected $fillable = ['message', 'href', 'facebook_id', 'created_at', 'updated_at'];
/**
* @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'
];
/**
* @var array Relations
*/
public $hasOne = [];
public $hasMany = [
'attachments' => [ 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');
}
}

31
models/Setting.php Normal file
View File

@ -0,0 +1,31 @@
<?php namespace Zoomyboy\Social\Models;
use Model;
/**
* Setting Model
*/
class Setting extends Model
{
public $implement = ['System.Behaviors.SettingsModel'];
public $settingsCode = 'zoomyboy_social';
public $settingsFields = 'fields.yaml';
public function getSynchedPagesOptions(): array
{
return Page::get()->pluck('name', 'id')->toArray();
}
public static function synchedPages(): array
{
return (new static([]))->getSynchedPagesOptions();
}
public function getInstagramUsersOptions(): array
{
return InstagramUser::get()->pluck('name', 'id')->toArray();
}
}

View File

@ -0,0 +1,8 @@
# ===================================
# List Column Definitions
# ===================================
columns:
id:
label: ID
searchable: true

View File

@ -0,0 +1,8 @@
# ===================================
# Form Field Definitions
# ===================================
fields:
id:
label: ID
disabled: true

View File

@ -0,0 +1,8 @@
# ===================================
# List Column Definitions
# ===================================
columns:
id:
label: ID
searchable: true

View File

@ -0,0 +1,8 @@
# ===================================
# Form Field Definitions
# ===================================
fields:
id:
label: ID
disabled: true

View File

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

BIN
updates/.version.yaml.swp Normal file

Binary file not shown.

View File

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

View File

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

View File

@ -0,0 +1,28 @@
<?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

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

View File

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

6
updates/version.yaml Normal file
View File

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