関連カテゴリーによる絞り込み機能をサイドバーウィジェットに表示するためのコード

Simple Tags プラグインには関連タグを表示する機能があり、これには関連タグによる絞り込みの機能も付いている。

せっかくなのでこれを活用してみよう。

絞り込みをわかりやすく表示したい

絞り込みの機能はなかなか便利そうだが、デフォルトの表示はちょっとわかりにくい。そこで少し手を加えることに。最終的に以下の画像のようなものが、タグアーカイブの表示時に限ってサイドバーに表示されるようにした。

以下のプラグインが必要。

Simple Tags プラグインの設定画面で関連タグを表示するように設定したら、あとは下記のコードをテキストウィジェットに書き込めばオーケー。

<?php 
if(is_tag()) {
echo str_replace("[s]", single_tag_title("", false), st_get_related_tags('xformat=<a href="%tag_link%" title="%tag_name%">%tag_name%</a>(<a href="%tag_link_add%" title="%tag_name%+[s]">+[s]</a>で絞り込み)'));
}
?>

Simple Tags プラグインのタグ絞り込みを改造

結局、SimpleTag に st_single_tag_title() という関数を書き加えた。

/2.5/inc/simple-tags.functions.php に以下を追加。

<?php
ここから
/**
 * MODIFIED BY minorio
 *
 */
function st_single_tag_title($prefix = '', $display = true, $joint = '') {
	global $simple_tags;
	$my_tag_name = $simple_tags->combined_tag_title($joint);
	
	if ( $display )
			echo $prefix . $my_tag_name;
	else
			return $my_tag_name;
}
ここまで
?>

さらに、/2.5/simple-tags.client.php に以下を追加。

<?php
ここから
	/**
	 * MODIFIED BY minorio
	 *
	 */
	function combined_tag_title($joint) {
		// Get currents slugs
		$slugs = get_query_var('tag');

		if ( is_array($slugs) ) {
			$slugs_array = $slugs;
		} elseif ( strpos( $slugs, '+') ) {
			$slugs_array = explode('+', $slugs);
		} elseif ( strpos( $slugs, ' ') ) {
			$slugs_array = explode(' ', $slugs);
		}else {
			$slugs_array[] = urldecode($slugs);
		}

		$str = implode($joint, $slugs_array);

		return $str;
	}
ここまで
?>

テキストウィジェットに以下のように書くと、その下の画像のようになる。

<?php 
if(is_tag()) {
echo str_replace("[s]", st_single_tag_title("", false, ""), st_get_related_tags('xformat=<a href="%tag_link_add%" title="[s]∩%tag_name%">[s]∩%tag_name%</a>'));
}
?>


さらに絞り込むと以下のようになる。

タイトルなどにも絞り込みを反映させる

アーカイブのタイトルなどにも整合性を持たせないとユーザーが混乱する。
絞り込みを反映して以下のようになればいい。

これにはテンプレートの修正が必要。テンプレート内の archive.php で、タグ名を出力するところを以下のように書きかえればいい。

<?php if(function_exists('st_single_tag_title')) { st_single_tag_title('', true, ''); } else {single_tag_title();} ?>

ページタイトルも直すには、header.php を修正する。こんな感じ。

<title><?php if ( is_single() ) { wp_title(); } elseif (is_category()) { single_cat_title(); } elseif (is_tag()) { if(function_exists('st_single_tag_title')) { st_single_tag_title('', true, ''); } else {single_tag_title();} } elseif (is_day()) { the_time('Y-m-d'); } elseif (is_month()) { the_time('Y 年 F'); } elseif (is_year()) { the_time('Y 年'); } elseif (is_author()) { ?>著者<?php } ?> | <?php bloginfo('name'); ?></title>

ページタイトルに関しては、All in One SEO プラグインを導入している場合は設定で「Tag Title Format:」のところを空欄にする必要がある。
空欄にした場合に書き換えを行わないようにするハックはこちらを参照のこと。
> WordPress プラグインの All In One SEO Pack を手直し

Collapsing Categories v.0.7 Issue

I tried this plugin together with Collapsing Archives plugin made by the same author.

Collapsing Archives plugin worked like a charm, but Collapsing Categories v.0.7 seems to have an issue explained bellow.

Parent / Child relations issue

The issue is that it sets every category as the child of another category, even when there is no hierarchy set. The top category changes depending on the sort order i.e. the first category becomes top in hierarchy and the second one becomes the child of the first one, and it repeats for all categories. Please see the example below.

In this example, I made no hierarchy among categories. All categories should have been flat. In addition, the Monthly Archive widget, using collapsing-archives plugin, is placed at the bottom of the hierarchy. The two plugins seem to be mixed up? Or just the issue on Collapsing Categories causes this.

The setting I used in the above example is this;


Plugin Download Page:
http://wordpress.org/extend/plugins/collapsing-categories

Plugin Author's Page:
http://blog.robfelty.com/plugins/collapsing-categories/

人気の WordPress プラグインから使いそうなものをメモ

Plugin Manager:Admin パネル内からプラグインの検索、導入などができる

http://wordpress.org/extend/plugins/plugin-manager/

Global Translator:14カ国語対応の自動翻訳プラグイン

Google Translation Engine など4つのエンジンから選べる。欧米の言語間なら実用になるかも知れない。日本語はうまく翻訳できないんじゃないかな?なにかの目的では使えるかも・・。

http://wordpress.org/extend/plugins/global-translator/

FeedWordPress:Atom/RSS アグリゲーター

複数の Atom/RSS をまとめてブログとして発行できるみたい。

Atom/RSS

WP Tuner:ブログの表示が遅い原因を突き止められる

プラグインが遅いのか、サーバーなのか、などを突き止める。WPMU にも対応。

http://wordpress.org/extend/plugins/wptuner/

追記(2008.12.01)>

  • あんまり使えるデータが得られない感じかも?

Ping Optimizer:Ping を送るタイミングをかなり控えめにする

最初に公開したときだけ Ping して、再編集しても送信しなくなるらしい。それで十分なのかな?
Ping しすぎるとスパマーリストに入れられる危険性があるらしいけど。そうだとしたら、すでにスパマーにされてそうだな・・

Ping Optimizer

WP-o-Matic:RSS フィードからキャンペーンブログを自動発行

FeedWordPress のライバルかな?
語句の書き換えや自動リンク設置(正規表現による)が可能らしい(FeedWordPress との差か?)。複数の RSS フィード、複数のカテゴリーに対応。もちろんテンプレートも使える。

http://wordpress.org/extend/plugins/wp-o-matic/

Navigation List Plugin NAVT:カテゴリーに関わらず自由にメニューリストをつくれる

場合によってはなかなか便利なプラグインじゃないだろうか?

http://wordpress.org/extend/plugins/wordpress-navigation-list-plugin-navt/

追記(2008.12.01)>

  • タグには対応していない
  • WordPress 2.6.3 では動かないみたい

Breadcrumb NavXT:カスタマイズしやすいパンくずプラグイン

テーマにも組み込みやすいようになっているらしい(Direct Class Access)。

http://wordpress.org/extend/plugins/breadcrumb-navxt/

IntenseDebate Comments:コメント欄での議論を促す

よくできてる。

IntenseDebate Comments

UMapper:Google や Yahoo の地図サービスを簡単に貼付け

UMapper

Download Monitor:ファイルダウンロード管理用プラグイン

ダウンロード数のカウントはもちろん、メンバーオンリーのダウンロードやアップロード時の管理も含まれる。Admin 以外にもアップロードさせることが可能かどうかは不明。

http://wordpress.org/extend/plugins/download-monitor/

Disable WordPress Core Update:WordPress のアップデートの通知を表示しないようにする

クライアントに CMS として納品しちゃうようなケースを想定したものか?

Disable WordPress Core Update

XML Google MapsGoogle Map 埋め込み用プラグイン

UMapper と似たような感じか?NextGenGallery と連携して位置情報付きの写真アルバムが簡単につくれるっぽい。

http://wordpress.org/extend/plugins/xml-google-maps/

BM Custom Login:ログイン画面をカスタマイズするプラグイン

http://wordpress.org/extend/plugins/bm-custom-login/

AJAXed Wordpress:インラインコメント機能など、複数の Ajax を簡単に追加

http://wordpress.org/extend/plugins/ajaxd-wordpress/

ちょっと使ってみたけど、日本語化された文章がめちゃくちゃ過ぎて意味が分からないので、Translations フォルダの中の aWP-ja.mo などの日本語化ファイルを削除しました。これらを消すだけで英語の管理画面になります。
これは想像だけど、おそらく Google 翻訳などの自動翻訳サービスを使って訳しただけじゃないのかな?

Google Maps 関連のプラグインもうひとつ

携帯でも地図が表示できるというのは利点。
http://wppluginsj.sourceforge.jp/lightweight-google-maps/

WordPress プラグインメモ

Audit Trail プラグイン

ユーザーのログイン履歴などをログ管理。

Audit Trail is a plugin to keep track of what is going on inside your blog. It does this by recording certain actions (such as who logged in and when) and storing this information in the form of a log. Not only that but it records the full contents of posts (and pages) and allows you to restore a post to a previous version at any time.

To summarize:

* Log of user actions inside your blog - useful for finding out who did what in a multi-user system
* Post/page revisions and restorations - every change to a post or page is recorded and can be instantly restored to a previous version
* Differences are shown graphically
* Extensible, allowing other plugins the ability to add and display items in the Audit Trail
* Ability to track registered user page visits
* Fully localized

Amazon S3 plugin for WordPress

WordPress ブログの画像などを Amazon S3 のストレージに保存し、そちらから配信することでサーバーの負荷を軽減できるというプラグイン。もちろん Amazon S3 のアカウントが必要。

This WordPress plugin allows you to use Amazon's Simple Storage Service to host your media for your WordPress powered blog.

Google Sitemap Generator を改造して、モバイルサイトマップも同時に出力させる

プラグインの中を覗いてみた。クラスを使って書かれているので、うまくオーバーライドしてやればよさそう。

google-sitemap-generator/sitemap.php を見るとこんな感じで sitemap ファイルを生成している。

function CallBuildSitemap() {
	if(GoogleSitemapGeneratorLoader::LoadPlugin()) {	
		$gs = GoogleSitemapGenerator::GetInstance();
		$gs->BuildSitemap();
	}
}

記事を投稿したり削除したあとに呼ばれる関数はこんな感じ。

function CallCheckForAutoBuild($args) {
	if(GoogleSitemapGeneratorLoader::LoadPlugin()) {	
		$gs = GoogleSitemapGenerator::GetInstance();
		$gs->CheckForAutoBuild($args);
	}
}

google-sitemap-generator/sitemap-core.php を見ると CheckForAutoBuild() の中でも最終的には BuildSitemap() を呼んでいるだけなので、BuildSitemap() がカギっぽい。

GoogleSitemapGenerator クラスを継承した GoogleSitemapGeneratorMobile というメソッドをつくってやって必要な箇所だけオーバーライドするのが一番省エネかな?

というわけで、その方針でやってみた。

sitemap-core.php の修正

sitemap-core.php の最後に以下のコードを追加する(「ここから」と「ここまで」の間のコードのみをコピペ)。

<?php
(ここから)
/* MOBILE SITEMAP GENERATION */

/**
 * Class to generate a MOBILE Sitemaps of a WordPress blog.
 * 
 * @author minorio
*/
class GoogleSitemapGeneratorMobile extends GoogleSitemapGenerator {

