249 lines
6.6 KiB
PHP
Executable File
249 lines
6.6 KiB
PHP
Executable File
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use App\Models\Traits\Feed\AnalysesFeed;
|
|
use App\Models\Traits\HasUrl;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Support\Facades\Storage;
|
|
|
|
class Feed extends Model
|
|
{
|
|
use AnalysesFeed;
|
|
use HasUrl;
|
|
|
|
// -------------------------------------------------------------------------
|
|
// ----| Properties |-------------------------------------------------------
|
|
// -------------------------------------------------------------------------
|
|
|
|
/**
|
|
* The attributes that are mass assignable.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $fillable = [
|
|
'url',
|
|
];
|
|
|
|
/**
|
|
* The accessors to append to the model's array form.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $appends = [
|
|
'favicon',
|
|
'is_ignored',
|
|
'ascii_url',
|
|
];
|
|
|
|
/**
|
|
* The attributes that should be mutated to dates.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $dates = [
|
|
'checked_at',
|
|
];
|
|
|
|
/**
|
|
* Hash of URL.
|
|
*
|
|
* @var string
|
|
*/
|
|
private $hash;
|
|
|
|
/**
|
|
* Path to storage.
|
|
*
|
|
* @var string
|
|
*/
|
|
private $storagePath;
|
|
|
|
// -------------------------------------------------------------------------
|
|
// ----| Attributes |-------------------------------------------------------
|
|
// -------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Return feed's title, or url if empty.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getTitleAttribute()
|
|
{
|
|
if (!empty($this->attributes['title'])) {
|
|
return $this->attributes['title'];
|
|
}
|
|
|
|
return $this->url;
|
|
}
|
|
|
|
/**
|
|
* Return full URL to favicon.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getFaviconAttribute()
|
|
{
|
|
if (!empty($this->attributes['favicon_path'])) {
|
|
return Storage::url($this->attributes['favicon_path']);
|
|
}
|
|
|
|
$document = $this->documents()->first();
|
|
|
|
if ($document) {
|
|
return $document->favicon;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Return a boolean value indicating if auth'ed user has ignored this feed.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function getIsIgnoredAttribute()
|
|
{
|
|
if (auth()->user()) {
|
|
return $this->ignored->firstWhere('user_id', auth()->user()->id) !== null;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// ----| Relations |--------------------------------------------------------
|
|
// -------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Documents referenced by this feed.
|
|
*
|
|
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
|
*/
|
|
public function documents()
|
|
{
|
|
return $this->belongsToMany(Document::class, 'document_feeds');
|
|
}
|
|
|
|
/**
|
|
* Feed items referenced by this feed.
|
|
*
|
|
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
|
*/
|
|
public function feedItems()
|
|
{
|
|
return $this->belongsToMany(FeedItem::class, 'feed_feed_items');
|
|
}
|
|
|
|
/**
|
|
* Associated unread feed items.
|
|
*
|
|
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
|
*/
|
|
public function feedItemStates()
|
|
{
|
|
return $this->hasMany(FeedItemState::class);
|
|
}
|
|
|
|
/**
|
|
* Users ignoring this feed.
|
|
*
|
|
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
|
*/
|
|
public function ignored()
|
|
{
|
|
return $this->hasMany(IgnoredFeed::class);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// ----| Scopes |-----------------------------------------------------------
|
|
// -------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Scope a query to only include feeds than were updated before specifed
|
|
* date.
|
|
*
|
|
* @param \Illuminate\Database\Eloquent\Builder $query
|
|
* @param mixed $date
|
|
*
|
|
* @return \Illuminate\Database\Eloquent\Builder
|
|
*/
|
|
public function scopeNeedingUpdate($query, $date)
|
|
{
|
|
return $query->where('checked_at', '<', $date)->orWhereNull('checked_at');
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// ----| Methods |----------------------------------------------------------
|
|
// -------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Build a hash for document's URL. Used to build path for storing assets
|
|
* related to this document. It doesn't need to provide a "secure" hash like
|
|
* for a password, so we're just going to use md5.
|
|
*
|
|
* The purpose of this hash is multiple:
|
|
*
|
|
* - Maximum number of folders in each level is 16, and hierarchy is 32
|
|
* folders deep, so it can be handled by any file system without problem
|
|
* - As it is based on document's URL and date of creation in Cyca, files
|
|
* cannot be "stolen" by direct access (Cyca couldn't and shouldn't be used
|
|
* as a favicon repository used by everyone, for instance)
|
|
* - It avoids issues with intl domain names or special chars in URLs
|
|
* - On the other side, it would be easy for Cyca to quickly know where to
|
|
* store assets for that particular document, and we can store all assets
|
|
* related to that document in the same folder
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getHash()
|
|
{
|
|
if (empty($this->hash)) {
|
|
$this->hash = md5($this->url . $this->created_at);
|
|
}
|
|
|
|
return $this->hash;
|
|
}
|
|
|
|
/**
|
|
* Return path to root folder for storing this document's assets. This path
|
|
* can then be used to store and retrieve files using the Storage facade, so
|
|
* it does not return the full path of a directory rather than the path
|
|
* related to configured storage disk.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getStoragePath()
|
|
{
|
|
if (empty($this->storagePath)) {
|
|
$hash = $this->getHash();
|
|
|
|
$this->storagePath = 'public/feeds/' . implode('/', str_split(($hash)));
|
|
}
|
|
|
|
return $this->storagePath;
|
|
}
|
|
|
|
/**
|
|
* Return a boolean value indicating if this feed still belongs to any
|
|
* document.
|
|
*
|
|
* @return true
|
|
*/
|
|
public function isOrphan()
|
|
{
|
|
return $this->documents()->count() === 0;
|
|
}
|
|
|
|
/**
|
|
* Return a boolean value indicating if this feed was orphan for
|
|
* specified days.
|
|
*
|
|
* @return true
|
|
*/
|
|
public function wasOrphanFor(int $days)
|
|
{
|
|
return !empty($this->checked_at) && $this->checked_at->addDays($days)->lt(now());
|
|
}
|
|
}
|