Wonderful! WordPress

wordpress管理画面にテーマのオプション設定ページを表示

テーマを作る時にそのテーマ独自のオプションを設定して使いたいときがあります。
設定によってサイトの表示や使用する機能を変更するとか、特にhtmlやcssをいじれない他の人用に作る場合など必要になることがあります。
そしてそのオプションの設定が管理画面でできれば誰にでも使えて便利です。

今回、そういうテーマを作成するにあたり以下のようにオプションを設定しました。合計11ケのオプションを設け、それらを連想配列にして一つ(hiros_options)にまとめ、wordpressのオプションに保存します。
wordpressには独自テーマやプラグインのための独自の設定データをwordpressデータベースに保存・更新・読出をする機能があります。
wordpress日本語版codexプラグイン作成のページを見るとwordpress設定にアクセスするための必要な関数は以下の二つ。

<?php
	update_option( $option_name, $newvalue );
	get_option( $option_name );
?>
PHP
CopyExpand

オプションを設定するにはupdate_optionを使い、設定したオプションの値を取得するにはget_optionを使用するということでわかりやすいです。
引数の$option_name は作成または更新、取得したいオプションの名前。$newvalue は作成、更新する時のオプションの値。新規にオプションを設定する場合でもupdate_optionで大丈夫。尚、オプションの値として保存できるのは文字列(string)、配列(array)、もしくはPHPオブジェクト。
ということで実際にfunctions.phpに設定したオプションは以下のよう。ちなみにテーマの名称はhiros です。
*2023/2/27:オプションをチェックする関数を更新してあります。

<?php
//delete_option('hiros_options');//既存のオプション値を強制的にデフォルトに戻す時使用

$hiros_dft_options = array(//オプションデフォルト値設定
	'en_log' => '1',//アクセスログ、画像クリックログを残す場合は'1'
	'add_coarse_screen' => '1',//top 背景画像にざらめを付ける場合は'1'
	'add_shadow' => '1',//top bar に影を付ける場合は'1'
	'add_rcorner' => '1',//post 欄を角丸表示にする場合は1
	'show_postcalendar' => '1',//投稿カレンダーを表示する場合は'1'
	'show_tagcloud' => '1',//タグクラウドまたはタグリストを表示する場合は1
	'en_popupimg' => '1',//javascriptで画像をポップアップ表示させる時は'1'
	'en_pagenation' => '1',//ページネーションを表示する時は'1'
	'reverse_pagenation' => '1',//ページネーションを逆順に表示させる場合は'1'
	'show_side_widget' => '0',//すでに設定してある独自サイドウィジットを表示する場合は'1'
	'show_footer_widget' => '1',//すでに設定してある独自フッターウィジットを表示する場合は'1'
	'en_toolbar_faceup' => '1',//認知用のtoolbar自動スクロールアップをさせる場合は'1'
);

$hiros_crt_options = get_option( 'hiros_options' );//現在のオプション設定値を取得
$hiros_crt_options = hiros_option_check( 'hiros_options', $hiros_crt_options, $hiros_dft_options );

function hiros_option_check( $optionname, $crt_options, $dft_options ) {
	//引数:オプション名、現在の取得したオプション、オプションデフォルト設定値

	if ( false === $crt_options ) {//初使用の時などオプションが設定されていない時

		update_option( $optionname, $dft_options );//デフォルトでオプション設定
		$rt_options = $dft_options;
	} else {

        // すでに保存してあるオプション値が存在する場合
        // デフォルトと既存値で共通のキーだけの配列を array_intersect_key() で取得し、
        // それら3つのそれぞれの要素の数が同じならば、オプション項目に変更は無い
        $deff = array_intersect_key( $dft_options, $crt_options );
        $countary = array( count ( $dft_options ) , count ( $crt_options ) , count ( $deff ) );

        if ( 1 !== count ( array_unique( $countary ) ) ) {
            foreach ( $crt_options as $key => $val ) {
                if ( isset ( $dft_options[ $key ] ) ) {// デフォルトオプションにそのキーが存在する要素だけ保存されている値で上書き。
                    $dft_options[ $key ] = $val;
                }
            }
            update_option( $optionname, $dft_options );
            $rt_options = $dft_options;

        } else {
            $rt_options = $crt_options;
        }
	}
	return $rt_options;
}
?>
PHP
CopyExpand

