Begin work on custom code plugins support.

This commit is contained in:
Buster "Silver Eagle" Neece 2018-09-21 07:04:05 -05:00
parent dece3e9e5a
commit 33210b9450
30 changed files with 820 additions and 253 deletions

5
.gitignore vendored
View File

@ -32,3 +32,8 @@ web/static/yarn-error\.log
# Docker files # Docker files
/docker-compose.yml /docker-compose.yml
/*.tar.gz /*.tar.gz
# Plugins
/plugins/*
/plugins/**/*
!/plugins/.gitkeep

View File

@ -58,6 +58,10 @@ ini_set('session.use_strict_mode', 1);
$autoloader = require(APP_INCLUDE_VENDOR . '/autoload.php'); $autoloader = require(APP_INCLUDE_VENDOR . '/autoload.php');
$autoloader->setPsr4('Proxy\\', APP_INCLUDE_TEMP . '/proxies'); $autoloader->setPsr4('Proxy\\', APP_INCLUDE_TEMP . '/proxies');
// Initialize plugins
$plugins = new \App\Plugins(APP_INCLUDE_ROOT.'/plugins');
$plugins->registerAutoloaders($autoloader);
// Set up DI container. // Set up DI container.
$di = new \Slim\Container([ $di = new \Slim\Container([
'settings' => [ 'settings' => [
@ -69,8 +73,12 @@ $di = new \Slim\Container([
] ]
]); ]);
$di[\App\Plugins::class] = $plugins;
// Define services. // Define services.
$settings = require(dirname(__DIR__).'/config/settings.php'); $settings = require(dirname(__DIR__).'/config/settings.php');
call_user_func(include(dirname(__DIR__).'/config/services.php'), $di, $settings); call_user_func(include(dirname(__DIR__).'/config/services.php'), $di, $settings);
$plugins->registerServices($di, $settings);
return $di; return $di;

View File

@ -41,6 +41,7 @@
"php-http/guzzle6-adapter": "^1.1", "php-http/guzzle6-adapter": "^1.1",
"slim/slim": "^3.0", "slim/slim": "^3.0",
"supervisorphp/supervisor": "^3.0", "supervisorphp/supervisor": "^3.0",
"symfony/event-dispatcher": "^4.1",
"symfony/finder": "^4.1", "symfony/finder": "^4.1",
"zendframework/zend-config": "^3.1.0", "zendframework/zend-config": "^3.1.0",
"zendframework/zend-paginator": "^2.7" "zendframework/zend-paginator": "^2.7"

128
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "594ac3f2423a143d5621fb2cffbd11fa", "content-hash": "1d77d7c553cb7dd8c3f52c09642fcf36",
"packages": [ "packages": [
{ {
"name": "azuracast/azuraforms", "name": "azuracast/azuraforms",
@ -3419,6 +3419,69 @@
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2018-07-26T11:24:31+00:00" "time": "2018-07-26T11:24:31+00:00"
}, },
{
"name": "symfony/event-dispatcher",
"version": "v4.1.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "bfb30c2ad377615a463ebbc875eba64a99f6aa3e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/bfb30c2ad377615a463ebbc875eba64a99f6aa3e",
"reference": "bfb30c2ad377615a463ebbc875eba64a99f6aa3e",
"shasum": ""
},
"require": {
"php": "^7.1.3"
},
"conflict": {
"symfony/dependency-injection": "<3.4"
},
"require-dev": {
"psr/log": "~1.0",
"symfony/config": "~3.4|~4.0",
"symfony/dependency-injection": "~3.4|~4.0",
"symfony/expression-language": "~3.4|~4.0",
"symfony/stopwatch": "~3.4|~4.0"
},
"suggest": {
"symfony/dependency-injection": "",
"symfony/http-kernel": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.1-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\EventDispatcher\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
"time": "2018-07-26T09:10:45+00:00"
},
{ {
"name": "symfony/finder", "name": "symfony/finder",
"version": "v4.1.4", "version": "v4.1.4",
@ -5956,69 +6019,6 @@
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2018-07-26T11:00:49+00:00" "time": "2018-07-26T11:00:49+00:00"
}, },
{
"name": "symfony/event-dispatcher",
"version": "v4.1.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "bfb30c2ad377615a463ebbc875eba64a99f6aa3e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/bfb30c2ad377615a463ebbc875eba64a99f6aa3e",
"reference": "bfb30c2ad377615a463ebbc875eba64a99f6aa3e",
"shasum": ""
},
"require": {
"php": "^7.1.3"
},
"conflict": {
"symfony/dependency-injection": "<3.4"
},
"require-dev": {
"psr/log": "~1.0",
"symfony/config": "~3.4|~4.0",
"symfony/dependency-injection": "~3.4|~4.0",
"symfony/expression-language": "~3.4|~4.0",
"symfony/stopwatch": "~3.4|~4.0"
},
"suggest": {
"symfony/dependency-injection": "",
"symfony/http-kernel": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.1-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\EventDispatcher\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
"time": "2018-07-26T09:10:45+00:00"
},
{ {
"name": "symfony/polyfill-ctype", "name": "symfony/polyfill-ctype",
"version": "v1.9.0", "version": "v1.9.0",

14
config/events.php Normal file
View File

@ -0,0 +1,14 @@
<?php
return function (\Symfony\Component\EventDispatcher\EventDispatcher $dispatcher, \Slim\Container $di, $settings) {
$dispatcher->addSubscriber(new \App\EventHandler\DefaultRoutes(__DIR__.'/routes.php'));
$dispatcher->addSubscriber(new \App\EventHandler\DefaultView($di));
$dispatcher->addSubscriber(new \App\EventHandler\DefaultNowPlaying());
$dispatcher->addListener(\App\Event\SendWebhooks::NAME, function(\App\Event\SendWebhooks $event) use ($di) {
/** @var \App\Webhook\Dispatcher $webhook_dispatcher */
$webhook_dispatcher = $di[\App\Webhook\Dispatcher::class];
$webhook_dispatcher->dispatch($event);
});
};

View File

@ -233,46 +233,9 @@ return function (\Slim\Container $di, $settings) {
$view = new App\View(dirname(__DIR__) . '/resources/templates'); $view = new App\View(dirname(__DIR__) . '/resources/templates');
$view->setFileExtension('phtml'); $view->setFileExtension('phtml');
$view->registerFunction('service', function($service) use ($di) { /** @var \Symfony\Component\EventDispatcher\EventDispatcher $dispatcher */
return $di->get($service); $dispatcher = $di[\Symfony\Component\EventDispatcher\EventDispatcher::class];
}); $dispatcher->dispatch(\App\Event\BuildView::NAME, new \App\Event\BuildView($view));
$view->registerFunction('escapeJs', function($string) {
return json_encode($string);
});
$view->registerFunction('mailto', function ($address, $link_text = null) {
$address = substr(chunk_split(bin2hex(" $address"), 2, ";&#x"), 3, -3);
$link_text = $link_text ?? $address;
return '<a href="mailto:' . $address . '">' . $link_text . '</a>';
});
$view->registerFunction('pluralize', function ($word, $num = 0) {
if ((int)$num === 1) {
return $word;
} else {
return \Doctrine\Common\Inflector\Inflector::pluralize($word);
}
});
$view->registerFunction('truncate', function ($text, $length = 80) {
return \App\Utilities::truncate_text($text, $length);
});
/** @var \App\Session $session */
$session = $di[\App\Session::class];
$view->addData([
'app_settings' => $di['app_settings'],
'router' => $di['router'],
'request' => $di['request'],
'assets' => $di[\App\Assets::class],
'auth' => $di[\App\Auth::class],
'acl' => $di[\App\Acl::class],
'flash' => $session->getFlash(),
'customization' => $di[\App\Customization::class],
]);
return $view; return $view;
}); });
@ -337,6 +300,21 @@ return function (\Slim\Container $di, $settings) {
]); ]);
}; };
$di[\Symfony\Component\EventDispatcher\EventDispatcher::class] = function($di) use ($settings) {
$dispatcher = new \Symfony\Component\EventDispatcher\EventDispatcher();
// Register application default events.
call_user_func(include(__DIR__.'/events.php'), $dispatcher, $di, $settings);
/** @var \App\Plugins $plugins */
$plugins = $di[\App\Plugins::class];
// Register plugin-provided events.
$plugins->registerEvents($dispatcher, $di, $settings);
return $dispatcher;
};
// //
// AzuraCast-specific dependencies // AzuraCast-specific dependencies
// //
@ -401,23 +379,9 @@ return function (\Slim\Container $di, $settings) {
$app = new \Slim\App($di); $app = new \Slim\App($di);
// Get the current user entity object and assign it into the request if it exists. /** @var \Symfony\Component\EventDispatcher\EventDispatcher $dispatcher */
$app->add(\App\Middleware\GetCurrentUser::class); $dispatcher = $di[\Symfony\Component\EventDispatcher\EventDispatcher::class];
$dispatcher->dispatch(\App\Event\BuildRoutes::NAME, new \App\Event\BuildRoutes($app));
// Inject the application router into the request object.
$app->add(\App\Middleware\EnableRouter::class);
// Inject the session manager into the request object.
$app->add(\App\Middleware\EnableSession::class);
// Check HTTPS setting and enforce Content Security Policy accordingly.
$app->add(\App\Middleware\EnforceSecurity::class);
// Remove trailing slash from all URLs when routing.
$app->add(\App\Middleware\RemoveSlashes::class);
// Load routes
call_user_func(include(__DIR__.'/routes.php'), $app);
return $app; return $app;
}; };

