渡米生活。(日記)

渡米生活。本家から切り離しました。あまり渡米生活に関係のないプログラムネタや音楽ネタなど。

Wordpressで特定のカテゴリ記事のみのイベントカレンダー(イベント名つき)を作成する

Wordpressでイベントカレンダーを表示するプラグインはたくさんあるのですが、多くがカスタム投稿を利用するタイプでして、フリーテーマを使っていたりすると具合が悪いケースがあります。

Wordpressでイベントの告知をするのに、私は通常の投稿記事を使っています。こうすると、綺麗なデザインのフリーテーマとほぼ衝突することなく使えて、イベント情報をスライド表示したりできるからです。
この部分を崩さないまま、イベントカレンダーを作る方法はないか、と探して、結局そこそこ簡単に実装できることがわかりました。
しかも、コードの部分はPHP Code For Posts などのプラグインを入れてそこに書きますので、テーマに手を加える必要がなく、着せ替えも思いのままです。

見た目は以下のスクリーンショットのような形になります。イベント名をクリックするとその記事に飛びます。
上の年月の横にある矢印(>)をクリックすると、翌月のカレンダーを表示します。
(<をクリックすると前月のカレンダーを表示しますが、この実装では本日以前のイベントは見えないようになっていますので、ただのカレンダーが表示されるだけです)

実装はこちらのページで見てください。


ただし、これをやるには、自分である程度のルールを作って記事を投稿する必要があります。

1)カレンダーに表示したい記事のカテゴリを設定し、イベント用の記事の名前の付け方にルールを設ける。

Wordpressの中でイベント告知を投稿記事を使って書く方法は、大きく分けて2つあります。
1つは投稿記事を未来のイベントの日付で作り、No Future Postなどのプラグインで未来の投稿も表示するやり方。これも悪くないのですが、投稿記事の投稿日がイベント日になってしまうため、スライダーでの表示順をいじるのが難しくなる、という問題があります。
多くのスライダーは新しい順に表示してしまうので、1ヶ月先のイベントの記事をスライダーのトップに表示したいのに、1年先のイベントが1番目になってしまう、といったような問題です。

そこで、私は現在、記事のタイトルそのものに日付を入れてしまう、という方法をとっています。これだと、投稿の日付は自由に設定できるので、そこをいじればスライダーの表示順を変えることができます。
ヘボいかな、と思いましたが、やってみると、スライダーでイベント日がタイトルの先頭に見えるのは、お客さんにもわかりやすくて、それほど悪くありません。

しかし、そのためには、記事のタイトルの付け方をあらかじめ決めておく必要があります。
この自前ルールは、あとでコードを書くときに必要になりますので、一度決めたら変えないこと。私はこんな感じにしました。

  1. イベント記事のタイトルは、必ず yyyy/mm/dd(またはyyyy/m/d)で始める
  2. そのあとに半角スペースをあけて、記事のタイトルを書く
  3. 記事タイトル中には半角スペースを含んではいけない。全角スペースはOK
  4. 数日にわたるイベントは、yyyy/mm/dd-dd とする。月をまたぐ場合は、yyyy/mm/dd-mm/dd、年をまたぐ場合はyyyy/mm/dd-yyyy/mm/ddとする。

あ、書いてて気づいた。今のプログラムだと月をまたいで数日にわたるイベントが対応できませんね。あとで直します。。変更しました。

いずれにしても、この方法の良いところは、自前で決めたルールでいくらでもあとで調整が可能なところです。別にタイトルをいじる方法ではなくても、カスタムフィールドの値を使うでもOK。カスタムフィールドに必要な情報を全部分類して入れておけば、あとでプログラムを書くときにかなり楽ができます。

ちなみに、カレンダーに表示するイベント記事用には、EVENTカテゴリ(別にどんな名前でも良い)を割り当てておけば、このカテゴリをスライダーにセットしてトップページにスライド表示できます。私はさらに、カレンダーには表示したいけど、トップスライダーには出したくないイベント用にSCHEDULEカテゴリも作りました。

2)終わったイベントを自動で非公開にするプラグイン(Enable Post Expiration)を入れる。

終わったイベントがいつまでも表示されているのはよくありませんので、私はPost Expiratorというプラグインを使っています。
これは、指定の日時がきたらイベントの公開状態を非公開や下書きなどに自動で変更してくれるプラグインです。「Enable Post Expiration」の項目にチェックを入れなければ適用されませんので、イベント情報記事以外が影響を受けることはありません。

このプラグインを入れたら、カレンダーに表示したい記事全ての「Enable Post Expiration」にチェックを入れ、取り下げる日時を設定します。私は単純に、イベント日当日の23時59分にセットしていますが、イベントが終わってもしばらくはカレンダーに表示しておきたい、という方はもう少し遅らせてもよいかと思います。

Enebale Post Expirationについては、以下のリンクで解説されています。

※ちなみに、このプラグインの使用は必須ではありません。要はWP_Queryでカレンダーに表示する記事を選べれば良いので、単純にあるカテゴリの記事だけ表示して、取り下げるのは自分でやる、という方はそれでも大丈夫です。また、自分でカスタムフィールドなどに別の日付を設定して、それを使うでもOK。

3)PHP Code For Posts など、管理画面からPHPのショートコードを作成できるプラグインを入れる。

カレンダーを作る関数はテーマの中のfunction.phpに書くのが定石ですが、それではテーマの変更が必要になります。テーマがアップデートしたら変更も消えてしまってよくありません。
別にファイルを作り、プラグインとして登録する手もありますが、このカレンダーはかなり使用方法に特化しているので、そこまで一般化して他のサイトで使えるようにする手間が面倒です。

というわけで、管理画面からPHPプログラムを作成しショートコードを作ってくれるプラグインを入れます。別にショートコードを作ってくれるプラグインならなんでもOKです。

PHP Code For Posts については、以下のリンクで解説されています。

4)ショートコードを作成

