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
- 9152 reads
Recent comments