Advertise with Googlier.com LooseKnot Blog https://blog.looseknot.jp Web制作で使うCSSとかJavaScriptとかの備忘録 Sat, 15 Aug 2026 15:49:06 +0000 ja hourly 1 https://wordpress.org/?v=7.1.4 https://blog.looseknot.jp/wp-content/uploads/2021/05/favicon.png LooseKnot Blog https://blog.looseknot.jp 32 32 PayPalのSandBoxで支払いテスト https://blog.looseknot.jp/misc/paypal_sandbox.html Sun, 02 Aug 2026 07:42:44 +0000 https://blog.looseknot.jp/?p=1851 (この記事はAIではなく人間が書いています)
前回はPayPal にビジネスアカウントを作成する手順を紹介した。今回は、PayPalのSandBox(検証環境)を用いて決済テストを行う手順をご紹介する。
SandBoxを用いたテストは必須ではないが、PayPalサービスの使い方や通知内容を事前に把握することができ、初めて商品を販売する場合には有用だと感じる。

ここでは次の手順で説明する。

  1. はじめに
  2. SandBox環境でアカウントを準備
  3. SandBox環境で決済機能を準備
  4. SandBox環境で決済を実行
  5. SandBox環境で決済がなされたことを確認

1. はじめに

SandBoxとはいわゆる検証環境・テスト環境のことで、一般的には、本番のシステム・サービスと同等の設定や操作ができ、動作を検証・テストする時に使う環境のことである。
PayPal SandBoxは、PayPalにビジネスアカウントを登録すると使用できるようになる。

PayPalの場合、SandBoxでのアカウントや決済カード情報などは、本番(実際に売買を行うサービス)と異なるダミーデータを用いる。また、決済ボタン・リンクやAPIといった決済機能もSandBoxで検証用に作成・適用して動作を確認する。
つまり、SandBoxを用いた動作確認は大きく次のような流れとなる。
 ・SandBox環境でアカウントを準備
 ・SandBox環境で決済機能を準備
 ・SandBox環境で決済を実行
 ・SandBox環境で決済がなされたことを確認

2. SandBox環境でアカウントを準備

SandBoxでの販売や支払いは、自分のPayPalアカウントを用いるのではなく、検証用のダミーアカウントを用いる。早速、Developerダッシュボード へアクセスし、ここでは自分自身のビジネスアカウントでログインする。

画面左側メニューの「Testing Tools」から「Sandbox Accounts」へ進む。ここで、SandBox環境で用いる、商品を販売する側と購入する側の各アカウントを準備していく。

筆者の場合、(SandBoxではなく、本番のPayPalで)ビジネスアカウントを作成して2~3日後には、この画面のように自動的に2つのSanBox用アカウント(ビジネスアカウントとパーソナルアカウントがひとつずつ)が作成されていた。

SanBox用アカウントは追加もできるが、今回はデフォルトで作成されていたアカウントを利用し、販売する側はビジネスアカウント、購入する側はパーソナルアカウントでテストを行うことにする。

SandBox環境での役割 動作確認する時のアカウント
商品を販売するアカウント デフォルトのビジネスアカウント
商品を購入するアカウント デフォルトのパーソナルアカウント

まずは、デフォルトで作成されていたアカウントのパスワードを変更しておくことをお勧めする。各アカウント行の右側(下の画面の矢印部分)から「View/Edit account」より、パスワードなどアカウント情報を変更できる。パスワードに加え、アカウントの氏名やカード情報など、テストしたい内容に変更しておくと良い。

3. SandBox環境で決済機能を準備

商品を販売する側と購入する側の各SandBoxアカウントが準備できたら、SandBox上で扱う商品やAPI連携といった、決済機能を準備していく。

商品を販売するSandBoxアカウント(*本番のPayPalアカウントではありません)で、SandBox環境(https://sandbox.paypal.com/mep/dashboard)へログインする。
ログイン後の画面は、本番のダッシュボード画面と同じであり、操作も本番サービスと同じであるため操作手順は割愛するが、筆者は支払いリンクを作成した。

4. SandBox環境で決済を実行

SandBox環境で支払いリンクを作成したので、そのリンクに対する支払いを実行する。

商品を購入する側のSandBoxアカウント(*本番のPayPalアカウントではありません)で、SandBox環境(https://sandbox.paypal.com/mep/dashboard)へログインする。
もしくは、支払いリンクへアクセスした後、手順に従いログインしても良い。

決済操作は本番サービスと同じであるため操作手順は割愛する。

5. SandBox環境で決済がなされたことを確認

SandBox環境を利用することで、本番サービスで決済をせずとも、(SandBoxで実施した決済の)支払い通知を確認することができる。

SandBoxアカウントを準備した時と同様、Developerダッシュボード へ自分自身のビジネスアカウントでログインする。
各アカウント行の右側(下の画面の矢印部分)から「View notifications」より、通知内容を確認できる。

以上、今回は、PayPal SandBoxの使い方を簡単にご紹介した。
SandBoxの活用が、顧客との決済トラブル防止の一助になるのではと考えている。

それでは みなさん良い1日を。

]]>
PayPalビジネスアカウントを作成(2026年) https://blog.looseknot.jp/misc/paypalbusinessaccount2026.html Fri, 31 Jul 2026 07:46:18 +0000 https://blog.looseknot.jp/?p=1804 (この記事はAIではなく人間が書いています)
PayPal にビジネスアカウントを作成する手順を紹介する。
筆者は事前に、PayPal Business Account の作成方法を AI に相談したりネットで調査したりしていたが、実際やってみると、操作UIや入力項目がかなり変わっていた。
実際の画面を示しながら、2026年7月に行ったビジネスアカウントの新規登録方法をご紹介する。

ここでは次の手順で説明する。

  1. はじめに
  2. 登録に必要な情報
  3. 登録手順
  4. 登録完了の通知
  5. SandBoxでの支払いテスト

1. はじめに

今回 海外顧客を視野に入れ、海外で利用ユーザが多いと言われているPayPalを導入することにした。PayPalビジネスアカウントの作成では審査があり、一旦審査に落ちると追加書類など手間がかかると予想される。
AIに「審査に落ちたらどうしよう」と相談したところ「PayPalの審査が通らなければ他の決済サービスを使えば良いよ」とのこと。そのアドバイスが幸いにも役に立つこともなく、筆者の場合は以下手順でビジネスアカウントを作成することができた。

