Drupal 7: Creating custom modules

Drupal7 18 เม.ย. 2017

การตั้งชื่อ(short name) module ใน drupal 7 ใช้ในไฟล์ทั้งหมดและ ชื่อ function ใน module

  • ต้องขึ้นต้นด้วยตัวอักษร
  • เป็นตัวอักษรตัวเล็กทั้งหมดและ underscore เท่านั้น
  • ** ห้ามใช้ตัวอักษรตัวใหญ่ในการตั้งชื่อ(short name)

ตัวอย่างเช่น “current_posts”

Drupal จะเข้าใจ function ที่มี prefix เหมือนกันใน module file เท่านั้น
ที่สำคัญต้องแน่ใจว่าไม่มี module ที่มีชื่อซ้ำกันในทุก theme ที่ใช้งานในเว็บ

สร้าง folder และ module file

ให้ตั้งชื่อว่า “current_posts”

  1. เริ่มสร้าง module โดยสร้าง folder ใน Drupal ที่ติดตั้งไว้ตาม path นี้
    • sites/all/modules/current_posts In Drupal 6.x and 7.x  sites/all/modules ( หรือ sites/all/modules/contrib และ sites/all/modules/custom) is the preferred place for non-core modules
    • sites/all/themes (or sites/all/themes/contrib and sites/all/themes/custom) for non-core themes.
    • sites/your-site-folder/modules ในกรณีที่เป็น multi-site Drupal
  2. สร้าง info ไฟล์สำหรับ module
    • สร้างไฟล์ “current_posts.info” ใน path “sites/all/modules/current_posts”
    • อย่างน้อยที่สุด ในไฟล์ต้องการ ตามนี้
      name = Current Posts
      description = Description of what this module does
      core = 7.x
  3. สร้างไฟล์ “current_posts.module” ใน path “sites/all/modules/current_posts”
  4. เพิ่ม PHP Tag เปิดในไฟล์ “current_posts.module”

ตาม Coding standards แล้วให้ไม่ต้องปิดด้วย tag ?> (การใส่แท็กปิดอาจทำให้เกิดปัญหาเกี่ยวกับรันไทม์แปลก ๆ ในการตั้งค่าเซิร์ฟเวอร์บางอย่าง)

function ใน module ทั้งหมด จะใช้การตั้งชื่อ “hooks” ตามนี้ {modulename}_{functionname}
“functionname” จะเป็นชื่อต่อท้าย function ที่ถูกกำหนดไว้ล่วงหน้าแล้ว เพื่อให้ drupal สามารถรู้ได้ว่าเป็น hook อะไร


ไฟล์ info ใน module จะเป็นไฟล์ที่ กำหนดรายละเอียด (meta information) ของ module

general format จะมีรูปแบบตามนี้

name = Module Name
description = A description of what your module does.
core = 7.x

รายละเอียด metadata ในไฟล์ info ทั้งหมด: https://www.drupal.org/docs/7/creating-custom-modules/writing-module-info-files-drupal-7x


Concept ของ Hook ใน Drupal 7

ระบบ hook ของ Drupal ช่วยให้โมดูลสามารถโต้ตอบและแก้ไขข้อมูลของโมดูลอื่น ๆ ได้ (หรือแม้แต่ core ของ Drupal เอง) ข้อมูลเพิ่มเติม

การสร้าง Drupal 7 hook (และเรียกการใช้งาน)

การสร้าง hook และเรียกการใช้งาน สามารถทำได้โดยใช้หนึ่งในฟังก์ชั่นต่อไปนี้ (ไม่ใช่ทั้งหมดอันใดอันนึงเท่านั้น) drupal_alter(), module_invoke_all() and module_invoke()

Drupal 7 Hook Types:

ในทางปฏิบัติจะมี hook อยู่ 2 แบบที่คุณต้องได้ใช้งาน

  1. Alter hooks: เป็นวิธีปกติในการแก้ไขเนื้อหาของวัตถุหรือตัวแปรเฉพาะโดยรับตัวแปรใน hook โดยการอ้างอิง ปกติแล้วจะใช้ drupal_alter()
  2. Intercepting hooks: ทำให้ external module สามารถทำงานได้ แต่ไม่สามารถรับค่าตัวแปรแบบ by reference ได้ ปกติใช้งาน module_invoke_all() and module_invoke()

ตัวอย่างการใช้งาน

Example #1 (Simple Invoking)

// Calling all modules implementing 'hook_name':
module_invoke_all('hook_name');

Example #2 (Invoking a Particular one)

// Calling a particular module's 'hook_name' implementation:
module_invoke('module_name', 'hook_name');
// @note: module_name comes without '.module'
// @note: hook_name is the specific hook defined in that module