	/**
	 * Returns the instance of the Sitemap Generator MOBILE
	 *
	 * @access public
	 * @return GoogleSitemapGenerator MOBILE The instance or null if not available. 
	 * @author minorio
	*/
	function &GetInstance() {
		if(isset($GLOBALS["sm_instance_mobile"])) {
			return $GLOBALS["sm_instance_mobile"];
		} else return null;
	}
	
	/**
	 * Returns if the MOBILE sitemap building process is currently active
	 *
	 * @access public
	 * @return bool true if active
	 * @author minorio
	*/
	function IsActive() {
		$inst = &GoogleSitemapGeneratorMobile::GetInstance();
		return ($inst != null && $inst->_isActive);
	}
	
	/**
	 * Enables the Google Sitemap Generator MOBILE and registers the WordPress hooks
	 *
	 * @access public
	 * @author minorio
	*/
	function Enable() {
		if(!isset($GLOBALS["sm_instance_mobile"])) {			
			$GLOBALS["sm_instance_mobile"]=new GoogleSitemapGeneratorMobile();
		}
	}

	/**
	 * Returns the URL for the MOBILE sitemap file
	 *
	 * @access private
	 * @author minorio
	 * @param bool $forceAuto Force the return value to the autodetected value.
	 * @return The URL to the MOBILE Sitemap file
	*/
	function GetXmlUrl($forceAuto=false) {
		
		if(!$forceAuto && $this->GetOption("b_location_mode")=="manual") {
			return substr($this->GetOption("b_fileurl_manual"), 0, -4) . "_mobile.xml";
		} else {
			return trailingslashit(get_bloginfo('siteurl')). substr($this->GetOption("b_filename"), 0, -4) . "_mobile.xml";
		}
	}

	/**
	 * Returns the URL for the gzipped MOBILE sitemap file
	 *
	 * @access private
	 * @author minorio
	 * @param bool $forceAuto Force the return value to the autodetected value.
	 * @return The URL to the gzipped MOBILE Sitemap file
	*/
	function GetZipUrl($forceAuto=false) {
		return $this->GetXmlUrl($forceAuto) . ".gz";
	}
	
	/**
	 * Returns the file system path to the MOBILE sitemap file
	 *
	 * @access private
	 * @author minorio
	 * @param bool $forceAuto Force the return value to the autodetected value.
	 * @return The file system path;
	*/
	function GetXmlPath($forceAuto=false) {		
		if(!$forceAuto && $this->GetOption("b_location_mode")=="manual") {
			return substr($this->GetOption("b_filename_manual"), 0, -4) . "_mobile.xml";
		} else {
			return $this->GetHomePath()  . substr($this->GetOption("b_filename"), 0, -4) . "_mobile.xml";
		}
	}
	
	/**
	 * Returns the file system path to the gzipped MOBILE sitemap file
	 *
	 * @access private
	 * @author minorio
	 * @param bool $forceAuto Force the return value to the autodetected value.
	 * @return The file system path;
	*/
	function GetZipPath($forceAuto=false) {
		return $this->GetXmlPath($forceAuto) . ".gz";	
	}
	
	/**
	 * Adds a url to the MOBILE sitemap. You can use this method or call AddElement directly.
	 *
	 * @access public
	 * @author minorio
	 * @param $loc string The location (url) of the page
	 * @param $lastMod int The last Modification time as a UNIX timestamp
	 * @param $changeFreq string The change frequenty of the page, Valid values are "always", "hourly", "daily", "weekly", "monthly", "yearly" and "never".
	 * @param $priorty float The priority of the page, between 0.0 and 1.0
	 * @see AddElement
	 * @return string The URL node
	 */
	function AddUrl($loc,$lastMod=0,$changeFreq="monthly",$priority=0.5) {
		$page = new GoogleSitemapGeneratorPageMobile($loc,$priority,$changeFreq,$lastMod);
		
		$this->AddElement($page);
	}

	/**
	 * Creates or opens the robots.txt in blog root and inserts the MOBILE sitemap location
	 * 
	 * @access private
	 * @author minorio
	 * @return true on success
	 */
	function WriteRobotsFile() {
		$file = $this->GetRobotsFilePath();
		
		$marker = 'XML-SITEMAP-PLUGIN-MOBILE';
		
		$current = extract_from_markers($file,$marker);
		if(is_array($current)) $current = $current[0];
		
		$smUrl = $this->GetXmlUrl();
		if($this->IsGzipEnabled()) {
			$smUrl = $this->GetZipUrl();	
		}
		
		$new = "Sitemap: " . $smUrl;
		
		if($current != $new) {
			if($this->IsFileWritable($file)) return insert_with_markers($file,$marker,array($new));
			else return false;
		}
		return true;						
	}

