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