Example #3 (Collecting results in an array)

$result = array();
foreach (module_implements('hook_name') as $module) {
// Calling all modules implementing hook_hook_name and
// Returning results than pushing them into the $result array:
$result[] = module_invoke($module, 'hook_name');
}

Example #4 (Altering data using drupal_alter())

$data = array(
'key1' => 'value1',
'key2' => 'value2',
);
// Calling all modules implementing hook_my_data_alter():
drupal_alter('my_data', $data);
// You should implement hook_my_data_alter() in all other modules in which you want to alter $data

Example #5 (passing by reference: cannot use module_invoke())
// @see user_module_invoke()
foreach (module_implements('hook_name') as $module) {
$function = $module . '_hook_name';
// will call all modules implementing hook_hook_name
// and can pass each argument as reference determined
// by the function declaration
$function($arg1, $arg2);
}

Comments ใน Drupal modules

จะอยู่ใน format

 

/**
* @file
* A block module that displays recent blog and forum posts.
*/

รายชื่อ hook ในที่สามารถใช้งานได้

ในการเรียกใช้งาน hook_help() สามารถเรียกใช้งานโดยเรียก current_posts_help() ในไฟล์ “current_posts.module”

function current_posts_help($path, $arg) {

}

The $path parameter provides context for the help: where in Drupal or the module the user is when they are accessing help. The recommended way to process this variable is with a switch statement. This code pattern is common in Drupal modules. Here is an abbreviated implementation of this function for your module, along with its doc block comment:

/**
* Implements hook_help().
*
* Displays help and module information.
*
* @param path
* Which path of the site we're using to display help
* @param arg
* Array that holds the current path as returned from arg() function
*/
function current_posts_help($path, $arg) {
switch ($path) {
case "admin/help#current_posts":
return t("Displays links to nodes created on this date");
break;
}
}

switch statement ใช้เพื่อเฉพาะเจาะจงว่าให้แสดงเฉพาะ admin/help#current_posts

เรื่อง Localization API ดูรายละเอียดเพิ่มเติมได้ที่ Localization API


.install ไฟล์ใน Drupal 7

Drupal 7 ใช้ .install ไฟล์เพื่อ create database tables, fields และ insert data รวมไปถึงการ update โครงสร้าง database และ content ภาพใน table

ถ้าใน module มี _install() function จะถูกเรียกใช้งานเมื่อเปิดใช้งานเป็นครั้งแรก ปกติแล้วจะใช้เพื่อสร้างตารางที่จำเป็นใน module

ในการสร้างตารางจะใช้ Schema API Schema API จะใช่ในการกำหนดโครงสร้างตารางโดยใช้ array โดยมี API function สำหรับ creating, dropping, and changing tables, columns, keys, และ indexes

ตัวอย่างโครงสร้าง schema data (นำมาจาก Schema Api Documentation)

ตัวอย่าง schema ของตาราง “node”

