作ったばかりのGoogle Map API利用サイトなのに「このサイトは地図の 1 日の使用量を超えました。」と言われたら

Basic認証を外すか、APIキーの「この HTTP リファラー(ウェブサイト)からのリクエストを受け入れる」設定を外してみよう!

テスト作業中に認証をかけたディレクトリにファイルを置くのはよくあることですが、Google Maps APIの規約として「一般公開されているサイトに置く以外は有料だよコラ」というのがあるので、ブロックされてしまうようです。

現状(2016年8月)では、APIのリファラ制限をはずすとテストが可能な様なので、本番アップするまではこの項目を空にしておきましょう。よく見ると該当の項目に薄く「このキーを本稼働環境で使用する前に、必ずリファラーを指定してください。」と書いてあるので、本稼動前は空欄でいいよ、ということなのかもしれません。

小ネタ:レスポンシブのページで改行位置を調整しよう!

ちょっと長めの見出しをレスポンシブサイトで表示すると、
PCでは

こんな感じで一行にすっきり表示されている見出し


スマホなどの幅の狭いデバイスになると

こんな感じで一行にすっきり表
示されている見出し


変なところで強制的に折り返されてしまうことがあります

今までは、対策として<br>タグを書いて幅の広いデバイスではdisplay:noneで隠していたのですが、もっとスマートな方法を思いつきました

<span class=”nobraking”>こんな感じで一行に</span><span class=”nobraking”>すっきり表示されている見出し</span>

と、「ここからここまでは途中改行されるとおかしい」と思う文節をspanでくくっておいて、それぞれにスタイルシートでdisplay:inline-blockを指定しておくのです

こんな感じで一行に
すっきり表示されている見出し

てな感じで回り込んでくれるはずです

Googleが推奨する「戻るボタン対応・SEO」の無限スクロールを使ってみた

無限スクロール、いいですよね。
ところが巷に配布されているプラグインの多くは、ブラウザのバックボタンで戻ったら最初から読み込み直しでヴァー、ということがあってユーザビリティ上よろしくないのです。

Google先生が対応策としてサンプルのJavascriptをつくってくれたので、いろんな案件で使えるようにちょっと手を入れてみました。

元にしたのはこちらのサンプルです。
http://scrollsample.appspot.com/items

解説は下記ブログ記事をご覧ください。
Googleが推奨するSEOに適した無限スクロールの構成方法

  • あらかじめページを分割しておく。いわゆるページネーション
  • 分割されたページのうち1つを表示(1ページ目でなくても見られるようにする)
  • その上下に前のページ、次のページをAjaxで読み込んでいく
  • 今表示している部分が何ページ目に相当するかに合わせて、URLを動的に書き換える

自分なりに書き換えてみたソースは下記です。jQueryを読み込んだ後に実行してください。