2. 登録に必要な情報

結論から言うと、筆者が入力・提出した情報は次の通りである。
*筆者は個人事業主として登録したので、法人は内容が異なる可能性がある。

・姓、名、生年月日、国籍、職業
・電話番号、メールアドレス、パスワード
・自宅住所:政府発行の身分証明書の記載と一致していること、私書箱は不可
・事業の拠点となっている国
・事業:「個人事業主」と「ビジネス」から選択
・事業者名、商号・屋号:確定申告に使用する事業者名
・事業が登記された国または地域
・販売、またはサービスの内容:選択肢より指定する
・このアカウントの目的:選択肢より指定する
・マイナンバーカードの撮影と、自分自身の顔を撮影

以下に、それぞれ画面で示しながら説明していく。

3. 登録手順

では実際の登録手順をご紹介する。
まずは、PayPal(https://www.paypal.com/jp/home)へアクセスし、画面にある「新規登録」ボタンをクリックする。次に、「パーソナル」か「事業用」か選択する画面では「事業用」を選択し先へ進む。

「ビジネスアカウントを開設する」画面となるので、情報を入力していく。項目は上から、姓、名、Country、電話番号、メールアドレス、パスワードである。
メールアドレスに関する余談だが、筆者は過去に、別件でGメールアカウントがロックされ復旧に苦労したため、ここではGメールではないアドレスを利用した。

画面下にある「同意してアカウントを作成」ボタンを押下すると、先ほど入力したメールアドレスに認証コードが届く。それを画面に入力しアカウント作成が完了する。

続いて以下の画面で「開始」ボタンを押下するとアカウントの設定へ進む。

プロフィールの作成画面が表示され、事業を「個人事業主」か「ビジネス」から選択する。筆者は「個人事業主」を選択した。

ここからは、より詳細な事業情報を入力していく。項目は、上から、姓、名、生年月日、国籍、職業、自宅住所(身分証明書と一致すること、私書箱は不可)である。

続いて、事業についての質問に答えていく。必須項目は上から、事業者名、商号・屋号、事業が登記された国または地域、販売またはサービスする内容、アカウントの目的、事業用ウェブサイトの有無、利用明細に記載する氏名、住所である。
ここを過ぎれば本人確認をして終わるため、頑張って進んでいきたい。

筆者は開業届けやウェブサイトは未対応だったため、商号・屋号などには予定している名称を入力した。
また、「販売、またはサービスの内容についてお知らせください」項目は選択肢が多く、迷いながらも一番近いと思うものを指定した。

そして最後、本人確認へと進む。まずは確認方法に同意する画面が表示されるため、「同意して続行」をクリックする。

本人確認用の情報を入力する。項目は上から、姓、名、生年月日、職業、PayPalを利用する目的、住所、提示する証明書(運転免許証やマイナンバーカードから選択する)である。筆者はマイナンバーカードを選択した。

ここで「続行」をクリックするとQRコードが表示される。コードをスマホで読み込み、指示に従いマイナンバーカードと自分の顔を撮影する。

ここまでで、情報の入力・送信作業が完了する。
画面に、PayPal側の確認が済んだらメールが届く(最長10日かかることがある)と表示されたので、しばし待つことにする。

4. 登録完了の通知

ビジネスアカウントの登録作業をしてから、筆者の場合は数時間で「本人確認が完了しました」のメールが届き、無事にビジネスアカウントの作成が完了した。
このあと、売上を振り込む先の銀行口座登録などを実施した。

事前のAI相談では、この項目ではこう書いた方がいいとか、こういう内容だと審査に落ちやすいとか、かなり困難な作業という印象だった。しかし、実際は記入項目は簡単なものが多く、本人確認がマイナンバーということもあってか、予想以上にすんなり登録することができた。

5. SandBoxでの支払いテスト

ビジネスアカウントができたら、商品用の支払い機能(ボタンやリンク、API連携など)を作成し運用していくが、事前に支払い画面など動作を確認したいと思う。
PayPalはSandBox(検証環境)を提供しているため、次回はこれを用いたテストをご紹介する。

以上です。みなさん良い1日を。

]]>
Localツールで「This is a port conflict」が発生した時の対応 https://blog.looseknot.jp/mac/localtoolport80.html Mon, 26 May 2025 08:01:13 +0000 https://blog.looseknot.jp/?p=1700 Localツールとは、ローカル環境(自分のPC)にWebサーバやDB、WordPress環境を構築できるツールで、以下からDLできる。
https://localwp.com/
LocalツールでWordPressサイトを作成した際に、「This is a port conflict with this site’s domain.」と表示された時の対応方法を紹介する。

筆者のマシン
Mac mac mini (M1, 2020)
OS Sequoia バージョン 15.5
Localツール Version 9.2.4+6788

ここでは次の手順で説明する。

  1. はじめに
  2. 状況の確認
  3. localhost ルーターモードへ変更
  4. WordPress画面を確認
  5. HTTPS接続について

1. はじめに

筆者の環境では、Mac mini に標準で搭載されている Apache httpd を稼働しており、使用ポートは80である。

% sudo lsof -i:80
COMMAND  PID USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
httpd   8095 root    4u  IPv6 0xe755010baa99781c      0t0  TCP *:http (LISTEN)

上記の状態でLocalツールをDL・インストールし、サイト(サイト名は「test050526」)を作成したところ、Localツール画面上部に以下の警告メッセージが表示された。

This is a port conflict with this site’s domain.


この状態でLocalツール上の「Open site」ボタンを押下しサイトを表示すると、WordPressサイトトップページは表示されず、以下のように「It works! Apple httpd」が表示される。尚、この時のURLは「test050526.local」(サイト名.local)である。

ブラウザに表示された内容は、筆者の環境でMacの標準Apacheで稼働するWebサイトのトップページ(http://localhost:80)と同じ内容である。

更にLocalツール上の「WP Admin」ボタンを押下しWordPressの管理画面(管理者画面)を開くと、管理画面が表示されず以下のように「Not Found」となる。

本記事では、これらへ対応しLocalツールで作成したサイトのWordPressが正しく稼働・表示する手順を紹介する。

2. 状況の確認

Localツール画面上部の警告メッセージにある「Learn more.」リンクをクリックする(以下の赤枠)。

Localツールのヘルプ「What is Router Mode?」ページ(以下URL)が表示される。
https://localwp.com/help-docs/advanced/router-mode/

ここを読むと、Localツールはポートに関連して以下2つのモードを使い分けることがわかる。(説明欄は翻訳ソフトにて翻訳)

ルーターモード 説明
Site Domain Router Mode Localが実行するデフォルトのモードです。このモードでは、Localはポート80をリッスンする「ルーター」を起動し、トラフィックをLocalが管理する個々のサイトに誘導します。各サイトのドメインは次のようになります。example.local
localhost Router Mode このフォールバックモードでは、ローカルルーターを起動せず、代わりにコンピュータの専用ポートを使用して各サイトに直接アクセスします。このモードを使用すると、ドメインは次のようになります。localhost:10005

Localツールは、デフォルトでは Site Domain ルーターモードとなっており、それは80ポートをListen(使用)すると書いてある。
80ポートはすでに標準インストールのApacheで使用中であるため競合したと考える。

Localツールの画面を再度確認すると、以下のように確かに「Site Domain」という項目があり、値が「test050526.local」(サイト名.local)となっている。これは「Open Site」ボタンでブラウザを表示した時のURLである。

3. localhost ルーターモードへ変更

筆者は標準インストールされているApacheを80ポートで動かしたままLocalツールでサイトを動作させたい。
そのため、Site Domain ではなく、localhost ルーターモードを使用することにした。

localhostルーターモードへの変更手順はいくつかあるが、今回は以下手順で実施した。

  1. Localツールの左上にある人アイコンから「Preference」を選択
  2. Preference画面で「Advanced」を選択し、「Router mode」項目を「Site domain」から「localhost」へ変更
  3. 画面右下の「Apply」を押下
  4. Preference画面は自動的に閉じないため画面右上「X」マークを押下して画面を閉じる
  5. localhostルーターモードになったことを確認する

    • 画面の「Site host」欄の値が「localhost:10030」となっており、10030ポートを使用するように変更されたことが分かる

4. WordPress画面を確認

Localツール上の「Open site」ボタンを押下しサイトを表示すると、Macの標準Apacheで稼働するWebサイトではなく、以下のようにWordPressのファーストページが正しく表示されるようになった。

ブラウザのアドレスは「localhost:10030」となっており、先ほどはアドレスが「test050526.local」であったため、ルータモードの切り替えによりドメインとポートが変わったことも分かる。

次にLocalツール上の「WP Admin」ボタンを押下しWordPressの管理画面を開くと、こちらも以下のように正しく管理画面が表示されるようになった。

以上のように、ルータモードをデフォルトの Site domain から localhostルーターモードへ変更することで、標準インストールのApacheを80ポートで稼働したまま、LocalツールでWordPressを動かすことができるようになった。

5. HTTPS接続について

ちなみに、Localツールのlocalhostルーターモードは HTTPS サポート外である。
HTTPS接続を使いたい人は、標準インストールのApatchを停止してLocalツールを起動するか、もしくは標準インストールのApacheポートを80以外にするなどし、Site Domain ルーターモードを使うようにする必要がある。

以上

]]>
Sequoia で Apache + PHP https://blog.looseknot.jp/mac/sequoiaphp.html Sun, 04 May 2025 13:43:50 +0000 https://blog.looseknot.jp/?p=1616 Mac mini に標準インストールされているApacheでPHPを使う手順を紹介する。
筆者の環境では、Apache httpd.confに”PHPが deprecated(非推奨)”と記述されている。これに対する解の一つとして、brewでPHPとApache 双方をインストールする方法があるが、ここでは、標準インストールされたApacheを使う方法を紹介する。

筆者のマシン
Mac mac mini (M1, 2020)
OS Sequoia バージョン 15.4.1
Apache httpd バージョン 2.4.62
Homebrew バージョン 4.4.20

ここでは次の手順で説明する。

  1. はじめに
  2. brewでPHPをインストール
  3. PHPモジュールを署名する
  4. Apacheのhttpd.confを更新、再起動
  5. ApacheでPHPコードが動くことの確認
  6. おまけ:Unable to find code signature authorityエラー対応
  7. おまけ:Could not reliably determine the server’s fully qualified domain nameメッセージ対応

1. はじめに

PHPコードを用いてWebページを制作する場合、Apache と PHP を連携させれば良い。
しかし筆者の環境では、標準インストールされているApache の httpd.conf の記述に以下があり、PHPが非推奨となっている。

#PHP was deprecated in macOS 11 and removed from macOS 12

そこで以下サイトを参考にさせて頂き、brewでPHPをインストールした上で、標準インストールされているApacheでPHPを使用することにした。
・Apacheでphpモジュールがロードできない(macOS/Monterey)
・Add PHP to Apache on MacOS 12

2. brewでPHPをインストール

  1. PHPが未インストールかどうかを確認する
    % brew list
    • 筆者の環境では、PHPは未インストールであった
  2. PHPをインストールする。今回はバージョン指定はせずに進めた
    % brew install php
  3. 後で必要となる情報を確認しておく(以下赤字の部分)
    % brew info php
    ==> **php**: stable 8.4.6 (bottled), HEAD
    ・・・(省略)
    To enable PHP in Apache add the following to httpd.conf and restart Apache:
    LoadModule php_module /usr/local/opt/php/lib/httpd/modules/libphp.so
  4. PHPをスタートする
    % brew services start php
    ==> **Successfully started `php` (label: homebrew.mxcl.php)**

3. PHPモジュールを署名する

