Merge pull request #455 from shikorism/develop

Release 20200801.2055
This commit is contained in:
shibafu 2020-08-01 21:00:17 +09:00 committed by GitHub
commit 3b37e86e55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 531 additions and 187 deletions

View File

@ -5,8 +5,8 @@ FROM php:7.3-apache
ENV APACHE_DOCUMENT_ROOT /var/www/html/public ENV APACHE_DOCUMENT_ROOT /var/www/html/public
RUN apt-get update \ RUN apt-get update \
&& apt-get install -y git libpq-dev unzip \ && apt-get install -y git libpq-dev unzip libicu-dev \
&& docker-php-ext-install pdo_pgsql \ && docker-php-ext-install pdo_pgsql intl \
&& pecl install xdebug \ && pecl install xdebug \
&& curl -sS https://getcomposer.org/installer | php \ && curl -sS https://getcomposer.org/installer | php \
&& mv composer.phar /usr/local/bin/composer \ && mv composer.phar /usr/local/bin/composer \

View File

@ -0,0 +1,61 @@
<?php
namespace App\Console\Commands;
use App\Tag;
use App\Utilities\Formatter;
use DB;
use Illuminate\Console\Command;
class NormalizeTags extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'tissue:tag:normalize';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Normalize tags';
private $formatter;
/**
* Create a new command instance.
*
* @return void
*/
public function __construct(Formatter $formatter)
{
parent::__construct();
$this->formatter = $formatter;
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$start = hrtime(true);
DB::transaction(function () {
/** @var Tag $tag */
foreach (Tag::query()->cursor() as $tag) {
$normalizedName = $this->formatter->normalizeTagName($tag->name);
$this->line("{$tag->name} : {$normalizedName}");
$tag->normalized_name = $normalizedName;
$tag->save();
}
});
$elapsed = (hrtime(true) - $start) / 1e+9;
$this->info("Done! ({$elapsed} sec)");
}
}

View File

@ -2,6 +2,7 @@
namespace App\Http\Controllers\Api; namespace App\Http\Controllers\Api;
use App\MetadataResolver\DeniedHostException;
use App\Services\MetadataResolveService; use App\Services\MetadataResolveService;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -13,7 +14,11 @@ class CardController
'url:required|url' 'url:required|url'
]); ]);
$metadata = $service->execute($request->input('url')); try {
$metadata = $service->execute($request->input('url'));
} catch (DeniedHostException $e) {
abort(403, $e->getMessage());
}
$metadata->load('tags'); $metadata->load('tags');
$response = response($metadata); $response = response($metadata);

View File

@ -4,20 +4,30 @@ namespace App\Http\Controllers;
use App\Ejaculation; use App\Ejaculation;
use App\Tag; use App\Tag;
use App\Utilities\Formatter;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
class SearchController extends Controller class SearchController extends Controller
{ {
/** @var Formatter */
private $formatter;
public function __construct(Formatter $formatter)
{
$this->formatter = $formatter;
}
public function index(Request $request) public function index(Request $request)
{ {
$inputs = $request->validate([ $inputs = $request->validate([
'q' => 'required' 'q' => 'required'
]); ]);
$q = $this->normalizeQuery($inputs['q']);
$results = Ejaculation::query() $results = Ejaculation::query()
->whereHas('tags', function ($query) use ($inputs) { ->whereHas('tags', function ($query) use ($q) {
$query->where('name', 'like', "%{$inputs['q']}%"); $query->where('normalized_name', 'like', "%{$q}%");
}) })
->whereHas('user', function ($query) { ->whereHas('user', function ($query) {
$query->where('is_protected', false); $query->where('is_protected', false);
@ -41,11 +51,17 @@ class SearchController extends Controller
'q' => 'required' 'q' => 'required'
]); ]);
$q = $this->normalizeQuery($inputs['q']);
$results = Tag::query() $results = Tag::query()
->where('name', 'like', "%{$inputs['q']}%") ->where('normalized_name', 'like', "%{$q}%")
->paginate(50) ->paginate(50)
->appends($inputs); ->appends($inputs);
return view('search.relatedTag')->with(compact('inputs', 'results')); return view('search.relatedTag')->with(compact('inputs', 'results'));
} }
private function normalizeQuery(string $query): string
{
return $this->formatter->normalizeTagName($query);
}
} }

