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/Document.php

289 lines
8.1 KiB
PHP
Executable File

<?php
namespace App\Models;
use App\Models\Traits\Document\AnalysesDocument;
use App\Models\Traits\HasUrl;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Storage;
class Document extends Model
{
use AnalysesDocument;
use HasUrl;
// -------------------------------------------------------------------------
// ----| Properties |-------------------------------------------------------
// -------------------------------------------------------------------------
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'url',
];
/**
* Array of folders containing this document. User will be specified in the
* findDupplicatesFor method.
*/
protected $dupplicates = [];
/**
* The accessors to append to the model's array form.
*
* @var array
*/
protected $appends = [
'dupplicates',
'favicon',
'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 document's title, or url if empty.
*
* @return string
*/
public function getTitleAttribute()
{
if (!empty($this->attributes['title'])) {
return $this->attributes['title'];
}
return $this->url;
}
/**
* Return array of folders containing a bookmark to this document.
*
* @return array
*/
public function getDupplicatesAttribute()
{
return $this->dupplicates;
}
/**
* Return full URL to favicon.
*
* @return string
*/
public function getFaviconAttribute()
{
if (empty($this->attributes['favicon_path']) || !Storage::exists($this->attributes['favicon_path'])) {
if ($this->mimetype) {
$filename = str_replace('/', '-', $this->mimetype);
$path = sprintf('images/icons/mimetypes/%s.svg', $filename);
if (file_exists(realpath(public_path($path)))) {
return asset($path);
}
}
return asset('images/icons/mimetypes/unknown.svg');
}
return Storage::url($this->attributes['favicon_path']);
}
public function getHttpStatusTextAttribute()
{
if (!empty($this->attributes['http_status_text'])) {
return $this->attributes['http_status_text'];
}
if (empty($this->http_status_code)) {
if (empty($this->checked_at)) {
return __('Cyca did not check this document yet');
}
return __('Cyca could not reach this document URL');
}
}
// -------------------------------------------------------------------------
// ----| Relations |--------------------------------------------------------
// -------------------------------------------------------------------------
/**
* Bookmarks referencing this document.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function bookmark()
{
return $this->belongsToMany(Document::class, 'bookmarks')->using(Bookmark::class)->as('bookmark')->withPivot(['initial_url', 'created_at', 'updated_at', 'visits']);
}
/**
* Folders referencing this document.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function folders()
{
return $this->belongsToMany(Folder::class, 'bookmarks');
}
/**
* Feeds referenced by this document.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function feeds()
{
return $this->belongsToMany(Feed::class, 'document_feeds');
}
/**
* Associated feed items states.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function feedItemStates()
{
return $this->hasMany(FeedItemState::class);
}
// -------------------------------------------------------------------------
// ----| Scopes |-----------------------------------------------------------
// -------------------------------------------------------------------------
/**
* Scope a query to only include documents 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 |----------------------------------------------------------
// -------------------------------------------------------------------------
/**
* Find dupplicates of this document in specified user's folders.
*/
public function findDupplicatesFor(User $user)
{
$ids = $user->documents()->where('document_id', $this->id)->select('folder_id')->pluck('folder_id');
$folders = Folder::find($ids);
foreach ($folders as $folder) {
$this->dupplicates[] = [
'id' => $folder->id,
'group_id' => $folder->group->id,
'breadcrumbs' => $folder->breadcrumbs,
];
}
return $this->dupplicates;
}
/**
* 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/documents/' . implode('/', str_split(($hash)));
}
return $this->storagePath;
}
/**
* Return a boolean value indicating if this document still belongs to any
* folder.
*
* @return true
*/
public function isOrphan()
{
return $this->folders()->count() === 0;
}
/**
* Return a boolean value indicating if this document was orphan for
* specified days.
*
* @return true
*/
public function wasOrphanFor(int $days)
{
return !empty($this->checked_at) && $this->checked_at->addDays($days)->lt(now());
}
}