diff --git a/app/Enums/PageMetaRobots.php b/app/Enums/PageMetaRobots.php new file mode 100644 index 000000000..e9077792e --- /dev/null +++ b/app/Enums/PageMetaRobots.php @@ -0,0 +1,43 @@ + '検索結果に表示させない(noindex)', + self::nofollow => 'ページ内のリンク先を検索エンジンに辿らせない(nofollow)', + self::noarchive => '検索結果にキャッシュ(保存版)を出さない(noarchive)', + self::nosnippet => '検索結果にページの説明を表示させない(nosnippet)', + self::noimageindex => 'ページ内の画像を画像検索に載せない(noimageindex)', + ]; + + /** + * 指定された値を説明文の配列に変換 + */ + public static function descriptions(array $values): array + { + $members = static::getMembers(); + + $descriptions = []; + foreach ($values as $value) { + if (array_key_exists($value, $members)) { + $descriptions[] = $members[$value]; + } + } + + return $descriptions; + } +} diff --git a/app/Models/Common/Page.php b/app/Models/Common/Page.php index 42159f280..789dc16ac 100644 --- a/app/Models/Common/Page.php +++ b/app/Models/Common/Page.php @@ -40,6 +40,7 @@ class Page extends Model 'class', 'othersite_url', 'othersite_url_target', + 'meta_robots', 'transfer_lower_page_flag', 'password', ]; @@ -539,6 +540,26 @@ public function getLayoutTitle() } } + /** + * メタrobotsの有効値を取得(自ページから親を遡って検索) + */ + public function getMetaRobots(?Collection $page_tree = null): ?string + { + if (empty($this->id)) { + return null; + } + + $page_tree = $this->getPageTreeByGoingBackParent($page_tree); + + foreach ($page_tree as $page) { + if (!empty($page->meta_robots)) { + return $page->meta_robots; + } + } + + return null; + } + /** * パスワードを要求するかの判断 */ diff --git a/app/Plugins/Manage/PageManage/PageManage.php b/app/Plugins/Manage/PageManage/PageManage.php index 8f3d81c82..276d6bfcf 100644 --- a/app/Plugins/Manage/PageManage/PageManage.php +++ b/app/Plugins/Manage/PageManage/PageManage.php @@ -3,6 +3,7 @@ namespace App\Plugins\Manage\PageManage; use App\Enums\PageCvsIndex; +use App\Enums\PageMetaRobots; use App\Enums\WebsiteType; use App\Models\Common\Buckets; use App\Models\Common\Frame; @@ -12,6 +13,7 @@ use App\Models\Common\PageRole; use App\Models\User\Contents\Contents; use App\Plugins\Manage\ManagePluginBase; +use App\Rules\CustomValiMetaRobots; use App\Rules\CustomValiTextMax; use App\Rules\CustomValiUrlMax; use App\Traits\Migration\MigrationTrait; @@ -183,6 +185,7 @@ private function pageValidator($request, $page_id = null) 'ip_address' => ['nullable', new CustomValiTextMax()], 'othersite_url' => ['nullable', new CustomValiUrlMax()], 'class' => ['nullable', 'max:255'], + 'meta_robots' => ['nullable', 'array', new CustomValiMetaRobots()], ]); $validator->setAttributeNames([ 'page_name' => 'ページ名', @@ -193,10 +196,46 @@ private function pageValidator($request, $page_id = null) 'ip_address' => 'IPアドレス制限', 'othersite_url' => '外部サイトURL', 'class' => 'メニュークラス名', + 'meta_robots' => '検索避け設定', ]); return $validator; } + /** + * meta robotsの入力値を正規化 + */ + private function normalizeMetaRobots($request): ?string + { + $meta_robots = $request->input('meta_robots'); + + if (is_array($meta_robots)) { + $meta_robots = array_filter($meta_robots, function ($value) { + return $value !== null && $value !== ''; + }); + + if (empty($meta_robots)) { + return null; + } + + $meta_robots = array_unique($meta_robots); + + $allowed = PageMetaRobots::getMemberKeys(); + $meta_robots = array_values(array_intersect($allowed, $meta_robots)); + + if (empty($meta_robots)) { + return null; + } + + return implode(',', $meta_robots); + } + + if (is_string($meta_robots) && $meta_robots !== '') { + return in_array($meta_robots, PageMetaRobots::getMemberKeys(), true) ? $meta_robots : null; + } + + return null; + } + /** * CSVインポート時のエラーチェックルール */ @@ -235,6 +274,8 @@ public function store($request) return redirect()->back()->withErrors($validator)->withInput(); } + $meta_robots = $this->normalizeMetaRobots($request); + // ページデータの登録 $page = new Page; $page->page_name = $request->page_name; @@ -251,6 +292,7 @@ public function store($request) $page->othersite_url = $request->othersite_url; $page->othersite_url_target = (isset($request->othersite_url_target) ? $request->othersite_url_target : 0); $page->transfer_lower_page_flag = $request->transfer_lower_page_flag ?? 0; + $page->meta_robots = $meta_robots; $page->class = $request->class; $page->save(); @@ -278,6 +320,8 @@ public function update($request, $page_id) return redirect()->back()->withErrors($validator)->withInput(); } + $meta_robots = $this->normalizeMetaRobots($request); + // ページデータの更新 Page::where('id', $page_id) ->update([ @@ -295,6 +339,7 @@ public function update($request, $page_id) 'othersite_url' => $request->othersite_url, 'othersite_url_target' => (isset($request->othersite_url_target) ? $request->othersite_url_target : 0), 'transfer_lower_page_flag' => $request->transfer_lower_page_flag ?? 0, + 'meta_robots' => $meta_robots, 'class' => $request->class, ]); diff --git a/app/Rules/CustomValiMetaRobots.php b/app/Rules/CustomValiMetaRobots.php new file mode 100644 index 000000000..61c797a07 --- /dev/null +++ b/app/Rules/CustomValiMetaRobots.php @@ -0,0 +1,61 @@ + + */ + private $invalidValues = []; + + /** + * Determine if the validation rule passes. + */ + public function passes($attribute, $value) + { + if (is_null($value) || $value === '') { + return true; + } + + $values = []; + + if (is_array($value)) { + $values = $value; + } else { + $values = [$value]; + } + + $values = array_filter($values, function ($item) { + return $item !== null && $item !== ''; + }); + + if (empty($values)) { + return true; + } + + $allowed = PageMetaRobots::getMemberKeys(); + + $this->invalidValues = array_diff($values, $allowed); + + return empty($this->invalidValues); + } + + /** + * Get the validation error message. + */ + public function message() + { + if (!empty($this->invalidValues)) { + return ':attributeに不正な値(' . implode(', ', $this->invalidValues) . ')が含まれています。'; + } + + return ':attributeに不正な値が含まれています。'; + } +} diff --git a/database/migrations/2025_09_25_101737_add_meta_robots_to_pages_table.php b/database/migrations/2025_09_25_101737_add_meta_robots_to_pages_table.php new file mode 100644 index 000000000..f017ccac0 --- /dev/null +++ b/database/migrations/2025_09_25_101737_add_meta_robots_to_pages_table.php @@ -0,0 +1,32 @@ +string('meta_robots')->nullable()->after('transfer_lower_page_flag')->comment('検索避け設定(robots meta)'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('pages', function (Blueprint $table) { + $table->dropColumn('meta_robots'); + }); + } +} diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index 1afa40296..b53eaa31f 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -41,6 +41,17 @@ @if (Configs::getConfigsValue($cc_configs, 'description')) @endif +{{-- 検索避け設定 --}} +@php + $meta_robots = null; + if (isset($page)) { + $page_tree = app('request')->attributes->get('page_tree'); + $meta_robots = $page->getMetaRobots($page_tree); + } +@endphp +@if ($meta_robots) + +@endif {{-- OGP Settings --}} @if (Configs::getConfigsValue($cc_configs, 'og_site_name')) diff --git a/resources/views/plugins/manage/page/page.blade.php b/resources/views/plugins/manage/page/page.blade.php index 878da2c11..4c07d84a5 100644 --- a/resources/views/plugins/manage/page/page.blade.php +++ b/resources/views/plugins/manage/page/page.blade.php @@ -6,6 +6,7 @@ * @category ページ管理 --}} @php +use App\Enums\PageMetaRobots; use App\Models\Common\Page; @endphp @@ -196,6 +197,7 @@ function select_page(source_id, page_name) { + @@ -420,6 +422,29 @@ function select_page(source_id, page_name) {
@if($page_item->othersite_url)@endif
+ + @if ($page_item->meta_robots) + @php + $meta_robots_descriptions = implode('、', PageMetaRobots::descriptions(explode(',', $page_item->meta_robots))); + $meta_robots_tooltip = $meta_robots_descriptions ?: $page_item->meta_robots; + @endphp +
+ @else + @php + $meta_robots_parent = null; + foreach ($page_tree as $page_tmp) { + if ($page_tmp->meta_robots) { + $meta_robots_parent = $page_tmp->meta_robots; + break; + } + } + $meta_robots_parent_description = $meta_robots_parent ? implode('、', PageMetaRobots::descriptions(explode(',', $meta_robots_parent))) : ''; + @endphp + @if ($meta_robots_parent) +
+ @endif + @endif +
@if($page_item->class)@endif
diff --git a/resources/views/plugins/manage/page/page_form.blade.php b/resources/views/plugins/manage/page/page_form.blade.php index 15510b996..208e68c01 100644 --- a/resources/views/plugins/manage/page/page_form.blade.php +++ b/resources/views/plugins/manage/page/page_form.blade.php @@ -9,6 +9,7 @@ * @category ページ管理 --}} @php +use App\Enums\PageMetaRobots; use App\Models\Common\Page; @endphp @@ -23,7 +24,7 @@ {{ csrf_field() }} @php - // 自分のページから親を遡って取得(+トップページ) + // 検索避け設定で継承表示に使うため、自分自身から親ページを順に取得 $page_tree = $page->getPageTreeByGoingBackParent(null); @endphp @@ -45,6 +46,57 @@ + {{-- 検索避け設定 --}} + @php + // チェックボックスの候補と初期選択値を準備 + $meta_robot_options = PageMetaRobots::getMembers(); + $selected_meta_robots = old('meta_robots', $page->meta_robots ? explode(',', $page->meta_robots) : []); + if (!is_array($selected_meta_robots)) { + $selected_meta_robots = [$selected_meta_robots]; + } + $selected_meta_robots = array_values(array_filter($selected_meta_robots, function ($value) { + return $value !== null && $value !== ''; + })); + $selected_meta_robots = array_values(array_intersect(array_keys($meta_robot_options), $selected_meta_robots)); + + // 親ページから継承される検索避け設定を判定 + $meta_robots_page_parent = new Page(); + foreach ($page_tree as $page_tmp) { + if ($page_tmp->meta_robots) { + $meta_robots_page_parent = $page_tmp; + break; + } + } + $meta_robots_parent_description = ''; + if ($meta_robots_page_parent->meta_robots) { + $meta_robots_parent_description = implode('、', PageMetaRobots::descriptions(explode(',', $meta_robots_page_parent->meta_robots))); + } + @endphp +
+ +
+
+ Google などの検索エンジンに「このページをどう扱ってほしいか」を伝えるための設定です。 +
+ @foreach ($meta_robot_options as $value => $label) +
+ + +
+ @endforeach + @include('plugins.common.errors_inline', ['name' => 'meta_robots']) + + @if (!$page->meta_robots && $meta_robots_page_parent->id) +
+ 設定なしのため、親ページ「{{$meta_robots_page_parent->page_name}} 」の検索避け設定を継承しています。
+ 「{{$meta_robots_parent_description}}」 +
+ @endif + + 複数選択できます。チェックしない場合は親ページの設定を継承します。 + 設定しても検索結果へすぐに反映されるわけではありません。検索エンジン側の再巡回と判断を待つ必要があります。 +
+