	/**
	 * Builds the MOBILE sitemap and writes it into a xml file.
	 * 
	 * @access public
	 * @author minorio <minorio [at] nokosu [dot] com>
	 * @return array An array with messages such as failed writes etc.
	 */
	function BuildSitemap() {
		global $wpdb, $posts, $wp_version;	
		$this->Initate();
		
		if($this->GetOption("b_memory")!='') {
			@ini_set("memory_limit",$this->GetOption("b_memory"));	
		}
		
		if($this->GetOption("sm_b_time")!=-1) {
			@set_time_limit($this->GetOption("sm_b_time"));				
		}		
		
		//This object saves the status information of the script directly to the database
		$status = new GoogleSitemapGeneratorStatus();
		
		//Other plugins can detect if the building process is active
		$this->_isActive = true;
		
		//$this->AddElement(new GoogleSitemapGeneratorXmlEntry());
		
		//Debug mode?
		$debug=$this->GetOption("b_debug");
		
		
		if($this->GetOption("b_xml")) {
			$fileName = $this->GetXmlPath();
			$status->StartXml($this->GetXmlPath(),$this->GetXmlUrl());
			
			if($this->IsFileWritable($fileName)) {
				
				$this->_fileHandle = fopen($fileName,"w");
				if(!$this->_fileHandle) $status->EndXml(false,"Not openable");
				
			} else $status->EndXml(false,"not writable");
		}
		
		//Write gzipped sitemap file
		if($this->IsGzipEnabled()) {
			$fileName = $this->GetZipPath();
			$status->StartZip($this->GetZipPath(),$this->GetZipUrl());
			
			if($this->IsFileWritable($fileName)) {
				
				$this->_fileZipHandle = gzopen($fileName,"w1");
				if(!$this->_fileZipHandle) $status->EndZip(false,"Not openable");
				
			} else $status->EndZip(false,"not writable");	
		}	
		
		if(!$this->_fileHandle && !$this->_fileZipHandle) {
			$status->End();
			return;
		}	
		
		
		//Content of the XML file
		$this->AddElement(new GoogleSitemapGeneratorXmlEntry('<?xml version="1.0" encoding="UTF-8"' . '?' . '>'));
		
		if($this->GetOption("b_style")!='') {
			$this->AddElement(new GoogleSitemapGeneratorXmlEntry('<' . '?xml-stylesheet type="text/xsl" href="' . $this->GetOption("b_style") . '"?' . '>'));	
		}
		
		$this->AddElement(new GoogleSitemapGeneratorDebugEntry("generator=\"wordpress/" . get_bloginfo('version') . "\""));
		$this->AddElement(new GoogleSitemapGeneratorDebugEntry("sitemap-generator-url=\"http://www.arnebrachhold.de\" sitemap-generator-version=\"" . $this->GetVersion() . "\""));
		$this->AddElement(new GoogleSitemapGeneratorDebugEntry("generated-on=\"" . date(get_option("date_format") . " " . get_option("time_format")) . "\""));
		
		//All comments as an asso. Array (postID=>commentCount)
		$comments=($this->GetOption("b_prio_provider")!=""?$this->GetComments():array());
		
		//Full number of comments
		$commentCount=(count($comments)>0?$this->GetCommentCount($comments):0);
		
		if($debug && $this->GetOption("b_prio_provider")!="") {
			$this->AddElement(new GoogleSitemapGeneratorDebugEntry("Debug: Total comment count: " . $commentCount));	
		}
		
		//Go XML!
		$this->AddElement(new GoogleSitemapGeneratorXmlEntry('<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0">'));
		
		$home = get_bloginfo('url');
		
		//Add the home page (WITH a slash!)
		if($this->GetOption("in_home")) {
			$this->AddUrl(trailingslashit($home),$this->GetTimestampFromMySql(get_lastpostmodified('GMT')),$this->GetOption("cf_home"),$this->GetOption("pr_home"));
		}
		
		//Add the posts
		if($this->GetOption("in_posts") || $this->GetOption("in_pages")) {
			
			if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Debug: Start Postings"));
		
			//Pre 2.1 compatibility. 2.1 introduced 'future' as post_status so we don't need to check post_date
			$wpCompat = (floatval($wp_version) < 2.1);
			
			$postPageStmt = '';
			
			$inSubPages = ($this->GetOption('sm_in_posts_sub')===true);
			
			if($inSubPages && $this->GetOption('in_posts')===true) {
				$pageDivider='<!--nextpage-->';
				$postPageStmt = ", (character_length(`post_content`)  - character_length(REPLACE(`post_content`, '$pageDivider', ''))) / " . strlen($pageDivider) . " as postPages";	
			}
			
			$sql="SELECT `ID`, `post_author`, `post_date`, `post_date_gmt`, `post_status`, `post_name`, `post_modified`, `post_modified_gmt`, `post_parent`, `post_type` $postPageStmt FROM `" . $wpdb->posts . "` WHERE ";
			
			$where = '(';
			
			if($this->GetOption('in_posts')) {
				//WP < 2.1: posts are post_status = publish 
				//WP >= 2.1: post_type must be 'post', no date check required because future posts are post_status='future'
				if($wpCompat) $where.="(post_status = 'publish' AND post_date_gmt <= '" . gmdate('Y-m-d H:i:59') . "')";
				else $where.=" (post_status = 'publish' AND (post_type = 'post' OR post_type = '')) ";
			}
			
			if($this->GetOption('in_pages')) {
				if($this->GetOption('in_posts')) {
					$where.=" OR ";	
				}
				if($wpCompat) {
					//WP < 2.1: posts have post_status = published, pages have post_status = static
					$where.=" post_status='static' ";
				} else {
					//WP >= 2.1: posts have post_type = 'post' and pages have post_type = 'page'. Both must be published. 
					$where.=" (post_status = 'publish' AND post_type = 'page') ";
				}
			}
			
			$where.=") ";
			
			$excludes = $this->GetOption('b_exclude');
			
			if(is_array($excludes) && count($excludes)>0) {
				$where.=" AND ID NOT IN ('" . implode("','",$excludes) . "')";	
			}
			
			$where.=" AND post_password='' ORDER BY post_modified DESC";
			
			$sql .= $where;
			
			if($this->GetOption("sm_b_max_posts")>0) {
				$sql.=" LIMIT 0," . $this->GetOption("sm_b_max_posts"); 		
			}	

			$postCount = intval($wpdb->get_var("SELECT COUNT(*) AS cnt FROM `" . $wpdb->posts . "` WHERE ". $where,0,0));
										
			//Create a new connection because we are using mysql_unbuffered_query and don't want to disturb the WP connection
			//Safe Mode for other plugins which use mysql_query() without a connection handler and will destroy our resultset :(
			$con = $postRes = null;
			if($this->GetOption("b_safemode")===true) {
				$postRes = mysql_query($sql,$wpdb->dbh);	
				if(!$postRes) {
					trigger_error("MySQL query failed: " . mysql_error(),E_USER_NOTICE); //E_NOTE will be displayed on our debug mode
					return;	
				}
			} else {
				$con = mysql_connect(DB_HOST,DB_USER,DB_PASSWORD,true);
				if(!$con) {
					trigger_error("MySQL Connection failed: " . mysql_error(),E_USER_NOTICE);
					return;
				}
				if(!mysql_select_db(DB_NAME,$con)) {
					trigger_error("MySQL DB Select failed: " . mysql_error(),E_USER_NOTICE);
					return;
				}
				$postRes = mysql_unbuffered_query($sql,$con);
				
				if(!$postRes) {
					trigger_error("MySQL unbuffered query failed: " . mysql_error(),E_USER_NOTICE);
					return;	
				}
			}
			
			if($postRes) {
				
				//#type $prioProvider GoogleSitemapGeneratorPrioProviderBase
				$prioProvider=NULL;
				
				if($this->GetOption("b_prio_provider") != '') {
					$providerClass=$this->GetOption('b_prio_provider');
					$prioProvider = new $providerClass($commentCount,$postCount);
				}
				
				//$posts is used by Alex King's Popularity Contest plugin
				//if($posts == null || !is_array($posts)) {
				//	$posts = &$postRes;	
				//}
				
				$z = 1;
				$zz = 1;
				
				//Default priorities
				$default_prio_posts = $this->GetOption('pr_posts');
				$default_prio_pages = $this->GetOption('pr_pages');
				
				//Change frequencies
				$cf_pages = $this->GetOption('sm_cf_pages');
				$cf_posts = $this->GetOption('sm_cf_posts');
				
				$minPrio=$this->GetOption('pr_posts_min');
				
				$useQTransLate = false; //function_exists('qtrans_convertURL'); Not ready yet
				
				//Cycle through all posts and add them
				while($post = mysql_fetch_object($postRes)) {
				
					//Fill the cache with our DB result. Since it's incomplete (no text-content for example), we will clean it later.
					$cache = array(&$post);
					update_post_cache($cache);
				
					$permalink = get_permalink($post->ID);
					if($permalink != $home) {
								
						$isPage = false;
						if($wpCompat) {
							$isPage = ($post->post_status == 'static');
						} else {
							$isPage = ($post->post_type == 'page');	
						}
						
						//Set the current working post
						$GLOBALS['post'] = &$post;
					
						//Default Priority if auto calc is disabled
						$prio = 0;
							
						if($isPage) {
							//Priority for static pages
							$prio = $default_prio_pages;
						} else {
							//Priority for normal posts
							$prio = $default_prio_posts;
						}
						
						//If priority calc. is enabled, calculate (but only for posts, not pages)!
						if($prioProvider !== null && !$isPage) {

							//Comment count for this post
							$cmtcnt = (isset($comments[$post->ID])?$comments[$post->ID]:0);
							$prio = $prioProvider->GetPostPriority($post->ID,$cmtcnt);

							if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry('Debug: Priority report of postID ' . $post->ID . ': Comments: ' . $cmtcnt . ' of ' . $commentCount . ' = ' . $prio . ' points'));
						}	
						
						if(!$isPage && $minPrio>0 && $prio<$minPrio) {
							$prio = $minPrio;
						}
						
						//Add it
						$this->AddUrl($permalink,$this->GetTimestampFromMySql(($post->post_modified_gmt && $post->post_modified_gmt!='0000-00-00 00:00:00'?$post->post_modified_gmt:$post->post_date_gmt)),($isPage?$cf_pages:$cf_posts),$prio);
						
						if($inSubPages) {
							$subPage = '';
							for($p =1; $p<=$post->postPages; $p++) {
								if(get_option('permalink_structure') == '') {
									$subPage = $permalink . '&amp;page=' . $p;
								} else {
									$subPage = trailingslashit($permalink) . user_trailingslashit($p, 'single_paged');
								}	

								$this->AddUrl($subPage,$this->GetTimestampFromMySql(($post->post_modified_gmt && $post->post_modified_gmt!='0000-00-00 00:00:00'?$post->post_modified_gmt:$post->post_date_gmt)),($isPage?$cf_pages:$cf_posts),$prio);
							}
						}
						
					
                       // Multilingual Support with qTranslate, thanks to Qian Qin
                       if($useQTransLate) {
                           global $q_config;
                           foreach($q_config['enabled_languages'] as $language) {
                               if($language != $q_config['default_language']) {
                                   $this->AddUrl(qtrans_convertURL($permalink,$language),$this->GetTimestampFromMySql(($post->post_modified_gmt && $post->post_modified_gmt!='0000-00-00 00:00:00'?$post->post_modified_gmt:$post->post_date_gmt)),($isPage?$cf_pages:$cf_posts),$prio);
                               }
                           }
                       }						
					}
					
					//Update the status every 100 posts and at the end. 
					//If the script breaks because of memory or time limit, 
					//we have a "last reponded" value which can be compared to the server settings
					if($zz==100 || $z == $postCount) {
						$status->SaveStep($z);
						$zz=0;						
					} else $zz++;
					
					$z++;
					
					//Clean cache because it's incomplete
					if(version_compare($wp_version,"2.5",">=")) {
						//WP 2.5 makes a mysql query for every clean_post_cache to clear the child cache
						//so I've vopied the function here until a patch arrives... 
						wp_cache_delete($post->ID, 'posts');
						wp_cache_delete($post->ID, 'post_meta');
						clean_object_term_cache($post->ID, 'post');
					} else {
						clean_post_cache($post->ID);
					}
				}
				unset($postRes);
				unset($prioProvider);
				
				if($this->GetOption("b_safemode")!==true && $con) mysql_close($con);
			}
			if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Debug: End Postings"));
		}
		
