drupal: สร้างมอดูลเอง

บันทึกการทำงานเกี่ยวกับการสร้างมอดูล

Topic: 

drupal: มอดูล Thai Search

คุณสมบัติ

ค้นภาษาไทยได้ โดยมีการให้น้ำหนักคำค้น และสามารถใส่คำค้นได้หลายคำ

drupal5
thaisearch-5.x-0.1.tar.gz
drupal6
thaisearch-6.x-0.2.tar.gz
drupal7
thaisearch-7.x-0.3.tar.gz

ประวัติ ...

Topic: 

drupal5: ทำมอดูล Thai Search

แปลง มอดูล Thai Search ของรุ่น 6 มาเป็น ของรุ่น 5
ความสามารถเหมือนกันคือ ค้นคำไทยได้ ทำบล๊อกได้ ค้นได้หลายคำค้นพร้อมกัน
ติดตั้งปกติเหมือนมอดูลของ Drupal ทั่วไป

ดาวน์โหลดได้ที่นี่ครับ

หมายเหตุ

  • ยังไม่ได้ตรวจสอบแบบจริงจัง แต่คงใช้งานได้ปกติโดยไม่ก่อให้เกิดความเสียหายครับ เพราะไม่ได้สร้างหรือเขียนลงฐานข้อมูลเลย
  • ของรุ่น 6 พบว่าใช้ประโยค SQL แบบมีคำสั่ง GROUP BY กับฟังก์ชั่น pager ไม่ได้ แต่กับรุ่น 5 นี้ ยังไม่ได้ทดลองครับ
Topic: 

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

Topic: 

drupal6: ปรับปรุงมอดูล Thai Search

ยังไม่เข้าใจการทำงานมอดูลอย่างจริงจัง ตอนนี้ใช้วิธีตัดแปะจาก Core Module ไปก่อน

  • ปรับปรุงให้มีฟอร์มสำหรับใส่คำค้นในหน้าหลัก ซึ่งจะทำให้สามารถเปิดใช้เมนู Thaisearch ได้ด้วย
  • จัดเรียงตามลำดับน้ำหนัก, และเวลา
  • ใส่คำค้นได้หลายคำ โดยจัดน้ำหนักให้ด้วย - ทุกคำ น้ำหนัก10, คำแรกน้ำหนัก 3, คำที่เหลือ น้ำหนัก 1 และเพิ่มน้ำหนักให้กับส่วนที่ค้น คือ node.title น้ำหนัก 10, node.body น้ำหนัก 2, comments.comment น้ำหนัก 1
  • 50-01-25 เพิ่ม block เพิ่ม css เพิ่ม watchdog
  • รู้สึกจะมีปัญหากับ Drupal เรื่อง HTTP request ยังไม่แน่ใจว่าเป็นที่ใคร วิธีแก้คือ ย้ายทุกมอดูลออกจาก sites/all/modules -> สั่ง update.php -> สั่งอัปเดตด้วยมือ 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;
}
?>

Topic: 

drupal6: ปรับปรุงมอดูล Thai Search 2

ผ่านไปนานแล้ว แต่มอดูล Search ของ drupal ก็ยังค้นภาษาไทยได้ไม่ดีขึ้นเลย จึงปรับปรุงของเก่าให้ใช้งานได้ดีขึ้นครับ

ปรับปรุง

  • เพิ่ม pager ให้สามารถแบ่งดูได้หลายหน้า
  • เพิ่มฟิลด์ timestamp ให้ทราบว่าโพสต์เมื่อไหร่

ดาวน์โหลด

ปรับปรุง

  • 20101113 ปรับการแสดงผลเล็กน้อย
Topic: 

drupal7: มอดูล Thai Search

ยังตามมาหลอกหลอนถึง Drupal 7

ปรับปรุง

  • โยกมาใส่ใน Drupal 7
  • เพิ่มการค้นหาใน comment.subject

ดาวน์โหลด

Topic: 

drupal: สร้างมอดูลเอง (Drupal 6 Module Tutorial)

