WordPressのカスタムフィールドでカレンダーを作成する

宿泊施設や貸会議室等の予約状況カレンダーをサイトに掲載したいという要望はこれまでも何度かありましたが、今回はWordPressで作成したサイトに簡単な予約状況カレンダーを載せたいという依頼を請けました。
そこで MTS Simple BookingEvents Manager などプラグインをいくつか試しに触ってみたのですが、なかなか顧客の要望に合致するシンプルな機能のものが見つからないので、最終的にカスタム投稿タイプとカスタムフィールドの機能を駆使して自作してみることにしました。

必要な機能の確認

今回は「派遣講師の予約状況カレンダー」を作るものと仮定します。
カレンダーの編集機能に加えて、将来的に講師の入れ替わりが随時あることを考慮すると、項目の追加と削除もクライアント側で編集可能にする必要があります。
箇条書きにまとめると次の通り。

  • 各講師ごとの予約状況をカレンダーに「◯☓」で表示。
    予約の可不可を随時編集できるようにする。
  • 講師は入れ替わりがあるので、随時追加と削除ができるようにする。

公開ページと編集画面の出来上がりイメージは下図のような感じです。

編集画面

公開ページ

クライアントの要望を実際にどのようにプログラム化するか、ノートに書き出したりデザインカンプを元に更新作業の流れを頭の中でシミュレートしてみたりして構成が決まったところで、それでは制作を開始します。

カスタム投稿タイプの追加

まずは、カスタム投稿タイプ「reservation」を作成します。
カスタム投稿タイプの追加には「Custom Post Type UI」プラグインを利用します。

Custom Post Type UI でカスタム投稿タイプを追加

プラグインを追加〜有効化して、管理メニューの「CTP UI」→「投稿タイプの追加と編集」から下記の設定でカスタム投稿タイプ「reservation」を作成します。

  • 投稿タイプスラッグ : reservation
  • 複数形のラベル : 予約状況
  • 単数形のラベル : 予約状況
  • メニュー名 : 予約状況
  • すべての項目 : 予約状況一覧
  • フロントでのリライト : false
  • メニューの位置 : 5
  • サポート : リビジョン(チェックボックスをON)※

その他の設定はデフォルトのままで基本的にOKです。
上記以外の追加ラベルは任意に設定してください。

※「Revision Control」プラグインを使用している場合は「サポート」→「リビジョン」のチェックボックスをONにするのを忘れずに。
必要に応じて、カスタム投稿タイプを追加後に「設定」→「リビジョン」でリビジョン保存数を設定しておきます。

パーマリンクの設定を更新

「Custom Post Type UI」プラグインを使用する際には、併せて「Custom Post Type Permalinks」プラグインをインストールしておきます。
カスタム投稿タイプを追加・編集した際には「設定」→「パーマリンク設定」で設定を更新しておきます。(パーマリンク設定ページの「変更を保存」ボタンをクリックするだけでOK)

投稿編集画面のカスタマイズ

次にカスタム投稿タイプ「reservation」の投稿編集画面のカスタマイズを行います。
予約状況カレンダーのカスタムフィールド部分以外は「Advanced Custom Fields」プラグインを使用します。

タイトル欄の透かし(プレースホルダー)の変更と紹介文の本文欄(コンテンツエディタ)のカスタマイズの方法については、後ほど補足します。

カスタムフィールドを独自に作成する

前置きが長くなりましたが、ようやくここからが本題。
予約状況カレンダーを編集するためのカスタムフィールドの追加を、プラグインを使わず独自に行います。

カスタムフィールドの追加については、現状あまり細かい点まで理解できていないので詳細は省きますが?、大まかなプログラムの流れとしては次のような感じです。

【1】編集画面に独自のメタボックスを追加
【2】メタボックスに入力された情報をDBに保存

基本的なカスタムフィールドのカスタマイズ方法については、
こちらのページを参考にしました。

(参考)[ WordPress ] 独自のカスタムフィールドを設定する

編集画面に独自のメタボックスを追加

functions.php に次の記述を追加します。
add_meta_box() でメタボックスを定義して、メタボックス内に表示(echo)するhtmlを定義したファンクション内に記述します。

/* --------------------------------------------------
  カスタム投稿タイプ「reservation」の編集画面に
  独自のメタボックスを追加
-------------------------------------------------- */
add_action('admin_menu', 'add_schedule');

function add_schedule(){
	if(function_exists('add_schedule')){
		add_meta_box('schedule', '予約状況', 'insert_schedule', 'reservation', 'normal', 'high');
	}
}