		//Add the cats
		if($this->GetOption("in_cats")) {
			if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Debug: Start Cats"));
			
			if(!$this->IsTaxonomySupported()) {
			
				$catsRes=$wpdb->get_results("
							SELECT 
								c.cat_ID AS ID, 
								MAX(p.post_modified_gmt) AS last_mod 
							FROM 
								`" . $wpdb->categories . "` c,
								`" . $wpdb->post2cat . "` pc,
								`" . $wpdb->posts . "` p
							WHERE
								pc.category_id = c.cat_ID
								AND p.ID = pc.post_id
								AND p.post_status = 'publish'
								AND p.post_type='post'
							GROUP
								BY c.cat_id
							");						
				if($catsRes) {
					foreach($catsRes as $cat) {
						if($cat && $cat->ID && $cat->ID>0) {
							if($debug) if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Cat-ID:" . $cat->ID)); 	
							$this->AddUrl(get_category_link($cat->ID),$this->GetTimestampFromMySql($cat->last_mod),$this->GetOption("cf_cats"),$this->GetOption("pr_cats"));
						}
					}	
				}
			} else {
				$cats = get_terms("category",array("hide_empty"=>true,"hierarchical"=>false));
				if($cats && is_array($cats) && count($cats)>0) {
					foreach($cats AS $cat) {
						$this->AddUrl(get_category_link($cat->term_id),0,$this->GetOption("cf_cats"),$this->GetOption("pr_cats"));	
					}	
				}	
			}
			if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Debug: End Cats"));	
		}
		
		//Add the archives
		if($this->GetOption("in_arch")) {
			if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Debug: Start Archive"));
			$now = current_time('mysql');

			//WP2.1 introduced post_status='future', for earlier WP versions we need to check the post_date_gmt
			$arcresults = $wpdb->get_results("
						SELECT DISTINCT 
							YEAR(post_date_gmt) AS `year`, 
							MONTH(post_date_gmt) AS `month`, 
							MAX(post_date_gmt) as last_mod, 
							count(ID) as posts 
						FROM 
							$wpdb->posts 
						WHERE 
							post_date < '$now' 
							AND post_status = 'publish' 
							AND post_type = 'post'
							" . (floatval($wp_version) < 2.1?"AND {$wpdb->posts}.post_date_gmt <= '" . gmdate('Y-m-d H:i:59') . "'":"") . "
						GROUP BY 
							YEAR(post_date_gmt), 
							MONTH(post_date_gmt) 
						ORDER BY 
							post_date_gmt DESC");
			if ($arcresults) {
				foreach ($arcresults as $arcresult) {
					
					$url  = get_month_link($arcresult->year,   $arcresult->month);
					$changeFreq="";
					
					//Archive is the current one
					if($arcresult->month==date("n") && $arcresult->year==date("Y")) {
						$changeFreq=$this->GetOption("cf_arch_curr");	
					} else { // Archive is older
						$changeFreq=$this->GetOption("cf_arch_old");	
					}
					
					$this->AddUrl($url,$this->GetTimestampFromMySql($arcresult->last_mod),$changeFreq,$this->GetOption("pr_arch"));				
				}
			}
			if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Debug: End Archive")); 	
		}
		
		//Add the author pages
		if($this->GetOption("in_auth")) {
			if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Debug: Start Author pages"));
			
			$linkFunc = null;
			
			//get_author_link is deprecated in WP 2.1, try to use get_author_posts_url first. 
			if(function_exists('get_author_posts_url')) {
				$linkFunc = 'get_author_posts_url'; 		
			} else if(function_exists('get_author_link')) {
				$linkFunc = 'get_author_link'; 	
			} 
			
			//Who knows what happens in later WP versions, so check again if it worked
			if($linkFunc !== null) {
			    //Unfortunately there is no API function to get all authors, so we have to do it the dirty way...
				//We retrieve only users with published and not password protected posts (and not pages)
				//WP2.1 introduced post_status='future', for earlier WP versions we need to check the post_date_gmt
				$sql = "SELECT DISTINCT 
							{$wpdb->users}.ID, 
							{$wpdb->users}.user_nicename, 
							MAX({$wpdb->posts}.post_modified_gmt) AS last_post 
						FROM 
							{$wpdb->users}, 
							{$wpdb->posts} 
						WHERE 
							{$wpdb->posts}.post_author = {$wpdb->users}.ID 
							AND {$wpdb->posts}.post_status = 'publish'
							AND {$wpdb->posts}.post_type = 'post' 
							AND {$wpdb->posts}.post_password = '' 
							" . (floatval($wp_version) < 2.1?"AND {$wpdb->posts}.post_date_gmt <= '" . gmdate('Y-m-d H:i:59') . "'":"") . "
						GROUP BY 
							{$wpdb->users}.ID, 
							{$wpdb->users}.user_nicename";
							
				$authors = $wpdb->get_results($sql);
				
				if($authors && is_array($authors)) {
					foreach($authors as $author) {
						if($debug) if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Author-ID:" . $author->ID)); 	
						$url = ($linkFunc=='get_author_posts_url'?get_author_posts_url($author->ID,$author->user_nicename):get_author_link(false,$author->ID,$author->user_nicename));
						$this->AddUrl($url,$this->GetTimestampFromMySql($author->last_post),$this->GetOption("cf_auth"),$this->GetOption("pr_auth"));
					}
				}
			} else {
				//Too bad, no author pages for you :(
				if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Debug: No valid author link function found"));	
			}
	
			if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Debug: End Author pages"));	
		}
		
		//Add tag pages
		if($this->GetOption("in_tags") && $this->IsTaxonomySupported()) {
			if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Debug: Start Tags"));
			$tags = get_terms("post_tag",array("hide_empty"=>true,"hierarchical"=>false));
			if($tags && is_array($tags) && count($tags)>0) {
				foreach($tags AS $tag) {
					$this->AddUrl(get_tag_link($tag->term_id),0,$this->GetOption("cf_tags"),$this->GetOption("pr_tags"));	
				}	
			}	
			if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Debug: End Tags"));	
		}
		
		//Add the custom pages
		if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Debug: Start Custom Pages"));
		if($this->_pages && is_array($this->_pages) && count($this->_pages)>0) {
			//#type $page GoogleSitemapGeneratorPage
			foreach($this->_pages AS $page) {
				$this->AddUrl($page->GetUrl(),$page->getLastMod(),$page->getChangeFreq(),$page->getPriority());
			}	
		}
		
		if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Debug: End Custom Pages"));
		
		if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Debug: Start additional URLs"));
		
		do_action("sm_buildmap");
		
		if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Debug: End additional URLs"));
		
		$this->AddElement(new GoogleSitemapGeneratorXmlEntry("</urlset>"));
		

		$pingUrl='';
		
		if($this->GetOption("b_xml")) {
			if($this->_fileHandle && fclose($this->_fileHandle)) {
				$this->_fileHandle = null;
				$status->EndXml(true);
				$pingUrl=$this->GetXmlUrl();
			} else $status->EndXml(false,"Could not close the sitemap file.");
		}
		
		if($this->IsGzipEnabled()) {
			if($this->_fileZipHandle && fclose($this->_fileZipHandle)) {
				$this->_fileZipHandle = null;
				$status->EndZip(true);
				$pingUrl=$this->GetZipUrl();
			} else $status->EndZip(false,"Could not close the zipped sitemap file");
		}
		
		//Ping Google
		if($this->GetOption("b_ping") && !empty($pingUrl)) {
			$sPingUrl="http://www.google.com/webmasters/sitemaps/ping?sitemap=" . urlencode($pingUrl);
			$status->StartGooglePing($sPingUrl);
			$pingres=$this->RemoteOpen($sPingUrl);
									  
			if($pingres==NULL || $pingres===false) {
				$status->EndGooglePing(false,$this->_lastError);
			} else {
				$status->EndGooglePing(true);
			}
		} 
				
		//Ping Ask.com
		if($this->GetOption("b_pingask") && !empty($pingUrl)) {
			$sPingUrl="http://submissions.ask.com/ping?sitemap=" . urlencode($pingUrl);
			$status->StartAskPing($sPingUrl);
			$pingres=$this->RemoteOpen($sPingUrl);
									  
			if($pingres==NULL || $pingres===false || strpos($pingres,"successfully received and added")===false) { //Ask.com returns 200 OK even if there was an error, so we need to check the content.
				$status->EndAskPing(false,$this->_lastError);
			} else {
				$status->EndAskPing(true);
			}
		}
		
		//Ping YAHOO
		if($this->GetOption("sm_b_pingyahoo")===true && $this->GetOption("sm_b_yahookey")!="" && !empty($pingUrl)) {
			$sPingUrl="http://search.yahooapis.com/SiteExplorerService/V1/updateNotification?appid=" . $this->GetOption("sm_b_yahookey") . "&url=" . urlencode($pingUrl);
			$status->StartYahooPing($sPingUrl);
			$pingres=$this->RemoteOpen($sPingUrl);

			if($pingres==NULL || $pingres===false || strpos(strtolower($pingres),"success")===false) {
				$status->EndYahooPing(false,$this->_lastError);
			} else {
				$status->EndYahooPing(true);
			}	
		}	
		
		//Ping MSN
		if($this->GetOption("b_pingmsn") && !empty($pingUrl)) {
			$sPingUrl="http://webmaster.live.com/ping.aspx?siteMap=" . urlencode($pingUrl);
			$status->StartMsnPing($sPingUrl);
			$pingres=$this->RemoteOpen($sPingUrl);
									  
			if($pingres==NULL || $pingres===false || strpos($pingres,"Thanks for submitting your sitemap")===false) { 
				$status->EndMsnPing(false,$this->_lastError);
			} else {
				$status->EndMsnPing(true);
			}
		}
	
		$status->End();	

		
		$this->_isActive = false;	
	
		//done...
		return $status;
	}

}


/**
 * Represents an item in the page list
 * @author minorio
 * @package sitemap
 */
class GoogleSitemapGeneratorPageMobile extends GoogleSitemapGeneratorPage {

