さくらのレンタルサーバーで立ち上げたWordPressを完全SSL対応(鍵緑)にするメモ

レンタルサーバーで安く安定した環境を構築する週末

世間はMastodonインスタンス立ち上げがDockerDockerおきているわけですが、この週末は仕事で使うWordpressの整備などに充ててしまいました.

ちょうど最近はレンタルサーバー各社を比較していて,さくら・GMO・Kagoyaなど,月1万円以下のVPSやレンタルサーバーの運用上の評価を行っておりました.レンタルサーバーで安く安定した環境を構築する週末,まあ時間の浪費かもしれませんが「趣味の盆栽」とでも思えば耐えられる.

この業界横並びで,ここ2年ぐらいの傾向としては,SSDなどの増量,共有SSL(SNI;Server Name Indication)対応,Webフォント対応などが主な機能です.あとはコントロールパネルが見やすいかとか,値段は実は結構違いますが,年間数千円の違いといったところでしょうか.

最近の共有SSLはリバースプロキシで実装されている

昔はSSLといえば,固定IPとった上にお高い(数万円/年)印象がありますが,SNIであれば年間数千円,キャンペーンで初年度無料といった価格で入手できます.そもそも2015年以降,GoogleがSSL非対応サイトの評価を大きく下げたことも影響しています(参考:GMOによる解説).

そもそも純然たるSSLと違いって,共有SSLは「保護された通信」とだけ出ます.表示は鍵と緑です.EV証明書がある接続はブラウザのURLの左に鍵緑に加えて,証明機関の名前が出ます.共有SSLはそもそもIPアドレス共有していても名前ベースの認証(SNI)で解決しているので,もしかすると同じIPをもったレンタルサーバーのユーザが(鍵をすべて入手するなど)成り済まそうと思えばできなくもない仕組みです(参考:富士通による詳細で簡単な解説).

また,通常,HTTPとHTTPSはアクセスするサーバーポートが80番と443番と異なります.自サーバーでのHTTPSの設定は,あくまで別のディレクトリ,別のサーバーとして設定していくことが多いのですが,さくらのレンタルサーバーの場合は,明示的にHTTPとHTTPSの仮想サーバー設定(具体的にはhttpd.conf)を触らせてくれません.例えば「www.domain.org」と「domain.org」のHTTPとHTTPSのアクセス先の挙動4種類をそれぞれ変える設定がしたい場合などが大変.デフォルトはwwwとwwwなしを同じディレクトリ,さらにHTTPとHTTPSをリバースプロクシで自動設定,です.それをコントロールパネルだけで設定できなくもないですが,ログも見えないのでとても作業は大変です.レンタルサーバーはそういうところがつらい,一方でサーバスペック的にはお安く上がっているし,セキュリティもさくら任せなので文句も言いづらいところ.

さくらのmod_rewriteの仕様が変わっている(うまくいかない設定例)

もちろん共有SSLが流行り始めて2年ですので,いろいろな人々がさくらのレンタルサーバーWordpress環境で共有SSLに挑戦しています.鍵緑状態になった実績がある設定としては,.htaccessにリバースプロクシによってHTTPSになった状態を判定する「HTTP:X-Sakura-Forwarded-For」を使って mod_rewriteを書く方法,そしてwp-config.phpに消えてしまったサーバ変数を明示的に書く方法.でも結論から言うとこれはうまくいきません(2017/4/16確認).

.htaccess全体
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ – [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
RewriteEngine On
RewriteCond %{REQUEST_URI} !^/robots.txt$
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteCond %{ENV:HTTPS} !^on$
RewriteCond %{HTTP:X-SAKURA-FORWARDED-FOR} ^$
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
wp-config.php 追記分
// プロクシでIPが入るとSSLアクセス状態をセットする
if( isset($_SERVER[‘HTTP_X_SAKURA_FORWARDED_FOR’]) ) {
 $_SERVER[‘HTTPS’] = ‘on’;
 $_ENV[‘HTTPS’] = ‘on’;
 $_SERVER[‘HTTP_HOST’] = ‘domain.org’;
 $_SERVER[‘SERVER_NAME’] = ‘domain.org’;
 $_ENV[‘HTTP_HOST’] = ‘domain.org’;
 $_ENV[‘SERVER_NAME’] =’domain.org’;
}
上記の参考にしたURLのいくつかでも2017年4月の追記で「さくらではリバースプロクシのmod_rewriteをサポートしない」という話がいくつか出ています.で,そういうサイトはURLが鍵緑ではなく「混在」になっています(このサイトはまだSSL設定していないので「保護されていません」です).
なお,これのおかげで最強のバックアッププラグイン「BackWPup」なども動かない説もあり.

WordPressのCSSだけ崩れる問題と Really Simple SSL

上記の設定でも「共有」まではいきます,少なくともサイトのアクセストップだけは鍵緑になりますが,ソースを見てみると,テーマのCSSへのアクセスだけが「http://自サイト」になっており,クリックするとリダイレクトでHTTPSになります.設定によってはHTTPとHTTPSでリダイレクトループに入り,サイトが表示されなくなります.問題は上記のリバースプロクシと,rewriteに起因するのですが,細かい設定ができないから仕方ない.
このような問題を解決するためのプラグインとしていくつかのサイトでは「Really Simple SSL」プラグインが便利であったと書かれています.ですがこれは時間の浪費でした.このプラグインは3つの機能があり,Wordpressによるリライト,301リダイレクト,JavaScriptによるリライトを実施し,サイトやコンテンツを一つ一つ書きなおさなくてもよい…という触れ込みです.実際には .htaccess を勝手に書き換えますので,上記の特殊仕様が入っている環境では慎重に使ったほうが良いと思います.私はこいつのおかげでとてもはまりました.上記のさくら特有の設定などは無視ですので.

どうやって解決するか?

さて,今回の問題ですが,結局以下のような解決策をとりました.
(1) コントロールパネルは[www]と[wwwなし]を共用,.htaccess はできるだけシンプルに
(2) wp-config.php には上記のサーバー変数を一応書いておく.コントロールパネル「一般」のサイトアドレスはhttpsのwwwなしで統一.
(3) サイトにアクセスして,ソース表示して,CSS以外はhttpsでアクセスできていることを確認する(表示は崩れている).
(4) 「SSL Insecure Content Fixer」というSSL用のリライトプラグインを導入
 インストールして設定で「Capture, unable to detect HTTPS」にチェックするだけ.おそらくリライト後のHTML全部をキャプチャして http:// になっているところを書き換えている,とても重い処理と思うのだが,たくさんあるわけではないし,要はこれをやってほしかっただけなので,もうこれでいいと思う!.htaccessに怪しい設定するほうが危険だし,リダイレクトループに入っている頃に比べれば全然快適に動いています.一日悩んだけど解決してよかった!

おまけ:レンタルサーバ公式のプラグインがあればいいのに…と思ったら

さくらのサーバー向けにSSL対応させるWordpressプラグインがあればいいんですよね,実際WordpressのプラグインでSSL関連を見ていると「CloudFlare Flexible SSL」などCDN業者提供のプラグインもちらほら見えます.最近提供されているWebフォント「TypeSquare」なんかもさくらGMOお名前.comも同じようなサポートプラグインをWordpressのプラグインで出しているから,できることなんじゃないかと思います.ちなみにTypeSquareはどちらの会社も月間25000PVまでは無料という微妙な設定ですが,さくらは9,000インストール,GMOは100以下….
っていうか「Sakura」でWordpressプラグイン検索してたら「さくらのレンタルサーバ 簡単SSL化プラグイン 作者: SAKURA Internet Inc.」ってプラグインがあったのですけど!早くいってよ!…ってことで試してみました.

重大なエラーを引き起こしたため、プラグインを有効化できませんでした。

Fatal error: Arrays are not allowed in class constants in /home/user/www/wordpress/wp-content/plugins/sakura-rs-wp-ssl/modules/model/force-ssl.php on line 32

おあとがよろしいようで….そもそもインストールできないレベルだと試せない,まあこのドキュメントは役に立ちそう.

 

WordPress Download Monitor「Download id not defined」と表示されるバグを発見

研究室の論文リストなどを管理するために、Wordpressのダウンロードページを管理する「download monitor」というプラグインを使っている。

フォーマットを定義すると論文の詳細なども表示できるので便利なのだけど、最近何かがおかしい…。

https://wordpress.org/support/plugin/download-monitor

Publications

一部の論文において、「Download id not defined」と表示されてしまう。

  • [相模原市市民協働事業] [Download id not defined]
  • [相模原市市民協働事業] [Download id not defined]

スクリーンショットだとこんな感じ

ちなみにソースにはこんな感じに書いてある。

  • [相模原市市民協働事業] [download id=”126″ format=”4″]
  • [相模原市市民協働事業] [download id=”125″ format=”4″]
  • [相模原市市民協働事業] [download id=”115″ format=”4″]

スクリーンショットやページを見てもわかるように、動いているものと動いていないものがある。ソースをよく確認して、コピペしてみても再現性があったりなかったりで、よくわからないが、ビジュアルエディタの「↓」ボタンから生成した場合は正しい動作をするようだ。

dl

実際の動くコードがこれ
[download id="126" format="4"]
動かないコードがこれ
[download id="126" format="4"]

この動くコードと動かないコードの間には、何か目に見えない霊的なものがあるのだろう…。

ghostinthespace

いやいやいや…テキストエディタで検索してみますよ?

a020

上の緑の検索対象が動かないコード、下の反応がないコードが動くコード。

目では見えないのですが、Unicodeの「a0」、HTMLでいうところの&nbsp(non-break space)が、半角空白「20」の代わりに入っている行が動かないようです。前は動いたのに!

とりあえず作者(Never5, Barry Kooij, Mike Jolley)にバグレポートを出してみよう…。
「a0使うな!」で終わりそうだけど。

<余談>

Download Monitorには旧バージョンのデータを変換するツールも提供されているので最初はこれかなと思ったわけです。結局使わなかったけど、これって本体のAdminページに仕込まれている変換ツールと何か違うのかな?
https://www.download-monitor.com/extensions/dlm-legacy-importer/

Old download shortcodes containing download IDs will mismatch after import because of the new data structure. Whilst active, the legacy plugin will step in if a legacy download ID is called, however it is recommended that you update your content with new IDs to prevent errors.

Evernoteが半角スペースを勝手に0xa0に変換するなんてこともあるようです。
■ ソースコード中に0xC2A0(UTF-8のNO-BREAK SPACE)が混ざり実行できなくて困った話 kk_Atakaの日記
http://d.hatena.ne.jp/kk_Ataka/20130826/1377527170

■ ASCIIコード変換器
http://web-apps.nbookmark.com/ascii-converter/

Twitter Digestを改造した

WordPressがTwitterのStatusURLを入れると,見やすい展開をはかってくれるようになったのに,
Twitter Digestは相変わらずリストでしか表示してくれないし,画像は展開してくれないので,以下のような魔改造を施しました.akiで検索してみてください.
あんまり重たいようだったらやめよう.

それにしてもどうして,定期まとめツイートが走らなかったんだろう…?しばらく様子見.

Twitter account.

Version: 2.9
Author: Tim Beck
Author URI: http://whalespine.org
*/

// Copyright (c) 2009 - 2010 Tim Beck and Paul Wlodarczyk. All rights reserved.
//
// Released under the GPL license
// http://www.opensource.org/licenses/gpl-license.php
//
// This is an add-on for WordPress
// http://wordpress.org/
//
// **********************************************************************
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// **********************************************************************
//
//=======================
// Defines

define('WS_FETCH_SPACE', 30); //X seconds between fetches
define('WS_TWITTER_LIMIT', 200); //the most tweets you can pull from the Twitter REST API
define('WS_API_USER_TIMELINE', 'https://api.twitter.com/1.1/statuses/user_timeline.json');
define('WS_STATUS_URL', 'https://twitter.com/###USERNAME###/statuses/###STATUS###');
define('WS_PROFILE_URL', 'https://twitter.com/###USERNAME###');
define('WS_HASHTAG_URL', 'https://twitter.com/search?q=###HASHTAG###&src=hash');
define('WS_VERSION', '2.9');

//========================
// Digest cron activation

register_activation_hook(__FILE__, 'ws_activate_digest');
register_deactivation_hook(__FILE__, 'ws_deactivate_digest');
add_action('ws_td_digest_event', 'ws_ping_twitter');

function ws_activate_digest() {
wp_schedule_event(time(), 'hourly', 'ws_td_digest_event');
}

function ws_deactivate_digest() {
wp_clear_scheduled_hook('ws_td_digest_event');
}

// Check Twitter to see if there are any new tweets that need to be published.
// Return values:
// null -> all good
// 1 -> Error w/ twitter
// 2 -> Too soon
// 3 -> Missing username/password
// 4 -> Post created and published now or put in draft (depending on status option)
// 5 -> No tweets to post
// 6 -> Post created but not published until later
// 7 -> Wrong day of week

function ws_ping_twitter() {

// Do we have a username?
if (!get_option("ws_td_username")) {
return 3;
}

// We don't need the password anymore, so delete it.
if (get_option("ws_td_password")) {
delete_option("ws_td_password");
}

// Has there been enough time since the last check? This avoids a race
// condition which would produce a duplicate post.

$last = get_option('ws_td_last_check');
if (time() - $last < WS_FETCH_SPACE) { return 2; } update_option("ws_td_last_check", time()); // Is this a daily or weekly post? // Need to know today's local midnight to work out start/end times $midnight = strtotime(date('Y-m-d, 00:00:00', time() + ws_gmtOffset())); $pub_day = get_option('ws_td_pub_day'); $pub_freq = get_option('ws_td_freq'); $current_day_of_week = date("w", $midnight); // Test if it's weekly, then if so is today the right day of the week. If // not, return; pub_freq=0 is daily if ($pub_freq == 1) { if ($current_day_of_week != $pub_day) { return 7; } else { // 7 days ago at midnight $startDate = strtotime('-7 days', $midnight); } } else { // yesterday at midnight $startDate = strtotime('-1 days', $midnight); // $startDate = strtotime('-15 days', $midnight); } // yesterday at 11:59:59 $endDate = strtotime('-1 second', $midnight); $startDateStr = date(ws_getDateFormat(), $startDate); $endDateStr = date(ws_getDateFormat(), $endDate); // Get the last tweet id from the last post $lastTweet = get_option('ws_td_last_tweet'); if (!$lastTweet) { $lastTweet = 0; update_option('ws_td_last_tweet', $lastTweet); } // Get the tweets $numtweets = get_option('ws_td_num_tweets'); // range check. Twitter limit is to 200 if ($numtweets > WS_TWITTER_LIMIT) {
$numtweets = WS_TWITTER_LIMIT;
}

if ($numtweets == 0) {
$numtweets = 20; //default is 20 with no count argument, so fetch 20 if NULL
}

// get the last N tweets, since the last tweet
$url = WS_API_USER_TIMELINE . "?screen_name=".get_option('ws_td_username') ."&count=". $numtweets ;

if (get_option('ws_td_includeRTs')) {
$url .= '&include_rts=1';
}

if ($lastTweet) {
$url .= "&since_id=" . $lastTweet;
}

// Fetch the tweets
$tweets = ws_fetch_tweets($url);

// Go through the array and process any tweets from the desired time period
$tweet_content = array();
$tweet_ids = array(); //aki
$newLastTweet = false;
if ($tweets && count($tweets) >= 0) {
// process the tweets

foreach ($tweets as $tw_data) {
// Convert the time to local
$tw_data->created_at = strtotime($tw_data->created_at) + ws_gmtOffset();

// Was this tweet added in the time period of interest?
if ($tw_data->created_at < $startDate) { continue; } if ($tw_data->created_at > $endDate ) {
continue;
}

// Are we dropping replies?
if (get_option('ws_td_drop_replies') && preg_match('/^@.*/', $tw_data->text)) {
continue;
}

// All good, so format and add to the content
array_push($tweet_content, ws_format_tweet($tw_data)."\n\n");
array_push($tweet_ids, ws_format_tweet_id($tw_data)); //aki

// Remember the first tweet in the list, which is actually the most
// recently tweeted.
if (!$newLastTweet) {
$newLastTweet = $tw_data->id_str;
}
}

// To avoid race conditions, check to make sure lastTweet hasn't changed
if ($lastTweet == get_option('ws_td_last_tweet')) {

$count = count($tweet_content);

if ($count != 0 && $count >= get_option('ws_td_min_tweets')) {

// Before we push the content, we need to put it in the right order
if (get_option('ws_td_chrono') == 1) {
$tweet_content = array_reverse($tweet_content);
$tweet_ids = array_reverse($tweet_ids); //aki
}

$title = get_option('ws_td_title');
if (!$title) {
$title = "Tweets for %startdate";
}

// We look for %date to be backwards compatible
$title = preg_replace("/%date/", $startDateStr, $title);
$title = preg_replace("/%startdate/", $startDateStr, $title);
$title = preg_replace("/%enddate/", $endDateStr, $title);

$excerpt = get_option('ws_td_excerpt');
$excerpt = preg_replace("/%date/", $startDateStr, $excerpt);
$excerpt = preg_replace("/%startdate/", $startDateStr, $excerpt);
$excerpt = preg_replace("/%enddate/", $endDateStr, $excerpt);

//aki start
$postcontent = join("\n", $tweet_ids);
$postcontent .= "\n\n

    \n\n";
    //aki end
    //aki $postcontent .= "

      \n\n";
      $postcontent .= join("\n\n", $tweet_content);
      $postcontent .= "\n

    ";

    // This is messy, but good enough for now.
    if (ws_create_post($postcontent, $title, get_option('ws_td_pub_time'), $excerpt)
    == 1 /* Published in future */) {
    $retval = 6;
    } else {
    // Published now or drafted.
    $retval = 4;
    }

    // Update the last tweet id
    update_option('ws_td_last_tweet', $newLastTweet);

    return $retval;

    }
    else {
    return 5;
    }
    }

    }
    else {
    return 5;
    }

    }

    // This function creates the actual post and schedules it for publishing time
    // at $pubtime, if the status option is set to 'publish'. Otherwise the post
    // is put in 'draft' status.
    // Return values:
    // 0: published now
    // 1: published in future
    // 2: drafted
    function ws_create_post($content, $title, $pubtime, $excerpt) {

    global $wpdb;
    $result = 0;

    // Are we putting this in draft or publishing (now or later)?
    if (get_option('ws_td_status') == 'draft') {
    $status = 'draft';
    $result = 2;
    } else {

    $status='publish';

    // Are we doing this now or later?
    if ($pubtime) {
    $time = date('Y-m-d ').$pubtime.":00";
    $timestamp = strtotime($time);
    if ($timestamp > current_time('timestamp')) {
    $result = 1;
    }
    } else {
    $time = current_time('mysql');
    }
    }

    // Create the post
    $post_data = array(
    'post_content' => $wpdb->escape($content),
    'post_title' => $wpdb->escape($title),
    'post_date' => $time,
    'post_category' => array(get_option('ws_td_category')),
    'post_status' => $status,
    'post_author' => $wpdb->escape(get_option('ws_td_author')),
    'post_excerpt' => $wpdb->escape($excerpt)
    );

    // Insert post
    $post_id = wp_insert_post($post_data);
    add_post_meta($post_id, 'ws_tweeted', '1', true);

    // Tag it
    wp_set_post_tags($post_id, get_option('ws_td_post_tags'));

    return $result;
    }
    // customized by aki 2014/3/26
    function ws_format_tweet_id($tweet) {
    $output = ws_status_url(get_option('ws_td_username'), $tweet->id_str);
    return $output;
    }

    // Returns an html formatted $tweet. This is almost directly borrowed from Twitter Tools
    function ws_format_tweet($tweet) {
    $output = '

  • ';
    $output .= ws_make_clickable(wp_specialchars($tweet->text));
    if (!empty($tweet->in_reply_to_screen_name)
    && (!empty($tweet->in_reply_to_status_id))) {
    $output .= ' '.sprintf(__('in reply to %s', 'twitter-digest'), $tweet->in_reply_to_screen_name).'';
    }

    // Show the date/time if the options are selected
    $showTime = get_option('ws_td_showtime');
    if (!$showTime) { $showTime = 0; }
    // Show the date if the option is selectd
    $showDate = get_option('ws_td_showdate');
    if (!$showDate) { $showDate = 0; }

    $time_display = '';
    if ($showTime == 1) {
    $time_display = gmdate('H:i:s', $tweet->created_at);

    // Add the comma if we are going to show the date as well
    if ($showDate == 1) {
    $time_display .= ", ";
    }
    }

    // Add the date
    if ($showDate == 1) {
    $time_display .= gmdate('Y-m-d', $tweet->created_at);
    }

    // Use a small arrow for the status links if time and date are off
    if ($showDate != 1 && $showTime != 1) {
    $time_display = "->";
    }

    // Add the status link
    $username = get_option('ws_td_username');
    $output .= ' '.$time_display.'';

    $output .= '

  • ';
    return $output;

    }

    // Most of the following formatting functions are borrowed from Twitter Tool
    function ws_make_clickable($tweet) {

    // $tweet = preg_replace('/\@([a-zA-Z0-9_]{1,15}) /','@\\1 ', $tweet);
    $tweet = preg_replace_callback(
    '/((?:^|\s+|[\"\'\[\(]))\@([\w]{1,30})/',
    create_function(
    '$matches',
    'return $matches[1].ws_profile_link($matches[2]);'
    ),
    $tweet
    );
    $tweet = preg_replace_callback(
    '/((?:^|\s+|[\"\'\[\(]))\#([\w]{1,30})/',
    create_function(
    '$matches',
    'return $matches[1].ws_hashtag_link($matches[2]);'
    ),
    $tweet
    );

    // make_clickable doesn't handle (url) very well, so we'll chuck some spaces in
    $tweet = preg_replace("/\((http.*)\)/", "( $1 )", $tweet);

    return make_clickable($tweet);
    }

    function ws_status_url($username, $status) {
    return str_replace(
    array('###USERNAME###', '###STATUS###'),
    array($username, $status),
    WS_STATUS_URL);
    }

    function ws_profile_link($username, $prefix = '@') {
    return ''.$prefix.$username.'';
    }

    function ws_hashtag_link($hashtag, $prefix = '#') {
    return ''.$prefix.htmlspecialchars($hashtag).'';
    }

    function ws_profile_url($username) {
    return str_replace('###USERNAME###', $username, WS_PROFILE_URL);
    }

    function ws_hashtag_url($hashtag) {
    $hashtag = urlencode('#'.$hashtag);
    return str_replace('###HASHTAG###', $hashtag, WS_HASHTAG_URL);
    }

    // Based on Alex King's implementation for the Twitter Tools plugin
    function ws_fetch_tweets($url) {

    // First get our oauth connection
    require_once("twitteroauth.php");
    $consumerkey = get_option('ws_td_conskey'); // "3JucGrvAxOuaAYbLml5nkQ";
    $consumersecret = get_option('ws_td_conssecret'); //"iIRXb7c0VFwTYmI4TCNNIrUWB2YDxmL6c11YogcqQw0";
    $accesstoken = get_option('ws_td_acctoken'); // "14198382-SnzuP0AMz3qVWTolneB8QG7D1kHt6WNMosZzb48LE";
    $accesstokensecret = get_option('ws_td_accsecret'); // "9O7sc2tbea8PEx3Py9S4VWsHkbZtDBcP0n4HvnzokqM";

    if (!$consumerkey || !$consumersecret || !$accesstoken || !$accesstokensecret) {
    update_option('ws_td_error', 'Check your account information and make sure all token fields have a valid value.');
    return false;
    }

    $connection = new TwitterOAuth($consumerkey, $consumersecret,
    $accesstoken, $accesstokensecret);

    // Now make the request
    $tweets = $connection->get($url);

    // Check the error code
    if(empty($connection->http_code) || $connection->http_code != 200) {
    update_option('ws_td_error', 'Error retrieving tweets.
    Status: '.$connection->http_code.' Check your authentication tokens.
    '.print_r($connection->http_info, true));
    return false;
    }

    // Everything is ok
    update_option('ws_td_error','');

    return $tweets;
    }

    // Simply resets the 'ws_td_last_tweet' option
    function ws_td_resetDatabase() {
    update_option('ws_td_last_tweet',0);
    }

    // The menu hook
    function ws_menu_item() {
    if (current_user_can('manage_options')) {
    add_options_page(
    __('Twitter Digest Options', 'twitter-digest')
    , __('Twitter Digest', 'twitter-digest')
    , 'manage_options'
    , basename(__FILE__)
    , 'ws_options_form'
    );
    }
    }
    add_action('admin_menu', 'ws_menu_item');

    function ws_options_form() {

    // Check here if we are going to do the check now
    global $wpdb;

    // Reset the database if necessary
    if (isset($_POST["action"]) && $_POST["action"] == 'resetdb') {
    ws_td_resetDatabase();
    }

    // Get some variables together
    $ping_message = "(This may take a while if there are tweets to process.)";
    $ping_style="color: black";

    // Range check the publish day
    $pub_day_value = get_option('ws_td_pub_day');
    if ($pub_day_value < 0 || $pub_day_value > 6) { $pub_day_value = date("w"); }

    // yesterday or last week?
    if (get_option('ws_td_freq') == 0) {
    $periodStr = 'yesterday';
    } else {
    $periodStr = 'last week';
    }

    // Ping if necessary, and show the correct message.
    if (isset($_POST["action"]) && $_POST["action"] == "ping") {
    switch(ws_ping_twitter(true)) {
    case 1:
    $pwuser = get_option('ws_td_username');

    $ping_message = "Something went wrong with Twitter. Check for an error message above. Current Twitter username is " . $pwuser. "." ;
    $ping_style = "color: red";
    break;
    case 2:
    $ping_message = "To keep things sane, we're going to wait ".WS_FETCH_SPACE." seconds between pings.";
    $ping_style = "color: red";
    break;
    case 3:
    $ping_message = "Missing Twitter username and/or password";
    $ping_style = "color: red";
    break;
    case 4:
    $ping_message = "Post containing ".$periodStr."'s tweets has been ".(get_option('ws_td_status'). 'ed.');
    $ping_style ="color: green";
    break;
    case 6: $ping_message = "Post containing ".$periodStr ."'s tweets scheduled.";
    $ping_style = "color: green";
    break;
    case 5: $ping_message = "No new tweets found from " . $periodStr;
    break;
    case 7:
    $daylabel = date("l");
    $pub_day_str = ws_getDoW($pub_day_value);
    $now = date("H:i:s");
    $pub_time_str = get_option('ws_td_pub_time');
    if (!$pub_time_str) { $pub_time_str = '00:00:00'; }

    $ping_message = "Today is " . $daylabel . " and the local time is " . $now . " and you are configured to publish on " . $pub_day_str . " at " . $pub_time_str . ".";
    $ping_style = "color: red";
    }
    }

    // Get all the options together
    $categories = get_categories('hide_empty=0');
    $cat_options = '';
    foreach ($categories as $category) {
    if ($category->term_id == get_option('ws_td_category')) {
    $selected = 'selected="selected"';
    }
    else {
    $selected = '';
    }
    $cat_options .= "\n\t";
    }

    // get_users_of_blog is deprecated.
    $authors = get_users_of_blog();
    $author_options = '';
    foreach ($authors as $user) {
    $usero = new WP_User($user->user_id);
    $author = $usero->data;
    // Only list users who are allowed to publish
    if (! $usero->has_cap('publish_posts')) {
    continue;
    }
    if ($author->ID == get_option('ws_td_author')) {
    $selected = 'selected="selected"';
    }
    else {
    $selected = '';
    }
    $author_options .= "\n\t";
    }

    // Set up the options for the status. Just draft or publish for now.
    $status_options = '';
    if (get_option('ws_td_status') == 'draft') {
    $status_options ='';
    } else {
    $status_options ='';
    }

    // Drop replies options
    $drop_replies_check = get_option('ws_td_drop_replies') == 1 ? 'checked="true"' : '';
    // Chrono options
    $chrono_check = get_option('ws_td_chrono') == 1 ? 'checked="true"' : '';
    // Show date options
    $showDate_check = get_option('ws_td_showdate') == 1 ? 'checked="true"' : '';
    $showTime_check = get_option('ws_td_showtime') == 1 ? 'checked="true"' : '';
    $includeRTs_check = get_option('ws_td_includeRTs') == 1 ? 'checked="true"' : '';

    // Publishing frequency options
    if (get_option('ws_td_freq') == 1) {
    $freq_options = '';

    $pub_day_display = '';
    } else {
    $freq_options = '';
    $pub_day_display = 'display: none';
    }

    // DoW option
    $pub_day_options = '';
    for ($i = 0; $i < 7; $i++) { $pub_day_options .= '

    // Default the min tweet value to 1
    $min_tweets_value = get_option('ws_td_min_tweets');
    if ($min_tweets_value < 1) { $min_tweets_value = 1; } // Default the number of tweets to 20 $num_tweets_value = get_option('ws_td_num_tweets'); if (!$num_tweets_value) { $num_tweets_value = 20; } print('

    '.__('Twitter Digest Options', 'twitter-tools').'

    '.ws_getErrorMessage().'


    '.wp_nonce_field('update-options').'


    Twitter Account Info






    Publish Options




    '._('Specify the time at which the tweet post should be published in 24 hour format e.g. 2:22pm = 14:22 or leave empty for ASAP').'

    '._('Specify the day of the week the post should be published in number format (0-6). e.g. Sunday = 0').'

    Post Options



    '._('Use %startdate and %enddate to insert the date').'


    '._('Use %startdate and %enddate to insert the dates').'


    '._('Defaults to Y-m-d').'



    '._('Separate multiple tags with commas. Example: tweets, twitter').'











    '.$ping_message.'



    Clicking this button resets the Twitter Digest database and may result in duplicate posts.

    ');
    }

    // Get the date option or fall back to the default
    function ws_getDateFormat() {
    $format = get_option('ws_td_dateFormat');
    if ($format == '') {
    $format = 'Y-m-d';
    }
    return $format;
    }

    // Get the string rep of a day given the index. Sunday = 0;
    function ws_getDoW($num) {
    switch($num) {
    case 0:
    return "Sunday";
    break;
    case 1:
    return "Monday";
    break;
    case 2:
    return "Tuesday";
    break;
    case 3:
    return "Wednesday";
    break;
    case 4:
    return "Thursday";
    break;
    case 5:
    return "Friday";
    break;
    case 6:
    return "Saturday";
    }

    }

    // Returns the gmt offset for WordPress in seconds
    function ws_gmtOffset() {
    return get_option('gmt_offset') * 3600;
    }

    function ws_gmtTime() {
    return time() - date("Z");
    }

    function ws_getErrorMessage() {
    $error = get_option('ws_td_error');
    if ($error) {
    return '

    '.$error.'

    ';
    }
    }

    define('MYPLUGIN_FOLDER', dirname(__FILE__));

対策法:WordPressでテーマを選んだら真っ白になった

研究室のWordpressで、とあるユーザが「テーマを選んだら画面が真っ白になって、ダッシュボードにも入れなくなりました!!」とヘルプを求めてきたので、以下メモ。

現象としては、ライブプレビューで「deep-silent」というテーマを選んでいて、

『なんかよさそうだな―』とおもって適用したとたんに真っ白になった、とのこと。

まあここまで状況説明できるユーザならまだいいのですが、管理者としてサーバにログインできるなら、

# tail /var/log/httpd/error-log
を見てみてほしい。

今回の場合はこんなエラーが出ていました。

[Mon Feb 18 17:20:39 2013] [error] [client 223.218.158.67] PHP Parse error:  syntax error, unexpected $end in /var/www/wordpress/wp-content/themes/deep-silent/functions.php on line 409

というわけで、下手人は「deep-silent」、しかもSyntax Errorとは!
とりあえず、Googleで調べてみましょう。いちおうダッシュボードからインストールしたテーマであれば公式にあるはずです。

http://wordpress.org/extend/themes/deep-silent

[browser-shot url=”http://wordpress.org/extend/themes/deep-silent” width=”600″]

よく見ると、

This theme hasn’t been updated in over 2 years. It may no longer be maintained or supported and may have compatibility issues when used with more recent versions of WordPress.

って最終更新日が2009年。もう3年近く更新されていない。
こういうテーマは新しいWordpressに対応していないばかりか、作者がやる気を失っているか、お亡くなりになったか、そもそも責任感がない人の作品なので継続して使い続けるのは危ないです。

サーバーログが見れないレンタルサーバーなどであった場合、wp-config.phpでデバッグモードをONにすれば、当該ページは真っ白ではなく、エラーが表示されます。
//define('WP_DEBUG', false);
define('WP_DEBUG', TRUE);

としておけばよいでしょう。viの使い方がわからない人はWinSCPなどで編集すればいいです。

で、問題のページを開けば、真っ白ではなくエラーが出るはずです。
今回の問題はログで指摘されている通り、functions.php の書式エラーで409行にいきなり終了が現れたと。
vi ./wp-content/themes/deep-silent/functions.php
とするとviが開きますので「ESC-:-409」と打って、409行目に飛びましょう。エスケープキーを押して「:409」です。

[php]
function coba(){
global $wpdb;
if($_POST[‘send’]==’setting’){
$tmp_name =& $_FILES[‘gambar’][‘tmp_name’];
$author_id ="test";
$ext="jpg";
$name = ABSPATH . ‘wp-content/authors/’ . $author_id . ‘.’ . $ext;
@move_uploaded_file($tmp_name, $name);
@chmod($name, 0666);
}
?>
<div class="wrap">
<form action="#" method="post" enctype="multipart/form-data">
<input type="hidden" name="send" value="setting">
<table width="80%" border="">
<tr><td align="center" colspan="2"><b>Upload photo</b></td></tr>
<tr><td align="center"><input type="file" name="gambar"></td></tr>
<tr><td align="center"><input type="submit"></td></tr>
</table>
</form>
</div>
<?
}
?>
[/php]

これは…!何が間違っているかわかりますか?
下から2行目を「 (viでは、「ESC-i」として編集モードにして、書き足したら、「ESC-:wq」で書き込み終了)

環境によってはphpは
<?~?> でも通りますが、それはphp.iniで設定しないと無理ですね。

これにて一件落着!

無事にダッシュボードが見れたので、他のテーマに移行するか自分で書いてな~。

wordpress3.5の新機能すごい

とりあえずサーバメンテついでに色々試しているけど、これはすごいね!

特にメディアライブラリの機能。

いちばんビックリしたのが、Twitterとの連携。

http://t.co/6MTAnVgR

という感じでiPhoneを使って画像添付で呟いた投稿が、そのままメディアライブラリに取り込まれている…!

ちなみにt.coなどの短縮URLも自動展開されるっぽいが、今のところ内部動作はよくわからない、既存のプラグインが持っている機能とぶつかってる気もする。

http://t.co/6MTAnVgR

画像ファイルは pic.twitter.com からではなく、実際のファイルがサーバ側に落ちているようです。

これは撮った写真のバックアップにもなるではないか。

一生懸命、nextGen Galleyプラグインとか、単体でGallery3とかインストールしてたけど、これらは非公開ギャラリー向けの機能になりそうですね。

他にはHTMLソースモードが「テキスト」なったとか。テーマが2013になったとか。

マルチサイトにも変更があったようですが、まだよくわかりません。継続調査中。

いずれにせよ、たくさん写真を撮る年末年始に良い機能だわ~!

オリジナルフォントでBlogを書いてみた

メディアアート講義20121206板書より
【メディアアート講義20121206板書より】

メディアアートの講義と3年生向けゼミで「手書き文字の重要性」について語ったので、
かねてからの夢であった「自分の文字でBlogを書く」という行為を実現してみた。

【使ったソフト】
おれん字 2

まるで手書き

http://www.amazon.co.jp/exec/obidos/ASIN/B000BDKUEM/
[browser-shot url=”http://www.amazon.co.jp/exec/obidos/ASIN/B000BDKUEM/amazonas-22/” width=”600″]

実際に使ったのは「手作りおれん字」。
このバージョンはちょっと安いけど、不在の場合は豆腐「・」になってしまうので高いヤツのほうがいいと思う。

原稿ファイルも横書きだとうまくスキャナが読み取ってくれないので、縦書のほうが良い。
ソフトの説明には「1ミリ程度」と書いてあるけど、細すぎて読みづらい。
作りたい人にもよるかもしれないが。
あと、半角英数は時間かけたほうが良い。記号もないと長音がないので読めない。

Blogで使うにはTTFだけでなくEOTも作っておくほうが良い。

【TTFフォントからEOTフォントに変換するサイト】
http://ttf2eot.sebastiankippe.com/
[browser-shot url=”http://ttf2eot.sebastiankippe.com/” width=”600″]

WordPress/wp-content/theme/–テーマ名–/にTTFとEOTファイルを置く。
style.cssを編集。

ファイル名は「aki2012」とした(歳を追うごとに字体が変わって来ているので…)

[css]
@font-face {
font-family: ‘aki2012’;
src: url(aki2012.eot);/* IE */
}
@font-face {
font-family: ‘aki2012’;
src: local(‘aki2012’),
local(‘aki2012’),
url(‘aki2012.ttf’) format(‘truetype’); /* Safari、Chrome、Firefox、Opera */
}

body {
background:#fff url(&amp;quot;images/top.gif&amp;quot;;) repeat-x;
font-family: &amp;quot;aki2012&amp;quot;,略,Helvetica, Arial, sans-serif;
font-size:20px; //大きめの方が良い
color:#222;
padding:0;
margin:0;
}
[/css]

で、Wordpressに仕込むとこんな感じのサイトになった。
もちろんChromeだと表示できる。iPhoneでもOK。
で、もちろん読みづらくなった。
でもいいんだ、このサイトはこれで。私の魂の叫びなので。

ちゃんとGoogleにもひっかかるし、フォント変えたり、コピペすれば読めますよね?

そのうち第2水準漢字まで頑張ってみようと思います。

20121206-001653.jpg

写経の山が出来上がる。

20121206-001701.jpg

最初のバージョン。よくみると分かるのですが、ひらがなカタカナが細すぎます。キャラじゃない感じ。

マッキーの極細から太い側に持ち替えて書き直しです。

20121206-005226.jpg
だいぶんとできあがってきました。

iPhone/携帯版なら読めるという選択性も残したゾ。

mysqlのrootパスワードを忘れたら

mysqlのパスワードって必要な人には必要なんだろうけど,必要ない人にとっては本当に忘れそう….
しかもphpのソースコードにも書いてあったりするから,迂闊なマスターパスワードを設定したら,逆にセキュリティホールになってしまいそうだし.

で,root@localhostのパスワードを忘れました.すみません.
たぶん,centos6のyumでインストールした後,セットアップスクリプトが走るのですが,そこで設定したパスワードをすっかり忘れたようです.
メモは取る癖をつけているのに何も残ってないので,恐らく寝落ちしたのでしょう.
どこまでセットアップが終わっているのかもわからないので,アンインストールしてしまえばいいのかもしれないのですが,
「もし生きているサービスを消してしまったら…」と思うとしのびないので,rootのパスワードだけ初期化してみます.

参考:
http://dev.mysql.com/doc/refman/4.1/ja/resetting-permissions.html

使っている環境ではmysql-server-5.1.61-4でした.

 /usr/libexec/mysqld --skip-grant-tables

ではうまく行かなかったので,

mysql -u root mysql
mysql> UPDATE user SET Password=PASSWORD('passwdh0geh0ge') WHERE User='root';
mysql> FLUSH PRIVILEGES;

という感じに,mysqlの管理テーブルを直接更新して,

/etc/rc.d/init.d/mysqld restart

するだけで片付きました.

ちなみにもともとやりたかったのは,wordpress用のDBを作りたかったので,ついでにやってしまうべきでした.
参考:http://www.adminweb.jp/wordpress/install/index1.html

create database wpdb;
grant all on wordpressdb.* to 'wpadmin'@'localhost' identified by 'password';

これでwpdbというデータベースがwpadminというユーザ,パスワードはpasswordで作成されました.インストール時にこれを設定すれば良いわけです.

以上一件落着!

 

WordPressのパーマリンク設定

昔は大変だったのに、けっこう簡単、だった。
http://wpdocs.sourceforge.jp/Using_Permalinks

Kagoya VPS、CentOS6にて。

<VirtualHost *:80>
ServerAdmin root@hogehoge
ServerName aki.shirai.as
DocumentRoot /home/aki/public_html
ErrorLog logs/aki-error_log
CustomLog logs/aki-error_log common
<Directory “/home/aki/”>
  Options Indexes FollowSymLinks
  AllowOverride FileInfo
</Directory>
</VirtualHost>

この2行だけ。

.htaccessはchown -R apache しておけば自動生成されています。