0
plugins/.gitkeep Normal file
View File

22
src/Event/BuildRoutes.php Normal file
View File

@ -0,0 +1,22 @@
<?php
namespace App\Event;
use Slim\App;
use Symfony\Component\EventDispatcher\Event;
class BuildRoutes extends Event
{
const NAME = 'build-routes';
protected $app;
public function __construct(App $app)
{
$this->app = $app;
}
public function getApp(): App
{
return $this->app;
}
}

22
src/Event/BuildView.php Normal file
View File

@ -0,0 +1,22 @@
<?php
namespace App\Event;
use App\View;
use Symfony\Component\EventDispatcher\Event;
class BuildView extends Event
{
const NAME = 'build-view';
protected $view;
public function __construct(View $view)
{
$this->view = $view;
}
public function getView(): View
{
return $this->view;
}
}

View File

@ -0,0 +1,82 @@
<?php
namespace App\Event;
use App\Entity\Station;
use App\Radio\Frontend\FrontendAbstract;
use App\Radio\Remote\RemoteAbstract;
use Symfony\Component\EventDispatcher\Event;
class GenerateRawNowPlaying extends Event
{
const NAME = 'nowplaying-generate-raw';
/** @var Station */
protected $station;
/** @var FrontendAbstract */
protected $frontend;
/** @var RemoteAbstract[] */
protected $remotes;
/** @var bool */
protected $include_clients = false;
/** @var string|null The preloaded "payload" to supply to the nowplaying adapters, if one is available. */
protected $payload;
/** @var array The composed "raw" NowPlaying data. */
protected $np_raw = [];
public function __construct(
Station $station,
FrontendAbstract $frontend,
array $remotes,
$payload = null,
$include_clients = false
) {
$this->station = $station;
$this->frontend = $frontend;
$this->remotes = $remotes;
$this->payload = $payload;
$this->include_clients = $include_clients;
}
public function getStation(): Station
{
return $this->station;
}
public function getFrontend(): FrontendAbstract
{
return $this->frontend;
}
/**
* @return RemoteAbstract[]
*/
public function getRemotes(): array
{
return $this->remotes;
}
public function includeClients(): bool
{
return $this->include_clients;
}
public function getPayload(): ?string
{
return $this->payload;
}
public function getRawResponse(): array
{
return $this->np_raw;
}
public function setRawResponse(array $np): void
{
$this->np_raw = $np;
}
}

View File