	function Render() {
		
		if($this->_url == "/" || empty($this->_url)) return '';
		
		$r="";
		$r.= "\t<url>\n";
		$r.= "\t\t<loc>" . $this->EscapeXML($this->_url) . "</loc>\n";
		if($this->_lastMod>0) $r.= "\t\t<lastmod>" . date('Y-m-d\TH:i:s+00:00',$this->_lastMod) . "</lastmod>\n";
		if(!empty($this->_changeFreq)) $r.= "\t\t<changefreq>" . $this->_changeFreq . "</changefreq>\n";	
		if($this->_priority!==false && $this->_priority!=="") $r.= "\t\t<priority>" . number_format($this->_priority,1) . "</priority>\n";
		$r.= "\t\t<mobile:mobile/>\n";
		$r.= "\t</url>\n";	
		return $r;
	}	

}
/* MOBILE SITEMAP GENERATION END */
(ここまで)
?>

クラスがもうちょっと細かく分割されてればコピペする量をもっと減らせるんだけど、とりあえずこれで動くでしょう。

sitemap.php の修正

<?php
(前略)
	function CallCheckForAutoBuild($args) {
		if(GoogleSitemapGeneratorLoader::LoadPlugin()) {	
			$gs = GoogleSitemapGenerator::GetInstance();
			$gs->CheckForAutoBuild($args);
		}
	}
	
