PHPのバージョンを7系にしてWordPress管理ページの表示が崩れた原因

PHPのバージョンを7系にしたらWordPress管理ページの表示が崩れました。スタイルシートを読み込めてないようですが、原因はwp-include/plugin.php の 600行目でした。この部分のソースを改変します。

このブログはまだPHP5で動かしていますが、自宅のPCにPHP7とWordPressの4.6を新規インストールして動かしたら管理画面の表示が崩れました。スタイルシートを読み込めてないようですが、原因はwp-include/plugin.php の 600行目でした。

ちなみに32Bit版 Linux Mint 18で次のようにして入れた PHP です。関係ないと思いますが、WebサーバーはH2Oでhttpsにしています。

sudo add-apt-repository ppa:ondrej/php
sudo apt-get update
sudo apt-get install php7.1-fpm php7.1-cgi php7.1-mbstring php7.1-mysql php7.1-xml php7.1-xmlrpc php7.1-xsl php7.1-zip php7.1-bz2 

wp-include/plugin.php の 600行目は次のようになっています。

do {
        foreach ( (array) current($wp_filter[$tag]) as $the_ )
                if ( !is_null($the_['function']) )
                        call_user_func_array($the_['function'], array_slice($args, 0, (int) $the_['accepted_args']));

} while ( next($wp_filter[$tag]) !== false );

この call_user_func_array が原因でした。上の部分を次のとおりに変えたら直りました。

do {
        foreach ( (array) current($wp_filter[$tag]) as $the_ )
                if ( !is_null($the_['function']) ){
                        $arr = array();
                        foreach(array_slice($args, 0, $the_['accepted_args']) as $a){
                            $arr[] = &$a;
                        }
                        call_user_func_array($the_['function'], $arr);
                }

} while ( next($wp_filter[$tag]) !== false );

PHP の関数 call_user_func_array へ渡す値が参照渡しになっていなかった事が原因です。上の改変箇所ではその値が参照渡しになるように変えています。

参考サイト PHP 5.3: 参照渡しの関数/メソッドを定義してた人は call_user_func_array に注意 – 肉とビールとパンケーキ by @sotarok

ただし、WordPress のバージョンをアップデートすると上書きされて元に戻ってしまうと思います。

プラグインを作る場合、この関数自体にはフックがないので、この関数を呼び出している元の関数にフックする必要がありそうです。

自サーバーに置いたWebフォントを読み込めない時の対処法

Google Fontsなどのサービスとして公開されているのではなく、独自にサーバーに置いたWebフォントをCSSで指定しても反映されない場合の対処方法です。独自ドメインで使っているサーバーとは別にFC2やSeesaaブログなどをやっていて、そちらで自サーバーのWebフォントが読み込めないという場合、クロスドメイン制約でアクセス制限がかかっている事があります。

Google Fontsなどのサービスとして公開されているのではなく、独自にサーバーに置いたWebフォントをCSSで指定しても反映されない場合の対処方法です。

独自ドメインで使っているサーバーとは別にFC2やSeesaaブログなどをやっていて、そちらで自サーバーのWebフォントが読み込めないという場合、クロスドメイン制約でアクセス制限がかかっている事があります。

ていうか、私自身が今使っているバリューサーバーに置いたフォントを、別のサイトで読み込ませて使っていたんですが、気がついたら使えなくなっていました。以前は使えていたんですけど、どこかの段階でバリューサーバーの設定が変わったようです。

その時にやった事のまとめです。

Webフォントが読み込めないドメインのアクセス制限を解除する

先に公開したページ「Google Feed APIを使わずXMLHttpRequestでクロスドメインのRSSを取得する方法 まとめ」に書いたんですが、.htaccess に Access-Control-Allow-Origin の設定をするとアクセス制限を解除できます。

.htaccess に次のように書くと全てのドメインからのアクセスが許可されます。

Header append Access-Control-Allow-Origin: *

* はワイルドカードで、この場合は全てのドメインを指します。

全てのドメインにではなく、自分の使っているドメインだけ許可したい時は、そのドメインを指定します。許可したいドメインが http://example.com なら下の通りです。

Header append Access-Control-Allow-Origin: http://example.com

ただし、ドメインを複数指定するには工夫が必要なようです。
参考「Access-Control-Allow-Originヘッダで複数のオリジンドメインを許可する方法 – ぷれすとぶろぐ

Internet ExplorerだけWebフォントが反映されない時の対処法

上の設定で、Webフォントは読み込まれるようになったんですが、IEだけ反映されませんでした。

そこで調べたら、次のページを発見。Truetype embedding-enabler

