プラグインを止めたらサイトが 500 になった:テーマがプラグインの定数や関数を直接使っていたときの直し方

広告WordPress・PHP を学べる本

Amazonのアソシエイトとして、WordPress functions.phpは適格販売により収入を得ています。

管理しているサイトで、ネットショップのプラグイン Welcart(usc-e-shop)を使わなくなりました。

ネットでの販売は外のサービスに任せることになったので、プラグインを止めることにしました。

ところが、作業環境で Welcart を止めたら、どのページも 500(Fatal error)になりました。

この記事では、原因と直し方、止める前に確かめておくことをまとめます。

何が起きたか

Welcart を止めて、トップのページを開くと、500 が返りました。

トップだけでなく、どのページも同じでした。

原因は、テーマの header.php でした。

ヘッダーは全部のページで読まれるので、ここで止まると、全部のページが 500 になります。

原因:条件を逆にして、プラグインの定数を使っていた

header.php には、スマホ用のカートのボタンがありました。

このボタンを出さないようにするため、前に誰かが条件を逆にしていました。

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() の中身は、次のとおりです。

functions/welcart.php(テーマ)
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:使わない部分は外す

ネットショップは外のサービスに任せるので、カートのボタンはもう要りません。

条件を直すより、ボタンの部分ごと外すのがいちばん簡単で確かです。

header.php(後)
<?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()

関数:ないときはテーマの関数で同じことをする

functions/welcart.php・functions/shop-item.php(テーマ・短くしたもの)
// 商品の記事かを見分ける(プラグインがあればプラグインの関数、なければテーマの関数)
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() も同じ目印で見分けているので、止める前と後で結果が変わりません。

定数:ないときは別の方法で同じ値を探す

functions/shop-item.php(テーマ)
// 商品のカテゴリーの 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 を止めると、その仕組みごとなくなります。

そこで、商品のページはテーマのテンプレートで出すようにしました。

functions/shop-item.php(テーマ)
// 商品のページは、プラグインの有無にかかわらずテーマのテンプレートで出す
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 の関数は呼ぶ前に確かめます。

template-parts/shop-item-single.php(テーマ・一部)
// テンプレートの中でも、プラグインの関数は呼ぶ前に確かめる
if ( function_exists( 'usces_remove_filter' ) ) {
	usces_remove_filter();
}

ウィジェット:同じ ID の代わりのウィジェットを登録する

サイドバーには、Welcart の「商品カテゴリー」のウィジェットを置いていました。

Welcart を止めると、このウィジェットは消えて、サイドバーから見えなくなります。

そこで、Welcart のクラスがないときだけ、同じ ID の代わりのウィジェットを登録しました。

functions/shop-item.php(テーマ・短くしたもの)
// サイドバーの商品カテゴリー:プラグインのウィジェットと同じ 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 の代わりを登録すると、設定も見た目もそのまま使える
  • 止める前に、テーマの中をプラグインの名前・関数・定数の頭の文字で探しておく

参考

関連機能

  1. WordPress 拡大画像をポップアップで表示させるプラグイン Easy FancyBox

  2. WordPress Duplicatorプラグインを使ったバックアップ&復旧対応手順

  3. WordPress標準のエクスポート・インポートにアイキャッチの画像を含める Export media with selected content プラグイン

サイドバー

よく使うカスタマイズ

最近の記事

Profile

PAGE TOP