266 lines
9.1 KiB
PHP
266 lines
9.1 KiB
PHP
|
|
<?php
|
||
|
|
/*
|
||
|
|
* Copyright (C) 2024 Xibo Signage Ltd
|
||
|
|
*
|
||
|
|
* Xibo - Digital Signage - https://xibosignage.com
|
||
|
|
*
|
||
|
|
* This file is part of Xibo.
|
||
|
|
*
|
||
|
|
* Xibo is free software: you can redistribute it and/or modify
|
||
|
|
* it under the terms of the GNU Affero General Public License as published by
|
||
|
|
* the Free Software Foundation, either version 3 of the License, or
|
||
|
|
* any later version.
|
||
|
|
*
|
||
|
|
* Xibo is distributed in the hope that it will be useful,
|
||
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
|
* GNU Affero General Public License for more details.
|
||
|
|
*
|
||
|
|
* You should have received a copy of the GNU Affero General Public License
|
||
|
|
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||
|
|
*/
|
||
|
|
|
||
|
|
namespace Xibo\Connector;
|
||
|
|
|
||
|
|
use Carbon\Carbon;
|
||
|
|
use GuzzleHttp\Client;
|
||
|
|
use Illuminate\Support\Str;
|
||
|
|
use Parsedown;
|
||
|
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||
|
|
use Xibo\Entity\SearchResult;
|
||
|
|
use Xibo\Event\TemplateProviderEvent;
|
||
|
|
use Xibo\Event\TemplateProviderImportEvent;
|
||
|
|
use Xibo\Event\TemplateProviderListEvent;
|
||
|
|
use Xibo\Support\Sanitizer\SanitizerInterface;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* XiboExchangeConnector
|
||
|
|
* ---------------------
|
||
|
|
* This connector will consume the Xibo Layout Exchange API and offer pre-built templates for selection when adding
|
||
|
|
* a new layout.
|
||
|
|
*/
|
||
|
|
class XiboExchangeConnector implements ConnectorInterface
|
||
|
|
{
|
||
|
|
use ConnectorTrait;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param EventDispatcherInterface $dispatcher
|
||
|
|
* @return ConnectorInterface
|
||
|
|
*/
|
||
|
|
public function registerWithDispatcher(EventDispatcherInterface $dispatcher): ConnectorInterface
|
||
|
|
{
|
||
|
|
$dispatcher->addListener('connector.provider.template', [$this, 'onTemplateProvider']);
|
||
|
|
$dispatcher->addListener('connector.provider.template.import', [$this, 'onTemplateProviderImport']);
|
||
|
|
$dispatcher->addListener('connector.provider.template.list', [$this, 'onTemplateList']);
|
||
|
|
return $this;
|
||
|
|
}
|
||
|
|
|
||
|
|
public function getSourceName(): string
|
||
|
|
{
|
||
|
|
return 'xibo-exchange';
|
||
|
|
}
|
||
|
|
|
||
|
|
public function getTitle(): string
|
||
|
|
{
|
||
|
|
return 'Xibo Exchange';
|
||
|
|
}
|
||
|
|
|
||
|
|
public function getDescription(): string
|
||
|
|
{
|
||
|
|
return 'Show Templates provided by the Xibo Exchange in the add new Layout form.';
|
||
|
|
}
|
||
|
|
|
||
|
|
public function getThumbnail(): string
|
||
|
|
{
|
||
|
|
return 'theme/default/img/connectors/xibo-exchange.png';
|
||
|
|
}
|
||
|
|
|
||
|
|
public function getSettingsFormTwig(): string
|
||
|
|
{
|
||
|
|
return 'connector-form-edit';
|
||
|
|
}
|
||
|
|
|
||
|
|
public function processSettingsForm(SanitizerInterface $params, array $settings): array
|
||
|
|
{
|
||
|
|
return $settings;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get layouts available in Layout exchange and add them to the results
|
||
|
|
* This is triggered in Template Controller search function
|
||
|
|
* @param TemplateProviderEvent $event
|
||
|
|
*/
|
||
|
|
public function onTemplateProvider(TemplateProviderEvent $event)
|
||
|
|
{
|
||
|
|
$this->getLogger()->debug('XiboExchangeConnector: onTemplateProvider');
|
||
|
|
|
||
|
|
// Get a cache of the layouts.json file, or request one from download.
|
||
|
|
$uri = 'https://download.xibosignage.com/layouts_v4_1.json';
|
||
|
|
$key = md5($uri);
|
||
|
|
$cache = $this->getPool()->getItem($key);
|
||
|
|
$body = $cache->get();
|
||
|
|
|
||
|
|
if ($cache->isMiss()) {
|
||
|
|
$this->getLogger()->debug('onTemplateProvider: cache miss, generating.');
|
||
|
|
|
||
|
|
// Make the request
|
||
|
|
$request = $this->getClient()->request('GET', $uri);
|
||
|
|
|
||
|
|
$body = $request->getBody()->getContents();
|
||
|
|
if (empty($body)) {
|
||
|
|
$this->getLogger()->debug('onTemplateProvider: Empty body');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
$body = json_decode($body);
|
||
|
|
if ($body === null || $body === false) {
|
||
|
|
$this->getLogger()->debug('onTemplateProvider: non-json body or empty body returned.');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Cache for next time
|
||
|
|
$cache->set($body);
|
||
|
|
$cache->expiresAt(Carbon::now()->addHours(24));
|
||
|
|
$this->getPool()->saveDeferred($cache);
|
||
|
|
} else {
|
||
|
|
$this->getLogger()->debug('onTemplateProvider: serving from cache.');
|
||
|
|
}
|
||
|
|
|
||
|
|
// We have the whole file locally, so handle paging
|
||
|
|
$start = $event->getStart();
|
||
|
|
$perPage = $event->getLength();
|
||
|
|
|
||
|
|
// Create a provider to add to each search result
|
||
|
|
$providerDetails = new ProviderDetails();
|
||
|
|
$providerDetails->id = $this->getSourceName();
|
||
|
|
$providerDetails->logoUrl = $this->getThumbnail();
|
||
|
|
$providerDetails->iconUrl = $this->getThumbnail();
|
||
|
|
$providerDetails->message = $this->getTitle();
|
||
|
|
$providerDetails->backgroundColor = '';
|
||
|
|
|
||
|
|
// parse the templates based on orientation filter.
|
||
|
|
if (!empty($event->getOrientation())) {
|
||
|
|
$templates = [];
|
||
|
|
foreach ($body as $template) {
|
||
|
|
if (!empty($template->orientation) &&
|
||
|
|
Str::contains($template->orientation, $event->getOrientation(), true)
|
||
|
|
) {
|
||
|
|
$templates[] = $template;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
$templates = $body;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Filter the body based on search param.
|
||
|
|
if (!empty($event->getSearch())) {
|
||
|
|
$filtered = [];
|
||
|
|
foreach ($templates as $template) {
|
||
|
|
if (Str::contains($template->title, $event->getSearch(), true)) {
|
||
|
|
$filtered[] = $template;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!empty($template->description) &&
|
||
|
|
Str::contains($template->description, $event->getSearch(), true)
|
||
|
|
) {
|
||
|
|
$filtered[] = $template;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (property_exists($template, 'tags') && count($template->tags) > 0) {
|
||
|
|
if (in_array($event->getSearch(), $template->tags)) {
|
||
|
|
$filtered[] = $template;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
$filtered = $templates;
|
||
|
|
}
|
||
|
|
|
||
|
|
// sort, featured first, otherwise alphabetically.
|
||
|
|
usort($filtered, function ($a, $b) {
|
||
|
|
if (property_exists($a, 'isFeatured') && property_exists($b, 'isFeatured')) {
|
||
|
|
return $b->isFeatured <=> $a->isFeatured;
|
||
|
|
} else {
|
||
|
|
return $a->title <=> $b->title;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
for ($i = $start; $i < ($start + $perPage - 1) && $i < count($filtered); $i++) {
|
||
|
|
$searchResult = $this->createSearchResult($filtered[$i]);
|
||
|
|
$searchResult->provider = $providerDetails;
|
||
|
|
$event->addResult($searchResult);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* When remote source Template is selected on Layout add,
|
||
|
|
* we need to get the zip file from specified url and import it to the CMS
|
||
|
|
* imported Layout object is set on the Event and retrieved later in Layout controller
|
||
|
|
* @param TemplateProviderImportEvent $event
|
||
|
|
*/
|
||
|
|
public function onTemplateProviderImport(TemplateProviderImportEvent $event)
|
||
|
|
{
|
||
|
|
$downloadUrl = $event->getDownloadUrl();
|
||
|
|
$client = new Client();
|
||
|
|
$tempFile = $event->getLibraryLocation() . 'temp/' . $event->getFileName();
|
||
|
|
$client->request('GET', $downloadUrl, ['sink' => $tempFile]);
|
||
|
|
$event->setFilePath($tempFile);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param $template
|
||
|
|
* @return SearchResult
|
||
|
|
*/
|
||
|
|
private function createSearchResult($template) : SearchResult
|
||
|
|
{
|
||
|
|
$searchResult = new SearchResult();
|
||
|
|
$searchResult->id = $template->fileName;
|
||
|
|
$searchResult->source = 'remote';
|
||
|
|
$searchResult->title = $template->title;
|
||
|
|
$searchResult->description = empty($template->description)
|
||
|
|
? null
|
||
|
|
: Parsedown::instance()->setSafeMode(true)->line($template->description);
|
||
|
|
|
||
|
|
// Optional data
|
||
|
|
if (property_exists($template, 'tags') && count($template->tags) > 0) {
|
||
|
|
$searchResult->tags = $template->tags;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (property_exists($template, 'orientation')) {
|
||
|
|
$searchResult->orientation = $template->orientation;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (property_exists($template, 'isFeatured')) {
|
||
|
|
$searchResult->isFeatured = $template->isFeatured;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Thumbnail
|
||
|
|
$searchResult->thumbnail = $template->thumbnailUrl;
|
||
|
|
$searchResult->download = $template->downloadUrl;
|
||
|
|
return $searchResult;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Add this connector to the list of providers.
|
||
|
|
* @param \Xibo\Event\TemplateProviderListEvent $event
|
||
|
|
* @return void
|
||
|
|
*/
|
||
|
|
public function onTemplateList(TemplateProviderListEvent $event): void
|
||
|
|
{
|
||
|
|
$this->getLogger()->debug('onTemplateList:event');
|
||
|
|
|
||
|
|
$providerDetails = new ProviderDetails();
|
||
|
|
$providerDetails->id = $this->getSourceName();
|
||
|
|
$providerDetails->link = 'https://xibosignage.com';
|
||
|
|
$providerDetails->logoUrl = $this->getThumbnail();
|
||
|
|
$providerDetails->iconUrl = 'exchange-alt';
|
||
|
|
$providerDetails->message = $this->getTitle();
|
||
|
|
$providerDetails->backgroundColor = '';
|
||
|
|
$providerDetails->mediaTypes = ['xlf'];
|
||
|
|
|
||
|
|
$event->addProvider($providerDetails);
|
||
|
|
}
|
||
|
|
}
|