View File

@ -3,6 +3,7 @@
namespace App\Listeners; namespace App\Listeners;
use App\Events\LinkDiscovered; use App\Events\LinkDiscovered;
use App\MetadataResolver\DeniedHostException;
use App\Services\MetadataResolveService; use App\Services\MetadataResolveService;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
@ -32,6 +33,8 @@ class LinkCollector
{ {
try { try {
$this->metadataResolveService->execute($event->url); $this->metadataResolveService->execute($event->url);
} catch (DeniedHostException $e) {
// ignored
} catch (\Exception $e) { } catch (\Exception $e) {
// 今のところこのイベントは同期実行されるので、上流をクラッシュさせないために雑catchする // 今のところこのイベントは同期実行されるので、上流をクラッシュさせないために雑catchする
report($e); report($e);

View File

@ -0,0 +1,30 @@
<?php
namespace App\MetadataResolver;
use Exception;
use Throwable;
/**
* メタデータの解決を禁止しているホストに対して取得を試み、ブロックされたことを表します。
*/
class DeniedHostException extends Exception
{
private $url;
public function __construct(string $url, Throwable $previous = null)
{
parent::__construct("Access denied by system policy: $url", 0, $previous);
$this->url = $url;
}
public function getUrl(): string
{
return $this->url;
}
public function getHost(): string
{
return parse_url($this->url, PHP_URL_HOST);
}
}

View File

@ -3,10 +3,12 @@
namespace App\Services; namespace App\Services;
use App\Metadata; use App\Metadata;
use App\MetadataResolver\DeniedHostException;
use App\MetadataResolver\MetadataResolver; use App\MetadataResolver\MetadataResolver;
use App\Tag; use App\Tag;
use App\Utilities\Formatter; use App\Utilities\Formatter;
use GuzzleHttp\Exception\TransferException; use GuzzleHttp\Exception\TransferException;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
class MetadataResolveService class MetadataResolveService
@ -27,32 +29,39 @@ class MetadataResolveService
// URLの正規化 // URLの正規化
$url = $this->formatter->normalizeUrl($url); $url = $this->formatter->normalizeUrl($url);
// 無かったら取得 // 自分自身は解決しない
// TODO: ある程度古かったら再取得とかありだと思う if (parse_url($url, PHP_URL_HOST) === parse_url(config('app.url'), PHP_URL_HOST)) {
$metadata = Metadata::find($url); throw new DeniedHostException($url);
if ($metadata == null || ($metadata->expires_at !== null && $metadata->expires_at < now())) {
try {
$resolved = $this->resolver->resolve($url);
$metadata = Metadata::updateOrCreate(['url' => $url], [
'title' => $resolved->title,
'description' => $resolved->description,
'image' => $resolved->image,
'expires_at' => $resolved->expires_at
]);
$tagIds = [];
foreach ($resolved->normalizedTags() as $tagName) {
$tag = Tag::firstOrCreate(['name' => $tagName]);
$tagIds[] = $tag->id;
}
$metadata->tags()->sync($tagIds);
} catch (TransferException $e) {
// 何らかの通信エラーによってメタデータの取得に失敗した時、とりあえずエラーログにURLを残す
Log::error(self::class . ': メタデータの取得に失敗 URL=' . $url);
throw $e;
}
} }
return $metadata; return DB::transaction(function () use ($url) {
// 無かったら取得
// TODO: ある程度古かったら再取得とかありだと思う
$metadata = Metadata::find($url);
if ($metadata == null || ($metadata->expires_at !== null && $metadata->expires_at < now())) {
try {
$resolved = $this->resolver->resolve($url);
$metadata = Metadata::updateOrCreate(['url' => $url], [
'title' => $resolved->title,
'description' => $resolved->description,
'image' => $resolved->image,
'expires_at' => $resolved->expires_at
]);
$tagIds = [];
foreach ($resolved->normalizedTags() as $tagName) {
$tag = Tag::firstOrCreate(['name' => $tagName]);
$tagIds[] = $tag->id;
}
$metadata->tags()->sync($tagIds);
} catch (TransferException $e) {
// 何らかの通信エラーによってメタデータの取得に失敗した時、とりあえずエラーログにURLを残す
Log::error(self::class . ': メタデータの取得に失敗 URL=' . $url);
throw $e;
}
}
return $metadata;
});
} }
} }

