ntorelabo
【Amazon ECS】Docker で開発した Web アプリケーションを AWS へデプロイする方法
Laravelでファイルアップロード

「ファイルのアップロード」がどういう流れで行われるのか?

  1. ブラウザ(クライアント)側:
<!-- ファイル送信用のフォーム -->
<form method="POST" enctype="multipart/form-data">
    <input type="file" name="uploadFile">
    <button>送信</button>
</form>

enctype="multipart/form-data" という部分がないとファイルを正しく送信できません。

なぜなら:

  • ファイルには画像やPDFなど、テキスト以外のデータ(バイナリデータ)が含まれます
  • そのため、特別な送信方式(multipart/form-data)が必要になります
  • ファイル送信:ヘッダー情報(メタデータ)とファイルデータが分かれて送信される
  • これは、ファイルには画像やPDFなど、テキスト以外のデータが含まれるため、特別な形式(multipart/form-data)が必要になるためです。

▼プレビュー

ユーザーがこのフォームで「ファイルを選択」して「アップロード」ボタンを押すと、選択したファイルがサーバーに送られます。

<ファイルアップロードのフロー>

<POSTリクエストの違い>

<サーバーサイドのフロー>

メモリ領域 一時ファイル保管 (バッファ) バリデーション – サイズチェック – 形式チェック ストレージ – ファイル保存 – 権限設定 エラーハンドリング エラー発生時の処理 処理の流れ: 1. ファイルデータをメモリに一時保管 2. バリデーションで各種チェック実行 3. 問題なければストレージに保存 4. エラー時は適切なエラーハンドリング
Feature BranchでのGitマージ戦略:安全なコード統合のためのベストプラクティス

Gitを使用したチーム開発において、feature branchとmain branchの統合は重要なポイントとなります。この記事では、安全かつ効率的なブランチ統合の戦略について解説します。

なぜFeature Branchで先にマージするのか?

Feature branchで開発を進める中で、main branchの変更を早めに取り込むことには、いくつかの重要な利点があります:

  1. コンフリクトの早期発見
  2. 段階的な統合テストが可能
  3. 最終的なmainブランチへの統合がスムーズ

具体的な手順

1. Mainの変更を取り込む

まず、feature branchで作業中に、main branchの変更を取り込みます:

# 現在のブランチを確認
git branch

# mainの変更を取り込む
git merge main

2. コンフリクト対応

この時点でコンフリクトが発生した場合の対処:

  1. コンフリクトが発生したファイルの確認
  2. 各ファイルのコンフリクトを解決
  3. 解決したファイルをステージング
git add <コンフリクトを解決したファイル>
  1. マージの完了
git commit -m "Resolve conflicts with main"
main feature mainの変更 feature開発 mainマージ& コンフリクト確認 最終マージ

テストと確認

コンフリクト解決後の重要なステップ:

  • 機能が正常に動作するか確認
  • テストの実行
  • 必要に応じて追加の修正を実施

4. Mainブランチへの統合

すべての確認が完了したら、main branchへの統合を行います:

# mainブランチに切り替え
git checkout main

# featureの変更を取り込む
git merge feature

このアプローチのメリット

  1. リスク管理
    • コンフリクトを早期に発見し、feature branch上で解決できる
    • 問題が発生しても、main branchには影響が及ばない
  2. 品質管理
    • feature branch上で十分なテストが可能
    • 統合前に動作確認を完了できる
  3. スムーズな統合
    • 最終的なmain branchへの統合がスムーズになる
    • 大きな問題が発生するリスクが低減される

まとめ

この方法は、特に以下のような状況で効果を発揮します:

  • 長期間に渡る開発プロジェクト
  • 複数人で同じコードベースを扱う場合
  • クリティカルな機能の開発時

早めにmainの変更を取り込んで確認することで、最終的な統合をより安全に行うことができます。これは、モダンなGit開発フローにおける重要なプラクティスの一つと言えるでしょう。

【Python】バックエンドの理解とフロントエンド開発のポイント

Pythonの実行環境について

Docker環境があれば、Python実行環境を新たにインストールする必要はありません

バックエンドとの連携

確認すべき重要な点:

APIエンドポイントの情報

  • APIのURLとパス
  • 各エンドポイントの機能
  • HTTPメソッド(GET, POST等)
  • 必要なリクエストヘッダー

データ形式

// 例:こういった形でAPIの型定義を作成すると良い
interface ApiResponse {
  status: string;
  data: {
    id: number;
    name: string;
    // その他の項目
  }[];
}
  1. .gitignoreファイルの更新:
Copynode_modules
.env
.env.local
.DS_Store
  1. package.jsonscriptsセクションを確認/追加:
jsonCopy{
  "scripts": {
    "start": "node src/index.js",
    "build": "npm install",
    "vercel-build": "npm run build"
  }
}

デプロイ手順:

  1. GitHubにリポジトリを作成:
bashCopygit init
git add .
git commit -m "Initial commit"
git remote add origin <your-github-repo-url>
git push -u origin main
  1. Vercelでのデプロイ設定:
  • vercel.com にGitHubアカウントでログイン
  • 「New Project」をクリック
  • GitHubリポジトリをインポート
  • プロジェクト設定で環境変数を設定: CopyAWS_ACCESS_KEY_ID=xxxxx AWS_SECRET_ACCESS_KEY=xxxxx AWS_REGION=ap-northeast-1
  • 「Deploy」をクリック

これで自動的にデプロイが開始され、完了すると公開URLが提供されます。その後はGitHubにプッシュするたびに自動デプロイが実行されます。

【WordPress】Atuaテーマ

Atuaテーマ概要

「Atua」というテーマは比較的新しく、インストール数も多くなくあまりメジャーではないといえます。

そのため情報が少なく下記の手段で調べる必要がありそうです

  • テーマの公式ページ
  • 製作者サイト
  • GitHubリポジトリ(もしあれば)でカスタマイズガイドやREADMEファイル

参考に「Lightning」と比較し見ると下記です

AtuaLightning
バージョン1.0.9815.29.0
Active installations600+100,000+
PHP version5.67.4
WordPress version4.76.4

Atua の子テーマ Atus でカスタマイズ

下記ページでテーマファイルはダウンロード可能です

子テーマをインストールするだけで大分デザインが変更されます

Atua 独自で作成した子テーマでカスタマイズ

wp-content/themes/atua-child/を作成

ディレクトリ構成

atua-child
 ┗┳ style.css
  ┣ archive.php
  ┣ 

style.css

/*
Theme Name: atua Child
Template: atua
*/

WordPress管理画面でテーマ追加画面で子テーマとして認識されます

(注意)テーマを有効化するとフロントページのコンテンツが表示されなくなってしまいます。

archive.php

<?php
/**
 * The template for displaying archive pages.
 *
 * @link https://codex.wordpress.org/Template_Hierarchy
 *
 * @package Atua
 */

get_header();
?>
<style>
	.dt_post_item {
		margin-bottom: 2rem;

	}
	.dt_post_item > a {
		display: flex;
		align-items: center;
		column-gap: 1rem;
	}

	.post-thumbnail-wrapper {
		flex-shrink: 0;
		position: relative;
		width: 18%;
		aspect-ratio: 16 / 9;
		overflow: hidden;
	}

	.post-thumbnail-wrapper img {
		position: absolute;
		top: 0;
		left: 0;
		width: 100%;
		height: 100%;
		object-fit: cover;
	}
</style>
<section class="dt_posts dt-py-default">
	<div class="dt-container">
		<div class="dt-row dt-g-5">
			<?php if (  !is_active_sidebar( 'atua-sidebar-primary' ) ): ?>
				<div class="dt-col-lg-12 dt-col-md-12 dt-col-12 wow fadeInUp">
			<?php else: ?>	
				<div class="dt-col-lg-8 dt-col-md-12 dt-col-12 wow fadeInUp">
			<?php endif; ?>	
				<?php if( have_posts() ): ?>
					<?php 
					// Start the loop.
					while( have_posts() ) : the_post(); ?>
						<div class="dt_post_item wow fadeInUp animated" data-wow-delay="100ms" data-wow-duration="1500ms">
							<?php 
							// 元のテーマファイルのコメントアウト
							// get_template_part('template-parts/content','page-2'); 
							?>
							<a href="<?php the_permalink(); ?>">
								<div class="post-thumbnail-wrapper">
									<?php 
										if ( has_post_thumbnail() ) {
											the_post_thumbnail('medium');
										} else {
											echo '<img src="https://www.shoshinsha-design.com/wp-content/uploads/2020/05/noimage-760x460.png" alt="Default Image">';
										}
									?>
								</div>
								<?php the_title(); ?>
							</a>
						</div>
					<?php endwhile; 
					// End the loop.
					
					 // Pagination.
						the_posts_pagination( array(
							'prev_text'          => '<i class="fa fa-angle-double-left"></i>',
							'next_text'          => '<i class="fa fa-angle-double-right"></i>'
						) );
						
						// If no content, include the "No posts found" template.
					else: 
						get_template_part('template-parts/content','none'); 
					endif; ?>
			</div>
			<?php get_sidebar(); ?>
		</div>
	</div>
</section>
<?php get_footer(); ?>
WordPressサイト全体を非表示にする一般的な方法

プラグインでメンテナンス表示

「Maintenance」プラグイン

.htaccessファイルで全体にアクセス制限をかける

1. .htaccess ファイルを編集

FTPソフトを使ってWordPressのルートディレクトリ(wp-config.phpがある場所)にある .htaccess ファイルを編集します。

# メンテナンスモード設定開始
RewriteEngine On

# メンテナンスページ (maintenance.html) へのアクセスは除外
RewriteCond %{REQUEST_URI} !/maintenance.html$

# 全てのアクセスを maintenance.html にリダイレクト
RewriteRule ^(.*)$ /maintenance.html [R=302,L]

# メンテナンスモード設定終了

サブディレクトリ環境での修正方法

# WordPressが /wordpress/ にインストールされている場合
RewriteEngine On

# maintenance.html へのアクセスは除外
RewriteCond %{REQUEST_URI} !^/wordpress/maintenance.html$

# すべてのアクセスを maintenance.html にリダイレクト
RewriteRule ^(.*)$ /wordpress/maintenance.html [R=302,L]

2. maintenance.html の作成