function insert_schedule(){
	global $post;
	wp_nonce_field(wp_create_nonce(__FILE__), 'my_nonce');
	echo '<p>予約状況を選択してください。【◯】予約可能 【×】予約不可 【休】休業日 【−】未設定</p>';
	//当月+2ヵ月分の入力フォームを表示
	echo output_cf_calendar(date('Y'),date('n'));
	echo output_cf_calendar(date('Y'),date('n')+1);
	echo output_cf_calendar(date('Y'),date('n')+2);
}
カレンダー部分のサブルーチン

「当月+2ヵ月分の入力フォームを表示」の箇所は実際にカレンダーの入力フォーム部分を表示するルーチンですが、長くなるので下記のように別途サブルーチンにまとめました。

/* --------------------------------------------------
  カスタムフィールドのカレンダー生成
-------------------------------------------------- */
function output_cf_calendar($year,$month) {
	global $post;

	//翌月、翌々月が年をまたぐ場合
	if( $month > 12 ) { $year = $year+1; $month = $month-12; }
	//月末日の取得
	$l_day = date('t', strtotime($year.sprintf('%02d',$month).'01'));
	//月初日の曜日の取得
	$first_week = date('w',strtotime($year.sprintf('%02d',$month).'01'));
	//セレクトメニューオプション
	$menu_options = array('−','◯','×','休');
	//祝日を取得
	$holidays = japan_holiday_ics();

	$calendar = <<<EOM
<table class="calendar">
	<caption>{$year}年{$month}月</caption>
	<tr>
		<th class="sun">日</th>
		<th>月</th>
		<th>火</th>
		<th>水</th>
		<th>木</th>
		<th>金</th>
		<th>土</th>
	</tr>
EOM;
	for( $i=1; $i<=$l_day+$first_week; $i++ ) {
		$day = $i-$first_week;
		if( $i%7 == 1 ) { $calendar .= '<tr>'."\n"; }
		if( $day <= 0 ) {
			$calendar .= '<td>&nbsp;</td>'."\n";
		} else {
			$key = 'date'.$year.sprintf('%02d',$month).sprintf('%02d',$day);
			$value = get_post_meta($post->ID, $key, true);
			if( mktime(0,0,0,$month,$day,$year) == mktime(0,0,0,date('n'),date('j'),date('Y')) ) { $class=' class="today"'; }
			elseif( date('w',mktime(0,0,0,$month,$day,$year)) == 0 ) { $class=' class="sun"'; }
			elseif( !empty($holidays[date("Ymd", mktime(0,0,0,$month,$day,$year))]) ) { $class=' class="holiday"'; }
			else { $class = ''; }
			$calendar .= '<td'.$class.'>';
			$calendar .= $day.'<br>';
			$calendar .= '<select name="'.$key.'">';
			foreach( $menu_options as $option ) {
				if( $option == $value ) { $select = ' selected'; } else { $select = ''; }
				$calendar .= '<option value="'.$option.'"'.$select.'>'.$option.'</option>';
			}
			$calendar .= '</select>';
			$calendar .= '</td>'."\n";
		}
	}
	if( $i%7 > 1 ) {
		for( $td=0; $td<=7-($i%7); $td++) {
			$calendar .= '<td>&nbsp;</td>'."\n";
		}
	}
	$calendar .= '</tr>'."\n";
	$calendar .= '</table>'."\n";
	return $calendar;
}
祝日を取得するサブルーチン

祝日表示の要不要は特に指示されていなかったのですが、カレンダーの自作は久しぶりだったので、今回Googleカレンダーを利用する方法を試してみることにしました。
当初は他サイトを参考にGoogleカレンダーAPIから祝日を取得する方法をいくつか試したのですが、JSONの取得が何故か一向に上手くいかないので?、今回はとりあえずiCal形式で取得する方法で実装してみました。