View File

@ -2,6 +2,7 @@
namespace App; namespace App;
use App\Utilities\Formatter;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
class Tag extends Model class Tag extends Model
@ -15,6 +16,15 @@ class Tag extends Model
'name' 'name'
]; ];
protected static function boot()
{
parent::boot();
self::creating(function (Tag $tag) {
$tag->normalized_name = app(Formatter::class)->normalizeTagName($tag->name);
});
}
public function ejaculations() public function ejaculations()
{ {
return $this->belongsToMany('App\Ejaculation')->withTimestamps(); return $this->belongsToMany('App\Ejaculation')->withTimestamps();

View File

@ -132,4 +132,12 @@ class Formatter
return $bytes . 'B'; return $bytes . 'B';
} }
public function normalizeTagName(string $name)
{
$name = \Normalizer::normalize($name, \Normalizer::FORM_KC);
$name = mb_strtolower($name);
return $name;
}
} }

View File

@ -12,6 +12,12 @@
], ],
"require": { "require": {
"php": "^7.2", "php": "^7.2",
"ext-dom": "*",
"ext-intl": "*",
"ext-json": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-pdo": "*",
"anhskohbo/no-captcha": "^3.0", "anhskohbo/no-captcha": "^3.0",
"doctrine/dbal": "^2.9", "doctrine/dbal": "^2.9",
"erusev/parsedown": "^1.7", "erusev/parsedown": "^1.7",

432
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "2c0bd951a595d4856079c5a13a72e651", "content-hash": "1bba68b609be6a0dcdaf05d72e8eb759",
"packages": [ "packages": [
{ {
"name": "anhskohbo/no-captcha", "name": "anhskohbo/no-captcha",
@ -547,6 +547,20 @@
"uppercase", "uppercase",
"words" "words"
], ],
"funding": [
{
"url": "https://www.doctrine-project.org/sponsorship.html",
"type": "custom"
},
{
"url": "https://www.patreon.com/phpdoctrine",
"type": "patreon"
},
{
"url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector",
"type": "tidelift"
}
],
"time": "2020-05-29T15:13:26+00:00" "time": "2020-05-29T15:13:26+00:00"
}, },
{ {
@ -609,6 +623,20 @@
"parser", "parser",
"php" "php"
], ],
"funding": [
{
"url": "https://www.doctrine-project.org/sponsorship.html",
"type": "custom"
},
{
"url": "https://www.patreon.com/phpdoctrine",
"type": "patreon"
},
{
"url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer",
"type": "tidelift"
}
],
"time": "2020-05-25T17:44:05+00:00" "time": "2020-05-25T17:44:05+00:00"
}, },
{ {
@ -1148,16 +1176,16 @@
}, },
{ {
"name": "laravel/framework", "name": "laravel/framework",
"version": "v6.18.25", "version": "v6.18.26",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laravel/framework.git", "url": "https://github.com/laravel/framework.git",
"reference": "fd4c42cb49b8777473d1161ef15d1104b2a33d6c" "reference": "d11b6168c65251ffa81ae0dfaf017ad2f30013da"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/fd4c42cb49b8777473d1161ef15d1104b2a33d6c", "url": "https://api.github.com/repos/laravel/framework/zipball/d11b6168c65251ffa81ae0dfaf017ad2f30013da",
"reference": "fd4c42cb49b8777473d1161ef15d1104b2a33d6c", "reference": "d11b6168c65251ffa81ae0dfaf017ad2f30013da",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1292,7 +1320,7 @@
"framework", "framework",
"laravel" "laravel"
], ],
"time": "2020-07-10T16:41:03+00:00" "time": "2020-07-21T14:25:39+00:00"
}, },
{ {
"name": "laravel/helpers", "name": "laravel/helpers",
@ -1413,16 +1441,16 @@
}, },
{ {
"name": "league/commonmark", "name": "league/commonmark",
"version": "1.5.1", "version": "1.5.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/thephpleague/commonmark.git", "url": "https://github.com/thephpleague/commonmark.git",
"reference": "6d74caf6abeed5fd85d6ec20da23d7269cd0b46f" "reference": "2574454b97e4103dc4e36917bd783b25624aefcd"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/6d74caf6abeed5fd85d6ec20da23d7269cd0b46f", "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/2574454b97e4103dc4e36917bd783b25624aefcd",
"reference": "6d74caf6abeed5fd85d6ec20da23d7269cd0b46f", "reference": "2574454b97e4103dc4e36917bd783b25624aefcd",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1441,7 +1469,7 @@
"michelf/php-markdown": "~1.4", "michelf/php-markdown": "~1.4",
"mikehaertl/php-shellcommand": "^1.4", "mikehaertl/php-shellcommand": "^1.4",
"phpstan/phpstan": "^0.12", "phpstan/phpstan": "^0.12",
"phpunit/phpunit": "^7.5", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.2",
"scrutinizer/ocular": "^1.5", "scrutinizer/ocular": "^1.5",
"symfony/finder": "^4.2" "symfony/finder": "^4.2"
}, },
@ -1504,7 +1532,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2020-06-27T12:50:08+00:00" "time": "2020-07-19T22:47:30+00:00"
}, },
{ {
"name": "league/csv", "name": "league/csv",
@ -2509,24 +2537,24 @@
}, },
{ {
"name": "phpoption/phpoption", "name": "phpoption/phpoption",
"version": "1.7.4", "version": "1.7.5",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/schmittjoh/php-option.git", "url": "https://github.com/schmittjoh/php-option.git",
"reference": "b2ada2ad5d8a32b89088b8adc31ecd2e3a13baf3" "reference": "994ecccd8f3283ecf5ac33254543eb0ac946d525"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/schmittjoh/php-option/zipball/b2ada2ad5d8a32b89088b8adc31ecd2e3a13baf3", "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/994ecccd8f3283ecf5ac33254543eb0ac946d525",
"reference": "b2ada2ad5d8a32b89088b8adc31ecd2e3a13baf3", "reference": "994ecccd8f3283ecf5ac33254543eb0ac946d525",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^5.5.9 || ^7.0 || ^8.0" "php": "^5.5.9 || ^7.0 || ^8.0"
}, },
"require-dev": { "require-dev": {
"bamarni/composer-bin-plugin": "^1.3", "bamarni/composer-bin-plugin": "^1.4.1",
"phpunit/phpunit": "^4.8.35 || ^5.0 || ^6.0 || ^7.0" "phpunit/phpunit": "^4.8.35 || ^5.7.27 || ^6.5.6 || ^7.0 || ^8.0 || ^9.0"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
@ -2570,7 +2598,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2020-06-07T10:40:07+00:00" "time": "2020-07-20T17:29:33+00:00"
}, },
{ {
"name": "psr/container", "name": "psr/container",
@ -4359,16 +4387,16 @@
}, },
{ {
"name": "symfony/polyfill-ctype", "name": "symfony/polyfill-ctype",
"version": "v1.17.1", "version": "v1.18.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git", "url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "2edd75b8b35d62fd3eeabba73b26b8f1f60ce13d" "reference": "1c302646f6efc070cd46856e600e5e0684d6b454"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/2edd75b8b35d62fd3eeabba73b26b8f1f60ce13d", "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/1c302646f6efc070cd46856e600e5e0684d6b454",
"reference": "2edd75b8b35d62fd3eeabba73b26b8f1f60ce13d", "reference": "1c302646f6efc070cd46856e600e5e0684d6b454",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -4380,7 +4408,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.17-dev" "dev-master": "1.18-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -4431,20 +4459,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2020-06-06T08:46:27+00:00" "time": "2020-07-14T12:35:20+00:00"
}, },
{ {
"name": "symfony/polyfill-iconv", "name": "symfony/polyfill-iconv",
"version": "v1.17.1", "version": "v1.18.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-iconv.git", "url": "https://github.com/symfony/polyfill-iconv.git",
"reference": "ba6c9c18db36235b859cc29b8372d1c01298c035" "reference": "6c2f78eb8f5ab8eaea98f6d414a5915f2e0fce36"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/ba6c9c18db36235b859cc29b8372d1c01298c035", "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/6c2f78eb8f5ab8eaea98f6d414a5915f2e0fce36",
"reference": "ba6c9c18db36235b859cc29b8372d1c01298c035", "reference": "6c2f78eb8f5ab8eaea98f6d414a5915f2e0fce36",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -4456,7 +4484,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.17-dev" "dev-master": "1.18-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -4508,25 +4536,26 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2020-06-06T08:46:27+00:00" "time": "2020-07-14T12:35:20+00:00"
}, },
{ {
"name": "symfony/polyfill-intl-idn", "name": "symfony/polyfill-intl-idn",
"version": "v1.17.1", "version": "v1.18.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-intl-idn.git", "url": "https://github.com/symfony/polyfill-intl-idn.git",
"reference": "a57f8161502549a742a63c09f0a604997bf47027" "reference": "bc6549d068d0160e0f10f7a5a23c7d1406b95ebe"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/a57f8161502549a742a63c09f0a604997bf47027", "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/bc6549d068d0160e0f10f7a5a23c7d1406b95ebe",
"reference": "a57f8161502549a742a63c09f0a604997bf47027", "reference": "bc6549d068d0160e0f10f7a5a23c7d1406b95ebe",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.3.3", "php": ">=5.3.3",
"symfony/polyfill-mbstring": "^1.3", "symfony/polyfill-intl-normalizer": "^1.10",
"symfony/polyfill-php70": "^1.10",
"symfony/polyfill-php72": "^1.10" "symfony/polyfill-php72": "^1.10"
}, },
"suggest": { "suggest": {
@ -4535,7 +4564,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.17-dev" "dev-master": "1.18-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -4559,6 +4588,10 @@
"name": "Laurent Bassin", "name": "Laurent Bassin",
"email": "laurent@bassin.info" "email": "laurent@bassin.info"
}, },
{
"name": "Trevor Rowbotham",
"email": "trevor.rowbotham@pm.me"
},
{ {
"name": "Symfony Community", "name": "Symfony Community",
"homepage": "https://symfony.com/contributors" "homepage": "https://symfony.com/contributors"
@ -4588,20 +4621,101 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2020-06-06T08:46:27+00:00" "time": "2020-07-14T12:35:20+00:00"
}, },
{ {
"name": "symfony/polyfill-mbstring", "name": "symfony/polyfill-intl-normalizer",
"version": "v1.17.1", "version": "v1.18.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
"reference": "7110338d81ce1cbc3e273136e4574663627037a7" "reference": "37078a8dd4a2a1e9ab0231af7c6cb671b2ed5a7e"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7110338d81ce1cbc3e273136e4574663627037a7", "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/37078a8dd4a2a1e9ab0231af7c6cb671b2ed5a7e",
"reference": "7110338d81ce1cbc3e273136e4574663627037a7", "reference": "37078a8dd4a2a1e9ab0231af7c6cb671b2ed5a7e",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"suggest": {
"ext-intl": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.18-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Intl\\Normalizer\\": ""
},
"files": [
"bootstrap.php"
],
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for intl's Normalizer class and related functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"intl",
"normalizer",
"polyfill",
"portable",
"shim"
],
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-07-14T12:35:20+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.18.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/a6977d63bf9a0ad4c65cd352709e230876f9904a",
"reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -4613,7 +4727,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.17-dev" "dev-master": "1.18-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -4665,20 +4779,97 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2020-07-14T12:35:20+00:00"
},
{
"name": "symfony/polyfill-php70",
"version": "v1.17.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php70.git",
"reference": "471b096aede7025bace8eb356b9ac801aaba7e2d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/471b096aede7025bace8eb356b9ac801aaba7e2d",
"reference": "471b096aede7025bace8eb356b9ac801aaba7e2d",
"shasum": ""
},
"require": {
"paragonie/random_compat": "~1.0|~2.0|~9.99",
"php": ">=5.3.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.17-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Php70\\": ""
},
"files": [
"bootstrap.php"
],
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-06-06T08:46:27+00:00" "time": "2020-06-06T08:46:27+00:00"
}, },
{ {
"name": "symfony/polyfill-php72", "name": "symfony/polyfill-php72",
"version": "v1.17.0", "version": "v1.18.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-php72.git", "url": "https://github.com/symfony/polyfill-php72.git",
"reference": "f048e612a3905f34931127360bdd2def19a5e582" "reference": "639447d008615574653fb3bc60d1986d7172eaae"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/f048e612a3905f34931127360bdd2def19a5e582", "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/639447d008615574653fb3bc60d1986d7172eaae",
"reference": "f048e612a3905f34931127360bdd2def19a5e582", "reference": "639447d008615574653fb3bc60d1986d7172eaae",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -4687,7 +4878,11 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.17-dev" "dev-master": "1.18-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
} }
}, },
"autoload": { "autoload": {
@ -4720,20 +4915,34 @@
"portable", "portable",
"shim" "shim"
], ],
"time": "2020-05-12T16:47:27+00:00" "funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-07-14T12:35:20+00:00"
}, },
{ {
"name": "symfony/polyfill-php73", "name": "symfony/polyfill-php73",
"version": "v1.17.1", "version": "v1.18.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-php73.git", "url": "https://github.com/symfony/polyfill-php73.git",
"reference": "fa0837fe02d617d31fbb25f990655861bb27bd1a" "reference": "fffa1a52a023e782cdcc221d781fe1ec8f87fcca"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fa0837fe02d617d31fbb25f990655861bb27bd1a", "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fffa1a52a023e782cdcc221d781fe1ec8f87fcca",
"reference": "fa0837fe02d617d31fbb25f990655861bb27bd1a", "reference": "fffa1a52a023e782cdcc221d781fe1ec8f87fcca",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -4742,7 +4951,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.17-dev" "dev-master": "1.18-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -4796,20 +5005,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2020-06-06T08:46:27+00:00" "time": "2020-07-14T12:35:20+00:00"
}, },
{ {
"name": "symfony/polyfill-php80", "name": "symfony/polyfill-php80",
"version": "v1.17.1", "version": "v1.18.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-php80.git", "url": "https://github.com/symfony/polyfill-php80.git",
"reference": "4a5b6bba3259902e386eb80dd1956181ee90b5b2" "reference": "d87d5766cbf48d72388a9f6b85f280c8ad51f981"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4a5b6bba3259902e386eb80dd1956181ee90b5b2", "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/d87d5766cbf48d72388a9f6b85f280c8ad51f981",
"reference": "4a5b6bba3259902e386eb80dd1956181ee90b5b2", "reference": "d87d5766cbf48d72388a9f6b85f280c8ad51f981",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -4818,7 +5027,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.17-dev" "dev-master": "1.18-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -4876,7 +5085,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2020-06-06T08:46:27+00:00" "time": "2020-07-14T12:35:20+00:00"
}, },
{ {
"name": "symfony/polyfill-uuid", "name": "symfony/polyfill-uuid",
@ -5491,22 +5700,22 @@
}, },
{ {
"name": "vlucas/phpdotenv", "name": "vlucas/phpdotenv",
"version": "v3.6.6", "version": "v3.6.7",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/vlucas/phpdotenv.git", "url": "https://github.com/vlucas/phpdotenv.git",
"reference": "4669484ccbc38fe7c4e0c50456778f2010566aad" "reference": "2065beda6cbe75e2603686907b2e45f6f3a5ad82"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/4669484ccbc38fe7c4e0c50456778f2010566aad", "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/2065beda6cbe75e2603686907b2e45f6f3a5ad82",
"reference": "4669484ccbc38fe7c4e0c50456778f2010566aad", "reference": "2065beda6cbe75e2603686907b2e45f6f3a5ad82",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^5.4 || ^7.0 || ^8.0", "php": "^5.4 || ^7.0 || ^8.0",
"phpoption/phpoption": "^1.5.2", "phpoption/phpoption": "^1.5.2",
"symfony/polyfill-ctype": "^1.16" "symfony/polyfill-ctype": "^1.17"
}, },
"require-dev": { "require-dev": {
"ext-filter": "*", "ext-filter": "*",
@ -5560,7 +5769,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2020-06-02T14:08:54+00:00" "time": "2020-07-14T19:04:52+00:00"
} }
], ],
"packages-dev": [ "packages-dev": [
@ -8579,83 +8788,6 @@
], ],
"time": "2020-05-30T20:35:19+00:00" "time": "2020-05-30T20:35:19+00:00"
}, },
{
"name": "symfony/polyfill-php70",
"version": "v1.17.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php70.git",
"reference": "471b096aede7025bace8eb356b9ac801aaba7e2d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/471b096aede7025bace8eb356b9ac801aaba7e2d",
"reference": "471b096aede7025bace8eb356b9ac801aaba7e2d",
"shasum": ""
},
"require": {
"paragonie/random_compat": "~1.0|~2.0|~9.99",
"php": ">=5.3.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.17-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Php70\\": ""
},
"files": [
"bootstrap.php"
],
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-06-06T08:46:27+00:00"
},
{ {
"name": "symfony/stopwatch", "name": "symfony/stopwatch",
"version": "v5.1.2", "version": "v5.1.2",
@ -8875,7 +9007,13 @@
"prefer-stable": true, "prefer-stable": true,
"prefer-lowest": false, "prefer-lowest": false,
"platform": { "platform": {
"php": "^7.2" "php": "^7.2",
"ext-dom": "*",
"ext-intl": "*",
"ext-json": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-pdo": "*"
}, },
"platform-dev": [], "platform-dev": [],
"plugin-api-version": "1.1.0" "plugin-api-version": "1.1.0"

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddNormalizedNameToTags extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('tags', function (Blueprint $table) {
$table->string('normalized_name')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('tags', function (Blueprint $table) {
$table->dropColumn('normalized_name');
});
}
}

View File

@ -1,2 +1,2 @@
User-agent: * User-agent: *
Disallow: Disallow: /search/

View File

@ -41,7 +41,7 @@ export default class TagInput extends Vue {
case 'Enter': case 'Enter':
case ' ': case ' ':
if ((event as any).isComposing !== true) { if ((event as any).isComposing !== true) {
this.tags.push(this.buffer.trim()); this.tags.push(this.buffer.trim().replace(/\s+/g, '_'));
this.buffer = ''; this.buffer = '';
} }
event.preventDefault(); event.preventDefault();
@ -49,7 +49,7 @@ export default class TagInput extends Vue {
case 'Unidentified': case 'Unidentified':
// () // ()
if (event.srcElement && (event.srcElement as HTMLInputElement).value.slice(-1) == ' ') { if (event.srcElement && (event.srcElement as HTMLInputElement).value.slice(-1) == ' ') {
this.tags.push(this.buffer.trim()); this.tags.push(this.buffer.trim().replace(/\s+/g, '_'));
this.buffer = ''; this.buffer = '';
event.preventDefault(); event.preventDefault();
} }

View File

@ -70,4 +70,30 @@ class FormatterTest extends TestCase
$formatter->profileImageSrcSet($profileImageProvider, 128, 2) $formatter->profileImageSrcSet($profileImageProvider, 128, 2)
); );
} }
/**
* @dataProvider provideNormalizeTagName
*/
public function testNormalizeTagName($input, $expected)
{
$formatter = new Formatter();
$normalized = $formatter->normalizeTagName($input);
$this->assertSame($expected, $normalized);
$this->assertSame($expected, $formatter->normalizeTagName($normalized));
}
public function provideNormalizeTagName()
{
return [
'LowerCase' => ['example', 'example'],
'UpperCase' => ['EXAMPLE', 'example'],
'HalfWidthKana' => ['ティッシュ', 'ティッシュ'],
'FullWidthAlphabet' => ['', 'tissue'],
'組み文字1' => ['13㎝', '13cm'],
'組み文字2' => ['13㌢㍍', '13センチメートル'],
'Script' => ['ℬ𝒶𝒷𝓊𝓂𝒾', 'babumi'],
'NFD' => ['オカズ', 'オカズ'],
];
}
} }

View File

@ -1496,9 +1496,9 @@ bluebird@^3.1.1, bluebird@^3.5.5:
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
version "4.11.8" version "4.11.9"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828"
integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==
body-parser@1.19.0: body-parser@1.19.0:
version "1.19.0" version "1.19.0"
@ -2865,9 +2865,9 @@ elegant-spinner@^1.0.1:
integrity sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4= integrity sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=
elliptic@^6.0.0: elliptic@^6.0.0:
version "6.5.2" version "6.5.3"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.2.tgz#05c5678d7173c049d8ca433552224a495d0e3762" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6"
integrity sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw== integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==
dependencies: dependencies:
bn.js "^4.4.0" bn.js "^4.4.0"
brorand "^1.0.1" brorand "^1.0.1"