บันทึกการทำงานเกี่ยวกับการสร้างมอดูล
ค้นภาษาไทยได้ โดยมีการให้น้ำหนักคำค้น และสามารถใส่คำค้นได้หลายคำ
แปลง มอดูล Thai Search ของรุ่น 6 มาเป็น ของรุ่น 5
ความสามารถเหมือนกันคือ ค้นคำไทยได้ ทำบล๊อกได้ ค้นได้หลายคำค้นพร้อมกัน
ติดตั้งปกติเหมือนมอดูลของ Drupal ทั่วไป
ดาวน์โหลดได้ที่นี่ครับ
หมายเหตุ
GROUP BY
กับฟังก์ชั่น pager
ไม่ได้ แต่กับรุ่น 5 นี้ ยังไม่ได้ทดลองครับมีโจทย์เรื่องต้องค้นข้อมูลใน Drupal เป็นภาษาไทยในไซต์ที่เป็นอินทราเน็ตให้ได้
การค้นข้อมูลใน Drupal เขาทำเป็น Full Text Search โดยทำเป็น cron ในการจัดเรียงดัชนีข้อมูล เวลามาค้นก็จะหาได้รวดเร็ว
แต่ปัญหาคือ
สำหรับไซต์ที่เป็นอินเตอร์เน็ต คือข้อมูลออกสู่โลกภายนอก สามารถแก้ได้โดยใช้กูเกิล โดยพิมพ์ต่อท้ายคำค้นว่า site:example.com
หรือสร้าง custom search engine เอง
สำหรับไซต์ที่เป็นอินทราเน็ต คือข้อมูลอยู่แต่ภายใน หรือไม่อนุญาตให้ crawler เข้ามาค้นข้อมูลในไซต์ ทางแก้คือลงมอดูล solr (ท่าทางจะลงยาก เป็นจาวา) หรือมอดูลอื่น (ที่อาจยุ่งพอกัน)
ทางออกคือทำเองแบบง่าย ๆ พอใช้งานได้ดีกว่า โดยพยายามทำคือ
ดูตัวอย่างจาก page_example.module และตัวอย่างจาก Core Module ได้ออกมาดังนี้
ตามขั้นตอนปกติคือ
site/all/modules/
ตั้งชื่อมอดูลว่า thaisearch
admin/build/modules
amdin/user/permissions
เนื่องจากเป็นรุ่นแรก ทำพอใช้งานได้ จึงทุลักทุเลพอควร
thaisearch/keywords
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; } ?>
รันได้รวดเร็วดีพอควร
(รอผู้ใจบุญ)
เพื่อความสุขสวัสดี อย่าลืมรัน update.php
ด้วย ไม่งั้นอาจมีปัญหาเรื่อง HTTP request failed
ยังไม่เข้าใจการทำงานมอดูลอย่างจริงจัง ตอนนี้ใช้วิธีตัดแปะจาก Core Module ไปก่อน
block
เพิ่ม css เพิ่ม watchdog
admin/reports/updates/check
-> ย้ายมอดูลกลับดาวน์โหลดได้แล้ว
ติดตั้งตามวิธีปกติ
$ cd /var/www/drupal/sites/all/modules
$ wget http://www.thaitux.info/files/modules/thaisearch-6.x-0.1.tar.gz
$ tar xfz thaisearch-6.x-0.1.tar.gz
หรือเขียนโค๊ดเอง
เพิ่มไฟล์ css
$ cd /var/www/drupal/sites/all/modules/thaisearch
$ vi thaisearch.css
/* $Id: thaisearch.css,v 1.3 2007/10/31 18:06:38 dries Exp $ */ .block-thaisearch .form-item { display: inline; margin: 0; padding: 0; }
แก้เฉพาะไฟล์ thaisearch.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, simple search Thai words. v.2008/1/24. /** * @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><br />Words separated by space, quote or double-quote are allowed.<br /><code>a "b c" d</code> gives <code>"<strong>a</strong>"</code> , <code>"<strong>b c</strong>"</code> , <code>"<strong>d</strong>"</code>'); case 'search#noresults': return t('<ul> <li>Check if your spelling is correct.</li> <li>Remove quotes around phrases to match each word individually: <em>"blue smurf"</em> will match less than <em>blue smurf</em>.</li> <li>Consider loosening your query with <em>OR</em>: <em>blue smurf</em> will match less than <em>blue OR smurf</em>.</li> </ul>'); } } /** * 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_block(). */ function thaisearch_block($op='list', $delta=0) { // listing of blocks, such as on the admin/block page if ($op == "list") { $block[0]["info"] = t('Thai Search'); return $block; } else if ($op == 'view' && user_access('access thaisearch')) { $block['content'] = drupal_get_form('thaisearch_form', $keys, $size=15); $block['subject'] = t('Thai Search'); return $block; } } /** * 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, ); $items['thaisearch'] = array( 'title' => 'Thai Search', 'page callback' => 'thaisearch_view', 'access arguments' => array('access thaisearch'), 'type' => MENU_SUGGESTED_ITEM, ); return $items; } /** * Form builder; Output a search form for the search block and the theme's search box. * * @ingroup forms * @see search_box_form_submit() * @see theme_search_box_form() */ function thaisearch_form(&$form_state, $form_id, $size=25) { // Add CSS drupal_add_css(drupal_get_path('module', 'thaisearch') .'/thaisearch.css', 'module', 'all', FALSE); $form['keys'] = array( '#title' => t('Search Thai words'), '#type' => 'textfield', '#size' => $size, '#default_value' => '', '#attributes' => array('title' => t('Enter the terms you wish to search for.')), ); $form['submit'] = array('#type' => 'submit', '#value' => t('Search')); $form['#submit'][] = 'thaisearch_form_submit'; $form['#validate'][] = 'thaisearch_form_validate'; return $form; } /** * Process a block search form submission. */ function thaisearch_form_submit($form, &$form_state) { $keys = $form['keys']['#value']; $form_state['redirect'] = 'thaisearch/'. $keys; } /** * Helper function for grabbing search keys. */ function thaisearch_get_keys() { static $return; if (!isset($return)) { // Extract keys as remainder of path // Note: support old GET format of searches for existing links. $path = explode('/', $_GET['q'], 2); $keys = empty($_REQUEST['keys']) ? '' : $_REQUEST['keys']; $return = count($path) == 2 ? $path[1] : $keys; } return $return; } /** * Menu callback; presents the search form and/or search results. */ function thaisearch_view() { // Search form submits with POST but redirects to GET. This way we can keep // the search query URL clean as a whistle: // search/type/keyword+keyword if (!isset($_POST['form_id'])) { $keys = thaisearch_get_keys(); // Only perform search if there is non-whitespace search term: $results = ''; if (trim($keys)) { // Collect the search results: $results = thaisearch_thaisearch($keys); if ($results) { $results = theme('box', t('Search results'), $results); } else { $results = theme('box', t('Your search yielded no results'), thaisearch_help('search#noresults', drupal_help_arg())); } } // Construct the search form. $output = drupal_get_form('thaisearch_form', $keys); $output .= $results; return $output; } return drupal_get_form('thaisearch_form', empty($keys) ? '' : $keys); } /** * Helper function: is word in string */ function _in_string($string, $word) { if (strpos($string, $word) === FALSE) { return FALSE; } else { return TRUE; } } /** * Helper function: get array of wordlist from words * 'a "b c" d' => ['a "b c" d', 'a', 'b c', 'd'] */ function _get_wordlist($string) { $temp = trim($string); $wordlist = array(); $wordlist[] = $string; while (strlen($temp) > 0) { $m = 9999; if (_in_string($temp, " ")) { $p1 = strpos($temp, " "); $m = ($m < $p1) ? $m : $p1; } if (_in_string($temp, '"')) { $p2 = strpos($temp, '"'); $m = ($m < $p2) ? $m : $p2; } if (_in_string($temp, "'")) { $p3 = strpos($temp, "'"); $m = ($m < $p3) ? $m : $p3; } if ($m == $p1) { $a = explode(" ",$temp,2); $wordlist[] = $a[0]; $temp = trim($a[1]); } elseif ($m == $p2 and substr_count($temp, '"') > 1 and (_in_string($temp, '" ') or substr($temp, -1) == '"')) { $a = explode('"',$temp,3); $wordlist[] = $a[1]; $temp = trim($a[2]); } elseif ($m == $p3 and substr_count($temp, "'") > 1 and (_in_string($temp, "' ") or substr($temp, -1) == "'")) { $a = explode("'",$temp,3); $wordlist[] = $a[1]; $temp = trim($a[2]); } else { $wordlist[] = $temp; $temp = ''; } } return($wordlist); } /** * Helper function for array_walk in thaisearch_except. */ function _thaisearch_excerpt_replace(&$text) { $text = preg_quote($text, '/'); } /** * Helper function for trim text */ function _trim_text($text, $pos = 'first', $length = 50) { $l = drupal_strlen($text); if ($l > $length) { switch ($pos) { case 'first': return ' ...'.drupal_substr($text, $l-$length, $length); case 'middle': $b = intval($length / 2); return drupal_substr($text, 0, $b).'...'.drupal_substr($text, $l-$b, $b); case 'last': return drupal_substr($text, 0, $length).'... '; } } return $text; } /** * 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. * do search from node_revision.title, node_revision.body and comments.comment */ function thaisearch_thaisearch($keys) { // Log the search keys: watchdog('thaisearchsearch', '%keys (@type).', array('%keys' => $keys, '@type' => module_invoke($type, 'thaisearch', 'name')), WATCHDOG_NOTICE, l(t('results'), 'thaisearch/'. $type .'/'. $keys)); $max_item = 50; $wordlist = _get_wordlist($keys); $qarray = array(); $arguments = array(); for ($iwl = 0; $iwl < count($wordlist); $iwl++) { // first argument in full word: allscore*10 switch ($iwl) { case 0: $w = 10; case 1: $w = 2; default: $w = 1; } // node.title: score*10 $qarray[] = " SELECT ".strval($w*10)." AS score, n.nid, 0 AS cid, n.title, n.body, n.timestamp FROM {node_revisions} n WHERE n.title LIKE '%%%s%%' "; // node.body: score*2 $qarray[] = " SELECT ".strval($w*2)." AS score, n.nid, 0 AS cid, n.title, n.body, n.timestamp FROM {node_revisions} n WHERE n.body LIKE '%%%s%%' "; // comments.comment: score*1 $qarray[] = " SELECT ".strval($w*1)." AS score, c.nid, c.cid, n.title, c.comment, c.timestamp AS body FROM {comments} c INNER JOIN {node_revisions} n ON n.nid = c.nid WHERE c.comment LIKE '%%%s%%' "; $arguments = array_merge($arguments, array($wordlist[$iwl], $wordlist[$iwl], $wordlist[$iwl])); } $q = 'SELECT SUM(score) AS score, nid, cid, title, body, timestamp FROM ('.implode(' UNION ALL ',$qarray).') t GROUP BY t.nid, t.cid, t.title, t.body, t.timestamp ORDER BY score DESC, nid DESC, timestamp DESC'; $sql_count = 'SELECT COUNT(*) AS num FROM ('.$q.') t'; $count = db_fetch_object(db_query($sql_count, $arguments)); $found = $count->num; $query = db_query_range($q, $arguments, 0, $max_item); // highlight words in trimmed output $dlist = ''; $max_length= 50; while ($links = db_fetch_object($query)) { // highlight words $text = ' '. strip_tags(str_replace(array('<', '>'), array(' <', '> '), $links->body)) .' '; array_walk($wordlist, '_thaisearch_excerpt_replace'); $text = preg_replace('/('. implode('|', $wordlist) .')/iu', '<strong>\0</strong>', $text); // trim output $text_front = ''; if (_in_string($text, '<strong>')) { $item_count = 0; $text_array = explode('<strong>', $text, 2); $text_front = _trim_text($text_array[0], 'first', $max_length).'<strong>'; $text_array = explode('</strong>', $text_array[1], 2); $text_front .= $text_array[0].'</strong>'; $text = $text_array[1]; while (_in_string($text, '<strong>') and $item_count < 5) { $item_count++; $text_array = explode('<strong>', $text, 2); $text_front .= _trim_text($text_array[0], 'middle', $max_length).'<strong>'; $text_array = explode('</strong>', $text_array[1], 2); $text_front .= $text_array[0].'</strong>'; if (_in_string($text_array[1], '<strong>')) { $text = $text_array[1]; } else { $text = _trim_text($text_array[1], 'last', $max_length); } } $text_front .= _trim_text($text, 'last', $max_length); } else { $text_front = truncate_utf8($text, $max_length, TRUE); } $dlist .= '<dt><strong>'.l($links->title, 'node/'.$links->nid).'</strong></dt>'; $dlist .= '<dd>'.$text_front.'</dd></strong><br />'; } $output = thaisearch_help('thaisearch','').'<br />'; $output .= drupal_get_form('thaisearch_form', $keys); $output .= t('Search words: <strong>'.$keys.'</strong>, found: <strong>'.$found.'</strong><br />'); if ($dlist) { $output .= theme('box', t('Search results'), '<br /><dl>'.$dlist.'</dl>', 10, 0); } else { $output .= theme('box', t('Your search yielded no results')); } return $output; } ?>
ผ่านไปนานแล้ว แต่มอดูล Search ของ drupal ก็ยังค้นภาษาไทยได้ไม่ดีขึ้นเลย จึงปรับปรุงของเก่าให้ใช้งานได้ดีขึ้นครับ
ยังตามมาหลอกหลอนถึง Drupal 7
เอามาจาก Creating modules - a tutorial: Drupal 6.x
พยายามเขียนให้เป็นเรื่องเป็นราว แต่ให้สั้น ๆ
ตามตัวอย่างเป็นการสร้างมอดูลชื่อ onthisdate
เพื่อจะทำเป็นบล๊อกแสดง "วันนี้ในอดีตเมื่ออาทิตย์ก่อน"
ลิงก์ที่ต้องไป
ควรเขียนไว้ภายใต้ site/all/module/onthisdate
เพื่อไม่ให้ปนกับของ Drupal เอง และปลอดภัยจากการอัปเกรด
$ cd /var/www/drupal
$ mkdir -p site/all/module/onthisdate
โครงสร้างชื่อฟังก์ชั่นในมอดูลจะเป็น
function {modulename}_{hook}
เช่น onthisdate_help
เพื่อข่วยเหลือ หรือ onthisdate_menu
เพื่อแสดงเมนู เป็นต้น
ต้องสร้างไฟล์ module_name.info
สำหรับบอก Drupal ตัวอย่างนี้คือ
$ vi onthisdate.info
; $Id$ name = On this date description = A block module that lists links to content such as blog entries or forum discussions that were created one week ago. core = 6.x
ต้องมีหัวข้อดังนี้
description = This is my "crazy@email.com" email address instead of description = This is my "crazy@email.com" email address
อาจมีหัวข้อดังนี้
dependencies[] = taxonomy dependencies[] = comment
package = "ชื่อมอดูลที่ร่วม"
ลองฟังก์ชั่นแรกคือ help
$ vi onthisdate.module
<?php /** * Display help and module information * @param path which path of the site we're displaying help * @param arg array that holds the current path as would be returned from arg() function * @return help text for the path */ function onthisdate_help($path, $arg) { $output = ''; switch ($path) { case "admin/help#onthisdate": $output = '<p>'. t("Displays links to nodes created on this date") .'</p>'; break; } return $output; } // function onthisdate_help ?>
ตัวแปร $path
แทนพาธว่า help
ของเราจะไปอยู่ตรงไหน
ดูเพิ่มที่
เขียนชื่อฟังก์ชั่นในรูป hook_perm
รูปแบบฟังก์ชั่นคือ
<?php function newmodule_perm() { return array('access newmodule', 'create newmodule', 'administer newmodule'); } / function newmodule_perm ?>
ตามตัวอย่างนี้คือ
<?php /** * Valid permissions for this module * @return array An array of valid permissions for the onthisdate module */ function onthisdate_perm() { return array('access onthisdate content', 'administer onthisdate'); } // function onthisdate_perm ?>
ดูเพิ่มที่
ถ้ามอดูลเราทำบล๊อกด้วย เราต้องเขียนฟังก์ชั่นในรูป hook_block
ดังนี้
<?php /** * Generate HTML for the onthisdate block * @param op the operation from the URL * @param delta offset * @returns block HTML */ function onthisdate_block($op='list', $delta=0) { // listing of blocks, such as on the admin/block page if ($op == "list") { $block[0]["info"] = t('On This Date'); return $block; } } // end onthisdate_block ?>
ตัวแปร $op
เรียกว่า operation บอกว่าข้อมูลบล๊อกของเราอยู่ในรูปไหน ในที่นี้เป็น list
ตัวแปร $delta
เรียกว่า offset บอกว่าระหว่างการแสดงผลเป็นบล๊อก หรือแสดงผลในรูปอื่นมีข้อแตกต่างกันหรือเปล่า
ดูเพิ่ม
มอดูลนี้ เราจะสร้างรายการของเนื้อหา (nodes
) ของวันนี้ในสัปดาห์ก่อน เวลาเราจะดึงรายการมา เราดูจากเวลาที่เนื้อหาถูกสร้าง โดยเราทำในรูปวินาที (ดู php เรื่องเวลา)
<?php /** * Generate HTML for the onthisdate block * @param op the operation from the URL * @param delta offset * @returns block HTML */ function onthisdate_block($op='list', $delta=0) { // listing of blocks, such as on the admin/block page if ($op == "list") { $block[0]["info"] = t('On This Date'); return $block; } else if ($op == 'view') { // our block content // Get today's date $today = getdate(); // calculate midnight one week ago $start_time = mktime(0, 0, 0, $today['mon'], ($today['mday'] - 7), $today['year']); // we want items that occur only on the day in question, so // calculate 1 day $end_time = $start_time + 86400; // 60 * 60 * 24 = 86400 seconds in a day } //We'll use db_query() to get the records (i.e. the database rows) with our SQL query $result = db_query("SELECT nid, title, created FROM {node} WHERE created >= '%s' AND created <= '%s'", $start_time, $end_time); // content variable that will be returned for display $block_content = ''; while ($links = db_fetch_object($result)) { $block_content .= l($links->title, 'node/'. $links->nid) .'<br />'; } // check to see if there was any content before setting up // the block if ($block_content == '') { /* No content from a week ago. If we return nothing, the block * doesn't show, which is what we want. */ return; } // set up the block $block['subject'] = 'On This Date'; $block['content'] = $block_content; return $block; } ?>
db_query()
โดยให้ชื่อตารางในฐานข้อมูลอยู่ในรูป {node}
ดูรายละเอียดจาก Table Prefix (and sharing tables across instances)db_fetch_object()
l()
ให้อยู่ในรูปของ <li><a href="node/nid">title</li>
onthisdate
(มีไฟล์ onthisdate.info
และ onthisdate.module
) ไปไว้ที่ sites/all/modules หรือ sites/hostname/modules
admin/build/modules
แล้วกาถูกadmin/build/block
onthisdate_admin
array( '#name => 'value', ... )
<?php function onthisdate_admin() { $form['onthisdate_maxdisp'] = array( '#type' => 'textfield', '#title' => t('Maximum number of links'), '#default_value' => variable_get('onthisdate_maxdisp', 3), '#size' => 2, '#maxlength' => 2, '#description' => t("The maximum number of links to display in the block."), '#required' => TRUE, ); return system_settings_form($form); } ?>
t()
ในการแสดงผลอักขระvariable_get('variable_name',default_value)
ในการรับค่าตัวแปรจากระบบ ซึ่งในที่นี้เรากำหนดค่าปริยายให้ onthisdate_maxdisp
เป็น 3system_settings_form()
เอาส่วนของการตั้งค่านี้ คือตัวเลข onthisdate_maxdisp
ไปใส่ในฟังก์ชั่น onthisdate_block()
ดังนี้
//--- onthisdate_block function --- ... $limitnum = variable_get("onthisdate_maxdisp", 3); $query = "SELECT nid, title, created FROM " . "{node} WHERE created >= %d " . "AND created <= %d"; $queryResult = db_query_range($query, $start_time, $end_time, 0, $limitnum); ...
onthisdate_menu
<?php function onthisdate_menu() { $items = array(); $items['admin/settings/onthisdate'] = array( 'title' => 'On this date module settings', 'description' => 'Description of your On this date settings control', 'page callback' => 'drupal_get_form', 'page arguments' => array('onthisdate_admin'), 'access arguments' => array('access administration pages'), 'type' => MENU_NORMAL_ITEM, ); return $items; } ?>
onthisdate_admin_validate
<?php function onthisdate_admin_validate($form, &$form_state) { $maxdisp = $form_state['values']['onthisdate_maxdisp']; if (!is_numeric($maxdisp)) { form_set_error('onthisdate_maxdisp', t('You must select a number for the maximum number of links.')); } else if ($maxdisp <= 0) { form_set_error('onthisdate_maxdisp', t('Maximum number of links must be positive.')); } } ?>
ดูเพิ่ม
โค๊ดหลักมีแต่การแสดงเนื้อในบล๊อกซึ่งมีเนื้อที่จำกัด คราวนี้เรามาเพิ่มให้แสดงเนื้อในหน้าหลักได้ โดยเราสามารถแสดงได้ไม่จำกัดจำนวนหัวข้อ
ทำผ่านฟังก์ชั่น onthisdate_all()
<?php function onthisdate_all() { // content variable that will be returned for display $page_content = ''; // Get today's date $today = getdate(); // calculate midnight one week ago $start_time = mktime(0, 0, 0, $today['mon'], ($today['mday'] - 7), $today['year']); // we want items that occur only on the day in question, // so calculate 1 day $end_time = $start_time + 86400; // 60 * 60 * 24 = 86400 seconds in a day $query = "SELECT nid, title, created FROM " . "{node} WHERE created >= '%d' " . " AND created <= '%d'"; // get the links (no range limit here) $queryResult = db_query($query, $start_time, $end_time); while ($links = db_fetch_object($queryResult)) { $page_content .= l($links->title, 'node/'.$links->nid).'<br />'; } // check to see if there was any content before // setting up the block if ($page_content == '') { // no content from a week ago, let the user know $page_content = "No events occurred on this site on this date in history."; } return $page_content; } ?>
onthisdate_all
ไม่ใช่ฮุก ถ้ามอดูลอื่นจะเรียกใช้ ต้องเรียกผ่านฟังก์ชั่นระบบ module_invoke()
_
ถ้าฟังก์ชั่นไหนของเราไม่ใช่ฮุก เราต้องบอกให้ Drupal รับรู้ถึงฟังก์ชั่นเราเสมอ
ทำได้ผ่านฮุก onthisdate_menu()
โดยกลับไปแก้ไขงานจากคราวก่อน
<?php function onthisdate_menu() { $items = array(); //this was created earlier in tutorial 7. $items['admin/settings/onthisdate'] = array( 'title' => 'On this date module settings', 'description' => 'Description of your On this date settings control', 'page callback' => 'drupal_get_form', 'page arguments' => array('onthisdate_admin'), 'access arguments' => array('access administration pages'), 'type' => MENU_NORMAL_ITEM, ); //this is added for this current tutorial. $items['onthisdate'] = array( 'title' => 'On this date', 'page callback' => 'onthisdate_all', 'access arguments' => array('access onthisdate content'), 'type' => MENU_CALLBACK ); return $items; } ?>
onthisdate
ฟังก์ชั่น onthisdate_all()
จะทำงานtype
) ของเมนู คือ
MENU_NORMAL_ITEM
เป็นเมนูปกติที่ผู้ใช้มองเห็นMENU_CALLBACK
ไม่แสดงในเมนูจริง ๆ แต่จะถูกเรียกใช้ผ่านทาง URL เท่านั้นดูเพิ่ม
เพิ่มลิงก์ 'more' หรือ 'มีต่อ' ในการแสดงหัวข้อเพิ่มเติมจากที่ล้นเนื้อบล๊อก
กลับไปแก้งานในส่วนของ onthisdate_block
<?php // add a more link to our page that displays all the links $block_content .= "<div class=\"more-link\">". l( t("more"), "onthisdate", array( "title" => t("More events on this day.") ) )."</div>"; ?>
ดูเพิ่ม
ได้ดังนี้
$ cd /var/www/drupal
$ mkdir -p sites/all/module/onthisdate
ไฟล์ info
$ vi onthisdate.info
; $Id$ name = On this date description = A block module that lists links to content such as blog entries or forum discussions that were created one week ago. core = 6.x
ไฟล์ module
$ vi onthisdate.module
<?php /** * Display help and module information * @param path which path of the site we're displaying help * @param arg array that holds the current path as would be returned from arg() function * @return help text for the path */ function onthisdate_help($path, $arg) { $output = ''; switch ($path) { case "admin/help#onthisdate": $output = '<p>'. t("Displays links to nodes created on this date") .'</p>'; break; } return $output; } // function onthisdate_help /** * Valid permissions for this module * @return array An array of valid permissions for the onthisdate module */ function onthisdate_perm() { return array('access onthisdate content', 'administer onthisdate'); } // function onthisdate_perm /** * Generate HTML for the onthisdate block * @param op the operation from the URL * @param delta offset * @returns block HTML */ function onthisdate_block($op='list', $delta=0) { // listing of blocks, such as on the admin/block page if ($op == "list") { $block[0]["info"] = t('On This Date'); return $block; } else if ($op == 'view') { // our block content // Get today's date $today = getdate(); // calculate midnight one week ago $start_time = mktime(0, 0, 0, $today['mon'], ($today['mday'] - 7), $today['year']); // we want items that occur only on the day in question, so // calculate 1 day $end_time = $start_time + 86400; // 60 * 60 * 24 = 86400 seconds in a day } //We'll use db_query() to get the records (i.e. the database rows) with our SQL query $limitnum = variable_get("onthisdate_maxdisp", 3); $query = "SELECT nid, title, created FROM " . "{node} WHERE created >= %d " . "AND created <= %d"; $queryResult = db_query_range($query, $start_time, $end_time, 0, $limitnum); // content variable that will be returned for display $block_content = '<ul>'; while ($links = db_fetch_object($queryResult)) { $block_content .= '<li>'.l($links->title, 'node/'. $links->nid) .'</li>'; } // check to see if there was any content before setting up // the block if ($block_content == '<ul>') { /* No content from a week ago. If we return nothing, the block * doesn't show, which is what we want. */ return; } $block_content .= '</ul>'; // add a more link to our page that displays all the links $block_content .= "<div class=\"more-link\">". l( t("more"), "onthisdate", array( "title" => t("More events on this day.") ) )."</div>"; // set up the block $block['subject'] = 'On This Date'; $block['content'] = $block_content; return $block; } // end onthisdate_block function onthisdate_admin() { $form['onthisdate_maxdisp'] = array( '#type' => 'textfield', '#title' => t('Maximum number of links'), '#default_value' => variable_get('onthisdate_maxdisp', 3), '#size' => 2, '#maxlength' => 2, '#description' => t("The maximum number of links to display in the block."), '#required' => TRUE, ); return system_settings_form($form); } function onthisdate_menu() { $items = array(); //this was created earlier in tutorial 7. $items['admin/settings/onthisdate'] = array( 'title' => 'On this date module settings', 'description' => 'Description of your On this date settings control', 'page callback' => 'drupal_get_form', 'page arguments' => array('onthisdate_admin'), 'access arguments' => array('access administration pages'), 'type' => MENU_NORMAL_ITEM, ); //this is added in tutorial 9. $items['onthisdate'] = array( 'title' => 'On this date', 'page callback' => 'onthisdate_all', 'access arguments' => array('access onthisdate content'), 'type' => MENU_CALLBACK, ); return $items; } function onthisdate_admin_validate($form, &$form_state) { $maxdisp = $form_state['values']['onthisdate_maxdisp']; if (!is_numeric($maxdisp)) { form_set_error('onthisdate_maxdisp', t('You must select a number for the maximum number of links.')); } else if ($maxdisp <= 0) { form_set_error('onthisdate_maxdisp', t('Maximum number of links must be positive.')); } } function onthisdate_all() { // content variable that will be returned for display $page_content = ''; // Get today's date $today = getdate(); // calculate midnight one week ago $start_time = mktime(0, 0, 0, $today['mon'], ($today['mday'] - 7), $today['year']); // we want items that occur only on the day in question, // so calculate 1 day $end_time = $start_time + 86400; // 60 * 60 * 24 = 86400 seconds in a day $query = "SELECT nid, title, created FROM " . "{node} WHERE created >= '%d' " . " AND created <= '%d'"; // get the links (no range limit here) $queryResult = db_query($query, $start_time, $end_time); while ($links = db_fetch_object($queryResult)) { $page_content .= l($links->title, 'node/'.$links->nid).'<br />'; } // check to see if there was any content before // setting up the block if ($page_content == '') { // no content from a week ago, let the user know $page_content = "No events occurred on this site on this date in history."; } return $page_content; } ?>
ติดตั้ง
ติดอยู่แล้ว
เปิดใช้
admin/build/module
กาถูกหน้า onthisdateadmin/user/permissions
เลือกกาถูกข้ออนุญาตที่เกี่ยวข้องกับ onthisdateตั้งค่า
admin/settings/onthisdate
ตั้งจำนวนหัวข้อadmin/build/block
เลือกกาถูก On this dateทดสอบ
ดูที่บล๊อก On this date ตามต้องการ
มีตัวอย่างการสร้าง node และ page แอบอยู่ที่ api.drupal.org คือ
สามารถดูซอร์ส แล้วเอามาทดลองสร้างมอดูลเองได้
จะแปลงมอดูลจากตัวอย่าง คือ On This Date ซึ่งดู "วันนี้ในอาทิตย์ก่อน" มาเป็น Recent Week คือดูหัวข้อใหม่ในสัปดาห์นี้ (เหมือนกับ tracker แต่ทำเป็นบล๊อกได้)
เริ่มเลย
$ cd /var/www/drupal/sites/all/modules
$ cp -xa onthisdate recentweek
$ cd recentweek
$ mv onthisdate.info recentweek.info
$ mv onthisdate.module recentweek.module
$ sed -i "s/onthisdate/recentweek/g" *
$ sed -i "s/on this date/recent week/g" *
$ sed -i "s/On this date/Recent week/g" *
$ sed -i "s/On This Date/Recent Week/g" *
$ vi recentweek.module
<?php /** * Display help and module information * @param path which path of the site we're displaying help * @param arg array that holds the current path as would be returned from arg() function * @return help text for the path */ function recentweek_help($path, $arg) { $output = ''; switch ($path) { case "admin/help#recentweek": $output = '<p>'. t("Displays links to nodes created recent week") .'</p>'; break; } return $output; } // function recentweek_help /** * Valid permissions for this module * @return array An array of valid permissions for the recentweek module */ function recentweek_perm() { return array('access recentweek content', 'administer recentweek'); } // function recentweek_perm /** * Generate HTML for the recentweek block * @param op the operation from the URL * @param delta offset * @returns block HTML */ function recentweek_block($op='list', $delta=0) { // listing of blocks, such as on the admin/block page if ($op == "list") { $block[0]["info"] = t('Recent Week'); return $block; } else if ($op == 'view') { // our block content // Get today's date $today = getdate(); // calculate midnight one week ago $start_time = mktime(0, 0, 0, $today['mon'], ($today['mday'] - 7), $today['year']); /* wd's mod // we want items that occur only on the day in question, so // calculate 1 day $end_time = $start_time + 86400; // 60 * 60 * 24 = 86400 seconds in a day */ #wd#// delete parameter $end_time, $query changed } //We'll use db_query() to get the records (i.e. the database rows) with our SQL query $limitnum = variable_get("recentweek_maxdisp", 3); /* wd's mod $query = "SELECT nid, title, created FROM " . "{node} WHERE created >= %d " . "AND created <= %d"; $queryResult = db_query_range($query, $start_time, $end_time, $limitnum); */ #wd#// delete parameter $end_time, $query changed $query = "SELECT nid, title, created FROM " . "{node} WHERE created >= %d ORDER BY created DESC" ; $queryResult = db_query_range($query, $start_time, 0, $limitnum); // content variable that will be returned for display $block_content = '<ul>'; while ($links = db_fetch_object($queryResult)) { $block_content .= '<li>'.l($links->title, 'node/'. $links->nid) .'</li>'; } // check to see if there was any content before setting up // the block if ($block_content == '<ul>') { /* No content from a week ago. If we return nothing, the block * doesn't show, which is what we want. */ return; } $block_content .= '</ul>'; // add a more link to our page that displays all the links $block_content .= "<div class=\"more-link\">". l( t("more"), "recentweek", array( "title" => t("More events on this day.") ) )."</div>"; // set up the block $block['subject'] = 'Recent Week'; $block['content'] = $block_content; return $block; } // end recentweek_block function recentweek_admin() { $form['recentweek_maxdisp'] = array( '#type' => 'textfield', '#title' => t('Maximum number of links'), '#default_value' => variable_get('recentweek_maxdisp', 3), '#size' => 2, '#maxlength' => 2, '#description' => t("The maximum number of links to display in the block."), '#required' => TRUE, ); return system_settings_form($form); } function recentweek_menu() { $items = array(); //this was created earlier in tutorial 7. $items['admin/settings/recentweek'] = array( 'title' => 'Recent week module settings', 'description' => 'Description of your Recent week settings control', 'page callback' => 'drupal_get_form', 'page arguments' => array('recentweek_admin'), 'access arguments' => array('access administration pages'), 'type' => MENU_NORMAL_ITEM, ); //this is added in tutorial 9. $items['recentweek'] = array( 'title' => 'Recent week', 'page callback' => 'recentweek_all', 'access arguments' => array('access recentweek content'), 'type' => MENU_CALLBACK, ); return $items; } function recentweek_admin_validate($form, &$form_state) { $maxdisp = $form_state['values']['recentweek_maxdisp']; if (!is_numeric($maxdisp)) { form_set_error('recentweek_maxdisp', t('You must select a number for the maximum number of links.')); } else if ($maxdisp <= 0) { form_set_error('recentweek_maxdisp', t('Maximum number of links must be positive.')); } } function recentweek_all() { // content variable that will be returned for display $page_content = ''; // Get today's date $today = getdate(); // calculate midnight one week ago $start_time = mktime(0, 0, 0, $today['mon'], ($today['mday'] - 7), $today['year']); /* wd's // we want items that occur only on the day in question, // so calculate 1 day $end_time = $start_time + 86400; // 60 * 60 * 24 = 86400 seconds in a day #wd#// delete parameter $end_time, $query changed $query = "SELECT nid, title, created FROM " . "{node} WHERE created >= '%d' " . " AND created <= '%d'"; $queryResult = db_query($query, $start_time, $end_time); */ $query = "SELECT nid, title, created FROM " . "{node} WHERE created >= '%d' ORDER BY created DESC"; // get the links (no range limit here) $queryResult = db_query($query, $start_time); $page_content = '<ul>'; while ($links = db_fetch_object($queryResult)) { $page_content .= '<li>'.l($links->title, 'node/'.$links->nid).'</li>'; } // check to see if there was any content before // setting up the block if ($page_content == '<ul>') { // no content from a week ago, let the user know $page_content = "No events occurred on this site recent week in history."; } else $page_content .= '</ul>'; return $page_content; } ?>
เสร็จแล้ว
เพื่อความสุขสวัสดี อย่าลืมรัน update.php
ด้วย ไม่งั้นอาจมีปัญหาเรื่อง HTTP request failed