$(function (){
//infinite scroll
var prev_data_selector = '.prev.page-numbers'; //「前へ」リンクのaタグのセレクタ
var next_data_selector = '.next.page-numbers'; //「次へ」リンクのaタグのセレクタ
var itemWrapperSelector = '.list_infinite'; //無限ローディングのwrapperとなるタグのセレクタ
var itemWrapper = $(itemWrapperSelector);
var itemInnerSelector = '.list_infinite_item'; //無限ローディングの本体(本文)となるタグのセレクタ
var itemPagerSelector = '.pagination'; //ページャーのセレクタ
var itemPager = $(itemPagerSelector);
var prev_data_url;
var next_data_url;
var next_data_cache;
var prev_data_cache;
var last_scroll = 0;
var is_loading = 0;

if(itemWrapper[0]) {
	prev_data_url = $(prev_data_selector).attr('href');
	next_data_url = $(next_data_selector).attr('href');
	fadeInItem(true);
	initPaginator();
	loadPrevious();
    // if we have enough room, load the next batch
    if ($(window).height()>itemWrapper.height()) {
      if (next_data_url!== '') {
        loadFollowing();
      } 
    }
}

function initPaginator() {
	$(document).off( 'scroll');
  $(document).on( 'scroll', function(){
    // handle scroll events to update content
    var scroll_pos = $(window).scrollTop();
    if (scroll_pos >= 0.9*($(document).height() - $(window).height())) {
      if (is_loading===0) {
		  loadFollowing();
	  }
    }
    if (scroll_pos <= 0) {
      if (is_loading===0) {
		  loadPrevious();
	  }
    }
    // Adjust the URL based on the top item shown
    // for reasonable amounts of items
    if (Math.abs(scroll_pos - last_scroll)>$(window).height()*0.1) {
      last_scroll = scroll_pos;
      $(itemInnerSelector).each(function() {
        if (mostlyVisible(this)) {
          history.replaceState(null, null, $(this).attr('data-url'));
          return(false);
        }
      });
    }
  });
}


function loadFollowing() {
  if (next_data_url){
    is_loading = 1; // note: this will break when the server doesn't respond
    itemPager.hide();
    if (next_data_cache) {
      showFollowing(next_data_cache);
      is_loading = 0;
    } else {
		$.ajax({
			url: next_data_url,
			type: 'get',
			dataType: 'html',
		}).success(function(data){
	        showFollowing(data);
   		     is_loading = 0;
		});
    }
  }
}
    function showFollowing(data) {
	var out_html = $($.parseHTML(data));
      itemWrapper.append(out_html.find(itemWrapperSelector).filter(itemWrapperSelector)[0].innerHTML);
	  initPaginator();
      next_data_url = out_html.find(next_data_selector).filter(next_data_selector).attr('href');
      next_data_cache = false;
		$.ajax({
			url: next_data_url,
			type: 'get',
			dataType: 'html',
		}).success(function(preview_data){
	        next_data_cache = preview_data;
		});
    }

function loadPrevious() {
  if (prev_data_url){
    is_loading = 1; // note: this will break when the server doesn't respond
    itemPager.hide();
    if (prev_data_cache) {
      showPrevious(prev_data_cache);
      is_loading = 0;
    } else {
		$.ajax({
			url: prev_data_url,
			type: 'get',
			dataType: 'html',
		}).success(function(data){
	        showPrevious(data);
   		     is_loading = 0;
		});
    }
  }
}
    function showPrevious(data) {
	var out_html = $($.parseHTML(data));
      itemWrapper.prepend(out_html.find(itemWrapperSelector).filter(itemWrapperSelector)[0].innerHTML);
	  initPaginator();
      var item_height = $(itemInnerSelector + ':first').height();
      window.scrollTo(0, $(window).scrollTop()+item_height); // adjust scroll
	  prev_data_url = out_html.find(prev_data_selector).filter(prev_data_selector).attr('href');
      prev_data_cache = false;
		$.ajax({
			url: prev_data_url,
			type: 'get',
			dataType: 'html',
		}).success(function(preview_data){
	        prev_data_cache = preview_data;
		});
    }

function mostlyVisible(element) {
  var scroll_pos = $(window).scrollTop();
  var window_height = $(window).height();
  var el_top = $(element).offset().top;
  var el_height = $(element).height();
  var el_bottom = el_top + el_height;
  return ((el_bottom - el_height*0.25 > scroll_pos) && 
          (el_top < (scroll_pos+0.5*window_height)));
}


});

グループを繰り返せるカスタムフィールド用プラグインを探してみた

みんな使っているのになぜか標準仕様がおざなりで使えない「Wordpressのカスタムフィールド」、プラグインをいろいろためしているのですが、グループ化したカスタムフィールドを繰り返す(「画像とキャプションの組み合わせ」を任意の個数追加、ってかんじ)のを実現できるもの、となるとかなり候補が絞られます。CMSとして使う際には頻出テクニックなんだけどね。

Custom Field Suite

http://docs.customfieldsuite.com/

おそらく現状最良の選択。ただ、細かいけど困るバグが多くてバグ対応も遅いのがなあ。「必須」にチェックを入れているのに保存したらチェックが消えている、とか。「次のアプデで対応します」て言ったきり1ヶ月放置されてたり。

グループ化した時だけテンプレートでの出力時に独自の関数を使うが、他はpost_customのままでいけそうなのも好感触。最初にグループ化するための操作が少しだけわかりづらいが、わかってしまえばOKだし、更新する人には関係ないことなので大丈夫。

※備考:「チェックボックスのリスト」は作れません。カスタマイズして追加する方法もあるようですが、ここはWordpress本来の仕様である 「カスタムフィールドは名前:値の1対1の組み合わせ」というのを尊重して、各チェックボックスの項目ごとに「真/偽(簡易チェックボックス)」を増やし て対応してみます