まず、オプションを登録するためのデフォルト設定値の配列を作っています。
そして既にオプションが設定されているかを確認するために取得します。
既にオプションが設定されている場合、オプションを追加したり削除したりした時のために、登録用に設定している配列との個数を比較し、異なった場合はすでに設定されているオプション値を有効にするために新しいオプションの配列をいままでの配列でarray_merge によって配列の上書きをし、オプションが設定されていない時と同様に新規に登録します。
ちなみに、オプションを削除したなど、オプションの数が減っている場合はphp のarray_intersect_key関数を使って余分となった配列中のオプションを消去しています。 既に登録済みでその個数も変わらなければ、その設定値をextract関数で連想配列のkeyを変数名とした個々の変数に分けています。 注:ちょっと思う所があってwordpress の PHP Coding Standards に目を通してみたのですが、「Don't exract()」と題打ってextract()関数は terrrible function であるから使用しないようにとありました。
実を言うとこれを見る前のコードは上述のように最後に オプションの連想配列から extract() で個々の変数を作るということをやっていたのですが、改めて考えて見ると個別の変数にわざわざ分ける必要は全くないので、その点を修正しています。
実際にupdate_option が動くのは初めてテーマを読み込んだ時か、オプションの個数に変化があった時だけということになります。
functions.phpで得た各オプションの値をもつ変数は、index.php やpage.php 内ではそのまま使用でき、header.php やfooter.php ではglobal 宣言すれば使用できます。
例えば何か使い勝手の都合とかで、取得するオプションが複数あって、オプションチェックの行程を同じ条件式のなかで出来るとかの場合なら、関数よりもクラスを使ったほうが使い勝手が良いかもしれません。
ほとんどクラスは使ったことがないのですが、こんな感じでしょうか。

<?php
//delete_option('si_options');//新規にオプションの項目を追加する場合に使用する

class Hiros_Theme_Option
{
	private $current;
	private $default;
	private $ret_option;
	private $option_name;

	public function __construct( $option_name, $ope = 0 ){
		$this->option_name = $option_name;
		if ( 0 === $ope ){//オプションのデフォルトを空か指定した配列にするかのフラグ
			$this->default = '';
		} else {
			$this->default = array(//オプションのデフォルト値設定、各設定 オフにする場合は0を指定
				'en_log'=>'1',//アクセスログ、画像クリックログを残す場合は'1'
				'show_body_backimage'=>'1',//コンテンツに背景画像を表示する場合は'1'
				'add_coarse_screen'=>'1',//top 背景画像にざらめを付ける場合は'1'
				'add_shadow'=>'1',//top bar に影を付ける場合は'1'
				'add_rcorner'=>'1',//post 欄を角丸表示にする場合は1
				'show_daycalendar'=>'1',//日めくりカレンダーを表示する場合は'1'
				'show_postcalendar'=>'1',//投稿カレンダーを表示する場合は'1'
				'show_tagcloud'=>'1',//タグクラウドまたはタグリストを表示する場合は1
				'en_popupimg'=>'1',//javascriptで画像をポップアップ表示させる時は'1'
				'en_pagenation'=>'1',//ページャーを表示する時は'1'
				'show_side_widget'=>'1',//すでに設定してある独自サイドウィジットを表示する場合は'1'
				'show_footer_widget'=>'1',//すでに設定してある独自フッターウィジットを表示する場合は'1'
				'en_toolbar_faceup'=>'1',//認知用のtoolbar自動スクロールアップをさせる場合は'1'
			);
		}

		$this->current = get_option( $this->option_name );

		if ( false === $this->current ) {//初使用の時などオプションが設定されていない時
	
			update_option( $this->option_name, $this->default );//デフォルトでオプション設定
			$this->ret_option = $this->default;
		} else {

                // すでに保存してあるオプション値が存在する場合
                // デフォルトと既存値で共通のキーだけの配列を array_intersect_key() で取得し、
                // それら3つのそれぞれの要素の数が同じならば、オプション項目に変更は無い
			$deff = array_intersect_key( $this->default, $this->current );
			$countary = array( count ( $this->default ) , count ( $this->current ) , count ( $deff ) );
	
			if ( 1 !== count ( array_unique( $countary ) ) ) {
				foreach ( $this->current as $key => $val ) {
					if ( isset ( $this->default[ $key ] ) ) {// デフォルトオプションにそのキーが存在する要素だけ保存されている値で上書き。
						$this->default[ $key ] = $val;
					}
				}
				update_option( $this->option_name, $this->default );
				$this->ret_option = $this->default;
	
			} else {
				$this->ret_option = $this->current;
			}
		}
	}

	public function getOption(){
		return $this->ret_option;
	}
}

$hiros_all_crt_options = new Hiros_Theme_Option( 'hiros_options', 1 );
$hiros_crt_options = $hiros_all_crt_options->getOption();
$hiros_ign_options = new Hiros_Theme_Option( 'hiros_ignore', 0 );
$hiros_ignore = $hiros_ign_options->getOption();
?>
PHP
CopyExpand

