模塊開發實例:自定義內容類型及字段

這篇文章主要是講如何自定義節點類型和字段,其實使用cck可以完成這些功能,但不如自己動手有靈活性。可以搭配著另一篇文章 –不使用views,如何自定義文章列表 – 看。

這個模塊開發實例基於drupal 6.x,以電影信息錄入為例,需要一個存儲電影信息的節點。我們設計三個字段:
1、導演(文本字段)。
2、片長(數字字段)。
3、海報(圖片字段)。

實際操作中,一部電影可能需要更多的字段,比如演員、語言、發行商等等,但我們以這三種有代表性的字段為例。確定了字段之後,接下來是規劃數據表。使用過cck的都知道,它會自動創建一個表,然後存儲每個類型的自定義字段。現在我們根據上面設計的字段來規劃表:
    dao: 存儲導演名稱,文本字段。
    cang:存儲片長時間,整數字段。
    fid:海報是一個文件上傳欄位,實際上我們需要保存的只是上傳後的文件id,這兒也是一個整數字段。
名稱是隨意的,這三個對應存儲我們的三個字段,然後還需要一個字段來把我們的表和系統node起來,表名一般根據慣例,和模塊命名一致,如果有多個表,就以模塊名稱為前綴。我們的表大概就是這樣:

<?php
CREATE TABLE IF NOT EXISTS `movie
` (
  `nid` int(11) NOT NULL
,
  `dao` varchar(72) NOT NULL
,
  `cang` int(3) NOT NULL
,
  `fid` int(11) NOT NULL
,
  PRIMARY KEY  (`nid
`),
  KEY `dao` (`dao
`),
  KEY `cang` (`cang
`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8
;
?>

 

然後,我們先定義模塊info文件。info文件如下(關於info文件的一些格式和用法,可以參考這一篇文章,這兒就不深入講述了):

<?php
  ; $Id: movie.info,v 1.0 2008/11/2 11:17 east Exp
$
  name =
"movie"
  package =
movie
  version =
"6.x-1.0-beta1"
  core =
"6.x"
  project =
"movie"
?>

 

下一步,創建安裝文件,movie.install,根據上面規劃的字段,install文件創建如下:

<?php
 
// $Id: movie.instal,v 1.0 2008/11/2 12:47 east Exp $
  function movie_schema() {
//定義要創建的表結構
    $schema['movie'] = array(
//創建一個數據表,表名:movie
      'fields'=> array(
//在表裡創建如下字段
      'nid'=> array('type' => 'serial', 'not null' => TRUE, 'description' => '主鍵,與node相連'
),
      'dao'=> array('type' => 'varchar', 'length' => 72, 'default' => '','description' => '導演名稱'
),
      'cang'=> array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE,'description' => '片長,整數型'
),
      'uid'=> array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE,'description' => '圖片fid,與files相連'
),
      ),
      'indexes' => array(
//添加索引字段
      'dao'    => array('dao'
),
      'cang'    => array('cang'
),
      ),
      'primary key' => array('nid'),
//添加主鍵
   
);

    return $schema;
  }

  function movie_install() {//調用hook_install,開始安裝數據庫
    drupal_install_schema('movie'
);
  }

  function movie_uninstall() {//卸載鉤子hook_uninstall
    drupal_uninstall_schema('movie'
);
   
//可以增加在卸載時需要刪除的數據
 
}
?>

 

接下來就是模塊主文件(movie.module)的創建和完善了。先來定義節點信息,需要用到這個鉤子:hook_node_info。在這裡邊傳遞一個多維數組,就如:$info[‘movie’] => movie就是節點類型的系統名稱,而$info[‘movie’]也是一個數組,下面的索引就是節點類型參數了,$info[‘movie’][‘name’] => 節點類型的可讀名稱,在創建內容菜單下的顯示名稱。$info[‘movie’][‘module’] => 告訴系統這個節點類型是由哪一個模塊創建的。$info[‘movie’][‘description’] => 節點類型描述,在node/add列表裡,它們會顯示出來。另外可以定義節點的標題、正文。最終的節點信息參數如下:

<?php
  function movie_node_info
() {
    return array(
      'movie'
=> array(
      'name' => '影片'
,
      'module' => 'movie'
,
      'has_title' => true,
//節點標題必須。
      'title_label' => '影片名稱'
,
      'has_body' => true,
//節點正文必須
      'body_label' => '影片簡介'
,
      'description' => '影片數據庫'
,
      ),
    );
  }