WordPressのルートディレクトリに maintenance.html ファイルを作成し、メンテナンス案内ページとしてアップロードします。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>メンテナンス中</title>
    <!-- CSSスタイルで簡単なデザイン -->
    <style>
        body {
            text-align: center;
            font-family: Arial, sans-serif;
            background-color: #f4f4f4;
            margin: 0;
            padding: 0;
        }
        .container {
            margin-top: 20%;
        }
        h1 {
            color: #333;
            font-size: 2em;
        }
        p {
            color: #666;
            font-size: 1.2em;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>メンテナンス中</h1>
        <p>現在、サイトはメンテナンス中です。しばらくお待ちください。</p>
    </div>
</body>
</html>

WordPressの「設定」で検索エンジンから非表示にする

  1. WordPress管理画面にログイン。
  2. 「設定」→「表示設定」→「検索エンジンでの表示」の項目で「検索エンジンがサイトをインデックスしないようにする」にチェック。
  3. 「変更を保存」ボタンをクリック。

注意: この方法は「検索エンジン」には非表示にできますが、直接URLにアクセスすれば閲覧可能です。

【Amazon WorkSpaces】トラブル、対処法

「WorkSpaceは数秒で準備完了になります…」から変わらない

  • 上記画面で終了してしまう
  • 朝スマホのデザリングで問題発生
  • 夜自宅Wifiですると問題なく接続できる
  • 他のスマホのデザリングでは接続できる
  • 問題のスマホ再起動で接続可能に

VBAの実践的なテクニックを網羅的に解説

マクロとVBAの概念

「マクロ」は2つの意味があります

「マクロ」はExcelを自動処理してくれるもExcelの機能です。

そして「VBA(Visual Basic for Applications)」という言語で命令書を作成することを「マクロを組む」といいます。

マクロはどうやって作成するか?

  • 自動で記録(マクロ記録)
  • 自分で書く(VBE(Visual Basic Editor)というツール)…Excelで[Alt] + [F11]

マクロの記録方法

再度押すと記録再開されます。

記録したマクロを使用する方法

記録したマクロを選択し、「実行」すると記録した処理が実行されます

「編集」でVBEを起動して編集できます

VBEを開く手順

開発タブを表示させる

「開発」の項目はExcelインストールしたばかりの状態だとだとはチェックが外れいるので有効化しておきます。

表示された「開発」タブから「Visual Basic Editor」をクリック

[Alt] + [F11]でも開けます

マクロのセキュリティレベル

Excelのセルやワークシートを操作できるだけでなく、悪意のある者が作成すればパソコンに障害を引き起こすような動作もプログラミングすることができます。

そうした悪意のあるマクロが勝手に実行されないように、セキュリティレベルを設定する機能があります。

[ファイル] → [オプション] → [トラスト センター]

既定では1度目のみ警告が表示される

VBEでプロシージャを実行すると警告が出る

プローシージャとは

マクロ動作の基本単位「プロシージャ」には「Sub」と「Function」があります。

サブ(Sub)とは

**サブプロシージャ(Sub)**は、「処理手順のまとまり」を表すひとまとまりのコードブロックです。「この手順を実行してね」と指示できる単位と考えると分かりやすいです。Subは処理を実行するための命令文のかたまりであり、戻り値を返しません

サブの基本形

Sub サブ名()
    ' ここに実行したい処理を書きます
End Sub

処理内容はインデント(字下げ)するのがお作法です

  • Functionで始まりEnd Functionで終わる
  • Function名を処理結果として代入することで戻り値を設定します(関数名 = 値
  • 戻り値は数値でも文字列でもオブジェクトでも、データ型を適宜指定できます
  • Functionはワークシート関数としても使える場合があり、Excelのセルに独自の関数として呼び出せます(ただしパブリック関数として標準モジュールに記述が必要)
  • 別のSubやFunctionから「戻り値 = 関数名(引数)」という形で呼び出して、計算結果などを受け取ることができます
【Amazon Bedrock】【Node.js】生成AIを使用したVBAフォーマッター

プロジェクトの内容

Amazon Bedrockの生成AIを活用した、VBAマクロコードを自動的にフォーマットするWebアプリケーション。インデント、変数名、コメントなどを統一的なルールで整形します。

機能概要

  • VBAコードのフォーマット
  • インデントの自動調整
  • 変数名の標準化
  • コメントの追加
  • ヘッダー・フッターの挿入

技術スタック

  • Node.js
  • Express.js
  • HTML/CSS/JavaScript

必要要件

  • Node.js (v14以上)
  • npm (v6以上)

セットアップ手順

  1. プロジェクト構成ファイルを作成
  2. 依存パッケージのインストール
npm install
  1. 開発サーバーの起動
npm run dev
  1. ブラウザでアクセス
http://localhost:3000

プロジェクト構成

vba-formatter/
├── src/
│   ├── index.js           # メインサーバーファイル
│   ├── formatter.js       # 基本的なフォーマット処理
│   ├── bedrock/          # Bedrock関連の新規ディレクトリ
│   │   ├── client.js     # Bedrock クライアント設定
│   │   ├── prompts.js    # プロンプトテンプレート
│   │   └── analyzer.js   # コード分析ロジック
│   └── public/           # フロントエンド

Bedorock関連

.env

AWS_ACCESS_KEY_ID=あなたのアクセスキー
AWS_SECRET_ACCESS_KEY=あなたのシークレットキー
AWS_REGION=us-east-1

dotenvは、.envファイルを読み込んで、その内容を環境変数としてprocess.envに設定するためのパッケージです。

npm install dotenv

@aws-sdk/client-bedrock-runtime: Amazon Bedrockと通信するため下記インストール

npm install dotenv @aws-sdk/client-bedrock-runtime
client.js:Amazon Bedrockへの接続設定
require('dotenv').config();
const { BedrockRuntimeClient } = require("@aws-sdk/client-bedrock-runtime");

// Bedrockクライアントの設定
const client = new BedrockRuntimeClient({ 
    region: process.env.AWS_REGION || "us-east-1",
    credentials: {
        accessKeyId: process.env.AWS_ACCESS_KEY_ID,
        secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
    }
});

module.exports = client;
prompts.js:AIへの指示(プロンプト)テンプレート
/**
* prompts.js
* AIへの指示内容を定義するファイル
* VBAコードの分析と改善の方法をAIに指示します
*/

// AIへの指示内容を定義(バッククォートで囲むことで複数行の文字列を作成)
const ANALYSIS_PROMPT = `
# ここからがAIへの指示内容です
=============================================

# AIの役割定義
あなたはVBAコードの専門家として、以下のコードを分析し、優先順位に従って改善してください。

# 入力コード部分
\`\`\`vba
{code}
\`\`\`

# 改善要件(優先順位:高 - 第一弾)
=============================================
1. インデント処理
  - SUBからENDSUBまでTABひとつ分のインデント
  - IF文、With文、For文等の制御構造にもインデント
  - ネストレベルに応じて適切なインデント

2. ヘッダー・フッター追加
  - Sub開始時のヘッダー:
    Option Explicit
    '***************************************************
    ' [プロシージャ名]
    '***************************************************
    '【機  能】[機能の説明]
    '【引  数】[引数の説明]
    '【戻 り 値】[戻り値の説明]
    '【機能説明】[詳細な説明]
    '【備  考】[その他特記事項]
    ' Copyright(c) 2024 Your Company All Rights Reserved.
    '***************************************************
    
  - 処理の区切り:
    '***************************************************
    ' 変数宣言
    '***************************************************
    [変数宣言部分]

  - Sub終了時のフッター:
    '***************************************************
    '---------------------------------------------------
    ' Version
    '---------------------------------------------------
    ' 1.00 | 2024.01.01 | ********* | *********
    '***************************************************

# 改善要件(優先順位:中 - 第二弾)
=============================================
3. 変数名の改善
  - 一文字の変数を禁止
  - セル参照の変数は意味のある名前に変更
    (例: Last_Row, Last_Column)
  - 配列はArrayを付ける
  - その他の変数はDataを付ける

4. Public変数・Call文の処理
  - Public変数にはPublic関数であることをコメントで記載
  - Call文にはモジュール名を追加
    (例: Call Module1.印刷)

5. 変数宣言のコメント
  - 宣言した変数の横にコメントを追加
  - 使用目的や内容を簡潔に説明

# 改善要件(優先順位:中 - 第三弾)
=============================================
6. 変数・シート名の処理
  - 日本語変数名を英語に変更
  - 未宣言変数を「変数宣言」セクションに追加
  - 型判定できない変数はVariantに
  - シート名は定数(Const)で定義
  - シート追加時の名前はVariantで処理

7. コメント追加
  - IF文、With文などの制御構造にコメント
  - 配列の内容説明
  - SET文の説明
  - Offsetのコメント必須
    (参照セルと目的を明記)

8. コードの最適化
  - 類似コードが3回以上続く場合はループ化
  - パスの直書きは避け、pathに置換
    (元のパスはコメントとして保持)

# 出力形式
=============================================
優先順位の高い要件(第一弾)から順に適用し、
改善したVBAコードのみを出力してください。
`;

module.exports = {
   ANALYSIS_PROMPT
};
analyzer.js:Bedrockとの通信処理
// AWS SDKからBedrockのクライアントとコマンドをインポート
const { InvokeModelCommand } = require("@aws-sdk/client-bedrock-runtime");
const client = require('./client');
const { ANALYSIS_PROMPT } = require('./prompts');

/**
 * VBAコードをClaudeモデルで分析する関数
 * @param {string} vbaCode - 分析対象のVBAコード
 * @returns {Promise<string>} - 改善されたVBAコード
 */
async function analyzeCode(vbaCode) {
    try {
        // プロンプトテンプレートにVBAコードを挿入
        const prompt = ANALYSIS_PROMPT.replace('{code}', vbaCode);

        // Claude用のリクエストコマンドを作成
        const command = new InvokeModelCommand({
            // Claude 3 Sonnetモデルを指定
            modelId: "anthropic.claude-3-sonnet-20240229-v1:0",
            contentType: "application/json",
            accept: "application/json",
            // Claude用のリクエスト形式
            body: JSON.stringify({
                anthropic_version: "bedrock-2023-05-31",
                max_tokens: 2048,
                messages: [
                    {
                        role: "user",
                        content: prompt
                    }
                ]
            })
        });

        // BedrockAPIを呼び出し
        const response = await client.send(command);
        
        // レスポンスの処理(Claude形式)
        const responseBody = Buffer.from(response.body).toString('utf8');
        const jsonResponse = JSON.parse(responseBody);

        // Claudeのレスポンス形式から結果を抽出
        if (jsonResponse.content && jsonResponse.content[0]) {
            return jsonResponse.content[0].text;
        }

        throw new Error('AIモデルからの応答が不正な形式です');
    } catch (error) {
        console.error("コード分析エラー:", error);
        throw error;
    }
}

module.exports = {
    analyzeCode
};

メインファイル

src/index.js:Webサーバーのメイン処理
// ========================================
// サーバーのメインファイル (index.js)
// VBAフォーマッターのバックエンド処理を担当
// ========================================

// 必要なモジュールのインポート
const express = require('express');
const { analyzeCode } = require('./bedrock/analyzer');  // Bedrock AIの分析機能
const { formatVBA } = require('./formatter'); // 基本的なフォーマット機能
const { ANALYSIS_PROMPT } = require('./bedrock/prompts'); // AIプロンプトの取得

// Expressアプリケーションの初期化
const app = express();

// ミドルウェアの設定
// 静的ファイル(HTML/CSS/JS)の提供設定
app.use(express.static('src/public')); 
// JSONリクエストの解析を有効化
app.use(express.json());

/**
 * AIへのプロンプト内容を提供するエンドポイント
 * フロントエンドでAIへの指示内容を表示するために使用
 */
app.get('/prompts', (req, res) => {
    res.json({ 
        success: true, 
        prompt: ANALYSIS_PROMPT 
    });
});

/**
 * VBAコード分析API
 * フロントエンドから送信されたコードを分析・改善
 */
app.post('/format', async (req, res) => {
    try {
        // リクエストからコードを取得
        const { code } = req.body;
        
        // 入力チェック
        if (!code) {
            return res.status(400).json({ 
                success: false, 
                error: 'コードが提供されていません' 
            });
        }

        // 1. 基本的なフォーマットを適用
        const basicFormatted = formatVBA(code);

        // 2. AIによる高度な分析と改善
        const aiAnalysis = await analyzeCode(basicFormatted);
        
        // 成功時のレスポンス
        res.json({ 
            success: true, 
            formatted: aiAnalysis
        });

    } catch (error) {
        // エラー発生時の処理
        console.error('エラーが発生しました:', error);
        res.status(500).json({ 
            success: false, 
            error: error.message 
        });
    }
});

// サーバーの起動設定
const PORT = process.env.PORT || 3000;

// 全てのIPアドレスからのアクセスを許可(社内ネットワーク用)
app.listen(PORT, '0.0.0.0', () => {
    console.log('========================================');
    console.log(`VBA Formatter サーバー起動`);
    console.log(`http://localhost:${PORT}`);
    console.log('========================================');
});

フロントエンド

public/index.html:ユーザーインターフェース
<!DOCTYPE html>
<html lang="ja">
<head>
    <!-- 文字エンコーディングとビューポートの設定 -->
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>VBA Formatter</title>
    <!-- reset.css ress -->
    <link rel="stylesheet" href="https://unpkg.com/ress/dist/ress.min.css" />
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link rel="stylesheet" href="style.css">
    <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@100..900&display=swap" rel="stylesheet">
    <!-- スタイルシートの読み込み -->
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <!-- メインコンテナ -->
    <div class="container">
        <h1>VBA Formatter</h1>

        <!-- AIへの指示内容表示セクション(src/bedrock/prompts.jsの内容) -->
        <div class="prompt-section">
            <h3>AIへの指示内容</h3>
            <!-- プロンプト内容を動的に表示する要素 -->
            <div class="prompt-display-container">
                <div class="user-icon"></div>
                <div class="prompt-display-wrapper">
                    <pre class="prompt-display">ユーザーからの指示内容がここに表示されます</pre>
                </div>
            </div>
        </div>

        <!-- コードエディタ部分 -->
        <div class="editors">
            <!-- 入力用エディタ -->
            <div class="editor">
                <h3>分析するコード</h3>
                <!-- 入力用テキストエリア:ユーザーがコードを入力または貼り付け可能 -->
                <textarea id="input" placeholder="VBAコードを入力してください"></textarea>
                
                <!-- ファイル選択ボタン:.basと.txtファイルのみ受付 -->
                <input type="file" id="vbaFile" accept=".bas,.txt">
                <!-- <span class="detail">※.basと.txtファイルのみ受付</span> -->
            </div>
            <!-- 出力用エディタ -->
            <div class="editor">
                <h3>改善後のコード</h3>
                <!-- 出力用テキストエリア:読み取り専用で改善されたコードを表示 -->
                <textarea id="output" readonly></textarea>
                
                <!-- フォーマット実行ボタン -->
                <button onclick="formatCode()">Format</button>
            </div>
        </div>
    </div>

    <!-- JavaScriptの処理 -->
    <script>
        // ページ読み込み時にプロンプト内容を取得
        window.onload = async function() {
            try {
                const response = await fetch('/prompts');
                const data = await response.json();
                if (data.success) {
                    document.querySelector('.prompt-display').textContent = data.prompt;
                }
            } catch (error) {
                console.error('プロンプト取得エラー:', error);
                document.querySelector('.prompt-display').textContent = 'プロンプトの読み込みに失敗しました';
            }
        };

        /**
         * コードのフォーマットを実行する関数
         * 1. 入力を取得
         * 2. サーバーにリクエスト
         * 3. 結果を表示
         */
        async function formatCode() {
            // テキストエリアから入力を取得
            const input = document.getElementById('input').value;
            // ステータス表示用の要素取得
            const formatButton = document.querySelector('button');
            
            // 入力値のバリデーション
            if (!input) {
                alert('VBAコードを入力してください');
                return;
            }

            // 処理開始のステータス表示
            formatButton.innerHTML = '<span class="processing"><span></span><span></span><span></span></span>';

            try {
                // サーバーへのリクエスト
                const response = await fetch('/format', {
                    method: 'POST',  // POSTメソッドを使用
                    headers: {
                        'Content-Type': 'application/json'  // JSONデータとして送信
                    },
                    body: JSON.stringify({ code: input })  // 入力をJSONに変換
                });

                // レスポンスの処理
                const data = await response.json();
                
                // 成功時は結果を表示、失敗時はエラーメッセージを表示
                if (data.success) {
                    document.getElementById('output').value = data.formatted;
                    formatButton.textContent = '分析完了!';
                } else {
                    formatButton.textContent = 'エラー: ' + data.error;
                }
            } catch (error) {
                // 通信エラーなどの例外処理
                formatButton.textContent = 'エラー: ' + error.message;
            }
            formatButton.classList.remove('processing'); // アニメーションクラスを削除
        }

        /**
         * ファイル選択時の処理
         * 選択されたファイルの内容を入力エリアに表示
         */
        document.getElementById('vbaFile').addEventListener('change', (e) => {
            // 選択されたファイルを取得
            const file = e.target.files[0];
            if (file) {
                // FileReaderを使用してファイルの内容を読み込み
                const reader = new FileReader();
                // ファイル読み込み完了時の処理
                reader.onload = (e) => {
                    // 読み込んだ内容を入力エリアに設定
                    document.getElementById('input').value = e.target.result;
                };
                // ファイルをテキストとして読み込み開始
                reader.readAsText(file);
            }
        });
    </script>
</body>
</html>
public/style.css:見た目の装飾
body {
    font-family: "Noto Sans JP", serif;
      background-color: #f1f2f3;
  }
  /* メインコンテナ */
  .container {
    max-width: 1200px;
    margin: 0 auto;
    padding: 0 2rem 2rem;
  }
  
  h1 {
      background-color: #fff;
      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
      text-align: center;
      letter-spacing: 0.1em;
      padding: 1rem 0;
      margin: 0 calc(50% - 50vw);
      width: 100vw;
      margin-bottom: 2rem;
  }
  
  h3 {
    margin-bottom: 1rem;
      text-align: center;
      letter-spacing: .1em;
  }
  
  /* AIへの指示内容表示部分 */
  .prompt-section {
    margin-bottom: 2rem;
  }
  
  .prompt-display-container {
      display: flex;
    align-items: end;
      justify-content: center;
    gap: 2rem;
      margin: 0 auto;
  }
  
  .user-icon {
    width: 40px;
    height: 40px;
    background-color: #f6ce55;
    border-radius: 50%;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
      margin-bottom: 3rem;
      position: relative;
  }
  
  .user-icon::after {
    content: "";
    position: absolute;
    top: 96%;
    left: 50%;
    transform: translateX(-50%);
    width: 50px;
    height: 40px;
    background-color: #f6ce55;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    border-top-left-radius: 50%;
    border-top-right-radius: 50%;
  }
  
  .prompt-display-wrapper {
      max-width: 80%;
      display: flex;
      align-items: center;
      justify-content: center;
      background-color: #fff;
    border-radius: 5rem 5rem 5rem 0;
    box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
      padding: 2rem;
  }
  
  .prompt-display {
    font-family: monospace;
    font-size: 0.825rem;
    line-height: 1.5;
    white-space: pre-wrap;
      height: 300px;
      overflow-y: scroll;
      resize: vertical;
  }
  
  /* カスタムスクロールバーのスタイル */
  .prompt-display::-webkit-scrollbar {
    width: 0.5rem; /* スクロールバーの幅 */
  }
  
  .prompt-display::-webkit-scrollbar-thumb {
    background: #888;
    border-radius: 1rem;
  }
  
  /* エディタ部分 */
  .editors {
    display: flex;
    gap: 2rem;
      margin-bottom: 1rem;
  }
  
  .editor {
    flex: 1;
      display: flex;
      flex-direction: column;
  }
  
  .editor > * {
      margin-bottom: 1rem;
  }
  
  .editor > *:last-child {
      margin-top: auto!important;
      }
  
  .editor input {
      height: 3rem;
      background-color: #ccc;
    padding: 0.5rem 1rem;
    border-radius: 4px;
      width: 100%;
      transition: all 0.3s ease;
  }
  
  .editor input:hover {
      background-color: #bbb;
  }
  
  .editor .detail {
      margin-top: 0.5rem;
      color: #FF565D;
  }
  
  /* テキストエリア */
  .editor textarea {
    width: 100%;
    height: 500px;
    font-family: monospace;
    padding: 10px;
    font-size: 14px;
    line-height: 1.5;
    border: 2px solid #ccc;
      background-color: #fff;
    border-radius: 4px;
  }
  
  button {
      height: 3rem;
    padding: 0.5rem 1rem;
    background-color: #e9be3b;
      ;
    border-radius: 4px;
    color: white;
      letter-spacing: 0.1em;
    border: none;
    cursor: pointer;
  }
  
  button:hover {
    background-color: #d3b458;
      ;
      transition: all 0.3s ease;
  }
  
  /* アニメーションを追加するクラス */
  .processing {
    display: inline-flex;
    gap: 0.5rem;
  }
  
  .processing span {
    display: inline-block;
    width: 0.75rem;
    height: 0.75rem;
    background-color: #fff;
    border-radius: 50%;
    animation: fadeDots 1.5s infinite ease-in-out;
  }
  
  /* それぞれのドットに異なるアニメーションの遅延を設定 */
  .processing span:nth-child(1) {
    animation-delay: 0s;
  }
  
  .processing span:nth-child(2) {
    animation-delay: 0.2s;
  }
  
  .processing span:nth-child(3) {
    animation-delay: 0.4s;
  }
  
  /* 薄さ(透明度)を変化させるアニメーション */
  @keyframes fadeDots {
    0%, 100% { opacity: 0.2; } /* 薄くなる */
    50% { opacity: 1; } /* 濃くなる */
  }
  
  

その他

formatter.js:VBAコードの基本的な整形処理
// VBAコードをフォーマットする関数
// 引数:code - フォーマットする元のVBAコードの文字列
// 戻り値:フォーマット済みのVBAコードの文字列
function formatVBA(code) {
    // ステップ1: コードを行単位に分割
    // splitは文字列を指定した区切り文字(ここでは改行)で配列に分割する
    const lines = code.split('\n');
    
    // ステップ2: インデントレベルを管理する変数を初期化
    // インデントレベルは、コードのネストの深さを表す
    // 例:Sub内なら1、If文の中なら2、というように増えていく
    let indentLevel = 0;
    
    // ステップ3: フォーマット済みの行を格納する配列を準備
    const formattedLines = [];

    // ステップ4: キーワードを定義
    // これらの単語が行の先頭にあるかどうかでインデントを判断する
    const startKeywords = ['sub', 'function', 'if', 'for', 'do'];
    const endKeywords = ['end sub', 'end function', 'end if', 'next', 'loop'];

    // ステップ5: 各行を処理
    // forEachは配列の各要素に対して処理を行う
    lines.forEach((line, lineNumber) => {
        // 行の空白を削除して、小文字に変換(比較用)
        const trimmedLine = line.trim();
        const lowerLine = trimmedLine.toLowerCase();

        // ステップ6: 空行の処理
        if (trimmedLine === '') {
            formattedLines.push('');
            return; // 次の行の処理へ
        }

        // ステップ7: インデントレベルの調整
        // 7.1: ブロックの終了を示すキーワードの前にインデントを減らす
        if (endKeywords.some(keyword => lowerLine.startsWith(keyword))) {
            indentLevel = Math.max(0, indentLevel - 1);
        }

        // ステップ8: 行のフォーマット
        // 8.1: 現在のインデントレベルに応じてスペースを追加
        // repeat()は文字列を指定回数繰り返す
        const indent = '    '.repeat(indentLevel);
        
        // 8.2: コメントの処理
        // シングルクォートで始まる行はコメント
        if (trimmedLine.startsWith("'")) {
            formattedLines.push(indent + trimmedLine);
        } else {
            // 8.3: 通常のコードの処理
            formattedLines.push(indent + trimmedLine);
        }

        // ステップ9: 次の行のためのインデントレベルの更新
        // 9.1: ブロックの開始を示すキーワードの後でインデントを増やす
        if (startKeywords.some(keyword => lowerLine.startsWith(keyword))) {
            indentLevel++;
        }
    });

    // ステップ10: フォーマット済みの行を改行文字で結合して1つの文字列に戻す
    return formattedLines.join('\n');
}

// ステップ11: ヘッダーコメントを生成する関数
function createHeader() {
    return [
        "Option Explicit",
        "'***************************************************",
        "' VBAプログラム",
        "'***************************************************",
        "'【機  能】",
        "'【返 り 値】なし",
        "'【機能説明】",
        "'【備  考】",
        "' Copyright(c) " + new Date().getFullYear(),
        "'***************************************************",
        ""
    ].join('\n');
}

// ステップ12: 変数をチェックして適切な型を提案する関数
function suggestVariableType(variableName) {
    // 変数名から型を推測
    if (variableName.toLowerCase().includes('count')) {
        return 'Long';
    } else if (variableName.toLowerCase().includes('date')) {
        return 'Date';
    } else if (variableName.toLowerCase().includes('flag')) {
        return 'Boolean';
    } else if (variableName.toLowerCase().includes('name')) {
        return 'String';
    } else {
        return 'Variant';
    }
}

// ステップ13: モジュールの外部公開
// このファイルの関数を他のファイルから使用できるようにする
module.exports = {
    formatVBA,
    createHeader,
    suggestVariableType
};

プロジェクト設定ファイル

package.json:依存パッケージの管理
{
    "name": "vba-formatter",
    "version": "1.0.0",
    "description": "VBA Code Formatter",
    "main": "src/index.js",
    "scripts": {
        "start": "node src/index.js",
        "dev": "nodemon src/index.js"
    },
    "dependencies": {
        "@aws-sdk/client-bedrock-runtime": "^3.712.0",
        "dotenv": "^16.4.7",
        "express": "^4.18.2",
        "multer": "^1.4.5-lts.1"
    },
    "devDependencies": {
        "nodemon": "^3.0.2"
    }
}
test-connection.js:正しく動作するかテスト
require('dotenv').config();
const { BedrockRuntimeClient, InvokeModelCommand } = require("@aws-sdk/client-bedrock-runtime");

async function testBedrock() {
    console.log("環境変数確認:");
    console.log("Region:", process.env.AWS_REGION);
    console.log("Access Key ID exists:", !!process.env.AWS_ACCESS_KEY_ID);
    console.log("Secret Access Key exists:", !!process.env.AWS_SECRET_ACCESS_KEY);

    const client = new BedrockRuntimeClient({
        region: process.env.AWS_REGION || "us-east-1",
        credentials: {
            accessKeyId: process.env.AWS_ACCESS_KEY_ID,
            secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
        }
    });

    // モデルごとに適切なリクエスト本文を生成する関数
    function getRequestBody(modelId) {
        if (modelId.startsWith('amazon.titan')) {
            return {
                inputText: "Hello",
                textGenerationConfig: {
                    maxTokenCount: 10,
                    temperature: 0.7,
                    topP: 1
                }
            };
        } else if (modelId.startsWith('anthropic')) {
            return {
                anthropic_version: "bedrock-2023-05-31",
                max_tokens: 1024,
                messages: [
                    {
                        role: "user",
                        content: "Hello, Claude!"
                    }
                ]
            };
        }
    }

    // テストするモデルのリスト
    const modelIds = [
        // Titan Models(動作確認済み)
        "amazon.titan-text-express-v1",
        "amazon.titan-text-lite-v1",

        // Mistral Models
        "mistral.mistral-7b-instruct-v0:2",
        "mistral.mistral-large-2402-v1:0",
        "mistral.mistral-large-2407-v1:0",
        "mistral.mistral-small-2402-v1:0",
        "mistral.mixtral-8x7b-instruct-v0:1",

        // Claude Models
        "anthropic.claude-3-sonnet-20240229-v1:0",  // Claude 3 Sonnet
        "anthropic.claude-3-haiku-20240307-v1:0",   // Claude 3 Haiku
        "anthropic.claude-3-opus-20240229-v1:0",    // Claude 3 Opus
        "anthropic.claude-3-5-haiku-20241022-v1:0", // Claude 3.5 Haiku
        "anthropic.claude-3-5-sonnet-20241022-v2:0",// Claude 3.5 Sonnet v2
        "anthropic.claude-3-5-sonnet-20240620-v1:0",// Claude 3.5 Sonnet
        "anthropic.claude-v2:1",                    // Claude 2.1
        "anthropic.claude-v2",                      // Claude 2
        "anthropic.claude-instant-v1"               // Claude Instant
    ];

    // 各モデルをテスト
    for (const modelId of modelIds) {
        console.log(`\nテスト: ${modelId}`);
        try {
            const command = new InvokeModelCommand({
                modelId: modelId,
                contentType: "application/json",
                accept: "application/json",
                body: JSON.stringify(getRequestBody(modelId))
            });
            const response = await client.send(command);
            console.log(`✅ ${modelId} は利用可能です`);
            
            // レスポンスの内容を表示
            const responseBody = Buffer.from(response.body).toString('utf8');
            console.log('応答:', responseBody);
        } catch (error) {
            console.log(`❌ ${modelId}: ${error.message}`);
        }
    }
}

testBedrock();
node test-connection.js
https://docs.aws.amazon.com/bedrock/latest/userguide/models-supported.html

使用方法

  1. Webブラウザで http://localhost:3000 にアクセス
  2. テキストエリアにVBAコードを貼り付け、または.basファイルを選択
  3. 「Format」ボタンをクリック
  4. 右側のテキストエリアにフォーマット済みコードが表示

要件定義

### VBA Formatter with Amazon Bedrock プロジェクト説明

#### プロジェクト概要
このプロジェクトはJavaScript言語でNode.jsを利用し、ExpressというWebアプリケーションフレームワークで作成されています。VBAコードを改善するためにAmazon BedrockのAIを活用しています。

#### ファイル構成と役割
1. `src/public/index.html`
   - Webブラウザで表示されるユーザーインターフェース
   - VBAコードの入力とAI改善結果の表示を担当

2. `src/index.js`
   - サーバープログラムのメインファイル
   - ブラウザからのリクエストを処理
   - AIとの連携を管理

3. `src/bedrock/prompts.js`
   - AIへの指示内容(プロンプト)を定義
   - コードの改善ルールを記述

4. `src/bedrock/analyzer.js`
   - Amazon BedrockのAIと通信
   - AIからの応答を処理

#### 処理の流れ
1. ブラウザでVBAコードを入力
2. サーバーがコードを受け取り
3. AIに分析を依頼
4. 改善されたコードが表示

#### 改善点
これまでAWSコンソールのAmazon Bedrockプレイグラウンドでチャット形式で行っていたAIとの対話を、使いやすいWebインターフェースで実現。以下が改善されました:
- コードの入力が簡単に
- AIへの指示内容が明確に表示
- 結果の表示が見やすく
- 複数のコードを連続して処理可能

#### 拡張性
- AIへの指示内容(プロンプト)を簡単に更新可能
- 新しい改善ルールの追加が容易
- チーム固有のコーディングルールも組み込み可能

#### 今後の展開
- より詳細なコード分析機能の追加
- チーム固有のルールの組み込み
- バッチ処理機能の追加

要確認

formatter.jsで生成A使用しない処理を使用できていない

改善項目

AWS SDK の追加活用

  • CloudWatch Logs: AIの応答ログを収集・分析
  • S3: VBAコードのバージョン管理や履歴保存
  • DynamoDB: 変換履歴や頻出パターンの保存

UI

  • 差分表示機能(基本的なnpmパッケージで実現可能)

vercelで公開

ExpressがNext.jsではないのにスムーズな理由は:

  • Vercelは「Next.jsのためのプラットフォーム」ではなく
  • 「モダンなWebアプリケーションのためのプラットフォーム」
  • ExpressもNode.jsアプリケーションとして自然にサポート
  • @vercel/nodeが複雑な変換を全て自動化

このため、ExpressアプリケーションでもNext.jsと同じようにスムーズにデプロイできるのです。

  1. @vercel/node
ExpressのVercelデプロイ 1. vercel.json追加 @vercel/node で Express を自動変換 2. GitHubにプッシュ コードをGitHubにアップロード 3. 環境変数設定 Vercel管理画面で環境変数を設定 Vercelが自動で処理: SSL証明書・ドメイン割り当て・デプロイ

デプロイフロー

vercel.jsonの作成

vercel.jsonは、Vercelにデプロイする際の設定ファイルです。

vercel.json
{
  "version": 2,
  "builds": [
    {
      "src": "src/index.js",
      "use": "@vercel/node"
    },
    {
      "src": "src/public/**",
      "use": "@vercel/static"
    }
  ],
  "routes": [
    {
      "src": "/prompts",
      "dest": "/src/index.js"
    },
    {
      "src": "/format",
      "dest": "/src/index.js"
    },
    {
      "src": "/api/(.*)",
      "dest": "/src/index.js"
    },
    {
      "src": "/(.+\\.[a-zA-Z]+)$",
      "dest": "/src/public/$1"
    },
    {
      "src": "/(.*)",
      "dest": "/src/public/index.html"
    }
  ]
}
.gitignore
node_modules
.env
.env.local
.DS_Store

GitHubにリポジトリを作成

vercel.com にGitHubアカウントでログイン

GitHubリポジトリをインポート

.envの内容を「Environment Variables」設定

Deployで公開完了

お名前.comでFTPソフトを使用する手順

お名前ドットコム管理画面

レンタルサーバーのコントロールパネルに入ります

「基本設定」→「ファイル管理」クリック

該当のドメインを選択→「はじめる」

設定情報で確認できる下記がFTPソフトで接続する際に必要です

  • ユーザー名
  • パスワード
  • FTPソフト

FTPソフトでの接続

SFTPで接続できない場合FTPでする必要があります

「FTPサーバー」が「ホスト名」に該当します

FTPソフト設定画面のサンプル ※ 重要: SFTPではなく、FTPを使用してください。 ※ お名前.com画面の「FTPサーバー」の値を  「ホスト名」に入力してください。 FTP設定 転送プロトコル: FTP ▼ ホスト名: www1234.onamae.ne.jp ポート: 21 ユーザー名: user@example.onamae.ne.jp パスワード: •••••••• 接続先 ディレクトリ: /home/rXXXXXXX/public_html/ 接続
PHPのサーバー変数を確認する方法

Webサーバー経由とターミナル実行時の$_SERVER変数の違い

Webサーバー経由での実行で確認(アクセス)することが重要です

Webサーバー経由でアクセスすることで$_SERVERにはHTTPリクエストの情報が含まれます

$_SERVER変数の実行環境による違い php -a $_SERVER = [ ‘HOSTNAME’, ‘HOME’, ‘TERM’, // HTTPデータなし ]; ターミナル実行 $_SERVER = [ ‘HTTP_HOST’, ‘REQUEST_METHOD’, ‘REMOTE_ADDR’, ‘HTTP_USER_AGENT’, // HTTPデータあり ]; Webサーバー経由実行 Webサーバー経由の実行では、HTTPリクエストに関連する追加情報が$_SERVER変数に含まれます

Webサーバー経由でアクセスする手順

  1. プロジェクト内にPHPファイルを作成
  2. ブラウザからアクセス

Docker Desktopで開発している場合

対象のコンテナを選択

「Exec」タブから、順番に以下のコマンドを実行

パーミッションの確認と必要に応じて変更

ls -l /var/www/html/server-info.php
chmod 644 /var/www/html/server-info.php

PHPファイルの作成

echo '<?php
header("Content-Type: application/json");
echo json_encode($_SERVER);' > /var/www/html/server-info.php

Webサーバー経由でアクセス

curl localhost/server-info.php

<注意点>

パスが/var/www/html/でない場合は、プロジェクトの正しいパスに変更してください

Next.jsのSSGサイトにGoogleアドセンスを設置する手順

Next.jsのSSGサイトへのGoogle AdSenseの設置方法

自動広告

自動広告とは

1つのコードを設置するだけで、AIが自動的にコンテンツを分析し、最適な位置に広告を配置する仕組み

自動広告の配置イメージ 青色部分:AIが自動で広告を配置
import './globals.css'
import Script from 'next/script';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <head>
        <Script
          async
          src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"
          data-ad-client="xxxxx"
          strategy="afterInteractive"
          crossOrigin="anonymous"
        />
      </head>
      <body><main className="container">
          {children}
        </main></body>
    </html>
  );
}
Next.js SSG レスポンシブでレンダリングのちらつき

https://www.gaji.jp/blog/2023/10/12/17283

【GTM】カスタムイベントをトリガーにして確実にCV計測

カスタムイベントとは:

  • データレイヤーを通じてGTMに送信される独自のイベントです
  • 通常のページビューやクリックなどの標準イベントとは異なり、開発者が任意のタイミングで発火できます
  • JavaScriptを使って明示的にトリガーする必要があります

カスタムイベントの設定方法:

  1. Webサイトのコード側で、以下のようなDataLayer.pushを実装します:
dataLayer.push({
  'event': 'customEventName',  // イベント名
  'eventCategory': 'category', // 任意のパラメータ
  'eventAction': 'action',     // 任意のパラメータ
  // その他必要なデータ
});
  1. GTM側では:
  • トリガーの新規作成で「カスタムイベント」を選択
  • イベント名に、dataLayer.pushで指定した’event’の値(例:’customEventName’)を設定

発火のタイミング:

  • フォーム送信完了時
  • 動画の再生開始/終了時
  • スクロール到達時
  • モーダル表示時 など、サイトの要件に応じて任意のタイミングで発火させることができます

具体例:サンクスページでの計測(よくある課題)

フォーム送信 → サンクスページ遷移 → 計測

よくある課題

このような通常のフローだと、以下のリスクがあります:

  • ページ遷移の途中でユーザーが離脱してしまう
  • 通信エラーでサンクスページが表示されない
  • ブラウザバックなどで誤カウント
  • サンクスページが正しく読み込まれない

計測のタイミングを前倒し

コンバージョン計測方式の比較 従来の方式(リスクあり) 開始 フォーム送信 サンクス遷移 計測 ❌ 遷移失敗のリスク virtual_pageview方式(推奨) 開始 フォーム送信 計測 サンクス遷移 ✓ 確実な計測が可能
  • フォーム送信完了時に仮想的なページビューイベントを発火
  • 実際のページ遷移ではなく、JavaScriptによって発火される

Google TagManagerの推奨プラクティスの1つより確実にコンバージョンを計測するための、一般的で信頼性の高い方法だと言えます。多くのウェブサイトで採用されている標準的なアプローチです。

フォーム送信 → 【ここで計測】 → サンクスページ遷移
dataLayer.push({
  'event': 'virtual_pageview',
  // 必要に応じて追加パラメータ
});

トリガーにカスタムイベントを設定

<トリガーの設定>

トリガーのタイプカスタムイベント
イベント名virtual_pageview(✓正規表現一致を使用)
このトリガーの発生場所すべてのカスタムイベント

    注意点

      • 各広告媒体専用のCVタグを個別に紐付ける必要がある
      • 例えば、LINE広告のCV計測をする場合は、LINE用のCVタグを別途このトリガーに紐付ける必要がある
      • タグが紐付けられていない媒体は計測されない
      広告運用とCV計測の全体フロー 広告媒体 Google広告 LINE広告 Yahoo!広告 ランディングページ フォーム 申し込み Google Tag Manager virtual_pageview トリガー 紐付けられるコンバージョンタグ Google広告 LINE広告 Yahoo!広告 広告クリック フォーム送信時に発火

      本日のCV計測に関するMTGについて、参加をご相談させていただきたく存じます。
      現在、以下の点について理解を深めたいと考えております: ・各広告媒体でのCV計測の仕組み ・各LPでのコンバージョン実装方法 ・virtual_pageviewの具体的な運用方法 まだ理解が不十分な部分もあり、MTGを通じて学ばせていただければと存じます。

      Amazon Bedrockの開発環境構築とAPI活用ガイド

      はじめに

      Amazon Bedrockの活用方法は、コンソールのプレイグラウンドだけではありません。開発環境を構築し、外部APIと連携することで、より高度な機能の実装が可能になります。

      従来のAmazon Bedrockのコンソール画面からの基本的な操作

      AWSマネジメントコンソールにログイン

      「Amazon Bedrock」を選択

      「Model access」を選択 使用したいモデルの横にあるチェックボックスを選択

      左側メニューから「Playground」を選択

      設定 1. Temperature(回答の硬さ・柔らかさ) 0.0 硬め 0.5 標準 1.0 柔らかめ 硬め:ビジネス文書・校閲 標準:一般的な会話 柔らかめ:創作・アイデア出し 2. Top-P(回答の選択肢の幅) 0.1 狭い 0.5 中間 0.9 広い 狭い:限定的な選択肢から選ぶ 中間:バランスの取れた選択 広い:多様な選択肢から選ぶ(推奨)

      開発環境の選択肢

      1. ローカル開発環境

      • 自分のPCに開発環境を構築
      • 必要なツール:
        • Node.js/Python
        • AWS CLI
        • コードエディタ(VS Codeなど)
      • メリット:カスタマイズ性が高い

      2. AWS Cloud9

      • AWSが提供するクラウドベースのIDE
      • AWSサービスとの統合が事前設定済み
      • ブラウザから直接開発可能

      3. AWS CloudShell

      • AWSコンソールから直接利用可能なシェル環境
      • AWS認証情報が事前設定済み
      • 基本的なAWSツールが利用可能
      Amazon Bedrock 開発環境の選択肢 ローカル開発環境 – VS Code – AWS CLI – Node.js/Python AWS Cloud9 – クラウドIDE – AWSと統合済み – ブラウザベース AWS CloudShell – ブラウザベースシェル – 事前認証済み – AWSツール搭載済み 必要なコンポーネント AWS SDK – AWS公式ライブラリ – 多言語対応 – APIラッパー 認証情報 – アクセスキーID – シークレットアクセスキー – リージョン設定 Bedrock設定 – モデルアクセス権限 – APIエンドポイント – 使用量設定

      AWS SDKについて

      AWS SDKとは:

      AWS Software Development Kit(SDK)は、AWSの各種サービスをプログラムから利用するためのライブラリ群です。AIに限らず、以下のようなAWSサービス全般を扱えます

      AWS SDK 活用例 Amazon Bedrock テキスト校閲 Claude Titan Text JSON Response 画像・音声生成 Stable Diffusion Titan Image Base64 Output OCR処理 Textract S3連携 Block Detection 集計分析 Claude分析 Comprehend QuickSight連携

      AWS SDKの実行環境

      1. 実行環境の制約
        • AWSコンソール(プレイグラウンド)では実行不可
        • 独自の実行環境が必要
      2. 必要な実行環境の例
        • ローカルPC開発環境
        • AWS Cloud9
        • EC2インスタンス
        • AWS Lambda
      3. 環境構築に必要なもの
        • AWS認証情報(アクセスキー)
        • AWS SDKのインストール
        • 開発言語の実行環境(Node.js, Pythonなど)
      AWS実行環境の違い AWSコンソール (Bedrockプレイグラウンド) できること: ✓ AIモデルとの対話 ✓ 基本的なパラメータ調整 ✓ プロンプトのテスト × 外部サービス連携 × カスタム開発 SDK実行環境 (ローカルPC等) できること: ✓ 全APIアクセス ✓ 外部サービス連携 ✓ カスタムアプリケーション開発 必要なもの: ・AWS認証情報(アクセスキー) 本格的な開発には SDK実行環境の構築が必要

      プログラムからの活用方法

      AWS SDK for JavaScript (Node.js)

      const { BedrockRuntimeClient } = require("@aws-sdk/client-bedrock-runtime");

      AWS SDK for Python (Boto3)

      import boto3
      bedrock = boto3.client('bedrock-runtime')

      必要な認証情報

      開発環境からAWSサービスを利用するには、以下の認証情報が必要です:

      1. アクセスキーID
      2. シークレットアクセスキー
      3. リージョン設定

      プレイグラウンドと開発環境の違い

      プレイグラウンドの制限

      • 基本的なモデル操作のみ
      • 外部サービス連携不可
      • ファイル操作の制限

      開発環境のメリット

      • 外部APIとの連携が可能
      • カスタム機能の実装
      • 自動化やバッチ処理
      • 他のAWSサービスとの統合

      まとめ

      より高度なアプリケーション開発には、適切な開発環境の構築と、AWS SDKを活用した実装が推奨されます。プレイグラウンドは簡単な実験や検証には適していますが、本格的な開発には開発環境の構築をお勧めします。

      Generative AI Use Cases JP (GenU) のセットアップ

      環境準備

      Node.jsのインストール

      AWS CLIのインストール

      AWS CLIとは
      AWSのサービスをコマンドラインから操作するためのツール
      インストール手順はAWS CLI公式インストーラーをダウンロード

      AWSクレデンシャルの設定

      GitHubからClone

      Cloneしたディレクトリでnpm ciを実行

      Bootstrap

      デプロイ

      表示されたUrlにアクセス

      CORSエラー(Cross-Origin Resource Sharing Error)とは?「Access-Control-Allow-Origin」の設定

      CORSエラーとは

      CORSは「異なるオリジン(ドメイン、プロトコル、ポート)間でのリソース共有」を制御するセキュリティの仕組みです。

      そのため異なるオリジン間でリソースをリクエストしようとする際にブラウザが制限をかけることにより、CORSエラーが発生します。

      ウェブサイト localhost:3000 fetch(‘/api/data’) APIサーバー localhost:8000 Access-Control-Allow-* ブラウザ CORSチェック ✓ オリジンの確認 ✓ メソッドの許可 ✓ ヘッダーの確認 CORSエラーが発生する場合: 1. オリジンの不一致(例:localhost:3000からのリクエストに対し、サーバーが異なるオリジンを許可) 2. 許可されていないHTTPメソッド(GET/POST/PUT等)の使用

      「Access-Control-Allow-Origin」の設定

      異なるオリジン間のリクエストで制限をうけずにアクセスするためには、レスポンスヘッダーに「Access-Control-Allow-Origin」を指定することで可能になります。

      https://zenn.dev/syo_yamamoto/articles/445ce152f05b02

      プリフライトリクエストとは

      異なるオリジン(ドメイン)へリクエストを送る前の「事前確認」

      • 本来のリクエストを送る前に、そのリクエストが安全かどうかを確認します
      • HTTPのOPTIONSメソッドを使用して送信されます

      プリフライトリクエストはブラウザが自動的に行います。開発者が明示的にコードを書く必要はありません。

      プリフライトが必要になるケース

      • 特殊なヘッダーを使用する場合
      • application/jsonなどの特殊なContent-Typeを使用する場合
      • GET/POST/HEAD以外のメソッド(PUT、DELETEなど)を使用する場合

      プリフライトリクエストの流れ

      ブラウザがOPTIONSリクエストを送信 サーバーがCORS設定を返答 許可された場合のみ、本来のリクエストを送信

      CORSリクエストパターン パターン1: Simple Request (プリフライトなし) フロントエンド API サーバー GETリクエスト/通常フォーム送信 パターン2: プリフライト成功パターン フロントエンド API サーバー 1. OPTIONSリクエスト 2. CORS OK 3. 本来のリクエスト パターン3: プリフライト失敗パターン フロントエンド API サーバー 1. OPTIONSリクエスト 2. CORS エラー 本来のリクエストは送信されない

      開発者ツールでの確認方法

      1. ブラウザの開発者ツールを開く
      2. Networkタブで確認
        • OPTIONSメソッドのリクエスト → これがプリフライトリクエスト
        • その直後の本来のリクエスト

      リクエストヘッダー:

      • Origin: [送信元のドメイン]
      • Access-Control-Request-Method: [使用予定のメソッド]
      • Access-Control-Request-Headers: [使用予定のヘッダー]

      レスポンスヘッダー:

      • Access-Control-Allow-Origin: [許可されているドメイン]
      • Access-Control-Allow-Methods: [許可されているメソッド]
      • Access-Control-Allow-Headers: [許可されているヘッダー]
      Elements Network Console Name Method Status /api/data OPTIONS 204 /api/data POST 200 プリフライトリクエストの確認方法: 1. ブラウザの開発者ツールを開く (F12 または右クリック → 検証) 2. Networkタブを選択 3. OPTIONSメソッドのリクエストを探す 4. リクエストをクリックすると詳細が表示される: – Request Headers: Origin, Access-Control-Request-Method – Response Headers: Access-Control-Allow-Origin, Methods, Headers

      ローカルのNext.jsのプロジェクトからさくらインターネットのレンタルサーバーのphpのAPIにアクセスする場合

      デジタルイラスト制作ツール徹底比較:Adobe Fresco vs Procreate vs CLIP STUDIO PAINT

      はじめに

      デジタルイラストの世界では、様々な制作ツールが存在します。今回は、主要な3つのソフトウェアの特徴と違いを詳しく見ていきましょう。

      Adobe Fresco

      主な特徴

      • Adobe Creative Cloud との完璧な連携
      • ライブブラシ機能による水彩やオイルの自然な表現
      • ベクターとラスターの両方に対応
      • プロフェッショナル向けの充実した機能

      メリット

      • Photoshopとの互換性が高い
      • クラウド同期で作品管理が容易
      • 実際の画材のような描き心地

      デメリット

      • 月額サブスクリプション制
      • 学習曲線がやや急
      Adobe Fresco 基本情報 価格: ¥1,180/月(CC込み) 対応: iPad / Windows 開発: Adobe Inc. 最新版: 2024年対応 主要機能 ベクター描画 ラスター描画 水彩ブラシ CC連携 最適な用途 • デジタルペインティング • イラスト制作 • コンセプトアート • デザイン作業 特徴的な機能 • リアルな水彩表現 • Photoshop連携 • クラウド同期 • ベクター/ラスター

      Procreate

      主な特徴

      • iPad専用に最適化された直感的なインターフェース
      • カスタムブラシの作成が可能
      • アニメーション機能搭載
      • 手頃な価格の買い切り制

      メリット

      • シンプルで使いやすい
      • 優れたパフォーマンス
      • 豊富なショートカット機能

      デメリット

      • iPad専用のため、他のデバイスでは使用不可
      • レイヤー数に制限あり
      Procreate 基本情報 価格: ¥1,480(買切) 対応: iPad専用 開発: Savage Interactive 最新版: Procreate 6 主要機能 ブラシ機能 レイヤー アニメーション カスタマイズ 最適な用途 • デジタルアート • イラスト制作 • スケッチ • アニメーション 特徴的な機能 • QuickShape • QuickMenu • カスタムブラシ • ジェスチャー操作

      CLIP STUDIO PAINT

      主な特徴

      • マンガ・イラスト制作に特化
      • 充実した3D素材とポーズ集
      • 多彩なブラシと効果
      • クロスプラットフォーム対応

      メリット

      • 専門的な制作に適した豊富な機能
      • アセットストアで素材の入手が容易
      • 安定した動作性能

      デメリット

      • 初心者には機能が多すぎる可能性
      • インターフェースがやや複雑
      CLIP STUDIO PAINT 基本情報 価格: ¥15,000~(買切) 対応: Win/Mac/iPad/Android 開発: セルシス グレード: PRO/EX 主要機能 マンガ機能 3D機能 素材数 アニメーション 最適な用途 • マンガ制作 • イラスト制作 • アニメーション • 商業作品 特徴的な機能 • 3Dポーズ集 • コマ

      選び方のポイント

      1. 用途による選択
      • マンガ制作 → CLIP STUDIO PAINT
      • 一般イラスト → Procreate
      • 商業デザイン → Adobe Fresco
      1. 使用デバイス
      • iPadのみ → Procreate
      • マルチデバイス → CLIP STUDIO PAINTやAdobe Fresco
      1. 予算
      • 買い切り希望 → ProcreateやCLIP STUDIO PAINT
      • サブスクOK → Adobe Fresco
      イラストツール比較 Adobe Fresco Creative Cloud 統合ツール 主な特徴: • ベクター&ラスター対応 • リアルな水彩ブラシ • Photoshop連携 価格: サブスクリプション ¥1,180/月 対応環境: iPad / Windows Procreate iPad専用プロフェッショナルツール 主な特徴: • 直感的なインターフェース • アニメーション機能 • カスタムブラシ作成 価格: 買い切り ¥1,480 対応環境: iPad専用 CLIP STUDIO PAINT マンガ・イラスト制作専門ツール 主な特徴: • マンガ制作機能 • 3D素材対応 • 充実したアセット 価格: 買い切り/サブスク ¥15,000~ 対応環境: Windows / Mac iPad / Android 推奨用途: デザイン・イラスト アート・イラスト マンガ・イラスト

      まとめ

      それぞれのツールに特徴があり、一概にどれが最高とは言えません。自分の用途や好みに合わせて選択することをお勧めします。初心者の方はProcreateから始めるのが良いでしょう。プロフェッショナルな用途ではCLIP STUDIO PAINTやAdobe Frescoが適しています。

      このブログ記事はいかがでしょうか?各ソフトウェアの特徴や比較点について、さらに詳しく知りたい部分がございましたら、お申し付けください。

      Web回覧アプリケーション

      内容

      Amazon Bedrock Amazon Titan(AIモデル)を使用して、シンプルなチャットプログラムの作成、応用(テキスト校閲)
      ローカル環境 Node.jsアプリ – 質問入力 – 回答表示 AWS Bedrock Amazon Titan – テキスト生成 – 自然言語処理 質問を送信 回答を受信

      シンプルなチャットプログラムの作成

      Node.jsのプロジェクトを初期化

      npm init -y

      @aws-sdk/client-bedrock-runtime

      Bedrock Runtime API のリクエスト作成と送信するために@aws-sdk/client-bedrock-runtimeをインストール

      npm install @aws-sdk/client-bedrock-runtime
      • Node.js環境でAWS Bedrockサービスと通信するための公式ライブラリ(AWSが提供する公式SDK)
      • 【開発環境の構築が容易】Node.jsのインストールだけで開発開始可能
      • フロントエンド開発との親和性が高い、共通の言語(JavaScript/TypeScript)使用

      1)Node.jsライブラリ(データの前処理や基本的な処理)

      • Tesseract.js(OCR)
      • Express(APIサーバー)
      • Multer(ファイルアップロード)
      • Sharp(画像処理)

      2)プロンプト:AIに適切な指示を出す

      App.jsを作成

      // AWS SDKから必要な機能をインポート
      // BedrockRuntimeClient: AWSのBedrockサービスに接続するためのクライアント
      // InvokeModelCommand: AIモデルを呼び出すためのコマンド
      const { BedrockRuntimeClient, InvokeModelCommand } = require('@aws-sdk/client-bedrock-runtime');
      
      // AWS Bedrockクライアントの設定
      // region: AWSのサービスを使用するリージョン(地域)
      // credentials: AWS認証情報
      const client = new BedrockRuntimeClient({
          region: "us-east-1",  // 米国東部(バージニア)リージョン
          credentials: {
              accessKeyId: "あなたのアクセスキー",      // AWSアクセスキー
              secretAccessKey: "あなたのシークレットキー" // AWSシークレットキー
          }
      });
      
      // AIに質問するための関数
      // async/await: 非同期処理を扱うための構文
      async function askAI(question) {
          try {
              // AIモデルへのリクエスト内容を設定
              const input = {
                  // 使用するAIモデルを指定(Amazon Titan)
                  modelId: "amazon.titan-text-express-v1",
                  
                  // データの形式をJSON形式に指定
                  contentType: "application/json",
                  accept: "application/json",
                  
                  // AIモデルへの具体的な指示内容
                  body: JSON.stringify({
                      // 質問文を設定
                      inputText: question,
                      
                      // AIの応答設定
                      textGenerationConfig: {
                          maxTokenCount: 1000,    // 最大応答長
                          temperature: 0.7        // 応答の創造性(0-1: 高いほど創造的)
                      }
                  })
              };
      
              // AIモデルを呼び出すコマンドを作成
              const command = new InvokeModelCommand(input);
              
              // コマンドを実行してAIからの応答を取得
              const response = await client.send(command);
              
              // AIからの応答(バイナリ)をテキストに変換
              const responseBody = JSON.parse(new TextDecoder().decode(response.body));
              
              // 応答を表示
              console.log("回答:", responseBody.results[0].outputText);
              
          } catch (error) {
              // エラーが発生した場合の処理
              console.error("エラーが発生しました:", error);
          }
      }
      
      // プログラムのテスト実行
      // ここの質問を変更することで、違う質問ができます
      askAI("こんにちは!簡単な自己紹介をしてください。");
      時間の流れ async function askAI() await client.send(command) AIからの応答待ち 応答を表示 1. 非同期関数の開始 2. AIモデルの呼び出しを待機 3. 応答を受け取って処理

      AWSが提供しているclient.send()というメソッドが 内部でPromiseを使用していて 私たちはそれをawaitで待っている

      AWSのアクセスキーとシークレットキーを取得する

      IAMユーザーの作成

      1. 「ポリシーを直接アタッチする」を選択
      2. 検索ボックスに「Bedrock」と入力
      3. 「AmazonBedrockFullAccess」にチェック
      4. 「次へ」をクリック
      1. 作成したユーザー名をクリック
      2. 「セキュリティ認証情報」タブを選択
      3. 「アクセスキーを作成」ボタンをクリック
      4. 「ユースケース」で「サードパーティーのサービス」を選択
      5. 警告の確認チェックボックスにチェック
      6. 「次へ」をクリック

      ファイルを実行

      先ほど作成したapp.jsに取得したキーに更新してファイルを下記の通り実行すると、、

      node app.js
      回答: 
      私はAIです。人工知能の一種で、あなたの質問に答えたり、あなたの指示に従ったりすることができます。
      私はあなたとチャットできることをうれしく思います。

      テキスト校閲

      使っているもの:

      1. AWS Bedrockのサービス
      2. Amazon TitanのAIモデル
      3. AWS SDKのクライアント
      async function checkText(text) {
          try {
              // 1. AIへの指示(プロンプト)を作成
              const input = {
                  modelId: "amazon.titan-text-express-v1",  // Amazon TitanのAIモデル
                  contentType: "application/json",
                  accept: "application/json",
                  body: JSON.stringify({
                      // ここが重要:AIへの指示内容
                      inputText: `
                      以下の文章を校閲してください。
                      - 誤字脱字
                      - 文法の間違い
                      - より良い表現の提案
                      を箇条書きで指摘してください。
      
                      文章:${text}`,
                      textGenerationConfig: {
                          maxTokenCount: 1000,
                          temperature: 0.7
                      }
                  })
              };
      
              // 2. AIに送信して結果を待つ
              const command = new InvokeModelCommand(input);
              const response = await client.send(command);
              
              // 3. 結果を表示
              const responseBody = JSON.parse(new TextDecoder().decode(response.body));
              console.log("校閲結果:", responseBody.results[0].outputText);
          } catch (error) {
              console.error("エラーが発生しました:", error);
          }
      }

      上記のの校閲処理は特別な校閲ロジックは実装していないので、全てAIモデル(プロンプトの指示)に依存しています

      より高度なプログラムにするために下記のようにします

      • 独自の校閲ルール
      • 形態素解析
      • 文法チェックライブラリ などを組み合わせる
      ComprehensiveTextChecker メインコントローラー CustomChecker MorphAnalyzer GrammarChecker
      入力テキスト チェック処理 独自ルールチェック 形態素解析 文法チェック AIチェック チェック結果 – 表記の修正 – 文法エラー – 形態素情報 – AI提案

      // 1. 独自の校閲ルールを追加
      const customRules = {
          "です/ます": "敬語の統一をチェック",
          "こと/もの": "表現の統一をチェック",
          // ... その他のルール
      };
      
      // 2. 形態素解析ライブラリの使用
      // npm install kuromoji
      const kuromoji = require('kuromoji');
      
      // 3. 独自の校閲ロジックを実装
      async function advancedCheck(text) {
          // AIによる校閲
          const aiResult = await checkText(text);
          
          // 独自ルールによるチェック
          const customResult = applyCustomRules(text);
          
          // 形態素解析による確認
          const morphResult = await morphologicalAnalysis(text);
          
          return {
              ai: aiResult,
              custom: customResult,
              morph: morphResult
          };
      }

      独自の校閲ルール

      // 独自の校閲ルールの実装例
      class TextChecker {
          constructor() {
              // 基本的なルール定義
              this.rules = {
                  // 表記ゆれチェック
                  styleRules: {
                      'とっても': 'とても',
                      'みたい': 'ような',
                      'すごく': '大変'
                  },
                  
                  // 文末表現チェック
                  endingRules: {
                      'です・ます': /([てで]います|てます|です)(?![かが])/,
                      'である': /である(?![かが])/
                  },
      
                  // ビジネス文書チェック
                  businessRules: {
                      '不適切': ['思います', 'そうです', 'だと思います'],
                      '推奨': ['考えられます', '示唆されます', '推察されます']
                  }
              };
          }
      
          // テキストをチェックするメソッド
          checkText(text) {
              const results = [];
              
              // 表記ゆれチェック
              for (const [incorrect, correct] of Object.entries(this.rules.styleRules)) {
                  if (text.includes(incorrect)) {
                      results.push(`「${incorrect}」は「${correct}」に修正することを推奨します`);
                  }
              }
      
              // その他のルールも同様にチェック
              return results;
          }
      }

      形態素解析

      // npm install kuromoji
      const kuromoji = require('kuromoji');
      
      class MorphologicalAnalyzer {
          async initialize() {
              return new Promise((resolve, reject) => {
                  kuromoji.builder({ dicPath: 'node_modules/kuromoji/dict' })
                      .build((err, tokenizer) => {
                          if (err) {
                              reject(err);
                          } else {
                              this.tokenizer = tokenizer;
                              resolve();
                          }
                      });
              });
          }
      
          analyze(text) {
              const tokens = this.tokenizer.tokenize(text);
              return this.processTokens(tokens);
          }
      
          processTokens(tokens) {
              const analysis = {
                  wordTypes: {},      // 品詞の分布
                  conjugations: [],   // 活用の問題
                  suggestions: []     // 改善提案
              };
      
              tokens.forEach(token => {
                  // 品詞の集計
                  const pos = token.pos;
                  analysis.wordTypes[pos] = (analysis.wordTypes[pos] || 0) + 1;
      
                  // 活用のチェック
                  if (token.pos === '動詞' && token.conjugated_form !== '基本形') {
                      analysis.conjugations.push({
                          word: token.surface_form,
                          basic: token.basic_form,
                          type: token.conjugated_form
                      });
                  }
              });
      
              return analysis;
          }
      }

      文法チェックライブラリ

      // npm install textlint textlint-rule-preset-japanese
      const { TextLintEngine } = require('textlint');
      
      class GrammarChecker {
          constructor() {
              this.engine = new TextLintEngine({
                  rules: {
                      // プリセットルールの使用
                      'preset-japanese': true,
                      // カスタムルールの追加
                      'max-ten': {
                          // 一文での「、」の数を制限
                          max: 3
                      },
                      'sentence-length': {
                          // 一文の長さを制限
                          max: 100
                      }
                  }
              });
          }
      
          async check(text) {
              try {
                  const results = await this.engine.executeOnText(text);
                  return this.formatResults(results);
              } catch (error) {
                  console.error('文法チェックエラー:', error);
                  return [];
              }
          }
      
          formatResults(results) {
              return results[0].messages.map(message => ({
                  line: message.line,
                  column: message.column,
                  message: message.message,
                  ruleId: message.ruleId
              }));
          }
      }

      組み合わせた場合

      class ComprehensiveTextChecker {
          constructor() {
              this.customChecker = new TextChecker();
              this.morphAnalyzer = new MorphologicalAnalyzer();
              this.grammarChecker = new GrammarChecker();
          }
      
          async initialize() {
              await this.morphAnalyzer.initialize();
          }
      
          async checkText(text) {
              const results = {
                  customRules: this.customChecker.checkText(text),
                  morphology: await this.morphAnalyzer.analyze(text),
                  grammar: await this.grammarChecker.check(text)
              };
      
              // AIの結果と組み合わせる
              const aiResults = await checkText(text);  // 先ほどのAI機能
              results.ai = aiResults;
      
              return this.summarizeResults(results);
          }
      
          summarizeResults(results) {
              // 結果を整理して返す
              return {
                  suggestions: [
                      ...results.customRules,
                      ...results.grammar.map(g => g.message)
                  ],
                  analysis: {
                      wordTypes: results.morphology.wordTypes,
                      grammarIssues: results.grammar.length,
                      aiSuggestions: results.ai
                  }
              };
          }
      }

      使用例

      async function main() {
          const checker = new ComprehensiveTextChecker();
          await checker.initialize();
      
          const text = "私はとっても疲れたので、今日は早く帰りたいと思います。";
          const results = await checker.checkText(text);
          console.log(results);
      }

      補足)フォルダ構成

      text-checker-project/
      │
      ├── src/                      # ソースコードのメインディレクトリ
      │   ├── checkers/            # 各チェッカーの実装
      │   │   ├── CustomChecker.js    # 独自ルールチェッカー
      │   │   ├── MorphAnalyzer.js    # 形態素解析
      │   │   ├── GrammarChecker.js   # 文法チェッカー
      │   │   └── AIChecker.js        # AI利用のチェッカー
      │   │
      │   ├── rules/               # ルール定義
      │   │   ├── styleRules.js       # 表記ゆれルール
      │   │   ├── grammarRules.js     # 文法ルール
      │   │   └── businessRules.js    # ビジネス文書ルール
      │   │
      │   ├── utils/               # ユーティリティ関数
      │   │   ├── formatter.js        # 結果フォーマット
      │   │   └── logger.js           # ログ処理
      │   │
      │   └── index.js             # メインのエントリーポイント
      │
      ├── config/                  # 設定ファイル
      │   ├── default.json           # デフォルト設定
      │   └── textlint-rules.json    # textlintルール設定
      │
      ├── tests/                   # テストファイル
      │   ├── customChecker.test.js
      │   ├── morphAnalyzer.test.js
      │   └── grammarChecker.test.js
      │
      ├── examples/                # 使用例
      │   └── basic-usage.js
      │
      ├── docs/                    # ドキュメント
      │   ├── API.md
      │   └── RULES.md
      │
      ├── node_modules/            # 依存パッケージ
      ├── package.json
      ├── package-lock.json
      └── README.md

      src/index.js

      const ComprehensiveTextChecker = require('./checkers/ComprehensiveChecker');
      const config = require('../config/default.json');
      
      module.exports = ComprehensiveTextChecker;

      src/checkers/CustomChecker.js

      const styleRules = require('../rules/styleRules');
      const businessRules = require('../rules/businessRules');
      
      class CustomChecker {
          constructor(config = {}) {
              this.rules = {
                  style: styleRules,
                  business: businessRules,
                  ...config.rules
              };
          }
      
          checkText(text) {
              // 実装
          }
      }
      
      module.exports = CustomChecker;

      src/rules/styleRules.js

      module.exports = {
          // 表記ゆれルール
          corrections: {
              'とっても': 'とても',
              'みたい': 'ような'
          },
          // その他のルール
      };

      package.json

      {
        "name": "text-checker",
        "version": "1.0.0",
        "main": "src/index.js",
        "scripts": {
          "start": "node src/index.js",
          "test": "jest",
          "lint": "eslint src/"
        },
        "dependencies": {
          "kuromoji": "^1.0.0",
          "textlint": "^12.0.0",
          "textlint-rule-preset-japanese": "^7.0.0",
          "@aws-sdk/client-bedrock-runtime": "^3.0.0"
        },
        "devDependencies": {
          "jest": "^27.0.0",
          "eslint": "^8.0.0"
        }
      }

      examples/basic-usage.js

      const TextChecker = require('../src/index');
      
      async function example() {
          const checker = new TextChecker();
          await checker.initialize();
      
          const text = "私はとっても疲れたので、今日は早く帰りたいと思います。";
          const results = await checker.checkText(text);
          console.log(results);
      }
      
      example().catch(console.error);
      【Next.js】ハイドレーションエラーSuspenseで解決

      非同期データの取得はuseEffectかSuspenseで管理

      ハイドレーションエラー

      ハイドレーションの流れとエラー 1. SSR HTMLを生成 {“name”: “John”} {“time”: “12:00”} 2. 初期HTML表示 ブラウザ表示 {“name”: “John”} {“time”: “12:00”} 3. ハイドレーション Reactの初期化 {“name”: “John”} {“time”: “12:01”} 一般的なエラーと解決策 ⚠️ window/documentの使用 ✅ useEffectで初期化 ⚠️ 日時の不一致 ✅ サーバー時刻の固定 ⚠️ 非同期データ ✅ Suspense使用 主なポイント 1. サーバー/クライアントの一致確認 2. 適切なローディング状態の管理

      Suspenseが効果的な場合

      • 非同期データの取得待ち
      • 動的インポート
      • 画像やその他のリソースのロード待ち
      Suspense による制御 Loading… データ取得中 表示完了
      【Laravel 】PHPUnit/Laravel

      テストとはアプリケーションが正しく動作することを確認する自動チェックです

      テスト

      Laravel

      • 自動なんで、人間がやらなくていい(コマンドで走らせる)
      • レッドグリーンテスト
      • テスト駆動開発(Test-Driven Development: TDD)テストファーストなプログラムの開発手法
      • テストコードを書くのに開発と同じ時間がかかるので、工数的には膨らむ。
      【Laravel】Sanctumでシンプルな認証

      トークンベースの認証により、ステートレスなAPI通信が可能

      シンプルなメモアプリのAPI

      1. ログイン email + password → トークン取得 2. メモ作成 認証済みユーザーのみ メモの作成が可能 3. メモ取得 自分のメモのみ 取得可能 POST /api/login → トークン取得 POST /api/memos → メモ作成 (要トークン) GET /api/memos → メモ一覧取得 (要トークン)
      // routes/api.php
      // ユーザー登録
      Route::post('/register', function (Request $request) {
          $user = User::create([
              'name' => $request->name,
              'email' => $request->email,
              'password' => Hash::make($request->password),
          ]);
          
          $token = $user->createToken('auth-token')->plainTextToken;
          
          return response()->json([
              'token' => $token
          ]);
      });
      
      // ログイン
      Route::post('/login', function (Request $request) {
          $user = User::where('email', $request->email)->first();
          
          if (!$user || !Hash::check($request->password, $user->password)) {
              return response()->json(['message' => 'Unauthorized'], 401);
          }
          
          $token = $user->createToken('auth-token')->plainTextToken;
          
          return response()->json([
              'token' => $token
          ]);
      });
      // routes/api.php
      Route::middleware('auth:sanctum')->group(function () {
          // メモの作成
          Route::post('/memos', function (Request $request) {
              $memo = $request->user()->memos()->create([
                  'title' => $request->title,
                  'content' => $request->content
              ]);
              
              return response()->json($memo);
          });
          
          // メモの取得
          Route::get('/memos', function (Request $request) {
              $memos = $request->user()->memos;
              return response()->json($memos);
          });
      });
      // ログイン
      async function login() {
          const response = await fetch('api/login', {
              method: 'POST',
              headers: {
                  'Content-Type': 'application/json'
              },
              body: JSON.stringify({
                  email: 'user@example.com',
                  password: 'password123'
              })
          });
          const data = await response.json();
          // トークンを保存
          localStorage.setItem('token', data.token);
      }
      
      // メモを作成
      async function createMemo() {
          const response = await fetch('api/memos', {
              method: 'POST',
              headers: {
                  'Authorization': `Bearer ${localStorage.getItem('token')}`,
                  'Content-Type': 'application/json'
              },
              body: JSON.stringify({
                  title: '新しいメモ',
                  content: 'メモの内容'
              })
          });
          const memo = await response.json();
          console.log('作成されたメモ:', memo);
      }
      Amazon Bedrockとは

      Amazon Bedrockとは

      Amazon Bedrockは、さまざまなAIモデルを簡単に利用できるようにするAWSのサービスです

      料金

      基本的な課金構造

      • 入力トークン数と出力トークン数で別々に課金
      • 1,000トークンあたりの料金で計算
      • 実際の使用量のみ請求(最低利用額なし)
      モデル バリアント 入力料金
      (1Kトークン)
      出力料金
      (1Kトークン)
      備考
      Claude (Anthropic) Claude Instant 0.80円 2.40円 最も手頃な選択肢
      Claude (Anthropic) Claude 3 Sonnet 3.00円 15.00円 バランスの取れたモデル
      Claude (Anthropic) Claude 3 Opus 15.00円 75.00円 最高性能モデル
      Llama 2 (Meta) Llama 2 13B 0.60円 0.80円 オープンソースベース
      Titan (Amazon) Titan Text Lite 0.30円 0.40円 軽量な処理向け
      1. 言語による違い
      • 英語:1単語≒1-2トークン
      • 日本語:1文字≒2-3トークン
      • 記号や空白:0.2-0.5トークン程度
      1. 一般的な目安
      • チャット1往復:100-300トークン
      • メール1通:200-400トークン
      • 技術文書1ページ:500-800トークン

      コスト最適化のポイント

      • 入力は簡潔にすることでコストを抑えられる
      • システムプロンプトの再利用で入力コストを削減
      • 出力制限を設定して想定外の高額請求を防止

      ハンズオン

      AWSマネジメントコンソール経由

      マネジメントコンソールにログイン

      リージョンをバージニア北部にします
      → 使用できるプレイグラウンドに影響

      「Amazon Bedrock」を選択

      Amazon Bedrock をマネジメントコンソールから利用するには、最初に Base Models(基盤モデル)へのアクセスを設定する必要があります。

      「モデルアクセス」

      使用するモデルのアクセスを設定
      → 「✅アクセスが付与されました」となります。

      「プレイグラウンド」から「Chat/text」を選択

      「モデルを選択」

      チャットで回答してくれました

      「イメージ」で画像生成も可能

      「AWS SDK」を実行しAmazon Bedrock APIを呼び出す

      CloudShellを起動

      すぐに使える主要なAIモデル

      • Anthropicの「Claude」(テキスト生成)
      • Meta「Llama 2」(言語モデル)
      • Stability AI「Stable Diffusion」(画像生成)
      • AmazonのTitanモデル など

      DifyとAmazon Bedrockの違い

      サービスの性質

      • Dify:
        • オープンソースのAIアプリケーション開発プラットフォーム
        • 自社でホストすることも可能
        • プロンプトエンジニアリングに特化
        • より小規模なプロジェクトに適している
      • Amazon Bedrock:
        • AWSの完全マネージドサービス
        • エンタープライズ向けの機能が充実
        • 大規模なスケーリングが可能
        • AWSの他サービスとの連携が容易

      主な用途

      • Dify:
        • チャットボットの作成
        • プロンプトの管理と最適化
        • シンプルなAIアプリケーションの開発
        • プロトタイピング
      • Amazon Bedrock:
        • 大規模なAIサービスの展開
        • 複数のAIモデルの統合
        • エンタープライズアプリケーションの開発
        • 高セキュリティな環境でのAI活用

      料金体系

      • Dify:
        • オープンソースで基本無料
        • 自身でホストする場合はインフラ費用のみ
        • クラウドホスティング版は従量課金
      • Amazon Bedrock:
        • AWS従量課金制
        • 利用するAIモデルごとに料金が異なる
        • エンタープライズ向けの価格帯
      【WordPress PHPエラー】Fatal error: Allowed memory size of xxx bytes exhausted

      Fatal error: Allowed memory size of 268435456 bytes exhausted (tried to allocate 430080 bytes) in /home/xxxx/public_html/xxxxx.com/wp-includes/wp-db.php on line xxxx

      WordPressで固定ページを編集しようと編集をクリックすると上記エラーが発生してしまいました。

      PHPのメモリ不足が原因のようです

      詳しく言うと、許可されているメモリサイズ(268435456 bytes = 256MB)を使い果たして、

      さらに397312 bytes(約388KB)の追加メモリ割りあてようとしたら失敗したようです

      おそらく原因は固定ページ内に大量の画像ブロックを配置しているから

      WordPressの編集画面でのメモリ問題 500KB × 400枚… メモリ使用量 PHPのメモリ制限に近い状態 発生する問題: 1. 編集画面を開く時にメモリエラー 2. 保存時にメモリエラー 3. 画面の応答が遅い
      https://internet.mints.ne.jp/%e3%83%af%e3%83%bc%e3%83%89%e3%83%97%e3%83%ac%e3%82%b9%e3%81%ae%e3%83%a1%e3%83%87%e3%82%a3%e3%82%a2%e3%81%ab%e4%bf%9d%e5%ad%98%e3%81%97%e3%81%9f%e7%94%bb%e5%83%8f%e3%81%a8%e3%83%87%e3%83%bc%e3%82%bf

      メモリの上限

      メモリの制限は複数の層で設定されています

      レンタルサーバー(PHP)の制限 php.ini による制限 (例:256MB) WordPress の制限 wp-config.php による制限 WordPressの制限は、PHPの制限を超えることができません
      1. レンタルサーバー(PHP)の制限
      • サーバーのphp.iniファイルで設定される制限
      • サーバー全体のPHPスクリプトに適用される最大メモリ使用量
      • レンタルサーバーの管理画面やFTPでphp.iniをアップロードすることで変更可能
      1. WordPressの制限
      • WordPressのwp-config.phpファイルで設定される制限
      • PHPの制限を超えて設定することはできない
        • 例:PHPの制限が256MBの場合、WordPressで512MBと設定しても256MBまでしか使用できない

      1)サーバー全体のPHPで設定されるメモリ制限

      これが最も上位の制限となり、この値を超えることはできない

      通常、レンタルサーバーの管理画面やサポートでのみ変更可能

      メモリの確認方法

      ルードディレクトリに下記の様なphpファイルを設置しアクセスし、memory_limitを確認できます

      info.php

      <?php phpinfo(); ?>

      ↓アクセスしてみるとメモリ上限が確認できます

      php.iniで上限をふやせます

      お名前ドットコムをレンタルサーバーで使用している場合は下記の記事を参考できます。

      ご利用サーバーのFTPサーバーへご接続いただき、該当ドメインディレクトリ直下に
      エラーの内容以上のメモリサイズを記述した「php.ini」ファイルを設置(アップロード)してください。
      ※既に「php.ini」ファイルを設置されている場合には、同ファイルを修正してください。

      https://help.onamae.com/answer/20364

      レンタルサーバー側からメモリの使用が集中してアクセス制限がかかることがあります

      「ERR_CONNECTION_TIMED_OUT」エラーの解決方法

      https://help.onamae.com/answer/20364

      2)WordPress側のメモリ

      • WP_MEMORY_LIMIT:通常時の制限(デフォルト40M)
      • WP_MAX_MEMORY_LIMIT:管理画面での制限(デフォルト256M)

      ワードプレスのメモリは管理画面のツールのサイトヘルスから確認できます

      一時的にメモリ制限を大幅に引き上げる方法:

      // wp-config.phpに追加
      define('WP_MEMORY_LIMIT', '256M');
      define('WP_MAX_MEMORY_LIMIT', '512M');

      wp-config.php冒頭の<?phpの次の行に追記しました

      ↓サイトヘルスで変更が確認できました

      https://sologaku.com/wordpress/how-to-change-wordpress-memory-limit/#google_vignette

      対処法

      今回のエラー

      Fatal error: Allowed memory size of 268435456 bytes exhausted (tried to allocate…

      はどちらのエラー??

      Fatal error: [エラーの説明] in [ファイルのパス] on line [行番号]

      はPHPのエラーメッセージの形式

      WordPressメディアライブラリから画像データそのものを削除すると、固定ページ上のギャラリーブロックに関連付けられていた画像の参照が切れ、メモリ消費が減少するため、編集画面に遷移できる可能性が高い

      ただし、以下の点に注意が必要です:

      1. メディアライブラリから画像を削除した場合:
      • 固定ページ上のギャラリーブロックは残りますが、削除した画像は「見つかりません」や空の枠として表示される
      • ブロックエディタで開くと、画像が欠落したギャラリーブロックとして表示される
      • サイト上では画像が表示されなくなる
      1. 推奨される手順:
      • まずサイトとデータベースのバックアップを取る
      • メディアライブラリから、問題のページで使用している画像を一部削除
      • 編集画面にアクセスできるようになったら、不要なギャラリーブロックを整理
      • 必要な画像は最適化して再アップロード
      1. より安全な代替アプローチ:
      • まず、wp-config.phpでメモリ制限を一時的に引き上げてから
      • 画像の整理と最適化を行う方が、データの制御がしやすい

      このアプローチで編集画面にアクセスできるようになったら、今後のために:

      必要に応じてギャラリーの分割 を検討することをお勧めします。

      画像サイズの最適化

      1ページあたりの画像数の制限

      ワードプレスのデバッグモードを有効にして詳細なエラー情報を確認:

      // wp-config.phpに追加
      define('WP_DEBUG', true);
      define('WP_DEBUG_LOG', true);

      遅延読み込み(Lazy Loading)

      遅延読み込みとは: 画面に表示されている部分の画像だけを読み込み、見えていない部分の画像は後回しにする仕組みです。

      表示エリア(画像読み込み済み) スクロール下の画像(未読み込み)
      // functions.php
      function add_lazy_loading_to_admin() {
          // 管理画面でのみ実行
          if (is_admin()) {
              // インラインでJavaScriptを追加
              wp_add_inline_script('jquery', '
                  jQuery(document).ready(function($) {
                      // 画像を監視するための設定
                      const observer = new IntersectionObserver((entries) => {
                          entries.forEach(entry => {
                              if (entry.isIntersecting) {
                                  const img = entry.target;
                                  if (img.dataset.src) {
                                      img.src = img.dataset.src;
                                      img.removeAttribute("data-src");
                                  }
                              }
                          });
                      });
      
                      // 編集画面内の画像を処理
                      $("#post-body img").each(function() {
                          const img = $(this);
                          const originalSrc = img.attr("src");
                          img.attr("data-src", originalSrc);
                          img.attr("src", "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");
                          observer.observe(img[0]);
                      });
                  });
              ');
          }
      }
      add_action('admin_enqueue_scripts', 'add_lazy_loading_to_admin');

      リスクについて

      リスクは比較的低いです。理由は:

      1. 編集画面のみの変更
        • 公開サイトには影響なし
        • 画像データ自体は変更しない
      2. 簡単に元に戻せる
        • functions.phpから該当コードを削除するだけ
      3. エラーが起きても
        • 最悪の場合、画像が表示されないだけ
        • WordPressの基本機能は影響を受けない

      筆者はこの方法ではメモリ数は抑えることはできませんでした…

      データベースクエリとリビジョンを対策

      データベースクエリとは:

      • WordPressがデータベースにデータを要求する際の命令
      • 例:
        • 記事の内容を取得
        • 画像情報の取得
        • カスタムフィールドの取得
      • 一つのページを表示するために、多数のクエリが実行される
      • 各クエリの結果はメモリに保存される

      リビジョンとは:

      • 記事の変更履歴を保存する機能
      • 保存する度に新しいバージョンが作られる
      • デフォルトでは無制限に保存される
      • 各バージョンがデータベースに保存され、編集画面で読み込まれる
      WordPressのメモリ消費要因 データベースクエリ 投稿データの取得 メタデータの取得 カスタムフィールドの取得 リビジョン 最新版 1つ前のバージョン 2つ前のバージョン メモリ消費要因の説明: 1. データベースクエリ: ・1回のページ表示で数十〜数百のクエリが実行 ・各クエリ結果がメモリに保存される 2. リビジョン: ・記事の変更履歴が全て保存される ・各バージョンの内容がメモリに読み込まれる

      データベースクエリの設定を最適化

      WordPressのデフォルト設定と最適化後 デフォルト設定 取得データ一覧 ・ID ・post_title(タイトル) ・post_content(本文) ・post_excerpt(抜粋) ・post_status(状態) ・comment_status(コメント設定) ・ping_status(ピング設定) ・post_password(パスワード) ・post_name(スラッグ) ・to_ping(ピング先) ・pinged(ピング済み) 最適化後 必要最小限のデータ ・ID ・post_title(タイトル) ・post_content(本文)
      // 必要な項目のみを取得するように変更
      function optimize_post_queries() {
          add_filter('posts_fields', function($fields) {
              global $wpdb;
              // 必要最小限のフィールドのみを指定
              return "{$wpdb->posts}.ID, {$wpdb->posts}.post_title, {$wpdb->posts}.post_content";
          });
      }
      add_action('init', 'optimize_post_queries');

      リビジョンの最適化

      リビジョンのデフォルト設定:

      • デフォルトでは無制限(制限なし)
      • 自動保存は60秒間隔
      • 全てのリビジョンがデータベースに保存される
      // wp-config.php に追加するだけ
      define('WP_POST_REVISIONS', 5);  // リビジョンを5個に制限

      LaravelのバージョンLaravel 10とLaravel 11の主な違い

      Laravelの開発環境をSailerを使って構築する方法

      Docker Desktopを起動しておく

      ターミナルでUbuntuをコマンドライン環境として選択する(理由:本番環境でLinuxを使う、Laravelの定番)

      ▼ 選択 PowerShell Command Prompt Ubuntu Azure Cloud Shell ここからUbuntuを 選択してください

      プロジェクト作成コマンド

      curl -s "https://laravel.build/laravel-study?with=mysql,mailpit" | bash

      Dockerを起動しているのに「Docker is not running.」となってしまったら、下記の通りDocker desktopの設定を変更してみてください

      ./vendor/bin/sail up -d
      ① curl:
      - ウェブ上からデータをダウンロードするためのツール
      - この場合、Laravel Sailのインストールスクリプトを取得している
      
      ② -s:
      - silentモード(進行状況などを表示しない)
      - ダウンロード時の詳細な情報を省略する設定
      
      ③ https://laravel.build/:
      - Laravel Sailの自動セットアップスクリプトが置かれているURL
      
      ④ laravel-study:
      - 作成するプロジェクトの名前
      - この部分は自由に変更可能(例:my-app, blog-systemなど)
      
      ⑤ ?with=mysql,mailpit:
      - 一緒にインストールする追加機能の指定
      - mysql:データベース
      - mailpit:メール送信のテスト環境
      
      ⑥ | bash:
      - ダウンロードしたスクリプトをすぐに実行する指示

      プロジェクト初期設定

      config\app.phpファイル

          'timezone' => env('APP_TIMEZONE', 'UTC'),
      
      
          'locale' => env('APP_LOCALE', 'en'),

      env関数

      .env( 設定項目名, デフォルト値)

      .envファイル

      # APP_TIMEZONE=UTC
      APP_TIMEZONE=Asia/Tokyo
      APP_URL=http://localhost
      
      # APP_LOCALE=en
      APP_LOCALE=ja

      コントローラーの使い方と目的

      ./vendor/bin/sail artisan make:controller UtilityController
      https://biz.addisteria.com/laravel11_upgrade/
      さくらインターネットのデータベースにさくらのWebサーバ以外からAPI経由でアクセスする方法

      さくらインターネットのWebサーバー外部から直接アクセスできない

      直接MySQLへの接続はブロックされています(セキュリティ対策)

      解決策:API経由の接続

      ローカルPC Next.jsアプリ さくらサーバー PHP API MySQL データベース ❌ 直接接続は制限されています 1. APIリクエスト 2. DB接続 3. データ取得 4. JSON応答

      ローカルNext.jsからアクセスする場合

      さくらインターネットの公開ディレクトにusers-api.phpを配置

      <?php
      header('Content-Type: application/json');
      
      $host = 'mysql80.xxxx.sakura.ne.jp';
      $dbname = 'xxxx';
      $user = 'xxxx';
      $pass = 'xxxx';
      
      try {
          $pdo = new PDO("mysql:host=$host;dbname=$dbname", $user, $pass);
          $stmt = $pdo->query('SELECT id, email, name FROM users');
          $users = $stmt->fetchAll(PDO::FETCH_ASSOC);
          
          echo json_encode([
              'success' => true,
              'data' => $users
          ]);
      
      } catch(PDOException $e) {
          echo json_encode([
              'success' => false,
              'error' => $e->getMessage()
          ]);
      }
      ?>

      Next.jsから上記APIを呼び出す

      app/api/users/route.ts

      import { NextResponse } from "next/server";
      
      export async function GET() {
        try {
          const response = await fetch('https://あなたのドメイン/users-api.php');
          const data = await response.json();
          return NextResponse.json(data);
        } catch (error) {
          return NextResponse.json({ 
            error: 'ユーザー情報の取得に失敗しました' 
          }, { status: 500 });
        }
      }

      参考サイト

      https://qiita.com/a1c/items/33757c0f5b3c3233450f
      LaravelでMySQLを使用したシンプルな認証システム (さくらインターネットDB作成手順)

      MVCのルーティングの流れ

      処理の流れ:

      1. ブラウザからリクエスト:
        • ユーザーがURLにアクセス
      2. Routeが処理を振り分け:
        • URLに合わせて適切なControllerを呼び出し
      3. ModelでDBアクセス:
        • 必要なデータを取得/保存
      4. Viewで画面を作成:
        • HTMLを生成
      5. ブラウザに結果を返す:
        • 作成した画面を表示
      ブラウザ example.com/users Route /users → Controller Controller 処理の制御 Model データベース操作 View 画面表示 1 2 3 4 5

      まずはデータベースを作成

      さくらインターネットでMySQLの用意をします

      (さくらインターネットのレンタルサーバーでは、標準でMySQLが提供されています)

      さくらのコントロールパネルにログイン

      「データベース」メニューを選択

      「新規作成」をクリック

      データベース名、ユーザー名(データベース同一)、パスワードを設定

      新規作成したDBを選択し「設定」から「phpMyAdmin」をクリック

      Laravelプロジェクトでの設定

      .envファイルでデータベース接続の設定

      .envファイルを以下のように設定します

      DB_CONNECTION=mysql
      DB_HOST=mysql○○.db.sakura.ne.jp    # さくらインターネットから提供されるホスト名
      DB_PORT=3306
      DB_DATABASE=データベース名           # さくらで作成したDB名
      DB_USERNAME=ユーザー名              # さくらで設定したユーザー名
      DB_PASSWORD=パスワード              # さくらで設定したパスワード
      ① さくらサーバー ・DB作成 ・ユーザー作成 ② .env設定 ・DB_HOST ・DB_DATABASE ③ 接続確認 php artisan tinker DB接続テスト

      Tinker でのDB接続確認方法

      Tinkerとは

      Tinker は 対話型コマンドラインツールでphpの実行やデータベースの操作が可能です

      Tinkerの起動

      php artisan tinker

      DB接続を内容を確認

      # DB接続確認(以下のいずれかを実行)
      DB::connection()->getPdo();   # 接続成功ならPDOオブジェクトが表示
      DB::connection()->getDatabaseName();   # データベース名が表示
      
      # 実際のデータを確認
      DB::table('users')->get();   # usersテーブルの全データ表示

      テストデータの作成

      ① User.php ユーザーモデルの定義 nickname, email, password ② UserFactory.php テストデータの作り方 ダミーデータの生成ルール ③ DatabaseSeeder 10件のデータを作成 User::factory(10)->create() 実行コマンド php artisan db:seed

      Usersテーブルのマイグレーションファイルでテーブルの構造を定義

      マイグレーションファイルは「データベースの設計図」のようなものです。
      データベースの構造を定義

      database\migrations\0001_01_01_000000_create_users_table.phpを編集

      <?php
      
      use Illuminate\Database\Migrations\Migration;
      use Illuminate\Database\Schema\Blueprint;
      use Illuminate\Support\Facades\Schema;
      
      return new class extends Migration
      {
          /**
           * Run the migrations.
           */
          public function up(): void
          {
              Schema::create('users', function (Blueprint $table) {
                  $table->id();
                  $table->string('nickname', 100);
                  $table->string('email')->unique();
                  $table->string('password');
                  $table->tinyInteger('locked_flg')->default(0);
                  $table->integer('error_count')->unsigned()->default(0);
                  $table->timestamps();
              });
          }
      };

      Laravelには「マイグレーション」という機能があり、データベースのテーブル構造やカラムをPHPコードで定義し、コマンドを実行することでデータベースに反映します。

      php artisan migrate

      User.phpを編集しユーザー関連のロジックを定義

      app\Models\User.phpを編集

      <?php
      
      namespace App\Models;
      
      // use Illuminate\Contracts\Auth\MustVerifyEmail;
      use Illuminate\Database\Eloquent\Factories\HasFactory;
      use Illuminate\Foundation\Auth\User as Authenticatable;
      use Illuminate\Notifications\Notifiable;
      
      class User extends Authenticatable
      {
          /** @use HasFactory<\Database\Factories\UserFactory> */
          use HasFactory, Notifiable;
      
          /**
           * The attributes that are mass assignable.
           *
           * @var array<int, string>
           */
          protected $fillable = [
              'nickname',
              'email',
              'password',
              'lcoked_flg',
              'error_count',
          ];
      
          /**
           * The attributes that should be hidden for serialization.
           *
           * @var array<int, string>
           */
          protected $hidden = [
              'password',
          ];
      
          /**
           * Get the attributes that should be cast.
           *
           * @return array<string, string>
           */
          // protected function casts(): array
          // {
          //     return [
          //         'email_verified_at' => 'datetime',
          //         'password' => 'hashed',
          //     ];
          // }
      }
      

      UserFactory.phpでユーザーデータを作成

      database\factories\UserFactory.phpを編集

      <?php
      
      namespace Database\Factories;
      
      use Illuminate\Database\Eloquent\Factories\Factory;
      use Illuminate\Support\Facades\Hash;
      use Illuminate\Support\Str;
      
      /**
       * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
       */
      class UserFactory extends Factory
      {
          /**
           * The current password being used by the factory.
           */
          protected static ?string $password;
      
          /**
           * Define the model's default state.
           *
           * @return array<string, mixed>
           */
          public function definition(): array
          {
              return [
                  'nickname' => fake()->name(), // ランダムな名前を生成
                  'email' => fake()->unique()->safeEmail(), // ランダムでユニークなメールアドレスを生成
                  'password' => static::$password ??= Hash::make('password'), // パスワードをハッシュ化
      
              ];
          }
      }
      

      Laravelのデータベースシーディング(seeding)機能を使ってテストデータを作成

      database\seeders\DatabaseSeeder.phpを編集

      <?php
      
      namespace Database\Seeders;
      
      use App\Models\User;
      // use Illuminate\Database\Console\Seeds\WithoutModelEvents;
      use Illuminate\Database\Seeder;
      
      class DatabaseSeeder extends Seeder
      {
          /**
           * Seed the application's database.
           */
          public function run(): void
          {
              User::factory(10)->create();
      
              // User::factory()->create([
              //     'name' => 'Test User',
              //     'email' => 'test@example.com',
              // ]);
          }
      }
      

      php artisan db:seed コマンドは、データベースに初期データを挿入するためのコマンドです。

      php artisan db:seed

      ログイン画面の設定

      auth/login.blade.phpを作成

      <!DOCTYPE html>
      <html lang="ja">
      <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>ログイン</title>
          <!-- Tailwind CSSを使用 -->
          @vite('resources/css/app.css')
      </head>
      <body class="bg-gray-100">
          <div class="min-h-screen flex items-center justify-center">
              <div class="bg-white p-8 rounded-lg shadow-md w-96">
                  <h1 class="text-2xl font-bold mb-8 text-center">ログイン</h1>
      
                  @if ($errors->any())
                      <div class="bg-red-100 text-red-700 p-4 mb-4 rounded">
                          <ul>
                              @foreach ($errors->all() as $error)
                                  <li>{{ $error }}</li>
                              @endforeach
                          </ul>
                      </div>
                  @endif
      
                  <form method="POST" action="{{ route('login') }}">
                      @csrf
                      
                      <!-- メールアドレス -->
                      <div class="mb-4">
                          <label for="email" class="block text-gray-700 mb-2">メールアドレス</label>
                          <input 
                              type="email" 
                              name="email" 
                              id="email" 
                              value="{{ old('email') }}"
                              class="w-full p-2 border rounded focus:outline-none focus:border-blue-500"
                              required 
                              autofocus
                              >
                      </div>
      
                      <!-- パスワード -->
                      <div class="mb-6">
                          <label for="password" class="block text-gray-700 mb-2">パスワード</label>
                          <input 
                              type="password" 
                              name="password" 
                              id="password" 
                              class="w-full p-2 border rounded focus:outline-none focus:border-blue-500"
                              required
                          >
                      </div>
      
                      <!-- ログインボタン -->
                      <div class="flex flex-col gap-4">
                          <button type="submit" 
                                  class="w-full py-2 px-4 bg-blue-600 text-white rounded hover:bg-blue-700 focus:outline-none">
                              ログイン
                          </button>
                      </div>
                  </form>
              </div>
          </div>
      </body>
      </html>

      ログイン機能のルーティングを設定

      routes/web.phpを編集

      
      use App\Http\Controllers\Auth\LoginController;
      
      // ログイン関連のルート
      Route::middleware('guest')->group(function () {
          // ログイン画面表示
          Route::get('/login', [LoginController::class, 'showLoginForm'])
              ->name('login');
          
          // ログイン処理
          Route::post('/login', [LoginController::class, 'login']);
      });
      
      // ログアウト(認証済みユーザーのみ)
      Route::middleware('auth')->group(function () {
          // ダッシュボード表示
          Route::get('/dashboard', function () {
              return view('dashboard');
          })->name('dashboard');
      
          // ログアウト処理
          Route::post('/logout', [LoginController::class, 'logout'])
              ->name('logout');
      });

      Cursor始め方

      ダウンロード

      https://www.cursor.com

      ダウンロードしたインストーラを起動

      「Autocomplete Preferences(自動補完設定)」に関する設定画面

      • GH Copilot …従来のもの
      • CursorのCopilot++(デフォルト) …より強力なバージョンのCopilot(デフォルト)

      「Data Preferences(データ設定)」

      • データ提供
      • プライバシーモード

      WinSCPのインストール方法

      インストール

      下記サイトより
      https://winscp.net/eng/download.php

      「DOWNLOAD WINSCP」をクリック

      ダウンロードしたインストーラーを実行

      自分のみの利用の為、「現在のユーザー用にインストール」

      使用許諾を「許諾」

      標準的なインストール

      インターフェースの設定は後から変更可能です

      表示 →環境設定

      インストール

      完了

      接続方法

      • プロトコル →SFTP(デフォルト)
      • ホスト名
      • ポート番号 →22(デフォルト)
      • ユーザ名
      • パスワード

      ssh接続を初回実行時、下記の警告が出ます

      承認すると、再度パスワード入力を求められますので、実施

      ディレクトリ同期

      ディレクトリ同期移動

      有効化すると左右同じディレクトリ階層で表示されます

      ファイルの編集でエディタをVScodeにしたい場合

      任意のファイルを「右クリック」→「編集」→「設定」に進みます

      エディタから外部エディタで参照でVSCodeのexeファイルを選択

      追加されたCode(VSCode)を上にをクリックし一番上に移動します

      隠しファイル表示する方法

      メニューバーの歯車(環境設定)からパネルを選択し、隠しファイルを表示するを✓

      参考サイト

      WinSCPとは?インストール方法や使い方を解説する【初心者向け】
      https://miyashimo-studio.jp/blog/detail/winscp-how-to-use/

      DockerでApacheのSSL接続

      こんにちは!今回は、Docker環境でのSSL接続設定について詳しく解説します。SSL(Secure Sockets Layer)は、インターネット上での通信を暗号化し、セキュリティを向上させる重要な技術です。Dockerを使用した開発環境でも、SSL接続を適切に設定することで、より本番環境に近い安全な環境を構築できます。

      自己署名証明書を使用する場合

      https://localhostとしてブラウザで確認すると警告がでます

      警告ページで「詳細」や「続行」などのオプションを探します。 リスクを理解した上で、「Webサイトへ進む」などのオプションを選択します。 ブラウザは警告を表示しつつも、ページの表示を許可します。

      注意点:この方法は開発環境でのみ推奨されます。

      1. docker-compose.ymlの設定

      まず、docker-compose.ymlファイルでSSL接続用のポートを公開する必要があります。以下のように設定します:

      services:
        app:
          ports:
            - "0.0.0.0:8082:80"
            - "443:443"

      この設定により、ホストマシンのポート443がDockerコンテナ内のポート443にマッピングされます。これにより、外部からのHTTPS接続(ポート443)をコンテナ内のApacheサーバーで受け付けることができます。

      ホストマシン(Dockerを実行しているPC) Dockerコンテナ 80 443 80 443 ポートマッピング: 80:80 と 443:443

      2. Dockerfileでの設定

      次に、Dockerfileで以下のようなSSL関連の設定を行います:

      1)SSLモジュールの有効化:

      RUN a2enmod ssl

      2)デフォルトのSSLサイト設定の有効化:

      RUN a2ensite default-ssl

      3)自己署名SSL証明書の生成:

      RUN echo '#!/bin/bash
      openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
      -keyout /etc/ssl/private/apache-selfsigned.key \
      -out /etc/ssl/certs/apache-selfsigned.crt \
      -subj "/CN=localhost"
      sed -i "s|SSLCertificateFile.*|SSLCertificateFile /etc/ssl/certs/apache-selfsigned.crt|" /etc/apache2/sites-available/default-ssl.conf
      sed -i "s|SSLCertificateKeyFile.*|SSLCertificateKeyFile /etc/ssl/private/apache-selfsigned.key|" /etc/apache2/sites-available/default-ssl.conf' > /usr/local/bin/generate_ssl_cert.sh
      
      RUN chmod +x /usr/local/bin/generate_ssl_cert.sh && /usr/local/bin/generate_ssl_cert.sh
      • opensslコマンドを使用して自己署名証明書を生成しています。
      • -x509:自己署名証明書を生成することを指定します。
      • -nodes:秘密鍵をパスワードで保護しないことを指定します。
      • -days 365:証明書の有効期間を1年に設定します。
      • -newkey rsa:2048:2048ビットのRSA鍵を新しく生成します。
      • -keyout-out:鍵と証明書の出力先を指定します。
      • -subj "/CN=localhost":証明書のサブジェクト(ここではCommon Name)を設定します。…証明書が保護するドメイン名または IP アドレスを指定します
      • sed は “stream editor” の略で、テキストの変換や置換に使用
        • 置換パターン:s|旧パターン|新パターン| の形式で、| はデリミタ(区切り文字)として機能

      4)ポート443の公開:

      EXPOSE 80 443

      PHP環境にLet’s Encryptを統合する手順

      ローカルDocker開発環境では難しい、、、?

      DockerPHP開発環境構築:mkcertを使用したSSL接続の設定

      mkcertを使用することで、自己署名証明書の警告なしに安全な開発環境を構築できます。

      1. mkcert「エムケイサート」のインストールと使用

      1)Windows PowerShell”を右クリックし、“管理者として実行”を選択します。

      2)Chocolateyがインストールされていない場合は、まずChocolateyをインストール

      Chocolateyは、Windowsのためのパッケージマネージャーです。

      Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))

      3)mkcertをインストール:

      choco install mkcert

      インストールの確認

      mkcert --version

      4)ローカル認証局のインストール

      mkcert -install

      インストールの確認

      mkcert -CAROOT

      5)証明書の生成:
      プロジェクトディレクトリに移動し、以下のコマンドを実行:

      mkdir certs
      cd certs
      mkcert localhost 127.0.0.1 ::1

      →ファイル生成
      ・証明書ファイル: [最初のドメイン名]+[追加エントリ数].pem (公開鍵を含む)
      ・秘密鍵ファイル: [最初のドメイン名]+[追加エントリ数]-key.pem

      localhost+2.pem 証明書 (公開情報) 公開鍵 localhost+2-key.pem 秘密鍵 対応 秘密 – 共有しないこと!

      6)各ファイルを編集

      鍵ファイルを作成したら下記を実施します

      • Apache設定ファイルでは、SSLエンジンを有効にし、証明書と秘密鍵のパスを正しく指定しています。
      • Dockerfileでは、Apacheの SSL モジュールを有効化しています。
      • docker-compose.ymlでは、443ポートをホストにマッピングし、必要なボリュームをマウントしています。
      DockerPHP開発環境でのSSL設定(mkcert使用) ホストマシン Dockerコンテナ mkcert SSL証明書 Apache PHP 証明書のコピー 1. mkcertインストール 2. 証明書生成 3. Dockerfile設定 4. docker-compose.yml設定 5. Apache SSL設定 6. PHP設定(必要に応じて) 7. アプリケーション開発 HTTPSアクセス
      project_root/
      │
      ├── local-mysite/
      │   ├── certs/
      │   │   ├── localhost+2.pem
      │   │   └── localhost+2-key.pem
      │   ├── env_vars/
      │   │   └── localhost
      │   ├── sites-available/
      │   │   └── 000-default.conf
      │   ├── .htaccess
      │   ├── docker-compose.yml
      │   ├── Dockerfile
      │   ├── php.ini
      │   └── ssl_cert_gen.sh
      │
      └── mysite/
          └── (PHPアプリケーションファイル)

      ▼docker-compose.yml

      version: "3.4"
      services:
        app:
          # Dockerfileからイメージをビルド
          build:
            context: .
            dockerfile: Dockerfile
          
          # ボリュームマウントの設定
          volumes:
            # 環境変数ファイルをマウント
            - ./env_vars:/var/www/env_vars
            # PHPアプリケーションのソースコードをマウント
            - ../0824_mysite:/var/www/html
            # SSL証明書をマウント(読み取り専用)
            - ./certs/localhost+2.pem:/etc/ssl/certs/localhost+2.pem:ro
            # SSL秘密鍵をマウント(読み取り専用)
            - ./certs/localhost+2-key.pem:/etc/ssl/private/localhost+2-key.pem:ro
            # Apache設定ファイルをマウント
            - ./sites-available:/etc/apache2/sites-available
      
          # ポートマッピングの設定
          ports:
            # HTTP: ホストの8082ポートをコンテナの80ポートにマッピング
            - "0.0.0.0:8082:80"
            # HTTPS: ホストの443ポートをコンテナの443ポートにマッピング
            - "443:443"
      
          # 環境変数の設定(必要に応じて追加)
          environment:
            - APACHE_DOCUMENT_ROOT=/var/www/html

      ▼Dockerfile

      # SSL対応PHP開発環境のDockerfile
      
      # PHP 8.1とApacheをベースとした公式イメージを使用
      FROM php:8.1-apache
      
      # カスタムApache設定ファイルをコンテナ内の指定された場所にコピー
      COPY ./sites-available/000-default.conf /etc/apache2/sites-available/000-default.conf
      
      # PDO_MySQL拡張をインストールし、Apacheのrewrite、proxy、proxy_httpモジュールを有効化
      RUN docker-php-ext-install pdo_mysql && a2enmod rewrite proxy proxy_http
      
      # SSLモジュールを有効化
      RUN a2enmod ssl
      
      # デフォルトのSSLサイト設定を有効化
      RUN a2ensite default-ssl
      
      # 必要なパッケージをインストール
      RUN apt-get update && apt-get install -y \
          curl \
          gnupg \
          git \
          unzip \
          openssl \
          # Node.jsのセットアップスクリプトを取得して実行
          && curl -sL https://deb.nodesource.com/setup_18.x | bash - \
          # Node.jsをインストール
          && apt-get install -y nodejs \
          # パッケージリストを削除してイメージサイズを削減
          && rm -rf /var/lib/apt/lists/*
      
      # Composerをコンテナ内にコピー
      COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
      
      # コンテナ内の作業ディレクトリを設定
      WORKDIR /var/www/html
      
      # Composerの設定
      ENV COMPOSER_HOME /var/www/.composer
      RUN mkdir -p $COMPOSER_HOME && chown -R www-data:www-data $COMPOSER_HOME
      
      # Apache実行ユーザーの設定
      ENV APACHE_RUN_USER www-data
      ENV APACHE_RUN_GROUP www-data
      
      # Apacheの設定を変更し、.htaccessが機能するようにAllowOverrideをAllに設定
      RUN sed -i '/<Directory \/var\/www\/>/,/<\/Directory>/ s/AllowOverride None/AllowOverride All/' /etc/apache2/apache2.conf
      
      # コンテナのポート80(HTTP)と443(HTTPS)を公開
      EXPOSE 80 443
      
      # .htaccessファイルをコンテナ内にコピー
      COPY .htaccess /var/www/html/.htaccess
      
      # .htaccessファイルの所有権と権限を設定
      RUN chown www-data:www-data /var/www/html/.htaccess && chmod 644 /var/www/html/.htaccess
      
      # カスタムphp.iniをコンテナ内にコピー
      COPY ./php.ini /usr/local/etc/php/php.ini
      
      # PHPエラーログファイルを作成し、適切な権限を設定
      RUN touch /var/log/php_errors.log && chmod 666 /var/log/php_errors.log
      
      # mkcertで生成した証明書をコンテナ内にコピー
      COPY ./certs/localhost+2.pem /etc/ssl/certs/localhost.crt
      COPY ./certs/localhost+2-key.pem /etc/ssl/private/localhost.key
      
      # 証明書のパーミッションを適切に設定
      RUN chmod 644 /etc/ssl/certs/localhost.crt && \
          chmod 600 /etc/ssl/private/localhost.key
      
      # Apacheをフォアグラウンドで実行
      CMD ["apache2-foreground"]

      ▼000-default.conf

      <VirtualHost *:80>
          ServerName localhost
          DocumentRoot /var/www/html
          Redirect permanent / https://localhost/
      
          # HTTPのアクセスログとエラーログ(必要に応じてコメントを外す)
          # ErrorLog ${APACHE_LOG_DIR}/error.log
          # CustomLog ${APACHE_LOG_DIR}/access.log combined
      </VirtualHost>
      
      <VirtualHost *:443>
          ServerName localhost
          DocumentRoot /var/www/html
          SSLEngine on
          SSLCertificateFile /etc/ssl/certs/localhost+2.pem
          SSLCertificateKeyFile /etc/ssl/private/localhost+2-key.pem
      
          <Directory /var/www/html>
              AllowOverride All
              Require all granted
          </Directory>
      
          ErrorLog ${APACHE_LOG_DIR}/error.log
          CustomLog ${APACHE_LOG_DIR}/access.log combined
      </VirtualHost>
      ```
      
      この設定は、HTTPからHTTPSへのリダイレクトと、SSL/TLS接続の基本的な設定を提供します。
      🐳 PHP 🔒 DockerPHP SSL設定

      mkcertとopenssl、Let’s Encryptを使用したSSL設定の比較

      mkcertとopenssl、Let’s Encryptを使用したSSL設定の主な違いは、使いやすさと生成される証明書の性質にあります。

      mkcertは開発環境に特化しており、簡単な操作で信頼されたSSL証明書を生成できます。一方、opensslはより汎用的で、詳細な設定が可能ですが、使用にはより深い知識が必要です。

      開発環境では、mkcertの使用が推奨されます:

      1. セットアップが簡単で、チーム全体で一貫した環境を構築しやすい。
      2. ブラウザ警告がなく、よりスムーズなテストが可能。
      3. 開発者が証明書の詳細な管理に時間を割く必要がない。

      一方、以下の場合はopensslの使用が適切です:

      1. 本番環境用の証明書生成。
      2. カスタムな証明書要件がある場合。
      3. 証明書生成プロセスの詳細な制御が必要な場合。

      重要なポイントは、開発環境と本番環境で異なるアプローチを取ることが多いということです。開発ではmkcertの簡便性を活かし、本番ではopensslやLet’s Encryptなどを使用して、より厳格なセキュリティを確保することが一般的です。

      SSL/TLS証明書ツールの比較 mkcert Let’s Encrypt OpenSSL ・ローカル開発用 ・簡単な操作 ・自動的にCA作成 ・ブラウザ警告なし ・無料の公的証明書 ・自動更新可能 ・Webサーバー必要 ・ドメイン名必須 ・汎用的なツール ・詳細な設定可能 ・自己署名証明書 ・高度な知識必要 開発環境 本番環境 カスタム要件

      セキュリティの考慮事項

      開発環境でのmkcert使用

      利点:

      1. 簡単にローカルでHTTPS環境を構築できる
      2. 開発者の生産性向上
      3. 本番環境に近い条件でのテストが可能

      制限:

      1. ローカルマシンでのみ信頼される証明書
      2. 公的に認証された証明書ではない

      mkcertはローカル環境に最適化されているため、本番環境特有の問題を見逃す可能性がある

      一般的な本番環境要件:

      1. 信頼された認証局(CA)による証明書の使用
      2. 強力な暗号化アルゴリズムの採用
      3. 定期的な証明書の更新
      4. 適切なサーバー設定(TLSバージョン、暗号スイートなど)
      「PHPMailer」使用手順、セキュリティ(機密情報設定ファイルは別配置等)、Docker開発から本番環境へアップロード

      ローカルDocker環境でPHPMailerを使用する初心者向けの手順

      1)プロジェクトディレクトリ

      project-dir
        ├dockerfile
        └docker-compose.yml

      2)Dockerfile作成

      Dockerfileについて

      Dockerfileに記述された指示に従って、Dockerイメージ(設計図)を作成します

      ▽Dockerfile

      3)docker-compose.yml作成

      version: '3'
      services:
        web:
          build: .
          ports:
            - "8080:80"
          volumes:
            - ./src:/var/www/html

      4)project-dirにて「docker-compose build「docker-compose up -d

      1. docker-compose build:
        • このコマンドは Dockerfile に基づいてイメージをビルドします。
      2. docker-compose up -d:
        • このコマンドはコンテナを起動し、バックグラウンドで実行します。
        • Docker Compose ファイルで定義されたボリュームをマウントします。
          ※もし指定されたホスト側のディレクトリ(この場合は src)が存在しない場合、Docker は自動的にそれを作成します。

      「docker desktop」でContainer作成が確認できます

      5)index.php send_mail.phpを作成

      project_root/
      ├── Dockerfile
      ├── docker-compose.yml
      └── src/
           ├── index.php
           └── send_mail.php
      

      ▽index.php

      <!DOCTYPE html>
      <html lang="ja">
      <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>PHPMailerテスト</title>
      </head>
      <body>
          <h1>PHPMailerテスト</h1>
          <form action="send_mail.php" method="post">
              <label for="to">宛先:</label>
              <input type="email" id="to" name="to" required><br><br>
              
              <label for="subject">件名:</label>
              <input type="text" id="subject" name="subject" required><br><br>
              
              <label for="message">本文:</label><br>
              <textarea id="message" name="message" rows="4" cols="50" required></textarea><br><br>
              
              <input type="submit" value="送信">
          </form>
      </body>
      </html>

      ▽send_mail.php

      <?php
      
      use PHPMailer\PHPMailer\PHPMailer;// PHPMailerライブラリの読み込み
      use PHPMailer\PHPMailer\Exception;// PHPMailerライブラリの読み込み
      
      require 'vendor/autoload.php';// PHPMailerライブラリの読み込み
      
      if ($_SERVER["REQUEST_METHOD"] == "POST") {
          $mail = new PHPMailer(true); //PHPMailerのインスタンス作成(trueは例外を有効にする)
      
          try {
              //SMTPサーバー:Gmailの設定
              $mail->isSMTP();
              $mail->Host       = 'smtp.gmail.com';  // SMTPサーバーを指定
              $mail->SMTPAuth   = true;
              $mail->Username   = 'yourmail@gmail.com';  // SMTPユーザー
              $mail->Password   = 'your app pass';          // SMTPパスワード
              $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
              $mail->Port       = 465;
      
              //送信元、送信先の設定
              $mail->setFrom('yourmail@gmail.com', 'Mailer');
              $mail->addAddress($_POST['to']);
      
              //メール本文
              $mail->isHTML(true);
              $mail->Subject = $_POST['subject'];
              $mail->Body    = $_POST['message'];
      
              $mail->send();
              echo 'メッセージが送信されました';
      
          } catch (Exception $e) {
              echo "メッセージを送信できませんでした。Mailer Error: {$mail->ErrorInfo}";
          }
      }

      6)コンテナ内でPHPMailerをインストールします:

      docker-compose exec web composer require phpmailer/phpmailer

      7)Googleアプリパスワード生成

      Google→セキュリティ→2段階認証プロセス→アプリパスワードから設定

      send_mail.phpを編集

      注意)本番環境では、これらの設定を環境変数や別の設定ファイルに移動し、Gitなどのバージョン管理システムにコミットしないようにすることをおすすめします。

      セキュリティ面

      サニタイズについて

      サニタイズ(sanitize)とは、ユーザーから入力されたデータを安全使用可能な形式に変換することを指します。

      セキュリティヘッダ

      セキュリティを強化するために使用される特別なHTTPヘッダ

      • header(“X-XSS-Protection: 1; mode=block”); //ブラウザの組み込みのXSS対策フィルターを有効にし、攻撃を検出したら、ページの読み込みをブロックします。
      • header(“X-Frame-Options: SAMEORIGIN”); //ページを<frame>、<iframe>、<embed>、<object>で表示することを許可します。ただし、同じオリジンの場合のみ。(クリックジャッキング対策)
      • header(“X-Content-Type-Options: nosniff”); //ブラウザがコンテンツタイプをスニッフィングしないようにします。
      • header(“Referrer-Policy: strict-origin-when-cross-origin”); //クロスオリジンのリクエストに対しては、Referer ヘッダーにはリクエスト元のオリジンのみを含めます。
      • header(“Content-Security-Policy: default-src ‘self’; script-src ‘self’ ‘unsafe-inline’ ‘unsafe-eval’; style-src ‘self’ ‘unsafe-inline’;”); //様々な攻撃(XSS、データ注入など)からサイトを保護します。

      サニタイズ、セキュリティヘッダを導入のためPHPファイルを修正する

      ▽send_mail.php

      <?php
      // ob_start(); は、「出力を一時的に裏側で溜めておく」という命令
      // もし途中でエラーメッセージが表示されたり、予期せぬ出力があったりすると、セキュリティヘッダーを設定できなくなるため
      ob_start();
      
      // セッションが開始されていない場合のみ、設定を変更してセッションを開始
      if (session_status() == PHP_SESSION_NONE) {    // セッション関連の設定
          ini_set('session.cookie_httponly', 1);
          ini_set('session.cookie_secure', 1);        // セッションを開始
          session_start();
      } else {
          // セッションが既に開始されている場合は、そのまま続行
          session_start();
      }
      
      // CSRFトークンがセッションに存在しない場合は生成
      if (!isset($_SESSION['csrf_token'])) {
          $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
      }
      
      // セキュリティヘッダーの設定
      header("X-XSS-Protection: 1; mode=block");
      header("X-Frame-Options: SAMEORIGIN");
      header("X-Content-Type-Options: nosniff");
      header("Referrer-Policy: strict-origin-when-cross-origin");
      header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';");
      
      use PHPMailer\PHPMailer\PHPMailer;// PHPMailerライブラリの読み込み
      use PHPMailer\PHPMailer\Exception;// PHPMailerライブラリの読み込み
      
      require 'vendor/autoload.php';// PHPMailerライブラリの読み込み
      
      if ($_SERVER["REQUEST_METHOD"] == "POST") {
      
          // CSRFトークンの検証
          if (!isset($_POST['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
              // エラーをログに記録
              error_log("CSRF token mismatch. POST token: " . ($_POST['csrf_token'] ?? 'not set') . ", Session token: " . ($_SESSION['csrf_token'] ?? 'not set'));
              die('セッションが期限切れか無効です。ページを更新して再度お試しください。');
          }
      
          // 入力のサニタイズとバリデーション
          $to = filter_var($_POST['to'], FILTER_SANITIZE_EMAIL);
          $subject = htmlspecialchars($_POST['subject'], ENT_QUOTES, 'UTF-8');
          $message = htmlspecialchars($_POST['message'], ENT_QUOTES, 'UTF-8');
      
          // メールアドレスの妥当性チェック
          if (!filter_var($to, FILTER_VALIDATE_EMAIL)) {
              die('無効なメールアドレスです。');
          }
      
          $mail = new PHPMailer(true); //PHPMailerのインスタンス作成(trueは例外を有効にする)
      
          try {
              //SMTPサーバー:Gmailの設定
              $mail->isSMTP();
              $mail->Host       = 'smtp.gmail.com';  // SMTPサーバーを指定
              $mail->SMTPAuth   = true;
              $mail->Username   = 'yourmail@gmail.com';  // SMTPユーザー
              $mail->Password   = 'your app pass';          // SMTPパスワード
              $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
              $mail->Port       = 465;
      
              //送信元、送信先の設定
              $mail->setFrom('yourmail@gmail.com', 'Mailer');
              $mail->addAddress($to);
      
              //メール本文
              $mail->Subject = $subject;
              $mail->Body    = $message;
              $mail->AltBody = strip_tags($message); //HTMLタグを除去
      
              $mail->send();
              echo 'メッセージが送信されました';
      
          } catch (Exception $e) {
              // 詳細なエラー情報を隠し、ログに記録します。
              error_log("メール送信エラー: " . $mail->ErrorInfo);
              echo "メッセージを送信できませんでした。管理者にお問い合わせください。";
          }
      
          // 新しいCSRFトークンの生成
          $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
      
      } else {
          // POSTメソッド以外でアクセスされた場合の処理
          die('不正なアクセスです。');
      }
      
      // 出力バッファリングを終了し、出力を送信
      ob_end_flush();

      ▽index.php

      <?php
      session_start();
      if (!isset($_SESSION['csrf_token'])) {
          $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
      } // CSRFトークンがセッションに存在しない場合は生成
      ?>

      機密情報を含む設定ファイルを作成

      メリット

      • セキュリティ向上:機密情報(パスワードなど)をGitリポジトリにコミットせずに済みます。
        (.gitignoreに追加すると)
      • ポータビリティの向上:異なるサーバーや環境への移行が容易

      1)プロジェクトのルートディレクトリに configフォルダその中にconfig.php を作成します

      project-dir\config\config.php

      <?php
      define('SMTP_HOST', getenv('SMTP_HOST') ?: 'smtp.example.com');
      define('SMTP_USER', getenv('SMTP_USER') ?: 'user@example.com');
      define('SMTP_PASS', getenv('SMTP_PASS') ?: 'password');

      2)上記修正に伴いsend_mail.php、docker-compose.ymlも修正

      ▽send_mail.php

      …
      
      require 'vendor/autoload.php';// PHPMailerライブラリの読み込み
      require_once __DIR__ . '/../config/config.php';
      …
      
      
              //SMTPサーバー:Gmailの設定
              $mail->isSMTP();
              $mail->Host       = SMTP_HOST; // SMTPサーバーを指定
              $mail->Username   = SMTP_USER; // SMTPユーザー
              $mail->Password   = SMTP_PASS; // SMTPパスワード
              $mail->SMTPAuth   = true;
              $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
              $mail->Port       = 465;

      ▽docker-compose.yml

          volumes:
            - ./src:/var/www/html
            - ./config:/var/www/config.php

      3)2)の修正を行った後、Dockerコンテナを再ビルドして起動

      docker-compose down
      docker-compose build
      docker-compose up -d

      2)プロジェクトのルートディレクトリに .gitignore ファイルを作成

      ▽.gitignore

      /config/config.php

      本番環境のさくらインターネットへアップロード

      ローカルDocker開発環境、さくらインターネットの本番環境の両環境併用できるよう下記の通り修正

      ▽config.php

      <?php
      // 環境の判別
      $is_local = ($_SERVER['SERVER_NAME'] == 'localhost' || $_SERVER['SERVER_ADDR'] == '127.0.0.1');
      
      // デバッグモードの設定
      define('DEBUG_MODE', $is_local); // ローカルではデバッグモードON、本番では OFF
      // 本番環境で一時的にデバッグモードを有効化する場合
      // define('DEBUG_MODE', true);  // コメントを外して使用
      
      
      if ($is_local) {
          // ローカル環境(Docker)の設定
          define('SMTP_HOST', getenv('SMTP_HOST') ?: 'smtp.gmail.com');
          define('SMTP_USER', getenv('SMTP_USER') ?: 'xxxx@gmail.com');
          define('SMTP_PASS', getenv('SMTP_PASS') ?: 'xxxx');
          define('SMTP_PORT', 587);
          define('SMTP_SECURE', 'tls');
      } else {
          // さくらインターネット環境の設定
          define('SMTP_HOST', 'xxxx');
          define('SMTP_USER', 'xxxx');
          define('SMTP_PASS', 'xxxx');
          define('SMTP_PORT', 587);
          define('SMTP_SECURE', 'tls');
      }
      
      // サイトの URL
      define('SITE_URL', $is_local ? 'http://localhost:8080' : 'xxxx');

      さくらインターネットのメール情報の確認方法の参考サイト

      下記のホスト、ユーザ名、パスワードについて

      • define(‘SMTP_HOST’, ‘xxxx’);
      • define(‘SMTP_USER’, ‘xxxx’);
      • define(‘SMTP_PASS’, ‘xxxx’);

      さくらのメールボックスを PHPMailer で SMTP + STARTTLS で送信する時の注意点https://qiita.com/ameyamashiro/items/c7283bd1ec5dd3146ef9

      ▽send_mail.php

      <?php
      // ob_start(); は、「出力を一時的に裏側で溜めておく」という命令
      // もし途中でエラーメッセージが表示されたり、予期せぬ出力があったりすると、セキュリティヘッダーを設定できなくなるため
      ob_start();
      
      // 設定ファイルの読み込み
      if (strpos(__DIR__, '/home/siennahare23') !== false) {
          // さくらインターネット環境
          require_once '/home/siennahare23/config/config.php';
      } else {
          // ローカル環境
          require_once __DIR__ . '/../config/config.php';
      }
      
      // セッションが開始されていない場合のみ、設定を変更してセッションを開始
      if (session_status() == PHP_SESSION_NONE) {
          // セッション関連の設定
          ini_set('session.cookie_httponly', 1);
          ini_set('session.cookie_secure', 1);
          
          // セッションを開始
          session_start();
      } else {
          // セッションが既に開始されている場合は、そのまま続行
          session_start();
      }
      
      // CSRFトークンがセッションに存在しない場合は生成
      if (!isset($_SESSION['csrf_token'])) {
          $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
      }
      
      // セキュリティヘッダーの設定
      header("X-XSS-Protection: 1; mode=block"); //ブラウザの組み込みのXSS対策フィルターを有効にし、攻撃を検出したら、ページの読み込みをブロックします。
      header("X-Frame-Options: SAMEORIGIN"); //ページを<frame>、<iframe>、<embed>、<object>で表示することを許可します。ただし、同じオリジンの場合のみ。(クリックジャッキング対策)
      header("X-Content-Type-Options: nosniff"); //ブラウザがコンテンツタイプをスニッフィングしないようにします。
      header("Referrer-Policy: strict-origin-when-cross-origin"); //クロスオリジンのリクエストに対しては、Referer ヘッダーにはリクエスト元のオリジンのみを含めます。
      header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';"); //様々な攻撃(XSS、データ注入など)からサイトを保護します。
      
      use PHPMailer\PHPMailer\PHPMailer;// PHPMailerライブラリの読み込み
      use PHPMailer\PHPMailer\Exception;// PHPMailerライブラリの読み込み
      
      require 'vendor/autoload.php';// PHPMailerライブラリの読み込み
      
      if ($_SERVER["REQUEST_METHOD"] == "POST") {
      
          // CSRFトークンの検証
          if (!isset($_POST['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
              // エラーをログに記録
              error_log("CSRF token mismatch. POST token: " . ($_POST['csrf_token'] ?? 'not set') . ", Session token: " . ($_SESSION['csrf_token'] ?? 'not set'));
              die('セッションが期限切れか無効です。ページを更新して再度お試しください。');
          }
      
          // 入力のサニタイズとバリデーション
          $to = filter_var($_POST['to'], FILTER_SANITIZE_EMAIL);
          $subject = htmlspecialchars($_POST['subject'], ENT_QUOTES, 'UTF-8');
          $message = htmlspecialchars($_POST['message'], ENT_QUOTES, 'UTF-8');
      
          // メールアドレスの妥当性チェック
          if (!filter_var($to, FILTER_VALIDATE_EMAIL)) {
              die('無効なメールアドレスです。');
          }
      
          $mail = new PHPMailer(true); //PHPMailerのインスタンス作成(trueは例外を有効にする)
      
          // デバッグモードの設定
          // $mail->SMTPDebug = 3; // デバッグ出力を有効化
          $mail->Debugoutput = 'html'; // デバッグ出力形式をHTMLに設定
      
          try {
              //SMTPサーバー:Gmailの設定
              $mail->isSMTP();
              $mail->Host       = SMTP_HOST; // SMTPサーバーを指定
              $mail->Username   = SMTP_USER; // SMTPユーザー
              $mail->Password   = SMTP_PASS; // SMTPパスワード
              $mail->SMTPAuth   = true;
              $mail->SMTPSecure = SMTP_SECURE;
              $mail->Port       = SMTP_PORT;
      
              //送信元、送信先の設定
              $mail->setFrom(SMTP_USER, 'Mailer');
              $mail->addAddress($to);
      
              //メール本文
              $mail->isHTML(true);
              $mail->Subject = $subject;
              $mail->Body    = $message;
              $mail->AltBody = strip_tags($message); //HTMLタグを除去
      
              $mail->send();
              echo 'メッセージが送信されました';
      
          } catch (Exception $e) {
      
              if (DEBUG_MODE) {
                  echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}<br>";
                  echo "Detailed error: " . $e->getMessage();
              } else {
                  echo "メッセージを送信できませんでした。管理者にお問い合わせください。";
              }
              error_log("メール送信エラー: " . $mail->ErrorInfo);
          }
      
          // 新しいCSRFトークンの生成
          $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
      
      } else {
          // POSTメソッド以外でアクセスされた場合の処理
          die('不正なアクセスです。');
      }
      
      // 出力バッファリングを終了し、出力を送信
      ob_end_flush();

      アップロード

      <ローカルのディレクトリ構成>

      project_root/
      │
      ├── src/
      │   ├── index.php
      │   ├── send_mail.php
      │   └── vendor/ ...
      ├── config/
      └── config.php
      │
      └── docker-compose.yml

      <本番環境のディレクトリ構成>

      /home/xxxx/www/xxxx/
      │
      ├── contact/
      │  │
      │   ├── index.php
      │   ├── send_mail.php
      │   └── vendor/ ...
      │
      └── config/
          └── config.php

      実際のフォーム

      【VSCode連携】「GitHub Copilot」使い方

      「GitHub Copilot」の特徴

      VSCodeと連携可能(その他エディターも)

      「GitHub Copilot」の費用

      GitHub Copilot Individual サブスクリプションは、月単位または年単位のサイクルで利用できます。

      • 毎月の支払いサイクルを選択すると、カレンダー月ごとに 10 米国ドル が課金されます。
      • 年単位の支払いサイクルを選択した場合、年間 100 米ドル が課金されます。

      GitHub Copilot の課金
      https://docs.github.com/ja/billing/managing-billing-for-github-copilot/about-billing-for-github-copilot

      「GitHub Copilot」の始め方

      1)プロフィールアイコンをクリック、メニューから「Settings」をクリック

      2)「Copilot」をクリック、「Start free trial」をクリック

      3)プランを選択し、「Get access to GitHub Copilot」をクリック

      4)名前、住所を入力し、「Save」

      5)クレジットカード等支払い方法情報を入力

      6)登録情報を確認後「Submit」をクリック

      Select Your preferencesで好みの設定

      • 「Suggestions matching public code…」ではGithubのパブリックコードの使用を許可するかで、特にルールがなければ「Allowed」でOK
      • 「Allow GitHub to use my code snippets from …」自分のコードをGithubが読み取ってよいかの設定です
        ※チェックしたとしてもプライベートコードは公開されません

      以上で完了です

      「GitHub Copilot」の解約方法

      解約方法もGithubのページ、ユーザアイコンをクリックし設定より可能です

      参考サイト

      https://app-tatsujin.com/what-is-githubcopilot-how-to-start
      Rubyインストール手順(Windows)

      Rubyインストーラーをダウンロード

      RubyInstallerのダウンロードページ
      https://rubyinstaller.org/downloads

      PCのビット数を確認し、インストーラーを選択

      ▽設定→システム→バージョン情報より確認できます

      インストーラーを実行

      1)「Install for me only」をクリック

      2)同意して「Next」をクリック

      3)下記の設定(デフォルトのまま)「Install」をクリック

      4)下記の設定(デフォルトのまま)「Next」をクリック

      →インストールが開始されます

      5)数分後、下記画面が表示されインストール終了

      →「Finish」をクリックするとインストーラーの画面は消えてターミナルが表示されます

      「MSYS2」のインストール

      「MSYS2」… Ruby開発で便利なツール

      「1」、「Enter」を押下するとインストールが開始されます

      続いて「2」、「Enter」を押下、最新版にアップデート

      最後に「3」、「Enter」を押下、3つ目のメニュー実行

      完了したら「Enter」をクリックして「MSYS2」のインストール完了

      Rubyインストールされているか確認

      ターミナルにてバージョンを確認→表示されれば正しくインストールされています

      C:\Users\xxx>ruby -v
      ruby 3.3.3 (2024-06-12 revision f1c7b6f435) [x64-mingw-ucrt]

      参考サイト

      https://web-camp.io/magazine/archives/15051
      「Github Page」にReactを公開する方法

      1)Reactプロジェクトを作成

      npx create-react-app app-dir

      2)Branchの発行し、GitHubのリポジトリを作成

      VS Codeの場合「Branchの発行」をクリックしリポジトリ名を入力する

      3)Reactアプリのビルド

      npm run build

      →buildディレクトリが作成されます

      4)gh-pagesをインストール

      Reactアプリを簡単にデプロイできる「gh-pages」ツールをインストール

      npm install --save gh-pages

      package.jsonを編集

      ▽package.jsonにて「homepageフィールド」の追加、「デプロイスクリプト」の追加

      {
        "homepage": "https://<GitHubアカウント名>.github.io/<GitHubリポジトリ名>/",
        "name": "app-dir",
        "version": "0.1.0",
        "private": true,
        "dependencies": {
          "@testing-library/jest-dom": "^5.17.0",
          "@testing-library/react": "^13.4.0",
          "@testing-library/user-event": "^13.5.0",
          "react": "^18.3.1",
          "react-dom": "^18.3.1",
          "react-scripts": "5.0.1",
          "web-vitals": "^2.1.4"
        },
        "scripts": {
          "start": "react-scripts start",
          "build": "react-scripts build",
          "predeploy": "npm run build",
          "deploy": "gh-pages -d build",
          "eject": "react-scripts eject"
        },
        "eslintConfig": {
          "extends": [
            "react-app",
            "react-app/jest"
          ]
        },
        "browserslist": {
          "production": [
            ">0.2%",
            "not dead",
            "not op_mini all"
          ],
          "development": [
            "last 1 chrome version",
            "last 1 firefox version",
            "last 1 safari version"
          ]
        }
      }

      「npm run deploy」を実行する

      cd app-dir
      npm run deploy

      公開URLについて

      "homepage": "https://github0612.github.io/app-dir0612/",

      「Branchの発行」後に作成されるリポジトリにてGitHubでBranchを「Save」をクリックすると数分後URLが表示されます

      ▽数分後URLが表示されます

      ▽サイトにて表示確認できます

      参考サイト

      GitHub Pages でReact Appを公開する方法
      https://note.com/wecken/n/n73196eb22a51

      ReactアプリをGithub Pagesにデプロイする方法
      https://qiita.com/snow_swallow/items/8455dd135b81fe0ce25f

      【WordPressエラー】Warning: Cannot modify header information – headers already sent by

      エラー内容

      functions.phpを作成し、編集していたところエラー発生。

      「functions.php」の1行目のコメントアウトを削除したところ解消しました。

      <!-- カスタムフィールドの値をタイトルに自動で反映    -->
      <?php
      add_action('acf/save_post', 'replace_post_title');
      function replace_post_title( $post_id ) {
      
          // タイトルの入ったフィールドを取得
          $newtitle = get_field("news_text",$post_id);
      
      	//タイトルが空でない場合は更新
          if( $newtitle ) {
              $args = array(
      			"ID"=>$post_id,
      			"post_title" => $newtitle
      		);
      		wp_update_post($args);
          }
      }

      ▽1行目のコメントアウトを削除

      <?php
      add_action('acf/save_post', 'replace_post_title');
      function replace_post_title( $post_id ) {
      
          // タイトルの入ったフィールドを取得
          $newtitle = get_field("news_text",$post_id);
      
      	//タイトルが空でない場合は更新
          if( $newtitle ) {
              $args = array(
      			"ID"=>$post_id,
      			"post_title" => $newtitle
      		);
      		wp_update_post($args);
          }
      }

      参考サイト

      https://irodori-design-web.com/blog/blog-1446
      【WordPressエラー】「Warning: Undefined variable $post in …」「Warning: Attempt to read property “ID” on null in …」

      PHP7.3からPHP8.0に変更がありWordPressでエラーメッセージが発生しました。

      表題のエラーがPHP8.0より通知ではなく、独立エラーとして扱われるようになった為です

      エラー内容

      「get_the_terms」記事に基づくタクソノミーの取得のための下記の記述でエラー発生

      <?php 
      $terms = get_the_terms($post->ID, 'tag-workshop');
      if ($terms) {
      foreach ($terms as $term) {
      echo '<a class="tagc-tag-workshop" tag-id="' . $term->term_id . '" href="' . get_permalink() . '">' . $term->name . '</a>';
      }
      ?>

      Warning: Undefined variable $post in …

      Warning: Attempt to read property “ID” on null in …

      (解決策)変数を定義する

      Undefined variable $postとは変数の未定義によるエラーです

      global $post;を追記することによりエラー解消しました

      <?php 
      global $post;
      $terms = get_the_terms($post->ID, 'tag-workshop');
      if ($terms) {
      foreach ($terms as $term) {
      echo '<a class="tagc-tag-workshop" tag-id="' . $term->term_id . '" href="' . get_permalink() . '">' . $term->name . '</a>';
      }
      ?>

      参考サイト

      【PHP7から8へ切替】Warning: Undefined variableが表示された場合の修正方法
      https://it-column.mjeinc.co.jp/archives/3513

      WordPressのグローバル変数「$post」とは?中身や使い方について解説
      https://tcd-theme.com/2024/01/wp-post-object.html

      PHP8.0でWordPressの「Attempt to read property “ID” on null」のエラーを解決したい
      https://ja.stackoverflow.com/questions/85387/php8-0%e3%81%a7wordpress%e3%81%ae-attempt-to-read-property-id-on-null-%e3%81%ae%e3%82%a8%e3%83%a9%e3%83%bc%e3%82%92%e8%a7%a3%e6%b1%ba%e3%81%97%e3%81%9f%e3%81%84

      【Googleタグマネージャー】「Page Path」で設定したが正しく発火しない

      ▽タグを作成したが発火しない

      トリガーの設定は「Page Path」

      トリガーの設定は「Page Path」で「含む」です。

      パスはドメインの後ろの文字列です

      発火しない理由

      「パス(/○○/)」に全角文字が含まれているからかと思われます。

      ▽WordPressパーマリンク設定で投稿名にチェックしているため、記事タイトルを日本語で書くと日本語パーマリンクになります

      日本語パーマリンクの注意点

      日本語パーマリンク(全角文字を含む)はコピーする際にエンコードされます。

      (1. エンコード前)
      https://internet.mints.ne.jp/googleタグマネージャーpage-pathで設定したが正しく発火し/

      (2. 貼り付け後)
      https://internet.mints.ne.jp/google%e3%82%bf%e3%82%b0%e3%83%9e%e3%83%8d%e3%83%bc%e3%82%b8%e3%83%a3%e3%83%bcpage-path%e3%81%a7%e8%a8%ad%e5%ae%9a%e3%81%97%e3%81%9f%e3%81%8c%e6%ad%a3%e3%81%97%e3%81%8f%e7%99%ba%e7%81%ab%e3%81%97

      (解決策)トリガーの設定でエンコード後のパスを設定する

      さいしょの発火しない設定方法は(1. エンコード前)のパスを設定していました。

      パスを(2. 貼り付け後)…エンコードされたものを設定したところ正しく発火しました。

      参考サイト

      https://kitsunecode.net/blog-homepage/permalink-japanese/#:~:text=%E3%83%AF%E3%83%BC%E3%83%89%E3%83%97%E3%83%AC%E3%82%B9%E3%81%A7%E3%83%91%E3%83%BC%E3%83%9E%E3%83%AA%E3%83%B3%E3%82%AF,%E3%82%B9%E3%83%A9%E3%83%83%E3%82%B0%E3%81%AB%E3%81%AA%E3%82%8B%E3%81%8B%E3%82%89%E3%81%A7%E3%81%99%E3%80%82

      「Advanced Custom Fields」の内容を自動でタイトルに反映させたい

      カスタムフィールドの値を自動でタイトルに反映させたい

      ▽投稿画面の編集項目をカスタムフィールドの値のみ(お知らせ内容のテキスト)にしています。

      ▽タイトルがないので、(タイトルなし)となります。
      そこで「カスタムフィールドの値を自動でタイトルに反映させたい」ということになりました。

      functions.phpを編集

      // wordpressでタイトルを空白で投稿した時に、自動的にタイトルを挿入する
      // https://teratail.com/questions/38598
      add_action('acf/save_post', 'replace_post_title');
      function replace_post_title( $post_id ) {
      
          // タイトルの入ったフィールドを取得
          $newtitle = get_field("news_text",$post_id);
      
      	//タイトルが空でない場合は更新
          if( $newtitle ) {
              $args = array(
      			"ID"=>$post_id,
      			"post_title" => $newtitle
      		);
      		wp_update_post($args);
          }
      }

      ※news_textはフィールド名です↓

      参考サイト

      WordPressでカスタムフィールドを自動保存したい
      https://teratail.com/questions/38598

      All-in-One WP Migration Unlimited Extensionにて「Unlimited Extension は最新バージョンではありません。使用前にプラグインを更新する必要があります。」

      アップデート手順

      1)「All-in-One WP Migration」をインストール、有効化

      2)「all-in-one-wp-migration-unlimited-extension」をインストール

      ▽プラグインを有効化

      3)バージョンを更新

      ▽「アップデートを確認」か「更新」をクリック

      Maximum upload file size: Unlimited

      さくらインターネットにて管理画面より「php.ini」を下記の通りに設定しおりますが、サイズ1Gのインポートもできました。

      ▽「php.ini」
      upload_max_filesize = 512M

      ▽ワードプレス「All-in-One WP Migration」インポート画面
      Maximum upload file size: Unlimited

      【生成AI】【illustrator】テキストからベクター生成の使い方(バージョン28.0~新機能)

      テキストからベクター生成の使い方

      ▽長方形ツールで表示させたい場所に作成

      ▽「ウィンドウ」→「プロパティ」をクリックするとプロパティ、「テキストからベクター生成(Beta)」が表示されます

      ▽プロンプトに生成したい画像の検索ワードを入力するとバリエーションが表示されます

      似たようなテイストのイラストを作成する方法

      ▽「参照アセット」を有効化し「ピッカー」をクリックし似たテイストにしたい画像を選択→「生成(Beta)」をクリック

      参考サイト

      【イラレの生成AI】 テキストからベクター作成の使い方【Illustrator】
      https://321web.link/illustrator-generative-vecter

      https://www.tactsystem.co.jp/blog/catalog_36
      【WordPress】本文入力欄を非表示にする方法

      「カスタム投稿タイプ」、「カスタムフィールド」を利用しているときに、本文入力欄が不要な場合があると思います。

      非表示にする方法の紹介です

      functions.phpを編集する

      // 本文入力欄非表示
      add_action( 'init' , 'my_remove_post_support' );
      function my_remove_post_support() {
           remove_post_type_support('非表示にしたいカスタム投稿タイプスラッグ','editor'); 
      }

      【Googleサーチコンソール】検索画面で表示させたくない「Googleのインデックス削除」

      Googleのインデックス削除とは

      Googleのインデックス削除を申請すると検索画面で該当ページが表示されなくなります

      不要なページのインデックス削除はSEOの観点からも重要

      Googleサーチコンソールでインデックス削除の手順

      ▽削除したいURLのあるサイトのプロパティを選択し「削除」をクリック

      ▽「新しいリクエスト」クリック

      ▽URLの入力し「次へ」

      ▽「リクエストを送信」クリック

      検索結果反映には1日ほどかかります

      インデックス削除されているか確認する方法

      site:該当URLで検索

      参考サイト

      https://rank-quest.jp/column/column/index-removals
      【Googleサーチコンソール】プロパティタイプ「ドメイン」と「URLプレフィックス」違い

      「ドメイン」と「URLプレフィックス」の2種類のプロパティタイプ

      Googleサーチコンソールでプロパティを追加する場合「ドメイン」と「URLプレフィックス」の2種類のプロパティタイプがあります。

      それぞれの特徴は下記の通り

      プロパティタイプが「ドメイン」

      • wwwあり/なし、http/https、サブドメインすべてのドメインを1つのプロパティで確認できる
        • https://ntorelabo.com/(httpsでwwwあり)
        • http://www.ntorelabo.com/(httpでwwwあり)
        • https://ntorelabo.com/(httpsでwwwなし)
        • http://ntorelabo/(httpでwwwなし)
        • https://○○.ntorelabo/(サブドメイン)
        • https://www.ntorelabo/○○/(サブディレクトリ)
      • サブドメイン、サブディレクトリのみデータの確認ができない
      • プロパティの追加方法がDNSレコードの確認のみ

      プロパティタイプが「URLプレフィックス」

      • 該当URLのみのため、wwwあり/なし、http/httpsの違いは対応できない
        例)URLプレフィックス「https://ntorelabo.com/(httpsでwwwなし)」であれば、↓以下が対象
        • https://ntorelabo.com/(httpsでwwwなし)
        • https://ntorelabo.com/〇〇〇〇/(httpsでwwwなし・サブディレクトリ)
      • プロパティの追加方法が4種(ドメインより簡単)
        • HTMLファイルのアップロード
        • metaタグの追加
        • Googleタグマネージャーのアカウントを利用
        • Googleアナリティクスのアカウントを利用
      【初心者】WordPressでjQuery使用するときの注意「Uncaught TypeError: $ is not a function」

      Uncaught TypeError: $ is not a function

      WordPressでjQueryを使用し「Uncaught TypeError: $ is not a function」と表示され、処理がされないとなりました。

      そもそも「$(function(){})」とは

      「$(function(){})」の記述によって中の処理はHTMLを読み込んでから実行されます

      →この記述がないとHTMLの指定を含まれるjQueryのプログラムでエラーとなります

      Uncaught TypeError: $ is not a function」の原因

      WordPressで「$」マークを使うとjQueryが動作しない場合があります。

      ※一般的に「jQuery」を省略し「$」と記述します

      ↓WordPressでデフォルトで読み込まれるjQueryに「noConflict」という関数があります、そのため$の使用ができません

      Uncaught TypeError: $ is not a function」の解決策

      「$」が使用できないので、下記の通り書き直します

      ▼修正前

      $(function(){ 
            $('○○')○○;
      });

      ▼修正後

      jQuery(function() {
            jQuery('○○')○○;
      });
      ネームサーバーをLOLIPOPに設定していて、DNSレコード設定がしたい場合(Googleサーチコンソールのプロパティの確認で必要)

      Googleサーチコンソールでプロパティを追加する際にプロパティタイプをドメインを選択した場合、DNSレコードの確認が必要になります。(↓所有権の証明)

      そこでDNSレコードの設定をすることになるのですが、LOLIPOPではできません。

      ですが、LOLIPOPのサーバー契約のまま(サイトの表示等のサービスを利用)DNSレコードの設定をする方法がありますので、本記事投稿しました。

      やや複雑な手順で、DNSレコードの意味や、各サーバー、ドメイン会社の特徴を理解しておく必要があります。

      ネームサーバーをロリポップに設定している場合

      今回該当パターンを確認すると下記2通り

      • ロリポップでドメインを取得し、ロリポップのサービス(サイトの表示やメール)を利用
      • 他社でドメインを取得し、ドメイン適用先でロリポップを選択

      上記の場合ネームサーバーがロリポップに設定されます。

      ロリポップではDNSレコードの設定ができない

      ロリポップのネームサーバーを選択すると、DNSレコードの設定ができません。

      ロリポップでネームサーバーを設定するとDNSレコードは下記の通りになりますが、

      mytalk.siteINA163.44.185.218
      mytalk.siteINMX10mx01.lolipop.jp
      mytalk.siteINNSuns01.lolipop.jp
      mytalk.siteINNSuns02.lolipop.jp
      mytalk.siteINTXTv=spf1 include:_spf.lolipop.jp ~all

      ネームサーバー設定をすると、ネームサーバー以外のレコード値も自動的に設定されるようです。

      お名前ドットコムで取得したドメインでロリポップにネームサーバー設定をしている場合、ロリポップのDNSを利用していることになります。この場合、お客様任意のレコード情報を設定することはできません。ロリポップを利用するために必要なレコードが自動で設定されております。

      プライマリネームサーバー: uns01.lolipop.jp セカンダリネームサーバー: uns02.lolipop.jp

      詳細は以下のリンクをご参照ください。

      https://lolipop.jp/?a8=YRvtwRALNEc6Zvn0zhcfFz06io95nFHX4FPpFMoKusHcDFnKQEPR_EPL86uFSqvYuRQoNFP2tjZ5xs00000000404001

      「ロリポップのDNS」はお客様任意のレコード情報を設定することができません
      https://support.lolipop.jp/hc/ja/articles/4403084397203-%E3%83%AD%E3%83%AA%E3%83%9D%E3%83%83%E3%83%97%E3%81%AEDNS%E3%81%A7%E3%83%AC%E3%82%B3%E3%83%BC%E3%83%89%E6%83%85%E5%A0%B1%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B%E3%81%AB%E3%81%AF%E3%81%A9%E3%81%86%E3%81%97%E3%81%9F%E3%82%89%E3%81%84%E3%81%84%E3%81%A7%E3%81%99%E3%81%8B

      DNSレコードの設定が可能なネームサーバーをロリポップ以外で設定する

      DNSレコードの設定はしたいが、ロリポップのサービスは使用したい場合

      DNSレコードの設定が可能な会社(ムームードメイン、Xserver …)にて下記の通り設定します

      • Aレコード、MXレコードはそのまま
      • ネームサーバーの値をロリポップから変更した先の値に変更
      • TXTで追記したい内容を編集
        (※1 記述方法はサーバー会社ごとに違うhttps://kiraba.jp/about-multi-spf-records-setting/
        (※2 _spf.lolipop.jpがないと正しくメールを使えない可能性がある(送信元ネームサーバーの認識、特定ができない→迷惑メールとされるかもしれない)

      DNSレコード取得ツール

      http://webadmin.jp/tooldns/

      捕捉情報

      Aレコードはサイト表示のため

      MXレコードはメールを使うため

      ネームサーバーの切り替えは(最大で)72時間

      Xserverドメインで取得した独自ドメインをロリポップで使用

      手順

      ▼Xserverレンタルサーバの管理画面にログインし対象のドメインを選択

      ▼ドメインの契約情報のネームサーバー設定の「設定変更」を選択

      ▼その他のサービスで利用するを選択しネームサーバー1、ネームサーバー2に下記の通り入力

      ネームサーバー1uns01.lolipop.jp
      ネームサーバー2uns02.lolipop.jp

      確認画面へ進むをクリック

      ▼「設定を変更する」をクリック

      ▼ロリポップユーザー専用画面にて

      公開(アップロード)フォルダについては複数のドメインを運用する場合は必ず入力

      参考サイト

      https://monefree.com/lolipop-xsever
      【LOLIPOP】サブディレクトリにインストールしたWordPressをドメイン直下で表示

      インストール手順

      <サーバーの管理画面>
      WordPressのインストール

      ▼インストール先にwpディレクトリを指定

      ▼インストール履歴

      <WordPress管理画面>

      ▼「/wp」を削除し、画面左下の「変更を保存」ボタンをクリック

      (wpフォルダ内とは別でドメイン直下にも)

      ↓.htaccess(ドメイン直下に生成)

      
      # BEGIN WordPress
      # "BEGIN WordPress" から "END WordPress" までのディレクティブ (行) は
      # 動的に生成され、WordPress フィルターによってのみ修正が可能です。
      # これらのマーカー間にあるディレクティブへのいかなる変更も上書きされてしまいます。
      <IfModule mod_rewrite.c>
      RewriteEngine On
      RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
      RewriteBase /
      RewriteRule ^index\.php$ - [L]
      RewriteCond %{REQUEST_FILENAME} !-f
      RewriteCond %{REQUEST_FILENAME} !-d
      RewriteRule . /index.php [L]
      </IfModule>
      
      # END WordPress

      ↓.htaccess(wpフォルダ内)

      
      # BEGIN WordPress
      # "BEGIN WordPress" から "END WordPress" までのディレクティブ (行) は
      # 動的に生成され、WordPress フィルターによってのみ修正が可能です。
      # これらのマーカー間にあるディレクティブへのいかなる変更も上書きされてしまいます。
      <IfModule mod_rewrite.c>
      RewriteEngine On
      RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
      RewriteBase /wp/
      RewriteRule ^index\.php$ - [L]
      RewriteCond %{REQUEST_FILENAME} !-f
      RewriteCond %{REQUEST_FILENAME} !-d
      RewriteRule . /wp/index.php [L]
      </IfModule>
      
      # END WordPress

      ↓こちらの手順はとばしてしまいました、、が問題なさそう。次回は実施したいと思います。

      〇パーマリンク設定を保存する

      設定 > パーマリンク へ移動し、何も変更せずに[変更を保存] をクリックします。
      この操作で、WordPressに更新を伝えます。

      <FTPソフト>
      ファイル複製し編集

      下記の通りのファイル構成です

      〇wpフォルダ内index.phpファイルをダウンロード

      ▼変更前

      # BEGIN WordPress
      # "BEGIN WordPress" から "END WordPress" までのディレクティブ (行) は
      # 動的に生成され、WordPress フィルターによってのみ修正が可能です。
      # これらのマーカー間にあるディレクティブへのいかなる変更も上書きされてしまいます。
      <IfModule mod_rewrite.c>
      RewriteEngine On
      RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
      RewriteBase /wp/
      RewriteRule ^index\.php$ - [L]
      RewriteCond %{REQUEST_FILENAME} !-f
      RewriteCond %{REQUEST_FILENAME} !-d
      RewriteRule . /wp/index.php [L]
      </IfModule>
      
      # END WordPress

      ▼変更後

      # BEGIN WordPress
      # "BEGIN WordPress" から "END WordPress" までのディレクティブ (行) は
      # 動的に生成され、WordPress フィルターによってのみ修正が可能です。
      # これらのマーカー間にあるディレクティブへのいかなる変更も上書きされてしまいます。
      <IfModule mod_rewrite.c>
      RewriteEngine On
      RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
      RewriteBase /
      RewriteRule ^index\.php$ - [L]
      RewriteCond %{REQUEST_FILENAME} !-f
      RewriteCond %{REQUEST_FILENAME} !-d
      RewriteRule . /index.php [L]
      </IfModule>
      
      # END WordPress

      〇wpフォルダ内のindex.phpをダウンロードし下記の通り編集

      ▼変更前

      <?php
      /**
       * Front to the WordPress application. This file doesn't do anything, but loads
       * wp-blog-header.php which does and tells WordPress to load the theme.
       *
       * @package WordPress
       */
      
      /**
       * Tells WordPress to load the WordPress theme and output it.
       *
       * @var bool
       */
      define( 'WP_USE_THEMES', true );
      
      /** Loads the WordPress Environment and Template */
      require __DIR__ . '/wp-blog-header.php';
      

      ▼変更後

      <?php
      /**
       * Front to the WordPress application. This file doesn't do anything, but loads
       * wp-blog-header.php which does and tells WordPress to load the theme.
       *
       * @package WordPress
       */
      
      /**
       * Tells WordPress to load the WordPress theme and output it.
       *
       * @var bool
       */
      define( 'WP_USE_THEMES', true );
      
      /** Loads the WordPress Environment and Template */
      require __DIR__ . '/wp/wp-blog-header.php';
      

      ドメイン直下にアップロード

      以上でドメイン直下でサイトの表示が確認できました

      <補足>

      サイト表示したときログインしているにもかかわらず、管理バーが表示されなかったのですが、WordPressをログインしなおしたら、表示されました。

      複数のサブディレクトリにそれぞれWordPressサイトを作成し、ドメイン直下のindex.phpの記述によって切り替えができそうです

      参考サイト

      WordPressがドメイン直下ではなくサブディレクトリにインストールされている場合、「Override the base URL of the sitemap」を設定する必要があります。
      https://nandemo-nobiru.com/wp-5941

      【ロリポップ版】WordPressインストール方法・始め方
      https://webst8.com/blog/lolipop-wordpress-open

      ロリポップからWordPressを簡単インストールする方法を解説
      https://communityserver.org/contents/4319

      サブディレクトリ(/wp/)にインストールしたwordpressをドメイン直下に表示する方法【ルートディレクトリを変更】
      http://kawatama.net/web/974#google_vignette

      サブディレクトリにWordPressを作成した場合のサイトマップはどうすれば良いですか?
      https://support.google.com/webmasters/thread/166288539/%E3%82%B5%E3%83%96%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%88%E3%83%AA%E3%81%ABwordpress%E3%82%92%E4%BD%9C%E6%88%90%E3%81%97%E3%81%9F%E5%A0%B4%E5%90%88%E3%81%AE%E3%82%B5%E3%82%A4%E3%83%88%E3%83%9E%E3%83%83%E3%83%97%E3%81%AF%E3%81%A9%E3%81%86%E3%81%99%E3%82%8C%E3%81%B0%E8%89%AF%E3%81%84%E3%81%A7%E3%81%99%E3%81%8B%EF%BC%9F?hl=ja

      サーチコンソールHTMLタグを2つ設置可能かどうか
      https://support.google.com/webmasters/thread/122051612/%E3%82%B5%E3%83%BC%E3%83%81%E3%82%B3%E3%83%B3%E3%82%BD%E3%83%BC%E3%83%ABhtml%E3%82%BF%E3%82%B0%E3%82%922%E3%81%A4%E8%A8%AD%E7%BD%AE%E5%8F%AF%E8%83%BD%E3%81%8B%E3%81%A9%E3%81%86%E3%81%8B?hl=ja

      https://15cc.com/design/wordpress-subdirectory
      https://wordpress-web.and-ha.com/wordpress-security
      LOLIPOPサーバーで「All-in-One WP Migration」のアップロードファイルサイズ変更方法

      ロリポップサーバー ユーザー専用ページ

      ▼ユーザー専用ページの「サーバーの管理・設定」→「PHP設定」を選択

      ▼CGI版のPHPに変更

      ▼設定項目のupload_max_filesizeが「2M」、「2M」しか設定できません

      そのため.htaccessに設定を追記する方法で行います

      ▼php_value,php_flagを利用可能にするを「On」に変更

      .htaccessを編集

      .htaccessの編集は「ダウンロード」を押して事前にバックアップをとっておくことをおすすめします

      ▼対象のドメイン.htaccessを下記の通り編集

      以下の内容を追記

      php_value memory_limit 512M
      php_value upload_max_filesize 512M
      php_value post_max_size 512M
      
      # BEGIN WordPress
      # "BEGIN WordPress" から "END WordPress" までのディレクティブ (行) は
      # 動的に生成され、WordPress フィルターによってのみ修正が可能です。
      # これらのマーカー間にあるディレクティブへのいかなる変更も上書きされてしまいます。
      <IfModule mod_rewrite.c>
      RewriteEngine On
      RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
      RewriteBase /
      RewriteRule ^index\.php$ - [L]
      RewriteCond %{REQUEST_FILENAME} !-f
      RewriteCond %{REQUEST_FILENAME} !-d
      RewriteRule . /index.php [L]
      </IfModule>
      
      # END WordPress
      
      php_value memory_limit 512M
      php_value upload_max_filesize 512M
      php_value post_max_size 512M

      ▼WordPress管理画面からサイズ変更が確認できました

      参考サイト

      https://www.vektor-inc.co.jp/post/lolipop-upload-max-filesize
      php_value memory_limit 128M
      php_value upload_max_filesize 100M
      php_value post_max_size 100M

      ロリポップでWordPressの最大アップロードサイズを変更する方法
      https://webst8.com/blog/lolipop-maximum-upload

      php_value memory_limit 512M
      php_value upload_max_filesize 512M
      php_value post_max_size 512M

      ↓PHPをバージョン変更後、 WordPressが表示できなくなった

      PHPのバージョン変更後、WordPressに関するエラーについて恐れ入りますがWordPress本体や各プラグインは弊社にて開発を行っていないため、ご利用のWordPress本体を含めテーマやプラグインが変更後のPHPのバージョンに対応しているか配布元に対応バージョンをご確認ください。
      https://support.lolipop.jp/hc/ja/articles/360048375214-PHP%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E5%A4%89%E6%9B%B4%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6%E6%B0%97%E3%82%92%E3%81%A4%E3%81%91%E3%82%8B%E3%81%93%E3%81%A8%E3%81%AF%E3%81%82%E3%82%8A%E3%81%BE%E3%81%99%E3%81%8B?a8=YRvtwRALNEc6Zvn0zhcfFz06io95nFHX4FPpFMoKusHcDFnKQEPR_EPL86uFSqvYuRQoNFP2tjZ5xs00000000404001

      【お名前.com】Googleサーチコンソールで「所有権を証明できませんでした」の対応

      起きていること

      ▼Googleサーチコンソールでプロパティを追加する際、下記の通り

      対応方法

      ▼お名前ドットコムにログインしドメイン設定を選択

      ▼「DNS設定/転送設定」を選択

      ▼該当ドメイン選択後、次へクリック

      DNSレコード設定を利用するの「設定する」をクリック

      ▼下記表のとおり各欄を入力後、追加をクリック

      ホスト名TYPETTLVALUE優先状態
      空白
      (所有権の証明を行うドメイン名)
      TXT3600google-site-verification=**********
      (※Google管理コンソール内で取得した文字列)
      なし有効

      参考サイト

      各レコードの意味は以下ご参照ください
      https://help.onamae.com/answer/7883

      【ドメイン】DNS設定の変更手続きをしてから有効になるまでの期間は?
      https://help.onamae.com/answer/8081

      【WordPress】パンくずリスト作成(プラグインBreadcrumb NavXT)

      Breadcrumb NavXT

      インストールして有効化

      パンくずにホームページを含める。等設定をする

      テンプレートファイル編集

      ファイルの表示したい箇所に下記コード

      <div class="breadcrumbs" typeof="BreadcrumbList" vocab="https://schema.org/">
          <?php if(function_exists('bcn_display'))
          {
              bcn_display();
          }?>
      </div>

      例)↓アクションフックでfunctions.phpに記述

      add_action('main_hook', function () {
      ?>
      <div class="breadcrumbs" typeof="BreadcrumbList" vocab="https://schema.org/">
      <?php if(function_exists('bcn_display'))
      {
      bcn_display();
      }?>
      </div>
      
      <style>
      		
      </style>
      <?php
      });

      参考サイト

      Breadcrumb NavXTプラグインの使い方(パンくずリストの表示)

      https://www.javadrive.jp/wordpress/plugin-list/index16.html