Creación de un módulo Drupal (II)

Tomando como referencia el tutorial Creación de un módulo Drupal, vamos a añadirle la posibilidad de "ver" los comentarios, marcándolos como leídos.

Qué se explica aquí

En esta ocasión veremos cómo:
- Actualizar la base de datos
- Crear enlaces desde una tabla
- Crear un formulario de edición para una tabla.
- Entraremos un poquito más en profundidad en algunos temas.

Notas previas

En la primera parte del tutorial ya traté de inculcaros que sólo se debe abrir el tag de <?php, y no se debe cerrar con ?>. En esta ocasión no seré tan pesado.

El primer tutorial fue algo rápido, ya que era muy largo. En los próximos tutoriales, iré ampliando conceptos para que podáis desarrollar vuestros propios módulos. Prometo que serán más cortos.

Modificando la BBDD

Nuestro primer problema es que la base de datos va a cambiar. Necesitamos una columna nueva para la tabla feedback_messages, llamada "read", (de 'leído'). Va a ser un booleano. Como tenemos nuestro plugin en producción, no nos apetece tener que estar desinstalándolo, instalándolo de nuevo, etc, perdiendo todos los comentarios que nos hayan puesto. Otra opción es modificar la base de datos a mano, pero no deja de ser igual de tedioso, y no podemos pedirles a nuestros usuarios que lo hagan así.

Vamos a hacerlo bien, editando feedback.install y añadiendo lo siguiente:

<?php
function feedback_update_6100() {
  $ret = array();
  db_add_field($ret, 'feedback_messages', 'read', array('type' => 'int', 'size' => 'tiny'));
  return $ret;
}
?>

En la función virtualcoin_schema que creamos en la primera parte, lo que hicimos fue crear un enorme array de arrays. Realmente se le llama "schema", ya que es el esquema de nuestra base de datos. Podríamos haber usado sentencias SQL, pero el uso de este esquema nos permite ser independientes de la base de datos que use el futuro usuario de nuestro módulo.

En este caso, completamos el schema añadiéndole la columna nueva, que, como veréis, no es booleana como dije. Eso es porque el tipo booleano no existe en Drupal. Podéis consultar la lista de tipos drupal.

La función se llama "feedback_update_6100", ya que es del módulo "feedback", es un "update", para la versión 6 de drupal y es el que debe ejecutarse en primer lugar, si es que tuviéramos más actualizaciones que aplicar. El por qué de los numeritos podéis encontrarlo aquí.

Después es necesario irnos a "update.php" y seguir las instrucciones. Con eso se nos actualizarán los módulos (en este caso, nuestro módulo) y tendremos la tabla actualizada.

Modificando los menús

Vamos a modificar el "hook_menu".

Como ODIO que me pongan sólo parte de una función, os pego el código entero:

<?php
/**
* Implementation of hook_menu().
*/

function feedback_menu ( )
{
  $items = array();

  $items['feedback/message'] = array
  (
    'title' => t('Feedback'),
    'page callback' => 'feedback_message',
    'access arguments' => array('send message'),
    'type' => MENU_CALLBACK,
  );
  $items['feedback/adminmessages/%'] = array
  (
    'title' => t('Feedback Admin'),
    'page callback' => 'feedback_admin',
    'page arguments' => array ( 2 ),
    'access arguments' => array('view messages'),
    'type' => MENU_CALLBACK,
  );
  $items['feedback/adminmessages'] = array
  (
    'title' => t('Feedback Admin'),
    'page callback' => 'feedback_admin',
    'access arguments' => array('view messages'),
    'type' => MENU_CALLBACK,
  );
  return $items;
}
?>

Hay dos cambios respecto de la original. ¿Los véis? ¿No?

El primero consiste en el nombre del menú. Antes era "feedback/adminmessages", y ahora "feedback/adminmessages/%". Drupal irá tratando de encajar la ruta que hayan insertado con alguno de los patrones; el porcentaje (%) es como decir: "cualquier cosa". En este caso, encajará con cualquier cosa que tenga: "feedback/adminmessages/" y algo más (ojo, ya que no encaja si no tiene nada más).

El segundo cambio es la opción "page arguments". Le asignamos el valor "array (2)". Eso implica tomar la 3ª parte de la ruta y meterla en un vector que se le pasará a nuestra función.