カレンダーの作成ですが、カレンダーの表示部分についてはほとんど以下のサイトで公開されていたものの丸写しです(使わせていただいてありがとうございます!)

動画で学ぶプログラミング・アプリ開発 Coding with Sara

変更したのは以下の2点のみ。

  1. カレンダーを月曜始まりに変更
  2. カレンダーに紐付ける記事を選ぶ部分

このカレンダーの例題は日曜始まりですが、イベントカレンダーは月曜始まりの方が都合がいいので、以下の1行を加えました。

//曜日を月曜始まりに変更 
$youbi = ($youbi == 0) ? 6 : $youbi - 1;

これは、その前のdate関数で月の1日が日曜なら0、月曜なら1…、土曜なら6の数字が返ってくるので、月曜なら0、火曜なら1、…日曜なら6、に変更するためのものです。こうすると、(この例題プログラムでは)月曜から日曜までのカレンダーを作ってくれます。

5)カレンダーに表示するイベントを選ぶ。

月や年をまたぐイベントに対応し、かつ同日に複数イベントが入った場合にも全部表示するようにしたら、結構面倒になりました…。
具体的には、さきにカレンダーに表示するイベントを選んで一度バッファにFILLしておき、あとでカレンダーに書く、という作業をしています。

このため、指定の範囲内の日付($startdayから$enddayまで)に同じイベントをフィルする関数をつくりました。
$databufは3次元の配列です。

function mycalendar_fillevents($databuf, $startday, $endday, $title, $url) {
	if ($startday > $endday) {
		print 'error, startday > endday <br>';
	}
	$curday = $startday;
	for ($i = 0; $i < $endday - $startday + 1; $i++) {
		$curday = $startday + $i; // カレンダーの日付
		$countid = count($databuf[$curday]); // 同日に複数イベントが入った場合のID
		$databuf[$curday][$countid]["title"] = $title;
		$databuf[$curday][$countid]["url"] = $url;
	}
	return $databuf;
}

さらに、以下の部分でイベントの抽出をしています。
何をやっているかはコメントを見てください。

//
// イベントを抽出するクエリを作成。
// 下の例題は、記事の種類は「投稿」、カテゴリーはeventsかschedule、
// 抽出した結果をソートするのに、Post Expiratorの取り下げ日時を
// 使っている。Post Expiratorが使うカスタムフィールドのキー値が
// _expiration-dateです。これを、meta_value_num、つまり数値として
// 解釈し、ASC(昇順)でソートしろ、ということ。
//
// これは私がPost Expiratorの取り下げ日時をイベント当日
// にしているためで、そうでない場合は別にカスタムフィールドに値を
// 入力してコントロールした方が良いと思います。
// 一番単純な方法は、yyyymmddの書式で_event_dateとか適当なキー値で
// カスタムフィールドに値を入力しておけば、ソートも楽になるかと。
//
//
// extract event list
//
$args = array(
	'post_type'  => 'post',
	'category_name' => 'events,schedule',
	'meta_key'   => '_expiration-date',
	'orderby'    => 'meta_value_num',
	'order'      => 'ASC',
	'posts_per_page' => -1	
	);

// The Query
$the_query = new WP_Query( $args );

// イベント日とそのイベント名、対応する記事へのリンクをおさめる配列を用意。
$databuf = array();

// 選んだ投稿をループしながら、必要な情報を保存。
if ( $the_query->have_posts() ) {
	
	while ( $the_query->have_posts() ) {
		$the_query->the_post();
		$url = get_the_permalink();

		global $post, $id;
		// 非公開記事には頭に「非公開」とついてしまうので、それを削除。テストは非公開記事で行える。
		$articletitle = trim(str_replace('非公開:','', get_the_title()));
		// 半角スペースでタイトルを分割。前半が日付情報、後半がイベントタイトルになる。
		$splits = explode(' ', $articletitle);
		$title = $splits[1];
		
		$datearray = explode('-', $splits[0]);
		
		$startymd = explode('/', $datearray[0]);
		$startyear = $startymd[0];
		$startmonth = $startymd[1];
		$startday = $startymd[2];
		$theday = new DateTime();
		$theday->setDate((int)$startyear, (int)$startmonth, (int)$startday);
		$startym = $theday->format('Ym');
		
		// $startymが現在の表示年月より大きい場合は、残りのイベントも全て同条件で現在のカレンダーの表示範囲外。
		// なので、すぐにloopを出る。
		if ($startym > $page_ym) {
			break;
		}
			
		if (count($datearray) == 1 && $startym == $page_ym) {
			// yyyy/mm/ddのフォーマット。
			// 1日のみのイベントなので、現在のカレンダーの年月に合致した場合のみデータベースにFILLする
			$databuf = mycalendar_fillevents($databuf, $startday, $startday, $title, $url);
			
		} else if (count($datearray) > 1) {
			// 複数日イベント
			// yyyy/mm/dd-dd、yyyy/mm/dd-mm/dd、 yyyy/mm/dd-yyyy/mm/dd のいずれかのフォーマット
			// $datearray[0] = yyyy/mm/dd
			// $datearray[1] は dd, mm/dd, yyyy/mm/dd のいずれか。
			
			$endymd = explode('/', $datearray[1]);
			$endymdcounts = count($endymd);
			
			// try simple case first.
			if ($endymdcounts == 1) {
				// イベント開始日と終了日が同じ月。現在の表示カレンダーの年月のものだけFILLする
				// フォーマットは yyyy/mm/dd-dd.
				if ($page_ym != $startym) {
					// month doesn't match through event period. go to next post.
					continue;
				}
				$endday = $endymd[0];
				$databuf = mycalendar_fillevents($databuf, $startday, $endday, $title, $url);
				
			} else {
				// イベント終了日が開始日と同じ年月にない場合は、イベント期間中のうち表示中のカレンダー年月のみFILLする
				// フォーマットは yyyy/mm/dd-mm/dd または yyyy/mm/dd-yyyy/mm/dd.
				$endyear = $startyear;
				$endmonth = $startmonth;
				$endday = $startday;
				if ($endymdcounts == 2) {
					$endmonth = $endymd[0];
					$endday = $endymd[1];
				} else if ($endymdcounts == 3) {
					$endyear = $endymd[0];
					$endmonth = $endymd[1];
					$endday = $endymd[2];	
				}
								
				$theday->setDate((int)$endyear, (int)$endmonth, (int)$endday);
				$endym =  $theday->format('Ym');
				
				if ($page_ym > $endym) {
					// イベント終了日が現在の表示年月より前の場合は、このイベントをスキップ
					continue;
				} 
				
				$curym = $startym;
				$curyear = $startyear;
				$curmonth = $startmonth;
				// 開始日付は1日かイベント開始日のどちらかになる
				$curday = ($page_ym == $startym) ? $startday : 1;
				
				// 開始年月から終了年月までループ
				while ($curym <= $page_ym) {
					
					if ($page_ym == $curym) {
						// カレンダーの表示中年月と一致したのでFILLする。
						// update start day and end day.
						$startday = $curday;
						$last_day = $day_count;
						// 開始日付は月の最後の日かイベント終了日のどちらか
						$endday = ($curym == $endym) ? $endday : $last_day;
						$databuf = mycalendar_fillevents($databuf, $startday, $endday, $title, $url);
					} 
					
					//次月を取得
					if ($curmonth == 12) {
						//次の月は翌年1月
						$curyear += 1;
						$curmonth = 1;
					} else {
						$curmonth += 1;
					}
					$theday->setDate((int)$curyear, (int)$curmonth, (int)$curday);
					$curym = $theday->format('Ym');
				}
			}
		}
	}
}