このページにあるembed.exeをダウンロードしてコマンドプロンプトで以下のように実行。そのフォントを再アップロードしただけでIEにも反映させることが出来ました。

embed font_name.ttf

私の場合は上のツールで解決したんですが、下のページで他の使いやすいツールとかを詳しく説明してくれています。

IEにも対応したWebフォントの使い方について | memocarilog

このページによると、IE以外のブラウザでは拡張子が .ttf のTrueTypeフォントや .otf のOpenTypeフォントが使えるんですが、IEだけは拡張子が .eot のEmbedded Open Typeしか使えないというのが原因のようです。

querySelectorでIEでもそれ以外でもネームスペース付きのXMLからデータを取得する方法

document.evaluateはIEで動かないんですが、RSSのようにネームスペース付きXMLファイルにIEでもそれ以外のブラウザでもquerySelectorを使った同一ソースでアクセスする方法をまとめました。jQueryを使わなくても大した長さにはなりません。

前のページ「Google Feed APIを使わずXMLHttpRequestでクロスドメインのRSSを取得する方法 まとめ」でJavaScriptのXMLHttpRequestを使ってRSSを取得するまで書きました。取得したRSSはネームスペース付きのXMLなので、どうやってデータにアクセスすればいいのかわかりませんでしたが、このページではそれを解決する方法をまとめています。jQueryは使っていません。

document.evaluateはIEで動かない

最近はIEで見ても、その他のブラウザで見ても同じに見えるので油断していました。

ググってみると「JavaScriptでデータを走査する時、namespaceを解決するにはdocument.evaluateを使う」というような感じだったので、それを参考にスクリプトを書いてGoogle Chromeで試したらちゃんと動いたんですけど、InternetExplorerでは動きません。document.evaluateはIEで実装していないらしいです。

という事はIEだけ別のソースを書かなくてはいけないのか?と思ったんですけど、querySelectorだけで両方対応出来ました。

responseTextではなくresponseXMLを使う

XMLHttpRequestで取得したデータをresponseTextで取って、それを次のようにHTMLにしてquerySelectorをかけてもうまくいきません。正確に言うと、これで取れるデータもあるんですが取れないデータがあります。

HTMLの受け皿に、下ではdivを使っていますが、ちゃんとそれ用のobjectがあったような気もします。もし、それをご存知ならそちらを使われた方がよろしいかと。。

var xhr = new XMLHttpRequest();

xhr.onreadystatechange = function(){
    if(xhr.readyState == 4 && xhr.status == 200){
        /* Bad example */
        var div = document.createElement('div');
        div.innerHTML = xhr.responseText;
        var items = div.querySelectorAll('item');
    }
};

xhr.open('GET', rss_url, true);
xhr.send('');

そうではなく、responseXMLにquerySelectorをかければ、namespaceがあってもデータが取得できます。

var items = xhr.responseXML.querySelectorAll('item');

namespaceがついたタグの指定方法

namespaceのついたタグを抽出する時は、querySelectorの引数に、namespaceを除いたタグ名を指定すればいいみたいです。

次のRSSから<dc:date>の中身を抜き出す場合を例にします。

<rdf:RDF xmlns:rdf="" xmlns:dc="" xmlns:admin="" xmlns:content="" xmlns="">
    <channel rdf:about="">
        <title></title>
        <link></link>
        <description></description>
        <dc:language></dc:language>
        <items>
            <rdf:Seq>
                <rdf:li rdf:resource=""/>
                <rdf:li rdf:resource=""/>
            </rdf:Seq>
        </items>
    </channel>
    <item rdf:about="">
        <link></link>
        <title></title>
        <description>
            ...
        </description>
        <dc:subject></dc:subject>
        <dc:creator></dc:creator>
        <dc:date>yyyy-mm-ddT00:00:00+09:00</dc:date>
        <content:encoded>
            <![CDATA[ ... ]]>
        </content:encoded>
    </item>
    <item rdf:about="">
        <link></link>
        <title></title>
        <description>
            ...
        </description>
        <dc:subject></dc:subject>
        <dc:creator></dc:creator>
        <dc:date>yyyy-mm-ddT00:00:00+09:00</dc:date>
        <content:encoded>
            <![CDATA[ ... ]]>
        </content:encoded>
    </item>
</rdf:RDF>

先ほどの

var items = xhr.responseXML.querySelectorAll('item');

これでitemの一覧は配列化されているので、それをループして各itemの中から日付を抽出したい場合はnamespaceのdcとコロンを取り除いたタグ名「date」でエレメントが取得できます。

var items = xhr.responseXML.querySelectorAll('item');

for(var i = 0; i < items.length; i++){
    var date = items[i].querySelector('date');
    alert(date.textContent); // yyyy-mm-ddT00:00:00+09:00
}