	function CallBuildSitemap() {
		if(GoogleSitemapGeneratorLoader::LoadPlugin()) {	
			$gs = GoogleSitemapGenerator::GetInstance();
			$gs->BuildSitemap();
		}
	}
	
	function CallCheckForManualBuild() {
		if(GoogleSitemapGeneratorLoader::LoadPlugin()) {	
			$gs = GoogleSitemapGenerator::GetInstance();
			$gs->CheckForManualBuild();
		}	
	}
	
	function LoadPlugin() {
		
		$mem = abs(intval(@ini_get('memory_limit')));
		if($mem && $mem < 32) {
			@ini_set('memory_limit', '32M');	
		}
		
		$time = abs(intval(@ini_get("max_execution_tim")));
		if($time != 0 && $time < 120) {
			@set_time_limit(120);	
		}
		
		if(!class_exists("GoogleSitemapGenerator")) {
			
			$path = trailingslashit(dirname(__FILE__));
			
			if(!file_exists( $path . 'sitemap-core.php')) return false;
			require_once($path. 'sitemap-core.php');
		} 

		GoogleSitemapGenerator::Enable();	
		return true;	
	}
(後略)
?>

を以下のように変更する。

<?php
(前略)
	function CallCheckForAutoBuild($args) {
		if(GoogleSitemapGeneratorLoader::LoadPlugin()) {	
			$gs = GoogleSitemapGenerator::GetInstance();
			$gs->CheckForAutoBuild($args);
			
			/* MOBILE SITEMAP GENERATION */
			$gsm = GoogleSitemapGeneratorMobile::GetInstance();
			$gsm->CheckForAutoBuild($args);
			/* MOBILE SITEMAP GENERATION END */
		}
	}
	
