@@ -13,7 +13,8 @@ return \PhpCsFixer\Config::create()
 | 
			
		||||
        'return_type_declaration' => true,
 | 
			
		||||
        'new_with_braces' => true,
 | 
			
		||||
        'no_empty_statement' => true,
 | 
			
		||||
        'standardize_not_equals' => true
 | 
			
		||||
        'standardize_not_equals' => true,
 | 
			
		||||
        'single_quote' => true
 | 
			
		||||
    ])
 | 
			
		||||
    ->setFinder(
 | 
			
		||||
        \PhpCsFixer\Finder::create()
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										22
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								README.md
									
									
									
									
									
								
							@@ -8,7 +8,7 @@ a.k.a. shikorism.net
 | 
			
		||||
## 構成
 | 
			
		||||
 | 
			
		||||
- Laravel 5.5
 | 
			
		||||
- Bootstrap 4.0
 | 
			
		||||
- Bootstrap 4.2.1
 | 
			
		||||
 | 
			
		||||
## 実行環境
 | 
			
		||||
 | 
			
		||||
@@ -46,8 +46,28 @@ docker-compose exec web php artisan key:generate
 | 
			
		||||
docker-compose exec web php artisan migrate
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
6. ファイルに書き込めるように権限を設定します。
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
docker-compose exec web chown -R www-data /var/www/html
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
7. 最後に `.env` を読み込み直すために起動し直します。
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
docker-compose up -d
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
これで準備は完了です。Tissue が動いていれば `http://localhost:4545/` でアクセスができます。
 | 
			
		||||
 | 
			
		||||
## デバッグ実行
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
docker-compose -f docker-compose.yml -f docker-compose.debug.yml up -d
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
で起動することにより、DB のポート`5432`を開放してホストマシンから接続できるようになります。
 | 
			
		||||
 | 
			
		||||
## 環境構築上の諸注意
 | 
			
		||||
 | 
			
		||||
- 初版時点では、DB サーバとして PostgreSQL を使うよう .env ファイルを設定するくらいです。
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										58
									
								
								app/Http/Controllers/SettingController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								app/Http/Controllers/SettingController.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Http\Controllers;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Http\Request;
 | 
			
		||||
use Illuminate\Support\Facades\Auth;
 | 
			
		||||
use Illuminate\Support\Facades\Validator;
 | 
			
		||||
 | 
			
		||||
class SettingController extends Controller
 | 
			
		||||
{
 | 
			
		||||
    public function profile()
 | 
			
		||||
    {
 | 
			
		||||
        return view('setting.profile');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function updateProfile(Request $request)
 | 
			
		||||
    {
 | 
			
		||||
        $inputs = $request->all();
 | 
			
		||||
        $validator = Validator::make($inputs, [
 | 
			
		||||
            'display_name' => 'required|string|max:20'
 | 
			
		||||
        ], [], [
 | 
			
		||||
            'display_name' => '名前'
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        if ($validator->fails()) {
 | 
			
		||||
            return redirect()->route('setting')->withErrors($validator)->withInput();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $user = Auth::user();
 | 
			
		||||
        $user->display_name = $inputs['display_name'];
 | 
			
		||||
        $user->save();
 | 
			
		||||
 | 
			
		||||
        return redirect()->route('setting')->with('status', 'プロフィールを更新しました。');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function privacy()
 | 
			
		||||
    {
 | 
			
		||||
        return view('setting.privacy');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function updatePrivacy(Request $request)
 | 
			
		||||
    {
 | 
			
		||||
        $inputs = $request->all(['is_protected', 'accept_analytics']);
 | 
			
		||||
 | 
			
		||||
        $user = Auth::user();
 | 
			
		||||
        $user->is_protected = $inputs['is_protected'] ?? false;
 | 
			
		||||
        $user->accept_analytics = $inputs['accept_analytics'] ?? false;
 | 
			
		||||
        $user->save();
 | 
			
		||||
 | 
			
		||||
        return redirect()->route('setting.privacy')->with('status', 'プライバシー設定を更新しました。');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ( ◠‿◠ )☛ここに気づいたか・・・消えてもらう ▂▅▇█▓▒░(’ω’)░▒▓█▇▅▂うわあああああああ
 | 
			
		||||
//    public function password()
 | 
			
		||||
//    {
 | 
			
		||||
//        abort(501);
 | 
			
		||||
//    }
 | 
			
		||||
}
 | 
			
		||||
@@ -83,7 +83,7 @@ SQL
 | 
			
		||||
        ))
 | 
			
		||||
            ->where('user_id', $user->id)
 | 
			
		||||
            ->groupBy(DB::raw("to_char(ejaculated_date, 'HH24')"))
 | 
			
		||||
            ->orderBy(DB::raw("1"))
 | 
			
		||||
            ->orderBy(DB::raw('1'))
 | 
			
		||||
            ->get();
 | 
			
		||||
 | 
			
		||||
        $dailySum = [];
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ class DLsiteResolver implements Resolver
 | 
			
		||||
        if ($res->getStatusCode() === 200) {
 | 
			
		||||
            $ogpResolver = new OGPResolver();
 | 
			
		||||
            $metadata = $ogpResolver->parse($res->getBody());
 | 
			
		||||
            $metadata->image =  str_replace("img_sam.jpg", "img_main.jpg", $metadata->image);
 | 
			
		||||
            $metadata->image =  str_replace('img_sam.jpg', 'img_main.jpg', $metadata->image);
 | 
			
		||||
 | 
			
		||||
            return $metadata;
 | 
			
		||||
        } else {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										42
									
								
								app/MetadataResolver/DeviantArtResolver.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								app/MetadataResolver/DeviantArtResolver.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\MetadataResolver;
 | 
			
		||||
 | 
			
		||||
class DeviantArtResolver implements Resolver
 | 
			
		||||
{
 | 
			
		||||
    public function resolve(string $url): Metadata
 | 
			
		||||
    {
 | 
			
		||||
        $client = new \GuzzleHttp\Client();
 | 
			
		||||
        $res = $client->get($url);
 | 
			
		||||
        if ($res->getStatusCode() === 200) {
 | 
			
		||||
            $ogpResolver = new OGPResolver();
 | 
			
		||||
            $metadata = $ogpResolver->parse($res->getBody());
 | 
			
		||||
 | 
			
		||||
            $dom = new \DOMDocument();
 | 
			
		||||
            @$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8'));
 | 
			
		||||
            $xpath = new \DOMXPath($dom);
 | 
			
		||||
 | 
			
		||||
            $node = $xpath->query('//*[@id="pimp-preload"]/following-sibling::div//img')->item(0);
 | 
			
		||||
            $srcset = $node->getAttribute('srcset');
 | 
			
		||||
            $srcset_array = explode('w,', $srcset);
 | 
			
		||||
            $src = end($srcset_array);
 | 
			
		||||
            $src = preg_replace('~ \d+w$~', '', $src);
 | 
			
		||||
 | 
			
		||||
            if (preg_match('~\.wixmp\.com$~', parse_url($src)['host'])) {
 | 
			
		||||
                // アスペクト比を保ったまま、縦か横が最大700pxになるように変換する。
 | 
			
		||||
                // Ref: https://support.wixmp.com/en/article/image-service-3835799
 | 
			
		||||
                if (strpos($src, '/v1/fill/')) {
 | 
			
		||||
                    $src = preg_replace('~/v1/fill/w_\d+,h_\d+,q_\d+,strp~', '/v1/fit/w_700,h_700,q_70,strp', $src);
 | 
			
		||||
                } else {
 | 
			
		||||
                    $src = $src . '/v1/fit/w_700,h_700,q_70,strp/image.jpg';
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $metadata->image = $src;
 | 
			
		||||
 | 
			
		||||
            return $metadata;
 | 
			
		||||
        } else {
 | 
			
		||||
            throw new \RuntimeException("{$res->getStatusCode()}: $url");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -25,7 +25,7 @@ class FantiaResolver implements Resolver
 | 
			
		||||
            $ogpUrl = $node->getAttribute('content');
 | 
			
		||||
 | 
			
		||||
            // 投稿に画像がない場合(ogp.jpgでない場合)のみ大きい画像に変換する
 | 
			
		||||
            if ($ogpUrl != "http://fantia.jp/images/ogp.jpg") {
 | 
			
		||||
            if ($ogpUrl != 'http://fantia.jp/images/ogp.jpg') {
 | 
			
		||||
                preg_match("~https://fantia\.s3\.amazonaws\.com/uploads/post/file/{$postId}/ogp_(.*?)\.(jpg|png)~", $ogpUrl, $match);
 | 
			
		||||
                $uuid = $match[1];
 | 
			
		||||
                $extension = $match[2];
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										21
									
								
								app/MetadataResolver/FanzaResolver.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								app/MetadataResolver/FanzaResolver.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\MetadataResolver;
 | 
			
		||||
 | 
			
		||||
class FanzaResolver implements Resolver
 | 
			
		||||
{
 | 
			
		||||
    public function resolve(string $url): Metadata
 | 
			
		||||
    {
 | 
			
		||||
        $client = new \GuzzleHttp\Client();
 | 
			
		||||
        $res = $client->get($url);
 | 
			
		||||
        if ($res->getStatusCode() === 200) {
 | 
			
		||||
            $ogpResolver = new OGPResolver();
 | 
			
		||||
            $metadata = $ogpResolver->parse($res->getBody());
 | 
			
		||||
            $metadata->image =  preg_replace("~(pr|ps)\.jpg$~", 'pl.jpg', $metadata->image);
 | 
			
		||||
 | 
			
		||||
            return $metadata;
 | 
			
		||||
        } else {
 | 
			
		||||
            throw new \RuntimeException("{$res->getStatusCode()}: $url");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -2,6 +2,8 @@
 | 
			
		||||
 | 
			
		||||
namespace App\MetadataResolver;
 | 
			
		||||
 | 
			
		||||
use Carbon\Carbon;
 | 
			
		||||
 | 
			
		||||
class KomifloResolver implements Resolver
 | 
			
		||||
{
 | 
			
		||||
    public function resolve(string $url): Metadata
 | 
			
		||||
@@ -21,6 +23,8 @@ class KomifloResolver implements Resolver
 | 
			
		||||
            $metadata->description = ($json['content']['attributes']['artists']['children'][0]['data']['name'] ?? '?') .
 | 
			
		||||
                ' - ' .
 | 
			
		||||
                ($json['content']['parents'][0]['data']['title'] ?? '?');
 | 
			
		||||
            $metadata->image = $json['content']['cdn_public'] . '/564_mobile_large_3x/' . $json['content']['named_imgs']['cover']['filename'] . $json['content']['signature'];
 | 
			
		||||
            $metadata->expires_at = Carbon::parse($json['content']['signature_expires'])->setTimezone(config('app.timezone'));
 | 
			
		||||
 | 
			
		||||
            return $metadata;
 | 
			
		||||
        } else {
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,8 @@ class MetadataResolver implements Resolver
 | 
			
		||||
        '~www\.dlsite\.com/.*/work/=/product_id/..\d+\.html~' => DLsiteResolver::class,
 | 
			
		||||
        '~www\.pixiv\.net/member_illust\.php\?illust_id=\d+~' => PixivResolver::class,
 | 
			
		||||
        '~fantia\.jp/posts/\d+~' => FantiaResolver::class,
 | 
			
		||||
        '~dmm\.co\.jp/~' => FanzaResolver::class,
 | 
			
		||||
        '~www\.deviantart\.com/.*/art/.*~' => DeviantArtResolver::class,
 | 
			
		||||
        '/.*/' => OGPResolver::class
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -18,12 +18,18 @@ class OGPResolver implements Resolver
 | 
			
		||||
    public function parse(string $html): Metadata
 | 
			
		||||
    {
 | 
			
		||||
        $dom = new \DOMDocument();
 | 
			
		||||
        @$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
 | 
			
		||||
        @$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'ASCII,JIS,UTF-8,eucJP-win,SJIS-win'));
 | 
			
		||||
        $xpath = new \DOMXPath($dom);
 | 
			
		||||
 | 
			
		||||
        $metadata = new Metadata();
 | 
			
		||||
 | 
			
		||||
        $metadata->title = $this->findContent($xpath, '//meta[@*="og:title"]', '//meta[@*="twitter:title"]');
 | 
			
		||||
        if (empty($metadata->title)) {
 | 
			
		||||
            $nodes = $xpath->query('//title');
 | 
			
		||||
            if ($nodes->length !== 0) {
 | 
			
		||||
                $metadata->title = $nodes->item(0)->textContent;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        $metadata->description = $this->findContent($xpath, '//meta[@*="og:description"]', '//meta[@*="twitter:description"]');
 | 
			
		||||
        $metadata->image = $this->findContent($xpath, '//meta[@*="og:image"]', '//meta[@*="twitter:image"]');
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -12,8 +12,8 @@ class PixivResolver implements Resolver
 | 
			
		||||
     */
 | 
			
		||||
    public function thumbnailToMasterUrl(string $thumbnailUrl): string
 | 
			
		||||
    {
 | 
			
		||||
        $temp = str_replace("/c/128x128", "", $thumbnailUrl);
 | 
			
		||||
        $largeUrl = str_replace("square1200.jpg", "master1200.jpg", $temp);
 | 
			
		||||
        $temp = str_replace('/c/128x128', '', $thumbnailUrl);
 | 
			
		||||
        $largeUrl = str_replace('square1200.jpg', 'master1200.jpg', $temp);
 | 
			
		||||
 | 
			
		||||
        return $largeUrl;
 | 
			
		||||
    }
 | 
			
		||||
@@ -27,21 +27,21 @@ class PixivResolver implements Resolver
 | 
			
		||||
     */
 | 
			
		||||
    public function proxize(string $pixivUrl): string
 | 
			
		||||
    {
 | 
			
		||||
        return str_replace("i.pximg.net", "i.pixiv.cat", $pixivUrl);
 | 
			
		||||
        return str_replace('i.pximg.net', 'i.pixiv.cat', $pixivUrl);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function resolve(string $url): Metadata
 | 
			
		||||
    {
 | 
			
		||||
        preg_match("~illust_id=(\d+)~", parse_url($url)["query"], $match);
 | 
			
		||||
        preg_match("~illust_id=(\d+)~", parse_url($url)['query'], $match);
 | 
			
		||||
        $illustId = $match[1];
 | 
			
		||||
 | 
			
		||||
        // 漫画ページかつページ数あり
 | 
			
		||||
        if (strpos(parse_url($url)["query"], "mode=manga_big") && strpos(parse_url($url)["query"], "page=")) {
 | 
			
		||||
            preg_match("~page=(\d+)~", parse_url($url)["query"], $match);
 | 
			
		||||
        if (strpos(parse_url($url)['query'], 'mode=manga_big') && strpos(parse_url($url)['query'], 'page=')) {
 | 
			
		||||
            preg_match("~page=(\d+)~", parse_url($url)['query'], $match);
 | 
			
		||||
            $page = $match[1];
 | 
			
		||||
 | 
			
		||||
            // 未ログインでは漫画ページを開けないため、URL を作品ページに変換する
 | 
			
		||||
            $url = str_replace("mode=manga_big", "mode=medium", $url);
 | 
			
		||||
            $url = str_replace('mode=manga_big', 'mode=medium', $url);
 | 
			
		||||
 | 
			
		||||
            $client = new \GuzzleHttp\Client();
 | 
			
		||||
            $res = $client->get($url);
 | 
			
		||||
@@ -55,7 +55,7 @@ class PixivResolver implements Resolver
 | 
			
		||||
                $illustUrl = $this->thumbnailToMasterUrl($illustThumbnailUrl);
 | 
			
		||||
 | 
			
		||||
                // 指定ページに変換
 | 
			
		||||
                $illustUrl = str_replace("p0_master", "p{$page}_master", $illustUrl);
 | 
			
		||||
                $illustUrl = str_replace('p0_master', "p{$page}_master", $illustUrl);
 | 
			
		||||
 | 
			
		||||
                $metadata->image =  $this->proxize($illustUrl);
 | 
			
		||||
 | 
			
		||||
@@ -71,10 +71,10 @@ class PixivResolver implements Resolver
 | 
			
		||||
                $metadata = $ogpResolver->parse($res->getBody());
 | 
			
		||||
 | 
			
		||||
                // OGP がデフォルト画像であるようならなんとかして画像を取得する
 | 
			
		||||
                if (strpos($metadata->image, "pixiv_logo.gif") || strpos($metadata->image, "pictures.jpg")) {
 | 
			
		||||
                if (strpos($metadata->image, 'pixiv_logo.gif') || strpos($metadata->image, 'pictures.jpg')) {
 | 
			
		||||
 | 
			
		||||
                    // 作品ページの場合のみ対応
 | 
			
		||||
                    if (strpos(parse_url($url)["query"], "mode=medium")) {
 | 
			
		||||
                    if (strpos(parse_url($url)['query'], 'mode=medium')) {
 | 
			
		||||
                        preg_match("~https://i\.pximg\.net/c/128x128/img-master/img/\d{4}/\d{2}/\d{2}/\d{2}/\d{2}/\d{2}/{$illustId}(_p0)?_square1200\.jpg~", $res->getBody(), $match);
 | 
			
		||||
                        $illustThumbnailUrl = $match[0];
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -52,12 +52,25 @@ class Formatter
 | 
			
		||||
        $url = preg_replace('~/#!/~u', '/', $url);
 | 
			
		||||
 | 
			
		||||
        // Sort query parameters
 | 
			
		||||
        $query = parse_url($url, PHP_URL_QUERY);
 | 
			
		||||
        if (!empty($query)) {
 | 
			
		||||
            $url = str_replace_last('?' . $query, '', $url);
 | 
			
		||||
            parse_str($query, $params);
 | 
			
		||||
        $parts = parse_url($url);
 | 
			
		||||
        if (!empty($parts['query'])) {
 | 
			
		||||
            // Remove query parameters
 | 
			
		||||
            $url = str_replace_last('?' . $parts['query'], '', $url);
 | 
			
		||||
            if (!empty($parts['fragment'])) {
 | 
			
		||||
                // Remove fragment identifier
 | 
			
		||||
                $url = str_replace_last('#' . $parts['fragment'], '', $url);
 | 
			
		||||
            } else {
 | 
			
		||||
                // "http://example.com/?query#" の場合 $parts['fragment'] は unset になるので、個別に判定して除去する必要がある
 | 
			
		||||
                $url = preg_replace('/#\z/u', '', $url);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            parse_str($parts['query'], $params);
 | 
			
		||||
            ksort($params);
 | 
			
		||||
 | 
			
		||||
            $url = $url . '?' . http_build_query($params);
 | 
			
		||||
            if (!empty($parts['fragment'])) {
 | 
			
		||||
                $url .= '#' . $parts['fragment'];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $url;
 | 
			
		||||
 
 | 
			
		||||
@@ -46,6 +46,9 @@
 | 
			
		||||
        "post-autoload-dump": [
 | 
			
		||||
            "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
 | 
			
		||||
            "@php artisan package:discover"
 | 
			
		||||
        ],
 | 
			
		||||
        "fix": [
 | 
			
		||||
            "php-cs-fixer fix"
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
    "config": {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
use Illuminate\Support\Facades\Schema;
 | 
			
		||||
use Illuminate\Database\Schema\Blueprint;
 | 
			
		||||
use Illuminate\Database\Migrations\Migration;
 | 
			
		||||
use Illuminate\Database\Schema\Blueprint;
 | 
			
		||||
use Illuminate\Support\Facades\Schema;
 | 
			
		||||
 | 
			
		||||
class AddExpiresOnMetadata extends Migration
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								docker-compose.debug.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								docker-compose.debug.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
version: "3"
 | 
			
		||||
 | 
			
		||||
services:
 | 
			
		||||
  db:
 | 
			
		||||
    ports:
 | 
			
		||||
      - 5432:5432
 | 
			
		||||
							
								
								
									
										4
									
								
								public/css/bootstrap.min.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								public/css/bootstrap.min.css
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -11,7 +11,7 @@
 | 
			
		||||
            @endif
 | 
			
		||||
        </h6>
 | 
			
		||||
 | 
			
		||||
        @if (!$user->is_protected)
 | 
			
		||||
        @if (!$user->is_protected || $user->isMe())
 | 
			
		||||
            <h6 class="font-weight-bold mt-4"><span class="oi oi-timer"></span> 現在のセッション</h6>
 | 
			
		||||
            @if (isset($currentSession))
 | 
			
		||||
                <p class="card-text mb-0">{{ $currentSession }}経過</p>
 | 
			
		||||
 
 | 
			
		||||
@@ -63,8 +63,8 @@
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </a>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <p class="mb-2 col-12 px-0">
 | 
			
		||||
                                    <span class="oi oi-link-intact mr-1"></span><a href="{{ $ejaculation->link }}" target="_blank" rel="noopener">{{ $ejaculation->link }}</a>
 | 
			
		||||
                                <p class="d-flex align-items-baseline mb-2 col-12 px-0">
 | 
			
		||||
                                    <span class="oi oi-link-intact mr-1"></span><a class="overflow-hidden" href="{{ $ejaculation->link }}" target="_blank" rel="noopener">{{ $ejaculation->link }}</a>
 | 
			
		||||
                                </p>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        @endif
 | 
			
		||||
 
 | 
			
		||||
@@ -61,8 +61,8 @@
 | 
			
		||||
                                                </div>
 | 
			
		||||
                                            </a>
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                        <p class="mb-2 col-12 px-0">
 | 
			
		||||
                                            <span class="oi oi-link-intact mr-1"></span><a href="{{ $ejaculation->link }}" target="_blank" rel="noopener">{{ $ejaculation->link }}</a>
 | 
			
		||||
                                        <p class="d-flex align-items-baseline mb-2 col-12 px-0">
 | 
			
		||||
                                            <span class="oi oi-link-intact mr-1"></span><a class="overflow-hidden" href="{{ $ejaculation->link }}" target="_blank" rel="noopener">{{ $ejaculation->link }}</a>
 | 
			
		||||
                                        </p>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                @endif
 | 
			
		||||
 
 | 
			
		||||
@@ -72,7 +72,7 @@
 | 
			
		||||
                                </p>
 | 
			
		||||
                            </a>
 | 
			
		||||
                            <div class="dropdown-divider"></div>
 | 
			
		||||
                            {{--<a href="#" class="dropdown-item">設定</a>--}}
 | 
			
		||||
                            <a href="{{ route('setting') }}" class="dropdown-item">設定</a>
 | 
			
		||||
                            <a href="{{ route('logout') }}" class="dropdown-item" onclick="event.preventDefault(); document.getElementById('logout-form').submit();">ログアウト</a>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </li>
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@
 | 
			
		||||
    @else
 | 
			
		||||
        <ul class="list-group">
 | 
			
		||||
        @foreach($results as $ejaculation)
 | 
			
		||||
            <li class="list-group-item border-bottom-only pt-3 pb-3">
 | 
			
		||||
            <li class="list-group-item border-bottom-only pt-3 pb-3 tis-word-wrap">
 | 
			
		||||
                <!-- span -->
 | 
			
		||||
                <div class="d-flex justify-content-between">
 | 
			
		||||
                    <h5>
 | 
			
		||||
@@ -44,8 +44,8 @@
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </a>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <p class="mb-2 col-12 px-0">
 | 
			
		||||
                            <span class="oi oi-link-intact mr-1"></span><a href="{{ $ejaculation->link }}" target="_blank" rel="noopener">{{ $ejaculation->link }}</a>
 | 
			
		||||
                        <p class="d-flex align-items-baseline mb-2 col-12 px-0">
 | 
			
		||||
                            <span class="oi oi-link-intact mr-1"></span><a class="overflow-hidden" href="{{ $ejaculation->link }}" target="_blank" rel="noopener">{{ $ejaculation->link }}</a>
 | 
			
		||||
                        </p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                @endif
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										22
									
								
								resources/views/setting/base.blade.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								resources/views/setting/base.blade.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
@extends('layouts.base')
 | 
			
		||||
 | 
			
		||||
@section('content')
 | 
			
		||||
    <div class="container">
 | 
			
		||||
        <div class="row">
 | 
			
		||||
            <div class="col-lg-4">
 | 
			
		||||
                <div class="list-group">
 | 
			
		||||
                    <div class="list-group-item disabled font-weight-bold">設定</div>
 | 
			
		||||
                    <a class="list-group-item list-group-item-action {{ Route::currentRouteName() === 'setting' ? 'active' : '' }}"
 | 
			
		||||
                       href="{{ route('setting') }}"><span class="oi oi-person mr-1"></span> プロフィール</a>
 | 
			
		||||
                    <a class="list-group-item list-group-item-action {{ Route::currentRouteName() === 'setting.privacy' ? 'active' : '' }}"
 | 
			
		||||
                       href="{{ route('setting.privacy') }}"><span class="oi oi-shield mr-1"></span> プライバシー</a>
 | 
			
		||||
                    {{--<a class="list-group-item list-group-item-action {{ Route::currentRouteName() === 'setting.password' ? 'active' : '' }}"
 | 
			
		||||
                       href="{{ route('setting.password') }}"><span class="oi oi-key mr-1"></span> パスワード</a>--}}
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="tab-content col-lg-8">
 | 
			
		||||
                @yield('tab-content')
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
@endsection
 | 
			
		||||
							
								
								
									
										33
									
								
								resources/views/setting/privacy.blade.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								resources/views/setting/privacy.blade.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
@extends('setting.base')
 | 
			
		||||
 | 
			
		||||
@section('title', 'プライバシー設定')
 | 
			
		||||
 | 
			
		||||
@section('tab-content')
 | 
			
		||||
    <h3>プライバシー</h3>
 | 
			
		||||
    <hr>
 | 
			
		||||
    <form action="{{ route('setting.privacy.update') }}" method="post">
 | 
			
		||||
        {{ csrf_field() }}
 | 
			
		||||
        <div class="form-group">
 | 
			
		||||
            <div class="custom-control custom-checkbox mb-2">
 | 
			
		||||
                <input id="protected" name="is_protected" class="custom-control-input" type="checkbox" {{ (old('is_protected') ?? Auth::user()->is_protected ) ? 'checked' : '' }}>
 | 
			
		||||
                <label class="custom-control-label" for="protected">全てのチェックイン履歴を非公開にする</label>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="custom-control custom-checkbox">
 | 
			
		||||
                <input id="accept-analytics" name="accept_analytics" class="custom-control-input" type="checkbox" {{ (old('accept_analytics') ?? Auth::user()->accept_analytics ) ? 'checked' : '' }}>
 | 
			
		||||
                <label class="custom-control-label" for="accept-analytics">匿名での統計にチェックインデータを利用することに同意します</label>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <button type="submit" class="btn btn-primary mt-2">更新</button>
 | 
			
		||||
    </form>
 | 
			
		||||
@endsection
 | 
			
		||||
 | 
			
		||||
@push('script')
 | 
			
		||||
    <script>
 | 
			
		||||
        $('#protected').on('change', function () {
 | 
			
		||||
            if (!$(this).prop('checked')) {
 | 
			
		||||
                alert('チェックイン履歴を公開に切り替えると、個別に非公開設定されているものを除いた全てのチェックインが誰でも閲覧できるようになります。\nご注意ください。');
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    </script>
 | 
			
		||||
@endpush
 | 
			
		||||
							
								
								
									
										32
									
								
								resources/views/setting/profile.blade.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								resources/views/setting/profile.blade.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
@extends('setting.base')
 | 
			
		||||
 | 
			
		||||
@section('title', 'プロフィール設定')
 | 
			
		||||
 | 
			
		||||
@section('tab-content')
 | 
			
		||||
    <h3>プロフィール</h3>
 | 
			
		||||
    <hr>
 | 
			
		||||
    <form action="{{ route('setting.profile.update') }}" method="post">
 | 
			
		||||
        {{ csrf_field() }}
 | 
			
		||||
        <div class="from-group">
 | 
			
		||||
            <label for="display_name">名前</label>
 | 
			
		||||
            <input id="display_name" name="display_name" type="text" class="form-control {{ $errors->has('display_name') ? ' is-invalid' : '' }}"
 | 
			
		||||
                   value="{{ old('display_name') ?? Auth::user()->display_name }}" maxlength="20" autocomplete="off">
 | 
			
		||||
 | 
			
		||||
            @if ($errors->has('display_name'))
 | 
			
		||||
                <div class="invalid-feedback">{{ $errors->first('display_name') }}</div>
 | 
			
		||||
            @endif
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="from-group mt-2">
 | 
			
		||||
            <label for="name">ユーザー名</label>
 | 
			
		||||
            <div class="input-group">
 | 
			
		||||
                <div class="input-group-prepend">
 | 
			
		||||
                    <div class="input-group-text">@</div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <input id="name" name="name" type="text" class="form-control" value="{{ Auth::user()->name }}" disabled>
 | 
			
		||||
            </div>
 | 
			
		||||
            <small class="form-text text-muted">現在は変更できません。</small>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <button type="submit" class="btn btn-primary mt-4">更新</button>
 | 
			
		||||
    </form>
 | 
			
		||||
@endsection
 | 
			
		||||
@@ -34,7 +34,7 @@
 | 
			
		||||
@else
 | 
			
		||||
    <ul class="list-group">
 | 
			
		||||
        @forelse ($ejaculations as $ejaculation)
 | 
			
		||||
            <li class="list-group-item border-bottom-only pt-3 pb-3">
 | 
			
		||||
            <li class="list-group-item border-bottom-only pt-3 pb-3 tis-word-wrap">
 | 
			
		||||
                <!-- span -->
 | 
			
		||||
                <div class="d-flex justify-content-between">
 | 
			
		||||
                    <h5>{{ $ejaculation->ejaculated_span ?? '精通' }} <a href="{{ route('checkin.show', ['id' => $ejaculation->id]) }}" class="text-muted"><small>{{ $ejaculation->before_date }}{{ !empty($ejaculation->before_date) ? ' ~ ' : '' }}{{ $ejaculation->ejaculated_date->format('Y/m/d H:i') }}</small></a></h5>
 | 
			
		||||
@@ -69,8 +69,8 @@
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </a>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <p class="mb-2 col-12 px-0">
 | 
			
		||||
                            <span class="oi oi-link-intact mr-1"></span><a href="{{ $ejaculation->link }}" target="_blank" rel="noopener">{{ $ejaculation->link }}</a>
 | 
			
		||||
                        <p class="d-flex align-items-baseline mb-2 col-12 px-0">
 | 
			
		||||
                            <span class="oi oi-link-intact mr-1"></span><a class="overflow-hidden" href="{{ $ejaculation->link }}" target="_blank" rel="noopener">{{ $ejaculation->link }}</a>
 | 
			
		||||
                        </p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                @endif
 | 
			
		||||
 
 | 
			
		||||
@@ -29,6 +29,13 @@ Route::middleware('auth')->group(function () {
 | 
			
		||||
    Route::get('/checkin/{id}/edit', 'EjaculationController@edit')->name('checkin.edit');
 | 
			
		||||
    Route::put('/checkin/{id}', 'EjaculationController@update')->name('checkin.update');
 | 
			
		||||
    Route::delete('/checkin/{id}', 'EjaculationController@destroy')->name('checkin.destroy');
 | 
			
		||||
 | 
			
		||||
    Route::redirect('/setting', '/setting/profile', 301);
 | 
			
		||||
    Route::get('/setting/profile', 'SettingController@profile')->name('setting');
 | 
			
		||||
    Route::post('/setting/profile', 'SettingController@updateProfile')->name('setting.profile.update');
 | 
			
		||||
    Route::get('/setting/privacy', 'SettingController@privacy')->name('setting.privacy');
 | 
			
		||||
    Route::post('/setting/privacy', 'SettingController@updatePrivacy')->name('setting.privacy.update');
 | 
			
		||||
//    Route::get('/setting/password', 'SettingController@password')->name('setting.password');
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
Route::get('/info', 'InfoController@index')->name('info');
 | 
			
		||||
 
 | 
			
		||||
@@ -36,9 +36,9 @@ class NijieResolverTest extends TestCase
 | 
			
		||||
        sleep(1);
 | 
			
		||||
        $resolver = new NijieResolver();
 | 
			
		||||
 | 
			
		||||
        $metadata = $resolver->resolve('https://nijie.info/view.php?id=258078');
 | 
			
		||||
        $this->assertEquals('騎乗位ルーミア | しょったれ', $metadata->title);
 | 
			
		||||
        $this->assertEquals("最初は顔をZUN絵で描こうとか思っていたのだが、難しかったのでやめた", $metadata->description);
 | 
			
		||||
        $metadata = $resolver->resolve('https://nijie.info/view.php?id=9537');
 | 
			
		||||
        $this->assertEquals('ニジエがgifに対応したんだってね 奥さん | 黒末アプコ', $metadata->title);
 | 
			
		||||
        $this->assertEquals('アニメgifとか専門外なのでよくわかりませんでした', $metadata->description);
 | 
			
		||||
        $this->assertRegExp('~/nijie\.info/pic/logo~', $metadata->image);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -82,9 +82,9 @@ class NijieResolverTest extends TestCase
 | 
			
		||||
        sleep(1);
 | 
			
		||||
        $resolver = new NijieResolver();
 | 
			
		||||
 | 
			
		||||
        $metadata = $resolver->resolve('https://sp.nijie.info/view.php?id=258078');
 | 
			
		||||
        $this->assertEquals('騎乗位ルーミア | しょったれ', $metadata->title);
 | 
			
		||||
        $this->assertEquals("最初は顔をZUN絵で描こうとか思っていたのだが、難しかったのでやめた", $metadata->description);
 | 
			
		||||
        $metadata = $resolver->resolve('https://nijie.info/view.php?id=9537');
 | 
			
		||||
        $this->assertEquals('ニジエがgifに対応したんだってね 奥さん | 黒末アプコ', $metadata->title);
 | 
			
		||||
        $this->assertEquals('アニメgifとか専門外なのでよくわかりませんでした', $metadata->description);
 | 
			
		||||
        $this->assertRegExp('~/nijie\.info/pic/logo~', $metadata->image);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -25,4 +25,14 @@ class OGPResolverTest extends TestCase
 | 
			
		||||
        $this->assertEquals('The Open Graph protocol enables any web page to become a rich object in a social graph.', $metadata->description);
 | 
			
		||||
        $this->assertEquals('http://ogp.me/logo.png', $metadata->image);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testResolveTitleOnly()
 | 
			
		||||
    {
 | 
			
		||||
        $resolver = new OGPResolver();
 | 
			
		||||
 | 
			
		||||
        $metadata = $resolver->resolve('http://example.com');
 | 
			
		||||
        $this->assertEquals('Example Domain', $metadata->title);
 | 
			
		||||
        $this->assertEmpty($metadata->description);
 | 
			
		||||
        $this->assertEmpty($metadata->image);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										57
									
								
								tests/Unit/Utilities/FormatterTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								tests/Unit/Utilities/FormatterTest.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,57 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Tests\Unit\Utilities;
 | 
			
		||||
 | 
			
		||||
use App\Utilities\Formatter;
 | 
			
		||||
use Tests\TestCase;
 | 
			
		||||
 | 
			
		||||
class FormatterTest extends TestCase
 | 
			
		||||
{
 | 
			
		||||
    public function testNormalizeUrlWithoutQuery()
 | 
			
		||||
    {
 | 
			
		||||
        $formatter = new Formatter();
 | 
			
		||||
 | 
			
		||||
        $url = 'http://example.com/path/to';
 | 
			
		||||
        $this->assertEquals($url, $formatter->normalizeUrl($url));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testNormalizeUrlWithSortedQuery()
 | 
			
		||||
    {
 | 
			
		||||
        $formatter = new Formatter();
 | 
			
		||||
 | 
			
		||||
        $url = 'http://example.com/path/to?foo=bar&hoge=fuga';
 | 
			
		||||
        $this->assertEquals($url, $formatter->normalizeUrl($url));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testNormalizeUrlWithUnsortedQuery()
 | 
			
		||||
    {
 | 
			
		||||
        $formatter = new Formatter();
 | 
			
		||||
 | 
			
		||||
        $url = 'http://example.com/path/to?hoge=fuga&foo=bar';
 | 
			
		||||
        $this->assertEquals('http://example.com/path/to?foo=bar&hoge=fuga', $formatter->normalizeUrl($url));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testNormalizeUrlWithSortedQueryAndFragment()
 | 
			
		||||
    {
 | 
			
		||||
        $formatter = new Formatter();
 | 
			
		||||
 | 
			
		||||
        $url = 'http://example.com/path/to?foo=bar&hoge=fuga#fragment';
 | 
			
		||||
        $this->assertEquals($url, $formatter->normalizeUrl($url));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testNormalizeUrlWithFragment()
 | 
			
		||||
    {
 | 
			
		||||
        $formatter = new Formatter();
 | 
			
		||||
 | 
			
		||||
        $url = 'http://example.com/path/to#fragment';
 | 
			
		||||
        $this->assertEquals($url, $formatter->normalizeUrl($url));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testNormalizeUrlWithSortedQueryAndZeroLengthFragment()
 | 
			
		||||
    {
 | 
			
		||||
        $formatter = new Formatter();
 | 
			
		||||
 | 
			
		||||
        $url = 'http://example.com/path/to?foo=bar&hoge=fuga#';
 | 
			
		||||
        $this->assertEquals('http://example.com/path/to?foo=bar&hoge=fuga', $formatter->normalizeUrl($url));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user