Crear un módulo custom para evitar errores 404 en los nodos eliminados en Drupal 7
El SEO es un mundo en sí mismo, y muchos programadores se olvidan de él. Según el proyecto tiene o no importancia, ya que si estamos desarrollando una plataforma de gestión interna no tiene sentido que Google y los demás buscadores nos indexen, mientras que si lo que estamos programando es un portal institucional, corporativo o un e-commerce la importancia es crítica. ¿Qué sentido tiene crear una tienda on-line si luego nadie nos va a encontrar? Podríamos desarrollar el mejor proyecto del mundo, que convirtiera en ventas las visitas en un alto porcentaje… pero si no llegan visitas el fracaso está al caer.
Después de esta pequeña reflexión sobre el SEO, os quería explicar uno de los módulos custom que suelo instalar en mis proyectos en Drupal 7. Depende del módulo redirect, ya que las opciones de configuración aparecen dentro de las suyas, además de utilizar algunas funciones propias de ese módulo.
El módulo Redirect
Comencemos con el concepto de redirección, que simplemente es configurar una directiva que hace que cuando un usuario visite una página Web, de forma automática vaya a otra distinta. Es decir, nos va a cambiar la dirección que visitamos en nuestro navegador. El sentido de esto es que cuando nosotros eliminamos un contenido de nuestra Web, cuando un usuario que tiene esa URL en favoritos o la encuentra en algún lado accede, se va a encontrar con un error de página no encontrada (el famoso error 404). Pero si nosotros creamos una redirección permanente (301) a otra página que tenga contenido igual o equivalente la experiencia de usuario no sufre, y además los buscadores no nos van a penalizar. Se utiliza mucho en las migraciones de páginas en las que todas las URLs cambian y no queremos perder el posicionamiento adquirido durante años con las URLs de la web antigua.
Pues bien, redirect es uno de los módulos imprescindibles para el SEO. Nos permite realizar redirecciones 301 de una URL a otra sin tener que meterlas en la configuración de Apache ni en el .htaccess, si bien es más recomendable hacerlo de este otro modo. Hacer las redirecciones con el módulo redirect en lugar de mediante .htaccess hace que exista mayor procesamiento en nuestro CMS, lo que puede retardar la carga (aunque casi siempre de forma imperceptible). Además de poder llevar una gestión de las redirecciones 301, nos permite que al cambiar una URL de una página se genere el redirect 301 de la URL antigua a la actual de forma automática.
¿Qué intento arreglar con mi módulo custom?
Haciendo una revisión SEO me di cuenta de que cuando elimino un contenido su identificador de nodo ya no se vuelve a rellenar. Así que si alguien se ha guardado la URL http://www.example.com/node/2 y yo he eliminado el nodo 2, cuando acceda se encontrará un error de página no encontrada. El propósito del módulo es buscar todos los nodos que han sido eliminados y que no tienen redirección 301, y añadirle una que apunte a la home de nuestra Web.
Programación
He denominado a estos nodos, nodos huérfanos. En primer lugar vamos a crear mediante el hook hook_menu las URLs de administración en el backend. Como ya he comentado antes, se colocarán dentro de las opciones del módulo redirect. Se trata de tres URLs:
- Las opciones globales del módulo: nos permitirán acceder a un formulario con dos opciones: si está marcada la primera revisará todos los nodos huérfanos y creará una redirección 301 a la home, mientras que si la segunda esta marcada hará esta operación en cada ejecución del cron.
- Listado de nodos huérfanos: será una lista de los nodos huérfanos. Nos permitirá añadirle redirección si no la tiene, o copiar el texto de la redirección para pegarlo directamente en .htaccess. Si la redirección existe nos mostrará hacia donde apunta y nos dejará modificarla.
- Texto para .htaccess: Será un texto con todas las redirección 301 creadas en formato .htaccess para copiarlo entero y pegarlo en ese archivo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
/** * Implements hook_menu(). */ function orphannids_menu() { // Module settings. $items['admin/config/search/redirect/orphannids'] = array( 'title' => 'Orphans', 'description' => 'Ver nids que no tienen contenido.', 'page callback' => 'drupal_get_form', 'page arguments' => array('orphannids_config_form'), 'access callback' => 'redirect_access', 'access arguments' => array('create', 'redirect'), 'file' => 'orphannids.admin.inc', 'type' => MENU_LOCAL_TASK, 'weight' => 21, ); $items['admin/config/search/redirect/orphannids/list'] = array( 'title' => 'Orphans list', 'description' => 'Ver nids que no tienen contenido.', 'page callback' => 'drupal_get_form', 'page arguments' => array('orphannids_list_form'), 'access callback' => 'redirect_access', 'access arguments' => array('create', 'redirect'), 'file' => 'orphannids.admin.inc', 'type' => MENU_LOCAL_TASK, 'weight' => 21, ); $items['admin/config/search/redirect/orphannids/text'] = array( 'title' => '.htaccess text', 'description' => 'Ver nids que no tienen contenido.', 'page callback' => 'drupal_get_form', 'page arguments' => array('orphannids_htaccess_text'), 'access callback' => 'redirect_access', 'access arguments' => array('create', 'redirect'), 'file' => 'orphannids.admin.inc', 'type' => MENU_LOCAL_TASK, 'weight' => 22, ); return $items; } |
Formulario principal
En este crearemos los dos checkboxes para las dos opciones que ya he comentado. Solo utilizaremos una variable de sistema para conocer si en el cron debe hacer alguna operación o no. El otro checkbox es una opción inmediata que se ejecuta al enviar el formulario, por lo que no tiene sentido:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
function orphannids_config_form($form, &$form_state) { $form['process'] = array( '#type' => 'checkbox', '#title' => t('Automatic redirects NOW'), '#description' => t('Create NOW all automatic redirects to home for orphan nodes'), ); $use_cron = variable_get('orphannids_use_cron', 0); $form['use_cron'] = array( '#type' => 'checkbox', '#title' => t('Use cron'), '#default_value' => $use_cron, '#description' => t('Use cron to check orphan nodes and create redirects to home.'), ); $form['submit'] = array( '#type' => 'submit', '#value' => t('Guardar'), '#submit' => array('orphannids_config_form_submit'), ); return $form; } |
Vamos a ver las operaciones que se ejecutan al guardar el form. Comprueba si debe crear los redirects ahora mismo, y si es así llama a una función que realizará esta tarea. Veremos esa función en seguida. Después actualiza la variable de la ejecución durante el cron según el valor marcado en el formulario:
1 2 3 4 5 6 7 8 9 10 11 12 |
function orphannids_config_form_submit($form, &$form_state) { if($form_state['values']['process']) { _orphannids_create_redirects(); drupal_set_message(t('All orphan nodes redirects created.')); } if($form_state['values']['use_cron']) { variable_set('orphannids_use_cron', 1); } else { variable_set('orphannids_use_cron', 0); } } |
Veamos ahora la función que crea los redirects. Obtiene el valor más alto del número de nodo creado, ya que va a tener que buscar de ahí para abajo aquellos que no existan. Cuando detecta uno, crea la redirección a la página principal del sitio web:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
function _orphannids_create_redirects() { global $base_url; global $base_path; $max = _orphannids_getMaxNid(); $query = db_select('node', 'n'); $query->addField('n', 'nid'); $query->condition('n.nid',$max,'<='); $query->orderBy('n.nid','DESC'); $nids = $query->execute()->fetchCol(); for($cont=$max;$cont>=1;$cont--) { if (!in_array($cont, $nids)) { $path = 'node/'.$cont; if(!$ridObject = redirect_load_by_source($path)) { $destination = $base_url . $base_path; $redirect = new stdClass(); redirect_object_prepare($redirect); $redirect->source = $path; $redirect->redirect = $destination; redirect_save($redirect); } } } } |
Esta es la función que obtiene el número máximo de nodo:
1 2 3 4 5 6 |
function _orphannids_getMaxNid() { $queryMax = db_select('node'); $queryMax->addExpression('MAX(nid)'); return $queryMax->execute()->fetchField(); } |
Ejecución del cron
Ahora debemos crear el comportamiento que tendrá cuando ejecute el cron, ya que si la configuración está activa tendrá que crear esos redirects 301. Utilizaremos el hook_cron. Como vemos simplemente es mirar la variable de sistema y si está activa llamar a la función que hemos visto anteriormente, que es la encargada de crear los redirects.
1 2 3 4 5 6 7 8 9 10 |
/** * Implements hook_cron(). */ function orphannids_cron() { $use_cron = variable_get('orphannids_use_cron', 0); if($use_cron) { _orphannids_create_redirects(); } } |
Listado de nodos huérfanos
La función que mostrará esta funcionalidad construye una tabla con todos los nodos huérfanos. Si el nodo tiene redirección ya, la muestra y permite modificarla o eliminarla, mientras que si no la tiene nos dejará agregarla, además de mostrarnos un texto que podríamos pegar en nuestro archivo .htaccess para hacerlo a nivel de servidor:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
function orphannids_list_form($form, &$form_state) { global $base_url; global $base_path; $destination = drupal_get_destination(); // Set up the header. $header = array( array('data' => t('Nid'), 'field' => 'nid', 'sort' => 'asc'), array('data' => t('Current Redirect'), 'field' => 'nid', 'sort' => 'asc'), array('data' => t('Copy to .htaccess'), 'field' => 'nid', 'sort' => 'asc'), array('data' => t('Operations')), ); // Building the SQL query and load the redirects. $query = db_select('node', 'n')->extend('TableSort'); $query->addField('n', 'nid'); $query->orderByHeader($header); // $query->limit(50); $nids = $query->execute()->fetchCol(); $max = _orphannids_getMaxNid(); $rows = array(); for($cont=$max;$cont>=1;$cont--) { if(!in_array($cont,$nids)) { $row = array(); $path = 'node/'.$cont; $row['nid'] = $cont; $operations = array(); if($ridObject = redirect_load_by_source($path)) { $exists = $ridObject->redirect; if (redirect_access('update', $ridObject)) { $operations['edit'] = array( 'title' => t('Edit'), 'href' => 'admin/config/search/redirect/edit/' . $ridObject->rid, 'query' => $destination, ); } if (redirect_access('delete', $ridObject)) { $operations['delete'] = array( 'title' => t('Delete'), 'href' => 'admin/config/search/redirect/delete/' . $ridObject->rid, 'query' => $destination, ); } $redirect = '-'; } else { $exists = '-'; if (redirect_access('create', 'redirect')) { $operations['add'] = array( 'title' => t('Add redirect'), 'href' => 'admin/config/search/redirect/add/', 'query' => array('source' => $path) + $destination, ); } $redirect = 'Redirect 301 /'.$path.' '.$base_url . $base_path; } $row['exists'] = $exists; $row['redirect'] = $redirect; $row['operations'] = array( 'data' => array( '#theme' => 'links', '#links' => $operations, '#attributes' => array('class' => array('links', 'inline', 'nowrap')), ), ); $rows[] = $row; } } $build['orphansnids_table'] = array( '#theme' => 'table', '#header' => $header, '#rows' => $rows, '#empty' => t('No orphans nodes.'), ); return $build; } |
Texto para .htaccess
Para terminar, encontramos esta función que genera un formulario con un área de texto. En esta encontraremos el texto que habría que pegar en .htaccess para generar las redirecciones 301 a nivel de servidor, si es que no queremos hacerlo mediante el módulo redirect.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
function orphannids_htaccess_text($form, &$form_state) { global $base_url; global $base_path; $max = _orphannids_getMaxNid(); $query = db_select('node', 'n'); $query->addField('n', 'nid'); $query->condition('n.nid',$max,'<='); $query->orderBy('n.nid','DESC'); // $query->limit(50); $nids = $query->execute()->fetchCol(); $output = ''; for($cont=$max;$cont>=1;$cont--) { if (!in_array($cont, $nids)) { $path = 'node/'.$cont; if(!$ridObject = redirect_load_by_source($path)) { $output .= 'Redirect 301 /' . $path . ' ' . $base_url . $base_path . ' '; } } } $form['orphannids_htaccess_text'] = array( '#value' => $output, '#type' => 'textarea', '#title' => t('Copy and Paste in your .htaccess file'), '#required' => TRUE, ); return $form; } |