管理しているサイトで、ネットショップのプラグイン Welcart(usc-e-shop)を使わなくなりました。
ネットでの販売は外のサービスに任せることになったので、プラグインを止めることにしました。
ところが、作業環境で Welcart を止めたら、どのページも 500(Fatal error)になりました。
この記事では、原因と直し方、止める前に確かめておくことをまとめます。
何が起きたか
Welcart を止めて、トップのページを開くと、500 が返りました。
トップだけでなく、どのページも同じでした。
原因は、テーマの header.php でした。
ヘッダーは全部のページで読まれるので、ここで止まると、全部のページが 500 になります。
原因:条件を逆にして、プラグインの定数を使っていた
header.php には、スマホ用のカートのボタンがありました。
このボタンを出さないようにするため、前に誰かが条件を逆にしていました。
<?php // 会員・カートのナビ(Welcart が動いているときだけ出す) if ( is_welcart_active() ) : // …… endif; // スマホのカートのボタン if ( ! is_welcart_active() ) : // ← 条件が逆 ?> <a href="<?php echo esc_attr( USCES_CART_URL ); ?>" class="c-cart-button"> <span class="badge"><?php echo usces_totalquantity_in_cart(); ?></span> </a> <?php endif; ?>
| 書き方 | 役目 |
|---|---|
is_welcart_active() |
テーマの関数。Welcart が動いていれば true |
! is_welcart_active() |
Welcart が動いていないときに true |
USCES_CART_URL |
Welcart が決める定数(カートのページの URL) |
usces_totalquantity_in_cart() |
Welcart の関数(カートの中の数) |
is_welcart_active() の中身は、次のとおりです。
function is_welcart_active() {
global $usces;
return defined( 'USCES_VERSION' ) && $usces;
}
Welcart が動いているあいだは、! is_welcart_active() が false なので、ボタンは出ません。
見た目は「ボタンを隠した」ように見えるので、ずっと気づかれていませんでした。
Welcart を止めると、条件が true になり、ボタンを出そうとします。
そのとき、Welcart の定数 USCES_CART_URL がないので、PHP が止まりました。
PHP 8 では、ない定数を使うと Error になります(PHP 7 では警告を出して、定数の名前を文字として使っていました)。
try / catch では受けにくい
「エラーになるなら try / catch で囲めばよいのでは」と思うかもしれません。
そこで、作業環境の PHP 8.3 で、試しのスクリプトを流して確かめました。
<?php
// 試しのスクリプト:プラグインの定数・関数・クラスがないとき、try / catch でどうなるか
try {
try {
echo MY_PLUGIN_CART_URL; // ない定数
} catch ( Exception $e ) {
echo 'Exception で受けた';
}
} catch ( Error $e ) {
echo get_class( $e ) . ' / ' . $e->getMessage();
}
| 試したこと | catch ( Exception $e ) |
catch ( Error $e ) |
|---|---|---|
| ない定数を使う | 受けられない | 受けた(Undefined constant) |
| ない関数を呼ぶ | 受けられない | 受けた(Call to undefined function) |
| ないクラスを new する | 受けられない | 受けた(Class not found) |
ないものを使ったときに出るのは Exception ではなく Error なので、ふつうの catch ( Exception $e ) では受けられません。
catch ( Error $e ) なら受けられますが、テンプレートのあちこちを try で囲むのは現実的ではありません。
また、受けたあとに何を出すかも決められません。
そこで、使う前に「プラグインがあるか」を確かめて、ないときは使わない(または代わりのものを出す)形にしました。
直し方 1:使わない部分は外す
ネットショップは外のサービスに任せるので、カートのボタンはもう要りません。
条件を直すより、ボタンの部分ごと外すのがいちばん簡単で確かです。
<?php // スマホのカートのボタンは出さない(ネットショップは外のサービスに任せる)。 // 前は条件を逆にして隠していたため、Welcart を止めると USCES_CART_URL がなく 500 になった ?>
外した理由をコメントで残しておくと、あとで見た人が迷いません。
ヘッダーには、ほかにも Welcart の会員・カートのナビがありましたが、こちらは if ( is_welcart_active() ) の中にあったので、そのままにしました。
直し方 2:プラグインがあるかを確かめる分岐
ヘッダーのほかにも、テーマの中には Welcart の関数・定数・テンプレート・ウィジェットを使っているところがありました。
店頭で売っている商品の記事は、Welcart の商品の形で作ってあり、これからも見せたいページです。
そこで、Welcart がなくても同じ見た目で出るように、プラグインがあるかを確かめる分岐と、ないときの代わりの表示を足しました。
| 使っているもの | 確かめ方 |
|---|---|
関数(usces_is_item() など) |
function_exists() |
定数(USCES_ITEM_CAT_PARENT_ID など) |
defined() |
| クラス(ウィジェット) | class_exists() |
関数:ないときはテーマの関数で同じことをする
// 商品の記事かを見分ける(プラグインがあればプラグインの関数、なければテーマの関数)
if ( function_exists( 'usces_is_item' )
? usces_is_item( $post_id )
: ( $post_id && my_is_shop_item( $post_id ) ) ) {
// 商品のカテゴリーを探す ……
}
// テーマの関数:プラグインと同じく、記事の post_mime_type で見分ける
function my_is_shop_item( $post_id = null ) {
$post = get_post( $post_id );
return $post && 'post' === $post->post_type && 'item' === $post->post_mime_type;
}
| 書き方 | 役目 |
|---|---|
function_exists( 'usces_is_item' ) |
Welcart の関数があるかを確かめる |
? usces_is_item( $post_id ) |
あれば、Welcart の関数で見分ける |
: my_is_shop_item( $post_id ) |
なければ、テーマの関数で見分ける |
post_mime_type が item |
Welcart は商品の記事にこの目印を付けている。プラグインを止めても DB には残る |
Welcart の usces_is_item() も同じ目印で見分けているので、止める前と後で結果が変わりません。
定数:ないときは別の方法で同じ値を探す
// 商品のカテゴリーの ID:プラグインの定数があればそれ、なければスラッグから探す
function my_shop_item_parent_cat_id() {
if ( defined( 'USCES_ITEM_CAT_PARENT_ID' ) ) {
return (int) USCES_ITEM_CAT_PARENT_ID;
}
$term = get_term_by( 'slug', 'item', 'category' );
return $term ? (int) $term->term_id : 0;
}
| 書き方 | 役目 |
|---|---|
defined( 'USCES_ITEM_CAT_PARENT_ID' ) |
Welcart の定数があるかを確かめる |
get_term_by( 'slug', 'item', 'category' ) |
なければ、商品のカテゴリーをスラッグから探す |
パンくずの「商品」のところも、定数を直接使わず、この関数を使うように変えました。
商品のページ:テーマのテンプレートで出す
商品のページは、Welcart のテンプレートの仕組みで出していました。
Welcart を止めると、その仕組みごとなくなります。
そこで、商品のページはテーマのテンプレートで出すようにしました。
// 商品のページは、プラグインの有無にかかわらずテーマのテンプレートで出す
function my_shop_item_template( $template ) {
if ( is_single() && my_is_shop_item( get_queried_object_id() ) ) {
$file = locate_template( 'template-parts/shop-item-single.php' );
if ( $file ) {
return $file;
}
}
return $template;
}
add_filter( 'template_include', 'my_shop_item_template', 99 );
| 書き方 | 役目 |
|---|---|
template_include |
WordPress がページに使うテンプレートを決める直前に呼ばれるフィルター |
locate_template() |
テーマ(子テーマがあれば子テーマ)の中のファイルを探す |
99 |
ほかのフィルターより後に動かして、こちらを優先する |
新しいテンプレートは、前の商品のページと同じ形(画像・本文・タブ)で、値段とカートだけを出さないものにしました。
カートのあった場所には「店頭で販売中です」の枠を出しています。
Welcart が動いていても止めていても、同じテンプレートを使うので、止める前に見た目を確かめられます。
テンプレートの中でも、Welcart の関数は呼ぶ前に確かめます。
// テンプレートの中でも、プラグインの関数は呼ぶ前に確かめる
if ( function_exists( 'usces_remove_filter' ) ) {
usces_remove_filter();
}
ウィジェット:同じ ID の代わりのウィジェットを登録する
サイドバーには、Welcart の「商品カテゴリー」のウィジェットを置いていました。
Welcart を止めると、このウィジェットは消えて、サイドバーから見えなくなります。
そこで、Welcart のクラスがないときだけ、同じ ID の代わりのウィジェットを登録しました。
// サイドバーの商品カテゴリー:プラグインのウィジェットと同じ ID の代わりのウィジェット
function my_shop_register_category_widget() {
if ( class_exists( 'Welcart_category' ) ) {
return; // プラグインが動いているときは、プラグインのウィジェットのまま
}
register_widget( 'My_Shop_Category_Widget' );
}
add_action( 'widgets_init', 'my_shop_register_category_widget', 20 );
class My_Shop_Category_Widget extends WP_Widget {
public function __construct() {
// ID と class をプラグインと同じにすると、置いたウィジェットの設定と CSS がそのまま使われる
parent::__construct( 'welcart_category', '商品カテゴリー(代わり)', array( 'classname' => 'widget_welcart_category' ) );
}
public function widget( $args, $instance ) {
$title = isset( $instance['title'] ) ? $instance['title'] : '';
$cat_slug = ! empty( $instance['cat_slug'] ) ? $instance['cat_slug'] : 'itemgenre';
echo $args['before_widget'];
if ( '' !== $title ) {
echo $args['before_title'] . esc_html( $title ) . $args['after_title'];
}
echo '<ul class="ucart_widget_body">';
$parent = get_category_by_slug( $cat_slug );
if ( $parent ) {
wp_list_categories( 'child_of=' . (int) $parent->term_id . '&title_li=' );
}
echo '</ul>';
echo $args['after_widget'];
}
}
| 書き方 | 役目 |
|---|---|
class_exists( 'Welcart_category' ) |
Welcart のウィジェットのクラスがあるかを確かめる |
register_widget() |
なければ、代わりのウィジェットを登録する |
'welcart_category'(ID) |
Welcart と同じ ID にすると、置いてあったウィジェットとその設定(タイトルなど)がそのまま使われる |
'widget_welcart_category'(class) |
同じ class にすると、今の CSS がそのまま効く |
このほか、商品の一覧に出す画像(Welcart の商品の画像)も、Welcart を止めたときだけテーマで出すようにしました。
確かめた結果
作業環境で Welcart を止めて開いた
直したテーマで、作業環境の Welcart を止め、16 ページを開きました。
どのページもエラーは出ませんでした。
止めているあいだは、ページの返事から Set-Cookie と no-store も消えていました(下の「おまけ」)。
Welcart を止めたのは確かめのあいだだけで、確かめたあとは元に戻しました。
そのあと、本番で Welcart を止めました。
try / catch
上の「try / catch では受けにくい」の表のとおりです(作業環境の PHP 8.3.32)。
おまけ:止めたらページの表示が速くなった
Welcart は、どのページでもセッションを始めていました。
そのため、全部のページが Set-Cookie と Cache-Control: no-store を返していました。
no-store は「このページを残さないで」という意味なので、サーバー(エックスサーバー)のキャッシュが効いていませんでした。
止めたあと、本番のページの返事を見ると、Set-Cookie と no-store はなくなっていました。
| 最初の文字が返るまで | |
|---|---|
| 止める前(作業のときに見た) | 0.3 秒ほど |
| 止めた後(本番で続けて開いた) | 0.03〜0.09 秒(1 回目は 0.3 秒ほどのこともあった) |
止めた後の数は、記事を書くときに本番のトップを 7 回、もう 1 ページを 4 回開いて測ったものです。
使っていないプラグインが、気づかないところで表示を遅くしていることがあります。
ほかのプラグインを止める前に確かめること
今回のことは、Welcart にかぎりません。
テーマや自作のプラグインが、止めるプラグインの関数・定数・クラスを使っていると、同じことが起きます。
止める前に、テーマの中を探しておくと安心です。
# テーマのフォルダで、止めるプラグインの名前・定数・関数の頭を探す(Welcart の例)
grep -rniE "welcart|usces" --include=*.php .
# 定数を、確かめずに使っているところ(defined と同じ行にないもの)
grep -rnE "USCES_[A-Z_]+" --include=*.php . | grep -v "defined("
# 「動いていないとき」に出す分岐(条件が逆になっていないか)
grep -rnE "! ?is_[a-z_]+_active\(" --include=*.php .
| 探すもの | 例 | 見るところ |
|---|---|---|
| プラグインの名前 | welcart |
プラグイン専用のテンプレート・関数のファイル |
| 関数の頭の文字 | usces_ |
function_exists() で確かめているか |
| 定数の頭の文字 | USCES_ |
defined() で確かめているか |
| クラスの名前 | Welcart_category |
class_exists() で確かめているか |
| 「動いていないとき」の分岐 | ! is_welcart_active() |
中でプラグインのものを使っていないか(今回の原因) |
| ショートコード・ウィジェット | 本文やサイドバーに置いたもの | 止めると、そのまま文字で出たり、消えたりする |
- まず作業環境で止めて、主なページを開き、エラーのログを見る
- 確かめが終わったら、作業環境のプラグインは元に戻しておく(本番と同じ状態にしておく)
- 本番で止めたあとも、すぐに主なページを開く。何かあれば、プラグインをもう一度有効にすれば戻る
今回は、ほかのプラグイン(Welcart 以外)では試していません。
まとめ
- テーマがプラグインの定数や関数を直接使っていると、プラグインを止めたときに全部のページが 500 になることがある
- 条件を逆にして隠した部分は、止めたときにだけ動くので、ふだんは気づけない
- PHP 8 では、ない定数・関数・クラスを使うと Error になり、
catch ( Exception $e )では受けられない - 使わない部分は外す。使う部分は
function_exists()・defined()・class_exists()でプラグインがあるかを確かめて、ないときの代わりの表示を出す - ウィジェットは同じ ID の代わりを登録すると、設定も見た目もそのまま使える
- 止める前に、テーマの中をプラグインの名前・関数・定数の頭の文字で探しておく
参考
- function_exists(php.net)
- defined(php.net)
- class_exists(php.net)
- PHP 7 のエラー(Error と Exception・php.net)
- PHP 8.0 の後方互換性のない変更(php.net)
- template_include(developer.wordpress.org)
- locate_template()(developer.wordpress.org)
- register_widget()(developer.wordpress.org)
- Debugging in WordPress(developer.wordpress.org)


