Dans cet article, nous allons voir comment créer un outil de glossaire, cet-à-dire, parcourir les mots pré-remplis (à partir d'un type de contenu par exemple) pour créer des liens (avec un filtre) vers une page glossaire.
D'abord il faut créer un filtre depuis notre module custom.
/**
* Implements hook_filter_info().
*/
function netapsys_glossaire_filter_info() {
$filters['netapsys_glossaire_wordlink'] = array(
'title' => t('Netapsys Glossaire - Word link'),
'description' => t('Automatically converts words into links.'),
'process callback' => '_netapsys_glossaire_wordlink_process',
'settings callback' => '_netapsys_glossaire_wordlink_settings',
'default settings' => array(
'word_link_highlight' => FALSE,
'word_link_wrap_tag' => NULL,
'word_link_boundary' => FALSE,
'word_link_tags_except' => '<h1> <h2> <h3> <h4> <h5> <h6> <code>',
'word_link_content_types' => array(),
),
'weight' => 15,
);
return $filters;
}
Nous créons le Process callback:
/**
* Helper function for filter process.
*/
function _netapsys_glossaire_wordlink_process($text, $filter) {
$text = netapsys_glossaire_wordlink_convert_text($text, $filter->settings);
return $text;
}
Juste après, pour chaque filtre (full_html, filtered html, plain text), il faut l'activer et le placer (par exemple en dernière position)
Dans notre module il faut placer une fonction pour trouver et transformer les mots en liens
/**
* Find and convert defined word to link.
*
* @param string $text
* Input text.
* @param array $settings
* Array of filter settings.
*
* @return string
* String with converted words.
*/
function netapsys_glossaire_wordlink_convert_text($text, $settings) {
global $base_url;
//Avoid empty
if ($text == '' || !$text) {
return $text;
}
//Avoid > URL-like to avoid + avoid homepage
$posAdmin = strpos($_GET['q'], 'admin');
// check strpos of glossaire / public pages
// check that page aint the view-admin one of the glossaire view ++ it contains glossaire in the path so we check /glossaire/* with it
if ($posAdmin !== FALSE || drupal_is_front_page()) {
return $text;
}
// Get array of words and check whether there's a glossary page or not
$words = null;
$words = netapsys_glossaire_wordlink_load_all($termid);
// Exit if there are no words to replace.
if (empty($words)) {
return $text;
}
// Default HTML tag used in theme.
$tag = 'a';
// Get disallowed html tags and convert it for Xpath.
if (!empty($settings['word_link_tags_except'])) {
$disallowed = &drupal_static('word_link_disallowed_tags');
if (!isset($disallowed)) {
$disallowed_tags = preg_split('/\s+|<|>/', $settings['word_link_tags_except'], -1, PREG_SPLIT_NO_EMPTY);
$disallowed = array();
foreach ($disallowed_tags as $ancestor) {
$disallowed[] = 'and not(ancestor::' . $ancestor . ')';
}
$disallowed = implode(' ', $disallowed);
}
}
else {
$disallowed = '';
}
// Create pattern.
$patterns = &drupal_static('word_link_patterns');
if (!isset($patterns)) {
$path = drupal_strtolower(drupal_get_path_alias());
$pattern = array();
foreach ($words as $word) {
$url = str_replace($base_url . '/', '', 'node/'. $word->nid);
$url = drupal_get_path_alias($url);
if ($url != $path) {
$title = preg_replace('/\s+/', ' ', trim($word->title));
$pattern[] = preg_replace('/ /', '\\s+', preg_quote($title, '/'));
}
}
// Chunk pattern string to avoid preg_match_all() limits.
$patterns = array();
foreach (array_chunk($pattern, 1000, TRUE) as $pattern_chunk) {
if ($settings['word_link_boundary']) {
$patterns[] = '/(?<=)(' . implode('|', $pattern_chunk) . ')/ui';
}
else {
$patterns[] = '/((\b)|(?<=))(' . implode('|', $pattern_chunk) . ')\b/ui';
}
}
}
foreach ($patterns as $pattern) {
netapsys_glossaire_wordlink_convert_text_recursively($text, $pattern, $words, $disallowed, $settings, $tag);
}
return $text;
}
Ici nous montrons la fonction d'aide (helper function) pour charger les mots pré-remplis depuis un type de contenu avec le machine_name "contenu_glossaire"
/**
* Loads words from the database. Actually all node titles of "content_glossaire" content type
*
* @param bool $enabled
* If TRUE load only enabled words.
*
* @param bool $termid
* If TRUE load only for a $tid site
*
* @return array
* An array of words objects indexed by text.
*/
function netapsys_glossaire_wordlink_load_all($termid = false, $enabled = TRUE) {
$words = &drupal_static(__FUNCTION__);
if (!isset($words)) {
if ($cache = cache_get('netapsys_glossaire_wordlink_words_' . $termid )) {
$words = $cache->data;
}
else {
$query = db_select('node', 'n');
$query->fields('n', array('title', 'nid'));
$query->condition('n.type', 'contenu_glossaire');
if ($enabled) {
$query->condition('status', 1);
}
$results = $query->execute();
$words = array();
foreach ($results as $word) {
if (!isset($words[$word->title])) {
$words[drupal_strtolower($word->title)] = $word;
}
}
cache_set('netapsys_glossaire_wordlink_words_' . $termid , $words, 'cache');
}
}
return $words;
}
Dans cette fonction, nous transformons le texte récursivement.
/**
* Helper function for converting text.
*
* @param string $text
* Input text.
* @param string $pattern
* Regular expression pattern.
* @param array $words
* Array of all words.
* @param string $disallowed
* Disallowed tags.
* @param array $settings
* Array of filter settings.
* @param string $tag
* Tag that will be used to replace word.
*/
function netapsys_glossaire_wordlink_convert_text_recursively(&$text, $pattern, $words, $disallowed, $settings, $tag) {
// Create DOM object.
$dom = filter_dom_load($text);
$xpath = new DOMXPath($dom);
$text_nodes = $xpath->query('//text()[not(ancestor::a) ' . $disallowed . ']');
foreach ($text_nodes as $original_node) {
$text = $original_node->nodeValue;
$match_count = preg_match_all($pattern, $text, $matches, PREG_OFFSET_CAPTURE);
if ($match_count > 0) {
$offset = 0;
$parent = $original_node->parentNode;
$next = $original_node->nextSibling;
$parent->removeChild($original_node);
foreach ($matches[0] as $delta => $match) {
$match_text = $match[0];
$match_pos = $match[1];
$text_lower = drupal_strtolower($match_text);
$word = $words[$text_lower];
//check all strtolower
if ( mb_strtolower($word->title) == mb_strtolower($match_text) ) {
$prefix = substr($text, $offset, $match_pos - $offset);
$parent->insertBefore($dom->createTextNode($prefix), $next);
$link = $dom->createDocumentFragment();
$word_link_rendered = &drupal_static('word_link_rendered');
if (!isset($word_link_rendered[$word->nid])) {
if ($cache = cache_get('word_link_rendered_' . $word->nid)) { //id changed for nid
$word_link_rendered[$word->nid] = $cache->data;
}
else {
$target = url_is_external('node/' . $word->nid) ? '_blank' : '';
$url_external = url_is_external('node/' . $word->nid);
$url_options = array();
$url_path = NULL;
//hack url > instead of node/ + $word->nid we set to glossaire?title=$word->title
if ($url_external) {
$url_path = 'glossaire?title=' . $word->title;
}
else {
$url_parts = parse_url('glossaire?title=' . $word->title);
$url_query = array();
if (isset($url_parts['query'])) {
parse_str($url_parts['query'], $url_query);
}
$url_options = array(
'query' => $url_query,
'fragment' => isset($url_parts['fragment']) ? $url_parts['fragment'] : '',
);
if (empty($url_parts['path'])) {
// Assuming that URL starts with #.
$url_options['external'] = TRUE;
$url_path = NULL;
}
else {
$url_path = $url_parts['path'];
}
}
$attributes = array(
'href' => url($url_path, $url_options),
'title' => $word->title,
'class' => 'netapsys_glossaire_wordlink', //$word->class
'target' => $target,
);
if ($settings['word_link_highlight']) {
$tag = 'span';
unset($attributes['href'], $attributes['target'], $attributes['rel']);
}
$word_link_rendered[$word->nid] = theme(
'netapsys_glossaire_wordlink',
array(
'text' => $match_text,
'tag' => $tag,
'attributes' => array_filter($attributes),
)
);
if (!empty($settings['word_link_wrap_tag'])) {
$word_link_rendered[$word->nid] = theme(
'html_tag',
array(
'element' => array(
'#tag' => $settings['word_link_wrap_tag'],
'#value' => $word_link_rendered[$word->nid],
),
)
);
}
cache_set('word_link_rendered_' . $word->nid, $word_link_rendered[$word->nid], 'cache');
}
}
$link->appendXML($word_link_rendered[$word->nid]);
$parent->insertBefore($link, $next);
$offset = $match_pos + strlen($match_text);
}
else {
$prefix = substr($text, $offset, $match_pos - $offset);
$parent->insertBefore($dom->createTextNode($prefix), $next);
$parent->insertBefore($dom->createTextNode($match_text), $next);
$offset = $match_pos + strlen($match_text);
}
if ($delta == $match_count - 1) {
$suffix = substr($text, $offset);
$parent->insertBefore($dom->createTextNode($suffix), $next);
}
}
}
}
$text = filter_dom_serialize($dom);
}
Sinon il faudrait une vue qui filtre les contenus du type de contenu "contenu_glossaire" et qui sera accessible depuis l'url /glossaire
Nous pourrons de la même manière customiser l'affichage de chaque mot, depuis le hook_theme
/**
* Implements hook_theme().
*/
function netapsys_glossaire_theme() {
return array(
'netapsys_glossaire_wordlink' => array(
'variables' => array(
'text' => NULL,
'tag' => NULL,
'attributes' => array(),
),
'file' => 'theme/netapsys_glossaire_wordlink.theme.inc',
),
);
}
Contenu du fichier netapsys_glossaire_wordlink.theme.inc
/**
* @file
* Theme for netapsys_glossaire_wordlink.
*/
/**
* Render a wordlink link.
*/
function theme_netapsys_glossaire_wordlink($vars) {
$attributes = drupal_attributes($vars['attributes']);
return '<' . $vars['tag'] . $attributes . '>' . check_plain($vars['text']) . '</' . $vars['tag'] . '>';
}