Voy a explicarlo con un ejemplo. El usuario pone la ruta "feedback/adminmessages/25". Por lo tanto, si ponemos el valor de "page arguments" a 0, obtendremos "feedback"; a 1, "adminmessages" y a 2, "25", que es justo lo que queremos.

¿Qué ocurre si el usuario pone "feedback/adminmessages/25/añsldfj"? Pues que vuelve a encajar en el patrón dado (a falta de otro mejor), y se trataría exactamente igual que el ejemplo anterior (la parte "añsldfj" ignorará).

Dejamos también la parte que existía antes, con el fin de poder pintar también algo cuando no se ha seleccionado un mensaje.

Modificando funciones

Bueno, pues ya tenemos modificados los menus, así que vamos a modificar también las funciones.

Comenzamos por la función "feedback_admin". En esta función vamos a hacer tres cambios:

<?php
function feedback_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('Message'), 'field' => 'message'),
  );

  $query = "SELECT fm.id, u.name, fm.indate, fm.message FROM {feedback_messages} fm, {users} u WHERE fm.uid=u.uid";
  $queryResult =  db_query ( $query );

  $rows = array ();
  while ( $message = db_fetch_object ( $queryResult ) )
  {
    $row = array ();
    $row['name'] = $message->name;
    $row['indate'] = $message->indate;
    $row['message'] = $message->message;
    $row['edit'] = l ( t('edit'), "feedback/adminmessages/" . $message->id );
    $rows[] = $row;
  }

  $content .= theme_table($header, $rows);
  return $content;
}
?>

En primer lugar, hemos añadido un parámetro con un valor por defecto.

En segundo lugar, se añade la comprobación para saber si a la función se le llamó con algún parámetro (en caso contario, habría obtenido el valor por defecto, que es "NULL"). Si es así, pintamos un formulario (más adelante).

Y para finalizar, le hemos añadido una nueva fila a cada elemento de la lista, de manera que se creen enlaces a la dirección "feedback/adminmessages/" seguida del id del mensaje a mostrar.

NOTA: ¿Qué ocurre si un usuario malicioso trata de escribir esta dirección sin tener permisos? No tenemos que preocuparnos. Recordad que al definir los menús (apartado anterior) establecimos los permisos.

Claro, la función anterior nos está pidiendo a gritos que implementemos el formulario que nos falta y que, por ser un formulario, requiere también de un submit:

<?php
function feedback_messageadmin_form ( $form, &$messageid )
{
  $query = "SELECT fm.message, fm.read, fm.indate, u.name FROM {feedback_messages} fm, {users} u WHERE fm.id=%d and u.uid=fm.uid";
  $queryResult = db_query ( $query, $messageid );
  $message = db_fetch_object ( $queryResult );

  $form = array
    (
      'feedback' => array
      (
        '#type' => 'fieldset',
        '#title' => t( 'Feedback from <b>' . $message->name . '</b> sended on ' . $message->indate ),
        'messageid' => array ( '#type' => 'hidden', "#default_value" => $messageid ),
        'message' => array ( '#type' => 'textarea', "#default_value" => $message->message, "#disabled" => TRUE ),
        'read'  => array ( '#type'=> 'checkbox', "#title" => "Read","#default_value" => $message->read ),
        'submit' => array ( '#type' => 'submit',  '#value' => t('Submit'),  ),
      ),
    );
    return $form;
}

function feedback_messageadmin_form_submit ( $form, &$form_state )
{
  $messageid = $form_state['values']['messageid'];
  $read      = $form_state['values']['read'];

  $query = "UPDATE {feedback_messages} fm set fm.read=%d where fm.id=%d";
  $queryResult = db_query ( $query, $read, $messageid );
}
?>

Una explicación breve: Hemos creado un formulario con un campo oculto ("messageid") para albergar el id del mensaje. Además, añadimos el propio mensaje en un textarea deshabilitado, con el fin de que no se pueda modificar. Por último, añadimos un checkbox para marcar como leído, que es lo que puede hacer el administrador. Bueno, claro: y el botón de submit.

En este caso no hemos implementado un hook_validate debido a lo sencillo que era el formulario (en un formulario con un único checkbox es difícil hacer marranadas), pero sí hemos implementado el hook_submit, que tan sólo actualiza el mensaje en cuestión.