属性の取得はgetAttribute

例えば、itemタグのrdf:aboutを取得するには、items[i].about としてもダメでした。この場合はgetAttribute()に取得したい属性名を指定するんですが、こちらの場合はnamespaceも必要みたいです。

// <item rdf:about="http://example.com"></item> からrdf:aboutの値を抽出

var items = xhr.responseXML.querySelectorAll('item');

for(var i = 0; i < items.length; i++){
    alert(items[i].about);                     // undefined
    alert(items[i].getAttribute('about'));     // null
    alert(items[i].getAttribute('rdf:about')); // http://example.com
}

<![CDATA[]]>の中のテキスト抽出にはtextContentを使う

<![CDATA[]]>セクションのテキストを取り出すには、innerHTMLではなくtextContentを使います。

正確に言うと、innerHTMLでも取れなくはないですが、<![CDATA[ と ]]> のタグも含まれたテキストになります。

textContentだと、<![CDATA[ と ]]> のタグがついていません。

参考ページ「Node.textContent – Web API インターフェイス | MDN

CDATAの中は例えHTMLのソースだとしても、あくまでテキストです。そのHTML文の中にあるエレメントを抽出するにはそのソースを元にHTML化します。

例えば、<content:encoded>タグの中にあるブログの各ページの本文から<img />タグの個数を調べるには、次のような方法があります。

var items = xhr.responseXML.querySelectorAll('item');

for(var i = 0; i < items.length; i++){
    var src = items[i].querySelector('encoded').textContent;

    var div = document.createElement('div');
    div.innerHTML = src;

    var img = div.querySelectorAll('img');
    alert(img.length);
}

HTMLの受け皿に、上ではdivを使っていますが、ちゃんとそれ用のobjectがあったような気もします。もし、それをご存知ならそちらを使われた方がよろしいかと。。

XMLHttpRequestで取得したデータを一覧表示するサンプル

以下は取得したいRSSのURLを入力すると、その結果を記事内の最初の画像付きで5個表示させるサンプルです。前のページ「Google Feed APIを使わずXMLHttpRequestでクロスドメインのRSSを取得する方法 まとめ」の方法でクロスドメインに対応しています。

ただしこのソースでは簡略化のためAtomには未対応です。

Atomにも対応させるには下のソースのcallback関数を変えるだけです。このページの一番下に「RSSとAtom両対応版のcallback関数」を置いておきます。

<!-- HTML部分 -->

<p id="sample_form">
<input type="text" name="sample" value="" placeholder="RSSのURLを入力して送信して下さい" />
<input type="button" value="送信" />
</p>

<div id="result"></div>
<!-- JavaScript部分 -->

<script>

// 絵文字を除外する
// imgタグが20ピクセル以下に指定されていたらfalse
// heightもwidthも未指定または20ピクセル以上ならtrue
function image_size_check(img){
    if(img.height && 20 >= img.height){
        return false;
    }

    if(img.width && 20 >= img.width){
        return false;
    }

    return true;
}

// 本文中から画像を抽出
function get_image(content, link){
  // contentの中のimgタグを全部取得
  var div = document.createElement('div');
  div.innerHTML = content;
  var images = div.querySelectorAll('img');

  for(var n = 0; n < images.length; n++){
    // imagesの中で最初に見つかった20ピクセル以上の画像を抽出
    // 20ピクセルは絵文字を除くため
    if(image_size_check(images[n])){
      // ブラウザによってはスラッシュ一つで始まる画像のURLが
      // 開いているページのものになってしまうのでその対策
      var pattern = /^(\S+?:\/)?\/([^\/\s]+).*$/;
      var location_domain = window.location.href.replace(pattern, '$2');
      var origin_domain = link.replace(pattern, '$2');
      var src = images[n].src.replace(location_domain, origin_domain);

      return '<p><img src="' + src + '" alt="画像" /></p>';
    }
  }

  return '<p></p>';
}

// callback本体
function callback(rss){
  var max_count = 5; // 5個まで表示
  var items = rss.querySelectorAll('item');
  var html  = '';

  for(var i = 0; i < items.length && i < max_count; i++){
    var link    = items[i].querySelector('link').textContent;
    var title   = items[i].querySelector('title').textContent;
    var date    = items[i].querySelector('date').textContent;
    var content = items[i].querySelector('encoded').textContent;
    var img     = get_image(content, link);

    date = date.replace(/^(\d{4})\-(\d{2})\-(\d{2})T.+$/,'$1年$2月$3日');

    html += '<li>' + img 
         + '<p><a href="' + link + '">' + title + '</a></p>'
         + '<p>' + date + '</p>'
         + '</li>';
  }

  document.querySelector('#result').innerHTML = '<ul>' + html + '</ul>';
}

