欢迎各位兄弟 发布技术文章
这里的技术是共享的
Drupal拥有极佳的弹性,允许各种类型的应用,然而在开始动手前,总是得先了解一下Drupal的核心运作原理,如此以来才会得心应手。
下面搜集了一些把玩Drupal的范例和教学,如果你觉得这里无法满足你的需求,还可以至drupal.org瞧瞧,找找看是否有对你有用的资讯。
Drupal API:提供Drupal核心函式的范例和说明http://drupaldocs.org/api
Development for Drupal:官方为开发者提供详尽的说明文件http://drupal.org/contributors-guide
大家好,我是最近才接触CMS的新手,在试着安装过joomla、e107、xoops之后,最后透过友人的推荐,决定使用drupal来架设网站,目前网站己经架设完成,但是非常阳春,我还是个初学者,其实很多东西也不是了解,市面上好像也没有drupal的书,可是我想要自己开发新的模组(或修改现成的模组)建立自己的网站,我该从何上手呢,有没有什么资料值得我先阅读的,我想从基本学起,也许只是从自己写一个功能简单的搜寻关键字的模组开始,慢慢渐入佳境,可是目前对drupal架构完全没什么概念,光是看原始码我就有点头痛了,所以想自己写一些简单基本的功能,有没有人可以给我一些意见要如何上手。谢谢
Views用来「列资料」很棒,可是如果要做一些统计、计算,虽然有些模组可以用(像是Views Cul等等),但其实都不是很好的解决方案。不过Views的API非常齐全,所以只要了解Views的写法,也是可以不需要自己写query喔!
以下范例是算出某个Views的total rows,也就是该Views的总数。
实际应用举例:
奖金猎人的「目前进行中比赛有XXX件」这样的区块。
<?php
$view = views_get_view ( 'VIEWS_NAME' );
$view -> get_total_rows = TRUE ;
$view -> execute ();
$count = $view -> total_rows ; print
'目前进行中比赛有' . $count . 'XXX件' ;
?>
如果你的状况需要设参数的话:
<?php
$view = views_get_view ( 'VIEWS_NAME' );
$view -> set_arguments ( array( 1 , 2 , 3 ) ); //参数设在这
$view -> get_total_rows = TRUE ;
$view -> execute () ;
$count = $view -> total_rows ; print
$count ;
?>
如果希望最后输出可以依照总数改变的话:
<?php
$view = views_get_view ( 'VIEWS_NAME' );
$view -> set_arguments ( array( 1 , 2 , 3 ) ); //参数设在这
$view -> get_total_rows = TRUE ;
$view -> execute () ;
$count = $view -> total_rows ;
if ( $count > 0 ) {
$output = format_plural ( $count ,
'只有1个' ,
'总共有@count个' );
print $output ;
}
?>
drupal_ foo的意思,就是drupal很多函数里面以「drupal_」为开头名称的,会以drupal开头的函数,表示很常被模组的开发者使用,也特别的重要。
几乎所有的drupal_foo函数,都会在下面这些位置找到。
include/bootstrap.inc
include/common.inc
这个单元是许多热心的贡献者,将许多drupal_ foo函数写上中文说明,每个drupal_foo的文件说明里里通常会包含:定义、描述、参数、传回值、范例、程式码...等等。若您也知道一些drupal_foo的用途,或是看到有错误的地方,欢迎直接共笔编辑,帮忙加上说明和解释。
drupal_add_js($data = NULL, $type = 'module', $scope = 'header', $defer = FALSE, $cache = TRUE)
includes/common.inc,第1600行之后
加入一个JavaScript 的档案、设定、或是程式码到页面中
根据不同的参数,会有不同的结果产生。一般,它会加入JavaScript 的"档案连结"或是"直接贴入程式码"到网页里。以下列出不同的结果:
drupal_addjs('js1.js', 'module');
drupal_addjs('js2.js', 'core');
$data (选择性) 如果用到这个参数,它的内容的和后面的$type 参数有关:
$type (选择性)一个表示要放到网页里的JavaScript是哪一种类型。可以是'core', 'module', 'theme', 'inline'和'setting'。你也可以自订一个名称,这时$data的内容会被视为一个JavaScript档案(*.js)路径。预设值是'module' 排列顺序: 'core' -> 'module' -> 'theme' -> 'setting' -> 'inline' -> [自订的名称]
$scope (选择性) JavaScript 程式要放置的位置,预设可以是'header'(开头) and 'footer'(节尾). 如果你所用的版型有定义其它的位置,也可以使用它们放到这个参数中。
$defer (选择性) 如果为TRUE,在标签中会加入defer 属性,预设为FALSE。这个参数在$type 是'setting' 没有作用。
$cache (选择性) 如果设定为FALSE, JavaScript 档案会在每一次呼叫页面的时候重新读取,预设值是TRUE,这个参数只对$type 表示引入的是一个JavaScript 档案有用
如果第一个参数为NULL,就会传回和$scope 同样参数JavaScript 阵列回来
1. 引入档案
drupal_add_js('test1.js', 'module');
drupal_add_js('test2.js', 'theme');
drupal_add_js('test3.js', 'core');
<script type="text/javascript" src="/drupal/test3.js"></script>
<script type="text/javascript" src="/drupal/test1.js"></script>
<script type="text/javascript" src="/drupal/test2.js"></script>
2. 加入程式码& 加入设定
drupal_add_js('alert("var1 = "+Drupal.settings.var1);', 'inline');
drupal_add_js(array('var1' => 123, 'var2' => 456), 'setting');
<?php
function drupal_add_js ( $data = NULL , $type = 'module' , $scope = 'header' , $defer = FALSE , $cache = TRUE ) {
if (! is_null ( $data )) {
_drupal_add_js ( 'misc /jquery.js' , 'core' , 'header' , FALSE , $cache );
_drupal_add_js ( 'misc/drupal.js' , 'core' , 'header' , FALSE , $cache );
}
return _drupal_add_js ( $data , $type , $scope , $defer , $cache );
}
?>
drupal_get_path($type, $name)
includes/common.inc
可用此函数取得模组、版型或版型引擎(theme engine)的路径
$type:要寻找的种类(如: theme, theme_engine, module)
$name:寻找东西的名字
要找东西的路径(网站相对路径,不是伺服器的系统路径)
当你开发的模组里面有图片,或是由其它需要引入的JavaScript的时候,那就很有用了。因为你不能确定你的模组会被放在哪里。
有可能在modules/下,也可能在sites/all/modules/下面。这时候就需要用它来 帮你找出模组的路径,好引入它资料夹里面的档案。
如:
$my_module_path = druapl_get_path('module', 'my_module');
$my_theme_path = druapl_get_path('theme', 'my_theme');
$phptemplate_engine_path = druapl_get_path('theme_engine', 'phptemplate');
$my_module_path = base_path() . drupal_get_path("module", "my_module");
//如果网站是在drupal资料夹下, base_path()会传回"/drupal"
不过如果使用drupal_add_js() 函数,因为它本身就会加上网站本身的路径,所以就不需要加上base_path()了。
<?php
function drupal_get_path ( $type , $name ) {
return dirname ( drupal_get_filename ( $type , $name ));
}
?>
drupal_set_message($message = NULL, $type = 'status')
includes/bootstrap.inc
定义一组讯息,以反映刚执行命令的状态
$message:要显示的讯息
$type:类别,可以是
所有已经定义的讯息
例如要告诉使用者”你己经成功注册了”:
<?php
drupal_set_message ( t ( 'Created a new user account. No e-mail has been sent.' ));
?>
又例如要显示一个错误:
<?php
drupal_set_message ( t ( 'Invalid password.' ) , 'error' );
?>
注:第一个参数可以为html
注:t()函数指可翻译的
<?php
function drupal_set_message ( $message = NULL , $type = 'status' ) {
if ( $message ) {
if (!isset( $_SESSION [ 'messages' ])) {
$_SESSION [ 'messages' ] = array( );
} if (!isset(
$_SESSION [ 'messages' ][ $type ])) {
$_SESSION [ 'messages' ][ $type ] = array();
}
$_SESSION [ 'messages' ][ $type ] [] = $message ;
}
//如果发生资料库连接错误,则讯息不会被记录
return isset( $_SESSION [ 'messages' ]) ? $_SESSION [ 'messages' ] : NULL ;
}
?>
附加档案 | 大小 |
---|---|
drupal_set_message_1.JPG | 6.59 KB |
drupal_set_title($title = NULL)
includes/path.inc,第187行之后
设定页面的标题文字(不是网站标题)
$title 选择性参数,如果为NULL 则不会改变概有的标题内容。
没有传回值,但会改变当下页面的标题。
drupal_set_title("新标题");
<?php
function drupal_set_title ( $title = NULL ) {
static $stored_title ; if (isset(
$title )) {
$stored_title = $title ;
}
return $stored_title ;
}
?>
看名字就知道这些函数是用来处理和模组有关的事情。
如果今天开发的模组需要和其它的模组互动,或是"相依"在其它的模组之上,或是要触发其它模组的hook 。
那么这个部分的函数就要花时间去研究。
module_exists($module)
includes/module.inc
判断某个模组是否存在(并已启用)
$module: 模组的名字(不要带上.module 的副档名).
如果该名称的模组已安装且正被启用,则传回TRUE 。
<?php
function module_exists ( $module ) {
$list = module_list ();
return array_key_exists ( $module , $list );
}
?>
之前在怎样styling user login block ?讨论内回应的内容上有一些错误
回应的内容已经没办法编辑
所以修改后另外PO一篇
在版型资料夹下的template.php 内加上
function theme_user_login_block($form){
//自订登入画面block
return _phptemplate_callback('login_block_form', array('form' => $form));
}
然后在版型资料夹下增加一个login_block_form.tpl.php的档案作为样板档
直接在样板档里排成我想要的样子
<div id="custom-login-bar">
<div class="form-label">帐号</div> <?php print drupal_render ( $form [ 'name' ]) ?>
<div class="form- label">密码</div> <?php print drupal_render ( $form [ 'pass' ]) ?>
<div class="form-item"> <?php print drupal_render ( $form [ 'submit' ]) ?> <?php print drupal_render ( $form [ 'form_id' ]) ?> </div> </div>
drupal的表单必须把$form['form_id'] & $form['form_token']必须print出来表单才有作用
不过user_login_block似乎是例外表单结构内原本就没有$form['form_token']
所以这边只render $form['form_id']
另外搭配imagebutton 还可以把原本的submit按钮改成图片
<?php
$form [ 'submit' ][ '#type' ] = 'imagebutton' ;
$form [ 'submit' ][ '#image' ] = 'xxx/xxx.jpg' ;
print drupal_render ( $form [ ' submit' ]);
?>
注册新帐号&忘记密码的连结也都是定义在$form里面
在样板档里print_r($form)看一下就知道了
然后再加入css (这边只是简单示意一下详细的css 请自己再作设定)
<style>
#custom-login-bar .form-item label {
display:none;
} #custom-login-bar .form-item, #custom-login-bar .form-label { float:left; } </style >
要直接加在样板档的档案内
或版型的style.css档案内都可以
如果是只有一个样板档用到的style
个人是喜欢直接放在样板档里
在样板档里就像一般的网页制作一样
随你怎么排列
只要知道element的名字
想在哪render都可以也不用照$form里的顺序
可以改出单用css或hook_form_alter没办法达成的排版
虽然在$form['name']['title']可以把标题改成空字串而不显示标题
但是不建议这样做
因为表单的错误讯息会没有栏位的名称
例如请填写密码栏位的错误讯息会变成请填写栏位
如果表单的栏位有5个栏位没填就会变成
请填写栏位
请填写栏位
请填写栏位
请填写栏位
请填写栏位
根本没办法辨识是哪个栏位未输入
所以#title还是留着比较好
不想显示标题的时候建议还是用css把label隐 藏掉
user也是模组之一,管理使用者的登入登出,或是权限的认证。
总之想作好一个有管理使用者的模组,这个部分的函数就必须有所了解。
这些函数不需要特别去引用,直接呼叫函数的名称就可以了。
user_access($string, $account = NULL)
modules/user/user.module,第351行开始
判断使用者是否具有某种权限。
所有的权限判断,都应该要例用这个函数。一来是让所有的程式有一致性,而且可以保证SuperUser能拥有所有的权限。
$string:要判定的权限名字,如"administer nodes"
$account (预设):一个User物件。在要判断的使用者并不是「目前登入的那个人」时使用。
如果判定是「有这个权限」,则传回布林值TRUE。
检查目前使用者是否有「存取管理页面(administer comments)」的权限
$has_permission = user_access("administer comments"); // 第二个参数省略
<?php
function user_access ( $string , $account = NULL ) {
global $user ;
static $perm = array(); if (
is_null ( $account )) {
$account = $user ;
}
//超级使用者SuperUser拥有所有的权限
if ( $account -> uid == 1 ) {
return TRUE ;
}
//为了灭少到资料库作查询连线数,会把作查过的资料放入静态变数中
if (!isset( $perm [ $account -> uid ])) {
$result = db_query ( "SELECT DISTINCT(p.perm) FROM {role} r INNER JOIN {permission} p ON p.rid = r.rid WHERE r.rid IN ( %s)" , implode ( ',' , array_keys ( $account -> roles )));
$perm [ $account -> uid ] = '' ;
while ( $row = db_fetch_object ( $result )) {
$perm [ $account -> uid ] .= "$row->perm, " ;
}
} if (isset(
$perm [ $account -> uid ])) {
return strpos ( $perm [ $account -> uid ], "$ string, " ) !== FALSE ;
} return
FALSE ;
}
?>
user_load($array = array())
modules/user/user.module
取得一个user 物件
$array 一个关连式阵列,让这个函数可以找得到你想找的使用者资料,可能是帐号名称或是e-mail位址。
如果找到的话,传回一个"完整"的user 物件,否则传回FALSE
其实Drupal 的登入就是靠它来检查,输入帐号密码,来作搜寻有没有符合的条件。
$account = user_load(array('name' => '纳格髓', 'pass' => 'very secret', 'status' => 1))
如果有找到,就表示帐号密码正确,$account 就会传回一个user 物件。再下两行
global $user;
$user = $account; //把找到的user物件"存"起来
附带一提,登出的语法也很简单。
global $user;
$user = drupal_anonymous_user();
<?php
function user_load ( $array = array()) {
// Dynamically compose a SQL query:
$query = array();
$params = array(); foreach (
$array as $key => $value ) {
if ( $key == 'uid' || $key == 'status' ) {
$query [] = "$key = %d" ;
$params [] = $value ;
}
else if ( $key == 'pass ' ) {
$query [] = "pass = '%s'" ;
$params [] = md5 ( $value );
}
else {
$query []= "LOWER($key) = LOWER('%s') " ;
$params [] = $value ;
}
}
$result = db_query ( 'SELECT * FROM {users} u WHERE ' . implode ( ' AND ' , $query ), $params ); if (
db_num_rows ( $result )) {
$user = db_fetch_object ( $result );
$user = drupal_unpack ( $user );
$user -> roles = array();
if ( $user -> uid ) {
$user -> roles [ DRUPAL_AUTHENTICATED_RID ] = 'authenticated user ' ;
}
else {
$user -> roles [ DRUPAL_ANONYMOUS_RID ] = 'anonymous user' ;
}
$result = db_query ( 'SELECT r.rid, r.name FROM {role} r INNER JOIN {users_roles} ur ON ur.rid = r.rid WHERE ur.uid = %d' , $user -> uid );
while ( $role = db_fetch_object ( $result )) {
$user -> roles [ $role -> rid ] = $role -> name ;
}
user_module_invoke ( 'load' , $array , $user );
}
else {
$user = FALSE ;
} return
$user ;
}
?>
user_roles($membersonly = 0, $permission = 0)
modules/user/user.module,第1762行开始
传回符合条件的「群组(身分)」阵列
(在Drupal里,群组(身分)被叫作"role")
$membersonly:如果设为TRUE ,则传回的时候会排除「访客群组(身分)」
$permission:一个字串,如果加到它,则只传回包含这个权限的群组(身分)回来
一个关连阵列,键是该群组在资料库的ID,值是它的名字。
列出现在所有的群组: (假设我事前建立一个叫作power user 的群组)
print_r(user_roles());
Array
(
[1] => anonymous user
[2] => authenticated user
[3] => power user
)
<?php
function user_roles ( $membersonly = 0 , $permission = 0 ) {
$roles = array(); if (
$permission ) {
$result = db_query ( "SELECT r.* FROM {role} r INNER JOIN {permission} p ON r.rid = p.rid WHERE p.perm LIKE '%%%s%%' ORDER BY r.name" , $permission );
}
else {
$result = db_query ( 'SELECT * FROM {role} ORDER BY name' );
}
while ( $role = db_fetch_object ( $result )) {
if (! $membersonly || ( $membersonly && $role -> rid != DRUPAL_ANONYMOUS_RID )) {
$roles [ $role -> rid ] = $ role -> name ;
}
}
return $roles ;
}
?>
user_save($account, $array = array(), $category = 'account')
modules/user/user.module,第106行开始
更新一个使用者帐号内容,或是建立一个新的帐号
$account一个user物件,如果$user->uid(帐号ID)为空,则建立新的帐号,如果有东西的话,就更新这个uid的帐号资料
$array一个带有帐号资讯的阵列。例如: array('name' => 'My name');值为NULL的话,就表示把这个栏位清空。
(注:在更新中,没有列在这个阵列里的栏位会保持不变)
$category (选择性参数)用来作注记的参数。在这个函数中并没有用到,主要是用来当参数传给相关的hook函数。
第一个参数要求的是一个user物件,但是其实只用到里面的uid属性。
所以要新增一个帐号的时候,并不用费心去建立一个"空的user物件",给它一个'' (空字串)就好了。
如:
$roles = array(
'3' => '角色名称',
'4' => '角色名称',
'5' => '角色名称',
);
user_save('', array("name" => '纳格髓', "pass" => 'unknow', "status" => 1, 'roles' => $roles , 'profile_tel' => '0800 123456'));;
注:
在Drupal的群组(roles)中,1表示访客,2表示已注册帐号。这两个资讯在记录的时候会"自动跳过"。因为有没有帐号就已经足以作为这两者的区分。所以就算你在roles放入1或2也不会被记入资料库中。
(原来文章的例子是直接打array(3, 4, 5)来加入编号3, 4, 5的角色,这种写法是错误的)
<?php
function user_save ( $account , $array = array(), $category = 'account' ) {
// Dynamically compose a SQL query:
$user_fields = user_fields ();
if ( $account -> uid ) {
user_module_invoke ( 'update' , $array , $account , $category );
$data = unserialize ( db_result ( db_query ( 'SELECT data FROM {users} WHERE uid = %d' , $account -> uid )));
foreach ( $array as $key => $value ) {
if ( $key == 'pass' && !empty( $value )) {
$query .= "$key = '%s', " ;
$v [] = md5 ( $ value );
}
else if (( substr ( $key , 0 , 4 ) !== 'auth' ) && ( $key != 'pass' )) {
if ( in_array ( $key , $user_fields )) {
// Save standard fields
$query .= "$key = '%s', " ;
$v [] = $value ;
}
else if ( $key != 'roles' ) {
// Roles is a special case: it used below .
if ( $value === NULL ) {
unset( $data [ $key ]);
}
else {
$data [ $key ] = $value ;
}
}
}
}
$query .= "data = '%s' " ;
$v [] = serialize ( $data );
db_query ( "UPDATE {users} SET $query WHERE uid = %d" , array_merge ( $v , array( $account -> uid )));
// Reload user roles if provided
if ( is_array ( $array [ 'roles' ])) {
db_query ( 'DELETE FROM {users_roles} WHERE uid = %d' , $account -> uid ); foreach (
array_keys ( $array [ 'roles' ]) as $rid ) {
if (! in_array ( $rid , array( DRUPAL_ANONYMOUS_RID , DRUPAL_AUTHENTICATED_RID ))) {
db_query ( 'INSERT INTO {users_roles} (uid, rid) VALUES (%d, %d)' , $account -> uid , $rid );
}
}
}
// Delete a blocked user's sessions to kick them if they are online.
if (isset( $array [ 'status' ]) && $array [ 'status' ] == 0 ) {
sess_destroy_uid ( $account -> uid );
}
// If the password changed, delete all open sessions and recreate
// the current one.
if (isset( $array [ 'pass' ])) {
sess_destroy_uid ( $account -> uid );
sess_regenerate ();
}
// Refresh user object
$user = user_load (array( 'uid' => $account -> uid ));
user_module_invoke ( 'after_update' , $array , $user , $category );
}
else {
$ array [ 'uid' ] = db_next_id ( '{users}_uid' ); if (!isset(
$array [ 'created' ])) { // Allow 'created' to be set by hook_auth
$array [ 'created' ] = time ();
}
// Note, we wait with saving the data column to prevent module-handled
// fields from being saved there. We cannot invoke hook_user('insert') here
// because we don't have a fully initialized user object yet.
foreach ( $array as $key => $value ) {
switch ( $key ) {
case 'pass' :
$fields [] = $key ;
$values [] = md5 ( $value );
$s [] = "'%s'" ;
break;
case 'uid' : case 'mode' : case 'sort' :
case 'threshold' : case 'created' : case 'access' :
case 'login' : case 'status ' :
$fields [] = $key ;
$values [] = $value ;
$s [] = "%d" ;
break;
default:
if ( substr ( $key , 0 , 4 ) !== 'auth' && in_array ( $key , $user_fields )) {
$fields [] = $key ;
$values [] = $value ;
$s [] = "'%s'" ;
}
break;
}
}
db_query ( 'INSERT INTO {users } (' . implode ( ', ' , $fields ) . ') VALUES (' . implode ( ', ' , $s ) . ')' , $values );
// Build the initial user object.
$user = user_load (array( 'uid' => $array [ 'uid' ]));
user_module_invoke ( 'insert' , $array , $user , $category );
// Build and save the serialized data field now
$data = array() ;
foreach ( $array as $key => $value ) {
if (( substr ( $key , 0 , 4 ) !== 'auth' ) && ( $key != 'roles' ) && (! in_array ( $key , $user_fields )) && ( $value !== NULL )) {
$data [ $key ] = $value ;
}
}
db_query ( "UPDATE {users} SET data = '%s' WHERE uid = %d" , serialize ( $data ), $user -> uid );
// Save user roles (delete just to be safe).
if ( is_array ( $array [ 'roles' ])) {
db_query ( 'DELETE FROM {users_roles} WHERE uid = %d' , $array [ 'uid' ]);
foreach ( array_keys ( $array [ 'roles' ]) as $rid ) {
if (! in_array ( $rid , array( DRUPAL_ANONYMOUS_RID , DRUPAL_AUTHENTICATED_RID ))) {
db_query ( ' INSERT INTO {users_roles} (uid, rid) VALUES (%d, %d)' , $array [ 'uid' ], $rid );
}
}
}
// Build the finished user object.
$user = user_load (array( 'uid' => $array [ 'uid' ]));
}
// Save distributed authentication mappings
$authmaps = array();
foreach ( $array as $key => $value ) {
if ( substr ( $key , 0 , 4 ) == 'auth' ) {
$authmaps [ $key ] = $value ;
}
}
if ( sizeof ( $authmaps ) > 0 ) {
user_set_authmaps ( $user , $authmaps );
} return
$user ;
}
?>
在写模组的同时,发现有些资讯是记在全域变数里面,在遍寻的函数找不到之后才发现。
所以把一些常用的全域变数整理到这里,提供给人查询。
所有用变数使用之前,必须要先「global $变数;」才能使用...
使用者物件
既使没有人登入,也会有一个"访客"况态的物件。要知道这个人有那些资讯,就必须要使用这个物件。
网站根目录网址
当使用「clean_url(简絜网址)」的时候,有时候会需要用到绝对路径来作网页连结。
不确定网址的开头是什么的时候,像是不确定自己的模组会被装到哪个站里面,可以用它来 查询网站根目录的网址。
如果我用把drupal装在"drupal_test"的目录下,在本机测试时,这个变数可能会是「http://localhost/drupal_test」。
网站根目录网址(相对路径)
当使用「clean_url(简絜网址)」的时候,有时候会需要用到绝对路径来作网页连结。
不确定网址的开头是什么的时候,像是不确定自己的模组会被装到哪个站里面,可以用它来 查询网站根目录的网址。
如果我用把drupal装在"drupal_test"的目录下,在本机测试时,这个变数会是「drupal_test」。
慢慢增加中....
模组系统是drupal很重要的运作方式,drupal依靠着少少的核心程式,便能让模组能做到任何事情。
drupal只有21个档案在include里头,每次必会loading进来,其他的全都放在modules。
也就是说,除了那几只档案以外,全部的东西都把他当成module在写。诸如CMS最基本的功能,文章管理、评论、讨论区、分类...等的功能,全部都写在module里,include里头所提供的是各种api,档案处理函式、资料库存取、表单生成...等等,这样的分层,module便可以专心的开发各种功能。
当然,这样的架构不够令人注目。有许多web app架构,对于模组(module)、插件(plug-in)...等的运作,通常是让他们各自为政,自己干自己的事情。多是用核心提供的object和function,加上module自己额外的code,达到module要做到的额外功能。但是drupal的核心运作却不是如此。
drupal处理使用的程式为modules/user.module
。如果今天想要在看使用者资料的同时,也想看看所有使用者过去发表文章的list,那该怎么写呢?
直接一点,更改user.modue,在显示时,顺便去文章资料库抓相关的资料?然而这样却不是一个好方式,今天任何想要对使用者增加新功能的时候,都得trace一次user.module的code,看懂他在干啥,然后把新的code安插在合适的地方.. .最后可能增加user.module的复杂度,增加维护那支module的难度,共同开发时,更是一个危险的方式。
第二种方式,重写一个新的浏览页面,重新写一个SELECT的语句,让SELECT的时候除了使用者资讯,也把文章资料一起抓出来,然后显示到不同的页面。但是这样很浪费,明明跟user.module重复的功能达到一半以上,那是不是之后要新增功能,都得重写一次呢?
上面两种方式在drupal中也都可以达成,然而熟悉Drupal的人却不会如此。Drupal的开发者很聪明,他的模组系统(module system)考虑到了模组再利用这一点,每个模组都视为可以再利用的资源,只要写module的人想写,透过模组系统便可以跟所有的module交互作用。
第三种方式以modules/user.module
为例,他即是处理包含新增、修改、删除、注册、登入....等所有与使用者相关的功能。在进行每个重要的功能时,user.module都会呼叫一个函式去扫所有的module,看看是否有其他的module要在user.module进行此动作时,也进行一些其他想要做的事情,这就是drupal重要的Hook System。example: 在drupal user.module里头可以找到如下的程式码
function user_view($uid = 0) { // ... skip // moudle_invoke扫描所有的module // 看看有没有modulename_user这个function // 有个话就看'view'这个功能的部份要加上什么 foreach (module_list() as $module) { if ($data = module_invoke($module, 'user', 'view', '', $account)) { // do something... } } // ... skip }
所以,第三种方式,不用重写,也不用改到user.module,只要自己新增module和写一个function,便可以轻松让浏览使用者资讯时,加上过往文章。example: 新增自己的module,与hook system紧密运作新增sample.module
function sample_user($type, &$edit, &$user, $category = NULL) { if ($type == 'view') { return /*过往文章,型别为一阵列*/; } }
这就是drupal把众多主要功能都写成module的原因,让所有模组之间都可以交互利用,或是写给别人利用,或是利用别人的module,像积木一样推砌成想要的功能,却又不浪费资源。
参考资源:
详细的用法在:http://drupaldocs.org/api/head/function/hook_user Module developer's guide:http://drupal.org/node/508
原文连结:http://www.500959.com/node/510
基本上,views和cck是大家都建议用的两个drupal模组,因为这两个模组太强大了,一个可以自由定制栏位,一个可以自由过滤资料用以显示, drupal的许多其它模组都是基于这两个的。正因其强大,所以也庞大,庞大耗费资源,相对来说,配置起来也较复杂。对于一些把drupal做为个人博客来用的朋友来说,通常不想使用这两个模组,但drupal预设就只有一种排列文章的方式,按时间发表顺序,而bloger们可能就需要有多一些的排列显示方式。
这个时候可以用查询资料库再配合drupal的一些核心函数来达到简单过滤显示的目的。举个例子吧,用习惯了国内cms的朋友,都喜欢在首页上显示一些区块,什么最新文章、最新推荐、最新评论、热门文章……
咱们先创建一个page节点,标题就看你的爱好了,随便取吧。内容呢,就随便写段代码吧:
<?php
echo '嘿罗,世界' ; //反正一般程式测试都这个套路。
?>
现在进入第二步,到“网站资讯(admin/settings/site-information)”中,拉到最后面,把默认首页设置为咱们刚才创建的页面路径:index.html,好,现在打开网站,发现默认首页就剩“嘿罗世界”了,忽悠人啊这不是。别急,接下来做第三步。
第三步,开始往里边添加内容了。我想添加个最新blog文章的列表。drupal区块里有个默认的最新blog文章,到区块中,查看最新blog文章的区块连结是这样的:admin/build/block/configure/blog/0,注意最后两层(blog/0) ,这很重要。这表示这个区块是由blog.module生成的第0个区块(从0开始计数的)。现在我们编辑那篇文章,把里边的“嘿罗世界”可以删除了,放这段代码进去:
<?php
$block = module_invoke ( 'blog' , 'block' , 'view' , 0 );
echo $block [ 'subject' ]; //显示区块的标题
echo $block [ 'content' ]; //显示区块的主内容区。
?>
我想要显示其它类型,比如story的最新文章呢?因为默认没有提供区块,所以一般的做法呢是用views来过滤出来,但文章一开头就说了,咱们不用views。这就进入另一个重点部分,读取资料库来显示。编辑文章,在后面插入这段代码:
<?php
echo '<h2>最新story</h2>' ; //标题随意
$result = db_query_range ( "SELECT n.nid, n.title FROM {node} n WHERE n.type = 'story' ORDER BY n .created DESC" , 10 );
while ( $test = db_fetch_object ( $result )) {
echo l ( $test -> title , 'node/' . $test -> nid ). '<br>' ;
};
? >
保存,现在看看效果,最新story文章列表是不是出现了呢?而热门内容呢,statistics模组也提供了一个按点击排序的文章列表,我们可以直接用上面插入区块的办法把它放到页面里。现在我们页面里有最新文章、最新推荐、最新日志、热门内容、最新评论等区块列表了,看起来像那么回事了,接下来的事就是排版了。这个就取决于个人的审美观了,熟悉css的就用css,不熟悉的就直接用表格套上去就行了。
可以收工了,可我还想有个more,你看大多数网站的列表下面,不都有个more,点击进去,显示更多的内容,并且这里边的内容还是可以分页的。好,现在我们来解决这个问题。还是举例子吧,也想不到其它更好的手段了。先在首页的清单下面加个more连结:
<?php
echo '<h2>最新story</h2>' ;
$result = db_query_range ( "SELECT n.nid, n.title FROM {node} n WHERE n.type = 'story' ORDER BY n.created DESC" , 10 );
while ( $test = db_fetch_object ( $result )) {
echo l ( $test -> title , 'node/' . $test -> nid ). '<br>' ;
};
echo '<a href ="/story/all">更多</a>' ;
?>
<?php
echo '<h2>最新story</h2>' ;
$result = db_query_range ( "SELECT n.nid, n.title FROM {node} n WHERE n.type = 'story' ORDER BY n.created DESC" , 10 );
if ( db_num_rows ( $result ) > 0 ) { //加入判断,查询结果大于0才显示;
while ( $test = db_fetch_object ( $result )) {
echo l ( $test -> title , 'node /' . $test -> nid ). '<br>' ;
};
}else { //否则就显示
echo '没有文章' ;
} if(
db_num_rows ( $result ) > 10 ){ //如果大于10篇,就显示更多连结。
echo '<a href="/story/all">更多</a>' ;
}
?>
<?php
echo '<h2>新闻列表</h2>' ;
$result = pager_query ( "SELECT n.nid, n.title FROM {node} n WHERE n.type = 'story' ORDER BY n.created DESC" , 15 );
if ( db_num_rows ( $result ) > 0 ) { //加入判断,查询结果大于0才显示;
while ( $test = db_fetch_object ( $result )) {
echo l ( $test -> title , 'node /' . $test -> nid ). '<br>' ;
};
}else { //否则就显示
echo '没有文章' ;
}
echo '<p>' . theme ( 'pager' , NULL , 15 ) . '</p>' ;
?>
这个时候可能又会觉得一个清单就显示个标题,未免太单调了,我还想显示点作者啊,发表时间啊,评论数目啊,点击数量啊等等。好吧,咱们来完成这个需求。
<?php
echo '<h2>新闻列表</h2>' ;
$result = pager_query ( "SELECT n.nid, n.title,n.comment, n.created,u.uid, u.name,s.totalcount FROM {node} n INNER JOIN {node_counter} s ON n.nid = s.nid INNER JOIN {users} u ON n.uid = u.uid WHERE n.type = 'story' ORDER BY n.created DESC" , 15 );
if ( db_num_rows ( $result ) > 0 ) { //加入判断,查询结果大于0才显示;
while ( $test = db_fetch_object ( $result )) {
echo '标题:' . l ( $test -> title , 'node/' . $test -> nid ).
' 作者:' . l ( $test -> name , 'user/' . $test -> uid ).
' 评论:' . $test -> comment .
' 点击:' . $test -> totalcount .
' 发表时间:' . format_date ( $test -> created ). '<br>' ;
};
} else { //否则就显示
echo '没有文章' ;
}
echo '<p>' . theme ( 'pager' , NULL , 15 ). '</p>' ;
?>
细心的你肯定发现了,这里使用了多表查询,因为节点的点击量是存放在另外一个表里的,而作者的资讯又存在users表里。老套路,提交保存,现在看看列表,是不是多了作者、评论这些资讯。只是排版未免太难看了。那就用css自己调整吧,可我又不想使用css,而且这种清单式的显示,使用表格更有优势,方便又快捷。那就用列表吧。这样改一改:
<?php
echo '<h2>新闻列表</h2>' ;
$header = array( //这里增加了,先定义个表格头部
array( 'data' => '标题' ),
array( 'data' => '作者' ),
array( 'data' => '评论' ),
array( 'data' => '点击' ),
array( 'data' => '发表时间' )
);
$tablesort = tablesort_sql ( $header );
$result = pager_query ( "SELECT n.nid, n.title,n.comment, n.created,u.uid, u.name,s.totalcount FROM {node} n INNER JOIN {node_counter} s ON n.nid = s.nid INNER JOIN {users} u ON n.uid = u.uid WHERE n.type = 'story' ORDER BY n.created DESC" . $tablesort , 15 );
if ( db_num_rows ( $result ) > 0 ) { //加入判断,查询结果大于0才显示;
while ( $test = db_fetch_object ( $result )) {
//这儿不直接列印,而是定义成一个阵列了。
$rows [] = array( 'data' =>
array(
l ( $test -> title , 'node/' . $test -> nid ),
l ( $test -> name , 'user/' . $test - > uid ),
$test -> comment ,
$test -> totalcount ,
format_date ( $test -> created ),
),
);
};
}else { //否则就显示
echo '没有文章' ;
}
echo theme ( ' table' , $header , $rows ); //这儿列印出表格。
echo '<p>' . theme ( 'pager' , NULL , 15 ). '</p>' ;
?>
提交保存,现在看一看页面,是不是都在一个表格里,排版都省了,整整齐齐。这样就差不多了吧,又有了区块,又有了列表页。不过,也许你突然又觉得全是标题列表有也点单调,还想看看摘要显示是什么效果。咱们就来把标题清单改为摘要模式,这个更简单一点,直接使用node_view和node_load,这两个函数,只要你告诉它节点nid,它就能载入节点的摘要或全文视图了。省得麻烦,就直接还是用这个新闻列表页来做试验吧。编辑节点,放入这段代码:
<?php
echo '<h2>新闻列表</h2>' ;
$result = pager_query ( "SELECT n.nid FROM {node} n INNER JOIN {node_counter} s ON n.nid = s.nid INNER JOIN {users} u ON n.uid = u.uid WHERE n.type = 'story' ORDER BY n.created DESC" , 15 );
if ( db_num_rows ( $result ) > 0 ) { //加入判断,查询结果大于0才显示;
while ( $test = db_fetch_object ( $result )) {
$output .= node_view ( node_load (array( 'nid' => $test -> nid )), 1 ); //这儿的1或0是全文或摘要。
};
}else { //否则就显示
echo '没有文章' ;
}
echo '<p>' . theme ( 'pager' , NULL , 15 ). '</p>' ;
?>
注意几个变化,首先要查询资料库的时候,只需要节点nid就行了。其次是在阵列回圈时,直接用node_view和node_load来显示出文章。这儿就用不着排版了,全部是按照你在node.tpl.php中定义的样式来显示。
现在终于可以结束了,好像在节点组织显示方面,也没其它的需求了。上面的内容我是一边试验一边写下来的,在5.x版本上,应该不会有错误。本来准备截图,嫌上传麻烦,就没截了。如果测试过程中有什么问题,请提出来。显示需求不是很多,可以使用这种查询资料库的方式,如果有许多自订的显示需求,建议还是用views,毕竟它内置了缓存,而且和其它模组的互动也更好,当然,用起来也更方便。希望这篇文章对于喜欢drupal,但又不想使用views的朋友有一定的帮助。
Drupal 有很多种投票/评比模组,有的简单、有的复杂,各有巧妙不同与适用时机。该如何选择?
之前在做nbalive.tw时,花满多时间在测试拥有类似功能的不同模组,例如投票、聊天、相簿、讨论区,而这篇Lullabot - A Review of Node Review Modules令我有相见恨晚的感觉,因为他集合了Drupal的6个投票模组,做了简单的介绍与图示,方便使用者做初步判定。
简单介绍一下:Node Vote -简单的1-10分数投票。NodeReview -可设定多种评比项目(要是能画个五力分析图出来就更好了)。Simple Vote -更简单的五星级投票。userreview -类似Amazon.com的书评功能,投票并写心得,心得是一个独立的内容类型。Vote up/down Package -类似digg.com,好或不好两种选择。Voting -也是五星级投票,可显示平均票数和我的投票。
嗨!最近研究的是form的美化,延续上篇[ 问题 ],
解决之后真开心,分享一下我的美化的code(用CSS)
然后还是有几个问题:
(1)想弄更漂亮,想在input加上onFocus之类的,不知怎么加
(2)像我这样把CSS放在tpl.php,会不会不好呢?
谢谢!
先看效果, 表单
这是按钮
下面是我的原始码:
template.php
<?php
function phptemplate_lifestyle_node_form ( $form ) {
global $user ;
$vars = array( 'user' => $user , 'form' => $form );
return _phptemplate_callback ( 'lifestyle_form' , $vars );
}
?>
lifestyle_form.tpl.php
<?php
//drupal_set_message('<pre>' . print_r($form, true) . '</pre>');
drupal_set_title ( '新增宠物店家资料' );
?>
<style>
#content-area . information{
background:#F8FFEF;
width: 585px;
padding:10px;
margin:10px auto;
border:1px dotted #ccc;
}
#content-area .form-item{
width: 450px; /*width of right column containing the inputs */
clear: left;
margin: 0 auto;
padding: 5px 0 8px 0;
padding-left: 155px; /*width of left column containing the label elements*/
border-top: 1px dashed gray;
height: 1%;
}
#content-area label{
font-weight: bold;
float: left;
line-height:25px;
height:25px;
margin-left: -155px; /*width of left column*/
width: 150px; /*width of labels . Should be smaller than left column (155px) to create some right margin*/
padding-left:10px;
}
#content-area input, #content-area textarea, #content-area select{
border:1px solid #ccc;
}
#content-area input:focus, #content-area textarea:focus{ /*for Firefox*/
background-color: #D5E7BD;
}
#content-area input{
width: 300px;
height:25px;
font-size:22px;
}
#content-area select{
height:25px;
font-size:17px;
}
#content-area textarea {
width: 440px;
}
#edit-body{ /*主要内文的高度预设好高哦!*/
height:100px;
}
#content-area .form-submit {
font-size: 12px;
background-color: #333333;
color: #FFFFFF;
width:100px;
height:30px;
position:relative;
left:250px;
top:20px;
}
</style>
<div class="information">
谢谢你提供店家资料哦!<br />
每次填写资料,我们都会怎样怎样怎样怎样怎样怎样怎样怎样怎样怎样怎样怎样怎样怎样怎样怎样怎样怎样怎样怎样怎样怎样怎样怎样怎样怎样怎样怎样怎样怎样怎样怎样怎样怎样怎样怎样怎样怎样怎样怎样怎样怎样,总之这个区块会放一些填表单的说明。
</div> <?php //把种类拉到最上面 ?> <?php print drupal_render ( $form [ 'taxonomy' ][ '4' ]); //type ?> <?php print drupal_render ( $form [ 'taxonomy' ][ '3' ]); //location ?> <?php //不要这些 ?><?php drupal_render ( $form [ 'taxonomy' ]); //分类的外框,很丑? > <?php drupal_render ( $form [ 'body_filter' ][ 'format' ]); //内文说明,不要?> <?php //剩下的表单依照权重排列 ?> <?php printdrupal_render ( $ form ); ?>
Hi!原文在这: Drupal API的Dreamweaver扩充套件
今天发现了一个不错的东西,如果你是用Dreamweaver来coding的话,又是Drupal的使用者(不知这两个条件都成立的人多不多),这玩意儿还满不错的噢!虽然帮不上大忙,不过,会在你输入一个函数的时候,闪一个小视窗告诉你这个函数里面要填的东西大概是什么。
像是这样。
这是这个extension的资讯。
名称:Drupal API extension for Dreamweaver
网站:http://xtnd.us
下载页面:这儿
直接下载:Drupal_API.mxp
官方说明:
*在原始码模式时,档案类型是Drupal相关类型(.module, .php, . tal, .info, .inc, .theme, .js等等)的话,就会出现提示。(是出现在打完函数和左边小括号之后)
*您可在编辑>偏好设定>程式码提示那边停用这个功能。
*您可以在说明>Drupal API for Dreamweaver这个选单找到Drupal API网站连结。
安装方式应该不用讲了。依照这边的说法,似乎装了之后就会自动让Dreamweaver可以开Drupal的档案呢!所以我之前这篇「[Dreamweaver]让非php档名也能有php色彩标示」就白搭了。
以下是官方说明原文。
Drupal API Code Hints for Dreamweaver
Dreamweaver Versions: MX(6) - CS3(9)
This extension provides code hints for Drupal API's versions: 5.x, 6.x
* Drupal Code Hints appear in Code or Split view (Ctrl+Space) when editing Drupal files (.module, .php, .tal, .info, .inc, .theme, .js, etc.)
* You can deactivate Drupal versions that you aren't using in the Edit > Preferences > Code Hints menu .
* Help is available in the Help > Drupal API for Dreamweaver menu.
Updates and information about this extension available online at http://xtnd.us