?>

 

下一步,定義用戶權限,需要使用到的鉤子是:hook_perm。這兒的權限可以隨便定義,你需要定義多少種都可以。定義了這些權限,對節點的瀏覽、創建、編輯、刪除沒有任何影響,你需要在另一個鉤子中調用它們。

<?php
  function movie_perm
() {
    return array(
      'view movie'
,
      'create movie'
,
      'edit own movie'
,
      'edit any movie'
,
      'delete own movie'
,
      'delete any movie'
,
    );
  }
?>

 

上面定義了許多種權限,下面就來調用它們,讓這些權限作為一個接入標準,節點系統的權限鉤子就上場了,鉤子名:hook_access。hook_access鉤子裡的內容就不能隨便寫了,它會傳入三個參數,第一個參數($op)是節點的四種操作狀態:view -> 瀏覽,create -> 創建,update -> 編輯,delete -> 刪除。許多朋友提到節點的瀏覽權限,默認的幾種節點類型(page, story)沒有單獨定義瀏覽權限,全部繼承自訪問內容(access content)。第二個參數($node)是節點信息,實際上它只需要節點類型就可以判斷了。第三個參數($account)是當前用戶信息,用來判斷當前用戶是否擁有這種權限。

我們這個實例,把四種節點操作狀態全部定義上,代碼就是這樣:

<?php
  function movie_access($op, $node, $account
) {
    switch($op
){
      case 'view'
:
        return user_access('create view', $account
);
      break;
      case 'create'
:
        return user_access('create movie', $account
);
      break;
      case 'update'
:
        if (user_access('edit any movie', $account)||(user_access('edit own movie', $account) && $account->uid = $node->uid
)) {
          return TRUE
;
        }
      break;
      case 'delete'
:
        if (user_access('delete any movie', $account) || (user_access('delete own movie', $account) && ($account->uid = $node->uid
))) {
          return TRUE
;
        }
      break;
    }
  }
?>

 

好了,權限完成後,開始創建節點的表單。這兒使用的鉤子是hook_form。form是drupal中非常重要的一部分,函數也相當多,可以看看在線函數: http://api.drupal.org/api/group/form_api/6 ,也可以參看form.inc文件。

<?php
  function movie_form(&$node
) {
      global $user
;
      $type = node_get_types('type', $node
);
      $form['title'
] = array(
        '#type' => 'textfield'
,
        '#title' => check_plain($type->title_label
),
        '#required' => TRUE
,
        '#default_value' => $node->title
,
        '#weight' => -
5
     
);
      $form['body'
] = array(
        '#type' => 'textarea'
,
        '#title' => check_plain($type->body_label
),
        '#required' => TRUE
,
        '#default_value' => $node->body
,
        '#cols' => 60
,
        '#rows' => 16
,
        '#weight' => -
1
     
);
      $form['set'
] = array(
          '#type' => 'fieldset'
,
          '#title' => '影片基本信息'
,
          '#collapsible' => TRUE
,
          '#collapsed' => FALSE
,
          '#weight' => -2
,
          '#attributes' => array('class' => 'menu-item-form'
),
      );
    $form['set']['movie']['#tree'] =  TRUE
;
   
    $form['set']['movie']['dao'
] = array(
      '#type' => 'textfield'
,
      '#title' => '導演'
,
      '#required' => TRUE
,
      '#default_value' => $node->movie['dao'
],
    );
    $form['set']['movie']['cang'
] = array(
      '#type' => 'textfield'
,
      '#title' => '片長(分鐘)'
,
      '#required' => TRUE
,
      '#default_value' => $node->movie['cang'
],
    );
    $form['set']['image'
] = array(
      '#type' => 'file'
,
      '#title' => '影片海報'
,
    );
      return $form
;
  }
?>