« add_theme_pageによる管理画面のメニュー登録 »

そして次なるはいよいよこのオプション値を管理画面で変更、更新できるようにすると。
まずは管理画面のメニューにオプション設定の項目を追加します。
今回はテーマのオプションなのでメニューの「外観」にサブメニューとして項目を追加します。その場合はadd_theme_page を使用すれば良いということです。
codexだと英語版ですがFunction Reference/add theme pageになります。

<?php
add_theme_page( $page_title, $menu_title, $capability, $menu_slug, $function );
?>
PHP
CopyExpand

パラメータは以下の通り。

$page_title必須メニューをクリックした時に表示されるページのタイトル
$menu_title必須メニューに表示されるタイトル
$capability必須このメニューページを閲覧・使用するために最低限必要なユーザーレベル
またはユーザーの種類と権限
$menu_slug必須このメニューページの識別子となる独特な名前
$function任意メニューページにコンテンツを表示する関数

このadd_theme_page を関数にして、アクションフックのadmin_menu で作った関数を登録するということです。
ユーザーレベルはadministrator にしています。

add_action( 'admin_menu', 'hiros_add_menu' );

function hiros_add_menu() {
	add_theme_page( 'hiros theme Options', 'hiros theme Option', 'administrator', 'hiros_theme_options', 'hiros_page_output' );
}
PHP
CopyExpand

次に入力した値をオプションとして保存するために登録することが必要なのだと思います。
それにはregister_setting 関数を用い、アクションフックadmin_init にて登録するということのようです。
codex 設定ページの作成にある例では下記の通りです。登録するオプションが複数ある場合は例のようにregister_setting を複数登録するということです。

<?php
add_action( 'admin_init', 'register_mysettings' );

function register_mysettings() { // whitelist options
  register_setting( 'myoption-group', 'new_option_name' );
  register_setting( 'myoption-group', 'some_other_option' );
  register_setting( 'myoption-group', 'option_etc' );
}
?>
PHP
CopyExpand
<?php
register_setting( $option_group, $option_name, $sanitize_callback );
?>
PHP
CopyExpand

register_setting のパラメータは次のとおり。

$option_group必須オプションのグループ名
$option_name必須登録するオプションそのものの名前
$sanitize_callback任意オプション値をサニタイズするためのコールバック関数

$option_group はadd_theme_page の第5パラメータにおいて指定した実際のページのhtml を出力する関数の中において使用する、settings_fields関数の引数として指定します。$option_name は実際に保存するオプションの名前で、出力されるhtml のinput 要素のname 属性に指定することで、フォームを送信した時にこのオプション名で保存されます。

「codex 設定ページの作成」のもっと下の方を見ると、add_theme_page とregister_setting を含む関数を登録するadmin_init のアクションフックは同じ関数に含め、アクションフックadmin_menu に登録しているので同じようにしました。
実際にfunctions.php に記述しているのはこれ以降のコードです。

<?php
add_action( 'admin_menu', 'hiros_add_menu' );

function hiros_add_menu() {
	add_theme_page( 'hiros theme Options', 'hiros theme Option', 'administrator', 'hiros_theme_options', 'hiros_page_output' );
	add_action( 'admin_init', 'register_mysettings' );
}

function register_mysettings() {
	register_setting( 'hiros-settings-group', 'hiros_options' );
}
?>
PHP
CopyExpand

そしてadd_theme_page の第5パラメータで指定した、実際のページのhtml を出力する関数hiros_page_output を作ります。