$schema['node'] = array(
'description' => 'The base table for nodes.',
'fields' => array(
'nid' => array(
'description' => 'The primary identifier for a node.',
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE),
'vid' => array(
'description' => 'The current {node_revisions}.vid version identifier.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0),
'type' => array(
'description' => 'The {node_type} of this node.',
'type' => 'varchar',
'length' => 32,
'not null' => TRUE,
'default' => ''),
'title' => array(
'description' => 'The title of this node, always treated a non-markup plain text.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => ''),
),
'indexes' => array(
'node_changed' => array('changed'),
'node_created' => array('created'),
),
'unique keys' => array(
'nid_vid' => array('nid', 'vid'),
'vid' => array('vid')
),
'primary key' => array('nid'),
);

การสร้างตาราง: hook_schema และ .install ไฟล์

สำหรับการใช้ Schema API ในการจัดการตารางของ module ซึ่งใน module ต้องมีไฟล์ .install เพื่อจัดการ hook_schema(), ตัวอย่างเช่น ไฟล์ mymodule.install ของ mymodule จะมี code

function mymodule_schema() {
$schema['mytable1'] = array(
// specification for mytable1
);
$schema['mytable2'] = array(
// specification for mytable2
);
return $schema;
}

หากเป็นการเข้าใช้งานครั้งแรกจะ hook_schema() จะถูกประกาศโดยอัตโนมัติ และถูกลบทิ้งไปเมื่อ uninstall module

hook_install() จะถูกเรียกใช้งานก่อนเป็นอันดับแรก เพื่อติดตั้งตาราง และ hook_uninstall() จะถูกเรียกตามมา เมื่อมีการ uninstall module

การ update module จะใช้ hook_update_N

ในการ update module จะใช้ functions hook_update_N (รายละเอียด API)

สมมติว่าเพิ่มคอลัมน์ใหม่ชื่อ ‘newcol’ เพื่อตามราง mytable1 ขั้นแรกตรวจสอบให้แน่ใจว่าได้อัปเดตโครงสร้าง schema ใน mymodule_schema () เพื่อให้ตารางที่สร้างขึ้นใหม่ได้คอลัมน์ใหม่ จากนั้นเพิ่มฟังก์ชันการอัพเดตใน mymodule.install:

function mymodule_update_1() {
db_add_field('mytable1', 'newcol', array('type' => 'int'));
}

นอกจากนี้ยังมีโมดูลที่เรียกว่า Schema Module ซึ่งมีฟังก์ชันเพิ่มเติมเกี่ยวกับ Schema ที่ไม่ได้ให้มาจาก Schema API หลักที่เป็นประโยชน์สำหรับนักพัฒนาโมดูล ในปัจจุบันนี้รวมถึง:

  • Schema documentation: hyperlinked display of the schema’s embedded documentation explaining what each table and field is for.
  • Schema structure generation: the module examines the live database and creates Schema API data structures for all tables that match the live database.
  • Schema comparison: the module compares the live database structure with the schema structure declared by all enabled modules, reporting on any missing or incorrect tables.

นอกจากนี้ยังมีโมดูลที่เรียกว่า Schemadata ซึ่งมีหน้าที่แสดงตาราง mysql และข้อมูลทั้งหมด โมดูลนี้มีประโยชน์สำหรับนักพัฒนาซอฟต์แวร์เพื่อประหยัดเวลา

ในการ replace Field

มีหลายกรณีไปดูเองที่ https://api.drupal.org/api/drupal/7.x/search/hook_field_

White screen of death

ในกรณีที่เกิด White screen of death สามารถดูปัญหาที่อาจเกิดขึ้นได้ที่ Show all errors while developing.


Declaring the block

ใน Drupal 7 จะมีอย่างน้อย 8 block hook สำหรับการแสดงผลแต่ละจุดในเว็บ เราจะใช้ function hook_block_info เพื่อให้ข้อมูล Drupal เพื่อให้ module แสดงผลในรายการ block

ในการใช้งาน hook นี้ในการกำหนด block ให้เข้าไปที่ current_posts.module และสร้าง function current_posts_block_info() ตามนี้

/**
* Implements hook_block_info().
*/
function current_posts_block_info() {
$blocks['current_posts'] = array(
// The name that will appear in the block list.
'info' => t('Current posts'),
// Default setting.
'cache' => DRUPAL_CACHE_PER_ROLE,
);
return $blocks;
}

(จำไว้เสมอว่าไม่ต้องปิด Tag ?> ใน code)

การตรวจสอบ

ให้ไปที่ Modules click ที่ checkbox เพื่อ enable “Current Posts” และ click ที่ “Save configuration” ต่อจากนั้นให้ไปที่ Structure > Blocks เลื่อนลงไปที่ด้านล่างของรายการ ในกลุ่ม disabled blocks ให้หาชื่อ ‘Current posts’ ถ้าหาก disable module ในหน้า Modules จะต้องไม่เห็นรายการใน list


การเรียกใช้ข้อมูลใน DB

หัวข้อหลักที่อธิบาย: Database API, ฟังก์ชั่นหลักอธิบาย: db_select()

ต่อไปเราจะสร้างฟังก์ชันที่กำหนดเองเพื่อเรียกค้นโพสต์ล่าสุด เมื่อโหนดถูกสร้างขึ้นครั้งแรกเวลาที่สร้างจะถูกเก็บไว้ในฐานข้อมูล เราจะใช้ฟิลด์ฐานข้อมูลนี้เพื่อค้นหาข้อมูลของเรา

เราจะเรียกใช้ function current_posts_contents ซึ่งเรายังคงทำตามแนวทางการตั้งชื่อโดยจะเริ่มต้นด้วย module short name ก่อนเสมอ จากนั้นเราจะใช้ชื่ออธิบายที่ไม่ใช่ Drupal hook

function นี้จะเริ่มต้นด้วยการหาตัวเลขเวลา (function ยังไม่จบนะ)

/**
* Custom content function.
*
* Set beginning and end dates, retrieve posts from database
* saved in that time period.
*
* @return
* A result set of the targeted posts.
*/
function current_posts_contents(){
//Get today's date.
$today = getdate();
//Calculate the date a week ago.
$start_time = mktime(0, 0, 0,$today['mon'],($today['mday'] - 7), $today['year']);
//Get all posts from one week ago to the present.
$end_time = time();

code นี้จะหาเวลาปัจจุบัน จากนั้นจะคำนวนหาเวลาออกมาเป็นวินาทีสำหรับ 1 อาทิตย์ที่ผ่านมา โดยใช้ function mktime และ time() สำหรับ $end_time

ต่อจากนั้นเราจะใช้ Drupal’s Database API แสดง list ของข้อมูล

//Use Database API to retrieve current posts.
$query = db_select('node', 'n')
->fields('n', array('nid', 'title', 'created'))
->condition('status', 1) //Published.
->condition('created', array($start_time, $end_time), 'BETWEEN')
->orderBy('created', 'DESC') //Most recent first.
->execute();
return $query;
}

code เมื่อรวมกันแล้ว

/**
* Custom content function.
*
* Set beginning and end dates, retrieve posts from database
* saved in that time period.
*
* @return
* A result set of the targeted posts.
*/
function current_posts_contents(){
//Get today's date.
$today = getdate();
//Calculate the date a week ago.
$start_time = mktime(0, 0, 0,$today['mon'],($today['mday'] - 7), $today['year']);
//Get all posts from one week ago to the present.
$end_time = time();

//Use Database API to retrieve current posts.
$query = db_select('node', 'n')
->fields('n', array('nid', 'title', 'created'))
->condition('status', 1) //Published.
->condition('created', array($start_time, $end_time), 'BETWEEN')
->orderBy('created', 'DESC') //Most recent first.
->execute();
return $query;
}

Generating block content

อธิบาย Drupal hook: hook_block_view()
function หลักที่ใช้: user_access(), l(), theme()

ขั้นตอนต่อไปในส่วนนี้คือนำข้อมูลที่เราสร้างขึ้นใน custom function ของเราและเปลี่ยนเป็นเนื้อหาสำหรับ block ซึ่งเราจะใช้ hook_block_view function นี้จะ return ค่า 2 ค่า คือ ‘subject’ คือ title ของ block และ ‘content’ คือ เนื้อหาของตัวมันเอง โดยใน function นี้จะมี switch คอยทำหน่าที่ กรอง โดยจะใช้ตัวแปร $delta ( function hook_block_view จะต้องมีตัวแปร $delta เสมอ )

Access check

ส่วนแรกของ code

function current_posts_block_view($delta = '') {
switch ($delta) {
case 'current_posts':
$block['subject'] = t('Current posts');
if (user_access('access content')) {
// Retrieve and process data here.
}

code ส่วนนี้จะเป็นการกำหนดว่า user ที่มี permission ตามที่ระบุเท่านั้นที่สามารถเข้าถึงส่วนนี้ได้ หากต้องการดูว่ามี permission อะไรบ้างให้เข้าไปดูได้ที่ People > List หรือ http://example.com/admin/people จะอยู่ใน permission dropdown

Coding the data as links

// Use our custom function to retrieve data.
$result = current_posts_contents();
// Array to contain items for the block to render.
$items = array();
// Iterate over the result set and format as links.
foreach ($result as $node) {
$items[] = array(
'data' => l($node->title, 'node/' . $node->nid),
);
}

ส่วนนี้ของ code จะใช้ตัวแปร $result รับค่าจาก current_posts_contents() เพื่อสร้างลิ้งแล้วนำไปเก็บใน array $items
function l() เป็น function ที่ใช้สำหรับสร้างลิ้ง (‘L’ ตัวพิมพ์เล็ก), parameter ตัวแรกคือ text ในลิ้ง parameter ตัวที่สอง คือ path ของลิ้ง

Theming the data

การทำงานของ Drupal จะมีการทำงานแบบ pluggable โดยแบ่งออกเป็นเลเยอร์

ส่วนสุดท้ายของ code สำหรับ current_posts_block_view:

// No content in the last week.
if (empty($items)) {
$block['content'] = t('No posts available.');
}
else {
//Pass data through theme function.
$block['content'] = theme('item_list', array('items' => $items));
}
}
return $block;
}

}

ถ้า $items มีข้อมูลตามเงื่อนไข จะไปทำงานต่อที่ theme() function ซึ่งใน argument แรกของ theme hook function นี้ของ Drupal มี theme hook เริ่มต้นหลายตัวที่สามารถใช้กับฟังก์ชันนี้ได้ ดูได้ที่ Default theme implementations ถ้าดูตาม code ด้านบนจะเป็นการกำหนดให้ข้อมูลของเราเป็น list รายการที่ไม่เรียงลำดับ

การใช้งาน function theme_item_list ของ theme hook จะมี argument ตัวที่ 2 เป็นการส่งค่า content เข้าไปใน theme

Code function นี้ทั้งหมด

/**
* Implements hook_block_view().
*
* Prepares the contents of the block.
*/
function current_posts_block_view($delta = '') {
switch ($delta) {
case 'current_posts':
$block['subject'] = t('Current posts');
if (user_access('access content')) {
// Use our custom function to retrieve data.
$result = current_posts_contents();
// Array to contain items for the block to render.
$items = array();
// Iterate over the resultset and format as links.
foreach ($result as $node) {
$items[] = array(
'data' => l($node->title, 'node/' . $node->nid),
);
}
// No content in the last week.
if (empty($items)) {
$block['content'] = t('No posts available.');
}
else {
// Pass data through theme function.
$block['content'] = theme('item_list', array(
'items' => $items));
}
}
return $block;
}

}

ส่วนที่เกี่ยวข้อง


Preparing for a module configuration form

 

อธิบายเกี่ยวกับ: hook_menu()

Registering the URL

ในส่วนนี้เราจะใช้ hook_menu() ถึงจะชื่อว่า hook_menu แต่มันไม่ได้แค่จัดการ menu เท่านั้น เรายังใช้มันในการลงทะเบียน path ในการเข้าถึงของแต่ละ URl ด้วย

Access check

hook_menu () ยังให้ความสำคัญกับความปลอดภัยของ Drupal เนื่องจากจะดำเนินการตรวจสอบการเข้าถึงของผู้ใช้ เราต้องการให้ผู้ดูแลระบบเพียงรายเดียวเท่านั้นที่สามารถเข้าถึงแบบฟอร์มนี้ได้และเราจะตรวจสอบสิทธิ์ใน hook_menu () เพื่อลดจำนวนสิทธิ์ที่ผู้ดูแลระบบต้องจัดการเราจะใช้สิทธิ์การดูแลระบบหลักแทนการสร้างสิทธิ์แบบกำหนดเองใหม่

ในระบบเมนูของ Drupal จะมีการแปลแอตทริบิวต์ ‘title’ และ ‘description’ อัตโนมัติ ซึ่งต้องใช้ฟังก์ชัน t() ในในการตัวอักษรสตริงทั้งหมด

/**
 * Implements hook_menu().
 */
function current_posts_menu() {
  $items = array();

  $items['admin/config/content/current_posts'] = array(
    'title' => 'Current posts',
    'description' => 'Configuration for Current posts module',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('current_posts_form'),
    'access arguments' => array('access administration pages'),
    'type' => MENU_NORMAL_ITEM,
  );

  return $items;
}

เพิ่มปุ่มกำหนดค่าในหน้า Modules

ในไฟล์ current_posts.info ของคุณเพิ่มบรรทัดต่อไปนี้เพื่อสร้างปุ่มที่เชื่อมโยงไปยังหน้าการกำหนดค่าของคุณในหน้าโมดูล

name = Current Posts
description = A block module that lists links to recent posts.
core = 7.x

; NEW LINE
configure = admin/config/content/current_posts

Declaring the form

“page callback” เป็นการบอก Drupal ว่าอะไรคือสิ่งที่ลิ้งนี้ต้องการ ในที่นี้คือ “drupal_get_form”
“page arguments” เป็นค่าที่ส่งผ่านไปที่ function ตอนนี้คือ “current_posts_form” เรากำหนดว่าเป็นทั้ง ID ของฟอร์มและชื่อของฟังก์ชันที่จะสร้างฟอร์มการตั้งค่าของเรา โดยชื่อ function ที่เรากำหนดจะเป็นไปตาม naming convention ของ PHP variable ซึ่งต้องขึ้นต้นด้วย ชื่อ module ตามด้วย underscore


Creating the configuration form

ส่วนนี้เกี่ยวกับ: Form API
function หลักเกี่ยวกับ: variable_get(), system_settings_form()

ต่อไปเราจะสร้าง current_posts_form() function เราสร้างฟอร์มด้วยการเพิ่ม elements ลงใน array ของตัวแปร $form ใน array แต่ละ key จะมี hash mark (#)

เพิิ่ม code นี้ลงในไฟล์ current_posts.module

/**
 * Page callback: Current posts settings
 *
 * @see current_posts_menu()
 */
function current_posts_form($form, &$form_state) {
  $form['current_posts_max'] = array(
    '#type' => 'textfield',
    '#title' => t('Maximum number of posts'),
    '#default_value' => variable_get('current_posts_max', 3),
    '#size' => 2,
    '#maxlength' => 2,
    '#description' => t('The maximum number of links to display in the block.'),
    '#required' => TRUE,
  );

  return system_settings_form($form);
}

Form element attributes ดูรายละเอียด Form API ดูได้ที่ Form API Reference

System settings

Drupal ช่วยให้เราสามารถบันทึกข้อมูลของฟอร์มด้วยฟังก์ชัน system_settings_form() โดยการใช้ฟังก์ชันในโค้ดของเราเราจะบอก Drupal ให้ส่งปุ่มและบันทึกข้อมูลลงในตัวแปรถาวรโดยใช้ variable_set() นอกจากนี้ยังจะมีข้อความยืนยันสีเขียวเมื่อบันทึกข้อมูลสำเร็จและข้อความแสดงข้อผิดพลาดสีแดงหากมีบางอย่างผิดพลาด หากต้องการคุณสามารถสร้างฟังก์ชันการส่งได้เองได้ (ดู Form API Quickstart Guide) แต่ตอนนี้เราจะใช้ฟังก์ชันนี้

Editing the query

เราต้องเพิ่มโค้ดสองบรรทัดลงใน query function current_posts_contents ส่วนแรกจะรับค่าจาก variable_get() มาเก็บไว้ที่ $max_num อีกส่วนจะเป็นการนำไปใช้ใน query (ดูตรงที่ comment ว่า NEW LINE)

function current_posts_contents() {
  //Get today's date.
  $today = getdate();
  //Calculate midnight a week ago.
  $start_time = mktime(0, 0, 0,$today['mon'],($today['mday'] - 7), $today['year']);
  //Get all posts from one week ago to the present.
  $end_time = time();

 //NEW LINE 
 $max_num = variable_get('current_posts_max', 3);

  //Use Database API to retrieve current posts.
  $query = db_select('node', 'n')
    ->fields('n', array('nid', 'title', 'created'))
    ->condition('status', 1) //Published.
    ->condition('created', array($start_time, $end_time), 'BETWEEN')
    ->orderBy('created', 'DESC') //Most recent first.
    ->range(0, $max_num) //NEW LINE
    ->execute();
  return $query;
}

การใช้ variable_get() เราจะ save ค่า configuration setting เข้าไปที่ $max_num โดยจะมีค่า default คือ 3 จากนั้นจะเพิ่ม method range ลงไปใน query

ลองทดสอบ
เริ่มจาก disable และ enable module “current_posts” หลังจากนั้นให้ clear cache สำหรับ menu ระบบของ Drupal จะ recognize URL ใหม่ทั้งหมด
สำหรับการ clear cache สำหรับ menu ให้ไปที่ Configuration > Performance หรือ http://example.com/admin/config/development/performance จากนั้นคลิกที่ปุ่ม Clear all caches

ตอนนี้คุณสามารถทดสอบ settings form ได้แล้ว, ให้ไปที่ Configuration > Content authoring > Current posts หรือ http://example.com/admin/config/content/current_posts ให้ลองกำหนดค่าจำนวนลิ้งที่ต้องการแสดงผล


Validating the data

ส่วนนี้เกี่ยวกับ: Form API
function หลักเกี่ยวกับ: _validate(), form_set_error()

Form API จะมี validation ไว้ให้แล้วด้วย แต่เราก็ยังสามารถสร้าง validation function เองได้ด้วย
validation function จะทำงานเหมือนกับ hook โดยมีการตั้งชื่อว่า current_posts_form_validate($form, &$form_state) (อย่าสับสนกับ hook_validate () ซึ่งเป็นส่วนนึงของ main hook)

ตัวแปล $form_state จะรับค่ามาแบบ passed by reference ตามที่ค่าที่ถูกส่งมาจาก form (default key อะไรบ้างที่สามารถใช้ได้ให้ไปดูที่ drupal_build_form())

เพิ่มฟังก์ชันนี้ลงในไฟล์ current_posts.module ของคุณ:

/**
 * Implements validation from the Form API.
 * 
 * @param $form
 *   A structured array containing the elements and properties of the form.
 * @param $form_state
 *   An array that stores information about the form's current state 
 *   during processing.
 */
function current_posts_form_validate($form, &$form_state){
  $max_num = $form_state['values']['current_posts_max'];
  if (!is_numeric($max_num)){
    form_set_error('current_posts_max', t('You must enter a number for the maximum number of posts to display.'));
  }
  elseif ($max_num <= 0){
    form_set_error('current_posts_max', t('Maximum number of posts to display must be positive.'));
  }
}

การกำหนด permission สำหรับ page

hook ที่เกี่ยวข้อง: hook_permission(), hook_menu()

ลองสร้าง custom permission โดยใช้ hook_permission() permissions hook นี้สามารถกำหนดค่าได้ที่ People > Permissions (tab), หรือ http://example.com/admin/people/permissions จะเป็นการ กำหนด role ของผู้ใช้ ว่า role ไหนบ้างจะมีสิทธิ์เข้าถึงหน้าเว็บที่เราจะสร้าง

เพิ่ม function นี้ลงในไฟล์ .module:

/**
 * Implements hook_permission().
 */
function current_posts_permission() {
  return array(
    'access current_posts content' => array(
      'title' => t('Access content for the Current posts module'),
    )
  );
}

Option ทั้งหมดที่สามารถใช้งานได้สามารถดูได้ที่ hook_permission()

ลงทะเบียน URL และการตั้งชื่อ page function

เราจะต้องแก้ไข current_posts_menu() ในการสร้าง path และชื่อของหน้าใหม่ โดยต้องตั้งชื่อตาม naming conventions ใน Drupal

  • หากคุณกำลังใช้งาน Drupal hook คุณต้องระบุชื่อฟังก์ชันเสมอ “your_module_name_hookname”
  • ถ้าฟังก์ชันของคุณไม่ใช่ Drupal hook แต่เป็นอย่างอื่นที่ public ให้ตั้งชื่อโดยมี “your_module_name_” ขึ้นก่อนแล้วตามด้วยชื่อฟังก์ชันของคุณ แต่ต้องแน่ใจว่าไม่ได้ซ้ำกันกับ Drupal hook
  • ถ้าคุณกำลังสร้าง private ฟังก์ชัน ให้เริ่มต้นชื่อฟังก์ชันด้วยเครื่องหมายขีดล่าง เช่น “_your_module_name_”
/**
* Implements hook_menu().
*/
function current_posts_menu() {
    $items = array();    
    $items['current_posts'] = array(
        'title' => 'Current posts',
        'page callback' => '_current_posts_page',
        'access arguments' => array('access current_posts content'),
        'type' => MENU_NORMAL_ITEM, //Will appear in Navigation menu.
      );
    return $items;
}

Adapting the query

ในตัวอย่างนี้จะเป็นการปรับการแสดงผล ให้เหมาะสมกับการใช้งานแต่ละแบบ เช่น ถ้าเป็น block ให้จำกัดจำนวนการแสดงผลตามที่กำหนดไว้ แต่ถ้าแสดงเป็น page ให้แสดงอีกแบบ

function current_posts_contents($display){   //$display argument is new.
  //Get today's date.
  $today = getdate();
  //Calculate midnight a week ago.
  $start_time = mktime(0, 0, 0,$today['mon'],($today['mday'] - 7), $today['year']);
  //Get all posts from one week ago to the present.
  $end_time = time();
  
  $max_num = variable_get('current_posts_max', 3);
  
  //Use Database API to retrieve current posts.
  $query = db_select('node', 'n')
    ->fields('n', array('nid', 'title', 'created'))
    ->condition('status', 1) //Published.
    ->condition('created', array($start_time, $end_time), 'BETWEEN')
    ->orderBy('created', 'DESC'); //Most recent first. Query paused here.
 
   if ($display == 'block'){ 
  // Restrict the range if called with 'block' argument.
    $query->range(0, $max_num);
  } //Now proceeds to execute().
  //If called by page, query proceeds directly to execute().
  
  return $query->execute();
}

แก้ไขการใช้งานที่ current_posts_block_view

เพื่อให้ code นี้ใช้งานได้ให้ลองไปแก้ไข code ที่ function current_posts_block_view ให้เป็นแบบนี้ก่อน

$result = current_posts_contents('block');

จากนั้นลองทดสอบดู


Theming the page

เนื้อหานี้เกี่ยวกับ: Render API

ตอนนี้เราจะเขียนฟังก์ชันเพจที่เราสร้างขึ้นใน current_posts_menu()
นี่เป็นส่วนแรกของโค้ด:

/**
 * Custom page callback function, declared in current_posts_menu().
 */
function _current_posts_page() {
  $result = current_posts_contents('page');
  //Array to contain items for the page to render.
  $items = array();
  //Iterate over the resultset and format as links.
  foreach ($result as $node) {
    $items[] = array(
    'data' => l($node->title, 'node/' . $node->nid),
    ); 
  }

เราเรียกใช้ฟังก์ชัน query ด้วยอาร์กิวเมนต์ ‘page’ เพื่อดึงข้อมูลทั้งหมดที่เกี่ยวข้องออกจากฐานข้อมูล จากนั้นเราจะส่งผ่าน resultset เพื่อสร้างลิงก์สำหรับแต่ละโพสต์

Render array

ตอนนี้เราจะเจาะลึกเข้าไปในระบบธีมและจัดรูปแบบเอาต์พุตของเราเป็น render array, เมื่อใดก็ตามที่เป็นไปได้ Drupal 7 ข้อมูลที่ใช้ในการสร้างเพจจะถูกเก็บไว้เป็นอาร์เรย์ที่มีโครงสร้างจนถึงขั้นตอนการแสดงผลใน theming system ซึ่งจะช่วยให้โมดูลและโมดูลอื่น ๆ ของคุณสามารถใช้เนื้อหาเป็นข้อมูลได้นานที่สุดเท่าที่จะเป็นไปได้ในกระบวนการสร้างเพจ ดูคำอธิบายเพิ่มเติมเรื่อง Render Arrays in Drupal 7 ทั้งหมด

code ต่อจากส่วนแรก

if (empty($items)) { //No content in the last week.
    $page_array['current_posts_arguments'] = array(
      //Title serves as page subtitle
      '#title' => t('All posts from the last week'),
      '#markup' => t('No posts available.'),
    );
    return $page_array;  
  } 

Theme hook suggestion

นี่เป็นส่วนสุดท้ายของโค้ด:

else {
    $page_array['current_posts_arguments'] = array(
      '#title' => t('All posts from the last week'),
      '#items' => $items,
      //Theme hook with suggestion.  
      '#theme' => 'item_list__current_posts',
    );
    return $page_array;
  }
}

underscore 2 ตัวจะเป็นส่วนที่บอก Drupal ว่านี่คือ theme hook suggestion ซึ่งเป็นรูปแบบที่ Drupal เข้าใจ

เรื่อง theme hook suggestion มีเรื่องการทำงานของ underscore 2 ตัว เมื่อใส่เข้าไป drupal จะ… ไปอ่านต่อเอง


Adding a ‘More’ link

เนื้อหาหลักเกี่ยวกับ: Block system, Render arrays, Menu system
function หลักเกี่ยวกับ: drupal_set_title()

ล่าสุดที่เราเพิ่มเข้าไปใน module เราจะดึงสิ่งที่เราได้เรียนรู้เกี่ยวกับ block system, menu system, และ render arrays และการแก้ปัญหา minor bug ใน Drupal 7

เป็นไปได้ว่า คุณอาจไม่สามารถเข้าถึงเนื้อหานี้จาก ‘Navigation menu’ ไม่ได้ แต่ถ้ามีลิ้ง ‘More’ ที่ด้านล่างของ ‘Current posts’ block คุณอาจเข้าจากทางนี้แทน

ถ้าคุณย้อยกลับไปดู Default theme implementations reference ใน Generating block content จะเห็นว่ามี theme hook ที่ชื่อว่า theme_more_link เราจะใช้ function นี้ร่วมกับ theme hook suggestion ในการแสดง ‘More’ link.

Child render elements

เริ่มจากเปลี่ยนไป call theme_item_list ในการ render array ใน page function เราจะทำให้มันเป็น child ของ block[‘content’] ในการแสดง ‘More’ link ให้เข้าไปแก้ code ตามนี้ที่ current_posts_block_view

else {
  //Pass data through theme function.
  $block['content']['posts'] = array(
    '#theme' => 'item_list__current_posts__block',
    '#items' => $items,
  );

จากนั้นเพิ่มลิ้ง more เข้าไปที่ code

//Add a link to the page for more entries.
  $block['content']['more'] = array(
    '#theme' => 'more_link__current_posts',
    '#url' => 'current_posts',
    '#title' => t('See the full list of current posts.'),
  );
}

รวมๆ แล้วจะเป็นแบบนี้

/**
 * Implements hook_block_view().
 * 
 * Prepares the contents of the block.
 */
function current_posts_block_view($delta = '') {
  switch ($delta) {
    case 'current_posts':
      $block['subject'] = t('Current posts');
      if (user_access('access content')) {
        // Use our custom function to retrieve data.
        //$result = current_posts_contents();
        $result = current_posts_contents('block');

        // Array to contain items for the block to render.
        $items = array();
        // Iterate over the resultset and format as links.
        foreach ($result as $node) {
          $items[] = array(
            'data' => l($node->title, 'node/' . $node->nid),
          ); 
        }
       // No content in the last week.
        if (empty($items)) {
          $block['content'] = t('No posts available.');  
        } 
        else {
          // Pass data through theme function.
          /*$block['content'] = theme('item_list', array(
            'items' => $items));*/
          $block['content']['posts'] = array(
    		'#theme' => 'item_list__current_posts__block',
    		'#items' => $items,
  			);
          //Add a link to the page for more entries.
		  $block['content']['more'] = array(
		    '#theme' => 'more_link__current_posts',
		    '#url' => 'current_posts',
		    '#title' => t('See the full list of current posts.'),
		  );
        }
      }
    return $block;
  }
  
}

แท็ก

Onyx

Just a middle-aged programmer, Can do many things but not the most.