brewでインストールしたPHPは署名されていないため、署名する。

  1. 認証局を作成する
    • キーチェーンアクセスアプリを起動し、メニューの「キーチェーンアクセス」から「認証局アシスタント」>「認証局を作成…」を選択する
    • 以下のような情報を記入・指定し作成する
      名前 hogeCA (自分で名前を付ける。あとで httpd.conf に記述するため英字を推奨する)
      固有名のタイプ 自己署名ルートCA
      ユーザ証明書 コード署名
      メールの送信先 自分のメールアドレス

  2. 証明書を信頼する
    • 「ログイン」項目で「自分の証明書」を参照すると、先ほど作成したCAがあり、赤字で「このルート証明書は信頼されていません」とある
    • 証明書をダブルクリックして表示し、「信頼」項目にある「この証明書を使用するとき」の値を「常に信頼」へ変更する
    • 作成した証明書は、/Users/me/Library/Keychains/login.keychain-dbにある
  3. PHPモジュールを署名する
    • 先ほどbrewでインストールしたPHPモジュールの場所は、上記% brew info phpで確認した結果の赤字部分となる(筆者の環境では/usr/local/opt/php/lib/httpd/modules/libphp.so)
    • この libphp.so を署名する
      % codesign --sign "hogeCA" --force --keychain /Users/me/Library/Keychains/login.keychain-db /usr/local/opt/php/lib/httpd/modules/libphp.so

4. Apacheのhttpd.confを更新、再起動

  1. 標準インストールされたApacheのhttpd.confに、brewでインストールしたphpモジュールをincludeする記述を追加
    • /etc/apache2/httpd.conf に以下を追記
      
      LoadModule php_module /usr/local/opt/php/lib/httpd/modules/libphp.so "hogeCA”
      <IfModule php_module>
        AddType application/x-httpd-php .php
      </IfModule>
  2. configtestで記述が正しいかチェックする
    % sudo apachectl configtest
    • Syntax OKと表示されれば問題ない
  3. Apacheを再起動
    % sudo apachectl -k restart

5. ApacheでPHPコードが動くことの確認

以下のように簡単なPHPコードを準備する。ここでは、ドキュメントルートは/Library/WebServer/Documentsである。


% pwd

/Library/WebServer/Documents

% cat phptest.php
<html>
<head></head>
<body>

<?php
echo 'This is php code.';
?>

</body>
</html>

ブラウザでhttp://localhost/phptest.phpへアクセスし「This is php code.」が表示されればOKである。
これでPHPによるWebページ制作ができるようになった。

6. おまけ:Unable to find code signature authorityエラー対応

% apachectl configtestコマンドにおいて、以下エラーが発生した時の対応をご紹介する。

[Mon Apr 21 11:43:28.471998 2025] [so:error] [pid 36889] AH06663: Unable to find code signature authority on module at /usr/local/opt/php/lib/httpd/modules/libphp.so that matches authority name “hogeCA\xe2\x80\x9d” configured on LoadModule directive.

httpd.conf の編集で、authority name「”hogeCA”」をコピペしたのだが、右側の「”」(RIGHT DOUBLE QUOTATION MARK)が悪かったようで、編集時にキーボードより直接「“」を打ち入力したところエラーは解消した。
補足であるが、PHPモジュールに対する署名情報は以下コマンドで確認できる

% codesign -dv --verbose=4 "/usr/local/opt/php/lib/httpd/modules/libphp.so"

7. おまけ:Could not reliably determine the server’s fully qualified domain nameメッセージ対応

% apachectl configtestコマンドにおいて、以下メッセージが発生した時の対応をご紹介する。

AH00558: httpd: Could not reliably determine the server’s fully qualified domain name, using hogenoMac-mini.local. Set the ‘ServerName’ directive globally to suppress this message

このメッセージはこのままでも状況によっては問題はないが、気持ち悪いので解消しておく。
httpd.conf に以下記述を設定(元々 ServerName行はコメントアウトされていた)。

ServerName localhost:80

以上、みなさん良い1日を。

]]>
WordPressで編集画面が動作不良!?リビジョン制限で改善する方法 https://blog.looseknot.jp/html/wp_revision.html Wed, 25 Dec 2024 03:58:43 +0000 https://blog.looseknot.jp/?p=1577

WordPressを使っていると、時々編集画面が重くなったり、動作が不安定になったりすることがありますよね。その原因の一つとして、リビジョン(記事の過去の編集履歴)が蓄積しすぎてしまうことが挙げられます。リビジョンが多くなりすぎると、データベースが膨大になり、必要なメモリ量も増加することがあります。最悪の場合、メモリ不足やFatal Errorが発生する可能性もあります。

今回は、この問題を解決するために、リビジョン管理を制限して、編集画面を快適に保つ方法をお伝えします。

fatal error: allowed memory size of 67108864 bytes exhausted

fatal errorの例。できれば一生出会いたくない文章です。。

1. wp-config.php でリビジョン数を制限する

直接wp-config.phpファイルを編集してリビジョン数を制限する方法です。これは、WordPressが記事を保存する際に、リビジョンをどのくらい保持するかを設定する方法になります。

具体的には、以下のコードを wp-config.php に追加します。

define('WP_POST_REVISIONS', 5); // 最新5回のリビジョンのみ保存

wp-config.phpに上記のコードを記述すると、最大5つのリビジョンだけが保存され、古いリビジョンは自動的に削除されます。リビジョンの数は、ここでは5としていますが、状況に合わせて任意の数に調整できます。

また、以下のようにWP_POST_REVISIONSの値をfalseに設定すると、リビジョンが保存されなくなります。

define('WP_POST_REVISIONS', false); // リビジョンを保存しない

ただし、こちらの場合は編集履歴が全く保存されませんので、後から復元する事ができなくなります。

どちらも注意点としては、

/* That's all, stop editing! Happy publishing. */

または

/* 編集が必要なのはここまでです ! WordPress でのパブリッシングをお楽しみください。 */

というコメントアウトよりも上に記述するようにしてください。なぜかというと、このコメントの後の

require_once ABSPATH . 'wp-settings.php';

という記述よりも後ろに追加すると、リビジョン設定が反映されないからです。

2. リビジョン管理プラグインを使う

リビジョンを管理するためのプラグインをインストールして、リビジョンの保存数を制限する方法があります。これにより、不要な履歴が増えすぎることを防ぎ、データベースを軽く保つことができます。

おすすめのプラグインには、"WP-Optimize" や "WP Revisions Control" などがあります。これらのプラグインは、記事ごとにリビジョンの保存数を設定できる機能を提供しており、例えば「5回までのリビジョンだけを保存」といった具合に、必要最低限の履歴だけを保持できます。