ACF

https://www.advancedcustomfields.com/

超有名プラグインだけど、繰り返しフィールド機能は有料。しかも内部的に独自のデータ処理をやっているみたいで、他のプラグインへの乗り換えが難しかったりWordpress標準の関数が一部使えなかったりと悪い評判も聞いたりする。

Smart Custom Fields

http://2inc.org/blog/category/products/wordpress_plugins/smart-custom-fields/

必要最小限の機能を、できるだけWordpressの仕様に則って実現するというコンセプトなので後々乗り換えることになっても安心そう。
ただし、現状では「必須項目」を設定できないので、仕事としての制作にはまだ使えないと思う。

カスタムフィールドテンプレート

http://ja.wpcft.com/

おれの環境では本文が文字化けして使えなかった。同様の報告例あり。
それと、エディタ画面でのボタン配置や操作感が悪く、仕事として作る際にはお客さんに説明しづらい。
プラグインは無料だがマニュアルが有料。

 

og:imageを正しく設定したのに反映されない(ように見える)現象

og:imageなどOpen Graph関連のタグを書いたとき、最後にやることはFacebook公式のデバッガに通すことだと思いますが、

og:image could not be downloaded or is too small

というエラーがどう頑張っても消えなくて困ることがあります。
「og:image 反映されない」あたりで検索しても「もっと画像を大きくしよう」「Facebook側のキャッシュをクリアしよう」ばかりなのですが、両方何度もやってるよ! おれのなにが間違ってるというんだよ! ムキー!

→あなたは悪くない。Open Graph Object Debuggerの困った仕様。
og:urlの値とOpen Graph Object Debuggerに通すURLは、一文字でも違っていてはいけない。
末尾「/」の有る無し、「www」の有る無し、「大文字小文字」、これらがちょっとでも違っていると「og:imageが見つからないか画像が小さいか…ゴニョゴニョ…」という歯切れの悪いエラーになります。

つまり、たとえばog:urlを下記のように設定した場合、
http://hoge.jp/FileName
FacebookのOpen Graphデバッガーには下記のようなURLを指定してはいけないのです。
http://www.hoge.jp/FileName
http://hoge.jp/FileName/
http://hoge.jp/filename
これらはすべて「別物」として認識されてしまいます。

仕方ないといえば仕方ないんだけど、一言添えておいてほしかったよね。

the_posts_paginationのページ番号を2桁にする

WordPressで構築するサイトで、デザイナーさんからこんなデザインをもらった。
ページ部分の表記が
 < 01 02 03 >
になってる。
サイバーでカッチョイイから、ということみたいだが、できるかそんなことー!→できたー!

以下、私がやった時のやり方です。

参考記事(ありがとうございました!)
the_posts_pagination()のタグをBootstrap 3形式にする WordPressカスタマイズ事典
正規表現で数字の桁数を表す 名もないテクノ手
※「1桁の数字」を検出するのは意外と複雑なようですが(「15.2」の「1」と「5」と「2」がヒットしちゃったりするから)、今回は整数だとわかっているので簡略化した正規表現にとどめました

function.phpに下記を追加します。

function the_posts_pagination_custom( $args = array() ) {
	$navigation = '';
	// 1ページのみの場合は出力しません。
	if ( $GLOBALS['wp_query']->max_num_pages > 1 ) {
		$args = wp_parse_args( $args, array(
			'mid_size'           => 1,
			'prev_text'          => _x( 'Previous', 'previous post' ),
			'next_text'          => _x( 'Next', 'next post' ),
			'screen_reader_text' => __( 'Posts navigation' ),
		) );
		// typeを固定します。今回の場合はlistです。
		$args['type'] = 'list';

		//  ページ区切りリンクを準備します。
		$links = paginate_links( $args );
		if ( $links ) {
			//1桁の場合は前に0を追加します。
			//閉じタグはの場合との場合があるのでどちらでも対応できるように
			$links = preg_replace(
				'#(\b\d\b)#', 
				'0$1', 
				$links);
			$template = '%1$s';
			$navigation = sprintf($template, $links);
		}
	}
	echo $navigation;
}

WordPressで特定エントリの所属カテゴリを「親→子」の順に並び替える

例えばこういう、親子関係のあるカテゴリ構成があったとします