function get_rss(url){
    var rss_server = 'ここに自作RSS中継サーバーのURL http://example.com/';

    var pattern       = /^(\S+?:\/)?\/([^\/\s]+).*$/;
    var server_domain = rss_server.replace(pattern, '$2');
    var location      = window.location.href.replace(pattern, '$2');

    /*
     指定されたRSSのドメインが閲覧中のページのドメインと
   同じならそのままRSSが取得できるので
   違う場合のみ中継サーバーを使う。
    */
    if(server_domain != location){
        url = rss_server + '?rss=' + encodeURIComponent(url);
    }

    if(window.XDomainRequest){
        var xhr = new XDomainRequest();
        xhr.onload  = function(){callback(this.responseXML);};
        xhr.onerror = function(){}
        xhr.open('GET', url, true);
        xhr.send('');
    }else if(window.XMLHttpRequest){
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function(){
            if(this.readyState == 4 && this.status == 200){
                callback(this.responseXML);
            }
        };
        xhr.open('GET', url, true);
        xhr.send('');
    }
}

var form = document.querySelectorAll('#sample_form input');

form[1].onclick = function(){
    get_rss(form[0].value);
}
</script>

下のフォームにRSSかAtomのURLを入力して送信ボタンを押すと最新記事一覧を(画像があるページではページ内の画像付きで)5個表示します。クロスドメイン対応です。

注)
エラーの時はエラーメッセージは表示せず、何もしません。
bloggerのURLはリダイレクトされるので、リダイレクト先のURLを入力しない場合は取得しません。

ちなみにCSSは次の通りです。

CSSの下にRSSとAtom両対応版のcallback関数を置いておきます。callback以外は上と同じです。

<!-- CSS部分 -->

<style>
#result {
 margin:0;
 padding:0;
 border:none;
 width:400px;
 background:transparent;
}

#result ul,
#result li {
 list-style-type:none !important;
 background:transparent;
}

#result li {
 padding-bottom:0.5em;
 margin-bottom:0.5em;
 border-bottom: 2px solid #ddd;
}

#result li:last-child {
 border-bottom:none;
}

#result li:after {
 content: ".";
 display: block;
 height: 0;
 font-size:0;
 clear: both;
 visibility:hidden;
}

#result img {
 width:100%;
 height:auto;
}

#result a {
 text-decoration:none;
 line-height:1.1em;
 background:transparent;
}

#result p {
 margin:0;
 padding:0;
 line-height:1em;
 background:transparent;
}

#result p:nth-child(1) {
 width:80px;
 float:left;
 margin-right:5px;
}

#result p:nth-child(2) {
 display:block;
 overflow:hidden;
}

#result p:nth-child(3) {
 line-height:1.4em;
 text-align:right;
 font-size:90%;
}

#sample_form input:nth-child(1) {
 width:400px;
}
</style>
<!-- RSSとAtom両対応版のcallback関数 -->

<script>
function callback(xml, content_type){
  // HTMLの生成関数(callbackの外に書いてもOK)
  function make_html(link, title, date, img){
    return '<li>' + img 
      + '<p><a href="' + link + '">' + title + '</a></p>'
      + '<p>' + date + '</p>'
      + '</li>';
  }

  var count_max = 5;
  var i = 0;
  var html  = '';

  var items = xml.querySelectorAll('item');

  if(items.length){
    if(items[0].querySelector('date')){
      // RSS
      // Seesaa FC2 livedoor(livedoorはRSSとAtomがある)
      for(var i = 0; i < items.length && i < count_max; i++){
        var link    = items[i].querySelector('link').textContent;
        var title   = items[i].querySelector('title').textContent;
        var date    = items[i].querySelector('date').textContent;
        var content = items[i].querySelector('encoded').textContent;
        var img     = get_image(content, link);

        date = date.replace(/^(\d{4})\-(\d{2})\-(\d{2})T.+$/,'$1年$2月$3日')
        html += make_html(link, title, date, img);
      }
    }else{
      // Atom
      // WordPress blogger
      // ただしbloggerのURLはリダイレクトされるので
      // リダイレクト先のURLを入力しない場合は取得しない
      for(var i = 0; i < items.length && i < count_max; i++){
        var link    = items[i].querySelector('link').textContent;
        var title   = items[i].querySelector('title').textContent;
        var date    = items[i].querySelector('pubDate').textContent;
        var thumb   = items[i].querySelector('thumbnail');
        var content = items[i].querySelector('encoded');
        var img     = '<p></p>';

        date = date.replace(/^\S+?,\s+([0-9]+)\s+(\S+)\s+([0-9]+)\s.+$/,function(all, g1, g2, g3){
          var m = {
            'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', 'May': '05', 'Jun': '06',
            'Jul': '07', 'Aug': '08', 'Sep': '09', 'Oct': '10', 'Nov': '11', 'Dec': '12'
          };

          return g3 + '年' + m[g2] + '月' + g1 + '日';
        });

        if(thumb){
          var src = thumb.getAttribute('url');
          img = '<p><img src="' + src + '" alt="画像" /></p>';
        }else if(content){
          img = get_image(content.textContent, link);
        }

        html += make_html(link, title, date, img);
      }
    }
  }else{
    // livedoorのAtom
    items = xml.querySelectorAll('entry');

    for(var i = 0; i < items.length && i < count_max; i++){
      var link    = items[i].querySelector('link').getAttribute('href');
      var title   = items[i].querySelector('title').textContent;
      var date    = items[i].querySelector('issued').textContent;
      var content = items[i].querySelector('content').textContent;
      var img     = get_image(content, link);

      date = date.replace(/^(\d{4})\-(\d{2})\-(\d{2})T.+$/,'$1年$2月$3日')
      html += make_html(link, title, date, img);
    }
  }

  document.querySelector('#result').innerHTML = '<ul>' + html + '</ul>';
}
</script>

