richard
/
cyca
Archived
1
0
Fork 0
This repository has been archived on 2024-05-04. You can view files and clone it, but cannot push or open issues or pull requests.
cyca/app/Models/Feed.php

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