plugins:mobilewithsinglepage:mobilewithsinglepage_code2

差分

この文書の現在のバージョンと選択したバージョンの差分を表示します。

この比較画面にリンクする

plugins:mobilewithsinglepage:mobilewithsinglepage_code2 [2011/03/23 21:28] (現在)
ライン 1: ライン 1:
 +<code php><?​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.
 + */
 +
 +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';​ }
 +  function getVersion() { return '2.1.0 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);​
 +    $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'​);​
 +  }
 +
 +  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) {
 +    $result = $baseName;
 +    $carrierName = trim(strtolower($this->​aMobileInfo['​carrierLongName'​]));​
 +    $optionName = $this->​getOption('​skinName'​);​
 +    $delim = PLUG_MOBILEWITHSINGLEPAGE_SKIN_DELIM;​
 +
 +    $aSkinName = array();
 +    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[$name])) {
 +        if ($value != ''​) {
 +          if (is_numeric($value)) {
 +            $result = (intval($this->​aMobileInfo[$name]) <= intval($value));​
 +          } else {
 +            $result = ($this->​aMobileInfo[$name] == $value);
 +          }
 +        } else {
 +          $result = !!($this->​aMobileInfo[$name]);​
 +        }
 +      }
 +    } 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;
 +  }
 +}
 +
 +?></​code>​
 
plugins/mobilewithsinglepage/mobilewithsinglepage_code2.txt · 最終更新: 2011/03/23 21:28 (外部編集)