3. 不要なリビジョンを手動で削除する

すでに蓄積されてしまった不要なリビジョンは、手動で削除することも可能です。これには、前述のWP-Optimize を使うと簡単に行えます。WP-Optimizeは、不要なリビジョンを一括で削除してデータベースをクリーンアップすることができます。

手動でリビジョンを削除する手順は以下の通りです。

  1. プラグインをインストールし、有効化します。
  2. 「WP-Optimize」→「データベースの最適化」をクリック。
  3. 「リビジョンの削除」を選択して、削除します。

なお、phpMyAdminなどでデータベースにアクセスしてSQLクエリを実行する、という方法もありますが、ここでは説明を省かせていただきます。

4. まとめ

リビジョン管理をうまく行うことで、WordPressの編集画面を軽く保ち、動作不良を防ぐことができます。リビジョン数を制限する方法は、wp-config.phpで直接設定する方法、リビジョン管理系プラグインを使う方法などがありますので、自分の使いやすい方法で試してみてください。

もし、既にリビジョンが溜まりすぎている場合は、手動で削除することも忘れずに行いましょう。これで、快適にWordPressを使い続けることができるはずです。

]]>
画像とキャプションの幅をfigureとdisplay:tableで揃える https://blog.looseknot.jp/css/figure_table.html Mon, 13 May 2024 06:16:19 +0000 https://blog.looseknot.jp/?p=1527

画像の下にキャプションを入れる際、画像の幅と揃える小技です。
今回はfigureとfigcaptionタグで実装します。

表示サンプル

サンプルページは下記URLよりどうぞ。

サンプルHTML

サンプル

html

<figure>
  <img src="dummy.gif">
  <figcaption>
    ダミーテキストダミーテキストダミーテキスト。ダミーテキストダミーテキストダミーテキスト。
  </figcaption>
</figure>

説明

必要部分だけの記述です。
全体をfigureで囲んでいます。

CSS

figure {
  display: table;
}
figcaption {
  display: table-caption;
  caption-side: bottom;
}

説明

figureに対してdisplay:tableとします。
これによってfigureの幅が要素の幅と同じになります。

次にfigcaptionに対してdisplay:table-captionとします。
これでfigureと同じ幅でキャプションが表示されるようになります。ただ、このままだとキャプションが画像の上に配置されるので、caption-side:bottomを加えキャプションが画像の下にくるようにします。

また、figureを中央合わせにしたい場合には、figureに対してmargin-inline:autoを加える事で実現できます。

]]>
jQueryを使わずにslideToggleを実装してみた https://blog.looseknot.jp/css/slidetoggle.html Tue, 17 Oct 2023 07:05:24 +0000 https://blog.looseknot.jp/?p=1498

よくある質問やQ&Aのようなページで、カパカパ開閉するアコーディオンを実装する際に使われることが多いjQueryのsildeToggle。
jQueryを使用すればわずか数行で記述できるため非常に便利なのですが、昨今の脱jQueryの流れに乗り、今回はjQueryを使わずにCSS gridとJavaScriptで再現してみました。

表示サンプル

サンプルページは下記URLよりどうぞ。

サンプルHTML

slideToggleのサンプル

html

<dl class="grid_toggle">
  <dt class="toggle_button">ここをクリックするとtoggleします</dt>
  <dd class="toggle_contents">
    <div class="toggle_contents_inner">
      <p>ダミーテキストダミーテキストダミーテキスト。</p>
      <p>ダミーテキストダミーテキストダミーテキスト。</p>
      <p>ダミーテキストダミーテキストダミーテキスト。</p>
    </div>
  </dd>
</dl>

説明

必要部分だけの記述です。
全体をgrid_toggleで囲み、toggle_buttonをクリックすることでtoggle_contentsがパカパカと開閉するようにします。

CSS(SCSS)

/* ベース */
.grid_toggle {
  display: grid;
  grid-template-rows: 60px 0fr;
  transition: grid-template-rows .3s ease;
  &.open {
    grid-template-rows: 60px 1fr;
  }
}
.toggle_button {
  cursor: pointer;
}
.toggle_contents {
  overflow-y: hidden;
}

/* 装飾部分のみ */
.grid_toggle {
  width: 900px;
  margin-block-start: 1em;
  margin-inline: auto;
}
.toggle_button {
  display: flex;
  align-items: center;
  background-color: #ebf0f5;
  padding-inline: 2em 4em;
}
.toggle_contents_inner {
  border: 1px solid #ddd;
  border-top: unset;
  padding: 1em;
}

説明

grid-template-rowsがCSS transitionに対応していることを利用し、grid-template-rowsを0frとすることと、toggle_contentsをoverflow-y:hiddenとすることで非表示状態を再現しています。
さらにopenでgrid-template-rowsを0fr→1frとし、後述のJavaScriptでこのクラスを付与(toggle)してtoggle_contentsを開閉するようにしています。

JavaScript

document.addEventListener('DOMContentLoaded', function(){
  const gridToggleButton = document.querySelectorAll('.toggle_button');
  gridToggleButton.forEach(function(elem){
    elem.addEventListener('click', function(){
      elem.parentNode.classList.toggle('open');
    })
  })
})

説明

toggle_buttonをクリックすることで親要素のgrid_toggleにopenをtoggleさせています。

]]>
StoreKit 2のAppStore.sync()検証 https://blog.looseknot.jp/mac/appstore_async.html Fri, 19 Aug 2022 00:17:57 +0000 https://blog.looseknot.jp/?p=1393 iPhone などの iOSアプリにおいて、StoreKit 2を用いたリストア(復元)処理の検証に関して、筆者の体験を紹介する。

筆者のマシン
Mac mac mini (M1, 2020)
OS Monterey バージョン 12.4
XCode バージョン 13.4.1
Swift バージョン 5.6.1


ここでは次の手順で説明する。

  1. はじめに
  2. リストア(復元)ボタンの追加
  3. TestFlightでの検証
  4. App Storeで公開・動作確認

はじめに

iOSアプリの App内課金処理を StoreKit 2 で実装してApp Store の審査に提出した際、リストア(復元)のボタンを設置していないと言う理由で、Review での却下(Reject)を受けた。

