ORIENT/plugins/vdlp/rssfetcher/classes/RssFetcher.php

179 lines
4.8 KiB
PHP

<?php
declare(strict_types=1);
namespace Vdlp\RssFetcher\Classes;
use Carbon\Carbon;
use Exception;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Support\Collection;
use Laminas\Feed\Reader\Entry\EntryInterface;
use Laminas\Feed\Reader\Entry\Rss;
use Laminas\Feed\Reader\Feed\FeedInterface;
use Laminas\Feed\Reader\Reader;
use October\Rain\Support\Traits\Singleton;
use Psr\Log\LoggerInterface;
use stdClass;
use Throwable;
use Vdlp\RssFetcher\Models\Item;
use Vdlp\RssFetcher\Models\Source;
final class RssFetcher
{
use Singleton;
/**
* @var LoggerInterface
*/
private $log;
/**
* @var Dispatcher
*/
private $dispatcher;
/**
* {@inheritDoc}
*/
protected function init(): void
{
$this->log = resolve(LoggerInterface::class);
$this->dispatcher = resolve(Dispatcher::class);
}
/**
* Run the fetching logic.
*
* @param int|null $sourceId
*/
public function fetch(int $sourceId = null): void
{
$sources = $this->getSourceCollection($sourceId);
$sources->each(function (Source $source) {
try {
$this->fetchSource($source);
} catch (Throwable $e) {
$this->log->error($e);
}
});
}
/**
* @param Source $source
* @throws Exception
*/
private function fetchSource(Source $source): void
{
$channel = Reader::import($source->getAttribute('source_url'));
$maxItems = $source->getAttribute('max_items');
$itemCount = 0;
/** @var Rss $item */
foreach ($channel as $item) {
++$itemCount;
$dateCreated = $item->getDateCreated();
$title = $item->getTitle();
$this->dispatcher->fire('vdlp.rssfetcher.item.processTitle', [&$title]);
$content = $item->getContent();
$this->dispatcher->fire('vdlp.rssfetcher.item.processContent', [&$content]);
$link = $item->getLink();
$this->dispatcher->fire('vdlp.rssfetcher.item.processLink', [&$link]);
$attributes = [
'item_id' => $item->getId(),
'source_id' => $source->getAttribute('id'),
'title' => $title,
'link' => $link,
'description' => $content,
'category' => implode(', ', $item->getCategories()->getValues()),
'comments' => $item->getCommentLink(),
'pub_date' => $dateCreated !== null ? $item->getDateCreated()->format('Y-m-d H:i:s') : null,
'is_published' => (bool) $source->getAttribute('publish_new_items'),
'author' => $this->getAuthor($channel, $item),
];
$enclosure = $item->getEnclosure();
if ($enclosure instanceof stdClass) {
$attributes['enclosure_url'] = $enclosure->url ?? null;
$attributes['enclosure_length'] = $enclosure->length ?? null;
$attributes['enclosure_type'] = $enclosure->type ?? null;
}
try {
Item::query()->updateOrCreate(
[
'source_id' => $source->getAttribute('id'),
'item_id' => $item->getId(),
],
$attributes
);
} catch (Throwable $e) {
$this->log->error($e);
}
if ($maxItems > 0 && $itemCount >= $maxItems) {
break;
}
}
$source->setAttribute('fetched_at', Carbon::now());
$source->save();
}
/**
* @param FeedInterface $feed
* @param EntryInterface $entry
* @return string|null
*/
private function getAuthor(FeedInterface $feed, EntryInterface $entry): ?string
{
$result = null;
$author = $entry->getAuthor();
if ($author === null || empty($author)) {
$author = $feed->getAuthor();
}
if (is_array($author)) {
$result = $author['name'] ?? null;
} elseif (is_string($author)) {
$result = $author;
}
return $result;
}
/**
* @param int|null $sourceId
* @return Collection
*/
private function getSourceCollection(int $sourceId = null): Collection
{
$sources = new Collection();
if ($sourceId !== null) {
$source = Source::query()
->where('id', '=', $sourceId)
->where('is_enabled', '=', true)
->first();
if ($source) {
$sources = new Collection([$source]);
}
} else {
$sources = Source::query()
->where('is_enabled', '=', true)
->get();
}
return $sources;
}
}