//最後にクエリのリセットを忘れずに
wp_reset_postdata();


これら組み合わせたプログラム全体は以下のようになります。

<?php 

date_default_timezone_set('Asia/Tokyo');

//前月と次月を表示する際は、GETで値を受け取る
if (isset($_GET['ym'])) {
	$page_ym = $_GET['ym'];

}else{
	$page_ym = date('Ym');
}


//形式チェック
$timestamp = strtotime($page_ym . "01"); // yyyymmddの書式にする


if ($timestamp === false) {
	$timestamp = time();
}


//今日の日付
$today = date('Ymd',time());
$thismonth = date('n', $timestamp);
$thisyear = date('Y', $timestamp);


//HTML表示用の日付
$html_title = date('Y年 n月',$timestamp);

//前月と次月を取得  mktime(hour, minute, second, month, day, year)
$prev = date('Ym', mktime(0, 0, 0, date('m', $timestamp)-1, 1 , date("Y", $timestamp)));
$next = date('Ym', mktime(0, 0, 0, date('m', $timestamp)+1, 1, date("Y", $timestamp)));

//対象月は何日あるか
$day_count = date('t', $timestamp);

//1日は何曜日か 0:日 1:月 .... 6:土
$youbi = date('w', mktime(0, 0, 0,date('m', $timestamp), 1, date("Y", $timestamp)));

//曜日を月曜始まりに変更 
$youbi = ($youbi == 0) ? 6 : $youbi - 1;

function mycalendar_fillevents($databuf, $startday, $endday, $title, $url) {
	if ($startday > $endday) {
		print 'error, startday > endday <br>';
	}
	$curday = $startday;
	for ($i = 0; $i < $endday - $startday + 1; $i++) {
		$curday = $startday + $i; // カレンダーの日付
		$countid = count($databuf[$curday]); // 同日に複数イベントが入った場合のID
		$databuf[$curday][$countid]["title"] = $title;
		$databuf[$curday][$countid]["url"] = $url;
	}
	return $databuf;
}

//
// extract event list
//
$args = array(
	'post_type'  => 'post',
	'category_name' => 'events,schedule',
	'meta_key'   => '_expiration-date',
	'orderby'    => 'meta_value_num',
	'order'      => 'ASC',
	'posts_per_page' => -1	
	);

// The Query
$the_query = new WP_Query( $args );

// this buffer will be 3dim array
$databuf = array();