Y, para ya dejarlo todo niquelao, vamos a modificar la función que obtenía el número de mensajes a ver. Antes era tan cutre que mostraba todos los que tuvieran menos de una semana. Ahora podemos hacer que muestre los que no hemos marcado como leídos:

<?php
function _feedback_count ()
{
  $query = "SELECT count(id) as total from {feedback_messages} fm where fm.read != 1";
  $queryResult =  db_query ( $query );
  $result = db_fetch_object ( $queryResult ) ;

  return $result->total;
}
?>

While dentro de form.

Hola, una pregunta, como hago si quisiera imprimir dentro del formulario de feedback_messageadmin_form un while?

Lo que sucede es que el query me duelve muchas lineas y quisiera implementar un while dentro del form que me imprima todas las lineas como en una lista y a un lado un checbox. Digo que debe ser el while dentro del form, porque luego lo que hayan colocado las personas en los checkbox con respecto a la lista lo tengo que procesar.

Muchas gracias por tu atencion, y muchas gracias por el tutorial.

Respondiendo al while dentro de form.

Hola!!

Chico, que no estoy todo el día mirando el foro, que trabajo y tal ;)

Pues lo suyo es que no imprimas nada desde dentro de un form.

Básicamente tienes dos maneras: o bien creas un form por línea o bien creas un único form. Creo que lo que buscas es lo segundo.

La manera más correcta es hacer que forme parte del form :-D

Por lo que veo, te has quedado en la segunda parte del tutorial. Y es una lástima, porque en la tercera parte del tutorial de drupal explico cómo hacer algo parecido a lo que buscas, aunque creo que no lo hago dentro de un form. Te invito a que le eches un ojo.

Un saludo!

While dentro de form

Hola Miguel, ya me he leido de pies a cabeza todos tus tutoriales de creacion de un modulo en drupal. Son muy buenos...

Con respecto a lo del while, yo quiero hacer algo asi, pero esta malisimo porque ni siquiera me muestra la interface de drupal cuando refresco:

<?php
function drupal_openerp_form ()
{
  $query = "SELECT mo.cid, mo.mid, mo.object, imf.model, imf.name_field, imf.model_openerp_id FROM  {model_openerp} mo, {ir_model_fields} imf WHERE mo.mid=%d and mo.mid =imf.model_openerp_id ";
  $queryResult = db_query ( $query, $messageid );
  $message = db_fetch_object ( $queryResult );

  $form = ' ';

  while($message = db_fetch_object ($queryResult ))
  {
  $form1 = array
  (
    'drupal_openerp' => array
    (
      '#type' => 'fieldset',
      '#title' => t( 'drupal_openerp' ),
      'name' => array ('#type' => 'textfield', "#title"=>"name", "#description"=>"Name Partner", "#default_value" => $message->name_field),
      'message' => array ( '#type' => 'textarea', "#description"=>"Write here your message" ),
      'submit' => array ( '#type' => 'submit',  '#value' => t('Submit'),  ),
    ),
  );

  }

  return $form;
}
?>

Lo que quiero es que se imprima un campo por cada campo en la bd, y a un lado de cada campo un checkbox. Pero nada que dooy con la solucion, ya me e leido tus tuto, y no consegui. :(

Lo que quiero es algo como lo qu sale en Administer->User management->Permissions.

Listo!!, arreglado el asunto del While

Listo!, ya logre hacer el while dentro de un form era de esta forma(me copie un poco de el tuto III, cuando crean una lista):

<?php
 $query = "SELECT mo.cid, mo.mid, mo.object, imf.model, imf.name_field, imf.model_openerp_id FROM  {model_openerp} mo, {ir_model_fields} imf WHERE mo.mid=%d and mo.mid =imf.model_openerp_id ";
 $queryResult = db_query ( $query, $messageid );
 $form = array();

  while($message = db_fetch_object ($queryResult ))
  {
  $form1 = array
  (
    'drupal_openerp' => array
    (
      'name' => array ('#type' => 'textfield', "#title"=>"name", "#description"=>"Name Partner", "#default_value" => $message->name_field),
      'check' => array('#type' => 'checkbox',  "#title"=>"check", "#default_value"=> "0"),
    ),
  );
  $form[] = $form1;
  }
?>

Tenia que declarar un variable tipo array, que en mi caso se llama form1, ir metiendo cada campo del formulario en esa variable, e ir asignándole esa variable $form1 a la variable final $form la cual es la que voy a retornar.

Me alegra!!

Hola de nuevo!!

Me alegra ver que lo has resuelto ;)

