Implement Station History API, convert history report to use it.

This commit is contained in:
Buster "Silver Eagle" Neece 2018-08-25 05:56:43 -05:00
parent 7d4330665b
commit 9a2d5108e8
15 changed files with 354 additions and 296 deletions

View File

@ -163,6 +163,11 @@ return function(\Slim\App $app) {
$this->get('/nowplaying', Controller\Api\NowplayingController::class.':indexAction');
// This would not normally be POST-able, but Bootgrid requires it
$this->map(['GET', 'POST'], '/history', Controller\Api\Stations\HistoryController::class)
->setName('api:stations:history')
->add([Middleware\Permissions::class, 'view station reports', true]);
// This would not normally be POST-able, but Bootgrid requires it
$this->map(['GET', 'POST'], '/requests', Controller\Api\RequestsController::class.':listAction')
->setName('api:requests:list');

View File

@ -3,21 +3,25 @@
<?php
/** @var \App\Assets $assets */
$assets
->load('daterangepicker')
->load('bootgrid');
?>
<div class="card">
<div class="card-header ch-alt">
<h2><?=__('Song Playback Timeline') ?></h2>
<ul class="actions">
<li>
<a href="<?=$router->fromHere(null, ['format' => 'csv']) ?>" title="<?=__('Download CSV') ?>">
<span class="sr-only"><?=__('Download CSV') ?></span>
<i class="zmdi zmdi-download"></i>
<div class="row">
<div class="col-md-5">
<h2><?=__('Song Playback Timeline') ?></h2>
</div>
<div class="col-md-7 text-right">
<a class="btn btn-default" id="reportrange" href="#">
<i class="zmdi zmdi-calendar"></i> <span><?=__('Last 14 Days') ?></span> <i class="caret"></i>
</a>
</li>
</ul>
<a class="btn btn-primary" id="btn-export" href="<?=$router->fromHere('api:stations:history', [], ['format' => 'csv']) ?>" target="_blank" title="<?=__('Download CSV') ?>">
<i class="zmdi zmdi-download"></i> <?=__('Download CSV') ?>
</a>
</div>
</div>
</div>
<div class="table-responsive">
<table class="data-table table table-striped">
@ -30,65 +34,105 @@ $assets
</colgroup>
<thead>
<tr>
<th data-column-id="date_time"><?=__('Date/Time') ?></th>
<th data-column-id="listeners"><?=__('Listeners') ?></th>
<th data-column-id="delta"><?=__('Change') ?></th>
<th data-column-id="song"><?=__('Song Title') ?></th>
<th data-column-id="playlists"><?=__('Source') ?></th>
<th data-column-id="date_time" data-formatter="datetime"><?=__('Date/Time') ?></th>
<th data-column-id="listeners_start"><?=__('Listeners') ?></th>
<th data-column-id="delta" data-formatter="delta"><?=__('Change') ?></th>
<th data-column-id="song" data-formatter="song_title"><?=__('Song Title') ?></th>
<th data-column-id="playlists" data-formatter="playlists"><?=__('Source') ?></th>
</tr>
</thead>
<tbody>
<?php foreach($songs as $song_row): ?>
<tr class="input" id="song_<?=$song_row['timestamp'] ?>">
<td class="text-center"><?=$customization->formatTime($song_row['timestamp_start']) ?></td>
<td class="text-center"><big><?=$song_row['stat_start'] ?></big></td>
<td class="text-center">
<big>
<?php if ($song_row['stat_delta'] > 0): ?>
<span class="text-success"><i class="icon-caret-up"></i> <?=$song_row['stat_delta'] ?></span>
<?php elseif ($song_row['stat_delta'] < 0): ?>
<span class="text-danger"><i class="icon-caret-down"></i> <?=abs($song_row['stat_delta']) ?></span>
<?php else: ?>
0
<?php endif; ?>
</big>
</td>
<td>
<?php if ($song_row['song']['title']): ?>
<b><?=$song_row['song']['title'] ?></b><br>
<?=$song_row['song']['artist'] ?>
<?php else: ?>
<?=$song_row['song']['text'] ?>
<?php endif; ?>
</td>
<td>
<?php if (isset($song_row['request'])): ?>
<?=sprintf(__('Request: %s'), date('D g:ia', $song_row['request']['timestamp'])) ?>
<?php elseif (isset($song_row['playlist'])): ?>
<?=sprintf(__('Playlist: %s'), $song_row['playlist']['name']) ?>
<?php else: ?>
<?=__('Live Broadcast') ?>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<script type="text/javascript" nonce="<?=$assets->getCspNonce() ?>">
$(function() {
$(".data-table").bootgrid({
caseSensitive: false,
sorting: false,
css: {
icon: 'zmdi icon',
iconColumns: 'zmdi-view-module',
iconDown: 'zmdi-sort-amount-desc',
iconRefresh: 'zmdi-refresh',
iconUp: 'zmdi-sort-amount-asc'
let range_start = null,
range_end = null;
const api_url = "<?=$router->fromHere('api:stations:history') ?>";
$(function() {
$(".data-table").bootgrid({
ajax: true,
sorting: false,
caseSensitive: false,
css: {
icon: 'zmdi icon',
iconColumns: 'zmdi-view-module',
iconDown: 'zmdi-sort-amount-desc',
iconRefresh: 'zmdi-refresh',
iconUp: 'zmdi-sort-amount-asc'
},
url: api_url,
post: function() {
return {
start: range_start,
end: range_end
};
},
formatters: {
"datetime": function(column, row) {
return formatTimestamp(row.played_at);
},
"delta": function(column, row) {
if (row.delta_total > 0) {
return '<big><span class="text-success"><i class="zmdi zmdi-trending-up"></i> '+row.delta_total+'</span></big>';
} else if (row.delta_total < 0) {
return '<big><span class="text-danger"><i class="zmdi zmdi-trending-down"></i> ' + Math.abs(row.delta_total) + '</span></big>';
} else {
return '<big>0</big>';
}
},
"song_title": function(column, row) {
if (row.song_title) {
return '<b>'+row.song_title+'</b><br>'+row.song_artist;
} else {
return row.song_text;
}
},
"playlists": function(column, row) {
if (row.is_request) {
return <?=$this->escapeJs(__('Listener Request')) ?>;
} else if (row.playlist) {
return <?=$this->escapeJs(__('Playlist:')) ?>+" "+row.playlist;
} else {
return <?=$this->escapeJs(__('Live Broadcast')) ?>;
}
}
});
}
});
$('#reportrange').daterangepicker({
startDate: moment().subtract(13, 'days'),
endDate: moment(),
opens: "left",
ranges: {
"<?=__('Today') ?>": [moment(), moment()],
"<?=__('Yesterday') ?>": [moment().subtract(1, 'days'), moment().subtract(1, 'days')],
"<?=__('Last 7 Days') ?>": [moment().subtract(6, 'days'), moment()],
"<?=__('Last 14 Days') ?>": [moment().subtract(13, 'days'), moment()],
"<?=__('Last 30 Days') ?>": [moment().subtract(29, 'days'), moment()],
"<?=__('This Month') ?>": [moment().startOf('month'), moment().endOf('month')],
"<?=__('Last Month') ?>": [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')]
}
}, function(start, end) {
$('#reportrange span').html(start.format('MMMM D, YYYY') + ' - ' + end.format('MMMM D, YYYY'));
range_start = start.format('YYYY-MM-DD');
range_end = end.format('YYYY-MM-DD');
$('#btn-export').attr('href', api_url+'?format=csv&start='+range_start+'&end='+range_end);
$('.data-table').bootgrid("reload");
});
function formatTimestamp(unix_timestamp) {
var m = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
var d = new Date(unix_timestamp*1000);
return [m[d.getMonth()],' ',d.getDate(),', ',d.getFullYear()," ",
(d.getHours() % 12 || 12),":",(d.getMinutes() < 10 ? '0' : '')+d.getMinutes(),
" ",d.getHours() >= 12 ? 'PM' : 'AM'].join('');
}
});
</script>

View File

@ -28,13 +28,6 @@ class ApiProvider implements ServiceProviderInterface
);
};
$di[Stations\MediaController::class] = function($di) {
return new Stations\MediaController(
$di[\Doctrine\ORM\EntityManager::class],
$di[\App\Customization::class]
);
};
$di[NowplayingController::class] = function($di) {
return new NowplayingController(
$di[\Doctrine\ORM\EntityManager::class],
@ -45,7 +38,13 @@ class ApiProvider implements ServiceProviderInterface
$di[RequestsController::class] = function($di) {
return new RequestsController(
$di[\Doctrine\ORM\EntityManager::class],
$di['router'],
$di[\App\ApiUtilities::class]
);
};
$di[Stations\HistoryController::class] = function($di) {
return new Stations\HistoryController(
$di[\Doctrine\ORM\EntityManager::class],
$di[\App\ApiUtilities::class]
);
};
@ -57,6 +56,13 @@ class ApiProvider implements ServiceProviderInterface
);
};
$di[Stations\MediaController::class] = function($di) {
return new Stations\MediaController(
$di[\Doctrine\ORM\EntityManager::class],
$di[\App\Customization::class]
);
};
$di[Stations\ServicesController::class] = function($di) {
return new Stations\ServicesController(
$di[\Doctrine\ORM\EntityManager::class],

View File

@ -2,7 +2,6 @@
namespace App\Controller\Api;
use App\Doctrine\Paginator;
use App\Http\Router;
use App\Utilities;
use App\ApiUtilities;
use Doctrine\ORM\EntityManager;
@ -15,22 +14,17 @@ class RequestsController
/** @var EntityManager */
protected $em;
/** @var Router */
protected $router;
/** @var ApiUtilities */
protected $api_utils;
/**
* RequestsController constructor.
* @param EntityManager $em
* @param Router $router
* @param ApiUtilities $api_utils
*/
public function __construct(EntityManager $em, Router $router, ApiUtilities $api_utils)
public function __construct(EntityManager $em, ApiUtilities $api_utils)
{
$this->em = $em;
$this->router = $router;
$this->api_utils = $api_utils;
}
@ -102,22 +96,20 @@ class RequestsController
$paginator->setFromRequest($request);
$is_bootgrid = $paginator->isFromBootgrid();
$router = $request->getRouter();
$paginator->setPostprocessor(function($media_row) use ($station_id, $is_bootgrid) {
$paginator->setPostprocessor(function($media_row) use ($station_id, $is_bootgrid, $router) {
/** @var Entity\StationMedia $media_row */
$row = new Entity\Api\StationRequest;
$row->song = $media_row->api($this->api_utils);
$row->request_id = (int)$media_row->getId();
$row->request_url = (string)$this->router->named('api:requests:submit', [
$row->request_url = (string)$router->named('api:requests:submit', [
'station' => $station_id,
'media_id' => $media_row->getUniqueId(),
]);
if ($is_bootgrid) {
$row = json_decode(json_encode($row), true);
foreach($row['song'] as $song_key => $song_val) {
$row['song_'.$song_key] = $song_val;
}
return Utilities::flatten_array($row, '_');
}
return $row;

View File

@ -0,0 +1,123 @@
<?php
namespace App\Controller\Api\Stations;
use App;
use App\Doctrine\Paginator;
use Doctrine\ORM\EntityManager;
use App\Entity;
use App\Http\Request;
use App\Http\Response;
/**
* Class HistoryController
* @package App\Controller\Api\Stations
* @see App\Controller\Api\ApiProvider
*/
class HistoryController
{
/** @var EntityManager */
protected $em;
/** @var App\ApiUtilities */
protected $api_utils;
/**
* @param EntityManager $em
* @param App\ApiUtilities $api_utils
*/
public function __construct(EntityManager $em, App\ApiUtilities $api_utils)
{
$this->em = $em;
$this->api_utils = $api_utils;
}
public function __invoke(Request $request, Response $response, $station_id)
{
$station = $request->getStation();
if ($request->hasParam('start')) {
$start = strtotime($request->getParam('start') . ' 00:00:00');
$end = strtotime($request->getParam('end', $request->getParam('start')) . ' 23:59:59');
} else {
$start = strtotime('-2 weeks');
$end = time();
}
$qb = $this->em->createQueryBuilder();
$qb->select('sh, sr, sp, s')
->from(Entity\SongHistory::class, 'sh')
->leftJoin('sh.request', 'sr')
->leftJoin('sh.playlist', 'sp')
->leftJoin('sh.song', 's')
->where('sh.station_id = :station_id')
->andWhere('sh.timestamp_end >= :start AND sh.timestamp_start <= :end')
->andWhere('sh.listeners_start IS NOT NULL')
->setParameter('station_id', $station_id)
->setParameter('start', $start)
->setParameter('end', $end);
if ($request->getParam('format', 'json') === 'csv') {
$export_all = [];
$export_all[] = [
'Date',
'Time',
'Listeners',
'Delta',
'Likes',
'Dislikes',
'Track',
'Artist',
'Playlist'
];
foreach ($qb->getQuery()->getArrayResult() as $song_row) {
$export_row = [
date('Y-m-d', $song_row['timestamp_start']),
date('g:ia', $song_row['timestamp_start']),
$song_row['listeners_start'],
$song_row['delta_total'],
$song_row['score_likes'],
$song_row['score_dislikes'],
$song_row['song']['title'] ?: $song_row['song']['text'],
$song_row['song']['artist'],
$song_row['playlist']['name'] ?? '',
];
$export_all[] = $export_row;
}
$csv_file = App\Export::csv($export_all);
$csv_filename = $station->getShortName() . '_timeline_' . date('Ymd', $start) . '_to_'. date('Ymd', $end).'.csv';
return $response->renderStringAsFile($csv_file, 'text/csv', $csv_filename);
}
$search_phrase = trim($request->getParam('searchPhrase'));
if (!empty($search_phrase)) {
$qb->andWhere('(s.title LIKE :query OR s.artist LIKE :query)')
->setParameter('query', '%'.$search_phrase.'%');
}
$qb->orderBy('sh.timestamp_start', 'DESC');
$paginator = new Paginator($qb);
$paginator->setFromRequest($request);
$is_bootgrid = $paginator->isFromBootgrid();
$paginator->setPostprocessor(function($sh_row) use ($is_bootgrid) {
/** @var Entity\SongHistory $sh_row */
$row = $sh_row->api(new Entity\Api\DetailedSongHistory, $this->api_utils);
if ($is_bootgrid) {
return App\Utilities::flatten_array($row, '_');
}
return $row;
});
return $paginator->write($response);
}
}

View File

@ -10,19 +10,19 @@ use InfluxDB\Database;
class IndexController
{
use Traits\SongHistoryFilters;
/** @var EntityManager */
protected $em;
/** @var Database */
protected $influx;
/**
* IndexController constructor.
* @param EntityManager $em
* @param Database $influx
*/
public function __construct(EntityManager $em, Cache $cache, Database $influx)
public function __construct(EntityManager $em, Database $influx)
{
$this->em = $em;
$this->cache = $cache;
$this->influx = $influx;
}
@ -111,11 +111,6 @@ class IndexController
->setMaxResults(40)
->getArrayResult();
$ignored_songs = $this->_getIgnoredSongs();
$song_totals_raw['played'] = array_filter($song_totals_raw['played'], function ($value) use ($ignored_songs) {
return !(isset($ignored_songs[$value['song_id']]));
});
// Compile the above data.
$song_totals = [];
foreach ($song_totals_raw as $total_type => $total_records) {
@ -143,11 +138,6 @@ class IndexController
->setParameter('timestamp', $threshold)
->getArrayResult();
$ignored_songs = $this->_getIgnoredSongs();
$songs_played_raw = array_filter($songs_played_raw, function ($value) use ($ignored_songs) {
return !(isset($ignored_songs[$value['song_id']]));
});
$songs_played_raw = array_values($songs_played_raw);
$songs = [];

View File

@ -10,7 +10,8 @@ use App\Http\Response;
class ReportsController
{
use Traits\SongHistoryFilters;
/** @var EntityManager */
protected $em;
/** @var RadioAutomation */
protected $sync_automation;
@ -18,77 +19,17 @@ class ReportsController
/**
* ReportsController constructor.
* @param EntityManager $em
* @param Cache $cache
* @param RadioAutomation $sync_automation
*/
public function __construct(EntityManager $em, Cache $cache, RadioAutomation $sync_automation)
public function __construct(EntityManager $em, RadioAutomation $sync_automation)
{
$this->em = $em;
$this->cache = $cache;
$this->sync_automation = $sync_automation;
}
public function timelineAction(Request $request, Response $response, $station_id, $format = 'html'): Response
public function timelineAction(Request $request, Response $response): Response
{
$station = $request->getStation();
$songs_played_raw = $this->_getEligibleHistory($station_id);
$songs = [];
foreach ($songs_played_raw as $song_row) {
// Song has no recorded ending.
if ($song_row['timestamp_end'] == 0) {
continue;
}
$song_row['stat_start'] = $song_row['listeners_start'];
$song_row['stat_end'] = $song_row['listeners_end'];
$song_row['stat_delta'] = $song_row['delta_total'];
$songs[] = $song_row;
}
if ($format === 'csv') {
$export_all = [];
$export_all[] = [
'Date',
'Time',
'Listeners',
'Delta',
'Likes',
'Dislikes',
'Track',
'Artist',
'Playlist'
];
foreach ($songs as $song_row) {
$export_row = [
date('Y-m-d', $song_row['timestamp_start']),
date('g:ia', $song_row['timestamp_start']),
$song_row['stat_start'],
$song_row['stat_delta'],
$song_row['score_likes'],
$song_row['score_dislikes'],
$song_row['song']['title'] ?: $song_row['song']['text'],
$song_row['song']['artist'],
$song_row['playlist']['name'] ?? '',
];
$export_all[] = $export_row;
}
$csv_file = \App\Export::csv($export_all);
$csv_filename = $station->getShortName() . '_timeline_' . date('Ymd') . '.csv';
return $response->renderStringAsFile($csv_file, 'text/csv', $csv_filename);
}
$songs = array_reverse($songs);
return $request->getView()->renderToResponse($response, 'stations/reports/timeline', [
'songs' => $songs,
]);
return $request->getView()->renderToResponse($response, 'stations/reports/timeline');
}
public function performanceAction(Request $request, Response $response, $station_id, $format = 'html'): Response

View File

@ -50,7 +50,6 @@ class StationsProvider implements ServiceProviderInterface
$di[IndexController::class] = function($di) {
return new IndexController(
$di[\Doctrine\ORM\EntityManager::class],
$di[\App\Cache::class],
$di[\InfluxDB\Database::class]
);
};
@ -97,7 +96,6 @@ class StationsProvider implements ServiceProviderInterface
$di[ReportsController::class] = function($di) {
return new ReportsController(
$di[\Doctrine\ORM\EntityManager::class],
$di[\App\Cache::class],
$di[\App\Sync\Task\RadioAutomation::class]
);
};

View File

@ -1,90 +0,0 @@
<?php
namespace App\Controller\Stations\Traits;
use App\Cache;
use App\Entity\Song;
use App\Entity\SongHistory;
use Doctrine\ORM\EntityManager;
trait SongHistoryFilters
{
/** @var Cache */
protected $cache;
/** @var EntityManager */
protected $em;
protected function _getEligibleHistory($station_id)
{
$cache_name = 'stations/'.$station_id.'/history';
$songs_played_raw = $this->cache->get($cache_name);
if (!$songs_played_raw) {
try {
$first_song = $this->em->createQuery('SELECT sh.timestamp_start FROM '.SongHistory::class.' sh
WHERE sh.station_id = :station_id AND sh.listeners_start IS NOT NULL
ORDER BY sh.timestamp_start ASC')
->setParameter('station_id', $station_id)
->setMaxResults(1)
->getSingleScalarResult();
} catch (\Exception $e) {
$first_song = strtotime('Yesterday 00:00:00');
}
$min_threshold = strtotime('-2 weeks');
$threshold = max($first_song, $min_threshold);
// Get all songs played in timeline.
$songs_played_raw = $this->em->createQuery('SELECT sh, sr, sp, s
FROM '.SongHistory::class.' sh
LEFT JOIN sh.request sr
LEFT JOIN sh.playlist sp
LEFT JOIN sh.song s
WHERE sh.station_id = :station_id AND sh.timestamp_start >= :timestamp AND sh.listeners_start IS NOT NULL
ORDER BY sh.timestamp_start ASC')
->setParameter('station_id', $station_id)
->setParameter('timestamp', $threshold)
->getArrayResult();
$ignored_songs = $this->_getIgnoredSongs();
$songs_played_raw = array_filter($songs_played_raw, function ($value) use ($ignored_songs) {
return !(isset($ignored_songs[$value['song_id']]));
});
$songs_played_raw = array_values($songs_played_raw);
$this->cache->save($songs_played_raw, $cache_name, 60 * 5);
}
return $songs_played_raw;
}
protected function _getIgnoredSongs()
{
$song_hashes = $this->cache->get('stations/all/ignored_songs');
if (!$song_hashes) {
$ignored_phrases = ['Offline', 'Sweeper', 'Bumper', 'Unknown'];
$qb = $this->em->createQueryBuilder();
$qb->select('s.id')->from(Song::class, 's');
foreach ($ignored_phrases as $i => $phrase) {
$qb->orWhere('s.text LIKE ?' . ($i + 1));
$qb->setParameter($i + 1, '%' . $phrase . '%');
}
$song_hashes_raw = $qb->getQuery()->getArrayResult();
$song_hashes = [];
foreach ($song_hashes_raw as $row) {
$song_hashes[$row['id']] = $row['id'];
}
$this->cache->save($song_hashes, 'stations/all/ignored_songs', 86400);
}
return $song_hashes;
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Entity\Api;
/**
* @SWG\Definition(type="object")
*/
class DetailedSongHistory extends SongHistory
{
/**
* Number of listeners when the song playback started.
*
* @SWG\Property(example=94)
* @var int
*/
public $listeners_start;
/**
* Number of listeners when song playback ended.
*
* @SWG\Property(example=105)
* @var int
*/
public $listeners_end;
/**
* The sum total change of listeners between the song's start and ending.
*
* @SWG\Property(example=11)
* @var int
*/
public $delta_total;
}

View File

@ -68,7 +68,7 @@ class SongHistoryRepository extends BaseRepository
$return = [];
foreach ($history as $sh) {
/** @var Entity\SongHistory $sh */
$return[] = $sh->api($api_utils);
$return[] = $sh->api(new Entity\Api\SongHistory, $api_utils);
}
return $return;

View File

@ -459,11 +459,12 @@ class SongHistory
}
/**
* @return Api\SongHistory|Api\NowPlayingCurrentSong
* @param Api\SongHistory $response
* @param \App\ApiUtilities $api
* @return Api\SongHistory
*/
public function api(\App\ApiUtilities $api, $now_playing = false)
public function api(Api\SongHistory $response, \App\ApiUtilities $api)
{
$response = ($now_playing) ? new Api\NowPlayingCurrentSong : new Api\SongHistory;
$response->sh_id = (int)$this->id;
$response->played_at = (int)$this->timestamp_start;
$response->duration = (int)$this->duration;
@ -476,6 +477,12 @@ class SongHistory
$response->playlist = '';
}
if ($response instanceof Api\DetailedSongHistory) {
$response->listeners_start = (int)$this->listeners_start;
$response->listeners_end = (int)$this->listeners_end;
$response->delta_total = (int)$this->delta_total;
}
$response->song = ($this->media)
? $this->media->api($api)
: $this->song->api($api);

View File

@ -180,7 +180,7 @@ class NowPlaying extends TaskAbstract
$next_song = $this->history_repo->getNextSongForStation($station);
if ($next_song instanceof Entity\SongHistory) {
$np->playing_next = $next_song->api($this->api_utils);
$np->playing_next = $next_song->api(new Entity\Api\SongHistory, $this->api_utils);
} else {
$np->playing_next = null;
}
@ -209,7 +209,7 @@ class NowPlaying extends TaskAbstract
$next_song = $this->history_repo->getNextSongForStation($station);
if ($next_song instanceof Entity\SongHistory) {
$np->playing_next = $next_song->api($this->api_utils);
$np->playing_next = $next_song->api(new Entity\Api\SongHistory, $this->api_utils);
}
}
@ -231,7 +231,7 @@ class NowPlaying extends TaskAbstract
}
// Register a new item in song history.
$np->now_playing = $sh_obj->api($this->api_utils, true);
$np->now_playing = $sh_obj->api(new Entity\Api\NowPlayingCurrentSong, $this->api_utils);
}
$np->update();

View File

@ -589,4 +589,51 @@ class Utilities
return getHostByName(getHostName()) ?? 'localhost';
}
/**
* Flatten an array from format:
* [
* 'user' => [
* 'id' => 1,
* 'name' => 'test',
* ]
* ]
*
* to format:
* [
* 'user.id' => 1,
* 'user.name' => 'test',
* ]
*
* This function is used to create replacements for variables in strings.
*
* @param array|object $array
* @param string $separator
* @param null $prefix
* @return array
*/
public static function flatten_array($array, $separator = '.', $prefix = null): array
{
if (!is_array($array)) {
if (is_object($array)) {
// Quick and dirty conversion from object to array.
$array = json_decode(json_encode($array), true);
} else {
return $array;
}
}
$return = [];
foreach($array as $key => $value) {
$return_key = $prefix ? $prefix.$separator.$key : $key;
if (\is_array($value)) {
$return = array_merge($return, self::flatten_array($value, $separator, $return_key));
} else {
$return[$return_key] = $value;
}
}
return $return;
}
}

View File

@ -2,6 +2,7 @@
namespace App\Webhook\Connector;
use App\Entity;
use App\Utilities;
use Monolog\Logger;
abstract class AbstractConnector implements ConnectorInterface
@ -29,44 +30,6 @@ abstract class AbstractConnector implements ConnectorInterface
return false;
}
/**
* Flatten an array from format:
* [
* 'user' => [
* 'id' => 1,
* 'name' => 'test',
* ]
* ]
*
* to format:
* [
* 'user.id' => 1,
* 'user.name' => 'test',
* ]
*
* This function is used to create replacements for variables in strings.
*
* @param array $array
* @param string $separator
* @param null $prefix
* @return array
*/
protected function _flattenArray(array $array, $separator = '.', $prefix = null): array
{
$return = [];
foreach($array as $key => $value) {
$return_key = $prefix ? $prefix.$separator.$key : $key;
if (\is_array($value)) {
$return = array_merge($return, $this->_flattenArray($value, $separator, $return_key));
} else {
$return[$return_key] = $value;
}
}
return $return;
}
/**
* Replace variables in the format {{ blah }} with the flattened contents of the NowPlaying API array.
*
@ -76,7 +39,7 @@ abstract class AbstractConnector implements ConnectorInterface
*/
public function _replaceVariables(array $raw_vars, Entity\Api\NowPlaying $np): array
{
$values = $this->_flattenArray(json_decode(json_encode($np), true));
$values = Utilities::flatten_array($np, '.');
$vars = [];
foreach($raw_vars as $var_key => $var_value) {