// The Loop
if ( $the_query->have_posts() ) {
	
	while ( $the_query->have_posts() ) {
		$the_query->the_post();
		$url = get_the_permalink();

		global $post, $id;
		$articletitle = trim(str_replace('非公開:','', get_the_title()));
		$splits = explode(' ', $articletitle);
		$title = $splits[1];
		
		$datearray = explode('-', $splits[0]);
		
		$startymd = explode('/', $datearray[0]);
		$startyear = $startymd[0];
		$startmonth = $startymd[1];
		$startday = $startymd[2];
		$theday = new DateTime();
		$theday->setDate((int)$startyear, (int)$startmonth, (int)$startday);
		$startym = $theday->format('Ym');
		
		// $startymが現在の表示年月より大きい場合は、残りのイベントも全て同条件で
		// 現在のカレンダーの表示範囲外なのでloopを出る
		if ($startym > $page_ym) {
			break;
		}
			
		if (count($datearray) == 1 && $startym == $page_ym) {
			// 1日のみのイベントなので、現在のカレンダーの年月に合致した場合のみデータベースにFILLする
			// single day event. yyyy/mm/dd
			// year and month matched. fill the event to the buffer.
			$databuf = mycalendar_fillevents($databuf, $startday, $startday, $title, $url);
			
		} else if (count($datearray) > 1) {
			// 複数日イベント
			// multiple days event.
			// date format is yyyy/mm/dd-dd or yyyy/mm/dd-mm/dd or yyyy/mm/dd-yyyy/mm/dd
			// $dataarray[0] = yyyy/mm/dd
			// $dataarray[1] could be dd, mm/dd, yyyy/mm/dd
			
			$endymd = explode('/', $datearray[1]);
			$endymdcounts = count($endymd);
			
			// try simple case first.
			if ($endymdcounts == 1) {
				// イベント開始日と終了日が同じ月。現在の表示カレンダーの年月のものだけFILLする
				// the event will be held within a month.
				// date format is yyyy/mm/dd-dd.
				if ($page_ym != $startym) {
					// month doesn't match through event period. go to next post.
					continue;
				}
				$endday = $endymd[0];
				$databuf = mycalendar_fillevents($databuf, $startday, $endday, $title, $url);
				
			} else {
				// イベント終了日が開始日と同じ年月にない場合は、イベント期間中のうち表示中のカレンダーの年月のみFILLする
				// the event will be held through at least two different months.
				// date format is yyyy/mm/dd-mm/dd or yyyy/mm/dd-yyyy/mm/dd.
				$endyear = $startyear;
				$endmonth = $startmonth;
				$endday = $startday;
				if ($endymdcounts == 2) {
					$endmonth = $endymd[0];
					$endday = $endymd[1];
				} else if ($endymdcounts == 3) {
					$endyear = $endymd[0];
					$endmonth = $endymd[1];
					$endday = $endymd[2];	
				}
								
				$theday->setDate((int)$endyear, (int)$endmonth, (int)$endday);
				$endym =  $theday->format('Ym');
				
				if ($page_ym > $endym) {
					// イベント終了日が現在の表示年月より前の場合はこのイベントをスキップ
					// current calendar is out of bound of this event. go to next post.
					continue;
				} 
				
				$curym = $startym;
				$curyear = $startyear;
				$curmonth = $startmonth;
				// 開始日付は1日かイベント開始日のどちらか
				$curday = ($page_ym == $startym) ? $startday : 1;
				
				// 開始年月から終了年月までループ
				while ($curym <= $page_ym) {
					
					if ($page_ym == $curym) {
						// OK, fill calendar for this month.
						// update start day and end day.
						$startday = $curday;
						$last_day = $day_count;
						// 開始日付は月の最後の日かイベント終了日のどちらか
						$endday = ($curym == $endym) ? $endday : $last_day;
						$databuf = mycalendar_fillevents($databuf, $startday, $endday, $title, $url);
					} 
					
					//次月を取得
					if ($curmonth == 12) {
						//次の月は翌年1月
						$curyear += 1;
						$curmonth = 1;
					} else {
						$curmonth += 1;
					}
					$theday->setDate((int)$curyear, (int)$curmonth, (int)$curday);
					$curym = $theday->format('Ym');
				}
			}
		}
	}
}

/* Restore original Post Data */
wp_reset_postdata();

//カレンダー作成準備
$weeks = array();
$week = '';

//空白を追加
//例) 1日が水曜日だった場合、カレンダーの月曜日から火曜日に空白を入れる
$week .= str_repeat('<td></td>', $youbi);


for ($day=1; $day <= $day_count; $day++, $youbi++){
	if (array_key_exists($day, $databuf)) {
		$week .= '<td>' . $day;
		for ($i = 0; $i < count($databuf[$day]); $i++) { 
			$week .= '<br><a href="' . $databuf[$day][$i]['url'] . '"><small>'.$databuf[$day][$i]['title'].'</small></a>';
		}	
	} else {
		$week .= '<td>'. $day ;
	}
	$week .= '</td>';
	
	//曜日が日曜日、または全ての日付のtdの作成が終わったら
	if($youbi % 7 == 6 OR $day == $day_count){

		//全ての日付のtdの作成が終わったら、残りに空白を追加
		if($day == $day_count){
			$week .= str_repeat('<td></td>', 6 - ($youbi % 7));
		}

		//1週間分のtdをまとめた$weekを$weeks配列に入れる。
		$weeks[] = '<tr>'.$week.'</tr>';
		//新しい週を作成するために$weekを空にする。
		$week = '';
	}

}
?>
	<h4><a href="?ym=<?php echo $prev;?>">&lt;</a>&nbsp;&nbsp;<?php echo $html_title;?>&nbsp;&nbsp;<a href="?ym=<?php echo $next;?>">&gt;</a></h4>
	<br>
	<table id="wp-calendar">
		<thead>
		<tr>
			<th width="14%"></th>
			<th width="14%"></th>
			<th width="14%"></th>
			<th width="14%"></th>
			<th width="14%"></th>
			<th width="14%"></th>
			<th width="14%"></th>
		</tr>
		</thead>
		<tbody>
	 	<?php
		 	//カレンダー表示
		 	foreach ($weeks as $week){
				echo $week;
			}
		?>
		</tbody>
	</table>

カレンダー用のテーブルに、id="wp-calendar"を設定してますが、もちろん別のIDでかまいません。
なぜこうしたかというと、以下のページで紹介されていたカレンダーがカッコよくて、そのままcssを拝借したかったからです。
テーマによっては、自前CSSの追加を許してくれるものがあるので、、、

WordPressでイベントカレンダー wpxtreme


このコードをPHP Code For Postsにセットし、ショートコードを作成します。
あとは、投稿なりページなりでショートコードを入力すれば、その場所にカレンダーが表示されます。

テキストウィジェットでも表示できますが、このカレンダーはイベント名を表示するので、サイドバーなどでは縦に伸びてしまって見づらくなります。
ウィジェットで小さく表示するには、別にショートコードを作成した方がよさそうです。

というわけで、この記事はほとんど人様のコードを拝借して書いたものです。
参考にさせていただいた皆様に感謝!

閲覧期限つきPDFファイルの作成(Adobe Acrobat)

Adobe Acrobatで閲覧期限つきのPDFファイルを作成する方法。
諸処で解説されているけど、自分用メモ。

Appleのプレビューで見ると、期限にかかわらず白くなって見えなくなります。
配布時に、Adobe Acrobatで開くように注意書きが必要かも。

1)Adobe Acrobatで文書を開く。