เอามาจาก Creating modules - a tutorial: Drupal 6.x
พยายามเขียนให้เป็นเรื่องเป็นราว แต่ให้สั้น ๆ

ตามตัวอย่างเป็นการสร้างมอดูลชื่อ onthisdate เพื่อจะทำเป็นบล๊อกแสดง "วันนี้ในอดีตเมื่ออาทิตย์ก่อน"

ลิงก์ที่ต้องไป

Topic: 

01. เริ่มต้น (Getting Started)

ควรเขียนไว้ภายใต้ site/all/module/onthisdate เพื่อไม่ให้ปนกับของ Drupal เอง และปลอดภัยจากการอัปเกรด
$ cd /var/www/drupal
$ mkdir -p site/all/module/onthisdate

โครงสร้างชื่อฟังก์ชั่นในมอดูลจะเป็น

function {modulename}_{hook}

เช่น onthisdate_help เพื่อข่วยเหลือ หรือ onthisdate_menu เพื่อแสดงเมนู เป็นต้น

Topic: 

02. บอกว่ามอดูลเราจะทำอะไร (Telling Drupal about your module)

ต้องสร้างไฟล์ 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

ต้องมีหัวข้อดังนี้

name
บอกชื่อมอดูล
description
บอกคนอื่นให้รู้ว่ามอดูลทำอะไร สั้น ๆ 1 บรรทัด ถ้ามีอักขระแปลก ๆ ต้องเขียนด้วย HTML entities เพื่อให้แสดงผลในเว็บได้ เช่น
description = This is my &quot;crazy@email.com&quot; email address instead of description = This is my "crazy@email.com" email address
core
บอกให้รู้รุ่น Drupal เพราะเขาใช้เป็นตัวแยกว่าอันไหนจะเปิดให้ใช้งานบ้าง เวลาอัปเกรด

อาจมีหัวข้อดังนี้

ดีเพนเดนซี (dependencies)
บอกให้รู้ว่ามอดูลเราต้องการมอดูลไหนเป็นฐานบ้าง
    dependencies[] = taxonomy
    dependencies[] = comment
แพกเกจ (package)
บอกให้รู้ว่ามอดูลเรามีเพื่อนร่วมมอดูลอะไรบ้าง
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 ของเราจะไปอยู่ตรงไหน

ดูเพิ่มที่

Topic: 

03. มอดูลมีข้ออนุญาตอะไรบ้าง (Telling Drupal who can use your module)

เขียนชื่อฟังก์ชั่นในรูป 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
?>

ดูเพิ่มที่

Topic: 

04. ทำเรื่องบล๊อก (Declare we have block content)

ถ้ามอดูลเราทำบล๊อกด้วย เราต้องเขียนฟังก์ชั่นในรูป 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 บอกว่าระหว่างการแสดงผลเป็นบล๊อก หรือแสดงผลในรูปอื่นมีข้อแตกต่างกันหรือเปล่า

ดูเพิ่ม

Topic: 

05. สร้างเนื้อให้บล๊อก (Generate the block content)

มอดูลนี้ เราจะสร้างรายการของเนื้อหา (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;
}
?>
  • สร้าง query ใช้ฟังก์ชั่น db_query() โดยให้ชื่อตารางในฐานข้อมูลอยู่ในรูป {node} ดูรายละเอียดจาก Table Prefix (and sharing tables across instances)
  • เวลาดึงจริง ใช้ฟังก์ชั่น db_fetch_object()
  • พอดึงมาปุ๊ป ก็สร้างรายการเป็นลิงก์ ด้วยฟังก์ชั่น l() ให้อยู่ในรูปของ <li><a href="node/nid">title</li>
Topic: 

06. ติดตั้ง เปิดใช้ ตั้งค่า และทดสอบ (Installing, enabling and testing the module)

