Files
Cloud-CMS/lib/Connector/XiboExchangeConnector.php
Matt Batchelder 05ce0da296 Initial Upload
2025-12-02 10:32:59 -05:00

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);
}
}