2)フォームから「編集」を選ぶ。フィールドフォームを検出するかきいてくるので、「いいえ」を選ぶ。(新規作成にするとフォームを自動解析しようとして面倒なことになるので)

3)テキストフィールドを隠したい部分に新規作成、隠す部分を覆うようにして設定。テキストフィールド名はデフォルトのText1でよい。複数ページを隠す場合は、全部同じフィールド名にする。必須フィールドにチェック。

4)テキストフィールドのプロパティを開き,「一般」タブを開き、表示と印刷で「表示」に設定する。

5)「表示方法」タブから,「塗りつぶしの色」で,「白色」を選ぶ。(透明チェックボックスは外す)

6)全部設定したら、「フォームの編集を綴じる」で編集終了。

7)ページサムネールから、フォームを加えたページを全部選び、右クリックで「ページのプロパティ」を選択。「アクション」を選び、トリガーは「ページを開く」、アクションの選択から「JavaScriptを実行」を選んで「追加」ボタンを押す。

窓が開くので、そこに以下のスクリプトをペースト。

var myDate = "2019/12/01"; //有効期限
var fieldprefix = "Text1"; // テキストフィールド名の接頭語
var message = "This document is expired.";   //警告メッセージ

var d = new Date();
var s = "yyyy/mm/dd";

if (myDate < util.printd(s, d)) {
  // 有効期限を過ぎている場合は白紙表示
  var myField = this.getField(fieldprefix);
  myField.display = display.visible;
  app.alert(message);

} else {
  // 有効期限内ならフィールドを表示しない
  var myField = this.getField(fieldprefix);
  myField.display = display.hidden;
}

8)ツールからJavaScriptを選び、「文書のアクションを設定」を選択
「文書を保存する」を選び「編集」ボタンを押す。以下のJavascriptを設定。

var fieldprefix = "Text1"; // テキストフィールド名の接頭語
var myField = this.getField(fieldprefix);
myField.display = display.visible;   //テキストフィールド表示

9)ツールからJavaScriptを選び、「文書のアクションを設定」を選択
「文書を保存した」を選び、以下のJavascriptを設定。

var fieldprefix = "Text1"; // テキストフィールド名の接頭語
var myField = this.getField(fieldprefix);
myField.display = display.visible;   //テキストフィールド表示

10)文書のプロパティから「セキュリティ」を選び、パスワードのセキュリティをかける。


以上、以下のページを参考にさせていただきました。
http://www.geocities.jp/habacchi1969/down/RIGHT.HTM

Contact form7 + WP-Mail-SMTP + gmailの組み合わせがいつの間にか動かなくなっていた件の対策

WordpressのメールフォームにContact Form 7を使っているのですが、海外で運用しているサーバで問題が発生。

まあ、安いホスティングサーバを使っているので、web サーバにメールサーバが利用可能なのは良いのだけど、メールサーバがスパムのブラックリストに登録されてしまっていて、折角のドメインメールがほとんどの受信者に跳ね返されてしまうのです。
つまり、ドメインメール持っていても、受信専用。送信にはほとんど使えない、という事態に。

というわけで、Contact Form7から送信するアドレスも、それ専用に取得したgmailを使うことになりました。

そのままでは使えないのだけど、wp_mail()関数の振る舞いを変えるプラグインはいくつか出ていて、それを使えば別のサーバからメールを送信させることができます。

とりあえず、うちでは、WP-Mail-SMTPを使っていました。
これに、gmailのログイン情報を登録して、MailerオプションをUse the PHP mail() function to send emailsにしておけば、普通に使えていたのですが……

2014/8/15のgmailSMTPセキュリティポリシーの改訂の影響か、いつのまにかメール送信で失敗するようになっていました。
もう多分何ヶ月もこの状態だったのでは。。

というわけで、以下対処方法。

1)WP-Mail-SMTPのMailerオプションを、Send all WordPress emails via SMTPに変更。
2)From Emailをgmailのアドレスにする。
3)この状態で、一度テストメールを配信。そうすると、テストは失敗するが、gmailから「Google アカウント: ログイン試行をブロックしました」というタイトルのメールが送られてくる。
4)メールを開き、中に書かれているリンク(https://www.google.com/settings/security/lesssecureapps)をクリックし、WP-Mail-SMTPで使うgmailのアカウントでログイン
5)「安全性の低いアプリのアクセス」を「有効」に設定
6)もう一回テストメールを送信して、動作確認

これで、なんとか元通りに動きました。
しかし、gmailが更にセキュリティ制限を強くしてきたら、どうするかな。。コレ。。

※なお、これでセキュリティが多少甘くなるので、パスワードは乱数エンジンで作成するなどして、強固なものに変えておくこと。

子テーマで親テーマのstyle.cssが読み込まれない

久々にWordpressで子テーマを作ったら、style.cssを置くだけでは親テーマのCSSファイルが読み込まれず、文字だけのページになってしまい、暫くハマりました。

以前は、

@charset "utf-8";
/*
 Theme Name:   Academy
 Description:  Twenty Fourteen Child Theme
 Template:     twentyfourteen
 Version:      1.0.0
*/

@import url(“../twentyfourteen/style.css”);

なんてstyle.cssに書くだけで、親テーマとそっくり同じ子テーマ(Academy)の出来上がりだったのですが、最新のWPでは@importは使うな、という仕様になっている模様。

http://codex.wordpress.org/Child_Themes

日本語コーデックスはまだ古い記述なので、ハマった人もいるかも。

正しくは、子テーマ側にfunctions.phpを作り、以下の行を書き込め、ということです。

<?php

add_action( 'wp_enqueue_scripts', 'enqueue_parent_theme_style' );
function enqueue_parent_theme_style() {
    wp_enqueue_style( 'parent-style', get_template_directory_uri().'/style.css' );
}


(他にも関数があればここに書く)

?>