理由は「Guideline 3.1.1 – Business – Payments – In-App Purchase」で、メッセージを日本語に訳すと、大まかには次のとおり。

お客様のアプリでは、復元可能なアプリ内課金(in-app purchases)を提供していますが、アプリ内課金プログラミングガイドの「購入商品の復元」に明記されているように、ユーザーが以前に購入したアプリ内課金を復元できる「購入商品の復元」機能(”Restore Purchases” feature)が含まれていないことがわかりました。

余談だが、下記をざっと読んだ感じでは、私にはその必要性は認識できなかった。
・In-App Purchase Programming Guide の “Restoring Purchase Products” section

筆者が審査に提出したアプリは、起動時に最新の購入状況を読み込むものであったが、Rejectメッセージには次のステップが明記されていた。
内容を日本語に訳すと、大まかには次のとおり。

アプリ内課金で購入した商品を復元するには、「復元」(”Restore”)ボタンを用意し、ユーザーが「復元」ボタンをタップすると復元処理が開始されるようにすることが適切です。なお、起動時に自動的に復元されるようにしても、この問題は解決しません。

つまり、起動時に自動的に最新の購入アイテムを復元していても、明示的な”Restore”ボタンなどを設置することが求められた。
確かに、Implementing a store in your app using the StoreKit APIからDLできるサンプルアプリには、購入したアイテムを復元するための明示的なボタン「Restore Puchases」がある。

リストア(復元)ボタンの追加

rejectされたアプリにはアイテム購入画面があるのだが、その画面の下部の方に、「購入情報の復元(リストア)」と言うボタンを設けた。
そして、そのボタンがタップされたときの処理として、上記サンプルアプリのコードおよび AppStore.sync()のAPI を参考にして次の処理を追加した。


AppStore.sync()
// 加えて、この後ろに、最新の購入済みのアイテムを反映した画面を表示する処理を実装

TestFlightでの検証

動作確認のために、TestFlightへアプリをアップロードし、手元の2台の実機で動作の確認を行った。
流れは次のとおり。

  1. 実機AとBで、アイテム購入画面を表示させておく
    • この時、いずれのアイテム購入画面でも、アイテムは未購入の状態である
  2. 実機Aにて、SandBoxテストユーザAでアイテムを購入する
    • 筆者のアプリでは購入後に画面が更新され、購入済みであることが分かるようにしている。この時実機Aでは、期待していたように購入済みの画面となった
  3. 実機B(アイテム購入画面では、未購入の状態が表示されている)で、「購入情報の復元(リストア)」ボタンをタップする
    • 認証ダイアログに従い、SandBoxテストユーザAで認証した
    • 期待する結果は、購入済みのアイテムを反映した画面となることだった。しかし、画面の見た目は変化なし

ここで、APIの説明や先のサンプルアプリのコードを確認したが、実装に不足な点は見当たらなかった。

試しに、AppStore.sync()のエラーをcatchした際のログを画面に表示するようにして、TestFlightへアップして検証した。
上記3の「購入情報の復元(リストア)」ボタンをタップでは下記エラーが出ていることがわかった。

The operation couldn’t be completed.(StoreKit.StoreKitErrr error 2.)
// 日本語では「リクエストを完了できません。」

TestFlight版アプリではSandBoxユーザを用いて検証したが、ネットでは一部挙動が怪しいと言う記事もあったので、本番のApp Storeでは動くと言う前提で、やむを得ず審査に提出をした。

ちなみに、上記3の後、実機Bで購入(復元ではない)を操作すると、認証後に「すでに購入済みです。無料で入手できます」と言う旨のメッセージが出て、購入済みであることが反映された。

App Storeで公開・動作確認

審査が通過したので、App Storeからリリース版をDLし、TestFlight版アプリで検証した上記操作を試した(この時は自分のApple IDで課金アイテムを購入)。

  1. 実機AとBで、アイテム購入画面を表示させておく
    • この時、いずれのアイテム購入画面でも、アイテムは未購入の状態である
  2. 実機Aにて、アイテムを購入する(勿論、自分のApple IDで)
    • この時実機Aでは、期待していたように購入済みの画面となった
  3. 実機Bで、「購入情報の復元(リストア)」ボタンをタップする
    • 認証ダイアログに従い、自分のApple IDで認証した
    • 期待の通り、画面が更新され、購入済みの画面となった

これらより、AppStore.sync()を呼び出すことにより、本番の App Store では適切に復元(リストア)できることが確認できた。

TestFlight検証時がたまたま良くなかったのか、SandBoxテストユーザではリストア検証できない仕様なのかは分からなかったが、
StoreKit2 でのリストア処理はAppStore.sync()で問題なし!と言うことが分かった。

以上

]]>
Swiftの簡単な並列処理サンプル https://blog.looseknot.jp/mac/swift_concurrency.html Fri, 05 Aug 2022 01:46:06 +0000 https://blog.looseknot.jp/?p=1343 iPhone などの iOSアプリで並列処理を行う、簡単なサンプルコードを紹介する。

筆者のマシン
Mac mac mini (M1, 2020)
OS Monterey バージョン 12.4
XCode バージョン 13.4.1
Swift バージョン 5.6.1


ここでは次の手順で説明する。

  1. はじめに
  2. 並列に実施する処理として複数のTaskを作成する
  3. 各Taskを実行する

はじめに

iOSアプリにおいて、複数の処理を並列に実行させたいときがある。例えば

  • タスクA
    • あるURLへアクセスし、データを取得する。
    • 取得したデータを用いた表テーブルを画面に表示。
  • タスクB
    • 別のあるURLへアクセスし、データを取得する。
    • 取得したデータを元に作成した文字列を画面に表示。

といったタスクがあるとする。
タスクAの処理が完了した後にタスクBを処理させるのではなく、タスクAとタスクBを並行に処理させたいことがある。

タスクAとタスクBのどちらが先に完了しても問題がない場合は、並列処理をすることで、速やかな画面描画を実現することができる。
ここでは、タスクAとタスクBといった複数タスクを並列処理させる、簡単なサンプルプログラムを紹介する。