勇者
 ー時の勇者
 ー風の勇者
 ー神に選ばれし勇者

ある記事を「勇者 風の勇者」のカテゴリに指定した場合、普通にget_the_category()を使うと「勇者 風の勇者」ではなく機械的にアルファベット順に「風の勇者 勇者」と並べられてしまってヴァーとなることがあります

試行錯誤で2パターンほど対処法を試したので両方メモしておきます

解法その1

参考にさせていただいた記事です。ありがとうございます!
カスタムタクソノミーに親子関係(階層)を持たせ、複数選択したタームの並び順を親>子の順番に表示する
※「children[]」という箇所が自分の環境では動作しなかったので、自分なりに書き換えましたが、ちょっと頭の悪い処理かもしれません。ごめん。

functions.php

//引数として与えた配列を親子順に
function getRootTaxonomies($terms=null){
	$rootterms = array();
	$roottermstemp = array();
	if(isset($terms))
	{
		foreach($terms as $k=>$v){
			if($v->parent === 0){
				$rootterms[] = $v;
			}
		}
		foreach($rootterms as $k=>$v)
		{
			$i = 0;
			foreach($terms as $sk=>$sv){
				$childname = 'child'.$i;
				if( $v->term_id === $sv->parent){
					$rootterms[$k]->$childname  = $sv;
					$i++;
				}
			}
		}
	}
	return $rootterms;
}

テンプレートの該当箇所