というわけで、親テーマとそっくり同じ子テーマを作るには、最低2つのファイルがいる模様です。
勿論、親テーマのstyle.cssの内容をそっくりそのまま子テーマのstyle.cssにコピーしてしまえば、インポートする必要はないですが。

Scientific Linux 6.3 またはCentOS7インストールログ3:software raid の設定とnfs mount

Linux設定の続き。

HDD5台でsoftware raid 5を組む

まず、5台のHDDをsoftware raid用にフォーマットする。fdiskを使う。
最初の1台目のデバイスが/dev/sdbとする。

fdisk /dev/sdb

...何か英語でいってくるかも

コマンド (m でヘルプ): m
コマンドの動作
   a   ブート可能フラグをつける
   b   bsd ディスクラベルを編集する
   c   dos 互換フラグをつける
   d   領域を削除する
   l   既知の領域タイプをリスト表示する
   m   このメニューを表示する
   n   新たに領域を作成する
   o   新たに空の DOS 領域テーブルを作成する
   p   領域テーブルを表示する
   q   変更を保存せずに終了する
   s   空の Sun ディスクラベルを作成する
   t   領域のシステム ID を変更する
   u   表示/項目ユニットを変更する
   v   領域テーブルを照合する
   w   テーブルをディスクに書き込み、終了する
   x   特別な機能 (エキスパート専用)

コマンド (m でヘルプ): p
領域番号 (1-4): 1
最初 シリンダ (1-17365, default 1): < enterを押す
Using default value 1
終点 シリンダ または +サイズ または +サイズM または +サイズK (1-17365, default 17365): < enterを押す
Using default value 17365

コマンド (m でヘルプ): t
Selected partition 1
16進数コード (L コマンドでコードリスト表示): fd
領域のシステムタイプを 1 から fd (Linux raid 自動検出) に変更しました

コマンド (m でヘルプ): w
領域テーブルは交換されました!

ioctl() を呼び出して領域テーブルを再読込みします。
ディスクを同期させます。

大体こんな感じで完了。同じ作業を他の4つのHDD(sdc, sdd, sde, sdf)でも繰り返す。

mdadmを使ってRaid 5 にまとめ、マウントする

mdadm -C /dev/md0 -l5 -n5 -f /dev/sd[bcdef]1

とやると、デバイス名/dev/md0 で1つのディスクとして見える。

ところで、このRAIDの設定というのは、一度書き込まれると残っているものらしい。
システムをクリーンインストールでアップデートして、RAID5のデータも書き直しかな、と思ったが、元と同じ配置でHDDをセットしたら、何もしなくても勝手に1つのデバイスとして認識した。ただし、デバイス名は何故か/dev/md127になっていた。

ここまでできたら、自動マウントの設定。/dataにマウントするものとする。
/etc/mdadm.confというファイルを作り、以下の情報を記入。

# vi /etc/mdadm.conf
DEVICE /dev/sd[bcdef]1
ARRAY /dev/md0 devices=/dev/sdb1,/dev/sdc1,/dev/sdd1,/dev/sde1,/dev/sdf1

更に、/etc/fstabに以下の情報を追加。

# vi /etc/fstab
/dev/md0     /data     ext4     defaults   1   2

初回は自分の手でマウントする。

# mkdir /data
# mount -a

ファイルサーバでnfsサーバの設定。

rootで作業。
/etc/exportsを変更。以下の行を加えると、ファイルサーバ上の/dataディレクトリにアクセス可能になる。(XXX.XX.XX.XXXはマウントを許すIP)
IPとカッコの間にスペースをあけないこと(違う意味になる)

/data XXX.XX.XX.XXX(rw,sync) XXX.XXX.XX.XXX(rw,sync)

他のマシンから見たときにrootによるアクセスを許す場合は、no_root_squashオプションをつける。

見せる相手が沢山の場合は、たとえば

/data 192.168.1.0/24(rw,sync,no_root_squash)

のようにも書ける。
ただしこれは、自分の(ローカル)IPが192.168.1.1等で、ぶら下がっているマシンが192.168.1.2, 192.168.1.3...といった場合。


サービスの再起動

# service nfs restart

クライアント側の設定

autofsをインストール。2行目は再起動後に勝手に立ち上がるようにする設定。

# yum install autofs
#systemctl enable --now autofs

Scientific Linux 6のみ)
nfs4を動かすと、何をやっても(no_all_squashをサーバー側でたてていても)マウント側でnobodyの持ち物になってしまうので、nfs3を動かす設定にする。
(nfs4はuidとは異なったファイルで所有者を判断しているらしいが、その設定方法がわからない。ndf3でも、サーバーごとにuidやgidが異なっていたら正しいユーザの持ち物としてマウントされないので注意。)

CentOS7ではこのバグは直っている模様なので、変更の必要なし。

# vi /etc/nfsmount.conf

Defaultvers=4 のコメントアウトを外し、
Defaultvers=3  に変更

autofsのダイレクトマップを使う。

/etc/auto.masterに以下の行を追加

ScientificLinux 6

# vi /etc/auto.master

# mount data dir
/-       /etc/auto.data    -rw,intr

# 以下の行はコメントアウト(NIS, NIS+を使わない場合)
#+auto.master

CentOC7

# vi /etc/auto.master

# mount data dir
/-       /etc/auto.data


/etc/auto.dataを作成し、以下の1行を記入して保存。

# vi /etc/auto.data
/data -ftype=nfs,rw 192.168.1.1:/data

192.168.1.1はこの場合ファイルサーバのIP.

最後にデーモンを再起動。

ScientificLinux 6

# /etc/rc.d/init.d/autofs restart

CentOS7

# systemctl start autofs

以上

qTranslateをとりあえずwordpress 3.9対応にするパッチ

wordpressの多言語プラグイン、やはりqTranslateが圧倒的に便利なので、ほとんどの管理サイトで使っています。

以前はwpの新バージョンが出て2週間ほどですぐにアップデートが出ていたのですが、最近開発者様がお忙しいのか、なかなかwp3.9対応版が出ません。