/* --------------------------------------------------
  GoogleカレンダーからiCal形式で祝日を取得
-------------------------------------------------- */
function japan_holiday_ics($year) {
    // カレンダーID
    $calendar_id = urlencode('japanese__ja@holiday.calendar.google.com');
    $url = 'https://calendar.google.com/calendar/ical/'.$calendar_id.'/public/full.ics';
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    $result = curl_exec($ch);
    curl_close($ch);
    if (!empty($result)) {
        $items = $sort = array();
        $start = false;
        $count = 0;
        foreach(explode("\n", $result) as $row => $line) {
            // 1行目が「BEGIN:VCALENDAR」でなければ終了
            if (0 === $row && false === stristr($line, 'BEGIN:VCALENDAR')) {
                break;
            }
            // 改行などを削除
            $line = trim($line);
            // 「BEGIN:VEVENT」なら日付データの開始
            if (false !== stristr($line, 'BEGIN:VEVENT')) {
                $start = true;
            } elseif ($start) {
                // 「END:VEVENT」なら日付データの終了
                if (false !== stristr($line, 'END:VEVENT')) {
                    $start = false;
                    // 次のデータ用にカウントを追加
                    ++$count;
                } else {
                    // 配列がなければ作成
                    if (empty($items[$count])) {
                        $items[$count] = array('date' => null, 'title' => null);
                    }
                    // 「DTSTART;~」(対象日)の処理
                    if(0 === strpos($line, 'DTSTART;VALUE')) {
                        $date = explode(':', $line);
                        $date = end($date);
                        $items[$count]['date'] = $date;
                        // ソート用の配列にセット
                        $sort[$count] = $date;
                    }
                    // 「SUMMARY:~」(名称)の処理
                    elseif(0 === strpos($line, 'SUMMARY:')) {
                        list($title) = explode('/', substr($line, 8));
                        $items[$count]['title'] = trim($title);
                    }
                }
            }
        }
        // 日付でソート
        $items = array_combine($sort, $items);
        ksort($items);
        return $items;
    }
}

(参考)PHPで日本の祝日のJSONをGoogleカレンダーから取得する(API認証等不要)

メタボックスに入力された情報をDBに保存

続けて、以下の記述を functions.php に追加します。
記事保存時に $_POST[$meta_key] で受け取った値($Meta_value)を、add_post_meta() でデータベースに保存します。

/* --------------------------------------------------
  メタボックスに入力された情報をDBに保存
-------------------------------------------------- */
add_action('save_post', 'save_schedule');

function save_schedule($post_id){
	$my_nonce = isset($_POST['my_nonce']) ? $_POST['my_nonce'] : null;
	if(!wp_verify_nonce($my_nonce, wp_create_nonce(__FILE__))) {
		return $post_id;
	}
	if(defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return $post_id; }
	if(!current_user_can('edit_post', $post_id)) { return $post_id; }

	//前月+当月+2ヵ月分のmeta_keyを設定
	$keys1 = set_calendar_keys(date('Y'),date('n')-1);
	$keys2 = set_calendar_keys(date('Y'),date('n'));
	$keys3 = set_calendar_keys(date('Y'),date('n')+1);
	$keys4 = set_calendar_keys(date('Y'),date('n')+2);
	$keys = array_merge($keys1,$keys2,$keys3,$keys4);

	foreach( $keys as $key ) {
		$data = $_POST[$key];
		if( get_post_meta($post_id, $key) == "" && $data != "−" ){
			add_post_meta($post_id, $key, $data, true);
		} elseif( $data != get_post_meta($post_id, $key, true) && $data != "−" ) {
			update_post_meta($post_id, $key, $data);
		} elseif( $data == "" || $data == "−" ) {
			delete_post_meta($post_id, $key, get_post_meta($post_id, $key, true));
		}
	}
}

「前月+当月+2ヵ月分のmeta_keyを設定」は下記のように別途サブルーチンにまとめました。
当日以前のデータはサイトの運用の都合上不要になるので、データベースの肥大化を少しでも減らすことを考慮して、前月分は空の値を上書きすることで削除(delete_post_meta)します。

//カスタムフィールド書き出し時のmeta_key設定
function set_calendar_keys($year,$month) {
	//前月、翌月、翌々月が年をまたぐ場合の処理
	if( $month > 12 ) { $year = $year+1; $month = $month-12; }
	elseif( !$month ) { $year = $year-1; $month = 12; }
	//月末日の取得
	$l_day = date('t', strtotime($year.sprintf('%02d',$month).'01'));
	//一ヶ月分のmeta_key(dateYYYYMMDD)を配列$keysに格納
	for ( $i=1; $i<=$l_day; $i++ ) {
		$keys[($i-1)] = 'date'.$year.sprintf('%02d',$month).sprintf('%02d',$i);
	}
	return $keys;
}

カレンダー表示のCSSをカスタマイズ

最後に functions.php に下記の記述を追加して、カレンダー部分の表示を見やすく整えます。