尚、本記事で紹介するコード全体(XCodeプロジェクト)は、GitHub で公開している。
(サンプルで用いる音源データはアップしていないため、コード実行時は別途ご準備ください)

並列に実施する処理として複数のTaskを作成する

ここでは、並列に実施する処理として、以下の2つのTaskを作成する。
今回は処理を簡単にするため、非同期処理としてよく扱われるURLへのリクエストとレスポンス受信といった処理ではなく、2秒待つといった処理としている。

  • Task1 : 2秒待って、ワンワンと鳴く。更に2秒待って、ワンワンと鳴く。
  • Task2 : 2秒待って、ニャンと鳴く。

Task1とTask2を同時に実行開始することで、2秒後にワンワンとニャンが同時に鳴き、更にその2秒後にワンワンだけが鳴くようになる。

まず、下記メソッドは2秒待つという処理である。

// This task is a pseudo asynchronous processing and actually sleeps for 2 seconds.
private func taskSleep(taskName: String) async throws {
    print("\(date) \(taskName) Start")
    try await Task.sleep(nanoseconds: 2_000_000_000)
    print("\(date) \(taskName) End")
}

このサンプルでは、引数taskNameでタスク名を受け取り、2秒を待つ前後にログを出力している。

Task1は次の通りである。

// Dog Task
// after await task, a dog barks, again after await task, a dog barks.
Task {
    try await taskSleep(taskName: "Dog-1")
    animalSoundPlayer.playDog()
    try await taskSleep(taskName: "Dog-2")
    animalSoundPlayer.playDog()
}

4行目のtaskSleepメソッドでは、2秒待つと言う処理がなされる。
5行目のanimalSoundPlayer.playDog()は、ワンワンと鳴く自作コードである。
6行目のtaskSleepメソッドでは、更に2秒待つと言う処理がなされる。
7行目のanimalSoundPlayer.playDog()を呼び出すことで、再度ワンワンと鳴く。

Task2は次の通りである。

// Cat Task
// after await task, a cat mews
Task {
    try await taskSleep(taskName: "Cat-1")
    animalSoundPlayer.playCat()
}

4行目のtaskSleepメソッドでは、2秒待つと言う処理がなされる。
5行目のanimalSoundPlayer.playCat()は、ニャンと鳴く自作コードである。

各Taskを実行する

今回のサンプルコードでは、上記の各Taskを iPhone画面上のボタン「Animal Sound Play」をタップした際に動作させる。
ContentView.swiftの全体コードは以下である。

struct ContentView: View {

    private let animalSoundPlayer = AnimalSoundsPlayer()

    // Current date
    private var date: String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy/MM/dd HH:mm:ss"
        return dateFormatter.string(from: Date())
    }

    var body: some View {
        Button("Animal Sound Play", action: {
            // Dog Task and Cat Task are parallel processes.

            // Dog Task
            // after await task, a dog barks, again after await task, a dog barks.
            Task {
                try await taskSleep(taskName: "Dog-1")
                animalSoundPlayer.playDog()
                try await taskSleep(taskName: "Dog-2")
                animalSoundPlayer.playDog()
            }
            // Cat Task
            // after await task, a cat mews
            Task {
                try await taskSleep(taskName: "Cat-1")
                animalSoundPlayer.playCat()
            }
        })
    }

    // This task is a pseudo asynchronous processing and actually sleeps for 2 seconds.
    private func taskSleep(taskName: String) async throws {
        print("\(date) \(taskName) Start")
        try await Task.sleep(nanoseconds: 2_000_000_000)
        print("\(date) \(taskName) End")
    }
}

上記ContentView.swiftコードを用いたアプリは、次のような画面となる。

画面上の「Animal Sound Play」をタップすることで、Task1とTask2が同時に開始される。

XCode の Simulator でアプリを起動し、画面をタップした時のログは以下の通りである。


2022/08/05 10:06:48 Dog-1 Start
2022/08/05 10:06:48 Cat-1 Start
2022/08/05 10:06:50 Cat-1 End
2022/08/05 10:06:50 Dog-1 End
2022/08/05 10:06:50 Dog-2 Start
2022/08/05 10:06:52 Dog-2 End


10:06:48 に、Task1のtaskSleepメソッド(taskNameはDog-1) と Task2のtaskSleepメソッド(taskNameはCat-1)が実行されており、
いずれも2秒後の10:06:50にメソッド処理が完了する。
この後ワンワンとニャンが同時に鳴く。
その後に、Task1のtaskSleepメソッド(taskNameはDog-2)が開始され、2秒後の10:06:52にメソッド処理が完了した後、ワンワンと鳴く。

つまり、2秒後にワンワンとニャンが同時に鳴き、更にその2秒後にニャンだけが鳴く。
このようにして、複数Taskを並列に実行することができる。

以上

]]>
アプリがアクティブになったタイミングでGame CenterのLeaderboardからスコアを取得する https://blog.looseknot.jp/mac/getapplegamecenterscore3.html Sun, 26 Jun 2022 23:28:14 +0000 https://blog.looseknot.jp/?p=1299 iPhone などの iOS アプリ で Game Center と連携する際に、アプリがアクティブになったタイミングで、Leaderboard に保存してあるスコア値を取得・表示するコードを紹介する。

筆者のマシン
Mac mac mini (M1, 2020)
OS Monterey バージョン 12.4
XCode バージョン 13.4
Swift バージョン 5.6.1


ここでは次の手順で説明する。

  1. はじめに
  2. アプリがアクティブになった状態を受信するための Observer をセットする
  3. Observer 処理で、Leaderboard からスコア値を取得し画面へ表示

はじめに

iPhoneアプリへ Game Center のスコアを表示する方法として、これまで下記記事を紹介してきた。

  1. Apple の Game Center スコア値を取得する
    • 画面のボタンをタップすることで、このアプリのスコアを取得し表示する方法
  2. Game CenterへログインしたタイミングでLeaderboardからスコアを取得する
    • Game Center の認証後、自動的にスコアを取得し画面に表示する方法

これらは、上記1.はプレイヤーのタップ動作が必要となり、上記2.は Game Center へのログイン時(通常、すでにログイン済みならアプリ起動時)にのみスコアを取得するものである。
そのため、ゲームアプリをバックグラウンドやインアクティブにした後に(つまりアプリを落とすことなく)、再度画面へ表示した場合には、最新のスコアを取得・表示することはできない。