slug; ?>
//カテゴリを出力したい場合
name; ?>
$childname && $pv->$childname->name != ''){
?>
//スラッグを出力したい場合
$childname->slug; ?>
//カテゴリを出力したい場合
$childname->name; ?>

解法その2

こっちの方法では、階層の同じカテゴリを複数指定した場合はうまくいかないです。

参考にさせていただいたのは下記の二つの記事です。ありがとうございます!
カテゴリIDを階層順に並べた配列をつくる Wordpressテーマの作り方
カテゴリIDからカテゴリ名/スラッグを取得する

function.php

//所属カテゴリ一覧を親子順に
function get_categories_tree() {
    $post_categories = get_the_category();
    $cat_trees = array();
    $cat_counts = array();
    $cat_depth_max = 10;
    foreach ( $post_categories as $post_category ) {
        $depth = 0;
        $cat_IDs = array($post_category->cat_ID);
        $cat_obj = $post_category;
        while ( $depth < $cat_depth_max ) {
            if ( $cat_obj->category_parent == 0 ) {
                break;
            }
            $cat_obj = get_category($cat_obj->category_parent);
            array_unshift($cat_IDs, $cat_obj->cat_ID);
            $depth++;
        }
        array_push($cat_trees, $cat_IDs);
        array_push($cat_counts, count($cat_IDs));
    }
    $depth_max = max($cat_counts);
    $cat_key = array_search($depth_max, $cat_counts);
    $cat_tree = $cat_trees[$cat_key];
    return $cat_tree;
}
//カテゴリIDからカテゴリ名とスラッグ取得
    function get_category_name_by_id($cat_ID,$type) {
        $cat_ID = (int) $cat_ID;
        $category = get_category($cat_ID);
        if($type == 'name'){
            return $category->cat_name;
        } elseif($type == 'slug') {
            return $category->slug;
        }
    }

テンプレートの該当箇所


//スラッグを出力したい場合

//カテゴリを出力したい場合


GAEで特定の下層ディレクトリにだけWordPressをインストールしたい時のメモ

16/2/10追記
そもそも、GAEをWebサーバとして使うときに結構でかい落とし穴を発見しました。
「末尾スラッシュ(trailing slash)」のあるなしで、相対パスが一つずれてしまう問題です。今、歴戦の勇者に「教えてえらい人」しているのでちょっと待ってね。

16/2/2追記
GAEをWebサーバとして使いたい人はファイル数制限「1バージョンにつき10000ファイルまで」を心のどこかに置いておいてください。中規模の、サブディレクトリごとにコンテンツが大量にあるサイトの場合あとで泣くことになります。解決法がわかったらまた別記事にします。

16/2/1修正
以前の書き方では「画像が全くアップロードできない」という結構な致命傷があったので、対策をwp-config.phpの項目に追記しました

────────ここから本題────────

Google App Engineを使って公開しているサイトで「新着情報だけWPになってればあとのコンテンツはベタのHTMLでいいんだよなー」というケースがあったので対処法をメモ。あ、このブログ自体のことではないです。

やりたいこと

  • GAEを使用
  • 下層の特定のディレクトリにだけWordpressを導入
  • パーマリンクが初期設定だと ださいので、カスタマイズする

パーマリンクというと前髪にパーマをかけた左利きの勇者を連想しますがそれは別の話。

まずGAEにWPを導入する。やり方は詳しく解説されてるサイトがいくつかあります。

つぎに
「下層の特定のディレクトリにアクセスした時だけ、WPで作動するようにする」
「パーマリンクの設定を変更してもつじつまが合うようにする」

この2点をクリアするには、app.yamlおよびwordpress/wp-config.phpを編集する必要があります。

ディレクトリ「news」だけWPで運用したい場合の記述例は下記です。

wordpress/wp-config.php

//変更前
define( 'WP_SITEURL', $protocol_to_use . $_SERVER['HTTP_HOST']);
define( 'WP_HOME', $protocol_to_use . $_SERVER['HTTP_HOST']);
//変更後
define( 'WP_SITEURL', $protocol_to_use . $_SERVER['HTTP_HOST'] . '/news/');
define( 'WP_HOME', $protocol_to_use . $_SERVER['HTTP_HOST'] . '/news/');
define('COOKIEPATH', '/' );

GAE用WPプラグインを使う時は管理画面からWP_SITEURLを変更できない仕様になっているので、ここを直書きで直してしまいます。これがみそ。

さらに、cookieのパスをルートに指定してやる必要があります。これをしないと、画像が全くアップできなくなってしまいます。理由は、GAE側で用意されている画像アップロード機能にて、認証用のクッキーをルートパスにて扱っているから、とのことです。
参考:WordPress 403 HTTP error on file uploads on App Engine with Custom Domain

app.yaml 抜粋

handlersの項目を下記のようにします。

- url: /news/(.*\.(htm|html|css|js))$
  static_files: wordpress/\1
  upload: wordpress/.*\.(htm|html|css|js)$
  application_readable: true

- url: /news/wp-content/(.*\.(ico|jpg|jpeg|png|gif|woff|ttf|otf|eot|svg))$
  static_files: wordpress/wp-content/\1
  upload: wordpress/wp-content/.*\.(ico|jpg|jpeg|png|gif|woff|ttf|otf|eot|svg)$
  application_readable: true

- url: /news/(.*\.(ico|jpg|jpeg|png|gif|woff|ttf|otf|eot|svg))$
  static_files: wordpress/\1
  upload: wordpress/.*\.(ico|jpg|jpeg|png|gif|woff|ttf|otf|eot|svg)$
  application_readable: true

- url: /news/wp-includes/images/media/(.*\.(ico|jpg|jpeg|png|gif|woff|ttf|otf|eot|svg))$
  static_files: wordpress/wp-includes/images/media/\1
  upload: wordpress/wp-includes/images/media/.*\.(ico|jpg|jpeg|png|gif|woff|ttf|otf|eot|svg)$
  application_readable: true

- url: /news/wp-admin/(.+)
  script: wordpress/wp-admin/\1
  secure: always

- url: /news/wp-admin/
  script: wordpress/wp-admin/index.php
  secure: always

- url: /news/wp-login.php
  script: wordpress/wp-login.php
  secure: always

- url: /news/wp-cron.php
  script: wordpress/wp-cron.php
  login: admin

- url: /news/xmlrpc.php
  script: wordpress/xmlrpc.php

- url: /news/wp-(.+).php
  script: wordpress/wp-\1.php

- url: /news(.+)?/?
  script: wordpress/index.php

- url: /(.+\.(gif|png|jpg|htm|html|css|js))$
  static_files: \1
  upload: .+\.(gif|png|jpg|htm|html|css|js)$
  application_readable: true

- url: /(.+\.php)$
  script: \1

- url: /
  script: index.php

- url: /(.+)$?/?
  script: \1/index.php

基本的な考え方としては、WPに関連する記述の「URL」をすべてWPを動かしたいディレクトリ名を挟んだものに書き換えます。
さらに、それ以降に「それ以外なら全部、要求したURLをそのまま表示する」旨の記述を加えて調理完了。

今の所これでうまくいっています。何かありましたら追記します。