	function CallBuildSitemap() {
		if(GoogleSitemapGeneratorLoader::LoadPlugin()) {	
			$gs = GoogleSitemapGenerator::GetInstance();
			$gs->BuildSitemap();
			
			/* MOBILE SITEMAP GENERATION */
			$gsm = GoogleSitemapGeneratorMobile::GetInstance();
			$gsm->BuildSitemap();
			/* MOBILE SITEMAP GENERATION END */
		}
	}
	
	function CallCheckForManualBuild() {
		if(GoogleSitemapGeneratorLoader::LoadPlugin()) {	
			$gs = GoogleSitemapGenerator::GetInstance();
			$gs->CheckForManualBuild();
			
			/* MOBILE SITEMAP GENERATION */
			$gsm = GoogleSitemapGeneratorMobile::GetInstance();
			$gsm->CheckForManualBuild();
			/* MOBILE SITEMAP GENERATION END */
		}	
	}
	
	function LoadPlugin() {
		
		$mem = abs(intval(@ini_get('memory_limit')));
		if($mem && $mem < 32) {
			@ini_set('memory_limit', '32M');	
		}
		
		$time = abs(intval(@ini_get("max_execution_tim")));
		if($time != 0 && $time < 120) {
			@set_time_limit(120);	
		}
		
		/* MOBILE SITEMAP GENERATION */
		//if(!class_exists("GoogleSitemapGenerator")) {
		if(!class_exists("GoogleSitemapGenerator") || !class_exists("GoogleSitemapGeneratorMobile")) {
		/* MOBILE SITEMAP GENERATION END */
			
			$path = trailingslashit(dirname(__FILE__));
			
			if(!file_exists( $path . 'sitemap-core.php')) return false;
			require_once($path. 'sitemap-core.php');
		} 

		GoogleSitemapGenerator::Enable();	

		/* MOBILE SITEMAP GENERATION */
		GoogleSitemapGeneratorMobile::Enable();	
		/* MOBILE SITEMAP GENERATION END */
		return true;	
	}
(後略)
?>

管理パネルの XML Sitemap の設定画面から「rebuild sitemap」manually はできません

上記の修正で、投稿時に携帯用のサイトマップが同時に出力されるようになります(ファイル名は sitemap_mobile.xml のように「_mobile」が自動的に追加されます)。

ただし、WordPress 管理パネルの XML Sitemap の設定画面の上の方、「Status」セクション内にある「rebuild sitemap」manually というところのリンクをクリックしても携帯用のサイトマップは生成されません。

これは sitemap-ui.php の方をいじればできるんだけど、個人的に必要ないのでやりません。

サイトマップ生成にかかる時間について

今回はクラスをできる限り再利用することで変更箇所を少なくするという方針でやったので、サイトマップ生成のために同じ SQL を2度発行することになってしまいました。これはかなり無駄です(時間もほぼ2倍かかるし)。

最初から携帯用のサイトマップも生成するつもりなら、1度の SQL で2種類のサイトマップを用意してやればいいので、プラグインの作者さんの方で対応版が出たら、そちらに切り替える方がいいと思います。とりあえず応急処置ということで。

robots.txt への Sitemap: の追加

これも sitemap-ui.php の方でやっている処理なので、そっちをいじる必要があります。
が、面倒だったので FTProbots.txt を開いて書き加えてしまいました。(^_^;

こんな感じで OK です。

# BEGIN XML-SITEMAP-PLUGIN
Sitemap: http://example.com/sitemap.xml.gz
# END XML-SITEMAP-PLUGIN

# BEGIN XML-SITEMAP-PLUGIN-MOBILE
Sitemap: http://example.com/sitemap_mobile.xml.gz
# END XML-SITEMAP-PLUGIN-MOBILE

Ktai-style プラグインで「リンク」ページが空っぽになる原因

かなりややこしいんだけど、WordPress のデフォルトテンプレートには Links という名のテンプレートが用意されているので、新規ページを作成してページテンプレートにこの Links を指定してやれば、そのページにはリンク(ブログロール)が表示される仕組みになっている。この場合、ページのコンテンツは空っぽのままにしておけばいい。

一方、KtaiStyle のテンプレートでは、ページ用のテンプレートはひとつしかなく、コンテンツの中身が空っぽだとなにも表示されない。そのため、KtaiStyle でリンク(ブログロール)ページを表示させると空っぽのページが出てしまう。これは困ったことだ。

KtaiStyle でリンクページを表示させるための対策

KtaiStyle には実はリンクページを表示させるためのテンプレートが別に用意されているので、これを使うのがよさそう。

ktai-themes/footer.php にこんな風にリンクを追加してやればいい。

<a href="<?php ks_blogurl(); ?>?menu=links"><?php _e('Links'); ?></a>