Google Feed APIを使わずXMLHttpRequestでクロスドメインのRSSを取得する方法 まとめ

Google Feed APIを使わずにXMLHttpRequestでクロスドメインのRSSを取得してデータを抽出した際に作った自作サーバーの立て方とJavaScriptでの取得方法です。JavaScriptに関して言うとjQueryを使わなくても大した行数にはならないです。

「Google Feed API」を使わず、XMLHttpRequestでクロスドメインのRSSを取得して最新記事一覧を表示させるスクリプトを書いたんですが、その際にわからなかったことをまとめておきます。JavaScriptに関しては下に書きますが、jQueryは使っていません。

RSS取得後のネームスペース付きXMLからデータを抽出する方法はクロスドメインとは別の問題なので、他のページ「querySelectorでIEでもそれ以外でもネームスペース付のXMLからデータを取得する方法」にまとめています。

クロスドメインのRSSを取得するサーバー構築

需要があるかどうかは別にして、単純に自ドメインのRSSを取り込むだけなら通常通りのやり方で問題ないんですが、クロスドメインになるとアクセス拒否されます。そういった問題に対する手段として「Google Feed API」がありますけど、先日一時的に「Google Feed API」が使えないという騒動になりました。外部サービスに頼るとそのサービスが止まった時に困るという事で、自前で外部RSSを中継するサーバーを立てました。

中継サーバーは数行のスクリプトで出来るんですが、PHPの場合なら次のようになると思います。

<?php
// GETで ?rss=URL という形でリクエストされる場合

// GETのパラメーターにrssがなかったら
// エラーステータスを返して終了
if(!isset($_GET['rss'])){
    header('HTTP/1.0 400 Bad Request');
    exit();
}

$url = rawurldecode($_GET['rss']);

// $_GET['rss']の文字列がURLのパターンにマッチしてなかったら
// エラーステータスを返して終了
if(!preg_match('|^(\S+?:/)?/\S+?\.\S+?/\S+$|', $url)){
    header('HTTP/1.0 400 Bad Request');
    exit();
}

$host = parse_url($url, PHP_URL_HOST);

// 指定のアドレスが存在しない場合
// エラーステータスを返して終了
if(@gethostbyname($host) == $host){
    header('HTTP/1.0 400 Bad Request');
    exit();
}

// 指定されたアドレスのデータを取得
$rss = @file_get_contents($url);

// 注)
// file_get_contents自体のトラブルで通信不能でも
// 通信出来てて200以外の場合でもfalseが返る。
// なので下はレスポンスヘッダを調べている

// レスポンスヘッダがなかったら通信できていない
// エラーステータス(500)を返して終了
if(!isset($http_response_header)){
    header('HTTP/1.0 500 Internal Server Error');
    exit();
}

$response = array_shift($http_response_header);

// レスポンスヘッダが200 OKでないなら
// そのステータス(おそらく500以外)を返して終了
if(false === strpos($response, '200')){
    header($response);
    exit();
}

$content_type = null;

// レスポンスヘッダのContent-Typeを調べる
foreach($http_response_header as $line){
    if(false !== strpos(strtolower($line), 'content-type:')){
        if(false !== strpos($line, 'xml')){
            $content_type = $line;
        }
        break;
    }
}

// 取得したファイルがxmlでない(RSSでない)なら
// エラーステータスを返して終了
if(!$content_type){
    header('HTTP/1.0 400 Bad Request');
    exit();
}

// 取得したRSS(と思われるxml)をそのまま送信
header('HTTP/1.0 200 OK');
header($content_type);
header('Access-Control-Allow-Origin: *');  
header('Access-Control-Allow-Methods: GET');
print($rss);

/*
 PHPのみの単体ファイルなので終了タグ ?> は不要
 PHPマニュアル http://php.net/manual/ja/language.basic-syntax.phptags.php より
 「ファイル全体が純粋な PHP コードである場合は、ファイルの最後の終了タグは省略するのがおすすめです。」
*/

もし特定ドメインからのアクセスしか許可しないなら、file_get_contents の結果がxmlなのかどうかをチェックせずにそのまま返してもいいのかも知れませんが、今回はアクセス制限をしないので悪用防止でxml以外は返さないようにしています。

ここで重要なのは赤文字の2行です。これがないとクロスドメイン問題が解消されません。

注)このスクリプトを置くURLと同じドメインからのアクセスなら赤文字2行がなくても通信できます。そうではなく、他のドメインとも通信する場合には必要です。例えばこのスクリプトにアクセスして特定サイトの最新記事一覧を取得するブログパーツを配布する場合などは、どのURLのブログからアクセスが来るかわかりません。そういう時には赤文字の2行が必要です。

HTTP access control

HTTP access control (CORS) | MDN

上のサイトに詳しく書いてあるんですが、XMLHttpRequestでクロスドメイン間通信を行うには、サーバー側でそれを許可する旨のヘッダーを送信しないといけません。そのための仕様が「Cross-Origin Resource Sharing (CORS) 」というらしくて、XMLHttpRequest以外にもWebフォントなどを特定ドメインだけに限定して許可したりできます。

今回は説明を簡単にするためにPHPのheader関数で送信していますが、.htaccessに書くことができます。

上の2行を.htaccessに書く場合はこうなります。

Header append Access-Control-Allow-Origin: *
Header append Access-Control-Allow-Methods: GET

Access-Control-Allow-Origin

Access-Control-Allow-Originには、アクセスを許可するURIを指定します。

たとえば、http://example.com にだけアクセスを許すのであれば、次のように書きます。

Access-Control-Allow-Origin: http://example.com

アクセスを許可するサイトを複数指定することも出来なくはないのですが、工夫が必要みたいです。
参考ページ「Access-Control-Allow-Originヘッダで複数のオリジンドメインを許可する方法 – ぷれすとぶろぐ

アドレスを指定せず、すべてのURIに解放するのであれば、上のようにワイルドカード * を使います。次の場合は、すべてのURIからのアクセスを許可することになります

Access-Control-Allow-Origin: *

Access-Control-Allow-Methods

Access-Control-Allow-Methodsには、アクセスに許可するメソッドを指定します。

GET通信のみ許可する場合は次のようになります。

Access-Control-Allow-Methods: GET

Access-Control- で始まるヘッダは他にもあるんですが、この2つの指定だけでもXMLHttpRequestでの通信は出来るようになります。

JavaScript側の対応

サーバー側で上記の設定をしていれば、JavaScript側では普通にXMLHttpRequest通信をするだけです。

WindowsのInternetExplorerだけは、XMLHttpRequest ではなく XDomainRequest でないとクロスドメイン間通信は出来ないと書いてあるサイトが多かったのですが今は大丈夫みたいです。

というかInternetExplorer11(バージョン: 11.0.9600.18124)では window.XDomainRequest が見つかりませんでした。次のようにして試してみたらIE11でもそれ以外でも2が表示されます。

if(window.XDomainRequest){
    alert(1);
}else if(window.XMLHttpRequest){
    alert(2);
}

InternetExplorer11でも window.XMLHttpRequest が返ってくるんですがクロスドメイン間通信は出来ました。ただ、古いバージョンのIEではダメでしょうから、結論としてJavaScriptのXMLHttpRequestでRSSを取得するには下のようになります。

ただし、XMLHttpRequest Level2に対応していないブラウザ(クロスドメイン間通信自体が出来ないブラウザ)ではcallback関数は呼ばれませし、エラーメッセージも表示しません。

私は「サイドバーに最新記事一覧を表示させるブログパーツ」に使ったので、未対応ブラウザではエラー表示をするよりも、パーツのタイトルも含めて一切何も表示させない方がユーザーが混乱しないと思ったので。

jQueryを使わずXMLHttpRequestでRSSを取得するサンプル

/*
取得したいRSSが   http://example.com/index.rdf で
先ほどのサーバーが http://hoge.dwm.me/ の場合
*/

function callback(){
  /* ここに取得したRSSの処理を記述 */
}

var rss_url = 'http://example.com/index.rdf';
var url = 'http://hoge.dwm.me/?rss=' + encodeURIComponent(rss_url);

if(window.XDomainRequest){
    var xhr = new XDomainRequest();
    xhr.onload  = function(){callback();};
    xhr.onerror = function(){}
    xhr.open('GET', url, true);
    xhr.send('');
}else if(window.XMLHttpRequest){
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function(){
        if(this.readyState == 4 && this.status == 200){
            callback();
        }
    };
    xhr.open('GET', url, true);
    xhr.send('');
}

これでRSSのxml自体は取得できます。後は取得したデータを処理するcallback関数の中身を書くだけです。

RSSはネームスペース付きXMLなのでquerySelectorでうまくデータが取得できなかったんですが、JavaScriptでネームスペース付きXMLからデータを取得する方法がわかれば解決します。

それに関してはXMLHttpRequestでのクロスドメイン通信とは違う話なので別のページ「querySelectorでIEでもそれ以外でもネームスペース付のXMLからデータを取得する方法」にまとめました。必要ならそちらをご覧ください。取得したRSSから各ページの最初の画像も抽出して一覧表示するサンプルを掲載しています。

取得するURLが同ドメインかクロスドメインか判断して取得先を振り分けるJavaScript

取得するRSSのURLが固定なら問題ないですが、場合によって同じドメインのRSSを取りに行ったり別ドメインのRSSを取りに行ったりする場合、同じドメインなのに上の自作サーバーを経由するのはムダです。

取得するURLに自ドメインと別ドメインが混在する場合は、JavaScript側でアクセスする先を切り替えるようにします。次はその一例です。

function callback(rss){
  /* ここに取得したRSSの処理を記述 */
}

function get_rss(url){
    var rss_server = 'ここに自作RSS中継サーバーのURL';

    var pattern       = /^(\S+?:\/)?\/([^\/\s]+).*$/;
    var server_domain = rss_server.replace(pattern, '$2');
    var location      = window.location.href.replace(pattern, '$2');

    /*
     指定されたRSSのドメインが閲覧中のページのドメインと
   同じならそのままRSSが取得できるので
   違う場合のみ中継サーバーを使う。
    */
    if(server_domain != location){
        url = rss_server + '?rss=' + encodeURIComponent(url);
    }

    if(window.XDomainRequest){
        var xhr = new XDomainRequest();
        xhr.onload  = function(){callback(this.responseXML);};
        xhr.onerror = function(){}
        xhr.open('GET', url, true);
        xhr.send('');
    }else if(window.XMLHttpRequest){
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function(){
            if(this.readyState == 4 && this.status == 200){
                callback(this.responseXML);
            }
        };
        xhr.open('GET', url, true);
        xhr.send('');
    }
}

get_rss('http://example.com/index.rdf');

取得したデータのContent-Typeを調べるにはgetResponseHeaderを使う

今回のスクリプトでは使っていませんが、もし取得したデータのレスポンスヘッダを見るにはgetResponseHeaderメソッドを使います。Content-Typeを取得したい場合は次のようにします。

var content_type = xhr.getResponseHeader('Content-Type');

WordPressプラグイン カテゴリ毎の最新記事表示ウィジェット

WordPressでカテゴリーを指定して最新記事を表示できるウィジェットを作りました。アイキャッチを表示したい場合は画像も表示します。アイキャッチがない場合は記事本文の中から最初の画像を抽出できます。また、画像のサイズは指定可能です。

WordPressでカテゴリーを指定して最新記事を表示できるウィジェットを作りました。アイキャッチを表示したい場合は画像も表示します。アイキャッチがない場合は記事本文の中から最初の画像を抽出できます。また、画像のサイズはピクセル単位で指定可能です。

ルックスは以前公開した「WordPress 同一DB内の別ブログ記事一覧表示ウィジェットプラグイン」と同じです。

最近の投稿が次の図の4つで、このうち「ケーキ」と「天丼食べた」が「グルメ」カテゴリー、その他が「未分類」の場合、カテゴリー別に2個のウィジェットを作ると下の赤枠内のように表示されます。

Recent posts of specific category

表示個数は指定できます。上述しましたが、画像のサイズも変更可能です。

表示個数とサイズは変更可能

インストール方法

WordPressの公式ディレクトリに「Recent posts of specific category」という名前で登録しましたので、ダッシュボードからインストールできます。

プラグインを探す際の目印は、当サイト「Big Bang」の公式アイコンです。

Big Bang

このマークは「Big Bang」の2つのBをかたどって作られたもので、宇宙の波動をイメージしています。


お使いのWordPressにログインしてダッシュボードの「プラグイン」⇒「新規追加」と移動します。

「プラグインを追加」の画面上部にある「プラグインの検索」に「Recent posts of specific category」と入力してEnterを押してください。候補がいくつか出てきますが、マークがBig Bangの公式アイコンで、作者が「dwm.me」になっているものが該当のプラグインです。

Recent posts of specific category

みつからない時は、「作成者」に「dwm.me」と入力して検索しなおしても出てきます。

pluginを作成者「dwm.me」で検索

Recent posts of specific category」が検索できたら「いますぐインストール」をクリックします。すると次の画面に移動します。

プラグインをインストール

ここで「プラグインを有効化」をクリックしてはじめて使えるようになります。「プラグインを有効化」をクリックするとインストール済みプラグイン一覧の画面に遷移します。

プラグインの一覧で、プラグイン名が「指定カテゴリーの最新記事」、説明が「選択したカテゴリに属している投稿の一覧を表示するウィジェットです。アイキャッチも表示することができます。」、作者が「dwm.me」のものが水色の背景になっていれば有効化されています。

プラグイン一覧の中に表示

設定方法

インストールと有効化ができたら、ダッシュボードの「外観」⇒「ウィジェット」と移動します。

画面の左側に使うことが出来るウィジェットの一覧が並んでいるので、そこから「指定カテゴリーの最新記事」を探します。

ウィジェットの選択

これをドラッグ&ドロップして、ウィジェットエリアの中の表示したい場所に配置します。配置できると次のように表示されるので各項目を設定します。

ウィジェットの設定

カテゴリー

ここで表示させたいカテゴリーを選択します。右端の逆三角を押すと登録済みカテゴリーの一覧が出てくるので、その中から選んでください。

タイトル

ここは指定しなくても構いません。ここにタイトルを指定するとウィジェットが表示される際にそのタイトルが表示されます。空欄にしておくと「(選択したカテゴリー名)の最新記事」というタイトルでウィジェットが表示されます。

表示する投稿数

一覧に表示させたい記事数を半角数字で記入します。数字を入れたのに、その数が反映されない場合は半角数字でなく全角で入力していないか確かめて下さい。

数字以外の文字、マイナスの値やゼロなど無効な数が指定された場合は5個表示します。指定個数分だけ記事がない場合はあるだけの表示になります。

リンクを別窓で表示

チェックを入れると、リンクをクリックした時に別窓を開いて表示します。

投稿日を表示しますか?

投稿日を表示させたい場合はチェックに印をつけて下さい。チェックしない場合は日付は表示しません。

日付の前で改行する?

この項目は「投稿日を表示しますか?」がチェックされていない場合は無視されます。

「日付の前で改行する?」をチェックしていない場合は、タイトルの後ろに日付を表示します。チェックを入れるとタイトルの下行に日付を表示します。

サムネイルを表示する?

チェックを入れるとアイキャッチが指定されているページでは、タイトルの左にアイキャッチを表示します。投稿ページにアイキャッチが指定されていない場合、以下の設定をしないと空白になります。

「サムネイルを表示する?」がチェックされていない場合、これより下の項目は指定しても無視されます。

横幅

タイトルの左に表示する画像の横幅を半角数字で指定して下さい。半角数字で指定していない場合、45ピクセルで表示します。

縦のサイズは元の画像に合わせた割合で自動調整します。比率を無視して正方形に表示させたい場合は次の項目にチェックを入れて下さい。

正方形で表示?

ここをチェックすると、正方形でない画像も比率を無視して正方形で表示します。サイズは上の項目「横幅」で指定したものになります。

本文中から画像を抽出する?

アイキャッチが指定されている場合はアイキャッチを表示しますが、アイキャッチがない場合に記事本文から画像を抽出して、最初に見つかったものを表示します。

ただし、横幅あるいは高さが1ピクセルの画像は抽出しません。

デフォルトのサムネイル

アイキャッチが指定されていない場合に、特定の画像を表示させたい時はここにそのURLを指定して下さい。

優先順位として

  • アイキャッチがあれば表示
  • 「本文中から画像を抽出する?」にチェックが付いていて、画像が見つかればそれを表示
  • そのどれもなければ指定したURLの画像を表示

となります。

画像がない時WordPressのロゴを表示?

この項目は「デフォルトのサムネイル」が指定されていれば無視されます。「デフォルトのサムネイル」が未指定で、ここにチェックを入れると、アイキャッチもなく本文中からの抽出画像もない時、タイトルの左にWordPressのロゴを表示します。

以上です。問題があった際はコメント欄にご記入くださると助かります。