Por cierto, espero que no te importe que haya puesto el código bonito en tus comentarios, pero es que me liaba ;D

Aun no he podido hacer el while :(

Aun no he podido hacer el while, alguien sabe como se podra? o si con otra herramienta sale mas facil?

Agradezco mucho su ayuda.

Porfa Ayuda... :(

Hola, primero que nada quiero decirte que eres especial chamo, de verdad que te admiro. Tenia tiempo buscando en la web, y tu portal es lo mejor que he conseguido, en reaildad te sabes explicar muy pero muy bien, seguramente eres tremendo desarrollador.

Entrando en tema, ya he realizado este modulo hasta esta parte, 2 veces y no me funciona correctamente, en el momento en que presiono el link edit, para que se muestre el formulario. Me lanza este error:

warning: Parameter 2 to feedback_messageadmin_form() expected to be a reference, value given in /var/www/feedback/includes/form.inc on line 372.

Porfa me puedes ayudar, tengo rato en esto, y todavia me falta bastante...

Gracias por todo de antemano.

Asunto Arreglado

Ya lo arregle, era que en esta funcion habia colocado:

function feedback_messageadmin_form($form, &$messageid)

Pero la forma correcta era asi, sin el ampersan(&) que tenia la variable messageid.

function feedback_messageadmin_form($form, $messageid)

Gracias! :)

problemas con feedback_menu

Holaa, antes que nada, quería agradecerle su tutorial que me esta sirviendo muchisimo para empezar a crear módulos en drupal.
Tengo dos problemas:
1- en el metodo feedback_menu, si uno escribe un mensaje, y le da a enviar, sigue en la misma página, y loq queremos es volver a la pagina principal, he cambiado
$items['feedback/message'] = array en esta linea, he cambiado lo que hay entre corchetes, pero no ha cambiado nada :( cuando le doy a ok, entonces lo intenté con 'page callback' => 'feedback_admin', cambiando feedback_admin, por otra cosa, tampoco va, porfavor, si puedes ayudarme y decirme donde y que tengo que cambiar para que vuelva a la pagina principal y no a la de escribir mensaje.

2- Para borrar un mensaje, como se puede hacer ??? pq en el codigo no se indica esto.

Muchas gracias

Respuesta rápida

Hola!!

Te mando una respuesta rápida. En cuanto saque un rato hago un tuto añadiendo eso.

1.- Redirigir a la página principal tras terminar el formulario.

dentro del hook_submit, es decir, al final de la función feedback_messageadmin_form_submit, puedes llamar a drupal_goto.

Es lógico que sea aquí, ya que es posible que quieras redirigir a distintas páginas en función de algún dato del formulario (por ejemplo).

2.- Borrar un mensaje.

Pues puedes añadir una columna de enlaces a la operación "feedback_delete". Sería algo así como feedback_admin, pero sin añadirle el formulario.

Otra opción es llamar a la propia feedback_admin, pero metiendo algún parámetro más en feedback_menu, en la clave 'access arguments'.

Te recomiendo mejor el primer método, porque es más fácil cambiar las cosas si más adelante quieres gestionar los permisos de forma separada.

Espero haber ayudado ;)

gracias

Holaa,
Gracias por tu respuesta :) a verdad que me ha solucionado el problema de redirigir una pagina en drupal, que no sabia como hacerlo. y funciona perfectamente.
Por la opción de borrar un mensaje, lo he intentado pero ninguna funciona:

Se ha creado la funcion:

<?php
function feedback_messageadmin_form_submit ( $form, &$form_state )
{
  $messageid = $form_state['values']['messageid'];
  $read      = $form_state['values']['read'];
  $opcion        = $form_state['values']['submit'];

  if($opcion=='Ok'){
   $query = "UPDATE {feedback_messages} fm set fm.read=%d where fm.id_msg=%d";
 
  $queryResult = db_query ( $query, $read, $messageid );

 }else if($opcion=='Eliminar'){
        $query="DELETE fm FROM {feedback_messages} fm WHERE fm.id_msg=%d";
        $queryResult=db_query ($query,$messageid);
 
        if(db_affected_rows()){
                drupal_set_message ( t ( "El mensaje ha sido eliminado" ) );
        }
 }
 drupal_goto("feedback/adminmessages");
 }
