<?php /** * NP_MobileWithSinglePage 2.1.0 * 携帯電話用にページ内容を変更 */ /* * Copyright (C) 2006-2007 NKJG * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * In addition, as a special exception, NKJG gives * permission to link the code of this program with those files in the PEAR * library that are licensed under the PHP License (or with modified versions * of those files that use the same license as those files), and distribute * linked combinations including the two. You must obey the GNU General Public * License in all respects for all of the code used other than those files in * the PEAR library that are licensed under the PHP License. If you modify * this file, you may extend this exception to your version of the file, * but you are not obligated to do so. If you do not wish to do so, delete * this exception statement from your version. */ /** * 2.1.0.1 fix mobile comment * if (postVar('plug_mobilewithsinglepage' == 'from_mobile')) { * -> * if (postVar('plug_mobilewithsinglepage') == 'from_mobile') { * add blog option * you can set new mobile skin for own blog **/ require_once (dirname(__FILE__).'/sharedlibs/sharedlibs.php'); require_once ('nkjg/pluginextra.php'); // プラグインの共通部分 require_once ('nkjg/pluginoptioncreator.php'); // オプション作成 define('PLUG_MOBILEWITHSINGLEPAGE_SKIN_DELIM', '/'); define('PLUG_MOBILEWITHSINGLEPAGE_SKIN_DEFAULTNAME', 'mobile'); class NP_MobileWithSinglePage extends SHAREDLIBS_NKJG_PLUGINEXTRA { var $isStarted = false; // バッファリング状況 var $aSkinName = false; // スキンのリスト var $toEncodeComment = false; // コメントをエンコードする var $isMobile = false; var $aMobileInfo = null; function getName() { return PLUG_MOBILEWITHSINGLEPAGE_TEXT_PLUGINNAME; } function getAuthor() { return 'NKJG, shizuki'; } function getVersion() { return '2.1.0.1 beta'; } function getURL() { return 'http://japan.nucleuscms.org/wiki/plugins:mobilewithsinglepage'; } function getDescription() { return PLUG_MOBILEWITHSINGLEPAGE_TEXT_DESCRIPTION; } function getEventList() { return array( 'PostAuthentication', 'PreSendContentType', 'InitSkinParse', 'PreSkinParse', 'PostSkinParse', 'FormExtra', 'ValidateForm', 'PreAddComment', ); } function init() { $this->aMobileInfo = array(); $this->incLangFile(); } //////// インストール・オプション //////// function install() { $objOptionCreator =& new SHAREDLIBS_NKJG_PLUGINOPTIONCREATOR(&$this); // Plugin Global Options $objOptionCreator->createGlobalOption_YesNo( 'changeSkin', PLUG_MOBILEWITHSINGLEPAGE_TEXT_OPTION_CHANGESKIN, 'yes' ); // since 2.1.0 $objOptionCreator->createGlobalOption_Text( 'skinName', PLUG_MOBILEWITHSINGLEPAGE_TEXT_OPTION_SKINNAME ); $objOptionCreator->createGlobalOption_YesNo( 'conversion', PLUG_MOBILEWITHSINGLEPAGE_TEXT_OPTION_CONVERSION, 'yes' ); // since 2.1.0 $objOptionCreator->createGlobalOption_Select( 'imgOperate', PLUG_MOBILEWITHSINGLEPAGE_TEXT_OPTION_IMGOPERATE, array( PLUG_MOBILEWITHSINGLEPAGE_TEXT_OPTION_IMGOPERATE_TOTEXT => 'text', PLUG_MOBILEWITHSINGLEPAGE_TEXT_OPTION_IMGOPERATE_TOLINK => 'link', PLUG_MOBILEWITHSINGLEPAGE_TEXT_OPTION_IMGOPERATE_NO_CONVERT => 'no', ) ); $objOptionCreator->createGlobalOption_YesNo( 'removeQuot', PLUG_MOBILEWITHSINGLEPAGE_TEXT_OPTION_REMOVEQUOT, 'yes' ); $objOptionCreator->createGlobalOption_YesNo( 'useHankaku', PLUG_MOBILEWITHSINGLEPAGE_TEXT_OPTION_USEHANKAKU, 'yes' ); $objOptionCreator->createGlobalOption_Text( 'tagsNotRemove', PLUG_MOBILEWITHSINGLEPAGE_TEXT_OPTION_TAGSNOTREMOVE, 'html|head|title|body|h1|h2|h3|h4|h5|h6|form|input|label|textarea|' . 'ul|ol|li|br|hr|p|div|table|tr|td|th|img|a|dl|dt|dd' ); $objOptionCreator->createGlobalOption_Text( 'attributesNotRemove', PLUG_MOBILEWITHSINGLEPAGE_TEXT_OPTION_ATTRSNOTREMOVE, 'id|src|name|href|alt|action|method|enctype|accept|accpept-charset|' . 'value|type|for|title|http-equiv|content|scheme|accesskey' ); // Plugin Blog Options // <add by shizuki since 2.1.0.1> ブログ毎にモバイル用スキンを設定できるようにオプションを追加 $objOptionCreator->createBlogOption_Text( 'bskinName', PLUG_MOBILEWITHSINGLEPAGE_TEXT_OPTION_SKINNAME ); // </add by shizuki since 2.1.0.1> } function event_PostAuthentication($data) { global $CONF; if (!$CONF['UsingAdminArea']) { $userAgent = serverVar('HTTP_USER_AGENT'); require_once('Net/UserAgent/Mobile.php'); $objUAMobile =& Net_UserAgent_Mobile::factory($userAgent); if (!$objUAMobile->isNonMobile() && !$objUAMobile->isError()) { $this->isMobile = true; $this->aMobileInfo['carrierLongName'] = $objUAMobile->getCarrierLongName(); $this->aMobileInfo['carrierShortName'] = $objUAMobile->getCarrierShortName(); $this->aMobileInfo['name'] = $objUAMobile->getName(); $this->aMobileInfo['version'] = $objUAMobile->getVersion(); $objDisplay = $objUAMobile->getDisplay(); if (!PEAR::isError($objDisplay)) { $this->aMobileInfo['height'] = $objDisplay->getHeight(); $this->aMobileInfo['width'] = $objDisplay->getWidth(); $this->aMobileInfo['color'] = $objDisplay->isColor(); $this->aMobileInfo['depth'] = $objDisplay->getDepth(); } } } } function event_InitSkinParse($data) { if ($this->isMobile && $this->getOption('changeSkin') != 'no') { $this->changeSkin(&$data['skin']); } } // スキン変更 function changeSkin(&$skin) { $currentSkinName = $skin->getName(); $newSkinName = $this->_getNewSkinName($currentSkinName); if ($newSkinName != $currentSkinName) { $newSkin =& SKIN::createFromName($newSkinName); // copied from NP_SkinSwitcher.php $skin->SKIN($newSkin->getID()); } } // 変更するスキン名の決定 function _getNewSkinName($baseName) { global $blog; // <add by shizuki since 2.1.0.1 /> $result = $baseName; $carrierName = trim(strtolower($this->aMobileInfo['carrierLongName'])); // <add by shizuki since 2.1.0.1> ブログオプションでスキンが指定されていればブログ別のスキンを使う if (is_object($blog) && $this->getBlogOption($blog->getID(), 'bskinName') != '') { $bOptionName = $this->getBlogOption($blog->getID(), 'bskinName'); } // </add by shizuki since 2.1.0.1> $optionName = $this->getOption('skinName'); $delim = PLUG_MOBILEWITHSINGLEPAGE_SKIN_DELIM; $aSkinName = array(); if ($bOptionName) { $aSkinName[] = $bOptionName . $delim . $carrierName; $aSkinName[] = $bOptionName; } if ($optionName) { $aSkinName[] = $optionName . $delim . $carrierName; $aSkinName[] = $optionName; } $aSkinName[] = $baseName . $delim . $carrierName; $aSkinName[] = $baseName . $delim . PLUG_MOBILEWITHSINGLEPAGE_SKIN_DEFAULTNAME; $aSkinList = $this->_getSkinList(); foreach ($aSkinName as $skinName) { if (in_array($skinName, $aSkinList)) { $result = $skinName; break; } } return $result; } function event_PreSendContentType($data) { if ($this->isMobile && $this->getOption('conversion') != 'no') { $data['charset'] = 'Shift_JIS'; $data['contentType'] = 'text/html'; } } function event_PreSkinParse($data) { if ($this->isMobile) { // キャッシュはさせない header('Pragma: no-cache'); header('Cache-Control: no-cache, must-revalidate'); if ($this->getOption('conversion') != 'no') { ob_implicit_flush(false); $this->isStarted = ob_start(array(&$this, 'doConvert')); } } } function event_PostSkinParse($data) { if ($this->isStarted) { ob_end_flush(); } } function doConvert($strHTML) { $strHTML = $this->doConvert_HTML($strHTML); $strHTML = $this->doConvert_encoding($strHTML); if ($this->getOption('useHankaku') != 'no') { $strHTML = $this->doConvert_hankaku($strHTML); } return $strHTML; } function doConvert_HTML($strHTML) { $aTagNotRemove = explode('|', strtolower($this->getOption('tagsNotRemove'))); $aAttrNotRemove = explode('|', strtolower($this->getOption('attributesNotRemove'))); $removeQuot = ($this->getOption('removeQuot') != no); $imgOperate = $this->getOption('imgOperate'); $handler =& new PLUG_MOBILEWITHSINGLEPAGE_HTMLCONVERTER($aTagNotRemove, $aAttrNotRemove, $removeQuot, $imgOperate); require_once('XML/XML_HTMLSax.php'); $parser =& new XML_HTMLSax(); $parser->set_option(XML_OPTION_CASE_FOLDING); $parser->set_object($handler); $parser->set_element_handler('openHandler', 'closeHandler'); $parser->set_data_handler('dataHandler'); $parser->parse($strHTML); $strHTML = $handler->getResult(); return $strHTML; } function doConvert_encoding($strHTML) { $strHTML = mb_convert_encoding($strHTML, 'SJIS', _CHARSET); return $strHTML; } function doConvert_hankaku($strHTML) { $strHTML = mb_convert_kana($strHTML, 'rnask', 'SJIS'); return $strHTML; } function event_FormExtra($data) { switch ($data['type']) { case 'commentform-loggedin' : case 'commentform-notloggedin' : break; default : return; } if ($this->isMobile && $this->getOption('conversion') != 'no') { echo ('<input type="hidden" name="plug_mobilewithsinglepage" value="from_mobile" />'); } } function event_ValidateForm($data) { if ($data['comment']) { if (postVar('plug_mobilewithsinglepage') == 'from_mobile') { $this->toEncodeComment = true; } } } function event_PreAddComment($data) { if ($this->toEncodeComment) { $data['comment']['user'] = mb_convert_encoding($data['comment']['user'], _CHARSET, 'auto'); $data['comment']['body'] = mb_convert_encoding($data['comment']['body'], _CHARSET, 'auto'); } } function _getSkinList() { if ($this->aSkinName == false) { $aSkinName = array(); $query = 'SELECT sdname FROM ' . sql_table('skin_desc'); $result = sql_query($query); while ($aResult = mysql_fetch_array($result)) { array_push($aSkinName, $aResult['sdname']); } $this->aSkinName = $aSkinName; } return $this->aSkinName; } // since 2.1.0 function doSkinVar($skinType, $name = '') { $result = ''; if (isset($this->aMobileInfo[$name])) { if ($this->aMobileInfo[$name] === true) { $result = 'yes'; } elseif ($this->aMobileInfo[$name] === false) { $result = 'no'; } else { $result = $this->aMobileInfo[$name]; } } echo $result; } function doIf($key = '', $value = '') { $result = false; if ($key != '') { if (isset($this->aMobileInfo[$key])) { if ($value != '') { if (is_numeric($value)) { $result = (intval($this->aMobileInfo[$key]) <= intval($value)); } else { $result = ($this->aMobileInfo[$key] == $value); } } else { $result = !!($this->aMobileInfo[$key]); } } } else { $result = $this->isMobile; } return $result; } } // PEAR::XML_HTMLSaxを使ってみる (間違いなく遅いけど) class PLUG_MOBILEWITHSINGLEPAGE_HTMLCONVERTER { var $aTagNotRemove; // 削除されないタグ var $aAttrNotRemove; // 削除されない属性名 var $removeQuot; // 引用符を削除するかどうか var $imgOperate; // IMG要素の処遇 var $isEmpty = false; // bool "直前の要素が開始タグである" var $aNestLevel = 0; // a要素のネストレベル(最低レベルのa要素のみタグを出力する) var $aVoidLevel = 0; // 無視要素のネストレベル var $result = ''; // 結果 // 各種オプションのセット function PLUG_MOBILEWITHSINGLEPAGE_HTMLCONVERTER ($aTagNotRemove, $aAttrNotRemove, $removeQuot, $imgOperate) { $this->aTagNotRemove = $aTagNotRemove; $this->aAttrNotRemove = $aAttrNotRemove; $this->removeQuot = $removeQuot; $this->imgOperate = $imgOperate; // 空要素であることが認められている要素 (大文字は仕様) $this->emptyElement = array( 'br', 'area', 'link', 'img', 'param', 'hr', 'input', 'col', 'base', 'meta', ); // タグだけでなく内容も表示しない要素 $this->voidElement = array( 'script', ); } // 開始ハンドラ function openHandler(&$parser, $tagName, $aAttr) { $result = ''; $tagName = strtolower($tagName); $result .= $this->endOfEmpty(); if (in_array($tagName, $this->voidElement)) { $this->voidLevel++; } else if ($this->voidLevel == 0 && in_array($tagName, $this->aTagNotRemove)) { switch ($tagName) { case 'img' : $result .= $this->openHandler_img($aAttr); break; case 'a' : $result .= $this->openHandler_a($aAttr); break; default : $result .= $this->openHandler_default($tagName, $aAttr); break; } } $this->result .= $result; } // img要素用開始ハンドラ function openHandler_img($aAttr) { $result = ''; // 代替テキスト $altText = ''; if (isset($aAttr['alt'])) { $altText = $aAttr['alt']; } switch ($this->imgOperate) { case 'text' : // テキストに変換する場合 if ($altText !== '') { $result .= $altText; $this->isEmpty = false; } break; case 'link' : // リンクに変換する場合 if ($altText === '') { $altText = '[image]'; // 代替テキストが空の場合は'[image]'で代替 } $aNewAttr = $aAttr; $aNewAttr['href'] = $aNewAttr['arc']; // src属性をhref属性にする unset($aNewAttr['arc']); unset($aNewAttr['alt']); $result .= $this->openHandler_a($aNewAttr); // a要素(開始タグ) $result .= $altText; $this->isEmpty = false; break; default : // そのまま画像で出力する場合 $result .= $this->openHandler_default('img', $aAttr); } return $result; } // a要素用開始ハンドラ function openHandler_a($aAttr) { $result = ''; // 祖先にまだa要素がない場合のみa要素を作る if ($this->aNestLevel == 0) { $result .= $this->openHandler_default('a', $aAttr); } $this->aNestLevel++; return $result; } // 通常の開始ハンドラ function openHandler_default($tagName, $aAttr) { $result = ''; $tagName = strtolower($tagName); $close = ''; if (in_array($tagName, $this->emptyElement)) { $this->isEmpty = true; } else { $this->isEmpty = false; } $result .= ('<' . $tagName); foreach ($aAttr as $attrName => $attrValue) { $attrName = strtolower($attrName); if (in_array($attrName, $this->aAttrNotRemove)) { $quot = '"'; $close = ''; if ($this->removeQuot && $attrValue != '' && strpos($attrValue, ' ') === FALSE) { // 引用符削除モードで、なおかつ内容が空文字列でなくて、空白も含まないときに引用符を削除る $quot = ''; if (!$this->isEmpty) { $close = ' '; // 引用符削除状態でタグが終わる場合はホワイトスペースを入れる } } $result .= (' ' . $attrName . '=' . $quot . trim($attrValue) . $quot); } } if (!$this->isEmpty) { $close .= '>'; } $result .= $close; return $result; } // 終了ハンドラ function closeHandler(&$parser, $tagName) { $result = ''; $tagName = strtolower($tagName); if (in_array($tagName, $this->voidElement)) { $this->voidLevel--; } else if ($this->voidLevel == 0 && in_array($tagName, $this->aTagNotRemove)) { switch ($tagName) { case 'img' : $result .= $this->closeHandler_img(); break; case 'a' : $result .= $this->closeHandler_a(); break; default : $result .= $this->closeHandler_default($tagName); break; } } $this->result .= $result; } // img要素用終了ハンドラ function closeHandler_img() { $result = ''; switch ($this->imgOperate) { case 'text' : // テキストに変換する場合 break; case 'link' : // リンクに変換する場合 $result .= $this->closeHandler_a(); // a要素(終了タグ) break; default : // そのまま画像で出力する場合 $result .= $this->closeHandler_default('img'); } return $result; } // a要素用終了ハンドラ function closeHandler_a() { $result = ''; $this->aNestLevel--; if ($this->aNestLevel == 0) { $result .= $this->closeHandler_default('a'); } return $result; } // 通常の終了ハンドラ function closeHandler_default($tagName) { $result = ''; $tagName = strtolower($tagName); if ($this->isEmpty) { $result .= (' />'); } else { $result .= ('</' . $tagName . '>'); } $this->isEmpty = false; return $result; } // データ部のハンドラ function dataHandler(&$parser, $data) { $result = ''; $result .= $this->endOfEmpty(); if ($this->voidLevel == 0) { $result .= $data; } $this->result .= $result; } function endOfEmpty() { $result = ''; if ($this->isEmpty) { $result .= ('>'); // かわいい→('>') } $this->isEmpty = false; return $result; } function getResult() { // 出力前に整形 $strHTML = $this->result; $strHTML = preg_replace("!(\r|\n|\n\r)!", "\n", $strHTML); $strHTML = preg_replace("!\n\s+!", "\n", $strHTML); $strHTML = preg_replace("!\s+\n!", "\n", $strHTML); $strHTML = preg_replace("!\n+!", "\n", $strHTML); $strHTML = preg_replace("!^\n!", "", $strHTML); return $strHTML; } } ?>