drupal6: ทำมอดูล Thai Search
มีโจทย์เรื่องต้องค้นข้อมูลใน Drupal เป็นภาษาไทยในไซต์ที่เป็นอินทราเน็ตให้ได้
ปัญหา
การค้นข้อมูลใน Drupal เขาทำเป็น Full Text Search โดยทำเป็น cron ในการจัดเรียงดัชนีข้อมูล เวลามาค้นก็จะหาได้รวดเร็ว
แต่ปัญหาคือ
- ภาษาไทยใช้แทบไม่ได้เลย เพราะการทำ Full Text Search ต้องอาศัยการตัดคำเพื่อนำไปจัดเรียงเป็นดัชนี ซึ่งตอนนี้ของเรายังด้อยเรื่องนี้อยู่ โดยเฉพาะกับ php (แต่เริ่มมีคนทำบ้างแล้ว เช่น ที่ pecl (เดาว่าจะออกกับ php-5.3 ดูรายละเอียดที่ ICU+PHP=love และ agavi.org ที่ผมยังไม่ได้ศึกษา)
- เนื่องจากเป็น Full Text Search อีกเหมือนกัน ที่การบางส่วนของคำ ใช้ไม่ได้ เช่น ไม่สามารถค้น "iffi" จากคำว่า "difficult" ได้ หรือไม่สามารถค้น "stand" จากคำว่า "understandable" ได้
สำหรับไซต์ที่เป็นอินเตอร์เน็ต คือข้อมูลออกสู่โลกภายนอก สามารถแก้ได้โดยใช้กูเกิล โดยพิมพ์ต่อท้ายคำค้นว่า site:example.com หรือสร้าง custom search engine เอง
สำหรับไซต์ที่เป็นอินทราเน็ต คือข้อมูลอยู่แต่ภายใน หรือไม่อนุญาตให้ crawler เข้ามาค้นข้อมูลในไซต์ ทางแก้คือลงมอดูล solr (ท่าทางจะลงยาก เป็นจาวา) หรือมอดูลอื่น (ที่อาจยุ่งพอกัน)
ทางออก
ทางออกคือทำเองแบบง่าย ๆ พอใช้งานได้ดีกว่า โดยพยายามทำคือ
- ทำให้ง่ายที่สุด (เพราะทำยากไม่เป็น ;D)
- ไม่ต้องสร้างตารางใหม่ เวลาอัปเกรดแล้วข้อมูลไม่รก
- ให้ค้นบางส่วนของคำได้ จะได้เป็นตัวเสริมของมอดูล search จริง ๆ (ในอนาคตอันใกล้นี้ search.module จะต้องทำงานนี้ได้แน่ ๆ)
- ไม่ไปยุ่งกับ Core Module
ดูตัวอย่างจาก page_example.module และตัวอย่างจาก Core Module ได้ออกมาดังนี้
ติดตั้ง
ตามขั้นตอนปกติคือ
- ติดตั้งไว้ที่
site/all/modules/ตั้งชื่อมอดูลว่าthaisearch - เปิดใช้งานจาก
admin/build/modules - เปิดข้ออนุญาตจากผู้ใช้
amdin/user/permissions
การใช้งานและข้อจำกัด
เนื่องจากเป็นรุ่นแรก ทำพอใช้งานได้ จึงทุลักทุเลพอควร
- ให้ใช้งานด้วยการพิมพ์ลงไปใน URL ตรง ๆ ว่า
thaisearch/keywords - สามารถเว้นวรรคในคำค้นได้ แต่โปรแกรมจะตัดทิ้งหมด เหลือแค่คำค้นคำแรก แก้การงงของ query ที่ซ้อนหลายชั้น
- ไปค้นที่ หัวเรื่องของ node เนื้อความของ node และเนื้อตวามของ comment เท่านั้น
- ตอนเขียน พบว่าฟังก์ชั่น pager_query ซึ่งต้องใช้ในการแยกหน้า กลับมีปัญหากับ query
GROUP BYเลยตัดทิ้งหมดเลย และจำกัดข้อมูลค้นแค่หน้าเดียว 50 รายการ - ยังปรับตั้งค่าอะไรไม่ได้เลย
- เนื่องจากใช้วิธีการค้นข้อมูลแบบโง่ ๆ ที่สุด จึงไม่ควรใช้กับไซต์ขนาดใหญ่ ควรใช้ในงานอินทราเน็ตเท่านั้น
หวังว่าคงจะมีเวลาศึกษา SQL และ PHP เพิ่มเติมเพื่อนำมาปรับปรุงในรุ่นหน้า หรือรอท่านผู้ใจบุญปรับปรุงแล้วแจกจ่ายต่อไป
เริ่มปรุง
สร้างไฟล์ info
$ cd /var/www/drupal
$ mkdir -p sites/all/modules/thaisearch
$ cd sites/all/modules/thaisearch
$ vi thaisearch.info
; $Id$ name = Thai search description = Search Thai words in node/comment. core = 6.x version = "6.0-rc2"
สร้างไฟล์ module
$ vi thaisearch.module
<?php
// $Id: thaisearch.module,v 1.13 2007/10/17 19:38:36 litwol Exp $
// wd's: modify to thaisearch.module
/**
* @file
* This is an example outlining how a module can be used to display a
* custom page at a given URL.
*/
/**
* Implementation of hook_help().
*
* Throughout Drupal, hook_help() is used to display help text at the top of
* pages. Some other parts of Drupal pages get explanatory text from these hooks
* as well. We use it here to illustrate how to add help text to the pages your
* module defines.
*/
function thaisearch_help($path, $arg) {
switch ($path) {
case 'thaisearch':
// Here is some help text for a custom page.
return t('Search thai words. Type in URL: <code><strong>thaisearch/<em>WORD1 WORD2 ...</em></strong></code> separated by space.');
}
}
/**
* Implementation of hook_perm().
*
* Since the access to our new custom pages will be granted based on
* special permissions, we need to define what those permissions are here.
* This ensures that they are available to enable on the user role
* administration pages.
*/
function thaisearch_perm() {
return array('access thaisearch');
}
/**
* Implementation of hook_menu().
*
* You must implement hook_menu() to emit items to place in the main menu.
* This is a required step for modules wishing to display their own pages,
* because the process of creating the links also tells Drupal what
* callback function to use for a given URL. The menu items returned
* here provide this information to the menu system.
*
* With the below menu definitions, URLs will be interpreted as follows:
*
* If the user accesses http://example.com/?q=foo, then the menu system
* will first look for a menu item with that path. In this case it will
* find a match, and execute thaisearch_foo().
*
* If the user accesses http://example.com/?q=bar, no match will be found,
* and a 404 page will be displayed.
*
* If the user accesses http://example.com/?q=bar/baz, the menu system
* will find a match and execute thaisearch_baz().
*
* If the user accesses http://example.com/?q=bar/baz/1/2, the menu system
* will first look for bar/baz/1/2. Not finding a match, it will look for
* bar/baz/1/%. Again not finding a match, it will look for bar/baz/%/2. Yet
* again not finding a match, it will look for bar/baz/%/%. This time it finds
* a match, and so will execute thaisearch_baz(1, 2). Note the parameters
* being passed; this is a very useful technique.
*/
function thaisearch_menu() {
// By using the MENU_CALLBACK type, we can register the callback for this
// path but not have the item show up in the menu; the admin is not allowed
// to enable the item in the menu, either.
//
// Notice that the 'page arguments' is an array of numbers. These will be
// replaced with the corresponding parts of the menu path. In this case a 0
// would be replaced by 'thaisearch', and 1 will be Thai words to search.
// These will be passed as arguments to the thaisearch_thaisearch() function.
$items['thaisearch/%'] = array(
'title' => 'Thai Search',
'page callback' => 'thaisearch_thaisearch',
'page arguments' => array(1),
'access arguments' => array('access thaisearch'),
// 'type' => MENU_CALLBACK,
);
return $items;
}
/**
* A more complex page callback that takes arguments.
*
* The arguments are passed in from the page URL. The in our hook_menu
* implementation we instructed the menu system to extract the last two
* parameters of the path and pass them to this function as arguments.
*/
function thaisearch_thaisearch($words) {
$max_item = 50;
$keys = explode(" ", $words);
$qnt = "SELECT 2 AS score, n.nid, 0 AS cid, n.title, n.body FROM {node_revisions} n WHERE n.title LIKE '%%%s%%' ";
$qnb = "SELECT 1 AS score, n.nid, 0 AS cid, n.title, n.body FROM {node_revisions} n WHERE n.body LIKE '%%%s%%' ";
$qc = "SELECT 1 AS score, c.nid, c.cid, n.title, c.comment AS body FROM {comments} c INNER JOIN {node_revisions} n ON n.nid = c.nid WHERE c.comment LIKE '%%%s%%' ";
$q = 'SELECT SUM(score) AS score, nid, cid, title, body FROM ('.$qnt.' UNION ALL '.$qnb.' UNION ALL '.$qc.' ) t GROUP BY t.nid, t.cid, t.title, t.body ORDER BY score, nid DESC';
$query = db_query_range($q, $keys[0], $keys[0], $keys[0], 0, $max_item);
$sql_count = 'SELECT COUNT(*) AS num FROM ('.$q.') t';
$count = db_fetch_object(db_query($sql_count, $keys[0], $keys[0], $keys[0]));
$found = $count->num;
$return = thaisearch_help('thaisearch','').'<br />';
$return .= t('Search words: <strong>'.$words.'</strong>, found: <strong>'.$found.'</strong><br />');
$dlist = '';
$max_words = 20;
$words_between = 10;
while ($links = db_fetch_object($query)) {
// highlight words
$ar = explode($keys[0], $links->body);
if ($ar[0]) {
$len_left = drupal_strlen($ar[0]);
$ar[0] = ($max_words > $len_left) ? $ar[0] : '...'.drupal_substr($ar[0], $len_left-$max_words, $max_words);
}
for ($i = 1; $i < count($ar); $i++) {
$len_right = drupal_strlen($ar[$i]);
if ($i == count($ar)-1) {
$ar[$i] = ($max_words > $len_right) ? $ar[$i] : drupal_substr($ar[$i], 0, $max_words).'...';
} else {
$ar[$i] = ($max_words > $len_right) ? $ar[$i] : drupal_substr($ar[$i], 0, $words_between).'...'.drupal_substr($ar[$i], $len_right-$word_between, $word_between);
}
}
if ($links->cid != 0) {
$dlist .= '<dt>'.l($links->title, 'node/'.$links->nid, array('fragment' => 'comment-'.$links->cid)).'</dt>';
} else {
$dlist .= '<dt>'.l($links->title, 'node/'.$links->nid).'</dt>';
}
$dlist .= '<dd>'.implode('<strong>'.$keys[0].'</strong>', $ar).'</dd><br />';
}
if ($dlist) {
$return .= theme('box', t('Search results'), '<br />'.$dlist, 10, 0);
} else {
$return .= theme('box', t('Your search yielded no results'));
}
return $return;
}
?>
ผลการใช้งาน
รันได้รวดเร็วดีพอควร
ต้องปรับปรุง
(รอผู้ใจบุญ)
- ทำเป็นบล๊อกไว้พิมพ์คำค้นง่าย ๆ
- เขียน SQL ดี ๆ ให้ใช้กับฟังก์ชั่น pager_query ได้
- ใช้กับเนื้อความได้ทุกแบบ ไม่จำกัดแค่ node กับ comments
- เขียนเพิ่มเรื่อง uppercase/lowercase/Capitalize
- ค้นได้ทีละหลายคำค้น โดยมีการให้น้ำหนัก (คำแรกเยอะหน่อย) แล้วจับมารวมกัน พอเรียงแบบ DESC มันน่าจะขึ้นหัวข้อที่เราต้องการที่สุดไว้ต้น ๆ คล้าย ๆ กูเกิล
อ้างอิง
เพื่อความสุขสวัสดี อย่าลืมรัน update.php ด้วย ไม่งั้นอาจมีปัญหาเรื่อง HTTP request failed
- Printer-friendly version
- Log in or register to post comments
- 9541 reads







Recent comments