/* --------------------------------------------------
  reservationの編集画面のCSS設定
-------------------------------------------------- */
function reservation_output_css() {
	$pt = get_post_type();
	if ( $pt == 'reservation' ) {
		$pt_reservation_css  = '<style type="text/css">';
		/* カレンダー表示のカスタマイズ */
		$pt_reservation_css .= '#schedule .inside { text-align: center; }';
		$pt_reservation_css .= '#schedule table.calendar { border-collapse: collapse; border-spacing: 0; margin: 0 auto 20px; }';
		$pt_reservation_css .= '#schedule table.calendar caption { font-size: 150%; font-weight: bold; color: #999; padding: 5px 0; }';
		$pt_reservation_css .= '#schedule table.calendar tr th { border: solid 1px #ccc; padding: 3px; background: #666; color: #fff; }';
		$pt_reservation_css .= '#schedule table.calendar tr th.sun { background: #e66; }';
		$pt_reservation_css .= '#schedule table.calendar tr td { border: solid 1px #ccc; padding: 3px; text-align: left; }';
		$pt_reservation_css .= '#schedule table.calendar tr td.sun { background: #fee; }';
		$pt_reservation_css .= '#schedule table.calendar tr td.holiday { background: #fee; }';
		$pt_reservation_css .= '#schedule table.calendar tr td.today { background: #ffe; }';
		$pt_reservation_css .= '#schedule table.calendar tr td select { width: 4em; margin: 5px 10px; }';
		$pt_reservation_css .= '</style>';
		echo $pt_reservation_css;
	}
}
add_action('admin_head', 'reservation_output_css');

最終的な編集画面は下図のようになります。

以上で管理ページ側のカスタマイズは一通り完了です。
プラグインを使用せずにカスタムフィールドを独自に追加できるようになると、WordPressの活用の幅がまた一段と広がりそうですね??

公開ページのテンプレートカスタマイズ

公開ページ側のカスタマイズは Advanced Custom Fields などのプラグインを使用した際と同様に get_post_meta() でカスタムフィールドの値を取得します。
以下は一覧ページ(archive-reservation.php)と個別ページ(single-reservation.php)のテンプレートをこちらの「ブランクテーマ(Type1)」に組み込んだ場合の一例です。

▼archive-reservation.php

コードを表示
/* --------------------------------------------------
  予約状況(reservation)一覧ページ
-------------------------------------------------- */
<?php get_header(); ?>

	<div id="content" class="reservation-content">

		<main id="main" role="main">
			<section class="reservation-archives">

<?php if ( have_posts() ): ?>

				<header class="page-header">
					<h1 class="page-title">予約状況一覧</h1>
				</header>

<?php
// 表示日数設定
$num = 20;
if($_POST['num']) { $num = $_POST['num']; }
$nums = array('14','20','30','40');
// 現在の日付を設定
$year	 = date('Y');
$month	 = date('n');
$day	 = date('j');
$date = mktime(0,0,0,$month,$day,$year);
$today = $date;
// ページ送りの日付を設定
if( $_POST['date'] ) { $date = $_POST['date']; }
if( $_POST['action']=='prev' ) { $date = $date-($num*60*60*24); }
if( $_POST['action']=='next' ) { $date = $date+($num*60*60*24); }
$year	 = date("Y", $date);
$month	 = date("n", $date);
$day	 = date("j", $date);
?>
				<div class="selectPage">
					<form method="post" action="./">
						<input type="hidden" name="num" value="<?php echo $num; ?>">
						<input type="hidden" name="date" value="<?php echo $date; ?>">
						<?php if( $today < $date ): ?><button type="submit" name="action" value="prev"><< 前の<?php echo $num; ?>日間</button><?php endif; ?>
						<button type="submit" name="action" value="next">次の<?php echo $num; ?>日間 >></button>
					</form>
				</div>

				<div class="selectNum">
					<p class="summary">◯…予約可能 ×…予約不可 休…休業日</p>
					<form method="post" action="./">
						<label for="num">表示期間:</label>
						<select name="num" id="num" onchange="submit(this.form)">
<?php /* select option start */ foreach( $nums as $n ): if( $n == $num ) { $select = ' selected'; } else { $select = ''; } ?>
							<option value="<?php echo $n; ?>"<?php echo $select; ?>><?php echo $n; ?>日間</option>
<?php /* select option end   */ endforeach; ?>
						</select>
					</form>
				</div>

				<div class="reservationList">
					<dl class="head" style="width: <?php echo 100+(50*$num); ?>px;">
						<dt class="listTitle">&nbsp;</dt>
<?php /* calendar head start */ 
$weeks = array('日','月','火','水','木','金','土');
$holidays = japan_holiday_ics();
for( $i=$day; $i<$day+$num; $i++ ):
	$week = date("w", mktime(0,0,0,$month,$i,$year));
	if( $week == 0 ) { $class = ' class="sun"'; }
	elseif( !empty($holidays[date("Ymd", mktime(0,0,0,$month,$i,$year))]) ) { $class=' class="holiday"'; }
	else { $class = ''; }
?>
						<dd<?php echo $class; ?>><?php echo date("n/j", mktime(0,0,0,$month,$i,$year)); ?><br><?php echo $weeks[$week]; ?></dd>
<?php /* calendar head end   */ endfor; ?>
					</dl>

<?php /* main loop start */ while ( have_posts() ): the_post(); ?>
					<dl class="body" style="width: <?php echo 100+(50*$num); ?>px;">
						<dt id="post-<?php the_ID(); ?>" <?php post_class('listTitle'); ?>>
							<h2 class="post-title"><a href="<?php the_permalink(); ?>" title="<?php the_title(); ?>"><?php the_title(); ?></a></h2>
						</dt>
<?php /* calendar body start */
for( $i=$day; $i<$day+$num; $i++ ):
	$week = date("w", mktime(0,0,0,$month,$i,$year));
	if( $week == 0 ) { $class = ' class="sun"'; }
	elseif( !empty($holidays[date("Ymd", mktime(0,0,0,$month,$i,$year))]) ) { $class=' class="holiday"'; }
	else { $class = ''; }
	$key = 'date'.date("Ymd", mktime(0,0,0,$month,$i,$year));
	$value = get_post_meta($post->ID, $key, true);
	if( !$value ) { $value = '<span class="undecided">未設定</span>'; }
?>
						<dd<?php echo $class; ?>><?php echo $value; ?></dd>
<?php /* calendar body end   */ endfor; ?>

					</dl>
<?php /* main loop end   */ endwhile; ?>
				</div>

				<div class="selectPage">
					<form method="post" action="./">
						<input type="hidden" name="num" value="<?php echo $num; ?>">
						<input type="hidden" name="date" value="<?php echo $date; ?>">
						<?php if( $today < $date ): ?><button type="submit" name="action" value="prev"><< 前の<?php echo $num; ?>日間</button><?php endif; ?>
						<button type="submit" name="action" value="next">次の<?php echo $num; ?>日間 >></button>
					</form>
				</div>

<?php else: ?>

				<article>
					<header class="page-header">
						<h1 class="page-title">Not found.</h1>
					</header>
					<div class="page-content notfound">
						<p>お探しのページが見つかりませんでした。検索をお試しください。</p>
						<?php get_search_form(); ?>
					</div><!-- .page-content -->
				</article>

<?php endif; ?>

			</section>
		</main>

	</div><!-- #content -->

<?php get_footer(); ?>

▼single-reservation.php

コードを表示
/* --------------------------------------------------
  予約状況(reservation)個別ページ
-------------------------------------------------- */
<?php get_header(); ?>

	<div id="content" class="reservation-content">

		<main id="main" role="main">
			<section class="reservation-single">

<?php if ( have_posts() ): ?>
				<header class="page-header">
					<h1 class="page-title">予約状況<a href="<?php echo home_url();?>/reservation/"><small>一覧へ</small></a></h1>
				</header>

<?php
// 表示日数設定
$num = 20;
if($_POST['num']) { $num = $_POST['num']; }
$nums = array('14','20','30','40');
// 現在の日付を設定
$year	 = date('Y');
$month	 = date('n');
$day	 = date('j');
$date = mktime(0,0,0,$month,$day,$year);
$today = $date;
// ページ送りの日付を設定
if( $_POST['date'] ) { $date = $_POST['date']; }
if( $_POST['action']=='prev' ) { $date = $date-($num*60*60*24); }
if( $_POST['action']=='next' ) { $date = $date+($num*60*60*24); }
$year	 = date("Y", $date);
$month	 = date("n", $date);
$day	 = date("j", $date);
?>

				<div class="reservationSingle">

<?php /* main loop start */ while ( have_posts() ): the_post(); ?>
					<div class="profile">
						<?php echo wp_get_attachment_image(post_custom('photo'), array(200,250)); ?>
						<h2 class="name"><?php the_title(); ?> <small><?php echo get_post_meta($post->ID, 'kana', true); ?></small></h2>
						<p class="course"><strong>担当科目</strong> : <?php echo get_post_meta($post->ID, 'course', true); ?></p>
						<div class="introduction"><?php echo nl2br(get_the_content()); ?></div>
					</div>
<?php
echo output_single_calendar(date('Y'),date('n'));
echo output_single_calendar(date('Y'),date('n')+1);
?>
<?php /* main loop end   */ endwhile; ?>
				</div>

<?php else: ?>

				<article>
					<header class="page-header">
						<h1 class="page-title">Not found.</h1>
					</header>
					<div class="page-content notfound">
						<p>お探しのページが見つかりませんでした。検索をお試しください。</p>
						<?php get_search_form(); ?>
					</div><!-- .page-content -->
				</article>

<?php endif; ?>

				<p class="backToList"><a href="../">一覧ページに戻る</a></p>

			</section>
		</main>

	</div><!-- #content -->

<?php get_footer(); ?>
<?php
function output_single_calendar($year,$month) {
	global $post;

	//翌月、翌々月が年をまたぐ場合
	if( $month > 12 ) { $year = $year+1; $month = $month-12; }
	//月末日の取得
	$l_day = date('t', strtotime($year.sprintf('%02d',$month).'01'));
	//月初日の曜日の取得
	$first_week = date('w',strtotime($year.sprintf('%02d',$month).'01'));
	//祝日を取得
	$holidays = japan_holiday_ics();
	
	$calendar = <<<EOM
<table>
	<caption>{$year}年{$month}月</caption>
	<tr>
		<th class="sun">日</th>
		<th>月</th>
		<th>火</th>
		<th>水</th>
		<th>木</th>
		<th>金</th>
		<th>土</th>
	</tr>
EOM;
	/* calendar body start */
	for( $i=1; $i<=$l_day+$first_week; $i++ ) {
		$day = $i-$first_week;
		if( $i%7 == 1 ) { $calendar .= '<tr>'."\n"; }
		if( $day <= 0 ) {
			$calendar .= '<td>&nbsp;</td>'."\n";
		} else {
			$key = 'date'.$year.sprintf('%02d',$month).sprintf('%02d',$day);
			$value = get_post_meta($post->ID, $key, true);
			if( $value == '休' ) { $value = '<span class="off">休業日</span>'; }
			elseif( !$value ) { $value = '<span class="undecided">未設定</span>'; }
			if( mktime(0,0,0,$month,$day,$year) == mktime(0,0,0,date('n'),date('j'),date('Y')) ) { $class=' class="today"'; }
			elseif( date('w',mktime(0,0,0,$month,$day,$year)) == 0 ) { $class=' class="sun"'; }
			elseif( !empty($holidays[date("Ymd", mktime(0,0,0,$month,$day,$year))]) ) { $class=' class="holiday"'; }
			else { $class = ''; }
			$calendar .= '<td'.$class.'>';
			$calendar .= '<span class="day">'.$day.'</span>';
			$calendar .= $value;
			$calendar .= '</td>'."\n";
		}
	}
	if( $i%7 > 1 ) {
		for( $td=0; $td<=7-($i%7); $td++) {
			$calendar .= '<td>&nbsp;</td>'."\n";
		}
	}
	$calendar .= '</tr>'."\n";
	/* calendar body end   */
	$calendar .= '</table>'."\n";
	$calendar .= '<p class="summary">◯…予約可能 ×…予約不可</p>'."\n";
	return $calendar;
}
?>

▼style.cssに追加

コードを表示
/*--------------------------------------------------
  予約状況
--------------------------------------------------*/
.post-type-archive-reservation  #main,
.single-reservation #main { width: auto; float: none; }

