Move models to their own subdirectory
This commit is contained in:
parent
2c5b0f3ab5
commit
2a0d188313
67 changed files with 82 additions and 86 deletions
144
app/Models/Article.php
Normal file
144
app/Models/Article.php
Normal file
|
@ -0,0 +1,144 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Cviebrock\EloquentSluggable\Sluggable;
|
||||
use League\CommonMark\CommonMarkConverter;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class Article extends Model
|
||||
{
|
||||
use Sluggable;
|
||||
use SoftDeletes;
|
||||
|
||||
/**
|
||||
* The attributes that should be mutated to dates.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dates = ['created_at', 'updated_at', 'deleted_at'];
|
||||
|
||||
/**
|
||||
* The database table used by the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'articles';
|
||||
|
||||
/**
|
||||
* Return the sluggable configuration array for this model.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function sluggable()
|
||||
{
|
||||
return [
|
||||
'titleurl' => [
|
||||
'source' => 'title',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* We shall set a blacklist of non-modifiable model attributes.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $guarded = ['id'];
|
||||
|
||||
/**
|
||||
* Process the article for display.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getHtmlAttribute()
|
||||
{
|
||||
$markdown = new CommonMarkConverter();
|
||||
$html = $markdown->convertToHtml($this->main);
|
||||
// changes <pre><code>[lang] ~> <pre><code data-language="lang">
|
||||
$match = '/<pre><code>\[(.*)\]\n/';
|
||||
$replace = '<pre><code class="language-$1">';
|
||||
$text = preg_replace($match, $replace, $html);
|
||||
$default = preg_replace('/<pre><code>/', '<pre><code class="language-markdown">', $text);
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert updated_at to W3C time format.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getW3cTimeAttribute()
|
||||
{
|
||||
return $this->updated_at->toW3CString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert updated_at to a tooltip appropriate format.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTooltipTimeAttribute()
|
||||
{
|
||||
return $this->updated_at->toRFC850String();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert updated_at to a human readable format.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getHumanTimeAttribute()
|
||||
{
|
||||
return $this->updated_at->diffForHumans();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pubdate value for RSS feeds.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPubdateAttribute()
|
||||
{
|
||||
return $this->updated_at->toRSSString();
|
||||
}
|
||||
|
||||
/**
|
||||
* A link to the article, i.e. `/blog/1999/12/25/merry-christmas`.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLinkAttribute()
|
||||
{
|
||||
return '/blog/' . $this->updated_at->year . '/' . $this->updated_at->format('m') . '/' . $this->titleurl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope a query to only include articles from a particular year/month.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function scopeDate($query, int $year = null, int $month = null)
|
||||
{
|
||||
if ($year == null) {
|
||||
return $query;
|
||||
}
|
||||
$start = $year . '-01-01 00:00:00';
|
||||
$end = ($year + 1) . '-01-01 00:00:00';
|
||||
if (($month !== null) && ($month !== 12)) {
|
||||
$start = $year . '-' . $month . '-01 00:00:00';
|
||||
$end = $year . '-' . ($month + 1) . '-01 00:00:00';
|
||||
}
|
||||
if ($month === 12) {
|
||||
$start = $year . '-12-01 00:00:00';
|
||||
$end = ($year + 1) . '-01-01 00:00:00';
|
||||
}
|
||||
|
||||
return $query->where([
|
||||
['updated_at', '>=', $start],
|
||||
['updated_at', '<', $end],
|
||||
]);
|
||||
}
|
||||
}
|
40
app/Models/Bookmark.php
Normal file
40
app/Models/Bookmark.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Bookmark extends Model
|
||||
{
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = ['url', 'name', 'content'];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'syndicates' => 'array',
|
||||
];
|
||||
|
||||
/**
|
||||
* The tags that belong to the bookmark.
|
||||
*/
|
||||
public function tags()
|
||||
{
|
||||
return $this->belongsToMany('App\Models\Tag');
|
||||
}
|
||||
|
||||
/**
|
||||
* The full url of a bookmark.
|
||||
*/
|
||||
public function getLongurlAttribute()
|
||||
{
|
||||
return config('app.url') . '/bookmarks/' . $this->id;
|
||||
}
|
||||
}
|
22
app/Models/Contact.php
Normal file
22
app/Models/Contact.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Contact extends Model
|
||||
{
|
||||
/**
|
||||
* The database table used by the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'contacts';
|
||||
|
||||
/**
|
||||
* We shall guard against mass-migration.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = ['nick', 'name', 'homepage', 'twitter', 'facebook'];
|
||||
}
|
44
app/Models/Like.php
Normal file
44
app/Models/Like.php
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Mf2;
|
||||
use HTMLPurifier;
|
||||
use HTMLPurifier_Config;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Like extends Model
|
||||
{
|
||||
protected $fillable = ['url'];
|
||||
|
||||
public function setUrlAttribute($value)
|
||||
{
|
||||
$this->attributes['url'] = normalize_url($value);
|
||||
}
|
||||
|
||||
public function setAuthorUrlAttribute($value)
|
||||
{
|
||||
$this->attributes['author_url'] = normalize_url($value);
|
||||
}
|
||||
|
||||
public function getContentAttribute($value)
|
||||
{
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$mf2 = Mf2\parse($value, $this->url);
|
||||
|
||||
return $this->filterHTML($mf2['items'][0]['properties']['content'][0]['html']);
|
||||
}
|
||||
|
||||
public function filterHTML($html)
|
||||
{
|
||||
$config = HTMLPurifier_Config::createDefault();
|
||||
$config->set('Cache.SerializerPath', storage_path() . '/HTMLPurifier');
|
||||
$config->set('HTML.TargetBlank', true);
|
||||
$purifier = new HTMLPurifier($config);
|
||||
|
||||
return $purifier->purify($html);
|
||||
}
|
||||
}
|
90
app/Models/Media.php
Normal file
90
app/Models/Media.php
Normal file
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Media extends Model
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'media_endpoint';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = ['token', 'path', 'type', 'image_widths'];
|
||||
|
||||
/**
|
||||
* Get the note that owns this media.
|
||||
*/
|
||||
public function note()
|
||||
{
|
||||
return $this->belongsTo('App\Models\Note');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL for an S3 media file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUrlAttribute()
|
||||
{
|
||||
if (starts_with($this->path, 'https://')) {
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
return config('filesystems.disks.s3.url') . '/' . $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL for the medium size of an S3 image file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMediumurlAttribute()
|
||||
{
|
||||
$basename = $this->getBasename($this->path);
|
||||
$extension = $this->getExtension($this->path);
|
||||
|
||||
return config('filesystems.disks.s3.url') . '/' . $basename . '-medium.' . $extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL for the small size of an S3 image file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSmallurlAttribute()
|
||||
{
|
||||
$basename = $this->getBasename($this->path);
|
||||
$extension = $this->getExtension($this->path);
|
||||
|
||||
return config('filesystems.disks.s3.url') . '/' . $basename . '-small.' . $extension;
|
||||
}
|
||||
|
||||
public function getBasename($path)
|
||||
{
|
||||
// the following achieves this data flow
|
||||
// foo.bar.png => ['foo', 'bar', 'png'] => ['foo', 'bar'] => foo.bar
|
||||
$filenameParts = explode('.', $path);
|
||||
array_pop($filenameParts);
|
||||
$basename = ltrim(array_reduce($filenameParts, function ($carry, $item) {
|
||||
return $carry . '.' . $item;
|
||||
}, ''), '.');
|
||||
|
||||
return $basename;
|
||||
}
|
||||
|
||||
public function getExtension($path)
|
||||
{
|
||||
$parts = explode('.', $path);
|
||||
|
||||
return array_pop($parts);
|
||||
}
|
||||
}
|
32
app/Models/MicropubClient.php
Normal file
32
app/Models/MicropubClient.php
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class MicropubClient extends Model
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'clients';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = ['client_url', 'client_name'];
|
||||
|
||||
/**
|
||||
* Define the relationship with notes.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function notes()
|
||||
{
|
||||
return $this->hasMany('App\Models\Note', 'client_id', 'client_url');
|
||||
}
|
||||
}
|
528
app/Models/Note.php
Normal file
528
app/Models/Note.php
Normal file
|
@ -0,0 +1,528 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Cache;
|
||||
use Twitter;
|
||||
use Normalizer;
|
||||
use GuzzleHttp\Client;
|
||||
use Laravel\Scout\Searchable;
|
||||
use League\CommonMark\Converter;
|
||||
use League\CommonMark\DocParser;
|
||||
use Jonnybarnes\IndieWeb\Numbers;
|
||||
use League\CommonMark\Environment;
|
||||
use League\CommonMark\HtmlRenderer;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Jonnybarnes\EmojiA11y\EmojiModifier;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Jonnybarnes\CommonmarkLinkify\LinkifyExtension;
|
||||
|
||||
class Note extends Model
|
||||
{
|
||||
use Searchable;
|
||||
use SoftDeletes;
|
||||
|
||||
/**
|
||||
* The reges for matching lone usernames.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private const USERNAMES_REGEX = '/\[.*?\](*SKIP)(*F)|@(\w+)/';
|
||||
|
||||
protected $contacts;
|
||||
|
||||
public function __construct(array $attributes = [])
|
||||
{
|
||||
parent::__construct($attributes);
|
||||
$this->contacts = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The database table used by the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'notes';
|
||||
|
||||
/*
|
||||
* Mass-assignment
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'note',
|
||||
'in_reply_to',
|
||||
'client_id',
|
||||
];
|
||||
|
||||
/**
|
||||
* Hide the column used with Laravel Scout.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $hidden = ['searchable'];
|
||||
|
||||
/**
|
||||
* Define the relationship with tags.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public function tags()
|
||||
{
|
||||
return $this->belongsToMany('App\Models\Tag');
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the relationship with clients.
|
||||
*
|
||||
* @var array?
|
||||
*/
|
||||
public function client()
|
||||
{
|
||||
return $this->belongsTo('App\Models\MicropubClient', 'client_id', 'client_url');
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the relationship with webmentions.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public function webmentions()
|
||||
{
|
||||
return $this->morphMany('App\Models\WebMention', 'commentable');
|
||||
}
|
||||
|
||||
/**
|
||||
* Definte the relationship with places.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public function place()
|
||||
{
|
||||
return $this->belongsTo('App\Models\Place');
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the relationship with media.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function media()
|
||||
{
|
||||
return $this->hasMany('App\Models\Media');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the attributes to be indexed for searching with Scout.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toSearchableArray()
|
||||
{
|
||||
return [
|
||||
'note' => $this->note,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the note to Unicode FORM C.
|
||||
*
|
||||
* @param string $value
|
||||
* @return string
|
||||
*/
|
||||
public function setNoteAttribute($value)
|
||||
{
|
||||
$this->attributes['note'] = normalizer_normalize($value, Normalizer::FORM_C);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-process notes for web-view.
|
||||
*
|
||||
* @param string
|
||||
* @return string
|
||||
*/
|
||||
public function getNoteAttribute($value)
|
||||
{
|
||||
$emoji = new EmojiModifier();
|
||||
|
||||
$hcards = $this->makeHCards($value);
|
||||
$hashtags = $this->autoLinkHashtag($hcards);
|
||||
$html = $this->convertMarkdown($hashtags);
|
||||
$modified = $emoji->makeEmojiAccessible($html);
|
||||
|
||||
return $modified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the NewBase60 ID from primary ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getNb60idAttribute()
|
||||
{
|
||||
$numbers = new Numbers();
|
||||
|
||||
return $numbers->numto60($this->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Long URL for a note.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLongurlAttribute()
|
||||
{
|
||||
return config('app.url') . '/notes/' . $this->nb60id;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Short URL for a note.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getShorturlAttribute()
|
||||
{
|
||||
return config('app.shorturl') . '/notes/' . $this->nb60id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ISO8601 value for mf2.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getIso8601Attribute()
|
||||
{
|
||||
return $this->updated_at->toISO8601String();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ISO8601 value for mf2.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getHumandiffAttribute()
|
||||
{
|
||||
return $this->updated_at->diffForHumans();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pubdate value for RSS feeds.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPubdateAttribute()
|
||||
{
|
||||
return $this->updated_at->toRSSString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latitude value.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getLatitudeAttribute()
|
||||
{
|
||||
if ($this->place !== null) {
|
||||
return $this->place->location->getLat();
|
||||
}
|
||||
if ($this->location !== null) {
|
||||
$pieces = explode(':', $this->location);
|
||||
$latlng = explode(',', $pieces[0]);
|
||||
|
||||
return trim($latlng[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the longitude value.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getLongitudeAttribute()
|
||||
{
|
||||
if ($this->place !== null) {
|
||||
return $this->place->location->getLng();
|
||||
}
|
||||
if ($this->location !== null) {
|
||||
$pieces = explode(':', $this->location);
|
||||
$latlng = explode(',', $pieces[0]);
|
||||
|
||||
return trim($latlng[1]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the address for a note. This is either a reverse geo-code from the
|
||||
* location, or is derived from the associated place.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getAddressAttribute()
|
||||
{
|
||||
if ($this->place !== null) {
|
||||
return $this->place->name;
|
||||
}
|
||||
if ($this->location !== null) {
|
||||
return $this->reverseGeoCode((float) $this->latitude, (float) $this->longitude);
|
||||
}
|
||||
}
|
||||
|
||||
public function getTwitterAttribute()
|
||||
{
|
||||
if ($this->in_reply_to == null || mb_substr($this->in_reply_to, 0, 20, 'UTF-8') !== 'https://twitter.com/') {
|
||||
return;
|
||||
}
|
||||
|
||||
$tweetId = basename($this->in_reply_to);
|
||||
if (Cache::has($tweetId)) {
|
||||
return Cache::get($tweetId);
|
||||
}
|
||||
|
||||
try {
|
||||
$oEmbed = Twitter::getOembed([
|
||||
'url' => $this->in_reply_to,
|
||||
'dnt' => true,
|
||||
'align' => 'center',
|
||||
'maxwidth' => 512,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return;
|
||||
}
|
||||
Cache::put($tweetId, $oEmbed, ($oEmbed->cache_age / 60));
|
||||
|
||||
return $oEmbed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a specific form of the note for twitter.
|
||||
*/
|
||||
public function getTwitterContentAttribute()
|
||||
{
|
||||
if ($this->contacts === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (count($this->contacts) === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (count(array_unique(array_values($this->contacts))) === 1
|
||||
&& array_unique(array_values($this->contacts))[0] === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// swap in twitter usernames
|
||||
$swapped = preg_replace_callback(
|
||||
self::USERNAMES_REGEX,
|
||||
function ($matches) {
|
||||
if (is_null($this->contacts[$matches[1]])) {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
$contact = $this->contacts[$matches[1]];
|
||||
if ($contact->twitter) {
|
||||
return '@' . $contact->twitter;
|
||||
}
|
||||
|
||||
return $contact->name;
|
||||
},
|
||||
$this->getOriginal('note')
|
||||
);
|
||||
|
||||
return $this->convertMarkdown($swapped);
|
||||
}
|
||||
|
||||
public function getFacebookContentAttribute()
|
||||
{
|
||||
if (count($this->contacts) === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (count(array_unique(array_values($this->contacts))) === 1
|
||||
&& array_unique(array_values($this->contacts))[0] === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// swap in facebook usernames
|
||||
$swapped = preg_replace_callback(
|
||||
self::USERNAMES_REGEX,
|
||||
function ($matches) {
|
||||
if (is_null($this->contacts[$matches[1]])) {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
$contact = $this->contacts[$matches[1]];
|
||||
if ($contact->facebook) {
|
||||
return '<a class="u-category h-card" href="https://facebook.com/'
|
||||
. $contact->facebook . '">' . $contact->name . '</a>';
|
||||
}
|
||||
|
||||
return $contact->name;
|
||||
},
|
||||
$this->getOriginal('note')
|
||||
);
|
||||
|
||||
return $this->convertMarkdown($swapped);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope a query to select a note via a NewBase60 id.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param string $nb60id
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function scopeNb60($query, $nb60id)
|
||||
{
|
||||
$numbers = new Numbers();
|
||||
|
||||
return $query->where('id', $numbers->b60tonum($nb60id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Take note that this method does two things, given @username (NOT [@username](URL)!)
|
||||
* we try to create a fancy hcard from our contact info. If this is not possible
|
||||
* due to lack of contact info, we assume @username is a twitter handle and link it
|
||||
* as such.
|
||||
*
|
||||
* @param string The note’s text
|
||||
* @return string
|
||||
*/
|
||||
private function makeHCards($text)
|
||||
{
|
||||
$this->getContacts();
|
||||
|
||||
if (count($this->contacts) === 0) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
$hcards = preg_replace_callback(
|
||||
self::USERNAMES_REGEX,
|
||||
function ($matches) {
|
||||
if (is_null($this->contacts[$matches[1]])) {
|
||||
return '<a href="https://twitter.com/' . $matches[1] . '">' . $matches[0] . '</a>';
|
||||
}
|
||||
|
||||
$contact = $this->contacts[$matches[1]]; // easier to read the following code
|
||||
$host = parse_url($contact->homepage, PHP_URL_HOST);
|
||||
$contact->photo = '/assets/profile-images/default-image';
|
||||
if (file_exists(public_path() . '/assets/profile-images/' . $host . '/image')) {
|
||||
$contact->photo = '/assets/profile-images/' . $host . '/image';
|
||||
}
|
||||
|
||||
return trim(view('templates.mini-hcard', ['contact' => $contact])->render());
|
||||
},
|
||||
$text
|
||||
);
|
||||
|
||||
return $hcards;
|
||||
}
|
||||
|
||||
public function getContacts()
|
||||
{
|
||||
if ($this->contacts === null) {
|
||||
$this->setContacts();
|
||||
}
|
||||
}
|
||||
|
||||
public function setContacts()
|
||||
{
|
||||
$contacts = [];
|
||||
if ($this->getOriginal('note')) {
|
||||
preg_match_all(self::USERNAMES_REGEX, $this->getoriginal('note'), $matches);
|
||||
|
||||
foreach ($matches[1] as $match) {
|
||||
$contacts[$match] = Contact::where('nick', mb_strtolower($match))->first();
|
||||
}
|
||||
}
|
||||
|
||||
$this->contacts = $contacts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a string and section, finds all hashtags matching
|
||||
* `#[\-_a-zA-Z0-9]+` and wraps them in an `a` element with
|
||||
* `rel=tag` set and a `href` of 'section/tagged/' + tagname without the #.
|
||||
*
|
||||
* @param string The note
|
||||
* @return string
|
||||
*/
|
||||
public function autoLinkHashtag($text)
|
||||
{
|
||||
return preg_replace_callback(
|
||||
'/#([^\s]*)\b/',
|
||||
function ($matches) {
|
||||
return '<a rel="tag" class="p-category" href="/notes/tagged/'
|
||||
. Tag::normalize($matches[1]) . '">#'
|
||||
. Tag::normalize($matches[1]) . '</a>';
|
||||
},
|
||||
$text
|
||||
);
|
||||
}
|
||||
|
||||
private function convertMarkdown($text)
|
||||
{
|
||||
$environment = Environment::createCommonMarkEnvironment();
|
||||
$environment->addExtension(new LinkifyExtension());
|
||||
$converter = new Converter(new DocParser($environment), new HtmlRenderer($environment));
|
||||
|
||||
return $converter->convertToHtml($text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do a reverse geocode lookup of a `lat,lng` value.
|
||||
*
|
||||
* @param float The latitude
|
||||
* @param float The longitude
|
||||
* @return string The location HTML
|
||||
*/
|
||||
public function reverseGeoCode(float $latitude, float $longitude): string
|
||||
{
|
||||
$latlng = $latitude . ',' . $longitude;
|
||||
|
||||
return Cache::get($latlng, function () use ($latlng, $latitude, $longitude) {
|
||||
$guzzle = new Client();
|
||||
$response = $guzzle->request('GET', 'https://nominatim.openstreetmap.org/reverse', [
|
||||
'query' => [
|
||||
'format' => 'json',
|
||||
'lat' => $latitude,
|
||||
'lon' => $longitude,
|
||||
'zoom' => 18,
|
||||
'addressdetails' => 1,
|
||||
],
|
||||
'headers' => ['User-Agent' => 'jonnybarnes.uk via Guzzle, email jonny@jonnybarnes.uk'],
|
||||
]);
|
||||
$json = json_decode($response->getBody());
|
||||
if (isset($json->address->town)) {
|
||||
$address = '<span class="p-locality">'
|
||||
. $json->address->town
|
||||
. '</span>, <span class="p-country-name">'
|
||||
. $json->address->country
|
||||
. '</span>';
|
||||
Cache::forever($latlng, $address);
|
||||
|
||||
return $address;
|
||||
}
|
||||
if (isset($json->address->city)) {
|
||||
$address = $json->address->city . ', ' . $json->address->country;
|
||||
Cache::forever($latlng, $address);
|
||||
|
||||
return $address;
|
||||
}
|
||||
if (isset($json->address->county)) {
|
||||
$address = '<span class="p-region">'
|
||||
. $json->address->county
|
||||
. '</span>, <span class="p-country-name">'
|
||||
. $json->address->country
|
||||
. '</span>';
|
||||
Cache::forever($latlng, $address);
|
||||
|
||||
return $address;
|
||||
}
|
||||
$address = '<span class="p-country-name">' . $json->address->country . '</span>';
|
||||
Cache::forever($latlng, $address);
|
||||
|
||||
return $address;
|
||||
});
|
||||
}
|
||||
}
|
163
app/Models/Place.php
Normal file
163
app/Models/Place.php
Normal file
|
@ -0,0 +1,163 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Cviebrock\EloquentSluggable\Sluggable;
|
||||
use Phaza\LaravelPostgis\Geometries\Point;
|
||||
use Phaza\LaravelPostgis\Eloquent\PostgisTrait;
|
||||
|
||||
class Place extends Model
|
||||
{
|
||||
use Sluggable;
|
||||
use PostgisTrait;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = ['name', 'slug'];
|
||||
|
||||
/**
|
||||
* The attributes that are Postgis geometry objects.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $postgisFields = [
|
||||
'location',
|
||||
'polygon',
|
||||
];
|
||||
|
||||
/**
|
||||
* Return the sluggable configuration array for this model.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function sluggable()
|
||||
{
|
||||
return [
|
||||
'slug' => [
|
||||
'source' => 'name',
|
||||
'onUpdate' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the relationship with Notes.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public function notes()
|
||||
{
|
||||
return $this->hasMany('App\Models\Note');
|
||||
}
|
||||
|
||||
/**
|
||||
* Select places near a given location.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param Point $point
|
||||
* @param int Distance
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function scopeNear(Builder $query, Point $point, $distance = 1000)
|
||||
{
|
||||
$field = DB::raw(
|
||||
sprintf(
|
||||
"ST_Distance(%s.location, ST_GeogFromText('%s'))",
|
||||
$this->getTable(),
|
||||
$point->toWKT()
|
||||
)
|
||||
);
|
||||
|
||||
return $query->where($field, '<=', $distance)->orderBy($field);
|
||||
}
|
||||
|
||||
public function scopeWhereExternalURL(Builder $query, string $url)
|
||||
{
|
||||
return $query->where('external_urls', '@>', json_encode([
|
||||
$this->getType($url) => $url,
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latitude from the `location` property.
|
||||
*
|
||||
* @return string latitude
|
||||
*/
|
||||
public function getLatitudeAttribute()
|
||||
{
|
||||
return explode(' ', $this->location)[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the longitude from the `location` property.
|
||||
*
|
||||
* @return string longitude
|
||||
*/
|
||||
public function getLongitudeAttribute()
|
||||
{
|
||||
return explode(' ', $this->location)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* The Long URL for a place.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLongurlAttribute()
|
||||
{
|
||||
return config('app.url') . '/places/' . $this->slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Short URL for a place.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getShorturlAttribute()
|
||||
{
|
||||
return config('app.shorturl') . '/places/' . $this->slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is an alternative for `longurl`.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUriAttribute()
|
||||
{
|
||||
return $this->longurl;
|
||||
}
|
||||
|
||||
public function setExternalUrlsAttribute($url)
|
||||
{
|
||||
if ($url === null) {
|
||||
return;
|
||||
}
|
||||
$type = $this->getType($url);
|
||||
$already = [];
|
||||
if (array_key_exists('external_urls', $this->attributes)) {
|
||||
$already = json_decode($this->attributes['external_urls'], true);
|
||||
}
|
||||
$already[$type] = $url;
|
||||
$this->attributes['external_urls'] = json_encode($already);
|
||||
}
|
||||
|
||||
private function getType(string $url): ?string
|
||||
{
|
||||
$host = parse_url($url, PHP_URL_HOST);
|
||||
if (ends_with($host, 'foursquare.com') === true) {
|
||||
return 'foursquare';
|
||||
}
|
||||
if (ends_with($host, 'openstreetmap.org') === true) {
|
||||
return 'osm';
|
||||
}
|
||||
|
||||
return 'default';
|
||||
}
|
||||
}
|
61
app/Models/Tag.php
Normal file
61
app/Models/Tag.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Tag extends Model
|
||||
{
|
||||
/**
|
||||
* We shall set a blacklist of non-modifiable model attributes.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $guarded = ['id'];
|
||||
|
||||
/**
|
||||
* Define the relationship with tags.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public function notes()
|
||||
{
|
||||
return $this->belongsToMany('App\Models\Note');
|
||||
}
|
||||
|
||||
/**
|
||||
* The bookmarks that belong to the tag.
|
||||
*/
|
||||
public function bookmarks()
|
||||
{
|
||||
return $this->belongsToMany('App\Models\Bookmark');
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize tags so they’re lowercase and fancy diatrics are removed.
|
||||
*
|
||||
* @param string
|
||||
*/
|
||||
public function setTagAttribute($value)
|
||||
{
|
||||
$this->attributes['tag'] = $this->normalize($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method actually normalizes a tag. That means lowercase-ing and
|
||||
* removing fancy diatric characters.
|
||||
*
|
||||
* @param string
|
||||
*/
|
||||
public static function normalize($tag)
|
||||
{
|
||||
return mb_strtolower(
|
||||
preg_replace(
|
||||
'/&([a-z]{1,2})(acute|cedil|circ|grave|lig|orn|ring|slash|th|tilde|uml|caron);/i',
|
||||
'$1',
|
||||
htmlentities($tag)
|
||||
),
|
||||
'UTF-8'
|
||||
);
|
||||
}
|
||||
}
|
29
app/Models/User.php
Normal file
29
app/Models/User.php
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
use Notifiable;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name', 'email', 'password',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for arrays.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password', 'remember_token',
|
||||
];
|
||||
}
|
141
app/Models/WebMention.php
Normal file
141
app/Models/WebMention.php
Normal file
|
@ -0,0 +1,141 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Cache;
|
||||
use Twitter;
|
||||
use HTMLPurifier;
|
||||
use HTMLPurifier_Config;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Jonnybarnes\WebmentionsParser\Authorship;
|
||||
|
||||
class WebMention extends Model
|
||||
{
|
||||
/**
|
||||
* The database table used by the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'webmentions';
|
||||
|
||||
/**
|
||||
* We shall set a blacklist of non-modifiable model attributes.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $guarded = ['id'];
|
||||
|
||||
/**
|
||||
* Define the relationship.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public function commentable()
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the author of the webmention.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAuthorAttribute()
|
||||
{
|
||||
$authorship = new Authorship();
|
||||
$hCard = $authorship->findAuthor(json_decode($this->mf2, true));
|
||||
if (array_key_exists('properties', $hCard) &&
|
||||
array_key_exists('photo', $hCard['properties'])
|
||||
) {
|
||||
$hCard['properties']['photo'][0] = $this->createPhotoLink($hCard['properties']['photo'][0]);
|
||||
}
|
||||
|
||||
return $hCard;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the published value for the webmention.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPublishedAttribute()
|
||||
{
|
||||
$microformats = json_decode($this->mf2, true);
|
||||
if (isset($microformats['items'][0]['properties']['published'][0])) {
|
||||
try {
|
||||
$published = carbon()->parse(
|
||||
$microformats['items'][0]['properties']['published'][0]
|
||||
)->toDayDateTimeString();
|
||||
} catch (\Exception $exception) {
|
||||
$published = $this->updated_at->toDayDateTimeString();
|
||||
}
|
||||
} else {
|
||||
$published = $this->updated_at->toDayDateTimeString();
|
||||
}
|
||||
|
||||
return $published;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filtered HTML of a reply.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getReplyAttribute()
|
||||
{
|
||||
$microformats = json_decode($this->mf2, true);
|
||||
if (isset($microformats['items'][0]['properties']['content'][0]['html'])) {
|
||||
return $this->filterHTML($microformats['items'][0]['properties']['content'][0]['html']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the photo link.
|
||||
*
|
||||
* @param string
|
||||
* @return string
|
||||
*/
|
||||
public function createPhotoLink(string $url): string
|
||||
{
|
||||
$url = normalize_url($url);
|
||||
$host = parse_url($url, PHP_URL_HOST);
|
||||
if ($host == 'pbs.twimg.com') {
|
||||
//make sure we use HTTPS, we know twitter supports it
|
||||
return str_replace('http://', 'https://', $url);
|
||||
}
|
||||
if ($host == 'twitter.com') {
|
||||
if (Cache::has($url)) {
|
||||
return Cache::get($url);
|
||||
}
|
||||
$username = ltrim(parse_url($url, PHP_URL_PATH), '/');
|
||||
$info = Twitter::getUsers(['screen_name' => $username]);
|
||||
$profile_image = $info->profile_image_url_https;
|
||||
Cache::put($url, $profile_image, 10080); //1 week
|
||||
|
||||
return $profile_image;
|
||||
}
|
||||
$filesystem = new Filesystem();
|
||||
if ($filesystem->exists(public_path() . '/assets/profile-images/' . $host . '/image')) {
|
||||
return '/assets/profile-images/' . $host . '/image';
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the HTML in a reply webmention.
|
||||
*
|
||||
* @param string The reply HTML
|
||||
* @return string The filtered HTML
|
||||
*/
|
||||
private function filterHTML($html)
|
||||
{
|
||||
$config = HTMLPurifier_Config::createDefault();
|
||||
$config->set('Cache.SerializerPath', storage_path() . '/HTMLPurifier');
|
||||
$config->set('HTML.TargetBlank', true);
|
||||
$purifier = new HTMLPurifier($config);
|
||||
|
||||
return $purifier->purify($html);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue