WordPressが生成したサムネイル画像からexif情報を削除する

WordPressで画像をアップロードするとサムネイル画像が生成されますよねー。
そのサムネイル生成方法はサーバー設定でImagickが有効化されているとImagickがGDが有効な場合はGDが使用されています。
うちの環境はImagickを実行するとApacheを巻き込んでクラッシュするので仕方なくというかPHPではメジャーで軽いGDほぼ一択。

しかしGDでJPEGを生成するとexifコメントに「CREATOR: gd-jpeg v1.0 (using IJG JPEG v90), quality = ...」って情報が追加されてしまいます。
たかが数バイトのデータですが、Yslow等のWebサイトの計測ページでは警告されマイナスポイントになりますし、要らないデータなのでない方がいいかなー。

なので、アップロードした画像と作成されるサムネイルのexif情報を削除するプラグインを作ってみました。
サムネイルのexif削除はGDを使用しているサーバー向けで、Imagickを使ってる場合はサムネイルにはexif情報は記録されません。

exifを削除するのに使用する物はImageMagickのスタンドアローンバージョン又はIMagick。
スタンドアローンを使う場合は、ImageMagickがインストールされている環境でないとダメ。

まずは完成PHPソース。

<?php
/*
Plugin Name: Remove EXIF
Plugin URI: http://blog.wolfs.jp/20160707-3638/
Description: アップロードした画像とサムネイル画像のexif情報を削除します。
Version: 1.0.0
Author: Kerberos
Author URI: http://blog.wolfs.jp/
*/
 
class removeExif {
    private $imagemagick_cli = true;
    private $imagemagick_path = '/usr/imagemagick/convert';
 
    public function __construct() {
        add_filter('wp_handle_upload', array($this, 'wp_handle_upload'));
        add_filter('wp_generate_attachment_metadata', array($this, 'wp_generate_attachment_metadata'), 10, 2);
    }
 
    public function wp_handle_upload($arg) {
        if ($this->check_mime($arg['type']) === true) {
            $this->remove_exif($arg['file']);
        }
 
        return $arg;
    }
 
    public function wp_generate_attachment_metadata($metadata, $attachment_id) {
        $dirArr = explode('/', $metadata['file']);
        $baseDir = wp_upload_dir(null, false);
        $uploadDir = $baseDir['basedir'].'/'.$dirArr[0].'/'.$dirArr[1].'/';
 
        foreach ($metadata['sizes'] as $entry) {
            if ($this->check_mime($entry['mime-type']) === true) {
                $this->remove_exif($uploadDir.$entry['file']);
            }
        }
 
        return $metadata;
    }
 
    private function check_mime($mime = null) {
        return ($mime === 'image/jpeg' || $mime === 'image/jpg');
    }
 
    private function remove_exif($filePath) {
        $filePath = addslashes($filePath);
 
        if (file_exists($filePath) === true) {
            if ($this->imagemagick_cli === true) {
                if (is_executable($this->imagemagick_path) === true) {
                    try {
                        exec('"'.$this->imagemagick_path.'" "'.$filePath.'" -strip "'.$filePath.'"');
                    } catch (Exception $e) {}
                }
            } else {
                if (class_exists('Imagick') === true) {
                    $im = new Imagick($filePath);
 
                    try {
                        $im->stripImage();
                        $im->writeImage($filePath);
                        $im->clear();
                        $im->destroy();
                    } catch (Exception $e) {}
                }
            }
        }
    }
}
 
new removeExif();
?>

