<?php
/* 
 * $Id: backlink.inc.php,v 1.4 2004/10/23 15:31:55 youka Exp $
 */



/**
 * 逆リンクを管理するクラス。
 * 
 * WikiIDごとにシングルトンのようにふるまう。
 */
class BackLink /* implements PageObserver */
{
	protected $wikiid;	//WikiFarmのID
	
	
	/**
	 * インスタンスを取得する。
	 * 
	 * @param	$id	WikiFarmのID。指定しない場合は現在のWikiFarmのIDが使われる。
	 */
	static function getinstance($id = WIKIID)
	{
		return new BackLink($id);
	}
	
	
	/**
	 * コンストラクタ。
	 */
	protected function __construct($id)
	{
		$this->wikiid = $id;
	}
	
	
	/**
	 * 逆リンクのリストを取得する。
	 * 
	 * @param	string	$pagename	リンクされている側のページ名。
	 * @return	array('pagename' => string, 'times' => int)	リンクしている側のページ名のリスト（intはリンクの重複数）。
	 */
	function getlist($pagename)
	{
		$db = DataBase::getinstance($this->wikiid);
		
		$_pagename = $db->escape($pagename);
		$query  = "SELECT linker,times FROM linklist";
		$query .= " WHERE linked = '$_pagename'";
		$query .= " ORDER BY times DESC, linker ASC";
		
		$result = $db->query($query);
		$ret = array();
		while($row = $db->fetch($result)){
			$ret[] = array('pagename' => $row['linker'], 'times' => $row['times']);
		}
		return $ret;
	}
	
	
	/**
	 * ページ更新と同時に逆リンクを更新する。
	 */
	static function update($page)
	{
		$backlink = BackLink::getinstance($page->getwikiid());
		$backlink->refreshlinker($page->getpagename());
		if($page->getsource(1) == '' || $page->getsource() == ''){
			$backlink->refreshlinked($page->getpagename());
		}
	}
	
	
	/**
	 * （ページ編集に伴い）逆リンクを更新する。
	 * 
	 * @param	string	$linker	リンクする側のページ名。
	 */
	function refreshlinker($linker)
	{
		$source = Page::getinstance($linker, $this->wikiid)->getsource();
		$body = parse_block($source, $linker);
		$seeker = new LinkSeeker($linker);
		$body->accept($seeker);
		$list = $seeker->getlist();
		
		$db = DataBase::getinstance($this->wikiid);
		$db->begin();
		
		$_linker = $db->escape($linker);
		$db->query("DELETE FROM linklist WHERE linker = '$_linker'");
		
		foreach($list as $linked => $times){
			if($linker != $linked){
				$_linked = $db->escape($linked);
				$query  = "INSERT INTO linklist (linker, linked, times)";
				$query .= " VALUES('$_linker', '$_linked', $times)";
				$db->query($query);
			}
		}
		
		$db->commit();
	}
	
	
	/**
	 * （ページ新規作成・削除に伴い）逆リンクを更新する。
	 * 
	 * これと同時にrefreshlinker()も呼ぶ必要がある。
	 * 
	 * @param	string	$linked	リンクされる側のページ名。
	 */
	function refreshlinked($linked)
	{
		$db = DataBase::getinstance($this->wikiid);
		$db->begin();
		
		$_linked = $db->escape($linked);
		$db->query("DELETE FROM linklist WHERE linked  = '$_linked'");
		
		$query  = "SELECT pagename,source FROM page";
		$query .= " WHERE (source like '%${_linked}%')";
		$result = $db->query($query);
		while($row = $db->fetch($result)){
			$this->refreshlinker($row['pagename']);
		}
		
		$db->commit();
	}
	
	
	/**
	 * 逆リンクを全て更新する。
	 */
	function refreshall()
	{
		$db = DataBase::getinstance($this->wikiid);
		$db->begin();
		
		$db->query("DELETE FROM linklist");
		
		$result = $db->query("SELECT pagename FROM page");
		while($row = $db->fetch($result)){
			$this->refreshlinker($row['pagename']);
		}
		
		$db->commit();
	}
}



/**
 * サイト内リンクを探すVisitor。
 */
class LinkSeeker
{
	protected $linklist = array();
	protected $currentpage;
	
	
	function __construct($pagename)
	{
		$this->currentpage = $pagename;
	}
	
	
	/**
	 * サイト内リンクのリストを返す。
	 * 
	 * @return	array(string $linked => int $times)	$linkedはリンク先ページ名、$timesは重複数。
	 */
	function getlist()
	{
		return $this->linklist;
	}
	
	
	/**
	 * visit系関数のデフォルトは内包する要素を呼び出す。
	 */
	function __call($funcname, $params)
	{
		$elements = $params[0]->getelements();
		foreach($elements as $elem){
			$elem->accept($this);
		}
	}
	
	
	/**
	 * リストに加算する。
	 */
	protected function add($linked)
	{
		if(isset($this->linklist[$linked])){
			$this->linklist[$linked]++;
		}
		else{
			$this->linklist[$linked] = 1;
		}
	}
	
	
	function visitT_AutoLink($e)
	{
		$this->add($e->getpagename());
	}
	
	
	function visitT_BlacketName($e)
	{
		$pagename = $e->getpagename();
		if(mb_ereg('^' . EXP_URL . '$', $pagename)){
			//URLにヒット。何もしない。
		}
		else if(mb_ereg('^' . EXP_MAIL . '$', $pagename)){
			//Mailにヒット。何もしない。
		}
		else if(mb_ereg('^(.+?):(.+)$', $pagename)){
			//InterWikiNameにヒット。何もしない。
		}
		else{
			//のこるはサイト内リンク。
			$fullname = resolvepath($pagename, $this->currentpage);
			$this->add($fullname);
		}
	}
}


?>