?>

y modificado la función:

<?php
function feedback_messageadmin_form ( $form,  $args )
{
  $messageid= $args[0];
  $query = "SELECT fm.message, fm.read, fm.fecha, u.name FROM {feedback_messages} fm, {users} u WHERE fm.id_msg=%d and u.uid=fm.uid";
  $queryResult = db_query ( $query, $messageid );
  $message = db_fetch_object ( $queryResult );

  $form = array
    (
      'sugerencia' => array
      (
        '#type' => 'fieldset',
        '#title' => t( 'feedback de <b>' . $message->name . '</b> enviada el ' . $message->fecha ),
        'messageid' => array ( '#type' => 'hidden', "#default_value" => $messageid ),
        'message' => array ( '#type' => 'textarea', "#default_value" => $message->message, "#disabled" => TRUE ),
        'read'  => array ( '#type'=> 'checkbox', "#title" => "Leido","#default_value" => $message->read ),
        'submit' => array ( '#type' => 'submit','#value' => t('Ok'),
                                       '#type' => 'submit','#value' => t('Eliminar') ),//la modificación                                      
      ),
    );
    return $form;
}
?>

Sale solo un boton de eliminar !!! si le doy a al boton de eliminar me elimina el mensaje, pero no tengo la opción de decirle ok y yasta, no se si me he explicado bien, gracias

Maria

Ups...

Hola de nuevo...

Perdona, pero he puesto un poquito de color a tu mensaje.... Ver tantas letras iguales seguidas me asustaban :-D

Primero, fíjate que al submit le estás metiendo dos valores: t('Ok') y ('Eliminar'). Te recomiendo que uses sólo cadenas en inglés (es el estándar drupal).

Lo segundo, te voy a enseñar a "depurar": En la función feedback_messageadmin_form_submit, pon lo siguiente (al principio, no es necesario que sustituyas toda la función):

<?php
function feedback_messageadmin_form_submit ( $form,  &$form_state )
{
  $messageid = $form_state['values']['messageid'];
  $read      = $form_state['values']['read'];
  $opcion        = $form_state['values']['submit'];

  print_r ($opcion);
[...]
}
?>

Como estás en la función de submit y has escrito algo, no se redireccionará a ninguna página, por lo que tendrás que darle a ir hacia atrás en el navegador. Si usas print_r en un formulario, se pintará toda la página después de mostrar tus trazas. Si lo usas para mostrar una hash o un vector, te recomiendo que le des al botón de mostrar el código de la página web.

Yo había pensado en otra solución distinta de la tuya... Espero tenerlo esta semana.

Un saludo.

Respuesta rápida 2

Lo que te di fue la respuesta rápida. Te doy otra respuesta rápida: fíjate en cómo está hecho el botón de editar.

Estoy terminando de pulir la tercera parte, que ya tiene este borrado. Además incluye alguna otra cosa que puede resultar interesante. Dame un par de días... :-D

Excelente Contribucion...

Gracias de por el tuto.. la verdad que es muy bien explicado y era justo lo que necesitaba... gracias espero puedas hacer uno mas avanzado...

salu2!!!!

una duda

Hola
quería preguntar si sabes como hacer la restricción de no poder enviar un mensaje vacío, lo he intentado pero no sé que solución hay que meterle.
gracias por contestar

Antonio

Evitar mensajes vacíos.

Pues me sorprende la pregunta, ya que está hecho:

<?php
function feedback_message_form_validate ( $form,  &$form_state )
{
  $message = $form_state['values']['message'];
  if ( $message == '' )
    form_set_error( 'message', t('Message cannot be empty.'));
}
?>

Realmente debo decir que hay un error en el primer tutorial de drupal: el primer argumento de form_set_error debería ser el nombre de uno de los campos.

Como ves, en el trozo que te he puesto en este mensaje ya está corregido. Estoy preparando la tercera parte del tutorial de drupal, pero de momento puedes ir mirando la segunda parte del tutorial de drupal, donde explico con ejemplos algunas cosas más.

Un saludo.

ya funciona

Graciaaas, ya me funciona la detección de mensajes vacíos, estamos a la espera de la tercera parte, thanks
Maria

Ya la tienes

buen artículo

Buen artículo, felicidades :)

Gracias

Se hace lo que se puede ;)

Theme by Danetsoft and Danang Probo Sayekti inspired by Maksimer