/* 一覧ページ */
.reservationList { overflow-x: auto; padding-bottom: 10px; margin-bottom: 10px; }
.reservationList::-webkit-scrollbar-button{ display: none; height: 10px; border-radius: 5px; background-color: #AAA; }
.reservationList::-webkit-scrollbar-button:hover{ background-color: #AAA; }
.reservationList::-webkit-scrollbar-thumb{ background-color: #ccc; border-radius: 5px; }
.reservationList::-webkit-scrollbar-thumb:hover{ background-color: #ddd; border-radius: 5px; }
.reservationList::-webkit-scrollbar-track{ background-color: #eee; border-radius: 5px; }
.reservationList::-webkit-scrollbar-track:hover{ background-color: #f3f3f3; border-radius: 5px; }
.reservationList::-webkit-scrollbar{ width: 10px; height: 10px; }
 
.reservationList dl { overflow: hidden; border-top: solid 1px #ccc; }
.reservationList dl dt,
.reservationList dl dd { border-right: solid 1px #ccc; border-bottom: solid 1px #ccc; padding: 3px 5px; text-align: center; float: left; width: 50px; height: 40px;
    -webkit-box-sizing: border-box;
       -moz-box-sizing: border-box;
         -o-box-sizing: border-box;
        -ms-box-sizing: border-box;
            box-sizing: border-box;
}
.reservationList dl.head dd { line-height: 1.4; font-size: 85.7%; background: #eee; }
.reservationList dl.head dd.sun { background: #fcc; }
.reservationList dl.head dd.holiday { background: #fcc; }
.reservationList dl.body { border-top: 0; }
.reservationList dl.body dt,
.reservationList dl.body dd { height: 50px; padding: 15px 5px; }
.reservationList dl.body dd.sun { background: #fee; }
.reservationList dl.body dd.holiday { background: #fee; }
.reservationList dl.body dd span.undecided { font-size: 85.7%; color: #999; }
.reservationList dl dt.listTitle { clear: both; white-space: nowrap; text-align: left; width: 100px; }
.reservationList dl.body dt.listTitle { margin: 0; }
.reservationList dl.body dt.listTitle .post-title { font-size: 100%; border: none; padding: 0; margin: 0; }

.reservationList dl { position: relative; }
.reservationList dl dt { position: fixed; background: #fff; border-left: solid 1px #ccc; }
.reservationList dl dd:nth-child(2) { margin-left: 100px; }

.reservation-archives .selectPage {}
.reservation-archives .selectPage button { background: #eee; color: #666; padding: 0 10px; margin-right: 5px; 
	border: solid 1px #ccc; line-height: 30px; outline: none; cursor: pointer;
	-webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; 
    -webkit-transition: 0.2s linear;
       -moz-transition: 0.2s linear;
         -o-transition: 0.2s linear;
            transition: 0.2s linear;
}
.reservation-archives .selectPage button:hover { filter: alpha(opacity=50); -moz-opacity: 0.50; opacity: 0.50; }
.reservation-archives .selectNum { overflow: hidden; margin-bottom: 5px; }
.reservation-archives .selectNum .summary { float: left; width: 50%; margin-top: 5px; }
.reservation-archives .selectNum form { float: right; width: 50%; text-align: right; }
.reservation-archives .selectNum form select { background: #999; color: #fff; padding: 0 10px; min-width: 6em; height: 24px; border: none; 
	-webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; 
}

/* 個別ページ */
.reservationSingle { margin: 0 150px; }
.reservationSingle .profile { overflow: hidden; }
.reservationSingle .profile img { float: left; margin: 0 20px 10px 0; }
.reservationSingle .profile .name small { font-size: 60%; color: #999; font-weight: normal; }
.reservationSingle .profile .course { font-size: 85.7%; margin-bottom: 10px; }
.reservationSingle .profile .introduction { margin: 0 0 30px 220px; }

.reservationSingle table { width: 100%; }
.reservationSingle table caption { font-size: 150%; font-weight: bold; color: #999; padding: 5px 0; }
.reservationSingle table tr th { border: solid 1px #ccc; padding: 3px; background: #666; color: #fff; text-align: center; }
.reservationSingle table tr th.sun { background: #e66; }
.reservationSingle table tr td { border: solid 1px #ccc; padding: 3px 3px 15px 3px; text-align: center; line-height: 1.2; width: 14%; }
.reservationSingle table tr td.sun { background: #fee; }
.reservationSingle table tr td.holiday { background: #fee; }
.reservationSingle table tr td.today { background: #ffe; }
.reservationSingle table tr td span.day { display: block; text-align: left; color: #999; }
.reservationSingle table tr td span.off { font-size: 85.7%; color: #999; }
.reservationSingle table tr td span.undecided { font-size: 85.7%; color: #999; }
.reservationSingle .summary { margin: 3px 0 20px; text-align: right; }

.reservation-single .backToList { text-align: center; margin-bottom: 20px; }
.reservation-single .backToList a { display: inline-block; background: #999; color: #fff; padding: 0 10px; 
	font-size: 85.7%; line-height: 30px; font-weight: normal; 
	-webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; 
}

以上、私のプログラムスキル上さほど凝ったことはできてませんが、
何かの参考になれば幸いです?✨

補足:タイトル欄と本文欄のカスタマイズ

カレンダー部分以外のカスタマイズについては本題から逸れるので、こちらで簡単に補足します。

タイトル欄の透かしのカスタマイズ

タイトル欄の透かし(プレースホルダー)カスタマイズには enter_title_here フックを使用して functions.php に下記の記述を追加します。

/* --------------------------------------------------
  reservationのタイトル欄の透かしをカスタマイズ
-------------------------------------------------- */
function reservation_CPT_item_EnterTitleHere($placeholder) {
	if (get_current_screen() -> post_type == 'reservation')
		$placeholder = 'ここに講師の氏名を入力';
	return $placeholder;
}
add_filter('enter_title_here','reservation_CPT_item_EnterTitleHere');

本文欄(コンテンツエディタ)のカスタマイズ

本文欄はプレーンテキストのみ入力可能に機能を限定するために、ビジュアルエディタを無効にして、さらに入力欄上部の各編集ボタンを非表示にします。

特定のカスタム投稿タイプでビジュアルエディタを無効にするには、下記の記述を functions.php に追加します。

/* --------------------------------------------------
  reservationでビジュアルエディタを使用しない
-------------------------------------------------- */
function disable_visual_editor_in_reservation(){
	global $typenow;
	if( $typenow == 'reservation' ){
		add_filter('user_can_richedit', 'disable_visual_editor_filter_in_reservation');
	}
}
function disable_visual_editor_filter_in_reservation(){
	return false;
}
add_action( 'load-post.php', 'disable_visual_editor_in_reservation' );
add_action( 'load-post-new.php', 'disable_visual_editor_in_reservation' );

さらにCSSで編集ボタンを非表示にします。
独自に追加したカレンダー部分のCSSをカスタマイズした際の記述に、下記の4行を追加します。

		$pt_reservation_css .= '#postdivrich { margin: -40px 0 20px; }';
		$pt_reservation_css .= '#wp-content-editor-tools, #ed_toolbar { display: none; }';
		$pt_reservation_css .= '#wp-content-wrap { padding-top: 5px !important; }';
		$pt_reservation_css .= '#content { margin-top: 0 !important; height: 200px !important; }';

CSSカスタマイズの記述の全容は次のようになります。

/* --------------------------------------------------
  reservationの編集画面のCSS設定
-------------------------------------------------- */
function reservation_output_css() {
	$pt = get_post_type();
	if ( $pt == 'reservation' ) {
		$pt_reservation_css  = '<style type="text/css">';
		/* コンテンツエディタのカスタマイズ */
		$pt_reservation_css .= '#postdivrich { margin: -40px 0 20px; }';
		$pt_reservation_css .= '#wp-content-editor-tools, #ed_toolbar { display: none; }';
		$pt_reservation_css .= '#wp-content-wrap { padding-top: 5px !important; }';
		$pt_reservation_css .= '#content { margin-top: 0 !important; height: 200px !important; }';
		/* カレンダー表示のカスタマイズ */
		$pt_reservation_css .= '#schedule .inside { text-align: center; }';
		$pt_reservation_css .= '#schedule table.calendar { border-collapse: collapse; border-spacing: 0; margin: 0 auto 20px; }';
		$pt_reservation_css .= '#schedule table.calendar caption { font-size: 150%; font-weight: bold; color: #999; padding: 5px 0; }';
		$pt_reservation_css .= '#schedule table.calendar tr th { border: solid 1px #ccc; padding: 3px; background: #666; color: #fff; }';
		$pt_reservation_css .= '#schedule table.calendar tr th.sun { background: #e66; }';
		$pt_reservation_css .= '#schedule table.calendar tr td { border: solid 1px #ccc; padding: 3px; text-align: left; }';
		$pt_reservation_css .= '#schedule table.calendar tr td.sun { background: #fee; }';
		$pt_reservation_css .= '#schedule table.calendar tr td.holiday { background: #fee; }';
		$pt_reservation_css .= '#schedule table.calendar tr td.today { background: #ffe; }';
		$pt_reservation_css .= '#schedule table.calendar tr td select { width: 4em; margin: 5px 10px; }';
		$pt_reservation_css .= '</style>';
		echo $pt_reservation_css;
	}
}
add_action('admin_head', 'reservation_output_css');

オマケ:カスタムフィールド数の上限は?

余談ですが、今回のカスタマイズで数ヶ月分の予定表つまりは大量のカスタムフィールド値を一記事内で処理させるにあたり、果たしてこんなに沢山の変数を一度に送っても大丈夫なものなのだろうか?という疑問がふと浮かんだので調べてみました。

結論を言うと、サーバーの max_input_vars の設定値がどうとか、 Advanced Custom Fields プラグインを使用した場合のカスタムフィールド数の上限が60〜80個だとか、やはり上限がありました。

今回は独自にカスタムフィールドを追加した分だけで、前月・当月・翌月・翌々月の4ヵ月分約120件の値を一度に処理させています。
max_input_vars のデフォルトの上限値1,000に対してはまだまだ十分余裕があるようなので、動作も問題なく安定していますが、カレンダーの月数をもっと増やす必要があったり、他のプラグインとの兼ね合いなどで、 max_input_vars の上限を超える可能性もあることを頭の隅に留めておくと、不具合が発生した際に参考になるかも知れません? 

(参考)Advanced Custom Fieldsのカスタムフィールドの登録上限が max_input_varsに影響する問題の対処方法
(参考)WordPressのカスタムメニューが82個以上登録できねーおらおらーって時の対処方法

PR

DO WP 管理人