Commit 141788fa authored by Benjamin Bellamy's avatar Benjamin Bellamy 💬
Browse files

feat(embeddable-player): add embeddable player widget

parent 526809ef
Pipeline #694 passed with stage
in 4 minutes and 58 seconds
......@@ -326,6 +326,14 @@ $routes->group(
'filter' => 'permission:podcast_episodes-edit',
]
);
$routes->get(
'embeddable-player',
'Episode::embeddablePlayer/$1/$2',
[
'as' => 'embeddable-player-add',
'filter' => 'permission:podcast_episodes-edit',
]
);
$routes->group('persons', function ($routes) {
$routes->get('/', 'EpisodePerson/$1/$2', [
......@@ -565,9 +573,19 @@ $routes->group(config('App')->authGateway, function ($routes) {
// Public routes
$routes->group('@(:podcastName)', function ($routes) {
$routes->get('/', 'Podcast/$1', ['as' => 'podcast']);
$routes->get('(:slug)', 'Episode/$1/$2', [
'as' => 'episode',
]);
$routes->group('(:slug)', function ($routes) {
$routes->get('/', 'Episode/$1/$2', [
'as' => 'episode',
]);
$routes->group('embeddable-player', function ($routes) {
$routes->get('/', 'Episode::embeddablePlayer/$1/$2', [
'as' => 'embeddable-player',
]);
$routes->get('(:slug)', 'Episode::embeddablePlayer/$1/$2/$3', [
'as' => 'embeddable-player-theme',
]);
});
});
$routes->head('feed.xml', 'Feed/$1', ['as' => 'podcast_feed']);
$routes->get('feed.xml', 'Feed/$1', ['as' => 'podcast_feed']);
});
......
......@@ -420,4 +420,21 @@ class Episode extends BaseController
$this->episode->id,
]);
}
public function embeddablePlayer()
{
helper(['form']);
$data = [
'podcast' => $this->podcast,
'episode' => $this->episode,
'themes' => EpisodeModel::$themes,
];
replace_breadcrumb_params([
0 => $this->podcast->title,
1 => $this->episode->title,
]);
return view('admin/episode/embeddable_player', $data);
}
}
......@@ -81,6 +81,12 @@ class PodcastPlatform extends BaseController
)
? $podcastPlatform['visible'] == 'yes'
: false,
'is_on_embeddable_player' => array_key_exists(
'on_embeddable_player',
$podcastPlatform
)
? $podcastPlatform['on_embeddable_player'] == 'yes'
: false,
]);
}
}
......
......@@ -49,8 +49,16 @@ class Analytics extends Controller
public function hit($base64EpisodeData, ...$filename)
{
helper('media', 'analytics');
$serviceName = isset($_GET['_from']) ? $_GET['_from'] : '';
$session = \Config\Services::session();
$session->start();
$serviceName = '';
if (isset($_GET['_from'])) {
$serviceName = $_GET['_from'];
} elseif (!empty($session->get('embeddable_player_domain'))) {
$serviceName = $session->get('embeddable_player_domain');
} elseif ($session->get('referer') !== '- Direct -') {
$serviceName = parse_url($session->get('referer'), PHP_URL_HOST);
}
$episodeData = unpack(
'IpodcastId/IepisodeId/IbytesThreshold/IfileSize/Iduration/IpublicationDate',
......
......@@ -36,8 +36,9 @@ class Episode extends BaseController
) {
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
}
return $this->$method();
unset($params[1]);
unset($params[0]);
return $this->$method(...$params);
}
public function index()
......@@ -54,48 +55,12 @@ class Episode extends BaseController
$this->podcast->type
);
helper(['persons']);
$persons = [];
foreach ($this->episode->episode_persons as $episodePerson) {
if (array_key_exists($episodePerson->person->id, $persons)) {
$persons[$episodePerson->person->id]['roles'] .=
empty($episodePerson->person_group) ||
empty($episodePerson->person_role)
? ''
: (empty(
$persons[$episodePerson->person->id][
'roles'
]
)
? ''
: ', ') .
lang(
'PersonsTaxonomy.persons.' .
$episodePerson->person_group .
'.roles.' .
$episodePerson->person_role .
'.label'
);
} else {
$persons[$episodePerson->person->id] = [
'full_name' => $episodePerson->person->full_name,
'information_url' =>
$episodePerson->person->information_url,
'thumbnail_url' =>
$episodePerson->person->image->thumbnail_url,
'roles' =>
empty($episodePerson->person_group) ||
empty($episodePerson->person_role)
? ''
: lang(
'PersonsTaxonomy.persons.' .
$episodePerson->person_group .
'.roles.' .
$episodePerson->person_role .
'.label'
),
];
}
}
construct_episode_person_array(
$this->episode->episode_persons,
$persons
);
$data = [
'previousEpisode' => $previousNextEpisodes['previous'],
......@@ -120,4 +85,58 @@ class Episode extends BaseController
return $cachedView;
}
public function embeddablePlayer($theme = 'light-transparent')
{
self::triggerWebpageHit($this->episode->podcast_id);
$session = \Config\Services::session();
$session->start();
if (isset($_SERVER['HTTP_REFERER'])) {
$session->set(
'embeddable_player_domain',
parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST)
);
}
$locale = service('request')->getLocale();
$cacheName = "page_podcast{$this->episode->podcast_id}_episode{$this->episode->id}_embeddable_player_{$theme}_{$locale}";
if (!($cachedView = cache($cacheName))) {
$episodeModel = new EpisodeModel();
$theme = EpisodeModel::$themes[$theme];
helper(['persons']);
$persons = [];
construct_episode_person_array(
$this->episode->episode_persons,
$persons
);
constructs_podcast_person_array(
$this->podcast->podcast_persons,
$persons
);
$data = [
'podcast' => $this->podcast,
'episode' => $this->episode,
'persons' => $persons,
'theme' => $theme,
];
$secondsToNextUnpublishedEpisode = $episodeModel->getSecondsToNextUnpublishedEpisode(
$this->podcast->id
);
// The page cache is set to a decade so it is deleted manually upon podcast update
return view('embeddable_player', $data, [
'cache' => $secondsToNextUnpublishedEpisode
? $secondsToNextUnpublishedEpisode
: DECADE,
'cache_name' => $cacheName,
]);
}
return $cachedView;
}
}
......@@ -109,48 +109,12 @@ class Podcast extends BaseController
]);
}
helper(['persons']);
$persons = [];
foreach ($this->podcast->podcast_persons as $podcastPerson) {
if (array_key_exists($podcastPerson->person->id, $persons)) {
$persons[$podcastPerson->person->id]['roles'] .=
empty($podcastPerson->person_group) ||
empty($podcastPerson->person_role)
? ''
: (empty(
$persons[$podcastPerson->person->id][
'roles'
]
)
? ''
: ', ') .
lang(
'PersonsTaxonomy.persons.' .
$podcastPerson->person_group .
'.roles.' .
$podcastPerson->person_role .
'.label'
);
} else {
$persons[$podcastPerson->person->id] = [
'full_name' => $podcastPerson->person->full_name,
'information_url' =>
$podcastPerson->person->information_url,
'thumbnail_url' =>
$podcastPerson->person->image->thumbnail_url,
'roles' =>
empty($podcastPerson->person_group) ||
empty($podcastPerson->person_role)
? ''
: lang(
'PersonsTaxonomy.persons.' .
$podcastPerson->person_group .
'.roles.' .
$podcastPerson->person_role .
'.label'
),
];
}
}
constructs_podcast_person_array(
$this->podcast->podcast_persons,
$persons
);
$data = [
'podcast' => $this->podcast,
......
......@@ -40,6 +40,11 @@ class AddPodcastsPlatforms extends Migration
'constraint' => 1,
'default' => 0,
],
'is_on_embeddable_player' => [
'type' => 'TINYINT',
'constraint' => 1,
'default' => 0,
],
]);
$this->forge->addPrimaryKey(['podcast_id', 'platform_slug']);
......
......@@ -94,6 +94,13 @@ class Episode extends Entity
*/
protected $description;
/**
* The embeddable player URL
*
* @var string
*/
protected $embeddable_player;
/**
* @var string
*/
......@@ -421,6 +428,24 @@ class Episode extends Entity
);
}
public function getEmbeddablePlayer($theme = null)
{
return base_url(
$theme
? route_to(
'embeddable-player-theme',
$this->getPodcast()->name,
$this->attributes['slug'],
$theme
)
: route_to(
'embeddable-player',
$this->getPodcast()->name,
$this->attributes['slug']
)
);
}
public function setGuid(string $guid)
{
return $this->attributes['guid'] = empty($guid)
......
......@@ -21,5 +21,6 @@ class Platform extends Entity
'link_url' => '?string',
'link_content' => '?string',
'is_visible' => '?boolean',
'is_on_embeddable_player' => '?boolean',
];
}
......@@ -324,6 +324,24 @@ class Podcast extends Entity
return $this->podcastingPlatforms;
}
/**
* Returns true if the podcast has podcasting platform links
*/
public function getHasPodcastingPlatforms()
{
if (empty($this->id)) {
throw new \RuntimeException(
'Podcast must be created before getting podcasting platform.'
);
}
foreach ($this->getPodcastingPlatforms() as $podcastingPlatform) {
if ($podcastingPlatform->is_on_embeddable_player) {
return true;
}
}
return false;
}
/**
* Returns the podcast's social platform links
*
......@@ -347,6 +365,24 @@ class Podcast extends Entity
return $this->socialPlatforms;
}
/**
* Returns true if the podcast has social platform links
*/
public function getHasSocialPlatforms()
{
if (empty($this->id)) {
throw new \RuntimeException(
'Podcast must be created before getting social platform.'
);
}
foreach ($this->getSocialPlatforms() as $socialPlatform) {
if ($socialPlatform->is_on_embeddable_player) {
return true;
}
}
return false;
}
/**
* Returns the podcast's funding platform links
*
......@@ -370,6 +406,24 @@ class Podcast extends Entity
return $this->fundingPlatforms;
}
/**
* Returns true if the podcast has social platform links
*/
public function getHasFundingPlatforms()
{
if (empty($this->id)) {
throw new \RuntimeException(
'Podcast must be created before getting Funding platform.'
);
}
foreach ($this->getFundingPlatforms() as $fundingPlatform) {
if ($fundingPlatform->is_on_embeddable_player) {
return true;
}
}
return false;
}
public function getOtherCategories()
{
if (empty($this->id)) {
......
......@@ -111,9 +111,6 @@ function set_user_session_player()
$session->start();
if (!$session->has('player')) {
$session = \Config\Services::session();
$session->start();
$playerFound = null;
$userAgent = $_SERVER['HTTP_USER_AGENT'];
......
......@@ -384,29 +384,12 @@ if (!function_exists('location_link')) {
$locationOsmid,
$class = ''
) {
$link = null;
$link = '';
if (!empty($locationName)) {
$uri = '';
if (!empty($locationOsmid)) {
$uri =
'https://www.openstreetmap.org/' .
['N' => 'node', 'W' => 'way', 'R' => 'relation'][
substr($locationOsmid, 0, 1)
] .
'/' .
substr($locationOsmid, 1);
} elseif (!empty($locationGeo)) {
$uri =
'https://www.openstreetmap.org/#map=17/' .
str_replace(',', '/', substr($locationGeo, 4));
} else {
$uri =
'https://www.openstreetmap.org/search?query=' .
urlencode($locationName);
}
$link = button(
$locationName,
$uri,
location_url($locationName, $locationGeo, $locationOsmid),
[
'variant' => 'default',
'size' => 'small',
......@@ -421,6 +404,7 @@ if (!function_exists('location_link')) {
]
);
}
return $link;
}
}
......
<?php
/**
* @copyright 2021 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
/**
* Fetches persons from an episode
*
* @param array $podcast_persons
* @param array &$persons
*/
function constructs_podcast_person_array($podcast_persons, &$persons)
{
foreach ($podcast_persons as $podcastPerson) {
if (array_key_exists($podcastPerson->person->id, $persons)) {
$persons[$podcastPerson->person->id]['roles'] .=
empty($podcastPerson->person_group) ||
empty($podcastPerson->person_role)
? ''
: (empty($persons[$podcastPerson->person->id]['roles'])
? ''
: ', ') .
lang(
'PersonsTaxonomy.persons.' .
$podcastPerson->person_group .
'.roles.' .
$podcastPerson->person_role .
'.label'
);
} else {
$persons[$podcastPerson->person->id] = [
'full_name' => $podcastPerson->person->full_name,
'information_url' => $podcastPerson->person->information_url,
'thumbnail_url' => $podcastPerson->person->image->thumbnail_url,
'roles' =>
empty($podcastPerson->person_group) ||
empty($podcastPerson->person_role)
? ''
: lang(
'PersonsTaxonomy.persons.' .
$podcastPerson->person_group .
'.roles.' .
$podcastPerson->person_role .
'.label'
),
];
}
}
}
/**
* Fetches persons from an episode
*
* @param array $episode_persons
* @param array &$persons
*/
function construct_episode_person_array($episode_persons, &$persons)
{
foreach ($episode_persons as $episodePerson) {
if (array_key_exists($episodePerson->person->id, $persons)) {
$persons[$episodePerson->person->id]['roles'] .=
empty($episodePerson->person_group) ||
empty($episodePerson->person_role)
? ''
: (empty($persons[$episodePerson->person->id]['roles'])
? ''
: ', ') .
lang(
'PersonsTaxonomy.persons.' .
$episodePerson->person_group .
'.roles.' .
$episodePerson->person_role .
'.label'
);
} else {
$persons[$episodePerson->person->id] = [
'full_name' => $episodePerson->person->full_name,
'information_url' => $episodePerson->person->information_url,
'thumbnail_url' => $episodePerson->person->image->thumbnail_url,
'roles' =>
empty($episodePerson->person_group) ||
empty($episodePerson->person_role)
? ''
: lang(
'PersonsTaxonomy.persons.' .
$episodePerson->person_group .
'.roles.' .
$episodePerson->person_role .
'.label'
),
];
}
}
}
......@@ -38,3 +38,39 @@ if (!function_exists('current_season_url')) {
return current_url() . $season_query_string;
}
}
if (!function_exists('location_url')) {
/**
* Returns URL to display from location info
*
* @param string $locationName
* @param string $locationGeo
* @param string $locationOsmid
*
* @return string
*/
function location_url($locationName, $locationGeo, $locationOsmid)
{
$uri = '';
if (!empty($locationOsmid)) {
$uri =
'https://www.openstreetmap.org/' .
['N' => 'node', 'W' => 'way', 'R' => 'relation'][
substr($locationOsmid, 0, 1)
] .
'/' .
substr($locationOsmid, 1);
} elseif (!empty($locationGeo)) {
$uri =
'https://www.openstreetmap.org/#map=17/' .
str_replace(',', '/', substr($locationGeo, 4));
} elseif (!empty($locationName)) {
$uri =
'https://www.openstreetmap.org/search?query=' .
urlencode($locationName);
}
return $uri;
}
}
......@@ -32,4 +32,5 @@ return [
'listening-time' => 'listening time',
'time-periods' => 'time periods',
'soundbites' => 'soundbites',
'embeddable-player' => 'embeddable player',
];
......@@ -109,4 +109,16 @@ return [
'Click while playing to get current position, click again to get duration.',
'submit_edit' => 'Save all soundbites',
],
'embeddable_player' => [