4 NP_Related developed by Sir_Psycho (http://www.fuckhedz.com/)
5 Ver0.25.1 by radek (http://hulan.info/)
6 Ver0.4 by yu (http://nucleus.datoka.jp/)
11 <%Related(mode,max,snippet,searchcond)%>
13 <%Related(mode,max,snippet,,searchcond)%> ... in item/search page
14 <%Related(mode,max,snippet,query,searchcond)%>
16 mode ... local / google
17 max[option] ... max amount. default:5
18 snippet[option] ... true / false. default:false
19 query[option] ... search keyword. (if in item/search page, it's filled automatically)
20 searchcond[option] ... and / or. default:or
24 In item template or item skintype:
25 (search skintype is also available)
27 <%Related(google,5,true)%>
30 <%Related(local,5,true,queryword,and)%>
36 [Chg] remove Amazon mode
37 [Chg] change to use Ajax Search API instead of Soap Search API (google)
40 [Fix] remove tags from url, title, snippet (google)
41 [Chg] change name of function soapclient() to soaplient_old() in nusoap.php (for PHP5 reason).
44 [Add] option "erase cache data now", "show snippet", "no header", "search range".
45 [Chg] delete inline style "font-size:smaller" for snippets.
46 [Chg] cancel keyword manupilation for google search.
47 [Chg] include multiple keywords on Amazon search.
48 [Fix] in google search, invalid max results is set when max results is larger than 5.
49 [Fix] version expression in delete style.
50 [Fix] 'DONOTSEARCH' output.
51 [Fix] encoding keyword for google search - by nakahara21 (http://nakahara21.com/)
52 [Fix] set input encoding to google "more link" - by mao (http://kirsche.mods.jp/catid/6)
53 [Fix] convert encoding for Amazon search - by sakuracandle (http://juntwo.s57.xrea.com/)
54 [Fix] remove tags from snippet in title attribute - by pushman (http://blog.heartfield-web.com/)
57 [Add] Amazon search in books-jp mode.
58 [Add] support FancyURLs - by mao (http://kirsche.mods.jp/catid/6)
59 [Fix] loop bug when "()" is used in keyword.
60 [Fix] debug code remained.
62 Main updates before Ver0.3jp ...
63 - SkinVar. This plugin can be used in both skin and template.
64 - Multi-keyword search.
65 - Pharase search (phrase = quoted words).
66 - Snippet for local search.
71 // plugin needs to work on Nucleus versions <=2.0 as well
72 if (!function_exists('sql_table')){
73 function sql_table($name) {
74 return 'nucleus_' . $name;
78 class NP_Related extends NucleusPlugin {
82 return 'Related items/sites';
86 function getAuthor() {
87 return 'Tim Broddin + radek + yu';
90 // version of the plugin
91 function getVersion() { return '0.4'; }
92 function getMinNucleusVersion() { return '250'; }
94 // an URL to the plugin website
95 // can also be of the form mailto:foo@bar.com
97 return 'http://japan.nucleuscms.org/wiki/plugins:related';
100 function supportsFeature($what) {
102 case 'SqlTablePrefix':
109 function getTableList() {
110 return array( sql_table('plug_related'), sql_table('plug_related_cache') );
113 function getEventList() {
114 return array('PostAddItem','PreUpdateItem','AddItemFormExtras','EditItemFormExtras','PostPluginOptionsUpdate');
119 global $manager, $blog, $CONF;
121 // include language file for this plugin
122 $language = ereg_replace( '[\\|/]', '', getLanguageName());
123 if (file_exists($this->getDirectory().$language.'.php'))
124 include_once($this->getDirectory().$language.'.php');
126 include_once($this->getDirectory().'english.php');
128 if ($blog) $b =& $blog;
129 else $b =& $manager->getBlog($CONF['DefaultBlog']);
132 if ($this->getBlogOption($bid, "googlekey") != '') $this->google_key = $this->getBlogOption($bid, "googlekey");
133 else $this->google_key = $this->getOption("googlekey");
134 $this->toexclude = $this->getOption("toexclude");
135 // $this->amazontoken = $this->getOption("amazontoken");
136 // $this->aso_id = $this->getOption("aso_id");
138 $this->header_lc = $this->getOption("header_lc");
139 $this->header_go = $this->getOption("header_go");
140 // $this->header_am = $this->getOption("header_am");
141 $this->header_end = $this->getOption("header_end");
143 $this->list_header = $this->getOption("listheading");
144 $this->list_footer = $this->getOption("listfooter");
145 $this->item_header = $this->getOption("itemheading");
146 $this->item_footer = $this->getOption("itemfooter");
148 $this->notitle = $this->getOption("notitle");
149 $this->noresults = $this->getOption("noresults");
150 $this->flg_noheader = $this->getOption("flg_noheader");
151 $this->morelink = $this->getOption("morelink");
152 $this->maxlength = $this->getOption("maxlength");
153 $this->maxlength2 = $this->getOption("maxlength2");
154 $this->flg_snippet = $this->getOption('flg_snippet');
155 $this->flg_timelocal = $this->getOption('flg_timelocal');
156 $this->currentblog = $this->getOption("currentblog");
157 $this->searchrange = $this->getOption("searchrange");
158 $this->flg_srchcond_and = $this->getOption("flg_srchcond_and");
160 // $this->interval = $this->getOption("interval");
161 $this->_check_cache_size();
164 // a description to be shown on the installed plugins listing
165 function getDescription() {
166 return _RELATED_MESSAGE_DESC;
171 $this->createOption("googlekey", _RELATED_OPTION_GOOGLEKEY, "text", "");
172 $this->createBlogOption("googlekey", _RELATED_OPTION_GOOGLEKEY, "text", "");
173 $this->createOption("toexclude", _RELATED_OPTION_TOEXCLUDE, "text", "yourdomain.com");
174 // $this->createOption("amazontoken", _RELATED_OPTION_AMAZONTOKEN, "text", "");
175 // $this->createOption("aso_id", _RELATED_OPTION_ASO_ID, "text", "");
177 $this->createOption("header_lc", _RELATED_OPTION_HEADER_LC, "text", "<h3>Local search for: <em>");
178 $this->createOption("header_go", _RELATED_OPTION_HEADER_GO, "text", "<h3>Google search for: <em>");
179 // $this->createOption("header_am", _RELATED_OPTION_HEADER_AM, "text", "<h3>Amazon search for: <em>");
180 $this->createOption("header_end", _RELATED_OPTION_HEADER_END, "text", "</em></h3>");
181 $this->createOption("listheading", _RELATED_OPTION_LISTHEADING, "text", "<ul class='related'>\n");
182 $this->createOption("listfooter", _RELATED_OPTION_LISTFOOTER, "text", "</ul>\n");
183 $this->createOption("itemheading", _RELATED_OPTION_ITEMHEADING, "text", "<li>\n");
184 $this->createOption("itemfooter", _RELATED_OPTION_ITEMFOOTER, "text", "</li>\n");
186 $this->createOption("notitle", _RELATED_OPTION_NOTITLE, "text", "(no title)");
187 $this->createOption("noresults", _RELATED_OPTION_NORESULTS, "text", "<p>No related items.</p>");
188 $this->createOption("flg_noheader", _RELATED_OPTION_FLG_NOHEADER, "yesno", "no");
189 $this->createOption("morelink", _RELATED_OPTION_MORELINK, "text", "and more...");
190 $this->createOption("maxlength", _RELATED_OPTION_MAXLENGTH, "text", "60");
191 $this->createOption("maxlength2", _RELATED_OPTION_MAXLENGTH2, "text", "220");
192 $this->createOption("flg_snippet", _RELATED_OPTION_FLG_SNIPPET, "yesno", "yes");
193 $this->createOption("flg_timelocal", _RELATED_OPTION_FLG_TIMELOCAL, "yesno", "no");
194 $this->createOption("currentblog", _RELATED_OPTION_CURRENTBLOG, "yesno", "yes");
195 $this->createOption('searchrange', _RELATED_OPTION_SEARCHRANGE, 'select', 'type2',
196 'Title|type1|Title, Body|type2|Title, Body, More|type3');
197 $this->createOption("flg_srchcond_and", _RELATED_OPTION_FLG_SRCHCOND_AND, "yesno", "no");
199 // $this->createOption("interval", _RELATED_OPTION_INTERVAL, "text", "96");
200 // $this->createOption("language", _RELATED_OPTION_LANGUAGE, "text", "lang_ja|lang_en");
201 $this->createOption("flg_cache_erase", _RELATED_OPTION_FLG_CACHE_ERASE, "yesno", "no");
202 $this->createOption("flg_erase", _RELATED_OPTION_FLG_ERASE, "yesno", "no");
204 mysql_query("CREATE TABLE IF NOT EXISTS ". sql_table("plug_related")
206 itemid INT(9) NOT NULL,
207 localkey VARCHAR(255) NOT NULL DEFAULT '',
208 googlekey VARCHAR(255) NOT NULL DEFAULT '',
209 amazonkey VARCHAR(255) NOT NULL DEFAULT '',
210 mode VARCHAR(100) NOT NULL DEFAULT '',
213 mysql_query("CREATE TABLE IF NOT EXISTS ". sql_table("plug_related_cache")
215 id INT(9) NOT NULL AUTO_INCREMENT PRIMARY KEY,
216 type VARCHAR(255) NOT NULL,
217 keyword VARCHAR(255) NOT NULL,
218 rank INT(9) NOT NULL,
219 url VARCHAR(255) NOT NULL,
220 title VARCHAR(255) NOT NULL,
221 stamp VARCHAR(14) NOT NULL,
226 function uninstall() {
227 if ($this->getOption('flg_erase') == 'yes') {
228 mysql_query ( "DROP table IF EXISTS ". sql_table("plug_related") );
229 mysql_query ( "DROP table IF EXISTS ". sql_table("plug_related_cache") );
233 function event_PostPluginOptionsUpdate($data) {
234 if ($this->getOption('flg_cache_erase') == 'yes') {
235 sql_query("TRUNCATE TABLE ". sql_table("plug_related_cache"));
236 $this->setOption('flg_cache_erase', 'no');
241 //Add options to add item form/bookmarklet
242 function event_AddItemFormExtras($data) {
244 <h3>Related Keyword</h3>
246 <label for="related_local">Local keyword(s):</label>
247 <input type="text" value="" id="related_local" name="local_keyword" size="60" />
250 <label for="related_google">Google keyword(s):</label>
251 <input type="text" value="" id="related_google" name="google_keyword" size="60" />
256 //Add options to edit item form/bookmarklet
257 function event_EditItemFormExtras($data) {
258 $id = $data['variables']['itemid'];
259 $result = mysql_query("SELECT itemid, localkey, googlekey, amazonkey, mode FROM ". sql_table("plug_related"). " WHERE itemid='$id'");
260 if (@mysql_num_rows($result) > 0) {
261 $localkey = mysql_result($result,0,"localkey");
262 $googlekey = mysql_result($result,0,"googlekey");
265 <h3>Related Keyword</h3>
267 <label for="related_local">Local keyword(s):</label>
268 <input type="text" value="<?php echo htmlspecialchars($localkey) ?>" id="related_local" name="local_keyword" size="60" />
271 <label for="related_google">Google keyword(s):</label>
272 <input type="text" value="<?php echo htmlspecialchars($googlekey) ?>" id="related_google" name="google_keyword" size="60" />
278 function event_PostAddItem($data) {
279 $local = requestVar('local_keyword');
280 $google = requestVar('google_keyword');
281 $amazon = requestVar('amazon_keyword');
283 // Nothing to do? Get out!!
284 if ((!$local) && (!$google) && (!$amazon)) return;
286 $itemid = $data['itemid'];
288 $local = mysql_escape_string($local);
289 $google = mysql_escape_string($google);
290 $amazon = mysql_escape_string($amazon);
292 mysql_query("INSERT INTO ". sql_table("plug_related") ." VALUES ('$itemid','$local','$google','$amazon','')");
295 //PreUpdateItem Event
296 function event_PreUpdateItem($data) {
297 $local = requestVar('local_keyword');
298 $google = requestVar('google_keyword');
299 $amazon = requestVar('amazon_keyword');
301 $itemid = $data['itemid'];
303 $local = mysql_escape_string($local);
304 $google = mysql_escape_string($google);
305 $amazon = mysql_escape_string($amazon);
307 $result = mysql_query("SELECT * FROM ". sql_table("plug_related") ." WHERE itemid='$itemid'");
309 if (@mysql_num_rows($result) > 0) {
310 // Nothing to do? Delete it!!
311 if ((!$local) && (!$google) && (!$amazon)) {
312 mysql_query("DELETE FROM ". sql_table("plug_related") ." WHERE itemid='$itemid'");
316 mysql_query("UPDATE ". sql_table("plug_related") ." SET localkey='$local',googlekey='$google',amazonkey='$amazon' WHERE itemid='$itemid'");
319 // Nothing to do? Get out!!
320 if ((!$local) && (!$google) && (!$amazon)) return;
321 mysql_query("INSERT INTO ". sql_table("plug_related") ." VALUES ('$itemid','$local','$google','$amazon','')");
326 function doSkinVar($skinType, $mode='local', $max='5', $showsnippet='', $skinquery='', $searchcond='') {
327 global $manager, $itemid;
329 if ($skinType == 'item') {
330 $item =& $manager->getItem($itemid,0,0);
332 else if ($skinquery != '') {
334 'itemid' => 0, //dummy
335 'title' => $skinquery,
338 else if ($skinType == 'search') {
340 'itemid' => 0, //dummy
341 'title' => requestVar('query'),
348 $this->doTemplateVar($item, $mode, $max, $showsnippet, $searchcond, $skinType);
351 // Handle Related Items
352 function doTemplateVar(&$item, $mode='local', $max='5', $showsnippet='', $searchcond='', $skinType='item') {
353 global $manager, $blog, $CONF;
355 if ($showsnippet == '') $showsnippet = $this->flg_snippet;
356 if ($showsnippet == 'true' or $showsnippet == 'yes') $showsnippet = true;
357 else if ($showsnippet == 'false' or $showsnippet == 'no') $showsnippet = false;
358 $this->showsnippet = $showsnippet;
363 $b =& $manager->getBlog($CONF['DefaultBlog']);
365 if (is_object($item)) $item = get_object_vars($item);
368 //$del_char['local'] = array("\\", '/');
369 $del_char['google'] = array('-', '+');
370 //$del_char['amazon'] = array('!','?');
371 $del_style = array("/(ver|version)[0-9.]+[0-9a-z.]*$/i", "/\.$/", "/-[0-9]+-$/");
372 $quote_style = _RELATED_REGEXP_QUOTESTYLE;
374 $websvc_url['google'] = "http://api.google.com/search/beta2";
375 $websvc_url['amazon'] = "http://soap.amazon.co.jp/schemas3/AmazonWebServices.wsdl"; //E amazon.com
380 // Related LOCAL items
385 $id = $item['itemid'];
386 $result = mysql_query("SELECT localkey FROM ". sql_table("plug_related") ." WHERE itemid='$id'");
387 if ($msg = mysql_fetch_array($result)) {
388 if ($msg['localkey'] == "DONOTSEARCH") $donotsearch = true;
389 else $q = $msg['localkey'];
392 // Is there a keyword present?
394 $q = strip_tags($item['title']);
398 if ($this->flg_noheader == 'yes') return;
399 $this->_show_header($mode, $q);
400 echo $this->noresults;
404 if ($this->flg_noheader == 'yes') return;
405 $this->_show_header($mode, '(No words)');
406 echo $this->noresults;
410 // prepare for multi-word search
418 $ary_quote = array();
419 while ( preg_match($quote_style, $q, $quoted_keys) ) {
420 $qlastidx = count($quoted_keys) -1;
422 //E if (preg_match("/^[0-9]+$/", $quoted_keys[$qlastidx]) ) {
423 if (preg_match("/^[0-9]+$/", mb_convert_kana($quoted_keys[$qlastidx], 'n', _CHARSET)) ) {
425 $q = preg_replace("/". preg_quote($quoted_keys[0]) ."/", '', $q);
428 $qrep = "__QUOTED{$qt_num}__";
430 // add comma around a quote for splitting
431 $ary_quote[$qt_num][0] = stripslashes($quoted_keys[0]); // use first match(with quote chars)
432 $ary_quote[$qt_num][1] = stripslashes($quoted_keys[$qlastidx]); //use last(without quote chars)
433 $q = preg_replace("/". preg_quote($quoted_keys[0]) ."/", ",$qrep,", $q);
437 // split and make multi keywords
438 $q = mb_convert_kana($q, 's', _CHARSET);
439 $ary_q = preg_split(_RELATED_REGEXP_DELIMITER, $q, -1, PREG_SPLIT_NO_EMPTY);
441 // set search condition type
442 if (strtoupper($searchcond) == 'AND' ||
443 strtoupper($searchcond) == 'OR') $qcat = $searchcond;
444 else if ($this->flg_srchcond_and == 'yes') $qcat = 'AND';
447 foreach ($ary_q as $qpiece) {
448 if (preg_match("/^__QUOTED([0-9]+)__$/", $qpiece, $qmatch)) {
449 $ary_modq[] = $ary_quote[$qmatch[1]][0]; // with quote chars
450 $qpiece = $ary_quote[$qmatch[1]][1]; // without quote chars
453 $qpiece = preg_replace($del_style, '', $qpiece);
454 if (mb_strlen($qpiece,_CHARSET) < 2) continue; // skip if the key is one letter
455 $ary_modq[] = $qpiece;
458 $qpiece = mysql_escape_string($qpiece);
460 $str_cat = ($str_where) ? " $qcat " : '';
462 switch ($this->searchrange) {
464 $str_where .= $str_cat ."( ititle LIKE '%$qpiece%' )";
467 $str_where .= $str_cat ."( ititle LIKE '%$qpiece%' OR ibody LIKE '%$qpiece%' )";
470 $str_where .= $str_cat ."( ititle LIKE '%$qpiece%' OR ibody LIKE '%$qpiece%' OR imore LIKE '%$qpiece%' )";
474 if (count($ary_modq) == 3) break; // max 3 words
476 $qmore = join($ary_modq, ' '); // for 'and more' query link
478 // Select only from same weblog?
479 if ($this->currentblog == 'yes' and $skinType == 'item') {
480 $result = mysql_query("SELECT iblog FROM ". sql_table("item") ." WHERE inumber='$item[itemid]'");
481 $msg = mysql_fetch_array($result);
482 $bid = $msg['iblog'];
483 $str_iblog = " AND iblog='$bid'";
487 $result = mysql_query("SELECT inumber, ititle, itime, ibody FROM ". sql_table("item")
488 ." WHERE ($str_where)" . $str_iblog
489 ." AND idraft=0 AND inumber<>'$id'"
490 ." AND itime<=" . mysqldate($b->getCorrectTime())
491 ." ORDER BY inumber DESC LIMIT 0,$max");
493 // Do we have any rows?
494 if (@mysql_num_rows($result) > 0) {
495 $this->_show_header($mode, $qmore);
498 while ($row = mysql_fetch_object($result)) {
502 echo $this->list_header;
506 if (empty($row->ititle)) $title = $this->notitle;
507 else $title = shorten(strip_tags($row->ititle),$this->maxlength,'...');
508 $itime = "[$row->itime]";
509 $snippet = shorten(strip_tags($row->ibody),$this->maxlength2,'...');
511 $iid = $row->inumber;
512 $bid = getBlogIDFromItemID($iid);
513 $b_tmp =& $manager->getBlog($bid);
514 $blogurl = $b_tmp->getURL() ;
516 $blogurl = $this->defaultblogurl;
518 if ($CONF['URLMode'] == 'pathinfo'){
519 if(substr($blogurl, -1) != '/')
521 $url = $blogurl .'item/'. $iid;
524 $url = createItemLink($iid);
527 $this->_show_list($mode, $url, $title, $snippet, $itime);
530 $this->_show_morelink($mode, $qmore, $b->getID());
532 if (!$first) echo $this->list_footer;
535 if ($this->flg_noheader == 'yes') return;
536 $this->_show_header($mode, $qmore);
537 echo $this->noresults;
542 // Related GOOGLE sites
548 $id = $item['itemid'];
549 if ($max > 10) $max = 10;
550 $apikey = $this->google_key;
552 $result = mysql_query("SELECT googlekey FROM ". sql_table("plug_related") ." WHERE itemid='$id'");
553 if ($msg = mysql_fetch_array($result)) {
554 if ($msg['googlekey'] == "DONOTSEARCH") $donotsearch = true;
555 else $q = $msg['googlekey'];
558 // Search keyword if no Q is found
560 $q = strip_tags($item['title']);
561 $q = str_replace($del_char[$mode], '', $q);
565 if ($this->flg_noheader == 'yes') return;
566 $this->_show_header($mode, $q);
567 echo $this->noresults;
571 if ($this->flg_noheader == 'yes') return;
572 $this->_show_header($mode, '(No words)');
573 echo $this->noresults;
577 $q = mb_convert_kana($q, 's', _CHARSET); //E comment out
580 if ($this->toexclude != '') $q .= " -site:". $this->toexclude;
581 $q = mysql_escape_string($q);
583 $this->_show_header($mode, $dispq);
585 <style type="text/css">
586 @import "http://www.google.com/uds/css/gsearch.css";
587 .gsc-control { width: auto; }
589 <script src="http://www.google.com/uds/api?file=uds.js&v=1.0&key=$apikey" type="text/javascript"></script>
590 <script language="Javascript" type="text/javascript">
594 // Create a search control
595 var searchControl = new GSearchControl();
597 searchControl.addSearcher(new GwebSearch());
598 searchControl.addSearcher(new GblogSearch());
599 //searchControl.addSearcher(new GvideoSearch());
601 // Tell the searcher to draw itself and tell it where to attach
602 searchControl.draw(document.getElementById("searchcontrol"));
604 // Execute an inital search
605 searchControl.execute("$q");
607 GSearch.setOnLoadCallback(OnLoad);
611 <div id="searchcontrol"></div>
619 function _make_stamp() {
620 return strtotime ("now");
623 function _check_cache_size() {
624 // We don't have to check this every time. By creating a random number between 0 and 50 we can reduce
625 // server load (I guess?)
627 $rand = mt_rand (0,50);
629 $cache = sql_query("SELECT * FROM ". sql_table("plug_related_cache"));
630 if (@mysql_num_rows($cache) > 2000) {
631 sql_query("TRUNCATE TABLE ". sql_table("plug_related_cache"));
636 function _show_header($mode, $q) {
639 echo $this->header_lc .$q. $this->header_end;
642 echo $this->header_go .$q. $this->header_end;
645 echo $this->header_am .$q. $this->header_end;
650 function _show_list($mode, $url, $title, $snippet='', $time='') {
651 echo "\n" . $this->item_header;
655 if ($this->showsnippet) {
656 if ($this->flg_timelocal == 'yes')
657 echo '<a href="'. $url .'">'. $title .' '. $time .'</a>';
659 echo '<a href="'. $url .'" title="'. $time .'">'. $title .'</a>';
660 echo '<br /> <span class="iteminfo">'. $snippet .'</span>';
663 if ($this->flg_timelocal == 'yes')
664 echo '<a href="'. $url .'" title="'. $snippet .'">'. $title .' '. $time .'</a>';
666 echo '<a href="'. $url .'" title="'. $snippet . $time .'">'. $title . '</a>';
672 if ($this->showsnippet) {
673 echo '<a href="'. $url .'" target="_blank">'. $title .' </a>';
674 echo '<br /> <span class="iteminfo">'. $snippet .'</span>';
677 echo '<a href="'. $url .'" title="'. $snippet .'" target="_blank">'. $title .' </a>';
682 echo $this->item_footer;
685 function _show_morelink($mode, $q, $extra='') {
688 if ($this->morelink == '') return;
690 echo "\n". $this->item_header;
694 if ($CONF['URLMode'] == 'pathinfo'){
695 $moreurl = $CONF['BlogURL'].'?amount=0&query='. urlencode($q) .'&blogid='.$bid;
698 $moreurl = createBlogidLink($bid) . '&amount=0&query='. urlencode($q);
700 echo '<a href="' . $moreurl . '" title="'. _RELATED_MSG_JUMP_LC .'">'
701 . $this->morelink.'</a>';
705 $moreurl = 'http://www.google.com/search?hl=ja&ie='. _CHARSET //E 'hl=en'
706 .'&q='. urlencode(stripslashes($q)) .'&lr=';
707 echo '<a href="' . $moreurl . '" target="_blank" title="'. _RELATED_MSG_JUMP_GO .'">'
708 . $this->morelink.'</a>';
711 echo $this->item_footer;