289 lines
8.1 KiB
PHP
Executable File
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());
|
|
}
|
|
}
|