<?php
function hiros_page_output() {
	$hiros_options = get_option( 'hiros_options' );
	$explstr = array( 'en_log' => 'アクセスログ、画像クリックログを残す場合はYes',
		'add_coarse_screen' => 'top 背景画像にざらめを付ける場合はYes',
		'add_shadow' => 'top bar 、コンテンツに影を付ける場合はYes',
		'add_rcorner' => 'post 欄を角丸表示にする場合はYes',
		'show_postcalendar' => '投稿カレンダーを表示する場合はYes',
		'show_tagcloud' => 'タグクラウドまたはタグリストを表示する場合はYes',
		'en_popupimg' => 'javascriptで画像をポップアップ表示させる時はYes',
		'en_pagenation' => 'ページャーを表示する時はYes',
		'show_side_widget' => 'すでに設定してある独自サイドウィジットを表示する場合はYes',
		'show_footer_widget' => 'すでに設定してある独自フッターウィジットを表示する場合はYes',
		'en_toolbar_faceup' => '認知用のtoolbar自動スクロールアップをさせる場合はYes',
		);
?>
	<div class="wrap" id="custom-logo">
		<h2>hiros theme option</h2>
		<h3>Option config</h3>
		<form method="post" action="options.php">
			<?php
				settings_fields( 'hiros-settings-group' );
				do_settings_sections( 'hiros-settings-group' );
			?>
			<table class="options-table">
				<tr><th>option</th><th>Yes</th><th>No</th><th>explain</th></tr>
				<?php
					foreach ( $hiros_options as $key => $val ) {
						if ( $val === '1' ) {
							$ychecked = ' checked';
							$nchecked = '';
						} else {
							$ychecked = '';
							$nchecked = ' checked';
						}
						echo '<tr><td>'.$key.'</td><td><input type="radio" name="hiros_options['.$key.']" value="1"'.$ychecked.'></td><td><input type="radio" name="hiros_options['.$key.']" value="0"'.$nchecked.'></td><td>'.$explstr[$key].'</td></tr>';
					}
				?> 
			</table>
			<?php submit_button(); ?>
		</form>
	</div>
<?php
}
PHP
CopyExpand

18、21~25、41行目はcodex において指定されているとおりの記述です。

23、24行目のsettings_fields関数とdo_settings_sections関数はお約束でこれによりwordpress がオプションページを上手に処理してくれるとのことです。尚、引数には前述したとおりregister_setting の第一引数$option_group で指定した名前を指定します。

5~16行目の連想配列は各オプションの説明を表示さるためのものなので必要の無い物です。このテーマの場合のオプションの設定値は0 か1 だけなので全てラジオボタンにしています。
保存するオプション値は個々のオプションを連想配列でまとめた値にしていますので、フォームから送信されるデータも同じ様に連想配列で送信されるようにしています。 実際に出力されたhtml は以下のとおり。
尚、見やすいように字下げと改行を入れてあり、途中を省略してあります。

5~8行目がsettings_fields関数によって出力された部分。noncerefererが出力されています。nonceに関してはこちら→「カスタムフィールドの投稿入力フォームへの表示«nonceに関して»」に書いています。

あれっ?となるとdo_settings_sections関数は何をしているのだろうかと?codex には「この関数は、フォームのマークアップ自体を面倒みてくれます。」と書いてあります。どうやら add_settings_field関数を使用する場合に有効なようなのですが・・・、説明がよくわからない。実際にこれを無くしてしまっても出力されるものは同じで、設定の保存も問題なく出来てしまう。この場合は必要無いようです。

<div class="wrap" id="custom-logo">
	<h2>hiros theme option</h2>
	<h3>Option config</h3>
	<form method="post" action="options.php">
		<input type='hidden' name='option_page' value='hiros-settings-group' />
		<input type="hidden" name="action" value="update" />
		<input type="hidden" id="_wpnonce" name="_wpnonce" value="19ed47b0df" />
		<input type="hidden" name="_wp_http_referer" value="/wp/wp-admin/themes.php?page=hiros_theme_options" />
		<table class="options-table">
			<tr><th>option</th><th>Yes</th><th>No</th><th>explain</th></tr>
			<tr>
				<td>en_log</td>
				<td><input type="radio" name="hiros_options[en_log]" value="1" checked></td>
				<td><input type="radio" name="hiros_options[en_log]" value="0"></td>
				<td>アクセスログ、画像クリックログを残す場合はYes</td>
			</tr>
			<tr>
				<td>add_coarse_screen</td>
				<td><input type="radio" name="hiros_options[add_coarse_screen]" value="1" checked></td>
				<td><input type="radio" name="hiros_options[add_coarse_screen]" value="0"></td>
				<td>top 背景画像にざらめを付ける場合はYes</td>
			</tr>
			<tr>
				<td>add_shadow</td>
				<td><input type="radio" name="hiros_options[add_shadow]" value="1" checked></td>
				<td><input type="radio" name="hiros_options[add_shadow]" value="0"></td>
				<td>top bar 、コンテンツに影を付ける場合はYes</td>
			</tr>
			<tr>
				<td>add_rcorner</td>
				<td><input type="radio" name="hiros_options[add_rcorner]" value="1"></td>
				<td><input type="radio" name="hiros_options[add_rcorner]" value="0" checked></td>
				<td>post 欄を角丸表示にする場合はYes</td>
			</tr>
			=途中省略=
		</table>
		<p class="submit">
			<input type="submit" name="submit" id="submit" class="button button-primary" value="変更を保存"  />
		</p>
	</form>
</div>
HTML
CopyExpand

« あとを汚したままにしないようにオプションの削除 »