form中,$form[‘title’]和$form[‘body’]就不用說了,標題和正文。而$form[‘set’]是我們自定義的一個表單組,把我們的字段全放在這個裡邊。我們知道,drupal把節點表單全放在$node數組中,提交後,用$node就能接收到,比如$node->title、$node->body。但它是如何取值的呢?drupal的form標準默認會取最後個值,比如$form[‘set’][‘movie’][‘dao’],這樣就能取值:$node->dao。為了方便,我們有時候會想把一些信息放在同一個組中,這個時候,需要設置[‘#tree’],這樣設置:$form[‘set’][‘movie’][‘#tree’] = TRUE;那麼取值就會從movie開始,最後的結果就是:$node->movie[‘dao’],$node->movie[‘cang’]。file欄位有自己的取值方式,後面保存時再詳細講述。  

節點表單定義完成(實際上還沒有完成,還要增加一些東西,我們在後面需要的時候,再回過頭來設置)後,我們可以先啟用模塊了。創建一個文件夾:movie,保存這幾個文件:movie.info,movie.install,movie.module。內容就跟我們上面的一樣,也可以在這兒下載。

啟用後,我們可以看到剛才自定義的節點類型了。發表一篇看看?先別急,還要定義節點提交後的數據寫義。這時需要使用到hook_nodeapi鉤子,實際上我們只會使用到其中的插入(insert)狀態。這個鉤子是節點系統的核心,傳遞了四個參數,$node -> 節點數據;$op -> 操作類型,這兒有多達10幾種類型,常用的是validate(驗證)、load(加載)、view(顯示)、insetr(插入)、update(編輯)、delete(刪除)。我們就用這些類型來完成在不同情況下的節點操作。$a3 -> 摘要($teaser)狀態下,$a4 -> 全文($page)狀態下,這兩個大多用在節點顯示的時候來判斷,哪些數據顯示在摘要,哪些顯示在全文。這個鉤子相當複雜,每一個類型也有具體的函數定義,用法也較多,如果要對node進行二次開發,最好深入瞭解它。

我們先把節點提交時的插入功能完成,使用到的是插入和驗證鉤子:hook_inserthook_validate,代碼如下:

<?php
  function movie_nodeapi(&$node, $op, $teaser, $page
) {
    global $user
;
    switch ($op
) {
      case 'validate'
:
        if(!is_numeric($node->movie['cang'
])){
          form_set_error('movie][cang', '片長必須是整數。');
//片長時間必須是整數,如果不是,就阻止提交。
       
}
      break;
      case 'insert'
:
        $validators
= array(
          'file_validate_is_image' => array(),
//文件驗證,只允許是圖片類型。
       
);
        $path = file_directory_path(). '/movie';
//file上傳路徑,file_directory_path()將獲得系統的默認路徑,我們設置為存儲到files下面的movie中。因為沒有設置自動創建目錄,請先手動創建一個movie目錄。
        if ($file = file_save_upload('image', $validators, $path)) {
//調用file_save_upload來完成圖片上傳,這兒需要指定三個參數:form中文件欄位名稱,驗證函數,存儲路徑。第一個是必須的,後兩個如果沒有,將使用系統默認設置。
          file_set_status($file, FILE_STATUS_PERMANENT);
//上傳完成後,把這個文件的狀態設置為已發佈。
          $fid = $file->fid;
//$file->fid就是這張圖片的id了,有了這個id,在files表中就可以找到圖片路徑了。
       
}
        db_query("INSERT INTO {movie} (nid, dao, cang, fid) VALUES (%d, '%s', %d, %d)", $node->nid, $node->movie['dao'], $node->movie['cang'], $fid);
//接下來寫入數據到我們的表中。
     
break;
      case 'view'
:
        $movie = '導演:'.$node->movie['dao'
];
        if($node->movie['cang'
]){
          $movie .= '片長:'.$node->movie['cang'].'分鐘'
;
        }
        if($node->movie['image_url'
]){
          $movie .= theme('image', $node->movie['image_url'
]);
        }
        $node->content['movie'
] = array(
          '#value' => $movie
,
          '#weight' => 10
,
        );
      break;
    }
  }
?>

 

好了,現在我們發表一篇movie類型的文件。發表完成,頁面上我們定義的數據呢?沒有。上數據庫看看,應該有了。如何顯示在頁面上呢?這時就需要定義load(加載)、view(顯示)狀態了。如果我們要編輯節點,該如何修改數據呢?比如我要換一張海報圖片,如何刪除原來的圖片?我想生成幾種縮略圖,要如何做?

這些問題就留到下一篇文章了。

註:這些文件在本機測試沒問題,如果你在測試中有問題,請提出以幫助修改這篇實例教程。

上一篇文章中,我們完成了節點權限的設置、表單的定義、以及節點數據的寫入,接下來要實現數據的顯示。這兒需要使用到兩個鉤子:hook_loadhook_view。hook_load用來加載數據,在這裡邊我們可以定義需要輸出的數據,這些數據將附加到$node數組中,其它模塊就可以調用它。同時,我們要判斷一下節點類型,以免做多餘的查詢,看一看hook_load部分的代碼:

<?php
  case 'load'
:
    if($node->type == 'movie'){
//判斷節點類型
      $t = db_fetch_object(db_query('SELECT dao, cang, fid FROM {movie} WHERE nid = %d', $node->nid
));
      if($t->dao
){
        $info['movie']['dao'] = $t->dao
;
        $info['movie']['cang'] = $t->cang
;
        if($info['movie']['fid'] = $t->fid
){
          $url = db_result(db_query('SELECT filepath FROM {files} WHERE fid = %d', $t->fid
));
          $info['movie']['image_url'] = $url
;
        }
      }
      return $info
;
    }
  break;
?>

 

這裡定義的$info數組是object類型,在使用node_load時,系統將會調用所有hook_load定義的數據,最後輸出為$node,輸出的$node會被轉換成stdClass類型。最後,在相應的頁面,使用變量即可獲取值,比如:$node->movie[‘dao’] -> 導演名稱,$node->movie[‘cang’] -> 片長,$node->movie[‘fid’] -> 影片海報的文件id。我們需要用這個id來查詢出圖片路徑,所以上面判斷如果$t->fid不為0時,就查詢files表中的filepath字段,那就是圖片路徑。而根據我們的定義,在相應頁面,使用這個變量就可以獲取到圖片路徑:$node->movie[‘image_url’]。

hook_load完成,該是完善hook_view的時候了。hook_view鉤子是直接定義node.tpl.php中的顯示數據,默認通過$node->content顯示出來,格式如下:

<?php
$node->content['movie'
] = array(
  '#value' => '要輸出的數據'
,
  '#weight' => 10,
//顯示權重,數字越小的越靠前。
);
?>

當然,你也可以不放在$node->content下,而是定義成$node->mycontent。這樣也沒問題,但是默認的node.tpl.php裡是只會顯示$node->content中的值得,你就需要在node.tpl.php中自行增加類似$node->mycontent[‘movie’][‘#value’]這樣的變量了。  

在hook_view中可以直接使用$node數組,它的內容就是從node_load得來。和load一樣,我們也要做一個節點類型的判斷,省得在執行其它類型節點時也調用了我們的定義。所以,我們最後的hook_view代碼如下:

<?php
  case 'view'
:
    if($node->type == 'movie'
){
      $movie = '導演:'.$node->movie['dao'].'<br>'
;
      if($node->movie['cang'
]){
        $movie .= '片長:'.$node->movie['cang'].'分鐘'.'<br>'
;
      }
      if($node->movie['image_url'
]){
        $movie .= theme('image', $node->movie['image_url'
]);
      }
      $node->content['movie'
] = array(
        '#value' => $movie
,
        '#weight' => 10
,
      );
    }
  break;
?>

因為這是一個簡單的實例,所以沒有進行複雜的css排版,在這裡邊,你可以跟操作html一樣來進行佈局排版。在圖片顯示上調用了一個核心中定義好的模板變量:theme_image,使用這個變量,你只需要傳入圖片路徑、title、alt等就可以了,省去了再寫一次img src=…..類似代碼的麻煩。  

現在,瀏覽文章,不出意外,應該可以看到導演、片長等內容了。想要什麼樣的顯示效果,就是前端的事情了,不在我們這個模塊開發實例的範圍之內。

我想編輯文章,如何修改數據?這裡調用到的鉤子是hook_update。我們知道,在提交表單時,表單裡的所有數據被附加在$node數組中傳遞了出去,在insert和update時,所接收的就是表單傳遞過來的值,而不是該節點原來的值。這兒說得有點繞,舉例子來說,某個節點的導演是張學友,你在編輯節點時修改為劉德華,這個時候,在update鉤子中獲得的導演值就是劉德華。

在理想狀態下,編輯文章時,數據表movie中一定有了該節點的的記錄,我們只要執行sql update就行了。但大多數模塊開發者都會考慮到這樣一種情況:某一個節點在hook_insert鉤子中寫入movie表失敗。這個時候節點發佈成功,但movie表中沒有該節點的記錄,在update時就無法執行更新,而必須執行寫入。所以,大多數模塊都將hook_insetr和hook_update合併在一起,用一個固定的變量來判斷記錄是否已經寫入。

我們的導演字段是必填值,所以就用這個來判斷movie表中是否有當前節點的記錄。但上面說了,在插入和更新這兩個鉤子中,接收到的$node是表單中最新修改的,如果在這兒判斷$node->movie[‘dao’]是不準確的。這時就需要在表單中把原本的值傳遞過來,回過去修改movie_form函數,添加一個值:

<?php
  $form['set']['movie']['dao_value'
] = array(
    '#type' => 'value'
,
    '#value' => $node->movie['dao'
],
  );
?>

 

同時,我們在編輯時要涉及到圖片的操作,如果上傳了新圖片,要把以前的圖片刪除掉,這也需要傳遞一個圖片id的參數過去,也就是$node->movie[‘fid’]。於是,在movie_form表單中,我們要增加如下代碼:

<?php
  $form['set']['movie']['fid'
] = array(
    '#type' => 'value'
,
    '#value' => $node->movie['fid'
],
  );
?>

value類型表示這個只做為代碼層的值來傳遞,不會在顯示層出現。這樣一來,我們就可以獲取到修改前的導演字段值和修改後的導演字段值。我們判斷修改前導演字段值是否存在,來決定是執行insert還是update。合併hook_insetr和hook_update後的代碼如下:

<?php
  case 'insert'
:
  case 'update'
:
    if($node->type == 'movie'
){
      $validators
= array(
        'file_validate_is_image'
=> array(),
      );
      $path = file_directory_path(). '/movie'
;
      if ($file = file_save_upload('image', $validators, $path
)) {
        file_set_status($file, FILE_STATUS_PERMANENT
);
        $fid = $file->fid
;
      }else{
        $fid = $node->movie['fid'
];
      }
      if(!$node->movie['dao_value'
]){
        db_query("INSERT INTO {movie} (nid, dao, cang, fid) VALUES (%d, '%s', %d, %d)", $node->nid, $node->movie['dao'], $node->movie['cang'], $fid
);
      }else{
        db_query("UPDATE {movie} SET dao = '%s', cang = %d, fid = %d WHERE nid = %d", $node->movie['dao'], $node->movie['cang'], $fid, $node->nid
);
      }
    }
  break;
?>

 

這段代碼中就可以實現編輯時的更新操作,但是如果新上傳了圖片,如何刪除舊的圖片呢?這兒我們就要判斷,如果新上傳了圖片,而舊的fid不為空,則執行刪除圖片、刪除files記錄的操作。刪除文件調用系統函數file_delete,傳遞文件路徑即可。但我們的表單中沒有文件路徑傳遞過來,所以我們有兩種解決辦法:1、在表單中傳遞一個路徑值過來。2、在hook_update鉤子中通過$node->movie[‘fid’]查詢文件路徑。這個實例中就使用第一種方法,這樣可以減少查詢數據庫的次數。同時,我們也許還需要增加一個功能:在編輯節點時,如果已經有海報圖片,就顯示它,做為預覽。這樣,再回頭去修改movie_form函數,增加文件路徑和預覽值,代碼如下:

<?php
  $form['set']['movie']['image_url'
] = array(
    '#type' => 'value'
,
    '#value' => $node->movie['image_url'
],
  );
//圖片路徑
  $form['set']['image_p']['#value'] =  theme('image', $node->movie['image_url']);
//圖片預覽
?>

 

有了圖片路徑的傳值,在update中就可以實現刪除了,代碼如下:

<?php
  if($node->movie['fid'
]){
    file_delete($node->movie['image_url']);
//刪除圖片
    db_query('DELETE FROM {files} WHERE fid = %d', $node->movie['fid']);
//刪除記錄
 
}
?>

 

結合起來,完整的hook_insetr、hook_update代碼就是這樣:

<?php
  case 'insert'
:
  case 'update'
:
    if($node->type == 'movie'
){
      $validators
= array(
        'file_validate_is_image'
=> array(),
      );
      $path = file_directory_path(). '/movie'
;
      if ($file = file_save_upload('image', $validators, $path
)) {
        file_set_status($file, FILE_STATUS_PERMANENT
);
        $fid = $file->fid
;
        if($node->movie['fid'
]){
          file_delete($node->movie['image_url'
]);
          db_query('DELETE FROM {files} WHERE fid = %d', $node->movie['fid'
]);
        }
      }else{
        $fid = $node->movie['fid'
];
      }
      if(!$node->movie['dao_value'
]){
        db_query("INSERT INTO {movie} (nid, dao, cang, fid) VALUES (%d, '%s', %d, %d)", $node->nid, $node->movie['dao'], $node->movie['cang'], $fid
);
      }else{
        db_query("UPDATE {movie} SET dao = '%s', cang = %d, fid = %d WHERE nid = %d", $node->movie['dao'], $node->movie['cang'], $fid, $node->nid
);
      }
    }
  break;
?>

 

節點的編輯階段代碼完成,下一步就是節點的刪除。刪除時調用了鉤子hook_delete。先確認一下我們需要刪除些什麼,第一、movie表中該節點的記錄。第二、files表中該節點所屬的海報記錄。第三、文件系統中的海報圖片。前兩個直接操作數據庫,後一個調用文件刪除函數。在這個鉤子裡,我們可以判斷一個節點類型,如果刪除的是其它類型,就不用去浪費時間執行這三個刪除操作了。根據上面的需求,代碼如下:

<?php
  if($node->type == 'movie'
){
    db_query('DELETE FROM {movie} WHERE nid = %d', $node->nid
);
    db_query('DELETE FROM {files} WHERE fid = %d', $node->movie['fid'
]);
    file_delete($node->movie['image_url'
]);
  }
?>

 

這樣,節點提交、編輯、刪除、顯示等流程就完整了。現在到權限裡,給相應的用戶組開啟瀏覽、創建、編輯或刪除等權限,就可以開始使用了。

如果需要更多的額外功能,比如海報生成多種格式的縮略圖,限製圖片的大小等等,這一部分就留到下一篇文章了。

 
 
 
 
 

模塊開發實例:文件上傳、分目錄存儲及圖片縮略圖
第一篇:
模塊開發實例:自定義內容類型及字段(1)
第二篇:模塊開發實例:自定義內容類型及字段(2)
在前面的實例中,我們將影響的海報保存在movie目錄下,這個文件夾也是手動創建的。在網站實際運營中,不可能手動創建文件夾,特別是我們需要按用戶、按時間動態生成文件夾的時候。我們可以使用php的創建文件夾函數mkdir,但drupal有定義好的函數,兼容性更好,那就是file_check_directory。假設需要按用戶、按年月來生成目錄(movie/1/2008-11)存儲海報圖片,我們來完成這樣的代碼:

<?php
  function movie_check_dir
() {
      global $user
;
      $path
= array();
      $path[] = 'movie';
//我們需要的目錄
      $path[] = $user->uid;
//下一級,以用戶id命名。
      $path[] = format_date(time(), 'custom', "Y-m");
//再下一級目錄,以年-月命名。
      $dirs
= array();
      foreach($path as $folder
) {
        $dirs[] = $folder;
//循環檢查目錄是否存在。
          $t = file_create_path(file_directory_path().'/' .implode("/", $dirs));
//以/分割數組。
          if (!file_check_directory($t, FILE_CREATE_DIRECTORY)) {
//這兒開始創建,如果目錄不存在,則創建。如果沒創建成功,則返回false。
              return false
;
          }
      }
   
//如果全部創建成功,則返回完整路徑,包括了系統存儲目錄。file_directory_path()可以獲取到系統存儲路徑,類似於sites/default/files。最後返回完整路徑,供調用。
      return file_directory_path().'/movie/' .$user->uid.'/'.format_date(time(), 'custom', "Y-m"
);
  }
?>

 

目錄生成後,在file_save_upload中進行上傳時,就調用這個函數做為第三個路徑參數。現在修改movie_nodeapi中的圖片上傳代碼:

<?php
 
//原代碼
  $path = file_directory_path(). '/movie'
;
  if ($file = file_save_upload('image', $validators, $path
)) {
 
 
//修改為:
  if ($file = file_save_upload('image', $validators, movie_check_dir
())) {
?>

 

接下來,傳張圖片看看:

<?php
/*
  * sites/default/files/movie/1目錄創建成功。
  * sites/default/files/movie/1/2008-11目錄創建成功。
  * 已更新 影片 未來水世界。
*/
?>

 

解決了文件存儲目錄的問題,現在來看第二個問題:生成多種格式的縮略圖。這裡需要用到圖片工具包,drupal核心文件中帶了一個圖片處理文件,裡邊有幾個常用的圖片處理函數:image.inc。我們主要用到這三個函數:image_get_infoimage_scale_and_cropimage_crop。image_get_info是獲取圖片文件相關信息,包括長、寬、大小、圖片類型。後兩個都是生成縮略圖,只是生成的樣式不一樣,一個會考慮原圖片的長寬比,一個按指定大小縮放不考慮原圖片長寬比。還有另外幾個函數,實現同樣的功能,但生成的樣式不同。用哪一個函數取決於自己的需要,這裡就不深入談了。

這幾個縮略圖的函數使用方法大致相同,傳遞的參數包括:原始圖片路徑,縮略圖的完整路徑(包括目錄和文件名),長、寬。還有一個需要注意:如果有相同名稱的文件存在,將會被覆蓋。下面來完成創建一個120×90的縮略圖處理代碼:

<?php
  if ($file = file_save_upload('image', $validators, movie_check_dir
())) {
    $image = image_get_info($file->filepath);
//獲取上傳成功後的圖片信息,主要是取得長寬。
    if($image['width'] > 120 || $image[height] > 90){
//如果原圖尺寸過小,就沒必要創建縮略圖了,那樣也會出錯。
      $filename = end(explode('/',$file->filepath));
//獲取圖片名稱。$file本身就包含一個$file->filename,為什麼還要另外去獲取呢?因為file_save_upload返回的$file->filename並不完全準確,當目錄中有一個同名文件存在時,系統會在原文件名加上參數,但file_save_upload返回的仍然是原文件名。所以我們要獲取準備的文件名,只有這樣自己動手了。以"/"分割,取最後一個值。
      //為什麼要獲取準確的原文件名呢?因為沒有把縮略圖的路徑存儲在數據庫中,完全靠原文件名來關聯,所以必須準確。如果把縮略圖路徑也存儲在數據庫中,這不必關心文件名了。
      image_scale($file->filepath, movie_check_dir().'/thumbnail_'.$filename, 120, 90
);
     
//縮略圖的路徑就跟原文件相同,只是文件名前多了一個thumbnail_。
      //如果保存成功,image_scale會返回true,否則返回空。可以在返回信息後來完成縮略圖存儲數據庫的操作。我們直接使用文件名來關聯,不需要存數據庫。
   
}
    file_set_status($file, FILE_STATUS_PERMANENT
);
    $fid = $file->fid
;
    if($node->movie['fid'
]){
      file_delete($node->movie['image_url'
]);
      db_query('DELETE FROM {files} WHERE fid = %d', $node->movie['fid'
]);
    }
  }else{
    $fid = $node->movie['fid'
];
  }
?>

 

現在上傳一個影片海報試試,不出意外,應該會自動創建一張縮略圖了。接下來我們讓在hook_load裡定義一個縮略圖變量,$node->movie[‘thumbnail_url’],代碼如下:

<?php
  case 'load'
:
  if($node->type == 'movie'
){
    $t = db_fetch_object(db_query('SELECT dao, cang, fid FROM {movie} WHERE nid = %d', $node->nid
));
    if($t->dao
){
      $info['movie']['dao'] = $t->dao
;
      $info['movie']['cang'] = $t->cang
;
      if($info['movie']['fid'] = $t->fid
){
        $url = db_result(db_query('SELECT filepath FROM {files} WHERE fid = %d', $t->fid
));
        $info['movie']['image_url'] = $url
;
        $filename = end(explode('/', $url));
//獲取原文件名。
        $thumbnail_url = str_replace($filename, 'thumbnail_' . $filename, $url);
//替換後獲取到縮略圖的完整路徑。
        if(is_file($thumbnail_url)){
//也許原圖片小於縮略圖尺寸,所以,如果縮略圖存在
          $info['movie']['thumbnail_url'] = $thumbnail_url
;
        }else{
//如果不存在,則使用原文件路徑
          $info['movie']['thumbnail_url'] = $url
;
        }
      }
    }
    return $info
;
  }
  break;
?>

 

同樣的,修改節點顯示時的代碼:

<?php
  case 'view'
:
    if($node->type == 'movie'
){
      $movie = '導演:'.$node->movie['dao'
];
      if($node->movie['cang'
]){
        $movie .= '片長:'.$node->movie['cang'].'分鐘'
;
      }
      if($node->movie['image_url'
]){
        $movie .= theme('image', $node->movie['thumbnail_url']);
//把原圖改成了縮略圖,我們也可以在這兒做一個超鏈接,點擊縮略圖則打開原圖。
     
}
      $node->content['movie'
] = array(
        '#value' => $movie
,
        '#weight' => 10
,
      );
    }
  break;
?>

 

在節點更新時,上傳新圖片後,也需要刪除縮略圖,這兒的代碼也要稍做修改:

<?php
  if($node->movie['fid'
]){
    file_delete($node->movie['image_url'
]);
    file_delete($node->movie['thumbnail_url']);
//添加刪除縮略圖的代碼
    db_query('DELETE FROM {files} WHERE fid = %d', $node->movie['fid'
]);
  }
?>

 

最後,完整的movie_nodeapi的代碼如下:

<?php
  function movie_nodeapi(&$node, $op, $teaser, $page
) {
    global $user
;
    switch ($op
) {
      case 'validate'
:
        if(!is_numeric($node->movie['cang'
])){
          form_set_error('movie][cang', '片長必須是整數。'
);
        }
      break;
      case 'load'
:
        if($node->type == 'movie'
){
          $t = db_fetch_object(db_query('SELECT dao, cang, fid FROM {movie} WHERE nid = %d', $node->nid
));
          if($t->dao
){
            $info['movie']['dao'] = $t->dao
;
            $info['movie']['cang'] = $t->cang
;
            if($info['movie']['fid'] = $t->fid
){
              $url = db_result(db_query('SELECT filepath FROM {files} WHERE fid = %d', $t->fid
));
              $info['movie']['image_url'] = $url
;
              $filename = end(explode('/', $url
));
              $thumbnail_url = str_replace($filename, 'thumbnail_' . $filename, $url
);
              if(is_file($thumbnail_url
)){
                $info['movie']['thumbnail_url'] = $thumbnail_url
;
              }else{
                $info['movie']['thumbnail_url'] = $url
;
              }
            }
          }
          return $info
;
        }
      break;
      case 'insert'
:
      case 'update'
:
        if($node->type == 'movie'
){
          $validators
= array(
            'file_validate_is_image'
=> array(),
          );
          if ($file = file_save_upload('image', $validators, movie_check_dir
())) {
            $image = image_get_info($file->filepath
);
            if($image['width'] > 120 || $image[height] > 90
){
              $filename = end(explode('/',$file->filepath
));
              image_scale($file->filepath, movie_check_dir().'/thumbnail_'.$filename, 120, 90
);
            }
            file_set_status($file, FILE_STATUS_PERMANENT
);
            $fid = $file->fid
;
            if($node->movie['fid'
]){
              file_delete($node->movie['image_url'
]);
              file_delete($node->movie['thumbnail_url'
]);
              db_query('DELETE FROM {files} WHERE fid = %d', $node->movie['fid'
]);
            }
          }else{
            $fid = $node->movie['fid'
];
          }
          if(!$node->movie['dao_value'
]){
            db_query("INSERT INTO {movie} (nid, dao, cang, fid) VALUES (%d, '%s', %d, %d)", $node->nid, $node->movie['dao'], $node->movie['cang'], $fid
);
          }else{
            db_query("UPDATE {movie} SET dao = '%s', cang = %d, fid = %d WHERE nid = %d", $node->movie['dao'], $node->movie['cang'], $fid, $node->nid
);
          }
        }
      break;
      case 'view'
:
        if($node->type == 'movie'
){
          $movie = '導演:'.$node->movie['dao'
];
          if($node->movie['cang'
]){
            $movie .= '片長:'.$node->movie['cang'].'分鐘'
;
          }
          if($node->movie['image_url'
]){
            $movie .= theme('image', $node->movie['thumbnail_url'
]);
          }
          $node->content['movie'
] = array(
            '#value' => $movie
,
            '#weight' => 10
,
          );
        }
      break;
      case 'delete'
:
        if($node->type == 'movie'
){
          db_query('DELETE FROM {movie} WHERE nid = %d', $node->nid
);
          db_query('DELETE FROM {files} WHERE fid = %d', $node->movie['fid'
]);
          file_delete($node->movie['image_url'
]);
        }
      break;
    }
  }
?>

 

圖片存儲路徑、縮略圖等需求就完成了。drupal6.x的module裡添加了一個theme函數,而模塊引擎已經弱化了,模塊可以自帶tpl.php文件以實現更自由靈活的開發。如何使用hook_theme,就是下一篇的內容。

Leave a Comment


NOTE - You can use these HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>