@ -0,0 +1,93 @@
<?php
namespace App\Event;
use App\Entity\Api\NowPlaying;
use App\Entity\Station;
use Symfony\Component\EventDispatcher\Event;
class SendWebhooks extends Event
{
const NAME = 'webhooks-send';
/** @var Station */
protected $station;
/** @var NowPlaying */
protected $np;
/** @var array */
protected $triggers = [];
/** @var bool */
protected $is_standalone = true;
public function __construct(Station $station, NowPlaying $np, $np_old = null, $is_standalone = true)
{
$this->station = $station;
$this->np = $np;
$this->is_standalone = $is_standalone;
$to_trigger = ['all'];
if ($np_old instanceof NowPlaying) {
if ($np_old->now_playing->song->id !== $np->now_playing->song->id) {
$to_trigger[] = 'song_changed';
}
if ($np_old->listeners->current > $np->listeners->current) {
$to_trigger[] = 'listener_lost';
} elseif ($np_old->listeners->current < $np->listeners->current) {
$to_trigger[] = 'listener_gained';
}
if ($np_old->live->is_live === false && $np->live->is_live === true) {
$to_trigger[] = 'live_connect';
} elseif ($np_old->live->is_live === true && $np->live->is_live === false) {
$to_trigger[] = 'live_disconnect';
}
}
$this->triggers = $to_trigger;
}
/**
* @return Station
*/
public function getStation(): Station
{
return $this->station;
}
/**
* @return NowPlaying
*/
public function getNowPlaying(): NowPlaying
{
return $this->np;
}
/**
* @return array
*/
public function getTriggers(): array
{
return $this->triggers;
}
/**
* @param $trigger_name
* @return bool
*/
public function hasTrigger($trigger_name): bool
{
return in_array($trigger_name, $this->triggers);
}
/**
* @return bool
*/
public function isStandalone(): bool
{
return $this->is_standalone;
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace App\EventHandler;
use App\Event\GenerateRawNowPlaying;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class DefaultNowPlaying implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
if (APP_TESTING_MODE) {
return [];
}
return [
GenerateRawNowPlaying::NAME => [
['loadRawFromFrontend', 10],
['addToRawFromRemotes', 0],
['cleanUpRawOutput', -10],
],
];
}
public function loadRawFromFrontend(GenerateRawNowPlaying $event)
{
$np_raw = $event->getFrontend()->getNowPlaying($event->getPayload(), $event->includeClients());
$event->setRawResponse($np_raw);
}
public function addToRawFromRemotes(GenerateRawNowPlaying $event)
{
$np_raw = $event->getRawResponse();
// Loop through all remotes and update NP data accordingly.
foreach($event->getRemotes() as $remote_adapter) {
$remote_adapter->updateNowPlaying($np_raw, $event->includeClients());
}
$event->setRawResponse($np_raw);
}
public function cleanUpRawOutput(GenerateRawNowPlaying $event)
{
$np_raw = $event->getRawResponse();
array_walk($np_raw['current_song'], function(&$value) {
$value = htmlspecialchars_decode($value);
$value = trim($value);
});
$event->setRawResponse($np_raw);
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace App\EventHandler;
use App\Event\BuildRoutes;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class DefaultRoutes implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
BuildRoutes::NAME => [
['addDefaultMiddleware', 1],
['addDefaultRoutes', 0],
],
];
}
protected $routes_path;
public function __construct($routes_path)
{
$this->routes_path = $routes_path;
}
public function addDefaultMiddleware(BuildRoutes $event)
{
$app = $event->getApp();
// Get the current user entity object and assign it into the request if it exists.
$app->add(\App\Middleware\GetCurrentUser::class);
// Inject the application router into the request object.
$app->add(\App\Middleware\EnableRouter::class);
// Inject the session manager into the request object.
$app->add(\App\Middleware\EnableSession::class);
// Check HTTPS setting and enforce Content Security Policy accordingly.
$app->add(\App\Middleware\EnforceSecurity::class);
// Remove trailing slash from all URLs when routing.
$app->add(\App\Middleware\RemoveSlashes::class);
}
public function addDefaultRoutes(BuildRoutes $event)
{
call_user_func(include($this->routes_path), $event->getApp());
}
}

View File

@ -0,0 +1,76 @@
<?php
namespace App\EventHandler;
use App\Event\BuildView;
use Slim\Container;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class DefaultView implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
BuildView::NAME => [
['addViewFunctions', 1],
['addViewData', 0],
],
];
}
/** @var Container */
protected $di;
public function __construct(Container $di)
{
$this->di = $di;
}
public function addViewFunctions(BuildView $event)
{
$view = $event->getView();
$view->registerFunction('service', function($service) {
return $this->di->get($service);
});
$view->registerFunction('escapeJs', function($string) {
return json_encode($string);
});
$view->registerFunction('mailto', function ($address, $link_text = null) {
$address = substr(chunk_split(bin2hex(" $address"), 2, ";&#x"), 3, -3);
$link_text = $link_text ?? $address;
return '<a href="mailto:' . $address . '">' . $link_text . '</a>';
});
$view->registerFunction('pluralize', function ($word, $num = 0) {
if ((int)$num === 1) {
return $word;
} else {
return \Doctrine\Common\Inflector\Inflector::pluralize($word);
}
});
$view->registerFunction('truncate', function ($text, $length = 80) {
return \App\Utilities::truncate_text($text, $length);
});
}
public function addViewData(BuildView $event)
{
/** @var \App\Session $session */
$session = $this->di[\App\Session::class];
$event->getView()->addData([
'app_settings' => $this->di['app_settings'],
'router' => $this->di['router'],
'request' => $this->di['request'],
'assets' => $this->di[\App\Assets::class],
'auth' => $this->di[\App\Auth::class],
'acl' => $this->di[\App\Acl::class],
'flash' => $session->getFlash(),
'customization' => $this->di[\App\Customization::class],
]);
}
}

View File

@ -0,0 +1,137 @@
<?php
namespace App\EventHandler;
use App\Entity;
use App\Event\SendWebhooks;
use App\Exception;
use App\Webhook\Connector;
use Monolog\Handler\TestHandler;
use Monolog\Logger;
use Pimple\Psr11\ServiceLocator;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class DefaultWebhooks implements EventSubscriberInterface
{
/** @var Logger */
protected $logger;
/** @var ServiceLocator */
protected $connectors;
public function __construct(Logger $logger, ServiceLocator $connectors)
{
$this->logger = $logger;
$this->connectors = $connectors;
}
public static function getSubscribedEvents()
{
if (APP_TESTING_MODE) {
return [];
}
return [
SendWebhooks::NAME => [
['dispatchWebhooks', 0],
],
];
}
public function dispatchWebhooks(SendWebhooks $event)
{
// Compile list of connectors for the station. Always dispatch to the local websocket receiver.
$connectors = [];
if ($event->isStandalone()) {
$connectors[] = [
'type' => 'local',
'triggers' => [],
'config' => [],
];
}
// Assemble list of webhooks for the station
$station_webhooks = $event->getStation()->getWebhooks();
if ($station_webhooks->count() > 0) {
foreach($station_webhooks as $webhook) {
/** @var Entity\StationWebhook $webhook */
if ($webhook->isEnabled()) {
$connectors[] = [
'type' => $webhook->getType(),
'triggers' => $webhook->getTriggers() ?: [],
'config' => $webhook->getConfig() ?: [],
];
}
}
}
$this->logger->debug('Triggering events: '.implode(', ', $event->getTriggers()));
// Trigger all appropriate webhooks.
foreach($connectors as $connector) {
if (!$this->connectors->has($connector['type'])) {
$this->logger->error(sprintf('Webhook connector "%s" does not exist; skipping.', $connector['type']));
continue;
}
/** @var Connector\ConnectorInterface $connector_obj */
$connector_obj = $this->connectors->get($connector['type']);
if ($connector_obj->shouldDispatch($event, (array)$connector['triggers'])) {
$this->logger->debug(sprintf('Dispatching connector "%s".', $connector['type']));
$connector_obj->dispatch($event, (array)$connector['config']);
}
}
}
/**
* Send a "test" dispatch of the web hook, regardless of whether it is currently enabled, and
* return any logging information this yields.
*
* @param Entity\Station $station
* @param Entity\StationWebhook $webhook
* @return TestHandler
* @throws Exception
*/
public function testDispatch(Entity\Station $station, Entity\StationWebhook $webhook)
{
$webhook_type = $webhook->getType();
$webhook_config = $webhook->getConfig();
if (!$this->connectors->has($webhook_type)) {
throw new Exception(sprintf('Webhook connector "%s" does not exist; skipping.', $webhook_type));
}
$handler = new TestHandler(Logger::DEBUG, false);
$this->logger->pushHandler($handler);
/** @var Connector\ConnectorInterface $connector_obj */
$connector_obj = $this->connectors->get($webhook_type);
$np = $station->getNowplaying();
$event = new SendWebhooks($station, $np);
$connector_obj->dispatch($event, $webhook_config);
$this->logger->popHandler();
return $handler;
}
/**
* Directly access a webhook connector of the specified type.
*
* @param $type
* @return Connector\ConnectorInterface
*/
public function getConnector($type): Connector\ConnectorInterface
{
if ($this->connectors->has($type)) {
return $this->connectors->get($type);
}
throw new \InvalidArgumentException('Invalid web hook connector type specified.');
}
}

View File

@ -28,7 +28,6 @@ class EnableView
{ {
$this->view->addData([ $this->view->addData([
'request' => $request, 'request' => $request,
'user' => $request->getAttribute(Request::ATTRIBUTE_USER),
]); ]);
$request = $request->withAttribute(Request::ATTRIBUTE_VIEW, $this->view); $request = $request->withAttribute(Request::ATTRIBUTE_VIEW, $this->view);

View File

@ -3,9 +3,11 @@ namespace App\Middleware;
use App\Auth; use App\Auth;
use App\Customization; use App\Customization;
use App\Event\BuildView;
use App\Http\Request; use App\Http\Request;
use App\Http\Response; use App\Http\Response;
use App\Entity; use App\Entity;
use Symfony\Component\EventDispatcher\EventDispatcher;
/** /**
* Get the current user entity object and assign it into the request if it exists. * Get the current user entity object and assign it into the request if it exists.
@ -18,10 +20,14 @@ class GetCurrentUser
/** @var Customization */ /** @var Customization */
protected $customization; protected $customization;
public function __construct(Auth $auth, Customization $customization) /** @var EventDispatcher */
protected $dispatcher;
public function __construct(Auth $auth, Customization $customization, EventDispatcher $dispatcher)
{ {
$this->auth = $auth; $this->auth = $auth;
$this->customization = $customization; $this->customization = $customization;
$this->dispatcher = $dispatcher;
} }
/** /**
@ -39,6 +45,12 @@ class GetCurrentUser
$this->customization->setUser($user); $this->customization->setUser($user);
$request = $this->customization->init($request); $request = $this->customization->init($request);
$this->dispatcher->addListener(BuildView::NAME, function(BuildView $event) use ($user) {
$event->getView()->addData([
'user' => $user,
]);
});
$request = $request $request = $request
->withAttribute(Request::ATTRIBUTE_USER, $user) ->withAttribute(Request::ATTRIBUTE_USER, $user)
->withAttribute('is_logged_in', ($user instanceof Entity\User)); ->withAttribute('is_logged_in', ($user instanceof Entity\User));

91
src/Plugins.php Normal file
View File

@ -0,0 +1,91 @@
<?php
namespace App;
use Composer\Autoload\ClassLoader;
use Slim\Container;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;
class Plugins
{
/** @var array An array of all plugins and their capabilities. */
protected $plugins = [];
public function __construct($base_dir)
{
$this->loadDirectory($base_dir);
}
public function loadDirectory($dir): void
{
$plugins = (new Finder())
->ignoreUnreadableDirs()
->directories()
->in($dir);
foreach($plugins as $plugin_dir) {
/** @var SplFileInfo $plugin_dir */
$plugin_prefix = $plugin_dir->getRelativePathname();
$plugin_namespace = 'Plugin\\'.\Doctrine\Common\Inflector\Inflector::classify($plugin_prefix).'\\';
$this->plugins[$plugin_prefix] = [
'namespace' => $plugin_namespace,
'path' => $plugin_dir->getPathname(),
];
}
}
/**
* Add plugin namespace classes (and any Composer dependencies) to the global include list.
*
* @param ClassLoader $autoload
*/
public function registerAutoloaders(ClassLoader $autoload): void
{
foreach($this->plugins as $plugin) {
$plugin_path = $plugin['path'];
if (file_exists($plugin_path.'/vendor/autoload.php')) {
require($plugin_path.'/vendor/autoload.php');
}
$autoload->addPsr4($plugin['namespace'], $plugin_path.'/src');
}
}
/**
* Register or override any services contained in the global Dependency Injection container.
*
* @param Container $di
* @param array $settings
*/
public function registerServices(Container $di, array $settings): void
{
foreach($this->plugins as $plugin) {
$plugin_path = $plugin['path'];
if (file_exists($plugin_path . '/services.php')) {
call_user_func(include($plugin_path . '/services.php'), $di, $settings);
}
}
}
/**
* Register custom events that the plugin overrides with the Event Dispatcher.
*
* @param EventDispatcher $dispatcher
* @param Container $di
* @param array $settings
*/
public function registerEvents(EventDispatcher $dispatcher, Container $di, array $settings): void
{
foreach($this->plugins as $plugin) {
$plugin_path = $plugin['path'];
if (file_exists($plugin_path . '/events.php')) {
call_user_func(include($plugin_path . '/events.php'), $dispatcher, $di, $settings);
}
}
}
}

View File

@ -33,7 +33,8 @@ class MiddlewareProvider implements ServiceProviderInterface
$di[Middleware\GetCurrentUser::class] = function($di) { $di[Middleware\GetCurrentUser::class] = function($di) {
return new Middleware\GetCurrentUser( return new Middleware\GetCurrentUser(
$di[App\Auth::class], $di[App\Auth::class],
$di[App\Customization::class] $di[App\Customization::class],
$di[\Symfony\Component\EventDispatcher\EventDispatcher::class]
); );
}; };

View File

@ -57,8 +57,8 @@ class SyncProvider implements ServiceProviderInterface
$di[\App\Radio\AutoDJ::class], $di[\App\Radio\AutoDJ::class],
$di[\App\Cache::class], $di[\App\Cache::class],
$di[\InfluxDB\Database::class], $di[\InfluxDB\Database::class],
$di[\App\Webhook\Dispatcher::class],
$di[\Doctrine\ORM\EntityManager::class], $di[\Doctrine\ORM\EntityManager::class],
$di[\Symfony\Component\EventDispatcher\EventDispatcher::class],
$di[\Monolog\Logger::class] $di[\Monolog\Logger::class]
); );
}; };

View File

@ -2,15 +2,17 @@
namespace App\Sync\Task; namespace App\Sync\Task;
use App\Cache; use App\Cache;
use App\Event\GenerateRawNowPlaying;
use App\Event\SendWebhooks;
use App\Radio\AutoDJ; use App\Radio\AutoDJ;
use App\ApiUtilities; use App\ApiUtilities;
use App\Radio\Adapters; use App\Radio\Adapters;
use App\Radio\Frontend\FrontendAbstract;
use App\Webhook\Dispatcher; use App\Webhook\Dispatcher;
use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManager;
use InfluxDB\Database; use InfluxDB\Database;
use App\Entity; use App\Entity;
use Monolog\Logger; use Monolog\Logger;
use Symfony\Component\EventDispatcher\EventDispatcher;
class NowPlaying extends TaskAbstract class NowPlaying extends TaskAbstract
{ {
@ -32,8 +34,8 @@ class NowPlaying extends TaskAbstract
/** @var Logger */ /** @var Logger */
protected $logger; protected $logger;
/** @var Dispatcher */ /** @var EventDispatcher */
protected $webhook_dispatcher; protected $event_dispatcher;
/** @var ApiUtilities */ /** @var ApiUtilities */
protected $api_utils; protected $api_utils;
@ -54,14 +56,15 @@ class NowPlaying extends TaskAbstract
protected $analytics_level; protected $analytics_level;
/** /**
* @param EntityManager $em
* @param Database $influx
* @param Cache $cache
* @param Adapters $adapters * @param Adapters $adapters
* @param Dispatcher $webhook_dispatcher
* @param ApiUtilities $api_utils * @param ApiUtilities $api_utils
* @param AutoDJ $autodj * @param AutoDJ $autodj
* @param Cache $cache
* @param Database $influx
* @param EntityManager $em
* @param EventDispatcher $event_dispatcher
* @param Logger $logger * @param Logger $logger
*
* @see \App\Provider\SyncProvider * @see \App\Provider\SyncProvider
*/ */
public function __construct( public function __construct(
@ -70,8 +73,8 @@ class NowPlaying extends TaskAbstract
AutoDJ $autodj, AutoDJ $autodj,
Cache $cache, Cache $cache,
Database $influx, Database $influx,
Dispatcher $webhook_dispatcher,
EntityManager $em, EntityManager $em,
EventDispatcher $event_dispatcher,
Logger $logger) Logger $logger)
{ {
$this->adapters = $adapters; $this->adapters = $adapters;
@ -79,9 +82,9 @@ class NowPlaying extends TaskAbstract
$this->autodj = $autodj; $this->autodj = $autodj;
$this->cache = $cache; $this->cache = $cache;
$this->em = $em; $this->em = $em;
$this->event_dispatcher = $event_dispatcher;
$this->influx = $influx; $this->influx = $influx;
$this->logger = $logger; $this->logger = $logger;
$this->webhook_dispatcher = $webhook_dispatcher;
$this->history_repo = $this->em->getRepository(Entity\SongHistory::class); $this->history_repo = $this->em->getRepository(Entity\SongHistory::class);
$this->song_repo = $this->em->getRepository(Entity\Song::class); $this->song_repo = $this->em->getRepository(Entity\Song::class);
@ -198,27 +201,13 @@ class NowPlaying extends TaskAbstract
/** @var Entity\Api\NowPlaying|null $np_old */ /** @var Entity\Api\NowPlaying|null $np_old */
$np_old = $station->getNowplaying(); $np_old = $station->getNowplaying();
// Build the new "raw" NowPlaying data.
$event = new GenerateRawNowPlaying($station, $frontend_adapter, $remote_adapters, $payload, $include_clients);
$this->event_dispatcher->dispatch(GenerateRawNowPlaying::NAME, $event);
$np_raw = $event->getRawResponse();
$np = new Entity\Api\NowPlaying; $np = new Entity\Api\NowPlaying;
$np->station = $station->api($frontend_adapter, $remote_adapters); $np->station = $station->api($frontend_adapter, $remote_adapters);
// Build the new "raw" NowPlaying data from the adapters.
if (APP_TESTING_MODE) {
$np_raw = \NowPlaying\Adapter\AdapterAbstract::NOWPLAYING_EMPTY;
} else {
$np_raw = $frontend_adapter->getNowPlaying($payload, $include_clients);
// Loop through all remotes and update NP data accordingly.
foreach($remote_adapters as $remote_adapter) {
$remote_adapter->updateNowPlaying($np_raw, $include_clients);
}
array_walk($np_raw['current_song'], function(&$value) {
$value = htmlspecialchars_decode($value);
$value = trim($value);
});
}
// Start to convert the "raw" NowPlaying data into the proper API entities.
$np->listeners = new Entity\Api\NowPlayingListeners($np_raw['listeners']); $np->listeners = new Entity\Api\NowPlayingListeners($np_raw['listeners']);
if (empty($np_raw['current_song']['text'])) { if (empty($np_raw['current_song']['text'])) {
@ -295,8 +284,9 @@ class NowPlaying extends TaskAbstract
$this->em->persist($station); $this->em->persist($station);
$this->em->flush(); $this->em->flush();
$np_old = ($np_old instanceof Entity\Api\NowPlaying) ? $np_old : $np; // Trigger the dispatching of webhooks.
$this->webhook_dispatcher->dispatch($station, $np_old, $np, ($payload !== null)); $webhook_event = new SendWebhooks($station, $np, $np_old, ($payload !== null));
$this->event_dispatcher->dispatch(SendWebhooks::NAME, $webhook_event);
$this->logger->popProcessor(); $this->logger->popProcessor();

View File

@ -2,6 +2,7 @@
namespace App\Webhook\Connector; namespace App\Webhook\Connector;
use App\Entity; use App\Entity;
use App\Event\SendWebhooks;
use App\Utilities; use App\Utilities;
use GuzzleHttp\Client; use GuzzleHttp\Client;
use Monolog\Logger; use Monolog\Logger;
@ -20,14 +21,14 @@ abstract class AbstractConnector implements ConnectorInterface
$this->http_client = $http_client; $this->http_client = $http_client;
} }
public function shouldDispatch(array $current_events, array $triggers): bool public function shouldDispatch(SendWebhooks $event, array $triggers): bool
{ {
if (empty($triggers)) { if (empty($triggers)) {
return true; return true;
} }
foreach($triggers as $trigger) { foreach($triggers as $trigger) {
if (in_array($trigger, $current_events)) { if ($event->hasTrigger($trigger)) {
return true; return true;
} }
} }

View File

@ -1,7 +1,7 @@
<?php <?php
namespace App\Webhook\Connector; namespace App\Webhook\Connector;
use App\Entity; use App\Event\SendWebhooks;
interface ConnectorInterface interface ConnectorInterface
{ {
@ -9,18 +9,17 @@ interface ConnectorInterface
* Return a boolean indicating whether this connector should dispatch, given the current events * Return a boolean indicating whether this connector should dispatch, given the current events
* that are set to be triggered, and the configured triggers for this connector. * that are set to be triggered, and the configured triggers for this connector.
* *
* @param array $current_events The events that are currently being triggered. * @param SendWebhooks $event The current webhook dispatching event being evaluated.
* @param array|null $triggers The configured triggers for this connector. * @param array|null $triggers The configured triggers for this connector.
* @return bool * @return bool
*/ */
public function shouldDispatch(array $current_events, array $triggers): bool; public function shouldDispatch(SendWebhooks $event, array $triggers): bool;
/** /**
* Trigger the webhook for the specified station, now playing entry, and specified configuration. * Trigger the webhook for the specified station, now playing entry, and specified configuration.
* *
* @param Entity\Station $station * @param SendWebhooks $event The details of the event that triggered the webhook.
* @param Entity\Api\NowPlaying $np_new * @param array $config The specific settings associated with this webhook.
* @param array $config
*/ */
public function dispatch(Entity\Station $station, Entity\Api\NowPlaying $np_new, array $config): void; public function dispatch(SendWebhooks $event, array $config): void;
} }

View File

@ -2,6 +2,7 @@
namespace App\Webhook\Connector; namespace App\Webhook\Connector;
use App\Entity; use App\Entity;
use App\Event\SendWebhooks;
use GuzzleHttp\Exception\TransferException; use GuzzleHttp\Exception\TransferException;
use Monolog\Logger; use Monolog\Logger;
@ -60,12 +61,7 @@ use Monolog\Logger;
class Discord extends AbstractConnector class Discord extends AbstractConnector
{ {
/** public function dispatch(SendWebhooks $event, array $config): void
* @param Entity\Station $station
* @param Entity\Api\NowPlaying $np
* @param array $config
*/
public function dispatch(Entity\Station $station, Entity\Api\NowPlaying $np, array $config): void
{ {
$webhook_url = $this->_getValidUrl($config['webhook_url'] ?? ''); $webhook_url = $this->_getValidUrl($config['webhook_url'] ?? '');
@ -84,7 +80,7 @@ class Discord extends AbstractConnector
'footer' => $config['footer'] ?? '', 'footer' => $config['footer'] ?? '',
]; ];
$vars = $this->_replaceVariables($raw_vars, $np); $vars = $this->_replaceVariables($raw_vars, $event->getNowPlaying());
// Compose webhook // Compose webhook
$embed = [ $embed = [

View File

@ -2,17 +2,13 @@
namespace App\Webhook\Connector; namespace App\Webhook\Connector;
use App\Entity; use App\Entity;
use App\Event\SendWebhooks;
use GuzzleHttp\Exception\TransferException; use GuzzleHttp\Exception\TransferException;
use Monolog\Logger; use Monolog\Logger;
class Generic extends AbstractConnector class Generic extends AbstractConnector
{ {
/** public function dispatch(SendWebhooks $event, array $config): void
* @param Entity\Station $station
* @param Entity\Api\NowPlaying $np
* @param array $config
*/
public function dispatch(Entity\Station $station, Entity\Api\NowPlaying $np, array $config): void
{ {
$webhook_url = $this->_getValidUrl($config['webhook_url'] ?? ''); $webhook_url = $this->_getValidUrl($config['webhook_url'] ?? '');
@ -26,7 +22,7 @@ class Generic extends AbstractConnector
'headers' => [ 'headers' => [
'Content-Type' => 'application/json', 'Content-Type' => 'application/json',
], ],
'json' => $np, 'json' => $event->getNowPlaying(),
]; ];
if (!empty($config['basic_auth_username']) && !empty($config['basic_auth_password'])) { if (!empty($config['basic_auth_username']) && !empty($config['basic_auth_password'])) {

View File

@ -3,6 +3,7 @@ namespace App\Webhook\Connector;
use App\Cache; use App\Cache;
use App\Entity; use App\Entity;
use App\Event\SendWebhooks;
use GuzzleHttp\Client; use GuzzleHttp\Client;
use InfluxDB\Database; use InfluxDB\Database;
use Monolog\Logger; use Monolog\Logger;
@ -27,33 +28,21 @@ class Local extends AbstractConnector
$this->settings_repo = $settings_repo; $this->settings_repo = $settings_repo;
} }
/** public function shouldDispatch(SendWebhooks $event, array $triggers): bool
* @param array $current_events
* @param array|null $triggers
* @return bool
*/
public function shouldDispatch(array $current_events, array $triggers): bool
{ {
return true; return true;
} }
/** public function dispatch(SendWebhooks $event, array $config): void
* @param Entity\Station $station
* @param Entity\Api\NowPlaying $np
* @param array $config
* @throws Database\Exception
* @throws \InfluxDB\Exception
*/
public function dispatch(Entity\Station $station, Entity\Api\NowPlaying $np, array $config): void
{ {
$this->logger->debug('Writing entry to InfluxDB...'); $this->logger->debug('Writing entry to InfluxDB...');
// Post statistics to InfluxDB. // Post statistics to InfluxDB.
$influx_point = new \InfluxDB\Point( $influx_point = new \InfluxDB\Point(
'station.' . $station->getId() . '.listeners', 'station.' . $event->getStation()->getId() . '.listeners',
(int)$np->listeners->current, (int)$np->listeners->current,
[], [],
['station' => $station->getId()], ['station' => $event->getStation()->getId()],
time() time()
); );
@ -67,7 +56,7 @@ class Local extends AbstractConnector
if ($np_full) { if ($np_full) {
foreach($np_full as &$np_row) { foreach($np_full as &$np_row) {
/** @var Entity\Api\NowPlaying $np_row */ /** @var Entity\Api\NowPlaying $np_row */
if ($np_row->station->id === $station->getId()) { if ($np_row->station->id === $event->getStation()->getId()) {
$np_row = $np; $np_row = $np;
} }

View File

@ -1,7 +1,7 @@
<?php <?php
namespace App\Webhook\Connector; namespace App\Webhook\Connector;
use App\Entity; use App\Event\SendWebhooks;
use GuzzleHttp\Exception\TransferException; use GuzzleHttp\Exception\TransferException;
/** /**
@ -11,12 +11,7 @@ use GuzzleHttp\Exception\TransferException;
*/ */
class Telegram extends AbstractConnector class Telegram extends AbstractConnector
{ {
/** public function dispatch(SendWebhooks $event, array $config): void
* @param Entity\Station $station
* @param Entity\Api\NowPlaying $np
* @param array $config
*/
public function dispatch(Entity\Station $station, Entity\Api\NowPlaying $np, array $config): void
{ {
$bot_token = $config['bot_token'] ?? ''; $bot_token = $config['bot_token'] ?? '';
$chat_id = $config['chat_id'] ?? ''; $chat_id = $config['chat_id'] ?? '';
@ -28,7 +23,7 @@ class Telegram extends AbstractConnector
$messages = $this->_replaceVariables([ $messages = $this->_replaceVariables([
'text' => $config['text'], 'text' => $config['text'],
], $np); ], $event->getNowPlaying());
try { try {
$api_url = (!empty($config['api'])) ? rtrim($config['api'], '/') : 'https://api.telegram.org'; $api_url = (!empty($config['api'])) ? rtrim($config['api'], '/') : 'https://api.telegram.org';

View File

@ -2,27 +2,18 @@
namespace App\Webhook\Connector; namespace App\Webhook\Connector;
use App\Entity; use App\Entity;
use App\Event\SendWebhooks;
use GuzzleHttp\Exception\TransferException; use GuzzleHttp\Exception\TransferException;
use Monolog\Logger; use Monolog\Logger;
class TuneIn extends AbstractConnector class TuneIn extends AbstractConnector
{ {
/** public function shouldDispatch(SendWebhooks $event, array $triggers): bool
* @param array $current_events
* @param array|null $triggers
* @return bool
*/
public function shouldDispatch(array $current_events, array $triggers): bool
{ {
return in_array('song_changed', $current_events); return $event->hasTrigger('song_changed');
} }
/** public function dispatch(SendWebhooks $event, array $config): void
* @param Entity\Station $station
* @param Entity\Api\NowPlaying $np
* @param array $config
*/
public function dispatch(Entity\Station $station, Entity\Api\NowPlaying $np, array $config): void
{ {
if (empty($config['partner_id']) || empty($config['partner_key']) || empty($config['station_id'])) { if (empty($config['partner_id']) || empty($config['partner_key']) || empty($config['station_id'])) {
$this->logger->error('Webhook '.$this->_getName().' is missing necessary configuration. Skipping...'); $this->logger->error('Webhook '.$this->_getName().' is missing necessary configuration. Skipping...');
@ -32,6 +23,8 @@ class TuneIn extends AbstractConnector
$this->logger->debug('Dispatching TuneIn AIR API call...'); $this->logger->debug('Dispatching TuneIn AIR API call...');
try { try {
$np = $event->getNowPlaying();
$response = $this->http_client->get('http://air.radiotime.com/Playing.ashx', [ $response = $this->http_client->get('http://air.radiotime.com/Playing.ashx', [
'query' => [ 'query' => [
'partnerId' => $config['partner_id'], 'partnerId' => $config['partner_id'],

View File

@ -2,18 +2,14 @@
namespace App\Webhook\Connector; namespace App\Webhook\Connector;
use App\Entity; use App\Entity;
use App\Event\SendWebhooks;
use GuzzleHttp\Exception\TransferException; use GuzzleHttp\Exception\TransferException;
use GuzzleHttp\HandlerStack; use GuzzleHttp\HandlerStack;
use Monolog\Logger; use Monolog\Logger;
class Twitter extends AbstractConnector class Twitter extends AbstractConnector
{ {
/** public function dispatch(SendWebhooks $event, array $config): void
* @param Entity\Station $station
* @param Entity\Api\NowPlaying $np
* @param array $config
*/
public function dispatch(Entity\Station $station, Entity\Api\NowPlaying $np, array $config): void
{ {
if (empty($config['consumer_key']) if (empty($config['consumer_key'])
|| empty($config['consumer_secret']) || empty($config['consumer_secret'])
@ -40,7 +36,7 @@ class Twitter extends AbstractConnector
'message' => $config['message'] ?? '', 'message' => $config['message'] ?? '',
]; ];
$vars = $this->_replaceVariables($raw_vars, $np); $vars = $this->_replaceVariables($raw_vars, $event->getNowPlaying());
// Dispatch webhook // Dispatch webhook
$this->logger->debug('Posting to Twitter...'); $this->logger->debug('Posting to Twitter...');

View File

@ -3,6 +3,7 @@ namespace App\Webhook;
use App\Config; use App\Config;
use App\Entity; use App\Entity;
use App\Event\SendWebhooks;
use App\Exception; use App\Exception;
use App\Provider\WebhookProvider; use App\Provider\WebhookProvider;
use Monolog\Handler\TestHandler; use Monolog\Handler\TestHandler;
@ -31,12 +32,9 @@ class Dispatcher
/** /**
* Determine which webhooks to dispatch for a given change in Now Playing data, and dispatch them. * Determine which webhooks to dispatch for a given change in Now Playing data, and dispatch them.
* *
* @param Entity\Station $station * @param SendWebhooks $event
* @param Entity\Api\NowPlaying $np_old
* @param Entity\Api\NowPlaying $np_new
* @param boolean $is_standalone
*/ */
public function dispatch(Entity\Station $station, Entity\Api\NowPlaying $np_old, Entity\Api\NowPlaying $np_new, $is_standalone = true): void public function dispatch(SendWebhooks $event): void
{ {
if (APP_TESTING_MODE) { if (APP_TESTING_MODE) {
$this->logger->info('In testing mode; no webhooks dispatched.'); $this->logger->info('In testing mode; no webhooks dispatched.');
@ -46,7 +44,7 @@ class Dispatcher
// Compile list of connectors for the station. Always dispatch to the local websocket receiver. // Compile list of connectors for the station. Always dispatch to the local websocket receiver.
$connectors = []; $connectors = [];
if ($is_standalone) { if ($event->isStandalone()) {
$connectors[] = [ $connectors[] = [
'type' => 'local', 'type' => 'local',
'triggers' => [], 'triggers' => [],
@ -55,7 +53,7 @@ class Dispatcher
} }
// Assemble list of webhooks for the station // Assemble list of webhooks for the station
$station_webhooks = $station->getWebhooks(); $station_webhooks = $event->getStation()->getWebhooks();
if ($station_webhooks->count() > 0) { if ($station_webhooks->count() > 0) {
foreach($station_webhooks as $webhook) { foreach($station_webhooks as $webhook) {
@ -70,26 +68,7 @@ class Dispatcher
} }
} }
// Determine which events should be triggered as a result of this change. $this->logger->debug('Triggering events: '.implode(', ', $event->getTriggers()));
$to_trigger = ['all'];
if ($np_old->now_playing->song->id !== $np_new->now_playing->song->id) {
$to_trigger[] = 'song_changed';
}
if ($np_old->listeners->current > $np_new->listeners->current) {
$to_trigger[] = 'listener_lost';
} elseif ($np_old->listeners->current < $np_new->listeners->current) {
$to_trigger[] = 'listener_gained';
}
if ($np_old->live->is_live === false && $np_new->live->is_live === true) {
$to_trigger[] = 'live_connect';
} elseif ($np_old->live->is_live === true && $np_new->live->is_live === false) {
$to_trigger[] = 'live_disconnect';
}
$this->logger->debug('Triggering events: '.implode(', ', $to_trigger));
// Trigger all appropriate webhooks. // Trigger all appropriate webhooks.
foreach($connectors as $connector) { foreach($connectors as $connector) {
@ -101,10 +80,10 @@ class Dispatcher
/** @var Connector\ConnectorInterface $connector_obj */ /** @var Connector\ConnectorInterface $connector_obj */
$connector_obj = $this->connectors->get($connector['type']); $connector_obj = $this->connectors->get($connector['type']);
if ($connector_obj->shouldDispatch($to_trigger, (array)$connector['triggers'])) { if ($connector_obj->shouldDispatch($event, (array)$connector['triggers'])) {
$this->logger->debug(sprintf('Dispatching connector "%s".', $connector['type'])); $this->logger->debug(sprintf('Dispatching connector "%s".', $connector['type']));
$connector_obj->dispatch($station, $np_new, (array)$connector['config']); $connector_obj->dispatch($event, (array)$connector['config']);
} }
} }
} }
@ -135,7 +114,8 @@ class Dispatcher
$np = $station->getNowplaying(); $np = $station->getNowplaying();
$connector_obj->dispatch($station, $np, $webhook_config); $event = new SendWebhooks($station, $np);
$connector_obj->dispatch($event, $webhook_config);
$this->logger->popHandler(); $this->logger->popHandler();