そこで今回は、アプリがアクティブになったタイミングで、自動的に Game Center の Leaderboard から最新のスコアを読み込み表示する方法を紹介する。

尚、本記事で紹介するコード全体(XCodeプロジェクト)は、GitHubで公開している。

アプリがアクティブになった状態を受信するための Observer をセットする

UIApplicationDelegateプロトコルには、アプリが inactive から active になったことを通知する static なプロパティ didBecomeActiveNotification がある。
今回はこの通知を利用する。

ここでは、アプリがアクティブになったら Game Center の Leaderboard にある最新のスコアを取得するために、次のように didBecomeActiveNotification を受信する Observer として、自作の func didBecomeActive() を実行させるようにする。

// add notification observer. When the App staus becomes active, works using block
NotificationCenter.default.addObserver(
    forName: UIApplication.didBecomeActiveNotification,
    object: nil,
    queue: OperationQueue.main,
    using: { _ in
        print("App status becomes active.")
        self.didBecomeActive()
    }
)

こうすることで、バックグラウンドなどの非アクティブな状態からアクティブになった時(つまりユーザがアプリを全面に表示した時)に、func didBecomeActive()が実行される。

ここでは、アクティブになった時の通知を受信するために didBecomeActiveNotification を利用したが、UIApplicationDelegate プロトコルには、バックグラウンドになった時の通知(didEnterBackgroundNotification)やフォアグラウンドになる時の通知(willEnterForegroundNotification)が用意されているため、仕様に応じて使い分けることができる。

Observer 処理で、Leaderboard からスコア値を取得し画面へ表示

アクティブになった時の Observer である自作の func didBecomeActive() は下記である。

private func didBecomeActive() {
    print("didBecomeActive.")
    loadScore(scoreDidLoad: { local, entries in
    // If entries are loaded, set the value to worldHighScore
        if let entries = entries,
            entries.isEmpty == false {
            self.autoLoadScore2.worldScore = entries[0].score
        }
        // If local is loaded, set the value to yourHighScore
        if let local = local {
            self.autoLoadScore2.yourScore = local.score
        }
    })
}

3行目で、もう一つの自作メソッド loadScore を実行しているが、このメソッドでは、Leaderboard からスコアを取得している。
そして取得したあとに、7行目で、予め定義済みの変数に、Leaderboard から取得した全世界のトップスコアをセットしている。 また11行目で、予め定義済みの変数に、Leaderboard から取得したローカルプレイヤーのスコアをセットしている。

もう一つの自作メソッド loadScore は下記である。

private func loadScore(scoreDidLoad: @escaping (_ localPlayerEntry: GKLeaderboard.Entry?, _ entries: [GKLeaderboard.Entry]?) -> Void) {
    print("func loadScore")
    if GKLocalPlayer.local.isAuthenticated {
        print("You are logged in. Start to load scores from Game Center.")
        // Get the Leaderboard ID of this app that you have previously registered in App Store Connect
        // (only one ID is registered here).
        GKLocalPlayer.local.loadDefaultLeaderboardIdentifier(completionHandler: { (leaderboardIdentifer, error) in
            if error != nil { // Error occured
                print(error!.localizedDescription)
            } else { // Got the loaderBoarderIdentifier for this app.
                // Load scores
                if let leaderboarderId = leaderboardIdentifer {
                    print("Default leaderboardIdentifer is \(leaderboarderId).")
                    GKLeaderboard.loadLeaderboards(IDs: [leaderboarderId]) { (boards, _) in
                        boards?.first?.loadEntries(for: .global, timeScope: .allTime, range: NSRange(location: 1, length: 1), completionHandler: {(myLocalPlayerEntry, myEntries, _, _) in
                            scoreDidLoad(myLocalPlayerEntry, myEntries)
                        })
                    }
                }
            }
        })
    }
}

ログイン済みであれば Leaderboard からスコアを取得するように 下記3行目の if GKLocalPlayer.local.isAuthenticatedで認証済みかをチェックしている。

補足として、このコードでは、デフォルトの Leaderboard を使用している。また、筆者は便宜上 自作メソッドを2つにしているが、メソッドの分割は必須ではない。

これらによる画面の動きを下記の画面キャプチャで説明する。ここでは、すでに Game Center へはログイン済みである。
また赤枠部分が、今回紹介するコードで実現する箇所である。それ以外のところは、過去の記事(Apple の Game Center スコア値を取得する, Game CenterへログインしたタイミングでLeaderboardからスコアを取得する)にて紹介している

アプリ起動直後の画面

まずは起動直後の画面である。
画面の赤枠の worldScore は Leaderboard から取得した全世界のトップスコア、myScoreは Leaderboard から取得したローカルプレイヤーのスコアである。

起動後、画面が表示される際にアプリはアクティブになるので、上記の自作のメソッド func didBecomeActive() は一度実行されている。
しかし、上の画面キャプチャの場合もそうであるが、Game Center へのログイン処理完了前にこの自作メソッドが実行されるため、上記コード if GKLocalPlayer.local.isAuthenticatedの結果が false となり、Game Center からのスコア取得は行われない。従って、赤枠内のスコアはいずれもデフォルトの 0 を表示している。

補足:赤枠の上部にある青枠にはスコアが表示されているが、これは認証状態の変更を受信してスコアを取得・表示している。

アプリをインアクティブにする

ここで、アプリを終了させずに、アクティブ以外の状態にする。アクティブ以外の状態とは、画面を他のアプリに切り替えたり、起動中のアプリを選択する App スイッチャーの状態などが挙げられる。

アプリをアクティブにする

アプリをアクティブにする(つまり再びこのアプリ画面を表示する)と、上記で説明したように、通知didBecomeActiveNotificationを受信し、自動的に Leaderboard から最新のスコアを読み込み、画面キャプチャのように表示することができる。

尚、赤枠の上部の青枠部分は、Game CenterへログインしたタイミングでLeaderboardからスコアを取得するで説明したコードで紹介している。
また赤枠の下部の青枠部分は、ボタンをタップしたら Leaderboard からスコアを取得し表示するで紹介している。

以上

]]>