diff --git a/lib/live/live_ShowThread.php b/lib/live/live_ShowThread.php new file mode 100755 index 000000000..9d205aedb --- /dev/null +++ b/lib/live/live_ShowThread.php @@ -0,0 +1,2050 @@ +(<[Aa][ ].+?>)(.*?)()) # リンク(PCREの特性上、必ずこのパターンを最初に試行する) +| +(?: + (?P # 引用 + ((?:>|>){1,2}[ ]?) # 引用符 + ( + (?:[1-9]\\d{0,3}) # 1つ目の番号 + (?: + (?:[ ]?(?:[,=]|、)[ ]?[1-9]\\d{0,3})+ # 連続 + | + -(?:[1-9]\\d{0,3})? # 範囲 + )? + ) + (?=\\D|$) + ) # 引用ここまで +| # PHP 5.3縛りにするなら、↓の\'のエスケープを外し、NOWDOCにする + (?P(ftp|h?t?tps?)://([0-9A-Za-z][\\w;/?:@=&$\\-_.+!*\'(),#%\\[\\]^~]+)) # URL + ([^\\s<>]*) # URLの直後、タグorホワイトスペースが現れるまでの文字列 +| + (?PID:[ ]?([0-9A-Za-z/.+]{8,11})(?=[^0-9A-Za-z/.+]|$)) # ID(8,10桁 +PC/携帯識別フラグ) +) +}x'; + + /** + * リダイレクタの種類 + * + * @var int + */ + const REDIRECTOR_NONE = 0; + const REDIRECTOR_IMENU = 1; + const REDIRECTOR_PINKTOWER = 2; + const REDIRECTOR_MACHIBBS = 3; + + /** + * NGあぼーんの種類 + * + * @var int + */ + const ABORN = -1; + const NG_NONE = 0; + const NG_NAME = 1; + const NG_MAIL = 2; + const NG_ID = 4; + const NG_MSG = 8; + const NG_FREQ = 16; + const NG_CHAIN = 32; + const NG_AA = 64; + + // }}} + // {{{ static properties + + /** + * まとめ読みモード時のスレッド数 + * + * @var int + */ + static private $_matome_count = 0; + + /** + * 本文以外がNGあぼーんにヒットした総数 + * + * @var int + */ + static protected $_ngaborns_head_hits = 0; + + /** + * 本文がNGあぼーんにヒットした総数 + * + * @var int + */ + static protected $_ngaborns_body_hits = 0; + + /** + * getAnchorRegex() のキャッシュ + * + * @var array + */ + static private $_anchorRegexes = array(); + + /** + * _getAnchorRegexParts() のキャッシュ + * + * @var array + */ + static private $_anchorRegexParts = null; + + // }}} + // {{{ properties + + /** + * まとめ読みモード時のスレッド番号 + * + * @var int + */ + protected $_matome; + + /** + * URLを処理する関数・メソッド名などを格納する配列 + * (組み込み) + * + * @var array + */ + protected $_url_handlers; + + /** + * URLを処理する関数・メソッド名などを格納する配列 + * (ユーザ定義、組み込みのものより優先) + * + * @var array + */ + protected $_user_url_handlers; + + /** + * 頻出IDをあぼーんする + * + * @var bool + */ + protected $_ngaborn_frequent; + + /** + * NG or あぼーんレスがあるかどうか + * + * @var bool + */ + protected $_has_ngaborns; + + /** + * あぼーんレス番号およびNGレス番号を格納する配列 + * array_intersect()を効率よく行うため、該当するレス番号は文字列にキャストして格納する + * + * @var array + */ + protected $_aborn_nums; + protected $_ng_nums; + + /** + * リダイレクタの種類 + * + * @var int + */ + protected $_redirector; + + /** + * スレッドオブジェクト + * + * @var ThreadRead + */ + public $thread; + + /** + * アクティブモナー・オブジェクト + * + * @var ActiveMona + */ + public $activeMona; + + /** + * アクティブモナーが有効か否か + * + * @var bool + */ + public $am_enabled = false; + + /** + * 引用しているレス番号を登録した配列 + * + * @var array + */ + protected $_quote_res_nums; + + + /** + * 引用チェック済みレス番号の配列 + * + * @var array + */ + protected $_quote_res_nums_checked; + + /** + * 引用変換済みレス番号の配列 + * + * @var array + */ + protected $_quote_res_nums_done; + + /** + * レス番号チェックの再帰の深さ + * + * @var int + */ + private $_quote_check_depth; + + /** + * デフォルトの名前 + * + * @var string + */ + protected $_nanashiName = null; + + /** + * 被アンカーを集計した配列(範囲アンカー含む) // [被参照レス番 : [参照レス番, ...], ...) + * + * @var array + */ + protected $_quote_from = null; + + /** + * アンカーを集計した配列(範囲アンカー除く) // [レス番 : [参照先レス番, ...], ...) + * + * @var array + */ + protected $_quote_to = null; + + /** + * お気に自動ランク + * + * @var bool + */ + private $_auto_fav_rank = false; + + /** + * リンクするサムネイルを生成するクラスのインスタンス + * + * @var ImageCache2_Thumbnailer + */ + public $thumbnailer; + + /** + * インライン表示するサムネイルを生成するクラスのインスタンス + * + * @var ImageCache2_Thumbnailer + */ + public $inline_prvw; + + /** + * インラインサムネイルのID属性接尾辞 + * + * @var string + */ + public $thumb_id_suffix; + + /** + * 画像に付加するメモ + * + * @var string + */ + public $img_memo; + + /** + * 画像にメモを付けるためのクエリ文字列 + * + * @var string + */ + public $img_memo_query; + + /** + * 画像解像度 + * + * @var float + */ + public $img_dpr = 1.0; + + /** + * 画像解像度を指定するクエリ文字列 + * + * @var string + */ + public $img_dpr_query; + + // }}} + // {{{ constructor + + /** + * コンストラクタ + */ + protected function __construct(ThreadRead $aThread, $matome = false) + { + global $_conf; + + // スレッドオブジェクトを登録 + $this->thread = $aThread; + $this->str_to_link_regex = $this->_buildStrToLinkRegex(); + + // まとめ読みモードか否か + if ($matome) { + $this->_matome = ++self::$_matome_count; + } else { + $this->_matome = false; + } + + $this->_url_handlers = array(); + $this->_user_url_handlers = array(); + + $this->_ngaborn_frequent = 0; + if ($_conf['ngaborn_frequent']) { + if ($_conf['ngaborn_frequent_dayres'] == 0) { + $this->_ngaborn_frequent = $_conf['ngaborn_frequent']; + } elseif ($this->thread->setDayRes() && $this->thread->dayres < $_conf['ngaborn_frequent_dayres']) { + $this->_ngaborn_frequent = $_conf['ngaborn_frequent']; + } + } + + $this->_has_ngaborns = false; + $this->_aborn_nums = array(); + $this->_ng_nums = array(); + + if (P2Util::isHostBbsPink($this->thread->host)) { + $this->_redirector = self::REDIRECTOR_PINKTOWER; + } elseif (P2Util::isHost2chs($this->thread->host)) { + $this->_redirector = self::REDIRECTOR_IMENU; + } elseif (P2Util::isHostMachiBbs($this->thread->host)) { + $this->_redirector = self::REDIRECTOR_MACHIBBS; + } else { + $this->_redirector = self::REDIRECTOR_NONE; + } + + $this->_quote_res_nums = array(); + $this->_quote_res_nums_checked = array(); + $this->_quote_res_nums_done = array(); + } + + // }}} + + /** + * @param void + * @return void + */ + protected function setBbsNonameName() + { + $st = new SettingTxt($this->thread->host, $this->thread->bbs); + $st->setSettingArray(); + if (array_key_exists('BBS_NONAME_NAME', $st->setting_array)) { + $BBS_NONAME_NAME = $st->setting_array['BBS_NONAME_NAME']; + if (strlen($BBS_NONAME_NAME)) { + $this->_nanashiName = $BBS_NONAME_NAME; + } + } + } + + // {{{ getDatToHtml() + + /** + * DatをHTML変換したものを取得する + * + * @param bool $is_fragment + * @return bool|string + */ + public function getDatToHtml($is_fragment = false) + { + return $this->datToHtml(true, $is_fragment); + } + public function getDatToHtml_resFrom($is_fragment = false) + { + return $this->datToHtml_resFrom(true, $is_fragment); + } + + // }}} + // {{{ datToHtml() + + /** + * DatをHTMLに変換して表示する + * + * @param bool $capture trueなら変換結果を出力せずに返す + * @param bool $is_fragment trueなら
で囲まない + * @return bool|string + */ + public function datToHtml($capture = false, $is_fragment = false) + { + global $_conf, $filter_hits, $last_hit_resnum; + + $aThread = $this->thread; + + // 表示レス範囲が指定されていなければ + if (!$aThread->resrange) { + $error = '

p2 error: {$this->resrange} is false at datToHtml()

'; + if ($capture) { + return $error; + } else { + echo $error; + return false; + } + } + + $start = $aThread->resrange['start']; + $to = $aThread->resrange['to']; + $nofirst = $aThread->resrange['nofirst']; + + $is_ktai = $_conf['ktai']; + $resFilter = ResFilter::getFilter(); + if ($resFilter && $resFilter->hasWord()) { + $do_filtering = true; + $nofirst = true; + } else { + $do_filtering = false; + } + + $datlines = $aThread->datlines; + $count = count($datlines); + + $buf['body'] = $is_fragment ? '' : "
\n"; + $buf['q'] = ''; + + // まず 1 を表示 + if (!$nofirst) { + $res = $this->transRes($datlines[0], 1); + if (is_array($res)) { + $buf['body'] .= $res['body']; + $buf['q'] .= $res['q'] ? $res['q'] : ''; + } else { + $buf['body'] .= $res; + } + } + + // 連鎖のため、範囲外のNGあぼーんチェック + if ($_conf['ngaborn_chain_all'] && empty($_GET['nong'])) { + $pre = min($count, $start); + for ($i = ($nofirst) ? 0 : 1; $i < $pre; $i++) { + $n = $i + 1; + list($name, $mail, $date_id, $msg) = $aThread->explodeDatLine($datlines[$i]); + if (($id = $aThread->ids[$n]) !== null) { + $date_id = str_replace($aThread->idp[$n] . $id, "ID:$id", $date_id); + } + $this->_ngAbornCheck($n, strip_tags($name), $mail, $date_id, $id, $msg); + } + } + + // フィルタリング + if ($do_filtering) { + $datlines = $resFilter->apply($this); + $filter_hits = $resFilter->hits; + $last_hit_resnum = $resFilter->last_hit_resnum; + } + + // 指定範囲を表示 + $i = 0; + $n = 0; + $rn = 0; + + if ($do_filtering) { + if (!empty($resFilter->range)) { + $start = $resFilter->range['start']; + $to = $resFilter->range['to']; + } + $pattern = $resFilter->getPattern(); + } else { + $pattern = null; + } + + foreach ($datlines as $i => $ares) { + if ($ares === null) { + continue; + } + $n++; + if ($i === 0 && !$nofirst) { + continue; + } + if ($n < $start) { + continue; + } + if ($n > $to) { + break; + } + $rn = $i + 1; + $res = $this->transRes($ares, $rn, $pattern); + if (is_array($res)) { + $buf['body'] .= $res['body']; + $buf['q'] .= $res['q'] ? $res['q'] : ''; + } else { + $buf['body'] .= $res; + } + if (!$capture && $n % 10 == 0) { + echo $buf['body']; + if ($do_filtering && !$is_ktai) { + echo "\n"; + } + flush(); + $buf['body'] = ''; + } + } + + if ($this->thread->readnum < $rn) { + $this->thread->readnum = $rn; + } + + if ($do_filtering && !$is_ktai) { + $buf['body'] .= "\n"; + } + +// +live オートリロードされるスレ内容の表示部 +echo <<
\n +LIVE; + + if (!$is_fragment) { + $buf['body'] .= "\n"; + } + + if ($capture) { + return $buf['body'] . $buf['q']; + } else { + echo $buf['body']; + echo $buf['q']; + flush(); + return true; + } + } + + /** + * 指定の書込みへのレスをHTMLに変換して表示する + * + * @param bool $capture trueなら変換結果を出力せずに返す + * @param bool $is_fragment trueなら
で囲まない + * @param bool $show_rootres trueなら指定の書込みも結果に含める + * @return bool|string + */ + public function datToHtml_resFrom($capture = false, $is_fragment = false, $show_rootres = false) + { + global $_conf; + + $aThread = $this->thread; + + // 表示レスが指定されていなければ + $target = $aThread->resrange['start']; + if (!$aThread->resrange || $target != $aThread->resrange['to']) { + $error = '

p2 error: {$this->resrange} is false at datToHtml()

'; + if ($capture) { + return $error; + } else { + echo $error; + return false; + } + } + + $datlines = $aThread->datlines; + $count = count($datlines); + + $buf['body'] = $is_fragment ? '' : "
\n"; + $buf['q'] = ''; + + // 連鎖のため、範囲外のNGあぼーんチェック + if ($_conf['ngaborn_chain_all'] && empty($_GET['nong'])) { + $pre = min($count, $start); + for ($i = ($nofirst) ? 0 : 1; $i < $pre; $i++) { + $n = $i + 1; + list($name, $mail, $date_id, $msg) = $aThread->explodeDatLine($datlines[$i]); + if (($id = $aThread->ids[$n]) !== null) { + $date_id = str_replace($aThread->idp[$n] . $id, "ID:$id", $date_id); + } + $this->_ngAbornCheck($n, strip_tags($name), $mail, $date_id, $id, $msg); + } + } + + // レス展開 + $datlines = array_fill(0, count($aThread->datlines), null); + if ($show_rootres) { + $datlines[$target - 1] = $aThread->datlines[$target - 1]; + } + list($name, $mail, $date_id, $msg) = + $aThread->explodeDatLine($aThread->datlines[$target - 1]); + foreach ($this->checkQuoteResNums($target, $name, $msg, false, true, false) as $rn) { + $ri = $rn - 1; + if ($datlines[$ri] === null) { + $datlines[$ri] = $aThread->datlines[$ri]; + } + } + + // 表示 + $i = 0; + $n = 0; + $rn = 0; + foreach ($datlines as $i => $ares) { + if ($ares === null) { + continue; + } + $n++; + $rn = $i + 1; + $res = $this->transRes($ares, $rn); + if (is_array($res)) { + $buf['body'] .= $res['body']; + $buf['q'] .= $res['q'] ? $res['q'] : ''; + } else { + $buf['body'] .= $res; + } + if (!$capture && $n % 10 == 0) { + echo $buf['body']; + flush(); + $buf['body'] = ''; + } + } + + if (!$is_fragment) { + $buf['body'] .= "
\n"; + } + + if ($capture) { + return $buf['body'] . $buf['q']; + } else { + echo $buf['body']; + echo $buf['q']; + flush(); + return true; + } + } + + // }}} + // {{{ transRes() + + /** + * DatレスをHTMLレスに変換する + * + * @param string $ares datの1ライン + * @param int $i レス番号 + * @return string + */ + abstract public function transRes($ares, $i); + + // }}} + // {{{ transName() + + /** + * 名前をHTML用に変換する + * + * @param string $name 名前 + * @return string + */ + abstract public function transName($name); + + // }}} + // {{{ transMsg() + + /** + * datのレスメッセージをHTML表示用メッセージに変換する + * + * @param string $msg メッセージ + * @param int $mynum レス番号 + * @return string + */ + abstract public function transMsg($msg, $mynum); + + // }}} + // {{{ replaceBeId() + + /** + * BEプロファイルリンク変換 + */ + public function replaceBeId($date_id, $i) + { + global $_conf; + + $beid_replace = "thread->host}/test/read.cgi/{$this->thread->bbs}/{$this->thread->key}/{$i}\"{$_conf['ext_win_target_at']}>Lv.\$2"; + + // + $be_match = '||i'; + if (preg_match($be_match, $date_id)) { + $date_id = preg_replace($be_match, $beid_replace, $date_id); + + } else { + + $beid_replace = "thread->host}/test/read.cgi/{$this->thread->bbs}/{$this->thread->key}/{$i}\"{$_conf['ext_win_target_at']}>?\$2"; + $date_id = preg_replace('|BE: ?(\d+)-(#*)|i', $beid_replace, $date_id); + } + + return $date_id; + } + + // }}} + // {{{ _ngAbornCheck() + + /** + * NGあぼーんチェック + * + * @param int $i レス番号 + * @param string $name 名前欄 + * @param string $mail メール欄 + * @param string $date_id 日付・ID欄 + * @param string $id ID + * @param string $msg レス本文 + * @param bool $nong NGチェックをするかどうか + * @param array &$info NGの理由が格納される変数の参照 + * @return int NGタイプ。ShowThread::NG_XXX のビット和か ShowThread::ABORN + */ + protected function _ngAbornCheck($i, $name, $mail, $date_id, $id, $msg, $nong = false, &$info = null) + { + global $_conf, $ngaborns_hits; + + $info = array(); + $type = self::NG_NONE; + + // {{{ 頻出IDチェック + + if ($this->_ngaborn_frequent && $id && $this->thread->idcount[$id] >= $_conf['ngaborn_frequent_num']) { + if (!$_conf['ngaborn_frequent_one'] && $id == $this->thread->ids[1]) { + // >>1 はそのまま表示 + } elseif ($this->_ngaborn_frequent == 1) { + $ngaborns_hits['aborn_freq']++; + return $this->_markNgAborn($i, self::ABORN, false); + } elseif (!$nong) { + $ngaborns_hits['ng_freq']++; + $type |= $this->_markNgAborn($i, self::NG_FREQ, false); + $info[] = sprintf('頻出ID:%s(%d)', $id, $this->thread->idcount[$id]); + } + } + + // }}} + // {{{ 連鎖チェック + + if ($_conf['ngaborn_chain'] && $this->_has_ngaborns && + preg_match_all('/(?:>|>)([1-9][0-9\\-,]*)/', $msg, $matches) + ) { + $references = array_unique(preg_split('/[-,]+/', + trim(implode(',', $matches[1]), '-,'), + -1, + PREG_SPLIT_NO_EMPTY)); + $intersections = array_intersect($references, $this->_aborn_nums); + $info_suffix = ''; + + if ($intersections) { + if ($_conf['ngaborn_chain'] == 1) { + $ngaborns_hits['aborn_chain']++; + return $this->_markNgAborn($i, self::ABORN, true); + } + if ($nong) { + $intersections = null; + } else { + $info_suffix = '(' . (($_conf['ktai']) ? 'アボン' : 'あぼーん') . ')'; + } + } elseif (!$nong) { + $intersections = array_intersect($references, $this->_ng_nums); + } + + if ($intersections) { + $ngaborns_hits['ng_chain']++; + $type |= $this->_markNgAborn($i, self::NG_CHAIN, true); + $info[] = sprintf('連鎖NG:>>%d%s', current($intersections), $info_suffix); + } + } + + // }}} + // {{{ あぼーんチェック + + // あぼーんレス + if ($this->abornResCheck($i) !== false) { + $ngaborns_hits['aborn_res']++; + return $this->_markNgAborn($i, self::ABORN, false); + } + + // あぼーんネーム + if ($this->ngAbornCheck('aborn_name', $name) !== false) { + $ngaborns_hits['aborn_name']++; + return $this->_markNgAborn($i, self::ABORN, false); + } + + // あぼーんメール + if ($this->ngAbornCheck('aborn_mail', $mail) !== false) { + $ngaborns_hits['aborn_mail']++; + return $this->_markNgAborn($i, self::ABORN, false); + } + + // あぼーんID + if ($this->ngAbornCheck('aborn_id', $date_id) !== false) { + $ngaborns_hits['aborn_id']++; + return $this->_markNgAborn($i, self::ABORN, false); + } + + // あぼーんメッセージ + if ($this->ngAbornCheck('aborn_msg', $msg) !== false) { + $ngaborns_hits['aborn_msg']++; + return $this->_markNgAborn($i, self::ABORN, true); + } + + // }}} + + if ($nong) { + return $type; + } + + // {{{ NGチェック + + // NGネームチェック + if ($this->ngAbornCheck('ng_name', $name) !== false) { + $ngaborns_hits['ng_name']++; + $type |= $this->_markNgAborn($i, self::NG_NAME, false); + } + + // NGメールチェック + if ($this->ngAbornCheck('ng_mail', $mail) !== false) { + $ngaborns_hits['ng_mail']++; + $type |= $this->_markNgAborn($i, self::NG_MAIL, false); + } + + // NGIDチェック + if ($this->ngAbornCheck('ng_id', $date_id) !== false) { + $ngaborns_hits['ng_id']++; + $type |= $this->_markNgAborn($i, self::NG_ID, false); + } + + // NGメッセージチェック + $a_ng_msg = $this->ngAbornCheck('ng_msg', $msg); + if ($a_ng_msg !== false) { + $ngaborns_hits['ng_msg']++; + $type |= $this->_markNgAborn($i, self::NG_MSG, true); + $info[] = sprintf('NG%s:%s', + ($_conf['ktai']) ? 'ワード' : 'ワード', + p2h($a_ng_msg)); + } + + // }}} + + return $type; + } + + // }}} + // {{{ _markNgAborn() + + /** + * NGあぼーんにヒットしたレス番号を記録する + * + * @param int $num レス番号 + * @param int $type NGあぼーんの種類 + * @param bool $isBody 本文にヒットしたかどうか + * @return int $typeと同じ値 + */ + protected function _markNgAborn($num, $type, $isBody) + { + if ($type) { + if ($isBody) { + self::$_ngaborns_body_hits++; + } else { + self::$_ngaborns_head_hits++; + } + + // array_intersect()を効率よく行うため、レス番号を文字列型にキャストする + $str = (string)$num; + if ($type == self::ABORN) { + $this->_aborn_nums[$num] = $str; + } else { + $this->_ng_nums[$num] = $str; + } + + $this->_has_ngaborns = true; + } + + return $type; + } + + // }}} + // {{{ ngAbornCheck() + + /** + * NGあぼーんチェック + */ + public function ngAbornCheck($code, $resfield, $ic = false) + { + global $ngaborns; + + //$GLOBALS['debug'] && $GLOBALS['profiler']->enterSection('ngAbornCheck()'); + + if (isset($ngaborns[$code]['data']) && is_array($ngaborns[$code]['data'])) { + // +Wiki:BEあぼーん + if ($code == 'aborn_be' || $code == 'ng_be') { + // プロフィールIDを抜き出す + if (preg_match('/BE:(\\d+)/', $resfield, $matches)) { + $beId = P2UtilWiki::calcBeId((int)$matches[1]); + if ($beId === 0) { + return false; + } + $resfield = (string)$beId; + } else { + return false; + } + } + + $bbs = $this->thread->bbs; + $title = $this->thread->ttitle_hc; + + foreach ($ngaborns[$code]['data'] as $k => $v) { + // 板チェック + if (isset($v['bbs']) && in_array($bbs, $v['bbs']) == false) { + continue; + } + + // タイトルチェック + if (isset($v['title']) && stripos($title, $v['title']) === false) { + continue; + } + + // ワードチェック + // 正規表現 + if ($v['regex']) { + $re_method = $v['regex']; + /*if ($re_method($v['word'], $resfield, $matches)) { + $this->ngAbornUpdate($code, $k); + //$GLOBALS['debug'] && $GLOBALS['profiler']->leaveSection('ngAbornCheck()'); + return p2h($matches[0]); + }*/ + if ($re_method($v['word'], $resfield)) { + $this->ngAbornUpdate($code, $k); + //$GLOBALS['debug'] && $GLOBALS['profiler']->leaveSection('ngAbornCheck()'); + return $v['cond']; + } + // +Wiki:BEあぼーん(完全一致) + } elseif ($code == 'aborn_be' || $code == 'ng_be') { + if ($resfield == $v['word']) { + $this->ngAbornUpdate($code, $k); + //$GLOBALS['debug'] && $GLOBALS['profiler']->leaveSection('ngAbornCheck()'); + return $v['cond']; + } + // 大文字小文字を無視 + } elseif ($ic || $v['ignorecase']) { + if (stripos($resfield, $v['word']) !== false) { + $this->ngAbornUpdate($code, $k); + //$GLOBALS['debug'] && $GLOBALS['profiler']->leaveSection('ngAbornCheck()'); + return $v['cond']; + } + // 単純に文字列が含まれるかどうかをチェック + } else { + if (strpos($resfield, $v['word']) !== false) { + $this->ngAbornUpdate($code, $k); + //$GLOBALS['debug'] && $GLOBALS['profiler']->leaveSection('ngAbornCheck()'); + return $v['cond']; + } + } + } + } + + //$GLOBALS['debug'] && $GLOBALS['profiler']->leaveSection('ngAbornCheck()'); + return false; + } + + // }}} + // {{{ abornResCheck() + + /** + * 特定レスの透明あぼーんチェック + */ + public function abornResCheck($resnum) + { + global $ngaborns; + + $target = $this->thread->host . '/' . $this->thread->bbs . '/' . $this->thread->key . '/' . $resnum; + + if (isset($ngaborns['aborn_res']['data']) && is_array($ngaborns['aborn_res']['data'])) { + foreach ($ngaborns['aborn_res']['data'] as $k => $v) { + if ($ngaborns['aborn_res']['data'][$k]['word'] == $target) { + $this->ngAbornUpdate('aborn_res', $k); + return true; + } + } + } + return false; + } + + // }}} + // {{{ ngAbornUpdate() + + /** + * NG/あぼ〜ん日時と回数を更新 + */ + public function ngAbornUpdate($code, $k) + { + global $ngaborns; + + if (isset($ngaborns[$code]['data'][$k])) { + $ngaborns[$code]['data'][$k]['lasttime'] = date('Y/m/d G:i'); // HIT時間を更新 + if (empty($ngaborns[$code]['data'][$k]['hits'])) { + $ngaborns[$code]['data'][$k]['hits'] = 1; // 初HIT + } else { + $ngaborns[$code]['data'][$k]['hits']++; // HIT回数を更新 + } + } + } + + // }}} + // {{{ addURLHandler() + + /** + * ユーザ定義URLハンドラ(メッセージ中のURLを書き換える関数)を追加する + * + * ハンドラは最初に追加されたものから順番に試行される + * URLはハンドラの返り値(文字列)で置換される + * falseを帰した場合は次のハンドラに処理が委ねられる + * + * ユーザ定義URLハンドラの引数は + * 1. string $url URL + * 2. array $purl URLをparse_url()したもの + * 3. string $str パターンにマッチした文字列、URLと同じことが多い + * 4. object $aShowThread 呼び出し元のオブジェクト + * である + * 常にfalseを返し、内部で処理するだけの関数を登録してもよい + * + * @param callback $function コールバックメソッド + * @return void + * @access public + * @todo ユーザ定義URLハンドラのオートロード機能を実装 + */ + public function addURLHandler($function) + { + $this->_user_url_handlers[] = $function; + } + + // }}} + // {{{ stripLineBreaks() + + /** + * 文末の改行と連続する改行を取り除く + * + * @param string $msg + * @param string $replacement + * @return string + */ + public function stripLineBreaks($msg, $replacement = '