で、無理矢理wordpress 3.9.1 でqTranslate 2.5.39を使うと、編集画面でこんなエラーがでちゃうんですね。。


The qTranslate Editor has disabled itself because it hasn't been tested with your Wordpress version yet. This is done to prevent Wordpress from malfunctioning. You can reenable it by clicking here (may cause data loss! Use at own risk!). To remove this message permanently, please update qTranslate to the corresponding version.


バージョンが合わないからエディタはオフにした、もう一回オンにしてもいいけど、データ消えるかもしれないよ、やるなら自己責任で!
といったところでしょうか。

無理矢理エディタ使ってデータ消されてはかなわんので、まあ、エディタは別に使えなくてもいいか、と思っていたら、表示の方にもこんなエラーがでてしまいました。

PHP Catchable fatal error: Object of class WP_Post could not be converted to string in ../wp-content/plugins/qtranslate/qtranslate_core.php on line 455

さすがにこれはまずいので、どうすべ、と解決策を探したらこんなのがでてきました。

Fix qTranslate with WordPress 3.9

要するに、qtranslate_core.phpのqtranslate_core.phpのqtrans_dateFromPostForCurrentLanguage関数を以下のように変更してしまえば良い、ということらしい。

-function qtrans_dateFromPostForCurrentLanguage($old_date, $format ='', $before = '', $after = '') {
+function qtrans_dateFromPostForCurrentLanguage($old_date, $format ='') {
  global $post;
- return qtrans_strftime(qtrans_convertDateFormat($format), mysql2date('U',$post->post_date), $old_date, $before, $after);
+ return qtrans_strftime(qtrans_convertDateFormat($format), mysql2date('U',$post->post_date), $old_date);
}

先頭に-が書いてあるラインがもとのコード、+が書いてあるラインが変更。
つまり、-のラインを+のラインで置き換えれば良い、ということです。

WP3.9からget_the_date関数の引数が変わっちゃったのが問題で、qtrans_dateFromPostForCurrentLanguage関数はフックとして使われているのだけど、3.9以降はオプション引数ひとつしか取れないんだそうな。

これで、表示の方の問題は治ります。
エディタをオンにしても安全かどうかは知りませんので、トライする方は、バックアップをとった上で自己責任で♪


……とここまでやったら、既にqTranslationにパッチをあてたmqTranslationなるものが出ているらしいと判明(笑)

Wordpress多言語化プラグインmqTranslate

mqTranslateをインストールして、qTranslateを無効化し、mqTranslateの言語設定をして有効化するだけで良いらしい。
(Language Switcherのウィジェットを使っている場合は、その設定も必要)


早速入れてみたら、エディタのバグも治ってました。
というわけで、一番簡単なのは、本家qTranslateのアップデートが出るまで、このプラグインを使うことの模様。
なんだかタイトルと違った結論になってしまった。。(笑)

Scientific Linux 6.3またはCentOS7インストールログ4:Condorのインストール

ジョブ管理システムのCondorをインストール。
面倒なので全部rootで作業。
「condorのコンパイル」までは共用ディスクの上でやっても良い(ただしクラスタのシステムが全部同じ場合)。

user condorを作成.ログインを禁止するため、invalidなシェルを指定。

$ su
# groupadd -g 1000 condor
# useradd -u 1000 -g condor condor
# usermod -s /bin/false condor

足りないファイルをとってくる

必要なファイルは以下を参照。
https://htcondor-wiki.cs.wisc.edu/index.cgi/wiki?p=BuildingHtcondorOnUnix

# yum install uuid-devel
# yum install uuid-c++-devel
# yum install pcre-devel
# yum install libuuid-devel
# yum install libtool-ltdl-devel
# yum install glibc-static
# yum install expat-devel.x86_64

cmakeは2.8以上でないと駄目らしいので、別にとってきてインストール。CentOS7は2.8以上が最初から入っているので必要ない。

# wget http://koji-hub.batlab.org/mnt/koji/packages/cmake/2.8.4/2.osg.el6/x86_64/cmake-2.8.4-2.osg.el6.x86_64.rpm

# rpm -ivh cmake-2.8.4-2.osg.el6.x86_64.rpm

condorのインストール

最近はyumでインストールできる。
https://research.cs.wisc.edu/htcondor/instructions/

# yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
# yum install -y https://research.cs.wisc.edu/htcondor/repo/8.8/el7/release/htcondor-release-8.8-1.el7.noarch.rpm
# yum install -y condor

yumでインストールすると、/usrの下にインストールされる。設定ファイルなどは/etc/condorの下にかたまるので、そこで編集する。

設定ファイルの編集

設定ファイルは、デフォルトでは/etc/condorの下に置かれる。
以下を/etc/condor/condor_config に記入。

CONDORデーモンの起動

# systemctl start condor 
# systemctl enable condor 

2行目は、再起動後に自動的にCONDORデーモンが走るようにする設定。
走ったかどうかは以下のコマンドで確認。

# ps aux | grep condor

condor     10885  0.4  0.0  70356  6520 ?        Ss   19:28   0:00 /usr/sbin/condor_master -f
root       10926  1.7  0.0  26480  4760 ?        S    19:28   0:00 condor_procd -A /var/run/condor/procd_pipe -L /var/log/condor/ProcLog -R 1000000 -S 60 -C 1000
condor     10927  0.4  0.0  45636  5528 ?        Ss   19:28   0:00 condor_shared_port -f
condor     10928  0.7  0.0  46440  6108 ?        Ss   19:28   0:00 condor_collector -f
condor     10931  1.0  0.0  46140  5924 ?        Ss   19:28   0:00 condor_negotiator -f
condor     10932  1.0  0.0  47524  6932 ?        Ss   19:28   0:00 condor_schedd -f
root       10981  0.0  0.0 112816   976 pts/1    R+   19:28   0:00 grep --color=auto condor

こんな感じでたくさん出てくればよい。

yumが使えなかった過去のインストールログ

standard makeはとことんコケたので、もうuwセットでインストールすることにする。

# cd <somewhere>
# tar -zxvf condor_src-8.1.4-all-all.tar.gz 
# cd condor-8.1.4
# ./configure_uw
# make install

これで、condor-8.1.4/release_dirができる。

condorのインストール

condorのインストールは、release_dirの下にバイナリをどこか別の場所にコピーする作業になるが、スクリプトを動かすには、あらかじめcondor_configファイルとcondor_config.localファイルが用意されている必要があるらしい。

というわけで、この2つを手動で作成。
ここでは、condor_configファイルは/home/condor/の下(defaultで探してくれるパスのうちの一つ。CONDOR_CONFIG環境変数で指定する場合はどこに置いてもよい)、condor_config.localファイルは/opt/condor/localの下に置くものとする。

※/opt/condor/localの下には一時ファイルが置かれるので、/optがネットワーク上にある場合はここにlocalディレクトリを置くのはよくない。
できれば、別パーティション上にある/varや/scratchのような場所がベストだが、うちの環境では/varなどに別パーティションを切っていないので、とりあえずoptの下に固めることにした。

更に、インストール先は/opt/condor/8.1.4とし、コレを/opt/condor/proにリンクする。

# mkdir -p /opt/condor/8.1.4
# pushd /opt/condor
# chmod 777 8.1.4
# ln -s 8.1.4 pro
# popd
# cp condor-8.1.4/release_dir/etc/examples/condor_config /home/condor/
# vi /home/condor/condor_config

# 以下のパラメタを書き換える
# your.condor.host.jpはセントラルマネージャーになるマシンのアドレスを入力
CONDOR_HOST    = your.condor.host.jp
RELEASE_DIR    = /opt/condor/pro
LOCAL_DIR      = /opt/condor/local
ALLOW_WRITE    = local01.my.domain, local02.my.domain, local03.my.domain

ALLOW_WRITEにはクラスタに参加する全てのマシンのアドレスが書かれている必要がある。
これを忘れるとcondor_status等のコマンドで全てのノードが見えない。
いちいち書くのが面倒なら*.my.domainとすれば同じドメインの全てのマシンに許可を出せる。

次に、ローカルマシン設定ファイルを定義する。
マスターになるマシンはcondor_config.local.central.managerを、それ以外はcondor_config.localをコピーする。

# mkdir -p /opt/condor/local
# cp condor-8.1.4/release_dir/etc/examples/condor_config.local.central.manager /opt/condor/local/condor_config.local
または
# cp condor-8.1.4/release_dir/etc/examples/condor_config.local /opt/condor/local/

以下のパラメタを変更(または追加)
YOUTPOOLは自分のマシンだけで運用する場合は定義しなくてよい

# vi /opt/condor/local/condor_config.local
CONDOR_HOST    = your.condor.host.jp
COLLECTOR_NAME = YOURPOOL

以下のコマンドをcondor-8.1.4/release_dirの下でtype

#./condor_install --install=. --prefix=/opt/condor/pro --local-dir=/opt/condor/local --type=manager,submit,execute

type=mamager,submit,executeはセントラルマネージャやサブミットマシンの設定。計算するだけでjobのマネージメントをやらないマシンはexecuteだけで良い。

condorのデーモンを走らせる

# export PATH=/opt/condor/pro/bin:/opt/condor/pro/sbin:$PATH
# condor_init
# condor_master

# デーモンが走っているか確かめる
# ps aux | grep condor

condor_master, condor_procd, condor_collector, condor_negotiator, condor_startd, condor_scheddなどが動いていればOK.

ここまでくれば、rootを抜けてもOK

# exit
$ export PATH=/opt/condor/pro/bin:$PATH
$ condor_status

複数台マシンへのインストール

まず、セントラルマネージャとなるマシンにcondorをインストールし、condor_masterを走らせておく。
次に、セントラルマネージャにぶら下がるマシンにcondorをインストールし、condor_masterを走らせる。
(逆の順番では駄目、と昔きいたが、今はどうか知らない)

この状態でcondor_statusを打つと、以下のようなエラーが出る。

Error: communication error
CEDAR:6001:Failed to connect to <XXX.XX.XXX.XXX:9618>

要するに、セントラルマネージャのポート9618が開いていない、という話。
というわけで、CONDORが使用するポートをあける。
CONDORはtcpとupdの9618と、1024番より後ろのポートを自動的にアサインして使う。この設定は変えることも出来るらしいが、面倒なのでクラスター内部の通信は全許可にした(勿論全てのマシンにそれなりのファイヤーウォールをたてておく必要があるけど)。
以下の例はこちらの記事の設定に付け足す場合。

my01 = 'xxx.xx.xxx.xx'
my02 = 'xxx.xx.xxx.xx'

/sbin/iptables -A INPUT -s $my01 -j ACCEPT
/sbin/iptables -A OUTPUT -d $my01 -j ACCEPT

/sbin/iptables -A INPUT -s $my02 -j ACCEPT
/sbin/iptables -A OUTPUT -d $my02 -j ACCEPT

ちなみに、my01、などとおいている変数名がホスト名と重なっていたりすると、まともに動かないので注意。

その他

condor_masterを一度実行すると、condor_offを実行してもデーモンしかストップできない。
面倒なので、condor関係のプログラムを全部殺すスクリプトを作る。

#!/bin/sh
for i in `ps aux | grep condor | grep -v 'grep condor' | awk '{print $2}'`; do eval kill -9 $i; done

ただし、condor_masterが殺せないのには理由があるわけで、ひとたびジョブを投入した後は、このコマンドを実行すると実行中のジョブやどこまで完了したかなどの情報も含め全て消えるので、最初のインストール時のテスト等などの限られた機会以外使用はおすすめしません。