この後、プラグインを作ったりしたのですが、プラグインを使用しなくなった時に、データベースに保存してあるオプション値を消去する処理も、いらない物を後に残さないためには必須な処理であったのですが、当然これはテーマであっても同じことです。
プラグインに関しての処理はここの→《コメントの編集機能、nonceで作り直してプラグイン》中段やや下のところに書いているのですが、テーマの場合はどうやればいいのか調べてみてもあまり出ていないというかよくわからない。探し方がへたくそなのかなぁ~?「delete_site_transient 」というのがあったもののなにやらよくわかりませんでした。唖然!
それではと、プラグインの処理がそのまま使えるかとやってみたものの、それはやはりダメですね。
どうしたものかと、テーマに関していそうなファイルの中身を全部調べて見れば何かわかるだろうと、結果、「/wp-includes/theme.php 」の中、677行目辺りにfunction switch_theme を見つけました。codex によれば「現在のテーマを、新しいテンプレートとスタイルシートに切り替えます。」という関数とのこと。
そして、この関数の最後にswitch_theme というアクションフックが用意されています。英語版codex によれば「switch_theme is triggered when the blog's theme is changed. Specifically, it fires after the theme has been switched but before the next request. Theme developers should use this hook to do things when their theme is deactivated. 」
なんとか、訳しますと、
「ブログのテーマが変更されたときswitch_themeが誘引されます。具体的に言えば、テーマが切り替わったあと、しかし次のリクエストの前に発火します。テーマ開発者はテーマが非アクティブ化されたときに処理することがあれば、このフックを使用するべきです。」と、いうことだと思います。
使用方法もいたってシンプルで、同じくcodex→ 《Plugin API/Action Reference/switch theme》によれば、

<?php
	add_action('switch_theme', 'mytheme_setup_options');

	function mytheme_setup_options () {
		delete_option('mytheme_enable_features');
		delete_option('mytheme_enable_catalog');
	}
?>
PHP
CopyExpand

と、いうことです。
ただし、これはテーマを変更した時に動作するということなので、変更するたびにオプションは消されてしまう。ただ、ちょっと他のテーマを試してみたいだけというようなときは、オプションを残しておきたいときもあるのではないかと。

と、いうことで、この処理もオプションで選択できるようにして実際には以下のようにしてします。
これもプラグインの時と同様にクラスの中に入れてしまおうと、同じように色々やってみたのですが、どうもうまくいきませんでした。よって、クラスの外に普通に書いています。こんな具合で・・・。

<?php
	add_action( 'switch_theme', 'hiros_theme_delete_options' );
	
	function hiros_theme_delete_options () {
		$current_opt = get_option( 'hiros_options' );
		if ( '1' === $current_opt['delete_option'] ) {
			delete_option( 'hiros_options' );
		} else {
			$current_opt = array();
		}
	}
?>
PHP
CopyExpand

Talking

  • admin より:

    通りすがりさんのおかげで他にも色々とおかしなことになっていることに気が付きました。
    さっそく直しています。
    ほんとに感謝してます。

  • 通りすがり より:

    言葉が悪すぎでしたねf^_^;
    迅速な対応でビックリしました笑

  • admin より:

    通りすがりさん、コメントありがとうございます。
    ほんとに仰る通りでうざいですね。
    うれいしいですね、このような的を射たアドバイスを下さって。
    ちゃんと確認しなければいけませんね。
    位置を変更したり色々やっているうちに、あまり必要の無いものと気づきました。
    本当に貴重なご意見ありがとうございました。

  • 通りすがり より:

    すまほで追従するメニューがうざすぎ

Leave a Reply!

JavaScript is necessary to send a comment.
You can edit and delete your comment if you input a edit key.
Edit key is necessary for attesting you when you edit and delete it.
The tag of HTML cannot be used in comment.
When you comment for the first time, it is displayed after the approval of the administrator.
Because I cannot speak English so much, it takes time to answer.
Required fields are marked *.

※Please enter more than 5 characters only alphabets.
※Edit or delete are possible for 2000 days after approval.

*

♠Simplistic Comment User Editable v4.0

♠When visitors leave comments on the site this site collect the data shown in the comments form, and also the visitor’s IP address and browser user agent string to help spam detection.
♠This site does not use cookie when visitors leave comments and commenter edit comment.
♠This site uses Akismet to reduce spam. Learn how your comment data is processed.

Comments feed

Trackback URL : https://strix.main.jp/wp-trackback.php?p=49712

Sanbanse Funabashi
2011.01.01 sunrise

Top

スクロールさせるか画像をクリックすると元に戻ります。