') + { + if (P2_MBREGEX_AVAILABLE) { + $msg = mb_ereg_replace('(?:[\\s ]*
)+[\\s ]*$', '', $msg); + $msg = mb_ereg_replace('(?:[\\s ]*
){3,}', $replacement, $msg); + } else { + mb_convert_variables('UTF-8', 'CP932', $msg, $replacement); + $msg = preg_replace('/(?:[\\s\\x{3000}]*
)+[\\s\\x{3000}]*$/u', '', $msg); + $msg = preg_replace('/(?:[\\s\\x{3000}]*
){3,}/u', $replacement, $msg); + $msg = mb_convert_encoding($msg, 'CP932', 'UTF-8'); + } + + return $msg; + } + + // }}} + // {{{ transLink() + + /** + * リンク対象文字列を変換する + * + * @param string $str + * @return string + */ + public function transLink($str) + { + return preg_replace_callback($this->str_to_link_regex, array($this, 'transLinkDo'), $str); + } + + // }}} + // {{{ transLinkDo() + + /** + * リンク対象文字列の種類を判定して対応した関数/メソッドに渡す + * + * @param array $s + * @return string + */ + public function transLinkDo(array $s) + { + global $_conf; + + $orig = $s[0]; + $following = ''; + + // PHP 5.2.7 未満の preg_replace_callback() では名前付き捕獲式集合が使えないので + /* + if (!array_key_exists('link', $s)) { + $s['link'] = $s[1]; + $s['quote'] = $s[5]; + $s['url'] = $s[8]; + $s['id'] = $s[11]; + } + */ + + // マッチしたサブパターンに応じて分岐 + // リンク + if ($s['link']) { + if (preg_match('{ href=(["\'])?(.+?)(?(1)\\1)(?=[ >])}i', $s[2], $m)) { + $url = $m[2]; + $str = $s[3]; + } else { + return $s[3]; + } + + // 引用 + } elseif ($s['quote']) { + return preg_replace_callback( + self::getAnchorRegex('/(%prefix%)?(%a_range%)/'), + array($this, '_quoteResCallback'), $s['quote']); + + // http or ftp のURL + } elseif ($s['url']) { + if ($_conf['ktai'] && $s[9] == 'ftp') { + return $orig; + } + $url = preg_replace('/^t?(tps?)$/', 'ht$1', $s[9]) . '://' . $s[10]; + $str = $s['url']; + $following = $s[11]; + if (strlen($following) > 0) { + // ウィキペディア日本語版のURLで、SJISの2バイト文字の上位バイト + // (0x81-0x9F,0xE0-0xEF)が続くとき + if (P2Util::isUrlWikipediaJa($url)) { + $leading = ord($following); + if ((($leading ^ 0x90) < 32 && $leading != 0x80) || ($leading ^ 0xE0) < 16) { + $url .= rawurlencode(mb_convert_encoding($following, 'UTF-8', 'CP932')); + $str .= $following; + $following = ''; + } + } elseif (strpos($following, 'tp://') !== false) { + // 全角スペース+URL等の場合があるので再チェック + $following = $this->transLink($following); + } + } + + // ID + } elseif ($s['id'] && $_conf['flex_idpopup']) { // && $_conf['flex_idlink_k'] + return $this->idFilter($s['id'], $s[12]); + + // その他(予備) + } else { + return strip_tags($orig); + } + + // リダイレクタを外す + switch ($this->_redirector) { + case self::REDIRECTOR_IMENU: + $url = preg_replace('{^([a-z]+://)ime\\.nu/}', '$1', $url); + break; + case self::REDIRECTOR_PINKTOWER: + $url = preg_replace('{^([a-z]+://)pinktower\\.com/}', '$1', $url); + break; + case self::REDIRECTOR_MACHIBBS: + $url = preg_replace('{^[a-z]+://machi(?:bbs\\.com|\\.to)/bbs/link\\.cgi\\?URL=}', '', $url); + break; + } + + // エスケープされていない特殊文字をエスケープ + $url = p2h($url, false); + $str = p2h($str, false); + // 実態参照・数値参照を完全にデコードしようとすると負荷が大きいし、 + // "&"以外の特殊文字はほとんどの場合URLエンコードされているはずなので + // 中途半端に凝った処理はせず、"&"→"&"のみ再変換する。 + $raw_url = str_replace('&', '&', $url); + + // URLをパース・ホストを検証 + $purl = @parse_url($raw_url); + if (!$purl || !array_key_exists('host', $purl) || + strpos($purl['host'], '.') === false || + $purl['host'] == '127.0.0.1' || + //HostCheck::isAddressLocal($purl['host']) || + //HostCheck::isAddressPrivate($purl['host']) || + P2Util::isHostExample($purl['host'])) + { + return $orig; + } + // URLのマッチングで"&"を考慮しなくて済むように、生のURLを登録しておく + $purl[0] = $raw_url; + + // URLを処理 + foreach ($this->_user_url_handlers as $handler) { + if (false !== ($link = call_user_func($handler, $url, $purl, $str, $this))) { + return $link . $following; + } + } + foreach ($this->_url_handlers as $handler) { + if (false !== ($link = $this->$handler($url, $purl, $str))) { + return $link . $following; + } + } + + return $orig; + } + + // }}} + // {{{ idFilter() + + /** + * IDフィルタリング変換 + * + * @param string $idstr ID:xxxxxxxxxx + * @param string $id xxxxxxxxxx + * @return string + */ + abstract public function idFilter($idstr, $id); + + // }}} + // {{{ _idFilterCallback() + + /** + * IDフィルタリング変換 + * + * @param array $s 正規表現にマッチした要素の配列 + * @return string + */ + protected function _idFilterCallback(array $s) + { + return $this->idFilter($s[0], $s[1]); + } + + // }}} + // {{{ _quoteNameCallback() + + /** + * @param array $s + * @return string HTML + */ + protected function _quoteNameCallback($s) + { + return preg_replace_callback( + self::getAnchorRegex('/(%prefix%)?(%a_num%)/'), + array($this, '_quoteResCallback'), $s[0] + ); + } + + // }}} + // {{{ quoteRes() + + /** + * 引用変換(単独) + * + * @param string $full >>1 + * @param string $qsign >> + * @param string $appointed_num 1 + * @return string + */ + abstract public function quoteRes($full, $qsign, $appointed_num); + + // }}} + // {{{ _quoteResCallback() + + /** + * 引用変換(単独) + * + * @param array $s 正規表現にマッチした要素の配列 + * @return string + */ + protected function _quoteResCallback(array $s) + { + return $this->quoteRes($s[0], $s[1], $s[2]); + } + + // }}} + // {{{ quoteResRange() + + /** + * 引用変換(範囲) + * + * @param string $full >>1-100 + * @param string $qsign >> + * @param string $appointed_num 1-100 + * @return string + */ + abstract public function quoteResRange($full, $qsign, $appointed_num); + + // }}} + // {{{ _quoteResRangeCallback() + + /** + * 引用変換(範囲) + * + * @param array $s 正規表現にマッチした要素の配列 + * @return string + */ + protected function _quoteResRangeCallback(array $s) + { + return $this->quoteResRange($s[0], $s[1], $s[2]); + } + + // }}} + // {{{ checkQuoteResNums() + + /** + * HTMLメッセージ中の引用レスの番号を再帰チェックする + */ + public function checkQuoteResNums($res_num, $name, $msg, + $with_quotes = true, + $with_backlinks = null, + $cascade = true) + { + global $_conf; + + $this->_quote_check_depth = 0; + + if ($with_backlinks === null) { + $with_backlinks = ($_conf['backlink_list'] > 0 || $_conf['backlink_block'] > 0) ? true : false; + } + + if ($with_backlinks) { + return $this->checkQuoteResNumsFromSummary( + $res_num == 0 ? 1 : $res_num, $with_quotes, $with_backlinks); + } + + return $this->_checkQuoteResNums($res_num, $name, $msg); + } + + // }}} + // {{{ _checkQuoteResNums() + + /** + * HTMLメッセージ中の引用レスの番号を再帰チェックする + */ + protected function _checkQuoteResNums($res_num, $name, $msg) + { + // 再帰リミッタ + if ($this->_quote_check_depth > 30) { + return array(); + } else { + $this->_quote_check_depth++; + } + + if (array_key_exists($res_num, $this->_quote_res_nums)) { + return $this->_quote_res_nums[$res_num]; + } + + $aThread = $this->thread; + + $quote_res_nums = array(); + + $name = preg_replace('/(◆.*)/', '', $name, 1); + + // 名前 + if ($matches = $this->getQuoteResNumsName($name)) { + foreach ($matches as $a_quote_res_num) { + if ($a_quote_res_num) { + $quote_res_nums[] = $a_quote_res_num; + $a_quote_res_idx = $a_quote_res_num - 1; + + // 自分自身の番号と同一でなければ、 + if ($a_quote_res_num != $res_num) { + // チェックしていない番号を再帰チェック + if (!isset($this->_quote_res_nums_checked[$a_quote_res_num])) { + $this->_quote_res_nums_checked[$a_quote_res_num] = true; + if (isset($aThread->datlines[$a_quote_res_idx])) { + $datalinear = $aThread->explodeDatLine($aThread->datlines[$a_quote_res_idx]); + $quote_name = $datalinear[0]; + $quote_msg = $aThread->datlines[$a_quote_res_idx]; + $quote_res_nums = array_merge($quote_res_nums, + $this->_checkQuoteResNums($a_quote_res_num, + $quote_name, + $quote_msg)); + } + } + } + } + // $name=preg_replace("/([0-9]+)/", "", $name, 1); + } + } + + // >>1のリンクをいったん外す + // >>1 + $msg = preg_replace('{<[Aa] .+?>(>>[1-9][\\d\\-]*)}', '$1', $msg); + + //echo $msg; + if (preg_match_all(self::getAnchorRegex('/%full%/'), $msg, $out, PREG_PATTERN_ORDER)) { + foreach ($out[2] as $numberq) { + if ($matches=preg_split(self::getAnchorRegex('/%delimiter%/'), $numberq)) { + foreach ($matches as $a_quote_res_num) { + if (preg_match(self::getAnchorRegex('/%range_delimiter%/'),$a_quote_res_num)) { continue;} + $a_quote_res_num = (int) (mb_convert_kana($a_quote_res_num, 'n')); + $a_quote_res_idx = $a_quote_res_num - 1; + + //echo $a_quote_res_num; + + if (!$a_quote_res_num) {break;} + $quote_res_nums[] = $a_quote_res_num; + + // 自分自身の番号と同一でなければ、 + if ($a_quote_res_num != $res_num) { + // チェックしていない番号を再帰チェック + if (!isset($this->_quote_res_nums_checked[$a_quote_res_num])) { + $this->_quote_res_nums_checked[$a_quote_res_num] = true; + if (isset($aThread->datlines[$a_quote_res_idx])) { + $datalinear = $aThread->explodeDatLine($aThread->datlines[$a_quote_res_idx]); + $quote_name = $datalinear[0]; + $quote_msg = $aThread->datlines[$a_quote_res_idx]; + $quote_res_nums = array_merge($quote_res_nums, + $this->_checkQuoteResNums($a_quote_res_num, + $quote_name, + $quote_msg)); + } + } + } + + } + + } + + } + + } + + if (count($quote_res_nums)) { + sort($quote_res_nums, SORT_NUMERIC); + $this->_quote_res_nums[$res_num] = array_unique($quote_res_nums); + $quote_res_nums = $this->_quote_res_nums[$res_num]; + } + + return $quote_res_nums; + } + + // }}} + // {{{ checkQuoteResNumsFromSummary() + + /** + * 引用レス集計結果からポップアップ用に用意すべき番号を再帰チェックする + */ + public function checkQuoteResNumsFromSummary($res_num, $with_quotes, $with_backlinks) + { + // 再帰リミッタ + if ($this->_quote_check_depth > 3000) { + return array(); + } else { + $this->_quote_check_depth++; + } + + $ret = array(); + + // 参照レス + if ($with_quotes) { + $ret = array_merge($ret, + $this->_checkQuoteResNumsFromSummary( + $res_num, $this->getQuoteTo(), $with_quotes, $with_backlinks)); + } + // 被参照レス + if ($with_backlinks) { + $ret = array_merge($ret, + $this->_checkQuoteResNumsFromSummary( + $res_num, $this->getQuoteFrom(), $with_quotes, $with_backlinks)); + } + return $ret; + } + + // }}} + // {{{ _checkQuoteResNumsFromSummary() + + protected function _checkQuoteResNumsFromSummary($res_num, $quotes, $with_quotes, $with_backlinks) + { + $ret = array(); + if (array_key_exists($res_num, $quotes)) { + foreach ($quotes[$res_num] as $quote_num) { + $ret[] = $quote_num; + if ($quote_num != $res_num) { + if (!isset($this->_quote_res_nums_checked[$quote_num])) { + $this->_quote_res_nums_checked[$quote_num] = true; + $ret = array_merge($ret, + $this->checkQuoteResNumsFromSummary($quote_num, $with_quotes, $with_backlinks)); + } + } + } + } + return $ret; + } + + // }}} + // {{{ getQuoteResNumsName() + + public function getQuoteResNumsName($name) + { + if (strlen(trim($name)) == 0 || $name == $this->_nanashiName) { + return false; + } + + // トリップを除去 + $name = preg_replace('/◆.*/', '', $name, 1); + $name = strip_tags($name); + + /* + //if (preg_match('/[0-9]+/', $name, $m)) { + return (int)$m[0]; + } + */ + + if (preg_match_all(self::getAnchorRegex('/(?:^|%prefix%)(%nums%)/'), $name, $matches)) { + foreach ($matches[1] as $a_quote_res_num) { + $quote_res_nums[] = (int)mb_convert_kana($a_quote_res_num, 'n'); + } + return array_unique($quote_res_nums); + } + + return false; + } + + // }}} + // {{{ _wikipediaFilter() + + /** + * [[語句]]があった時にWikipediaへ自動リンク + * + * @param string $msg メッセージ + * @return string + * + * original code: + * http://akid.s17.xrea.com/p2puki/index.phtml?%A5%E6%A1%BC%A5%B6%A1%BC%A5%AB%A5%B9%A5%BF%A5%DE%A5%A4%A5%BA%28rep2%20Ver%201.7.0%A1%C1%29#led2c85d + */ + protected function _wikipediaFilter($msg) + { + if (strpos($msg, '[[') === false) { + return $msg; + } + + $msg = preg_replace_callback('/\\[\\[([^\\[\\]\\n<>]+)\\]\\]+/u', + array($this, '_linkToWikipeidaCallback'), + mb_convert_encoding($msg, 'UTF-8', 'CP932')); + + return mb_convert_encoding($msg, 'CP932', 'UTF-8'); + } + + // }}} + // {{{ _linkToWikipeidaCallback() + + /** + * Wikipediaの語句をリンクに変換して返す. + * + * @param array $matches + * @return string + */ + protected function _linkToWikipeidaCallback($matches) + { + return '[[' . $this->_linkToWikipeida($matches[1]) . ']]'; + } + + // }}} + // {{{ _linkToWikipeida() + + /** + * Wikipediaの語句をリンクに変換して返す. + * + * @param string $word 語句 + * @return string + */ + abstract protected function _linkToWikipeida($word); + + // }}} + // {{{ _makeQuotes() + + /** + * レスデータを集計して$this->_quote_toと$this->_quote_fromに保存. + */ + protected function _makeQuotes() + { + global $_conf; + + $this->_quote_to = array(); + $this->_quote_from = array(); + + if (!$this->thread->datlines) { + return; + } + + foreach ($this->thread->datlines as $num => $line) { + list($name, $mail, $date_id, $msg) = $this->thread->explodeDatLine($line); + + // NGあぼーんチェック + if (($id = $this->thread->ids[$num + 1]) !== null) { + $date_id = str_replace($this->thread->idp[$i] . $id, 'ID:' . $id, $date_id); + } + $ng_type = $this->_ngAbornCheck($num + 1, strip_tags($name), $mail, $date_id, $id, $msg); + if ($ng_type == self::ABORN) { + continue; + } + + // 名前 + if ($nmatches = $this->getQuoteResNumsName($name)) { + foreach ($nmatches as $a_quote_res_num) { + if ($a_quote_res_num) { + if (!array_key_exists($a_quote_res_num, $this->_quote_from) || $this->_quote_from[$a_quote_res_num] === null) { + $this->_quote_from[$a_quote_res_num] = array(); + } + if (!in_array($num + 1, $this->_quote_from[$a_quote_res_num])) { + $this->_quote_from[$a_quote_res_num][] = $num + 1; + } + + if (!array_key_exists($num + 1, $this->_quote_to) || $this->_quote_to[$num + 1] === null) { + $this->_quote_to[$num + 1] = array(); + } + if (!in_array($a_quote_res_num, $this->_quote_to[$num + 1])) { + $this->_quote_to[$num + 1][] = $a_quote_res_num; + } + } + } + } + + + // >>1のリンクをいったん外す + // >>1 + $msg = preg_replace('{<[Aa] .+?>(>>[1-9][\\d\\-]*)}', '$1', $msg); + if (!preg_match_all(self::getAnchorRegex('/%full%/'), $msg, $out, PREG_PATTERN_ORDER)) { + continue; + } + foreach ($out[2] as $numberq) { + if (!preg_match_all(self::getAnchorRegex('/(?:%prefix%)?(%a_range%)/'), $numberq, $anchors, PREG_PATTERN_ORDER)) continue; + foreach ($anchors[1] as $anchor) { + if (preg_match(self::getAnchorRegex('/(%a_num%)%range_delimiter%(?:%prefix%)?(%a_num%)/'), $anchor, $matches)) { + $from = intval(mb_convert_kana($matches[1], 'n')); + $to = intval(mb_convert_kana($matches[2], 'n')); + if ($from < 1 || $to < 1 || $from > $to + || ($to - $from + 1) > sizeof($this->thread->datlines)) { + continue; + } + if ($_conf['backlink_list_range_anchor_limit'] != 0) { + if ($to - $from >= $_conf['backlink_list_range_anchor_limit']) { + continue; + } + } + for ($i = $from; $i <= $to; $i++) { + if ($i > sizeof($this->thread->datlines)) { + break; + } + if ($_conf['backlink_list_future_anchor'] == 0) { + // レス番号以降のアンカーは無視する + if ($i >= $num + 1) { + continue; + } + } + if (!array_key_exists($i, $this->_quote_from) || $this->_quote_from[$i] === null) { + $this->_quote_from[$i] = array(); + } + if (!in_array($num + 1, $this->_quote_from[$i])) { + $this->_quote_from[$i][] = $num + 1; + } + } + } elseif (preg_match(self::getAnchorRegex('/(%a_num%)/'), $anchor, $matches)) { + $quote_num = intval(mb_convert_kana($matches[1], 'n')); + if (!array_key_exists($num + 1, $this->_quote_to) || $this->_quote_to[$num + 1] === null) { + $this->_quote_to[$num + 1] = array(); + } + if (!in_array($quote_num, $this->_quote_to[$num + 1])) { + $this->_quote_to[$num + 1][] = $quote_num; + } + + if ($_conf['backlink_list_future_anchor'] == 0) { + // レス番号以降のアンカーは無視する + if ($quote_num >= $num + 1) { + continue; + } + } + if (!array_key_exists($quote_num, $this->_quote_from) || $this->_quote_from[$quote_num] === null) { + $this->_quote_from[$quote_num] = array(); + } + if (!in_array($num + 1, $this->_quote_from[$quote_num])) { + $this->_quote_from[$quote_num][] = $num + 1; + } + } + } + } + } + } + + // }}} + // {{{ getQuoteFrom() + + /** + * 被レスリストを返す. + * + * @return array + */ + public function getQuoteFrom() + { + if ($this->_quote_from === null) { + $this->_makeQuotes(); // 被レスデータ集計 + } + return $this->_quote_from; + } + + // }}} + // {{{ getQuoteTo() + + /** + * レスリストを返す. + * + * @return array + */ + public function getQuoteTo() + { + if ($this->_quote_to === null) { + $this->_makeQuotes(); // レスデータ集計 + } + return $this->_quote_to; + } + + // }}} + // {{{ _quotebackListHtml() + + /** + * 被レスリストをHTMLで整形して返す. + * + * @param int $resnum レス番号 + * @param int $type 1:縦形式 2:横形式 3:展開用ブロック用文字列 + * @param bool $popup 横形式でのポップアップ処理(true:ポップアップする、false:挿入する) + * @return string + */ + protected function _quotebackListHtml($resnum, $type, $popup=true) + { + $quote_from = $this->getQuoteFrom(); + if (!array_key_exists($resnum, $quote_from)) return $ret; + + $anchors = $quote_from[$resnum]; + sort($anchors); + + if ($type == 1) { + return $this->_quotebackVerticalListHtml($anchors, $resnum); + } elseif ($type == 2) { + return $this->_quotebackHorizontalListHtml($anchors, $resnum); + } elseif ($type == 3) { + return $this->_quotebackResData($anchors, $resnum); + } + } + + // }}} + // {{{ _quotebackVerticalListHtml() + + protected function _quotebackVerticalListHtml($anchors, $resnum) + { + $ret = '
    '; + $anchor_cnt = 1; + foreach ($anchors as $anchor) { + if ($anchor_cnt > 1) { + $ret .= '
  • '; + } + if ($anchor_cnt < count($anchors)) { + $ret .= '
  • ├'; + } else { + $ret .= '
  • └'; + } + $ret .= ($anchor == $resnum) + ? $anchor + : $this->quoteRes($anchor, '', $anchor, true); + $anchor_cnt++; + } + $ret .= '
'; + return $ret; + } + + // }}} + // {{{ _quotebackHorizontalListHtml() + + protected function _quotebackHorizontalListHtml($anchors, $resnum) + { + $ret = '
'; + $count = 0; + + foreach ($anchors as $idx => $anchor) { + if ($anchor == $resnum) { + continue; + } + $anchor_link= $this->quoteRes('>>' . $anchor, '>>', $anchor); + $qres_id = ($this->_matome ? "t{$this->_matome}" : '') ."qr{$anchor}"; + $ret .= '
'; + $ret .= sprintf('
【参照レス:%s】
',$anchor_link); + $ret .= '
'; + $count++; + } + $ret .= '
'; + + return $ret; + } + + // }}} + // {{{ _quotebackResData() + + protected function _quotebackResData($anchors, $resnum) + { + $ret = array(); + foreach ($anchors as $idx => $anchor) { + if ($anchor == $resnum) { + continue; + } + $ret[] = ($this->_matome ? "t{$this->_matome}" : '') ."qr{$anchor}"; + } + + return join('/', $ret); + } + + // }}} + // {{{ getDatochiResiduums() + + /** + * DAT落ちの際に取得できた>>1と最後のレスをHTMLで返す. + * + * @return string|false + */ + public function getDatochiResiduums() + { + $ret = ''; + $elines = $this->thread->datochi_residuums; + if (!count($elines)) { + return $ret; + } + + $this->thread->onthefly = true; + $ret = "
on the fly
\n"; + $ret .= "
\n"; + + foreach($elines as $num => $line) { + $res = $this->transRes($line, $num); + if (is_array($res)) { + $ret .= $res['body'] . $res['q']; + } else { + $ret .= $res; + } + } + + $ret .= "
\n"; + + return $ret; + } + + // }}} + // {{{ getAutoFavRanks() + + /** + * 自動ランク設定を返す. + * + * @return array + */ + public function getAutoFavRank() + { + global $_conf; + + if ($this->_auto_fav_rank !== false) { + return $this->_auto_fav_rank; + } + + $ranks = explode(',', strtr($_conf['expack.ic2.fav_auto_rank_setting'], ' ', '')); + $ret = null; + if ($_conf['expack.misc.multi_favs']) { + $idx = 0; + if (!is_array($this->thread->favs)) { + return null; + } + foreach ($this->thread->favs as $fav) { + if ($fav) { + $rank = $ranks[$idx]; + if (is_numeric($rank)) { + $rank = intval($rank); + if ($ret === null) { + $ret = $rank; + } else { + $ret = max($ret, $rank); + } + } + } + $idx++; + } + } else { + if ($this->thread->fav && is_numeric($ranks[0])) { + $ret = intval($ranks[0]); + } + } + $this->_auto_fav_rank = $ret; + + return $ret; + } + + // }}} + // {{{ isAutoFavRankOverride() + + /** + * 自動ランク設定でランクを上書きすべきか返す. + * + * @param int $now 現在のランク + * @param int $new 自動ランク + * @return bool + */ + static public function isAutoFavRankOverride($now, $new) + { + global $_conf; + + switch ($_conf['expack.ic2.fav_auto_rank_override']) { + case 0: + return false; + break; + case 1: + return $now != $new; + break; + case 2: + return $now == 0 && $now != $new; + break; + case 3: + return $now < $new; + break; + default: + return false; + } + return false; + } + + // }}} + // {{{ getAnchorRegex() + + /** + * アンカーの正規表現を返す + * + * @param string $pattern ex)'/%full%/' + * @param boolean $unicode + * @return string + */ + static public function getAnchorRegex($pattern, $unicode = false) + { + if (!array_key_exists($pattern, self::$_anchorRegexes)) { + self::$_anchorRegexes[$pattern] = strtr($pattern, self::_getAnchorRegexParts()); + // 大差はないが compileMobile2chUriCallBack() のように preg_relplace_callback()してもいいかも。 + } + if ($unicode) { + return StrSjis::toUnicodePattern($_anchorRegexes[$pattern]); + } + return self::$_anchorRegexes[$pattern]; + } + + // }}} + // {{{ _getAnchorRegexParts() + + /** + * アンカーの構成要素(正規表現パーツの配列)を返す + * + * @param void + * @return string + */ + static private function _getAnchorRegexParts() + { + if (!is_null(self::$_anchorRegexParts)) { + return self::$_anchorRegexParts; + } + + $anchor = array(); + + // アンカーの構成要素(正規表現パーツの配列) + + // 空白文字 + $anchor_space = '(?:[ ]| )'; + //$anchor[' '] = ''; + + // アンカー引用子 >> + $anchor['prefix'] = "(?:(?:>|>|<|<|〉){1,2}|(?:\)){2}|》|≫){$anchor_space}*\.?"; + + // 数字 + $anchor['a_digit'] = '(?:\\d|0|1|2|3|4|5|6|7|8|9)'; + /* + $anchor[0] = '(?:0|0)'; + $anchor[1] = '(?:1|1)'; + $anchor[2] = '(?:2|2)'; + $anchor[3] = '(?:3|3)'; + $anchor[4] = '(?:4|4)'; + $anchor[5] = '(?:5|5)'; + $anchor[6] = '(?:6|6)'; + $anchor[7] = '(?:7|7)'; + $anchor[8] = '(?:8|8)'; + $anchor[9] = '(?:9|9)'; + */ + + // 範囲指定子 + // -|‐|ー = HYPHEN-MINUS | HYPHEN | KATAKANA-HIRAGANA PROLONGED SOUND MARK + $anchor['range_delimiter'] = '(?:-|\\x81\\x5d|\\x81\\x5b)'; // [\\-\\x{2010}\\x{30fc}] + + // 列挙指定子 + $anchor['delimiter'] = "{$anchor_space}?(?:[\.,=+]|、|・|=|,){$anchor_space}?"; + + // あぼーん用アンカー引用子 + //$anchor['prefix_abon'] = ">{1,2}{$anchor_space}?"; + + // レス番号 + $anchor['a_num'] = sprintf('%s{1,4}', $anchor['a_digit']); + + // レス範囲 + /* + $anchor['a_range'] = sprintf('%s(?:%s%s)?', + $anchor['a_num'], $anchor['range_delimiter'], $anchor['a_num'] + ); + */ + $anchor['a_range'] = sprintf('%s(?:%s(?:%s)?%s)?', + $anchor['a_num'], $anchor['range_delimiter'], $anchor['prefix'], $anchor['a_num'] + ); + + // レス範囲の列挙 + $anchor['ranges'] = sprintf('%s(?:%s%s)*(?!%s)', + $anchor['a_range'], $anchor['delimiter'], $anchor['a_range'], $anchor['a_digit'] + ); + + // レス番号の列挙 + $anchor['nums'] = sprintf('%s(?:%s%s)*(?!%s)', + $anchor['a_num'], $anchor['delimiter'], $anchor['a_num'], $anchor['a_digit'] + ); + + // アンカー全体 + $anchor['full'] = sprintf('(%s)(%s)', $anchor['prefix'], $anchor['ranges']); + + // getAnchorRegex() の strtr() 置換用にkeyを '%key%' に変換する + foreach ($anchor as $k => $v) { + $anchor['%' . $k . '%'] = $v; + unset($anchor[$k]); + } + + self::$_anchorRegexParts = $anchor; + + return self::$_anchorRegexParts; + } + + // }}} + // {{{_buildStrToLinkRegex() + + /** + * リンクとして扱うパターンを返す + * + * @param void + * @return string + */ + static protected function _buildStrToLinkRegex() + { + return '{' + . '(?P(<[Aa] .+?>)(.*?)())' // リンク(PCREの特性上、必ずこのパターンを最初に試行する) + . '|' + . '(?:' + . '(?P' // 引用 + . self::getAnchorRegex('%full%') + . ')' + . '|' + . '(?P' + . '(ftp|h?ttps?|tps?)://([0-9A-Za-z][\\w!#%&+*,\\-./:;=?@\\[\\]^~]+)' // URL + . ')' + . '|' + . '(?PID: ?([0-9A-Za-z/.+]{8,11})(?=[^0-9A-Za-z/.+]|$))' // ID(8,10桁 +PC/携帯識別フラグ) + . ')' + . '}'; + } + + // }}} +} + +// }}} + +/* + * Local Variables: + * mode: php + * coding: cp932 + * tab-width: 4 + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ +// vim: set syn=php fenc=cp932 ai et ts=4 sw=4 sts=4 fdm=marker: diff --git a/lib/live/live_ShowThreadPc.php b/lib/live/live_ShowThreadPc.php new file mode 100755 index 000000000..7c2f12102 --- /dev/null +++ b/lib/live/live_ShowThreadPc.php @@ -0,0 +1,1999 @@ +_url_handlers = array( + 'plugin_linkThread', + 'plugin_link2chSubject', + ); + // +Wiki + if (isset($GLOBALS['linkPluginCtl'])) { + $this->_url_handlers[] = 'plugin_linkPlugin'; + } + if (isset($GLOBALS['replaceImageUrlCtl'])) { + $this->_url_handlers[] = 'plugin_replaceImageUrl'; + } + if (P2_IMAGECACHE_AVAILABLE == 2) { + $this->_url_handlers[] = 'plugin_imageCache2'; + } elseif ($_conf['preview_thumbnail']) { + $this->_url_handlers[] = 'plugin_viewImage'; + } + if ($_conf['link_youtube']) { + $this->_url_handlers[] = 'plugin_linkYouTube'; + } + if ($_conf['link_niconico']) { + $this->_url_handlers[] = 'plugin_linkNicoNico'; + } + $this->_url_handlers[] = 'plugin_linkURL'; + + // imepitaのURLを加工してImageCache2させるプラグインを登録 + if (P2_IMAGECACHE_AVAILABLE == 2) { + $this->addURLHandler(array($this, 'plugin_imepitaToImageCache2')); + } + + // サムネイル表示制限数を設定 + if (!isset($GLOBALS['pre_thumb_unlimited']) || !isset($GLOBALS['pre_thumb_limit'])) { + if (isset($_conf['pre_thumb_limit']) && $_conf['pre_thumb_limit'] > 0) { + $GLOBALS['pre_thumb_limit'] = $_conf['pre_thumb_limit']; + $GLOBALS['pre_thumb_unlimited'] = false; + } else { + $GLOBALS['pre_thumb_limit'] = null; // ヌル値だとisset()はfalseを返す + $GLOBALS['pre_thumb_unlimited'] = true; + } + } + $GLOBALS['pre_thumb_ignore_limit'] = false; + + // アクティブモナー初期化 + if (P2_ACTIVEMONA_AVAILABLE) { + ExpackLoader::initActiveMona($this); + } + + // ImageCache2初期化 + if (P2_IMAGECACHE_AVAILABLE == 2) { + ExpackLoader::initImageCache($this); + } + + // 非同期レスポップアップ・SPM初期化 + $js_id = sprintf('%u', crc32($this->thread->keydat)); + if ($this->_matome) { + $this->asyncObjName = "t{$this->_matome}asp{$js_id}"; + $this->spmObjName = "t{$this->_matome}spm{$js_id}"; + } else { + $this->asyncObjName = "asp{$js_id}"; + $this->spmObjName = "spm{$js_id}"; + } + + // 名無し初期化 + $this->setBbsNonameName(); + } + + // }}} + // {{{ transRes() + + /** + * DatレスをHTMLレスに変換する + * + * @param string $ares datの1ライン + * @param int $i レス番号 + * @param string $pattern ハイライト用正規表現 + * @return string + */ + public function transRes($ares, $i, $pattern = null) + { + global $_conf, $STYLE, $mae_msg; + + list($name, $mail, $date_id, $msg) = $this->thread->explodeDatLine($ares); + if (($id = $this->thread->ids[$i]) !== null) { + $idstr = 'ID:' . $id; + $date_id = str_replace($this->thread->idp[$i] . $id, $idstr, $date_id); + } else { + $idstr = null; + } + + // +Wiki:置換ワード + if (isset($GLOBALS['replaceWordCtl'])) { + $replaceWordCtl = $GLOBALS['replaceWordCtl']; + $name = $replaceWordCtl->replace('name', $this->thread, $ares, $i); + $mail = $replaceWordCtl->replace('mail', $this->thread, $ares, $i); + $date_id = $replaceWordCtl->replace('date', $this->thread, $ares, $i); + $msg = $replaceWordCtl->replace('msg', $this->thread, $ares, $i); + } + + $tores = ''; + $rpop = ''; + if ($this->_matome) { + $res_id = "t{$this->_matome}r{$i}"; + $msg_id = "t{$this->_matome}m{$i}"; + } else { + $res_id = "r{$i}"; + $msg_id = "m{$i}"; + } + $msg_class = 'message'; + + // NGあぼーんチェック + $ng_type = $this->_ngAbornCheck($i, strip_tags($name), $mail, $date_id, $id, $msg, false, $ng_info); + if ($ng_type == self::ABORN) { + return $this->_abornedRes($res_id); + } + if ($ng_type != self::NG_NONE) { + $ngaborns_head_hits = self::$_ngaborns_head_hits; + $ngaborns_body_hits = self::$_ngaborns_body_hits; + } + + // AA判定 + if ($this->am_autodetect && $this->activeMona->detectAA($msg)) { + $msg_class .= ' ActiveMona'; + } + + //============================================================= + // レスをポップアップ表示 + //============================================================= + if ($_conf['quote_res_view']) { + $quote_res_nums = $this->checkQuoteResNums($i, $name, $msg); + + foreach ($quote_res_nums as $rnv) { + if (!isset($this->_quote_res_nums_done[$rnv])) { + $this->_quote_res_nums_done[$rnv] = true; + if (isset($this->thread->datlines[$rnv-1])) { + if ($this->_matome) { + $qres_id = "t{$this->_matome}qr{$rnv}"; + } else { + $qres_id = "qr{$rnv}"; + } + $ds = $this->qRes($this->thread->datlines[$rnv-1], $rnv); + $onPopUp_at = " onmouseover=\"showResPopUp('{$qres_id}',event)\" onmouseout=\"hideResPopUp('{$qres_id}')\""; + $rpop .= "
\n{$ds}
\n"; + } + } + } + } + + //============================================================= + // まとめて出力 + //============================================================= + + $name = $this->transName($name); // 名前HTML変換 + $msg = $this->transMsg($msg, $i); // メッセージHTML変換 + + + // BEプロファイルリンク変換 + $date_id = $this->replaceBeId($date_id, $i); + + // HTMLポップアップ + if ($_conf['iframe_popup']) { + $date_id = preg_replace_callback("{((\?#*)|(Lv\.\d+))}", array($this, 'iframePopupCallback'), $date_id); + } + + // NGメッセージ変換 + if ($ng_type != self::NG_NONE && count($ng_info)) { + $ng_info = implode(', ', $ng_info); + $msg = <<{$ng_info} +
{$msg}
+EOMSG; + } + + // NGネーム変換 + if ($ng_type & self::NG_NAME) { + $name = <<{$name} +EONAME; + $msg = <<{$msg} +EOMSG; + + // NGメール変換 + } elseif ($ng_type & self::NG_MAIL) { + $mail = <<{$mail} +EOMAIL; + $msg = <<{$msg} +EOMSG; + + // NGID変換 + } elseif ($ng_type & self::NG_ID) { + $date_id = <<{$date_id} +EOID; + $msg = <<{$msg} +EOMSG; + + } + + /* + //「ここから新着」画像を挿入 + if ($i == $this->thread->readnum +1) { + $tores .= <<新着レス +EOP; + } + */ + + // SPM + if ($_conf['expack.spm.enabled']) { + $spmeh = " onmouseover=\"{$this->spmObjName}.show({$i},'{$msg_id}',event)\""; + $spmeh .= " onmouseout=\"{$this->spmObjName}.hide(event)\""; + } else { + $spmeh = ''; + } + + // +live スレ内容表示部削除 + + /*if ($_conf['expack.am.enabled'] == 2) { + $tores .= << +// +\n +EOJS; + }*/ + + // まとめてフィルタ色分け + if ($pattern) { + $tores = StrCtl::filterMarking($pattern, $tores); + } + + return array('body' => $tores, 'q' => $rpop); + } + + // }}} + // {{{ quoteOne() + + /** + * >>1 を表示する (引用ポップアップ用) + */ + public function quoteOne() + { + global $_conf; + + if (!$_conf['quote_res_view']) { + return false; + } + + $rpop = ''; + $quote_res_nums = $this->checkQuoteResNums(0, '1', ''); + if (array_search(1, $quote_res_nums) === false) { + $quote_res_nums[] = 1; + } + + foreach ($quote_res_nums as $rnv) { + if (!isset($this->_quote_res_nums_done[$rnv])) { + $this->_quote_res_nums_done[$rnv] = true; + if (isset($this->thread->datlines[$rnv-1])) { + if ($this->_matome) { + $qres_id = "t{$this->_matome}qr{$rnv}"; + } else { + $qres_id = "qr{$rnv}"; + } + $ds = $this->qRes($this->thread->datlines[$rnv-1], $rnv); + $onPopUp_at = " onmouseover=\"showResPopUp('{$qres_id}',event)\" onmouseout=\"hideResPopUp('{$qres_id}')\""; + $rpop .= "
\n{$ds}
\n"; + } + } + } + + $res1['q'] = $rpop; + $res1['body'] = $this->transMsg('>>1', 1); + + return $res1; + } + + // }}} + // {{{ qRes() + + /** + * レス引用HTML + */ + public function qRes($ares, $i) + { + global $_conf; + + $resar = $this->thread->explodeDatLine($ares); + $name = $this->transName($resar[0]); + $mail = $resar[1]; + if (($id = $this->thread->ids[$i]) !== null) { + $idstr = 'ID:' . $id; + $date_id = str_replace($this->thread->idp[$i] . $id, $idstr, $resar[2]); + } else { + $idstr = null; + $date_id = $resar[2]; + } + $msg = $this->transMsg($resar[3], $i); + + $tores = ''; + + if ($this->_matome) { + $qmsg_id = "t{$this->_matome}qm{$i}"; + } else { + $qmsg_id = "qm{$i}"; + } + + // >>1 + if ($i == 1) { + $tores = "

{$this->thread->ttitle_hd}

"; + } + + // BEプロファイルリンク変換 + $date_id = $this->replaceBeId($date_id, $i); + + // HTMLポップアップ + if ($_conf['iframe_popup']) { + $date_id = preg_replace_callback("{((\?#*)|(Lv\.\d+))}", array($this, 'iframePopupCallback'), $date_id); + } + // + + // IDフィルタ + if ($_conf['flex_idpopup'] == 1 && $id && $this->thread->idcount[$id] > 1) { + $date_id = str_replace($idstr, $this->idFilter($idstr, $id), $date_id); + } + + $msg_class = 'message'; + + // AA 判定 + if ($this->am_autodetect && $this->activeMona->detectAA($msg)) { + $msg_class .= ' ActiveMona'; + } + + // SPM + if ($_conf['expack.spm.enabled']) { + $spmeh = " onmouseover=\"{$this->spmObjName}.show({$i},'{$qmsg_id}',event)\""; + $spmeh .= " onmouseout=\"{$this->spmObjName}.hide(event)\""; + } else { + $spmeh = ''; + } + + // $toresにまとめて出力 + $tores .= '
'; + $tores .= "{$i} : "; // 番号 + $tores .= preg_replace('{[ ]*}i', '', "{$name} : "); + if ($mail) { + $tores .= $mail . ' : '; // メール + } + $tores .= $date_id; // 日付とID + if ($this->am_side_of_id) { + $tores .= ' ' . $this->activeMona->getMona($qmsg_id); + } + $tores .= "
\n"; + + // 被レスリスト(縦形式) + if ($_conf['backlink_list'] == 1 || $_conf['backlink_list'] > 2) { + $tores .= $this->_quotebackListHtml($i, 1); + } + + $tores .= "
{$msg}
\n"; // 内容 + // 被レスリスト(横形式) + if ($_conf['backlink_list'] == 2 || $_conf['backlink_list'] > 2) { + $tores .= $this->_quotebackListHtml($i, 2); + } + + // 被参照ブロック用データ + if ($_conf['backlink_block'] > 0) { + $tores .= $this->_getBacklinkComment($i); + } + + return $tores; + } + + // }}} + // {{{ _getBacklinkComment() + + protected function _getBacklinkComment($i) + { + $backlinks = $this->_quotebackListHtml($i, 3); + if (strlen($backlinks)) { + return ''; + } + return ''; + } + + // }}} + // {{{ transName() + + /** + * 名前をHTML用に変換する + * + * @param string $name 名前 + * @return string + */ + public function transName($name) + { + global $_conf; + + // トリップやホスト付きなら分解する + if (($pos = strpos($name, '◆')) !== false) { + $trip = substr($name, $pos); + $name = substr($name, 0, $pos); + } else { + $trip = null; + } + + // 数字を引用レスポップアップリンク化 + if ($_conf['quote_res_view']) { + if (strlen($name) && $name != $this->_nanashiName) { + $name = preg_replace_callback( + self::getAnchorRegex('/(?:^|%prefix%)(%nums%)/'), + array($this, '_quoteNameCallback'), $name + ); + } + } + + if ($trip) { + $name .= $trip; + } elseif ($name) { + // 文字化け回避 + $name = $name . ' '; + //if (in_array(0xF0 & ord(substr($name, -1)), array(0x80, 0x90, 0xE0))) { + // $name .= ' '; + //} + } + + return $name; + } + + // }}} + // {{{ transMsg() + + /** + * datのレスメッセージをHTML表示用メッセージに変換する + * + * @param string $msg メッセージ + * @param int $mynum レス番号 + * @return string + */ + public function transMsg($msg, $mynum) + { + global $_conf; + global $pre_thumb_ignore_limit; + + // 2ch旧形式のdat + if ($this->thread->dat_type == '2ch_old') { + $msg = str_replace('@`', ',', $msg); + $msg = preg_replace('/&(?=[^;])/', '&', $msg); + } + + // &補正 + $msg = preg_replace('/&(?!#?\\w+;)/', '&', $msg); + + // Safariから投稿されたリンク中チルダの文字化け補正 + //$msg = preg_replace('{(h?t?tp://[\w\.\-]+/)〜([\w\.\-%]+/?)}', '$1~$2', $msg); + + // >>1のリンクをいったん外す + // >>1 + $msg = preg_replace('{<[Aa] .+?>(>>\\d[\\d\\-]*)}', '$1', $msg); + + // 本来は2chのDAT時点でなされていないとエスケープの整合性が取れない気がする。(URLリンクのマッチで副作用が出てしまう) + //$msg = str_replace(array('"', "'"), array('"', '''), $msg); + + // 2006/05/06 ノートンの誤反応対策 body onload=window() + $msg = str_replace('onload=window()', 'onload=window()', $msg); + + // 新着レスの画像は表示制限を無視する設定なら + if ($mynum > $this->thread->readnum && $_conf['expack.ic2.newres_ignore_limit']) { + $pre_thumb_ignore_limit = true; + } + + // 文末の改行と連続する改行を除去 + if ($_conf['strip_linebreaks']) { + $msg = $this->stripLineBreaks($msg /*, '
***
'*/); + } + + // 引用やURLなどをリンク + $msg = $this->transLink($msg); + + // Wikipedia記法への自動リンク + if ($_conf['_linkToWikipeida']) { + $msg = $this->_wikipediaFilter($msg); + } + + return $msg; + } + + // }}} + // {{{ _abornedRes() + + /** + * あぼーんレスのHTMLを取得する + * + * @param string $res_id + * @return string + */ + protected function _abornedRes($res_id) + { + global $_conf; + + if ($_conf['ngaborn_purge_aborn']) { + return ''; + } + + return << +
 
+
 
+\n +EOP; + } + + // }}} + // {{{ idFilter() + + /** + * IDフィルタリングポップアップ変換 + * + * @param string $idstr ID:xxxxxxxxxx + * @param string $id xxxxxxxxxx + * @return string + */ + public function idFilter($idstr, $id) + { + global $_conf; + + // IDは8桁または10桁(+携帯/PC識別子)と仮定して + /* + if (strlen($id) % 2 == 1) { + $id = substr($id, 0, -1); + } + */ + $num_ht = ''; + if (isset($this->thread->idcount[$id]) && $this->thread->idcount[$id] > 0) { + $num = (string) $this->thread->idcount[$id]; + if ($_conf['iframe_popup'] == 3) { + $num_ht = ' '; + $num_ht .= preg_replace('/\\d/', '', $num); + $num_ht .= ' '; + } else { + $num_ht = '('.$num.')'; + } + } else { + return $idstr; + } + + if ($_conf['coloredid.enable'] > 0 && preg_match("|^ID:[ ]?[0-9A-Za-z/.+]{8,11}|",$idstr)) { + if ($this->_ids_for_render === null) { + $this->_ids_for_render = array(); + } + $this->_ids_for_render[substr($id, 0, 8)] = $this->thread->idcount[$id]; + if ($_conf['coloredid.click'] > 0) { + $num_ht = '' . $num_ht . ''; + } + $idstr = $this->_coloredIdStr( + $idstr, $id, $_conf['coloredid.click'] > 0 ? true : false); + } + + $filter_url = $_conf['read_php'] . '?' . http_build_query(array( + 'host' => $this->thread->host, + 'bbs' => $this->thread->bbs, + 'key' => $this->thread->key, + 'ls' => 'all', + 'offline' => '1', + 'idpopup' => '1', + 'rf' => array( + 'field' => ResFilter::FIELD_ID, + 'method' => ResFilter::METHOD_JUST, + 'match' => ResFilter::MATCH_ON, + 'include' => ResFilter::INCLUDE_NONE, + 'word' => $id, + ), + ), '', '&') . $_conf['k_at_a']; + + if ($_conf['iframe_popup']) { + return $this->iframePopup($filter_url, $idstr, $_conf['bbs_win_target_at']) . $num_ht; + } + return "{$idstr}{$num_ht}"; + } + + // }}} + // {{{ _linkToWikipeida() + + /** + * @see ShowThread + */ + protected function _linkToWikipeida($word) + { + global $_conf; + + $link = 'http://ja.wikipedia.org/wiki/' . rawurlencode($word); + if ($_conf['through_ime']) { + $link = P2Util::throughIme($link); + } + + return "{$word}"; + } + + // }}} + // {{{ quoteRes() + + /** + * 引用変換(単独) + * + * @param string $full >>1-100 + * @param string $qsign >> + * @param string $appointed_num 1-100 + * @param bool $anchor_jump + * @return string + */ + public function quoteRes($full, $qsign, $appointed_num, $anchor_jump = false) + { + global $_conf; + + $appointed_num = mb_convert_kana($appointed_num, 'n'); // 全角数字を半角数字に変換 + if (preg_match('/\\D/', $appointed_num)) { + $appointed_num = preg_replace('/\\D+/', '-', $appointed_num); + return $this->quoteResRange($full, $qsign, $appointed_num); + } + if (preg_match('/^0/', $appointed_num)) { + return $full; + } + + $qnum = intval($appointed_num); + if ($qnum < 1 || $qnum > sizeof($this->thread->datlines)) { + return $full; + } + + // あぼーんレスへのアンカー + if ($_conf['quote_res_view_aborn'] == 0 && + in_array($qnum, $this->_aborn_nums)) { + return '' . "{$full}"; + } + + if ($anchor_jump && $qnum >= $this->thread->resrange['start'] && $qnum <= $this->thread->resrange['to']) { + $read_url = '#' . ($this->_matome ? "t{$this->_matome}" : '') . "r{$qnum}"; + } else { + $read_url = "{$_conf['read_php']}?host={$this->thread->host}&bbs={$this->thread->bbs}&key={$this->thread->key}&offline=1&ls={$appointed_num}"; + } + $attributes = $_conf['bbs_win_target_at']; + if ($_conf['quote_res_view'] && ($_conf['quote_res_view_ng'] != 0 || + !in_array($qnum, $this->_ng_nums))) { + if ($this->_matome) { + $qres_id = "t{$this->_matome}qr{$qnum}"; + } else { + $qres_id = "qr{$qnum}"; + } + $attributes .= " onmouseover=\"showResPopUp('{$qres_id}',event)\""; + $attributes .= " onmouseout=\"hideResPopUp('{$qres_id}')\""; + } + return "_aborn_nums) ? ' class="abornanchor"' : + (in_array($qnum, $this->_ng_nums) ? ' class="nganchor"' : '')) + . ">{$full}"; + } + + // }}} + // {{{ quoteResRange() + + /** + * 引用変換(範囲) + * + * @param string $full >>1-100 + * @param string $qsign >> + * @param string $appointed_num 1-100 + * @return string + */ + public function quoteResRange($full, $qsign, $appointed_num) + { + global $_conf; + + if ($appointed_num == '-') { + return $full; + } + + $read_url = "{$_conf['read_php']}?host={$this->thread->host}&bbs={$this->thread->bbs}&key={$this->thread->key}&offline=1&ls={$appointed_num}n"; + + if ($_conf['iframe_popup']) { + $pop_url = $read_url . "&renzokupop=true"; + return $this->iframePopup(array($read_url, $pop_url), $full, $_conf['bbs_win_target_at'], 1); + } + + // 普通にリンク + return "{$full}"; + + // 1つ目を引用レスポップアップ + /* + $qnums = explode('-', $appointed_num); + $qlink = $this->quoteRes($qsign . $qnum[0], $qsign, $qnum[0]) . '-'; + if (isset($qnums[1])) { + $qlink .= $qnums[1]; + } + return $qlink; + */ + } + + // }}} + // {{{ iframePopup() + + /** + * HTMLポップアップ変換 + * + * @param string|array $url + * @param string|array $str + * @param string $attr + * @param int|null $mode + * @param bool $marker + * @return string + */ + public function iframePopup($url, $str, $attr = '', $mode = null, $marker = false) + { + global $_conf; + + // リンク用URLとポップアップ用URL + if (is_array($url)) { + $link_url = $url[0]; + $pop_url = $url[1]; + } else { + $link_url = $url; + $pop_url = $url; + } + + // リンク文字列とポップアップの印 + if (is_array($str)) { + $link_str = $str[0]; + $pop_str = $str[1]; + } else { + $link_str = $str; + $pop_str = null; + } + + // リンクの属性 + if (is_array($attr)) { + $_attr = $attr; + $attr = ''; + foreach ($_attr as $key => $value) { + $attr .= ' ' . $key . '="' . p2h($value) . '"'; + } + } elseif ($attr !== '' && substr($attr, 0, 1) != ' ') { + $attr = ' ' . $attr; + } + + // リンクの属性にHTMLポップアップ用のイベントハンドラを加える + $pop_attr = $attr; + if ($_conf['iframe_popup_event'] == 1) { + $pop_attr .= " onclick=\"stophide=true; showHtmlPopUp('{$pop_url}',event,0" . ($marker ? ' ,this' : '') . "); return false;\""; + } else { + $pop_attr .= " onmouseover=\"showHtmlPopUp('{$pop_url}',event,{$_conf['iframe_popup_delay']}" . ($marker ? ' ,this' : '') . ")\""; + } + $pop_attr .= " onmouseout=\"offHtmlPopUp()\""; + + // 最終調整 + if (is_null($mode)) { + $mode = $_conf['iframe_popup']; + } + if ($mode == 2 && !is_null($pop_str)) { + $mode = 3; + } elseif ($mode == 3 && is_null($pop_str)) { + global $skin, $STYLE; + + $custom_pop_img = "skin/{$skin}/pop.png"; + if (file_exists($custom_pop_img)) { + $pop_img = p2h($custom_pop_img); + $x = $STYLE['iframe_popup_mark_width']; + $y = $STYLE['iframe_popup_mark_height']; + } else { + $pop_img = 'img/pop.png'; + $y = $x = 12; + } + $pop_str = "\"\""; + } + + // リンク作成 + switch ($mode) { + // マーク無し + case 1: + return "{$link_str}"; + // (p)マーク + case 2: + return "(p){$link_str}"; + // [p]画像、サムネイルなど + case 3: + return "{$pop_str}{$link_str}"; + // ポップアップしない + default: + return "{$link_str}"; + } + } + + // }}} + // {{{ iframePopupCallback() + + /** + * HTMLポップアップ変換(コールバック用インターフェース) + * + * @param array $s 正規表現にマッチした要素の配列 + * @return string + */ + public function iframePopupCallback($s) + { + return $this->iframePopup(p2h($s[1], false), p2h($s[3], false), $s[2]); + } + + // }}} + // {{{ _coloredIdStr() + + /** + * Merged from http://jiyuwiki.com/index.php?cmd=read&page=rep2%A4%C7%A3%C9%A3%C4%A4%CE%C7%D8%B7%CA%BF%A7%CA%D1%B9%B9&alias%5B%5D=pukiwiki%B4%D8%CF%A2 + * + * @return string + */ + protected function _coloredIdStr($idstr, $id, $classed = false) + { + global $_conf; + + if (!(isset($this->thread->idcount[$id]) + && $this->thread->idcount[$id] > 1)) { + return $idstr; + } + if ($classed) { + return $this->_coloredIdStrClassed($idstr, $id); + } + + switch ($_conf['coloredid.rate.type']) { + case 1: + $rate = $_conf['coloredid.rate.times']; + break; + case 2: + $rate = $this->getIdCountRank(10); + break; + case 3: + $rate = $this->getIdCountAverage(); + break; + default: + return $idstr; + } + + if ($rate > 1 && $this->thread->idcount[$id] >= $rate) { + switch ($_conf['coloredid.coloring.type']) { + case 0: + return $this->_coloredIdStr0($idstr, $id); + break; + case 1: + return $this->_coloredIdStr1($idstr, $id); + break; + default: + return $idstr; + } + } + + return $idstr; + } + + // }}} + // {{{ _coloredIdStrClassed() + + private function _coloredIdStrClassed($idstr, $id) + { + $ret = array(); + $arr = explode(':', $idstr); + foreach ($arr as $i => $str) { + if ($i == 0 || $i == 1) { + $ret[] = '' . $str . ''; + } else { + $ret[] = $str; + } + } + return implode(':', $ret); + } + + // }}} + // {{{ _coloredIdStr0() + + /** + * IDカラー オリジナル着色用 + */ + private function _coloredIdStr0($idstr, $id) + { + if (!function_exists('coloredIdStyle0')) { + require P2_LIB_DIR . '/color/coloredIdStyle0.inc.php'; + } + + if (isset($this->idstyles[$id])) { + $colored = $this->idstyles[$id]; + } else { + $colored = coloredIdStyle0($id, $this->thread->idcount[$id]); + $this->idstyles[$id] = $colored; + } + $ret = array(); + foreach ($arr = explode(':', $idstr) as $i => $str) { + if ($colored[$i]) { + $ret[] = "{$str}"; + } else { + $ret[] = $str; + } + } + return implode(':', $ret); + } + + // }}} + // {{{ _coloredIdStr1() + + /** + * IDカラー thermon版用 + */ + private function _coloredIdStr1($idstr, $id) + { + if (!function_exists('coloredIdStyle')) { + require P2_LIB_DIR . '/color/coloredIdStyle.inc.php'; + } + + $colored = coloredIdStyle($idstr, $id, $this->thread->idcount[$id]); + $idstr2 = preg_split('/:/',$idstr,2); // コロンでID文字列を分割 + $ret = array_shift($idstr2).':'; + if ($colored[1]) { + $idstr2[1] = substr($idstr2[0], 4); + $idstr2[0] = substr($idstr2[0], 0, 4); + } + foreach ($idstr2 as $i => $str) { + if ($colored[$i]) { + $ret .= "{$str}"; + } else { + $ret .= $str; + } + } + return $ret; + } + + // }}} + // {{{ cssClassedId() + + /** + * IDカラーに使用するCSSクラス名をID文字列から算出して返す. + */ + static public function cssClassedId($id) + { + return 'idcss-' . bin2hex( + base64_decode(str_replace('.', '+', substr($id, 0, 8)))); + } + + // }}} + // {{{ ユーティリティメソッド + // {{{ imageHtmlPopup() + + /** + * 画像をHTMLポップアップ&ポップアップウインドウサイズに合わせる + */ + public function imageHtmlPopup($img_url, $img_tag, $link_str) + { + global $_conf; + + if ($_conf['expack.ic2.enabled'] && $_conf['expack.ic2.fitimage']) { + $popup_url = 'ic2_fitimage.php?url=' . rawurlencode(str_replace('&', '&', $img_url)); + } else { + $popup_url = $img_url; + } + + $pops = ($_conf['iframe_popup'] == 1) ? $img_tag . $link_str : array($link_str, $img_tag); + return $this->iframePopup(array($img_url, $popup_url), $pops, $_conf['ext_win_target_at'], null, true); + } + + // }}} + // {{{ respopToAsync() + + /** + * レスポップアップを非同期モードに加工する + */ + public function respopToAsync($str) + { + $respop_regex = '/(onmouseover)=\"(showResPopUp\(\'(q(\d+)of\d+)\',event\).*?)\"/'; + $respop_replace = '$1="loadResPopUp(' . $this->asyncObjName . ', $4);$2"'; + return preg_replace($respop_regex, $respop_replace, $str); + } + + // }}} + // {{{ getASyncObjJs() + + /** + * 非同期読み込みで利用するJavaScriptオブジェクトを生成する + */ + public function getASyncObjJs() + { + global $_conf; + static $done = array(); + + if (isset($done[$this->asyncObjName])) { + return; + } + $done[$this->asyncObjName] = true; + + $code = << +//asyncObjName} = { + host:"{$this->thread->host}", bbs:"{$this->thread->bbs}", key:"{$this->thread->key}", + readPhp:"{$_conf['read_php']}", readTarget:"{$_conf['bbs_win_target']}" +}; +//]]> +\n +EOJS; + return $code; + } + + // }}} + // {{{ getSpmObjJs() + + /** + * スマートポップアップメニューを生成するJavaScriptコードを生成する + */ + public function getSpmObjJs($retry = false) + { + global $_conf, $STYLE; + + if (isset(self::$_spm_objects[$this->spmObjName])) { + return $retry ? self::$_spm_objects[$this->spmObjName] : ''; + } + + $ttitle_en = UrlSafeBase64::encode($this->thread->ttitle); + + if ($_conf['expack.spm.filter_target'] == '' || $_conf['expack.spm.filter_target'] == 'read') { + $_conf['expack.spm.filter_target'] = '_self'; + } + + $motothre_url = $this->thread->getMotoThread(); + $motothre_url = substr($motothre_url, 0, strlen($this->thread->ls) * -1); + + $_spmOptions = array( + 'null', + ((!$_conf['disable_res'] && $_conf['expack.spm.kokores']) ? (($_conf['expack.spm.kokores_orig']) ? '2' : '1') : '0'), + (($_conf['expack.spm.ngaborn']) ? (($_conf['expack.spm.ngaborn_confirm']) ? '2' : '1') : '0'), + (($_conf['expack.spm.filter']) ? '1' : '0'), + (($this->am_on_spm) ? '1' : '0'), + (($_conf['expack.aas.enabled']) ? '1' : '0'), + ); + $spmOptions = implode(',', $_spmOptions); + + // エスケープ + $_spm_title = StrCtl::toJavaScript($this->thread->ttitle_hc); + $_spm_url = addslashes($motothre_url); + $_spm_host = addslashes($this->thread->host); + $_spm_bbs = addslashes($this->thread->bbs); + $_spm_key = addslashes($this->thread->key); + $_spm_ls = addslashes($this->thread->ls); + + $code = << +//spmObjName} = { + 'objName':'{$this->spmObjName}', + 'rc':'{$this->thread->rescount}', + 'title':'{$_spm_title}', + 'ttitle_en':'{$ttitle_en}', + 'url':'{$_spm_url}', + 'host':'{$_spm_host}', + 'bbs':'{$_spm_bbs}', + 'key':'{$_spm_key}', + 'ls':'{$_spm_ls}', + 'spmOption':[{$spmOptions}] +}; +SPM.init({$this->spmObjName}); +//]]> +\n +EOJS; + + self::$_spm_objects[$this->spmObjName] = $code; + + return $code; + } + + // }}} + // }}} + // {{{ transLinkDo()から呼び出されるURL書き換えメソッド + /** + * これらのメソッドは引数が処理対象パターンに合致しないとfalseを返し、 + * transLinkDo()はfalseが返ってくると$_url_handlersに登録されている次の関数/メソッドに処理させようとする。 + */ + // {{{ plugin_linkURL() + + /** + * URLリンク + * + * @param string $url + * @param array $purl + * @param string $str + * @return string|false + */ + public function plugin_linkURL($url, $purl, $str) + { + global $_conf; + + if (isset($purl['scheme'])) { + // ime + if ($_conf['through_ime']) { + $link_url = P2Util::throughIme($purl[0]); + } else { + $link_url = $url; + } + + $is_http = ($purl['scheme'] == 'http' || $purl['scheme'] == 'https'); + + // HTMLポップアップ + if ($_conf['iframe_popup'] && $is_http) { + // *pm 指定の場合のみ、特別に手動転送指定を追加する + if (substr($_conf['through_ime'], -2) == 'pm') { + $pop_url = P2Util::throughIme($purl[0], -1); + } else { + $pop_url = $link_url; + } + $link = $this->iframePopup(array($link_url, $pop_url), $str, $_conf['ext_win_target_at']); + } else { + $link = "{$str}"; + } + + // ブラクラチェッカ + if ($_conf['brocra_checker_use'] && $_conf['brocra_checker_url'] && $is_http) { + if (strlen($_conf['brocra_checker_query'])) { + $brocra_checker_url = $_conf['brocra_checker_url'] . '?' . $_conf['brocra_checker_query'] . '=' . rawurlencode($purl[0]); + } else { + $brocra_checker_url = rtrim($_conf['brocra_checker_url'], '/') . '/' . $url; + } + $brocra_checker_url_orig = $brocra_checker_url; + // ブラクラチェッカ・ime + if ($_conf['through_ime']) { + $brocra_checker_url = P2Util::throughIme($brocra_checker_url); + } + $check_mark = 'チェック'; + $check_mark_prefix = '['; + $check_mark_suffix = ']'; + // ブラクラチェッカ・HTMLポップアップ + if ($_conf['iframe_popup']) { + // *pm 指定の場合のみ、特別に手動転送指定を追加する + if (substr($_conf['through_ime'], -2) == 'pm') { + $brocra_checker_url = P2Util::throughIme($brocra_checker_url_orig, -1); + } else { + $brocra_pop_url = $brocra_checker_url; + } + if ($_conf['iframe_popup'] == 3) { + $check_mark = ''; + $check_mark_prefix = ''; + $check_mark_suffix = ''; + } + $brocra_checker_link = $this->iframePopup(array($brocra_checker_url, $brocra_pop_url), $check_mark, $_conf['ext_win_target_at']); + } else { + $brocra_checker_link = "{$check_mark}"; + } + $link .= $check_mark_prefix . $brocra_checker_link . $check_mark_suffix; + } + + return $link; + } + return false; + } + + // }}} + // {{{ plugin_link2chSubject() + + /** + * 2ch bbspink 板リンク + * + * @param string $url + * @param array $purl + * @param string $str + * @return string|false + */ + public function plugin_link2chSubject($url, $purl, $str) + { + global $_conf; + + if (preg_match('{^http://(\\w+\\.(?:2ch\\.net|bbspink\\.com))/(\\w+)/$}', $purl[0], $m)) { + $subject_url = "{$_conf['subject_php']}?host={$m[1]}&bbs={$m[2]}"; + return "{$str} [板をp2で開く]"; + } + return false; + } + + // }}} + // {{{ plugin_linkThread() + + /** + * スレッドリンク + * + * @param string $url + * @param array $purl + * @param string $str + * @return string|false + */ + public function plugin_linkThread($url, $purl, $str) + { + global $_conf; + + list($nama_url, $host, $bbs, $key, $ls) = P2Util::detectThread($purl[0]); + if ($host && $bbs && $key) { + $read_url = "{$_conf['read_php']}?host={$host}&bbs={$bbs}&key={$key}&ls={$ls}"; + if ($_conf['iframe_popup']) { + if ($ls && preg_match('/^[0-9\\-n]+$/', $ls)) { + $pop_url = $read_url; + } else { + $pop_url = $read_url . '&one=true'; + } + return $this->iframePopup(array($read_url, $pop_url), $str, $_conf['bbs_win_target_at']); + } + return "{$str}"; + } + + return false; + } + + // }}} + // {{{ plugin_linkYouTube() + + /** + * YouTubeリンク変換プラグイン + * + * Zend_Gdata_Youtubeを使えばサムネイルその他の情報を簡単に取得できるが... + * + * @param string $url + * @param array $purl + * @param string $str + * @return string|false + */ + public function plugin_linkYouTube($url, $purl, $str) + { + global $_conf; + + // http://www.youtube.com/watch?v=Mn8tiFnAUAI + // http://m.youtube.com/watch?v=OhcX0xJsDK8&client=mv-google&gl=JP&hl=ja&guid=ON&warned=True + if (preg_match('{^http://(www|jp|m)\\.youtube\\.com/watch\\?(?:.+&)?v=([0-9a-zA-Z_\\-]+)}', $url, $m)) { + // ime + if ($_conf['through_ime']) { + $link_url = P2Util::throughIme($url); + } else { + $link_url = $url; + } + + // HTMLポップアップ + if ($_conf['iframe_popup']) { + $link = $this->iframePopup($link_url, $str, $_conf['ext_win_target_at']); + } else { + $link = "{$str}"; + } + + $subd = $m[1]; + $id = $m[2]; + + if ($_conf['link_youtube'] == 2) { + return << +EOP; + } else { + return << +EOP; + } + } + return false; + } + + // }}} + // {{{ plugin_linkNicoNico() + + /** + * ニコニコ動画変換プラグイン + * + * @param string $url + * @param array $purl + * @param string $str + * @return string|false + */ + public function plugin_linkNicoNico($url, $purl, $str) + { + global $_conf; + + // http://www.nicovideo.jp/watch?v=utbrYUJt9CSl0 + // http://www.nicovideo.jp/watch/utvWwAM30N0No + // http://m.nicovideo.jp/watch/sm7044684 + if (preg_match('{^http://(?:www|m)\\.nicovideo\\.jp/watch(?:/|(?:\\?v=))([0-9a-zA-Z_-]+)}', $url, $m)) { + // ime + if ($_conf['through_ime']) { + $link_url = P2Util::throughIme($purl[0]); + } else { + $link_url = $url; + } + + // HTMLポップアップ + if ($_conf['iframe_popup']) { + $link = $this->iframePopup($link_url, $str, $_conf['ext_win_target_at']); + } else { + $link = "{$str}"; + } + + $id = $m[1]; + + if ($_conf['link_niconico'] == 2) { + return << +EOP; + } else { + return << +EOP; + } + } + return false; + } + + // }}} + // {{{ plugin_viewImage() + + /** + * 画像ポップアップ変換 + * + * @param string $url + * @param array $purl + * @param string $str + * @return string|false + */ + public function plugin_viewImage($url, $purl, $str) + { + global $_conf; + global $pre_thumb_unlimited, $pre_thumb_limit; + + if (P2Util::isUrlWikipediaJa($url)) { + return false; + } + + // 表示制限 + if (!$pre_thumb_unlimited && empty($pre_thumb_limit)) { + return false; + } + + if (preg_match('{^https?://.+?\\.(jpe?g|gif|png)$}i', $purl[0]) && empty($purl['query'])) { + $pre_thumb_limit--; // 表示制限カウンタを下げる + $img_tag = ""; + + if ($_conf['iframe_popup']) { + $view_img = $this->imageHtmlPopup($url, $img_tag, $str); + } else { + $view_img = "{$img_tag}{$str}"; + } + + // ブラクラチェッカ (プレビューとは相容れないのでコメントアウト) + /*if ($_conf['brocra_checker_use']) { + $link_url_en = rawurlencode($url); + if ($_conf['iframe_popup'] == 3) { + $check_mark = ''; + $check_mark_prefix = ''; + $check_mark_suffix = ''; + } else { + $check_mark = 'チェック'; + $check_mark_prefix = '['; + $check_mark_suffix = ']'; + } + $view_img .= $check_mark_prefix . "{$check_mark}" . $check_mark_suffix; + }*/ + + return $view_img; + } + + return false; + } + + // }}} + // {{{ plugin_imageCache2() + + /** + * ImageCache2サムネイル変換 + * + * @param string $url + * @param array $purl + * @param string $str + * @return string|false + */ + public function plugin_imageCache2($url, $purl, $str, + $force = false, + $referer = null) + { + static $serial = 0; + + global $_conf; + global $pre_thumb_unlimited, $pre_thumb_ignore_limit, $pre_thumb_limit; + + if (P2Util::isUrlWikipediaJa($url)) { + return false; + } + + if ((preg_match('{^https?://.+?\\.(jpe?g|gif|png)$}i', $purl[0]) && empty($purl['query'])) || $force) { + // 準備 + $serial++; + $thumb_id = 'thumbs' . $serial . $this->thumb_id_suffix; + $tmp_thumb = './img/ic_load.png'; + $url_ht = $url; + $url = $purl[0]; + $url_en = rawurlencode($url) . + ($referer ? '&ref=' . rawurlencode($referer) : ''); + $img_id = null; + + $icdb = new ImageCache2_DataObject_Images(); + + // r=0:リンク;r=1:リダイレクト;r=2:PHPで表示 + // t=0:オリジナル;t=1:PC用サムネイル;t=2:携帯用サムネイル;t=3:中間イメージ + $img_url = 'ic2.php?r=1&uri=' . $url_en; + $thumb_url = 'ic2.php?r=1&t=1&uri=' . $url_en; + // お気にスレ自動画像ランク + $rank = null; + if ($_conf['expack.ic2.fav_auto_rank']) { + $rank = $this->getAutoFavRank(); + if ($rank !== null) { + $thumb_url .= '&rank=' . $rank; + } + } + + // DBに画像情報が登録されていたとき + if ($icdb->get($url)) { + $img_id = $icdb->id; + + // ウィルスに感染していたファイルのとき + if ($icdb->mime == 'clamscan/infected') { + return " {$str}"; + } + // あぼーん画像のとき + if ($icdb->rank < 0) { + return " {$str}"; + } + + // オリジナルがキャッシュされているときは画像を直接読み込む + $_img_url = $this->thumbnailer->srcUrl($icdb->size, $icdb->md5, $icdb->mime); + if (file_exists($_img_url)) { + $img_url = $_img_url; + $cached = true; + } else { + $cached = false; + } + + // サムネイルが作成されていているときは画像を直接読み込む + $_thumb_url = $this->thumbnailer->thumbUrl($icdb->size, $icdb->md5, $icdb->mime); + if (file_exists($_thumb_url)) { + $thumb_url = $_thumb_url; + // 自動スレタイメモ機能がONでスレタイが記録されていないときはDBを更新 + if (!is_null($this->img_memo) && strpos($icdb->memo, $this->img_memo) === false){ + $update = new ImageCache2_DataObject_Images(); + if (!is_null($icdb->memo) && strlen($icdb->memo) > 0) { + $update->memo = $this->img_memo . ' ' . $icdb->memo; + } else { + $update->memo = $this->img_memo; + } + $update->whereAddQuoted('uri', '=', $url); + } + + // expack.ic2.fav_auto_rank_override の設定とランク条件がOKなら + // お気にスレ自動画像ランクを上書き更新 + if ($rank !== null && + self::isAutoFavRankOverride($icdb->rank, $rank)) { + if ($update === null) { + $update = new ImageCache2_DataObject_Images(); + $update->whereAddQuoted('uri', '=', $url); + } + $update->rank = $rank; + } + + if ($update !== null) { + $update->update(); + } + } + + // サムネイルの画像サイズ + $thumb_size = $this->thumbnailer->calc($icdb->width, $icdb->height); + $thumb_size = preg_replace('/(\d+)x(\d+)/', 'width="$1" height="$2"', $thumb_size); + $tmp_thumb = './img/ic_load1.png'; + + $orig_img_url = $img_url; + $orig_thumb_url = $thumb_url; + + // 画像がキャッシュされていないとき + // 自動スレタイメモ機能がONならクエリにUTF-8エンコードしたスレタイを含める + } else { + // 画像がブラックリストorエラーログにあるか確認 + if (false !== ($errcode = $icdb->ic2_isError($url))) { + return " {$str}"; + } + + $cached = false; + + $orig_img_url = $img_url; + $orig_thumb_url = $thumb_url; + $img_url .= $this->img_memo_query; + $thumb_url .= $this->img_memo_query; + $thumb_size = ''; + $tmp_thumb = './img/ic_load2.png'; + } + + // キャッシュされておらず、表示数制限が有効のとき + if (!$cached && !$pre_thumb_unlimited && !$pre_thumb_ignore_limit) { + // 表示制限を超えていたら、表示しない + // 表示制限を超えていなければ、表示制限カウンタを下げる + if ($pre_thumb_limit <= 0) { + $show_thumb = false; + } else { + $show_thumb = true; + $pre_thumb_limit--; + } + } else { + $show_thumb = true; + } + + // 表示モード + if ($show_thumb) { + $img_tag = ""; + if ($_conf['iframe_popup']) { + $view_img = $this->imageHtmlPopup($img_url, $img_tag, $str); + } else { + $view_img = "{$img_tag}{$str}"; + } + } else { + $img_tag = ""; + $view_img = "{$img_tag}{$str}"; + } + + // ソースへのリンクをime付きで表示 + if ($_conf['expack.ic2.enabled'] && $_conf['expack.ic2.through_ime']) { + $ime_url = P2Util::throughIme($url); + if ($_conf['iframe_popup'] == 3) { + $ime_mark = ''; + } else { + $ime_mark = '[ime]'; + } + $view_img .= " {$ime_mark}"; + } + + $view_img .= ''; + + return $view_img; + } + + return false; + } + + // }}} + // {{{ plugin_replaceImageUrl() + + /** + * 置換画像URL+ImageCache2 + */ + public function plugin_replaceImageUrl($url, $purl, $str) + { + static $serial = 0; + + global $_conf; + global $pre_thumb_unlimited, $pre_thumb_ignore_limit, $pre_thumb_limit; + + // +Wiki + global $replaceImageUrlCtl; + + $url = $purl[0]; + $replaced = $replaceImageUrlCtl->replaceImageUrl($url); + if (!$replaced[0]) { + return false; + } + + foreach ($replaced as $v) { + $url_en = rawurlencode($v['url']); + $url_ht = p2h($v['url']); + $ref_en = $v['referer'] ? '&ref=' . rawurlencode($v['referer']) : ''; + + // 準備 + $serial++; + $thumb_id = 'thumbs' . $serial . $this->thumb_id_suffix; + $tmp_thumb = './img/ic_load.png'; + + $icdb = new ImageCache2_DataObject_Images(); + + // r=0:リンク;r=1:リダイレクト;r=2:PHPで表示 + // t=0:オリジナル;t=1:PC用サムネイル;t=2:携帯用サムネイル;t=3:中間イメージ + // +Wiki + $img_url = 'ic2.php?r=1&uri=' . $url_en . $ref_en; + $thumb_url = 'ic2.php?r=1&t=1&uri=' . $url_en . $ref_en; + // お気にスレ自動画像ランク + $rank = null; + if ($_conf['expack.ic2.fav_auto_rank']) { + $rank = $this->getAutoFavRank(); + if ($rank !== null) $thumb_url .= '&rank=' . $rank; + } + + // DBに画像情報が登録されていたとき + if ($icdb->get($v['url'])) { + + // ウィルスに感染していたファイルのとき + if ($icdb->mime == 'clamscan/infected') { + $result .= ""; + continue; + } + // あぼーん画像のとき + if ($icdb->rank < 0) { + $result .= ""; + continue; + } + + // オリジナルがキャッシュされているときは画像を直接読み込む + $_img_url = $this->thumbnailer->srcUrl($icdb->size, $icdb->md5, $icdb->mime); + if (file_exists($_img_url)) { + $img_url = $_img_url; + $cached = true; + } else { + $cached = false; + } + + // サムネイルが作成されていているときは画像を直接読み込む + $_thumb_url = $this->thumbnailer->thumbUrl($icdb->size, $icdb->md5, $icdb->mime); + if (file_exists($_thumb_url)) { + $thumb_url = $_thumb_url; + // 自動スレタイメモ機能がONでスレタイが記録されていないときはDBを更新 + if (!is_null($this->img_memo) && strpos($icdb->memo, $this->img_memo) === false){ + $update = new ImageCache2_DataObject_Images(); + if (!is_null($icdb->memo) && strlen($icdb->memo) > 0) { + $update->memo = $this->img_memo . ' ' . $icdb->memo; + } else { + $update->memo = $this->img_memo; + } + $update->whereAddQuoted('uri', '=', $v['url']); + } + + // expack.ic2.fav_auto_rank_override の設定とランク条件がOKなら + // お気にスレ自動画像ランクを上書き更新 + if ($rank !== null && + self::isAutoFavRankOverride($icdb->rank, $rank)) { + if ($update === null) { + $update = new ImageCache2_DataObject_Images(); + $update->whereAddQuoted('uri', '=', $v['url']); + } + $update->rank = $rank; + } + + if ($update !== null) { + $update->update(); + } + } + + // サムネイルの画像サイズ + $thumb_size = $this->thumbnailer->calc($icdb->width, $icdb->height); + $thumb_size = preg_replace('/(\d+)x(\d+)/', 'width="$1" height="$2"', $thumb_size); + $tmp_thumb = './img/ic_load1.png'; + + $orig_img_url = $img_url; + $orig_thumb_url = $thumb_url; + + // 画像がキャッシュされていないとき + // 自動スレタイメモ機能がONならクエリにUTF-8エンコードしたスレタイを含める + } else { + // 画像がブラックリストorエラーログにあるか確認 + if (false !== ($errcode = $icdb->ic2_isError($v['url']))) { + $result .= ""; + continue; + } + + $cached = false; + + $orig_img_url = $img_url; + $orig_thumb_url = $thumb_url; + $img_url .= $this->img_memo_query; + $thumb_url .= $this->img_memo_query; + $thumb_size = ''; + $tmp_thumb = './img/ic_load2.png'; + } + + // キャッシュされておらず、表示数制限が有効のとき + if (!$cached && !$pre_thumb_unlimited && !$pre_thumb_ignore_limit) { + // 表示制限を超えていたら、表示しない + // 表示制限を超えていなければ、表示制限カウンタを下げる + if ($pre_thumb_limit <= 0) { + $show_thumb = false; + } else { + $show_thumb = true; + $pre_thumb_limit--; + } + } else { + $show_thumb = true; + } + + // 表示モード + if ($show_thumb) { + $img_tag = ""; + if ($_conf['iframe_popup']) { + $view_img = $this->imageHtmlPopup($img_url, $img_tag, ''); + } else { + $view_img = "{$img_tag}"; + } + } else { + $img_tag = ""; + $view_img = "{$img_tag}"; + } + + $view_img .= '"; + . "'{$url_ht}', event)\">"; + + $result .= $view_img; + } + // ソースへのリンクをime付きで表示 + $ime_url = P2Util::throughIme($url); + $result .= "{$str}"; + return $result; + } + + /** + * +Wiki:リンクプラグイン + */ + public function plugin_linkPlugin($url, $purl, $str) + { + return $GLOBALS['linkPluginCtl']->replaceLinkToHTML($url, $str); + } + + // }}} + // {{{ plugin_imepitaToImageCache2() + + /** + * imepitaのURLを加工してImageCache2させるプラグイン + * + * @param string $url + * @param array $purl + * @param string $str + * @return string|false + */ + public function plugin_imepitaToImageCache2($url, $purl, $str) + { + if (preg_match('{^https?://imepita\.jp/(?:image/)?(\d{8}/\d{6})}i', + $purl[0], $m) && empty($purl['query'])) { + $_url = 'http://imepita.jp/image/' . $m[1]; + $_purl = @parse_url($_url); + $_purl[0] = $_url; + return $this->plugin_imageCache2($_url, $_purl, $str, true, $url); + } + return false; + } + + // }}} + // }}} + // {{{ getQuotebacksJson() + + public function getQuotebacksJson() + { + $ret = array(); + foreach ($this->getQuoteFrom() as $resnum => $quote_from) { + if (!$quote_from) { + continue; + } + if ($resnum != 1 && ($resnum < $this->thread->resrange['start'] || $resnum > $this->thread->resrange['to'])) { + continue; + } + $tmp = array(); + foreach ($quote_from as $quote) { + if ($quote != 1 && ($quote < $this->thread->resrange['start'] || $quote > $this->thread->resrange['to'])) { + continue; + } + $tmp[] = $quote; + } + if ($tmp) $ret[] = "{$resnum}:[" . join(',', $tmp) . "]"; + } + return '{' . join(',', $ret) . '}'; + } + + // }}} + // {{{ getResColorJs() + + public function getResColorJs() + { + global $_conf, $STYLE; + + $fontstyle_bold = empty($STYLE['fontstyle_bold']) ? 'normal' : $STYLE['fontstyle_bold']; + $fontweight_bold = empty($STYLE['fontweight_bold']) ? 'normal' : $STYLE['fontweight_bold']; + $fontfamily_bold = $STYLE['fontfamily_bold']; + $backlinks = $this->getQuotebacksJson(); + $colors = array(); + $backlink_colors = join(',', + array_map(create_function('$x', 'return "\'{$x}\'";'), + explode(',', $_conf['backlink_coloring_track_colors'])) + ); + $prefix = $this->_matome ? "t{$this->_matome}" : ''; + return << +if (typeof rescolObjs == 'undefined') rescolObjs = []; +rescolObjs.push((function() { + var obj = new BacklinkColor('{$prefix}'); + obj.colors = [{$backlink_colors}]; + obj.highlightStyle = {fontStyle :'{$fontstyle_bold}', fontWeight : '{$fontweight_bold}', fontFamily : '{$fontfamily_bold}'}; + obj.backlinks = {$backlinks}; + return obj; +})()); + +EOJS; + } + + // }}} + // {{{ getIdsForRenderJson() + + public function getIdsForRenderJson() + { + $ret = array(); + if ($this->_ids_for_render) { + foreach ($this->_ids_for_render as $id => $count) { + $ret[] = "'{$id}':{$count}"; + } + } + return '{' . join(',', $ret) . '}'; + } + + // }}} + // {{{ getIdColorJs() + + public function getIdColorJs() + { + global $_conf, $STYLE; + + if ($_conf['coloredid.enable'] < 1 || $_conf['coloredid.click'] < 1) { + return ''; + } + if (count($this->thread->idcount) < 1) { + return ''; + } + + $idslist = $this->getIdsForRenderJson(); + + $rate = $_conf['coloredid.rate.times']; + $tops = $this->getIdCountRank(10); + $average = $this->getIdCountAverage(); + $color_init = ''; + if ($_conf['coloredid.rate.type'] > 0) { + switch($_conf['coloredid.rate.type']) { + case 2: + $init_rate = $tops; + break; + case 3: + $init_rate = $average; + break; + case 1: + $init_rate = $rate; + default: + } + if ($init_rate > 1) + $color_init .= 'idCol.initColor(' . $init_rate . ', idslist);'; + } + $color_init .= "idCol.rate = {$rate};"; + if (!$this->_matome) { + $color_init .= "idCol.tops = {$tops};"; + $color_init .= "idCol.average = {$average};"; + } + $hissiCount = $_conf['coloredid.rate.hissi.times']; + $mark_colors = join(',', + array_map(create_function('$x', 'return "\'{$x}\'";'), + explode(',', $_conf['coloredid.marking.colors'])) + ); + $fontstyle_bold = empty($STYLE['fontstyle_bold']) ? 'normal' : $STYLE['fontstyle_bold']; + $fontweight_bold = empty($STYLE['fontweight_bold']) ? 'normal' : $STYLE['fontweight_bold']; + $fontfamily_bold = $STYLE['fontfamily_bold']; + $uline = $STYLE['a_underline_none'] != 1 + ? 'idCol.colorStyle["textDecoration"] = "underline"' : ''; + return << +(function() { +var idslist = {$idslist}; +if (typeof idCol == 'undefined') { + idCol = new IDColorChanger(idslist, {$hissiCount}); + idCol.colors = [{$mark_colors}]; +{$uline}; + idCol.highlightStyle = {fontStyle :'{$fontstyle_bold}', fontWeight : '{$fontweight_bold}', fontFamily : '{$fontfamily_bold}', fontSize : '104%'}; +} else idCol.addIdlist(idslist); +{$color_init} +idCol.setupSPM('{$this->spmObjName}'); +})(); + +EOJS; + } + + // }}} + // {{{ getIdCountAverage() + + public function getIdCountAverage() + { + if ($this->_idcount_average !== null) { + return $this->_idcount_average; + } + + $sum = 0; + $param = 0; + + foreach ($this->thread->idcount as $count) { + if ($count > 1) { + $sum += $count; + $param++; + } + } + + $result = ($param < 1) ? 0 : intval(ceil($sum / $param)); + $this->_idcount_average = $result; + + return $result; + } + + // }}} + // {{{ getIdCountRank() + + public function getIdCountRank($rank) + { + if ($this->_idcount_tops !== null) { + return $this->_idcount_tops; + } + + $ranking = array(); + + foreach ($this->thread->idcount as $count) { + if ($count > 1) { + $ranking[] = $count; + } + } + + if (count($ranking) == 0) { + return 0; + } + + rsort($ranking); + $rcount = count($ranking); + + $result = ($rcount >= $rank) ? $ranking[$rank - 1] : $ranking[$rcount - 1]; + $this->_idcount_tops = $result; + + return $result; + } + + // }}} +} + +// }}} + +/* + * Local Variables: + * mode: php + * coding: cp932 + * tab-width: 4 + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ +// vim: set syn=php fenc=cp932 ai et ts=4 sw=4 sts=4 fdm=marker: diff --git a/rep2/live_read.php b/rep2/live_read.php index e3d9bb49a..e9f9b1650 100755 --- a/rep2/live_read.php +++ b/rep2/live_read.php @@ -1,6 +1,6 @@ getThreadInfoFromIdx(); +//========================================================== +// preview >>1 +//========================================================== + +//if (!empty($_GET['onlyone'])) { +if (!empty($_GET['one'])) { + $aThread->ls = '1'; + $aThread->resrange = array('start' => 1, 'to' => 1, 'nofirst' => false); + + // 必ずしも正確ではないが便宜的に + //if (!isset($aThread->rescount) && !empty($_GET['rc'])) { + if (!isset($aThread->rescount) && !empty($_GET['rescount'])) { + //$aThread->rescount = $_GET['rc']; + $aThread->rescount = (int)$_GET['rescount']; + } + + $preview = $aThread->previewOne(); + $ptitle_ht = p2h($aThread->itaj) . ' / ' . $aThread->ttitle_hd; + + include READ_HEADER_INC_PHP; + echo $preview; + include READ_FOOTER_INC_PHP; + + return; +} + //=========================================================== // DATのダウンロード //=========================================================== @@ -258,10 +284,99 @@ //$GLOBALS['debug'] && $GLOBALS['profiler']->enterSection("datToHtml"); if ($aThread->rescount) { - echo '
'; + $mainhtml = ''; + require_once P2_LIB_DIR . '/live/live_ShowThreadPc.php'; + $aShowThread = new ShowThreadPc($aThread); + + if ($_conf['expack.spm.enabled']) { + echo $aShowThread->getSpmObjJs(); + } + + $res1 = $aShowThread->quoteOne(); // >>1ポップアップ用 + if ($_conf['coloredid.enable'] > 0 && $_conf['coloredid.click'] > 0 && + $_conf['coloredid.rate.type'] > 0) { + if ($_GET['showbl']) { + $mainhtml = $aShowThread->datToHtml_resFrom(true); + } else { + $mainhtml .= $aShowThread->datToHtml(true); + } + $mainhtml .= $res1['q']; + } else { + if ($_GET['showbl']) { + $aShowThread->datToHtml_resFrom(); + } else { + $aShowThread->datToHtml(); + } + echo $res1['q']; + } + + + // レス追跡カラー + if ($_conf['backlink_coloring_track']) { + echo $aShowThread->getResColorJs(); + } + + // IDカラーリング + if ($_conf['coloredid.enable'] > 0 && $_conf['coloredid.click'] > 0) { + echo $aShowThread->getIdColorJs(); + // ブラウザ負荷軽減のため、CSS書き換えスクリプトの後でコンテンツを + // レンダリングさせる + echo $mainhtml; + } + + // 外部ツール + $pluswiki_js = ''; + + if ($_conf['wiki.idsearch.spm.mimizun.enabled']) { + if (!class_exists('Mimizun', false)) { + require P2_PLUGIN_DIR . '/mimizun/Mimizun.php'; + } + $mimizun = new Mimizun(); + $mimizun->host = $aThread->host; + $mimizun->bbs = $aThread->bbs; + if ($mimizun->isEnabled()) { + $pluswiki_js .= "WikiTools.addMimizun({$aShowThread->spmObjName});"; + } + } + + if ($_conf['wiki.idsearch.spm.hissi.enabled']) { + if (!class_exists('Hissi', false)) { + require P2_PLUGIN_DIR . '/hissi/Hissi.php'; + } + $hissi = new Hissi(); + $hissi->host = $aThread->host; + $hissi->bbs = $aThread->bbs; + if ($hissi->isEnabled()) { + $pluswiki_js .= "WikiTools.addHissi({$aShowThread->spmObjName});"; + } + } + + if ($_conf['wiki.idsearch.spm.stalker.enabled']) { + if (!class_exists('Stalker', false)) { + require P2_PLUGIN_DIR . '/stalker/Stalker.php'; + } + $stalker = new Stalker(); + $stalker->host = $aThread->host; + $stalker->bbs = $aThread->bbs; + if ($stalker->isEnabled()) { + $pluswiki_js .= "WikiTools.addStalker({$aShowThread->spmObjName});"; + } + } + + if ($pluswiki_js !== '') { + echo << +// + +EOP; + } } elseif ($aThread->diedat && count($aThread->datochi_residuums) > 0) { - echo '過去ログ又はDATを取得出来ないスレッドは実況できません'; + require_once P2_LIB_DIR . '/ShowThreadPc.php'; + $aShowThread = new ShowThreadPc($aThread); + echo $aShowThread->getDatochiResiduums(); } //$GLOBALS['debug'] && $GLOBALS['profiler']->leaveSection("datToHtml"); @@ -289,6 +404,37 @@ } flush(); +//=========================================================== +// idxの値を設定、記録 +//=========================================================== +if ($aThread->rescount) { + + // 検索の時は、既読数を更新しない + if ((isset($GLOBALS['word']) && strlen($GLOBALS['word']) > 0) || $is_ajax) { + $aThread->readnum = $idx_data[5]; + } else { + $aThread->readnum = min($aThread->rescount, max(0, $idx_data[5], $aThread->resrange['to'])); + } + $newline = $aThread->readnum + 1; // $newlineは廃止予定だが、旧互換用に念のため + + $sar = array($aThread->ttitle, $aThread->key, $idx_data[2], $aThread->rescount, '', + $aThread->readnum, $idx_data[6], $idx_data[7], $idx_data[8], $newline, + $idx_data[10], $idx_data[11], $aThread->datochiok); + P2Util::recKeyIdx($aThread->keyidx, $sar); // key.idxに記録 +} + +//=========================================================== +// 履歴を記録 +//=========================================================== +if ($aThread->rescount && !$is_ajax) { + recRecent(implode('<>', array($aThread->ttitle, $aThread->key, $idx_data[2], '', '', + $aThread->readnum, $idx_data[6], $idx_data[7], $idx_data[8], $newline, + $aThread->host, $aThread->bbs))); +} + +// NGあぼーんを記録 +NgAbornCtl::saveNgAborns(); + // 以上 --------------------------------------------------------------- exit; @@ -316,6 +462,63 @@ function detectThread() } } +// }}} +// {{{ recRecent() + +/** + * 履歴を記録する + */ +function recRecent($data) +{ + global $_conf; + + $lock = new P2Lock($_conf['recent_idx'], false); + + // $_conf['recent_idx'] ファイルがなければ生成 + FileCtl::make_datafile($_conf['recent_idx']); + + $lines = FileCtl::file_read_lines($_conf['recent_idx'], FILE_IGNORE_NEW_LINES); + $neolines = array(); + + // {{{ 最初に重複要素を削除しておく + + if (is_array($lines)) { + foreach ($lines as $l) { + $lar = explode('<>', $l); + $data_ar = explode('<>', $data); + if ($lar[1] == $data_ar[1]) { continue; } // keyで重複回避 + if (!$lar[1]) { continue; } // keyのないものは不正データ + $neolines[] = $l; + } + } + + // }}} + + // 新規データ追加 + array_unshift($neolines, $data); + + while (sizeof($neolines) > $_conf['rct_rec_num']) { + array_pop($neolines); + } + + // {{{ 書き込む + + if ($neolines) { + $cont = ''; + foreach ($neolines as $l) { + $cont .= $l . "\n"; + } + + if (FileCtl::file_write_contents($_conf['recent_idx'], $cont) === false) { + p2die('cannot write file.'); + } + } + + // }}} + + return true; +} + // }}} /* diff --git a/rep2/post.php b/rep2/post.php index 0902dde02..d07f7a907 100644 --- a/rep2/post.php +++ b/rep2/post.php @@ -858,14 +858,6 @@ function showCookieConfirmation($host, $response) $form->appendChild($elem); } - // 実況モード - if (!empty($_POST['live'])) { - $elem = $hidden->cloneNode(); - $elem->setAttribute('name', 'live'); - $elem->setAttribute('value', '1'); - $form->appendChild($elem); - } - // 強制ビュー指定 if ($_conf['b'] != $_conf['client_type']) { $elem = $hidden->cloneNode(); diff --git a/rep2/read.php b/rep2/read.php index 1e497f2ba..2d9b277fb 100644 --- a/rep2/read.php +++ b/rep2/read.php @@ -314,9 +314,9 @@ $res1 = $aShowThread->quoteOne(); // >>1ポップアップ用 if ($_GET['showbl']) { - $mainhtml = $aShowThread->datToHtml_resFrom(true); + $mainhtml = $aShowThread->getDatToHtml_resFrom(); } else { - $mainhtml .= $aShowThread->datToHtml(true); + $mainhtml .= $aShowThread->getDatToHtml(); } $mainhtml .= $res1['q']; diff --git a/rep2/read_async.php b/rep2/read_async.php index 5c8c43ab3..7f633a2a2 100644 --- a/rep2/read_async.php +++ b/rep2/read_async.php @@ -145,14 +145,6 @@ exit; } - -// テレビ番組欄@2chなどはログ・idx・履歴を保存しない -if (P2Util::isHostNoCacheData($aThread->host)) { - //@unlink($aThread->keydat); // ThreadRead::readDat()で削除する - exit; -} - - //=========================================================== // idxの値を設定、記録 //===========================================================