En esta ocasión vamos a tirar a la basura la mayor parte del código que ya tenemos. Así es la vida de dura… cuando estamos aprendiendo, las cosas se hacen de una manera; cuando ya sabemos, las cosas se hacen de otra manera diferente.
Hoy queremos que nuestros “feedbacks” se adapten a drupal. Hasta ahora parecían formar parte del site, pero realmente sólo tenían el mismo aspecto. Lo que queremos conseguir es que realmente pertenezcan a nuestro drupal: aparecerán en las búsquedas, se administrarán como parte del contenido, etc.
Igual que es posible que este documento se copie, también es posible que yo lo
modifique. Por eso, la última versión del mismo siempre se encontrará en la
cuarta parte del tutorial de drupal. Un
saludo.
Qué se explica aquí
Cómo crear un nodo programáticamente. Una vez hayamos terminado, todo lo que hayamos hecho se podría haber hecho con el módulo CCK, pero de lo que se trata es aprender.
Qué es el CCK
Fácil: es el Content Construction Kit :-D
Consiste en una herramienta muy potente para la creación de formularios mediante el propio Drupal. De esta manera, sólo tenemos que ir añadiendo una lista de elementos, que posteriormente se renderizarán como un formulario. Gran parte de nuestro módulo “feedback” se podría haber hecho directamente sobre el CCK, por no decir todo.
¿Por qué no usar el CCK? En nuestro caso, “porque estamos aprendiendo” es una razón tan válida como cualquier otra. En concreto, para un módulo de las características de “feedback”, tal vez haya sido una pérdida de tiempo usar un módulo en lugar de el CCK. Sin embargo, un módulo tiene unas características de replicabilidad que el CCK creo que no tiene.
Además, el CCK nos permite crear formularios simples, pero si lo que queremos es poder manipular los datos, entonces necesitaremos crear nuestro propio módulo.
Pasos
Vamos a necesitar los siguientes hooks, aunque algunos de ellos ya los hemos implementado:
hook_node_info
hook_perm
hook_access
hook_form
También vamos a necesitar algunos de los siguientes hooks opcionales:
hook_insert
hook_update
hook_delete
hook_validate
hook_nodeapi
hook_view
hook_load
Hooks obligatorios
Nosotros ya tenemos nuestro hook_node_info y nuestro propio hook_perm, así que saltamos directamente a implementar el hook_access:
La función va a devolver true si queremos que el usuario tenga acceso. Para ello comprobamos si el usuario tiene los roles necesarios como para realizar ciertas acciones. En el caso de “update” y “delete” comprobamos también que el usuario sea el propietario del nodo.
Siguiendo con la lista, implementaremos el hook_node_info:
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php/**
* Implements hook_node_info()
*/functionfeedback_node_info(){returnarray('feedback'=>array('name'=>t('Feedback'),'module'=>'feedback','description'=>"Handle a feedback message",),);}
Ahora vamos a renombrar algunas de nuestras funciones… En concreto, feedback_message_form, que pasará a ser feedback_form. Es necesario también renombrar las funciones feedback_message_form_validate y feedback_message_form_submit para que todo siga funcionando como hasta ahora. También tendremos que modificar la llamada a feedback_message_form (ojo, que luego seguiremos transformando estas funciones):
Y…. ya está. Ya tenemos nuestro elemento integrado con Drupal. Si recargamos la caché (Administration->Performance), y nos vamos a “create content”, veremos que tenemos un tipo de contenido llamado “feedback”.
El caso es que también veremos que no funciona del todo bien :-D
Hooks recomendables
Antes de nada, os voy a hablar de hook_nodeapi. Hacéos un favor a vosotros mismos: olvidad que alguna vez existió. Las funciones que tienen parámetros que cambian en función de otros parámetros nunca son una buena idea. Además, en Drupal 7 lo han quitado, por lo que su uso está obsoleto. Toda la funcionalidad que podía ofrecer esta función la cumplen el resto de los hooks de la lista: insert, update, delete, validate, view y load. Existe una remota posibilidad de que necesitéis usar hook_nodeapi; pensáoslo bien antes de usarla.
El resto de hooks se ejecutarán automáticamente cuando alguien realice la operación esperada: hook_insert cuando se cree un nuevo elemento, etc.
Realmente lo que vamos a hacer es realizar una serie de transformaciones. Por ejemplo, nuestro antiguo feedback_form_submit se va a transformar en el nuevo y flamante feedback_insert, nuestro propio hook_insert:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?phpfunctionfeedback_insert($node){$userid=$node->uid;$message=$node->message;$mail=$node->mail;$query="INSERT into {feedback_messages} ( uid, message, indate, usermail ) values ( %d, '%s', now(), '%s' ) ";$queryResult=db_query($query,$userid,$message,$mail);if(db_affected_rows()){drupal_set_message(t("Thank you for your feedback."));drupal_goto('node');}elseform_set_error('feedback',t("An error has happened. Your feedback has not been sent."));}
Vamos a estudiarlo un poquito… Hemos cambiado la cabecera, de manera que ahora
el UID, el mensaje y el e-mail lo sacamos del nodo en lugar de usar variables
globales y formularios. Nosotros tenemos que decidir cómo almacenar la
información que sobrepasa a la información del nodo, por lo que insertamos en
nuestra tabla exactamente la misma información que antes. Es decir: a partir de
$query no hemos cambiado nada.
No ha sido difícil, ¿verdad? Ahora ya podemos insertar nodos que son feeds. Y
podemos probarlo: Create Content->feedback, escribimos y guardamos.
Se nos ha quedado el submit del formulario. Eso queda un poquito feo, ¿no? Ahora
que tenemos nuestro botón “preview” y nuestro “save”, amén de todas las opciones
de publicación, permisos, etc… Así que vamos a quitarlo. Además hay un
problema: se va a utilizar el mismo formulario para editar y para crear nuevos
elementos. Por esa razón, he decidido sacar una serie de variables que se
establecerán cuando se esté editando el nodo. Además, en modo “edición” se
mostrará también el check para indicar que ya se ha leído.
<?phpfunctionfeedback_form(&$node){global$user;$title=sprintf('Feed from %s',$node->name);$mail="";$message="";$read=false;$editing=false;if($user->uid!=0)$mail=$user->mail;if($node->nid>0){// estamos editando
$editing=true;$query="SELECT uid, message, indate, usermail, fm.read FROM {feedback_messages} fm WHERE id=%d";$queryResult=db_query($query,$node->nid);$storednode=db_fetch_object($queryResult);$mail=$storednode->usermail;$message=$storednode->message;$read=$storednode->read;}$form['feedback']=array('#type'=>'fieldset','#title'=>t('Feedback'),'mail'=>array('#type'=>'textfield',"#title"=>"E-Mail","#description"=>"If you want any answer, please, tell me your mail. It would not be shown anywhere.","#default_value"=>$mail),'message'=>array('#type'=>'textarea',"#description"=>"Write here your message",'#default_value'=>$message),'#required'=>TRUE,);if($editing){$form['feedback']['read']=array('#type'=>'checkbox',"#title"=>"The message has been read",'#default_value'=>$read);}$form['title']=array('#type'=>'hidden','#default_value'=>$title,);return$form;}
¡Eh!, ¡Para!, que aquí al final hay algo extraño… Sí, lo que hacemos es
preguntarle a Drupal el tipo de nodo que estamos tratando, y comprobar si este
tipo necesita un título. Si es así, pues lo pintamos. Se podría hacer lo mismo
con el body, pero en esta ocasión no lo queremos. El título es interesante
para que después nos aparezca en la lista de contenido y podamos pincharle.
Ahora vamos a realizar las comprobaciones adecuadas, renombrando
feedback_form_validate y haciendo los cambios pertinentes:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?phpfunctionfeedback_validate($node,&$form){$message=$form['values']['message'];$mail=$form['values']['mail'];$validmail=preg_match("/^[^0-9][A-z0-9_]+([.][A-z0-9_]+)*[@][A-z0-9_]+([.][A-z0-9_]+)*[.][A-z]{2,4}$/",$mail);if($mail==''&&$node->uid==0)form_set_error('mail',t('You are not a registered user. Please, insert your e-mail.'));if($mail!=''&&!$validmail)form_set_error('mail',t('The mail does not like an e-mail address.'));if($message=='')form_set_error('message',t('Message cannot be empty.'));return$form;}
Es una lástima ver lo fácil que habría resultado realizarlo así desde el principio, ¿verdad? ¡¡Pero había que aprender!! Pasito a pasito…
Ahora vamos a “ver” los feeds. No sé si habréis notado que cuando pulsáis sobre un feed, no se ve nada. Igual que en el preview. Eso es porque tenemos que implementar el hook_view:
<?phpfunctionfeedback_view($node,$teaser=FALSE,$page=FALSE){$messageread=false;$messagedata="";$messagename="";$messageusermail="";if($node->message){// ya tenemos el mensaje. Puede deberse a que estamos editando.
$messageread=$node->read;$messagedata=$node->message;$messagename=$node->name;$messageusermail=$node->mail;}else{// tenemos que obtener el mensaje
$query="SELECT fm.uid, message, indate, usermail, u.name, fm.read FROM {feedback_messages} fm, {users} u WHERE id=%d and fm.uid=u.uid";$queryResult=db_query($query,$node->nid);$message=db_fetch_object($queryResult);$messageread=$message->read;$messagedata=$message->message;$messagename=$message->name;$messageusermail=$message->usermail;}$read="";if($messageread)$read=t(" and <b>already read</b>");$content=sprintf(t('<blockquote><i>%s</i></blockquote><p>Sent by %s<a href="mailto:%s"><%s></a>%s.</p>'),$messagedata,$messagename,$messageusermail,$messageusermail,$read);$node=node_prepare($node,$teaser);$node->content['message']=array('#value'=>$content,'#weight'=>1,);return$node;}
Ahora vamos con la edición, para que se hagan efectivos los cambios:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?phpfunctionfeedback_update($node){$userid=$node->uid;$message=$node->message;$mail=$node->mail;$read=$node->read;$query="UPDATE {feedback_messages} fm SET message='%s', usermail='%s', fm.read=%d where id=%d";$queryResult=db_query($query,$message,$mail,$read,$node->nid);if(db_affected_rows()){drupal_set_message(t("The feedback has been updated."));drupal_goto('node');}elseform_set_error('feedback',t("An error has happened. Your feedback has not been sent."));}
Ahora una nota sobre el borrado: Si tratamos de borrar el nodo, veremos que se realiza sin problemas, pero eso no quiere decir que lo estemos haciendo BIEN. Se habrá borrado el nodo, pero no se habrá borrado la información asociada a nuestro feed, dejando basura inaccesible en la base de datos. Por eso implementaremos:
1
2
3
4
5
6
7
8
9
<?php/**
* Implementation of hook_delete
*/functionfeedback_delete($node){$query="DELETE FROM {feedback_messages} where id=%d";$queryResult=db_query($query,$node->nid);}
Con esto, queda obsoleto lo que implementamos en la cuarta entrega del curso
para poder eliminar mensajes :-D. Claro, que también hay una serie de enlaces
anticuados que nos están permitiendo crear feeds que no son nodos. Por estas
razones, vamos a eliminar:
<?phpfunctionfeedback_admin($messageid=null){$content='';if($messageid!=null){$content.=drupal_get_form('feedback_messageadmin_form',$messageid);$content.="<hr/>";}$header=array(array('data'=>t('Date'),'field'=>'indate','sort'=>'desc'),array('data'=>t('User'),'field'=>'name'),array('data'=>t('email'),'field'=>'mail'),array('data'=>t('Message'),'field'=>'message'),array('data'=>t('Read'),'field'=>'fm.read'),);$query="SELECT fm.id, u.uid as uid, u.name, fm.usermail, fm.indate, fm.message, fm.read FROM {feedback_messages} fm, {users} u WHERE fm.uid=u.uid";$query.=tablesort_sql($header);$queryResult=db_query($query);$rows=array();while($message=db_fetch_object($queryResult)){$row=array();$row['indate']=$message->indate;$row['name']=$message->uid!=0?'<a href="?q=user/'.$message->uid.'">'.$message->name.'</a>':$message->name;$row['mail']=$message->usermail;$row['message']=$message->message;$row['read']=$message->read;$row['edit']=l(t('edit'),"node/".$message->id."/edit");$rows[]=$row;}$content.=theme_table($header,$rows);return$content;}
Y lo más importante: ¡¡Ahora no podemos permitir que se cree un elemento
feedback sin que sea un nodo!! Así que se tercian dos cambios: Cambiar el
enlace del bloque (acordaos de limpiar la caché en Administration->performance)
y eliminar el acceso del hook_menu:
<?php/**
* Implementation of hook_block().
*/functionfeedback_block($op='list',$delta=0,$edit=array()){if($op=="list"){$blocks=array();$blocks[0]["info"]=t('Feedback');$blocks[1]["info"]=t('New Feedbacks');return$blocks;}elseif($op=="view"){$content='';$block=array();switch($delta){case0:$block['subject']=t('Feedback');$options=array("attributes"=>array("title"=>t("Sends a feedback message")));$link=l(t("New Feedback"),"node/add/feedback",$options);$content.='<div class="link">'.$link."</div>";break;case1:$block['subject']=t('Feedback Messages');$options=array("attributes"=>array("title"=>t("Shows feedback messages")));$title=t("There are @total new feeds",array('@total'=>_feedback_count()));$link=l($title,"feedback/adminmessages",$options);$content.='<div class="link">'.$link."</div>";break;}$block['content']=$content;return$block;}}/**
* Implementation of hook_menu().
*/functionfeedback_menu(){$items=array();$items['feedback/adminmessages']=array('title'=>'Feedback admin','page callback'=>'feedback_admin','access arguments'=>array('view messages'),'type'=>MENU_CALLBACK,);return$items;}
Drupal 7
Mucho me temo que todo lo que cuento aquí es válido para Drupal 6, pero no para Drupal 7.
Hay tutoriales de cómo pasar de Drupal 6 a Drupal 7, y seguro que todo lo contado en esta parte del tutorial se verá afectado.
Más tutoriales
Tengo en mente hacer un tutorial más y terminar con toda la tanda. Lo que queda por ver es sencillo: aplicar themes y añadir archivos de traducción. Si se os ocurre algo más, ¡¡decidlo ahora!!
Opinión personal
En muchos casos puede venir bien crear un módulo para drupal que gestiona nodos. Sin embargo, también es cierto que en este caso a mí, particulamente, me parece un problema. Al ser nodos, se verán afectados por los permisos de nodos, por lo que si en tu site no dejas crear contenido, se acabaron los feeds.
De todas maneras, me parecía interesante contarlo de esta manera, con el fin de que quede como una continuación y se aprenda a crear contenido para Drupal, que no es nada difícil
Más tutoriales
Este tutorial es la continuación de una serie de tutoriales relacionados con Drupal. Podéis consultar cualquiera de ellos:
<?php/*
Copyright 2009-2010 Miguel Ángel García Martínez <miguelangel.garcia@gmail.com>
'Feedback' is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
'Feedback' is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with 'Feedback' (maybe in file "COPYING"); if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*//**
* Implements hook_help().
*/functionfeedback_help($path,$arg){$output='';// para construir la salida
switch(path){case'admin/help#feedback':$output.='<p>'.t('Módulo que permite la inserción de mensajes de feedback.').'</p>';break;}return$output;}/**
* Implements hook_perm().
*/functionfeedback_perm(){returnarray('send message','view messages','admin messages');}/**
* Implements hook_access()
*/functionfeedback_access($op,$node,$account){$retval=false;switch($op){case'view':$retval=user_access('view messages',$account);case'create':$retval=user_access('send message',$account);break;case'update':$retval=user_access('admin messages',$account)&&($account->uid==$node->uid);break;case'delete':$retval=user_access('admin messages',$account)&&($account->uid==$node->uid);break;}return$retval;}/**
* Implements hook_node_info()
*/functionfeedback_node_info(){returnarray('feedback'=>array('name'=>t('Feedback'),'module'=>'feedback','description'=>"Handle a feedback message",),);}/**
* Implementation of hook_block().
*/functionfeedback_block($op='list',$delta=0,$edit=array()){if($op=="list"){$blocks=array();$blocks[0]["info"]=t('Feedback');$blocks[1]["info"]=t('New Feedbacks');return$blocks;}elseif($op=="view"){$content='';$block=array();switch($delta){case0:$block['subject']=t('Feedback');$options=array("attributes"=>array("title"=>t("Sends a feedback message")));$link=l(t("New Feedback"),"node/add/feedback",$options);$content.='<div class="link">'.$link."</div>";break;case1:$block['subject']=t('Feedback Messages');$options=array("attributes"=>array("title"=>t("Shows feedback messages")));$title=t("There are @total new feeds",array('@total'=>_feedback_count()));$link=l($title,"feedback/adminmessages",$options);$content.='<div class="link">'.$link."</div>";break;}$block['content']=$content;return$block;}}/**
* Implementation of hook_menu().
*/functionfeedback_menu(){$items=array();$items['feedback/adminmessages']=array('title'=>'Feedback admin','page callback'=>'feedback_admin','access arguments'=>array('view messages'),'type'=>MENU_CALLBACK,);return$items;}functionfeedback_message(){$content='';$content.=drupal_get_form('feedback_form');return$content;}functionfeedback_form(&$node){global$user;$title=sprintf('Feed from %s',$node->name);$mail="";$message="";$read=false;$editing=false;if($user->uid!=0)$mail=$user->mail;if($node->nid>0){// estamos editando
$editing=true;if($node->message){// estamos previsualizando datos
$mail=$node->mail;$message=$node->message;$read=$node->read;}else{$query="SELECT uid, message, indate, usermail, fm.read FROM {feedback_messages} fm WHERE id=%d";$queryResult=db_query($query,$node->nid);$storednode=db_fetch_object($queryResult);$mail=$storednode->usermail;$message=$storednode->message;$read=$storednode->read;}}$form['feedback']=array('#type'=>'fieldset','#title'=>t('Feedback'),'mail'=>array('#type'=>'textfield',"#title"=>"E-Mail","#description"=>"If you want any answer, please, tell me your mail. It would not be shown anywhere.","#default_value"=>$mail),'message'=>array('#type'=>'textarea',"#description"=>"Write here your message",'#default_value'=>$message),'#required'=>TRUE,);if($editing){$form['feedback']['read']=array('#type'=>'checkbox',"#title"=>"The message has been read",'#default_value'=>$read);}$form['title']=array('#type'=>'hidden','#default_value'=>$title,);return$form;}functionfeedback_insert($node){$userid=$node->uid;$message=$node->message;$mail=$node->mail;$query="INSERT into {feedback_messages} ( id, uid, message, indate, usermail ) values ( %d, %d, '%s', now(), '%s' ) ";$queryResult=db_query($query,$node->nid,$userid,$message,$mail);if(db_affected_rows()){drupal_set_message(t("Thank you for your feedback."));drupal_goto('node');}elseform_set_error('feedback',t("An error has happened. Your feedback has not been sent."));}functionfeedback_update($node){$userid=$node->uid;$message=$node->message;$mail=$node->mail;$read=$node->read;$query="UPDATE {feedback_messages} fm SET message='%s', usermail='%s', fm.read=%d where id=%d";$queryResult=db_query($query,$message,$mail,$read,$node->nid);if(db_affected_rows()){drupal_set_message(t("The feedback has been updated."));drupal_goto('node');}elseform_set_error('feedback',t("An error has happened. Your feedback has not been sent."));}/**
* Implementation of hook_delete
*/functionfeedback_delete($node){$query="DELETE FROM {feedback_messages} where id=%d";$queryResult=db_query($query,$node->nid);}functionfeedback_validate($node,&$form){$message=$node->message;$mail=$node->mail;$validmail=preg_match("/^[^0-9][A-z0-9_]+([.][A-z0-9_]+)*[@][A-z0-9_]+([.][A-z0-9_]+)*[.][A-z]{2,4}$/",$mail);if($mail==''&&$node->uid==0)form_set_error('mail',t('You are not a registered user. Please, insert your e-mail.'));if($mail!=''&&!$validmail)form_set_error('mail',t('The mail does not like an e-mail address.'));if($message=='')form_set_error('message',t('Message cannot be empty.'));return$form;}functionfeedback_view($node,$teaser=FALSE,$page=FALSE){$messageread=false;$messagedata="";$messagename="";$messageusermail="";if($node->message){// ya tenemos el mensaje. Puede deberse a que estamos editando.
$messageread=$node->read;$messagedata=$node->message;$messagename=$node->name;$messageusermail=$node->mail;}else{// tenemos que obtener el mensaje
$query="SELECT fm.uid, message, indate, usermail, u.name, fm.read FROM {feedback_messages} fm, {users} u WHERE id=%d and fm.uid=u.uid";$queryResult=db_query($query,$node->nid);$message=db_fetch_object($queryResult);$messageread=$message->read;$messagedata=$message->message;$messagename=$message->name;$messageusermail=$message->usermail;}$read="";if($messageread)$read=t(" and <b>already read</b>");$content=sprintf(t('<blockquote><i>%s</i></blockquote><p>Sent by %s<a href="mailto:%s"><%s></a>%s.</p>'),$messagedata,$messagename,$messageusermail,$messageusermail,$read);$node=node_prepare($node,$teaser);$node->content['message']=array('#value'=>$content,'#weight'=>1,);return$node;}functionfeedback_admin($messageid=null){$content='';if($messageid!=null){$content.=drupal_get_form('feedback_messageadmin_form',$messageid);$content.="<hr/>";}$header=array(array('data'=>t('Date'),'field'=>'indate','sort'=>'desc'),array('data'=>t('User'),'field'=>'name'),array('data'=>t('email'),'field'=>'mail'),array('data'=>t('Message'),'field'=>'message'),array('data'=>t('Read'),'field'=>'fm.read'),);$query="SELECT fm.id, u.uid as uid, u.name, fm.usermail, fm.indate, fm.message, fm.read FROM {feedback_messages} fm, {users} u WHERE fm.uid=u.uid";$query.=tablesort_sql($header);$queryResult=db_query($query);$rows=array();while($message=db_fetch_object($queryResult)){$row=array();$row['indate']=$message->indate;$row['name']=$message->uid!=0?'<a href="?q=user/'.$message->uid.'">'.$message->name.'</a>':$message->name;$row['mail']=$message->usermail;$row['message']=$message->message;$row['read']=$message->read;$row['edit']=l(t('edit'),"node/".$message->id."/edit");$rows[]=$row;}$content.=theme_table($header,$rows);return$content;}/** Devuelve el número de feeds creados en la última semana
*/function_feedback_count(){$query="SELECT count(id) as total from {feedback_messages} fm where fm.read != 1 or isnull(fm.read)";$queryResult=db_query($query);$result=db_fetch_object($queryResult);return$result->total;}