かなり簡単なコード。(`・ω・)b

各ファンクションの簡単な説明は・・・
続きを読む

WordPressのパスワード保護している記事のtitleも変更する

今現在、WordPressでパスワード保護された記事の本文やコメントは非表示になりますが、titleエレメントやRSSなんかは非表示になりません。
今回は非推奨になったwp_titleとWordPress 4.4から登場したadd_theme_support('title-tag')を有効化したテーマ等で使うwp_get_document_title()でも使える方法でいきます。

序でに「非公開:」や「保護中:」って文字も削除・変更しちゃいますー。

まずは完成したWordPressのプラグインコード。

<?php
/*
Plugin Name: Change Password POST Title
Plugin URI: http://blog.wolfs.jp/20160513-3616/
Description: パスワードで保護された記事のタイトルを変更します。
Version: 1.0.0
Author: Kerberos
Author URI: http://blog.wolfs.jp/
*/
 
class changePasswordPostTitle {
    private $plugin_dir = ABSPATH.'/wp-content/plugins/change_password_post_title/feed/';
    private $post_password_title = '[パスワードで保護された記事]';
 
    public function __construct() {
        add_filter('the_title', array($this, 'the_title'), 10, 2);
        add_filter('pre_get_document_title', array($this, 'pre_get_document_title'));
        add_filter('protected_title_format', array($this, 'protected_title_format'));
 
        add_filter('aioseop_title_single', array($this, 'aioseop_title'));
        add_filter('aioseop_title_page', array($this, 'aioseop_title'));
 
        remove_action('do_feed_rss2', 'do_feed_rss2');
        remove_action('do_feed_atom', 'do_feed_atom');
        add_action('do_feed_rss2', array($this, 'do_feed_rss2'), 10, 1);
        add_action('do_feed_atom', array($this, 'do_feed_atom'), 10, 1);
    }
 
 
    public function the_title($title = '', $currentID = 0) {
        global $post;
 
        if ($currentID !== 0) {
            if (post_password_required($currentID) === true) {
                if ($post->ID == $currentID && is_admin() === false) {
                    return $this->post_password_title;
                }
            }
 
            return $this->remove_title_prefix($title);
        }
 
        return $title;
    }
 
    public function pre_get_document_title($title) {
        if (is_single() === true || is_page() === true) {
            if (post_password_required($post->ID) === true) {
                return $this->post_password_title;
            }
 
            return $this->remove_title_prefix($title);
        }
 
        return $title;
    }
 
    public function protected_title_format($title) {
        global $post;
 
        if (post_password_required($post->ID) === false) {
            return '&#x1F512; %s';
        }
 
        return $this->post_password_title;
    }
 
    public function aioseop_title($title) {
        global $post;
 
        if (post_password_required($post->ID) === true) {
            return $this->post_password_title;
        }
 
        return $title;
    }
 
    private function remove_title_prefix($title) {
        $search[0] = '/^' . str_replace('%s', '(.*)', preg_quote(__('Protected: %s'), '/' )) . '$/';
        $search[1] = '/^' . str_replace('%s', '(.*)', preg_quote(__('Private: %s'), '/' )) . '$/';
 
        return preg_replace($search, '$1', $title);
    }
 
 
    public function do_feed_rss2($for_comments) {
        if ($for_comments) {
            load_template($this->plugin_dir.'feed-rss2-comments.php');
        }
    }
 
    public function do_feed_atom($for_comments) {
        if ($for_comments) {
            load_template($this->plugin_dir.'feed-atom-comments.php');
        }
    }
}
 
new changePasswordPostTitle();
?>

フィルターのフック名と関数は同じ名前にしてあるのでわかると思うので、その辺は略しますw
このプラグインでパスワード保護ページはこうなります:サンプルページ
続きを読む

XML-RPCへのアクセスが激化

ここ2日ほどでXML-RPCの脆弱性を狙ったアクセスが急上昇しました。
WordPress - XML-RPC ログ
ログの殆どがXMLRPC関連で2日で600件前後ありました。

アクセスしてくるときの一部のデータはこんな感じ

[AuthDigestEnableQueryStringHack] => On
[CONTENT_LENGTH] => 246
[CONTENT_TYPE] => text/xml
[GATEWAY_INTERFACE] => CGI/1.1
[HTTP_HOST] => blog.wolfs.jp
[HTTP_USER_AGENT] => Mozilla/4.0 (compatible: MSIE 7.0; Windows NT 6.0)
[PHP_SELF] => /xmlrpc.php
[QUERY_STRING] =>
[REMOTE_ADDR] => 5.39.85.104
[REMOTE_PORT] => 55470
[REQUEST_METHOD] => POST
[REQUEST_TIME] => 1411214404
[REQUEST_TIME_FLOAT] => 1411214404.624
[REQUEST_URI] => /xmlrpc.php
[SCRIPT_NAME] => /xmlrpc.php
[SERVER_ADDR] => 180.147.107.163
[SERVER_PORT] => 80
[SERVER_PROTOCOL] => HTTP/1.0
[dont-vary] => 1
[no-gzip] => 1
[POST] => ...

 
送られてくるXMLも変わらずこんなかんじ。

これはpingback
<?xmlversion="1.0"?>
<methodcall>
    <methodname>pingback.ping</methodname>
    <params>
        <param /><value><string>http://kolgeda.com/select_.html</string></value>
        <param /><value><string>http://blog.wolfs.jp/</string></value>
    </params>
</methodcall>
 
これはgetUsersBlogs
<?xml version="1.0" encoding="iso-8859-1"?>
<methodcall>
    <methodname>wp.getUsersBlogs</methodname>
    <params>
        <param /><value><string>admin</string></value>
        <param /><value><string>test</string></value>
    </params>
</methodcall>

ちょっと前から思っていたんだけど、この系統のアクセスをしてくるユーザーのユーザーエージェントは決まってMozilla/4.0 (compatible: MSIE 7.0; Windows NT 6.0)なんですよね。
Windows VistaのInternet Explorer 7。

こんな綺麗なユーザーエージェントはそうないので、IPアドレスで弾くのが面倒になったらこれで弾いても良さそう。
送信元の殆どがフランスってのも気になる所。
続きを読む

クライアント認証を利用してWordPressへ自動ログイン

なんか最近はブログへのログインが面倒くさくなってきましたw
かなり前に公開したDigest認証を利用してWordPressに自動ログインさせるプラグインでDigest認証のIDとパスを記録させてエンターを押すだけでいいのだけれど、それすらメンドウクサイw
 
最近はSSLで管理画面にログインしているので、「そうだ!クライアント認証ならエンターキーも必要ないんじゃね?」ってことでクライアント認証を導入。
SSL クライアント認証
firefoxのプラグインでサイトのクライアント証明書が1つの場合は、初回のみ選択ダイアログが出て以降は自動化してくれるようにしました。
 
あとはクライアント認証の環境変数を利用してWordpressへ自動ログインさせるようにするだけ・・なんだけど・・・
通常にクライアント認証をかけるとブログ全体にクライアント認証がかかり一般のユーザーは閲覧できなくなります。
 
なのでApacheのSSLバーチャルホスト内で以下のようにしました。

<Directory "/wordpress/">
	SSLOptions +StdEnvVars

	<Files wp-login.php>
		Satisfy any
		SSLVerifyClient require
	</Files>
</Directory>
<Directory "/wordpress/wp-admin/">
	SSLVerifyClient require

	<Files admin-ajax.php>
		Satisfy any
		SSLVerifyClient none
	</Files>
	<Files ~ "\.(gif|jpg|png|css|js|xml)$">
		Satisfy any
		SSLVerifyClient none
	</Files>
</Directory>

 
プラグインの方はこんな感じ。

<?php
/*
Plugin Name: WP Auto Login
Description: HTTP認証又はクライアント認証に成功した場合、自動でWordPressへログインします。
Version: 1.1.0
Author: REIMA
Author URI: http://blog.wolfs.jp/
*/
 
class wp_autoLogin {
    private $config = array(
        'no-loginform' => false, // hide wordpress loginform
        'wordpress-user' => 'wordpress-username',
        'client-auth' => false, // certificate auth
        'client-auth-cn' => '', // certificate-common name
        'client-auth-sn' => '', // certificate-serial number
        'http-auth-user' => 'http-username',
        'http-allow-host' => ''
    );
 
    public function __construct() {
        add_action('login_form', array($this, 'autoLogin'));
    }
 
    public function autoLogin() {
        $flagSuccess = false;
        $flagRedirect = false;
 
        if (is_user_logged_in()) {
            $flagSuccess = true;
            $flagRedirect = true;
        } else if (!$_GET['loggedout'] == 'true') {
            if ($_SERVER['SERVER_PORT'] == 443 && $this->config['client-auth'] == true) {
                if ($_SERVER['SSL_CLIENT_VERIFY'] == 'SUCCESS' && $_SERVER['SSL_CLIENT_S_DN_CN'] == $this->config['client-auth-cn']) {
                    $flagSuccess = true;
 
                    if ($this->config['client-auth-sn']) {
                        if ($_SERVER['SSL_CLIENT_M_SERIAL'] != $this->config['client-auth-sn']) {
                            $flagSuccess = false;
                        }
                    }
                }
            } else {
                if ($_SERVER['REMOTE_USER'] == $this->config['http-auth-user']) {
                    $flagSuccess = true;
                }
 
                if ($this->config['http-allow-host']) {
                    if (!(preg_match('/'.$this->config['http-allow-host'].'/', gethostbyaddr($_SERVER['REMOTE_ADDR'])))) {
                        $flagSuccess = false;
                    }
                }
            }
 
            if ($flagSuccess) {
                $wpUser = get_userdatabylogin($this->config['wordpress-user']);
 
                wp_set_current_user($wpUser->ID, $this->config['wordpress-user']);
                wp_set_auth_cookie($wpUser->ID);
                do_action('wp_signon', $this->config['wordpress-user']);
 
                $flagRedirect = true;
            }
        }
 
        if ($flagSuccess) {
            if ($flagRedirect) {
                $redirect_to = $_GET['redirect_to'];
                if (!$redirect_to) {
                    $redirect_to = admin_url();
                }
                wp_safe_redirect($redirect_to);
            }
        } else {
            if ($this->config['no-loginform'] == true) {
                wp_die('認証情報が正しくない為、ログインすることはできません。', '認証エラー');
            }
        }
    }
}
 
$wp_autoLogin_ins = new wp_autoLogin();
 
?>

HTTP認証の流れは以前と同じです。
クライアント認証は設定のclient-authがtrueかつ、SSLでアクセスされた場合のみ有効になります。
 
有効なクライアント証明書でSSLでアクセスされた場合、環境変数のSSL_CLIENT_S_DN_CNと設定のclient-auth-cnが同じ場合はログインを許可するようになっています。
client-auth-snに値がある場合は、クライアント証明書のシリアルナンバーも一致しないとログインできなくなります。
 
SSLの認証局を自分の所にした場合、認証局のデータベースからシリアルナンバーの正当性確認もできますね(`・ω・)b

サーバー環境の変更。

この前wp_dieを変更したときに少しサーバー環境も弄ったのですが、それが原因でphpの実行環境が少し壊れてしまったよう。
最近Apacheがエラーで自動再起動しまくってる原因はコレだったみたいw
 
phpを再構築し直すならパフォーマンスがかなり上がっている5.4を使おう!って思って、27日にテスト環境で導入テストをしていました。
序でにApacheも2.4に。(`・ω・´)

テスト環境ではPHP5.3+eAccelerator+APCよりPHP5.4+APCの方が実行速度がはやかった。
なんで5.4でeAcceleratorを使っていないかと言うと・・・x64にうまくコンパイルできなかったので投げましたwヽ(`Д´)ノ
Apache2.4の方は小さいファイルに関してはパフォーマンスが悪いけど、大きいファイルに対してはそれなりにいい感じでした。
 
1日軽ーい負荷テストをかけて放置して問題なかったので、28日にメイン環境へ導入したのですが・・・これが大惨事。

テスト環境で問題無かったApacheはメイン環境では、レスポンスを一切返さなくなる現象が多発。
発生するときは何をしても発生するので条件がわからないし、エラーログも吐かない、極希にリクエストを再送信すると復帰する場合がある。。。
プロセスは生きているので、監視プロセスからは正常に見えるためプロセスの再起動を自動でさせる事もできない(´・ω・`)

リクエストを再送信すると復帰する場合があるからパケットのロスか何かかなー?と思ったけれど、サーバー内DNSは*.wolfs.jp = 127.0.0.1としているのでロスのしようがないw
 
心残りがあるけれど・・Apache2.4の導入を見送りました。
PHP5.4は前から導入しようとしていたので、既存のPHPはほぼ対応済みだったけれど一部抜けがあって大急ぎで対応するハメにw

 

WordPressはと言うとCoolplayerのRPCサーバーが動作不能になったので代わりに、JW Player for WordPressを導入しました。

導入してメディアの管理画面を開いたところ↓
JWPlayer for WordPress - 挿入画面
メディア管理画面が拡張されて、サムネなどの設定が簡単できるようになっています。
これは便利すぎる!
 
設定はやっぱりpostmetaに保存かなー?とデータベースを覗いてみると大当たり。
JWPlayer for WordPress - SQLレコード数
しかし、1つの動画の情報を保存するのに必要なレコード数は11レコード・・ ( Д)゜゜
数が知れているのでガンガン増えても問題ないのだけど、他のプラグインみたいに1レコードにJSONっぽい形式で詰め込んでほしかった。

 

いろいろトラブルはあったけれど、少し環境を生理する事ができたのでヨシとしようー