ติดตั้ง
เอาไดเรคทอรี onthisdate (มีไฟล์ onthisdate.info และ onthisdate.module) ไปไว้ที่ sites/all/modules หรือ sites/hostname/modules
เปิดใช้
ผ่านเมนูคือ Administer » Site building » Modules หรือพิมพ์ตรง ๆ ใน URL ว่า admin/build/modules แล้วกาถูก
ตั้งค่า
มอดูลเราเป็นบล๊อก จึงต้องเปิดใช้งานบล๊อกผ่านเมนู Administer » Site building » Blocks หรือ URL ว่า admin/build/block
ทดสอบ
ดูจากตรงบล๊อกที่เราเพิ่งเปิดใช้ ถ้าวันนี้ในสัปดาห์ก่อนไม่มีเนื้อหาอะไร เราก็จะไม่เห็นอะไรเลย แต่ถ้ามีเนื้อหามากหลายหัวข้อ จะเห็นบล๊อกนี้ยาวเหยียด (ซึ่งเราจะไปปรับแต่งต่อไป)
Topic: 

07. เพิ่มส่วนของการตั้งค่า (Create a module configuration (settings) page)

ทำให้ตั้งค่าได้ด้วยฟังก์ชั่น 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 เป็น 3
  • ต้องคืนค่าให้ระบบด้วยฟังก์ชั่น system_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.'));
  }
}
?>

ดูเพิ่ม

Topic: 

08. เพิ่มส่วนแสดงเนื้อหาในหน้าหลัก (Generate a page content)

โค๊ดหลักมีแต่การแสดงเนื้อในบล๊อกซึ่งมีเนื้อที่จำกัด คราวนี้เรามาเพิ่มให้แสดงเนื้อในหน้าหลักได้ โดยเราสามารถแสดงได้ไม่จำกัดจำนวนหัวข้อ

ทำผ่านฟังก์ชั่น 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()
  • แต่ถ้าเราจะให้ฟังก์ชั่นของเราดูได้เฉพาะภายใน ต้องนำหน้าชื่อฟังก์ชั่นด้วยขีดเส้นใต้ _
Topic: 

09. บอกให้ Drupal รู้ถึงการทำงานใหม่ (Letting Drupal know about the new function)

ถ้าฟังก์ชั่นไหนของเราไม่ใช่ฮุก เราต้องบอกให้ 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;
}
?>
  • เมื่อเรียกผ่าน URL ว่า onthisdate ฟังก์ชั่น onthisdate_all() จะทำงาน
  • ชนิด (type) ของเมนู คือ
    • MENU_NORMAL_ITEM เป็นเมนูปกติที่ผู้ใช้มองเห็น
    • MENU_CALLBACK ไม่แสดงในเมนูจริง ๆ แต่จะถูกเรียกใช้ผ่านทาง URL เท่านั้น

    ดูเพิ่ม

    Topic: 

    10. เพิ่ม 'more' ให้บล๊อก (Adding a 'more' link and showing all entries)

    เพิ่มลิงก์ '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>";
    ?>

    ดูเพิ่ม

    Topic: 

    11. จับมารวมกัน

    ได้ดังนี้
    $ 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;
    }
    ?>
    

    ติดตั้ง
    ติดอยู่แล้ว

    เปิดใช้

    • URL admin/build/module กาถูกหน้า onthisdate
    • URL admin/user/permissions เลือกกาถูกข้ออนุญาตที่เกี่ยวข้องกับ onthisdate

    ตั้งค่า

    • URL admin/settings/onthisdate ตั้งจำนวนหัวข้อ
    • ทำเป็นบล๊อกผ่าน URL admin/build/block เลือกกาถูก On this date

    ทดสอบ
    ดูที่บล๊อก On this date ตามต้องการ

    Topic: 

    drupal6: ตัวอย่างการสร้าง node, page

    มีตัวอย่างการสร้าง node และ page แอบอยู่ที่ api.drupal.org คือ

    สามารถดูซอร์ส แล้วเอามาทดลองสร้างมอดูลเองได้

    Topic: 

    drupal6: แปลงมอดูล onthisdate เป็น recentweek

    จะแปลงมอดูลจากตัวอย่าง คือ 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

    Topic: