You are here

tagclouds.module in TagCloud 7

Same filename and directory in other branches
  1. 8 tagclouds.module
  2. 2.0.x tagclouds.module
  3. 1.0.x tagclouds.module

File

tagclouds.module
View source
<?php

/**
 * Implements hook_help().
 */
function tagcloudss_help($path, $arg) {
  switch ($path) {
    case 'admin/help#tagcloudss':
      return t('Tagclouds offers dynamic urls. <br/>Visit example.com/tagclouds/list/2,1,5 to get the vocabularies 2,1 and 5 listed as tag groups. <br/>Visit example.com/tagclouds/chunk/2,1,5 to get a tag cloud of the terms in the vocabularies 2,1 and 5.<br/> Note that we limit to five vocabularies.');
  }
}

/**
 * Implements hook_init().
 */
function tagclouds_init() {
  drupal_add_css(drupal_get_path('module', 'tagclouds') . '/tagclouds.css');
}

/**
 * Implements hook_menu().
 */
function tagclouds_menu() {
  $items = array();
  $items['admin/config/content/tagclouds'] = array(
    'title' => 'TagClouds configuration',
    'description' => 'Configure the tag clouds. Set the order, the number of tags, and the depth of the clouds.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'tagclouds_settings',
    ),
    'access callback' => 'user_access',
    'access arguments' => array(
      'administer taxonomy',
    ),
  );
  $items['tagclouds'] = array(
    'title' => 'Tags',
    'page callback' => 'tagclouds_page_chunk',
    'page arguments' => array(
      NULL,
    ),
    'access callback' => 'user_access',
    'access arguments' => array(
      'access content',
    ),
    'type' => MENU_SUGGESTED_ITEM,
  );
  $items['tagclouds/list/%tagclouds_vocs'] = array(
    'title' => 'Tags',
    'page callback' => 'tagclouds_page_list',
    'page arguments' => array(
      2,
    ),
    'access callback' => 'user_access',
    'access arguments' => array(
      'access content',
    ),
    'type' => MENU_CALLBACK,
  );
  $items['tagclouds/chunk/%tagclouds_vocs'] = array(
    'title' => 'Tags',
    'page callback' => 'tagclouds_page_chunk',
    'page arguments' => array(
      2,
    ),
    'access callback' => 'user_access',
    'access arguments' => array(
      'access content',
    ),
    'type' => MENU_SUGGESTED_ITEM,
  );
  return $items;
}

/**
 * Menu callback. Admin setting page for tagclouds.
 */
function tagclouds_settings() {
  $options = array(
    'title,asc' => t('by title, ascending'),
    'title,desc' => t('by title, descending'),
    'count,asc' => t('by count, ascending'),
    'count,desc' => t('by count, descending'),
    'random,none' => t('random'),
  );
  $form['tagclouds_sort_order'] = array(
    '#type' => 'radios',
    '#title' => t('Tagclouds sort order'),
    '#options' => $options,
    '#default_value' => variable_get('tagclouds_sort_order', 'title,asc'),
    '#description' => t('Determines the sort order of the tags on the freetagging page.'),
  );
  $options_display = array(
    'style' => t('Display Tags with Style'),
    'count' => t('Display Tags with Count'),
  );
  $form['tagclouds_display_type'] = array(
    '#type' => 'radios',
    '#title' => t('Tagclouds Display Type'),
    '#options' => $options_display,
    '#default_value' => variable_get('tagclouds_display_type', 'style'),
    '#description' => t('Determines the style of the page.'),
  );
  $form['tagclouds_display_node_link'] = array(
    '#type' => 'checkbox',
    '#title' => t('Link term to node when only one content is tagged'),
    '#default_value' => variable_get('tagclouds_display_node_link', true),
    '#description' => t('When there is only one content tagged with a certain term, link that term to this node instead of the term list page.'),
  );
  $form['tagclouds_display_more_link'] = array(
    '#type' => 'checkbox',
    '#title' => t('Display "more" link in the bottom of Tagcloud blocks'),
    '#default_value' => variable_get('tagclouds_display_more_link', TRUE),
    '#description' => t('Display "more" link, if number of existing tags bigger than the number of tags to show.'),
  );
  $form['tagclouds_page_amount'] = array(
    '#type' => 'textfield',
    '#size' => 5,
    '#title' => t('Amount of tags on the pages'),
    '#default_value' => variable_get('tagclouds_page_amount', '60'),
    '#description' => t("The amount of tags that will show up in a cloud on the\n      pages. Enter '0' to display all tags. Amount of tags in blocks must be\n      configured in the block settings of the various cloud blocks."),
  );
  $form['tagclouds_levels'] = array(
    '#type' => 'textfield',
    '#size' => 5,
    '#title' => t('Number of levels'),
    '#default_value' => variable_get('tagclouds_levels', 6),
    '#description' => t('The number of levels between the least popular
      tags and the most popular ones. Different levels will be assigned a different
      class to be themed in tagclouds.css'),
  );
  $lang = language_list('enabled');
  if (!empty($lang) && count($lang[1]) > 1) {

    /* select this option if you want language separation and the site
       is using node translation rather than entity_translation */
    if (!module_exists('entity_translation')) {
      $form['tagclouds_language_separation'] = array(
        '#type' => 'checkbox',
        '#title' => t('Separation of Tags per language for bundles/content types using node translation'),
        '#default_value' => variable_get('tagclouds_language_separation', 0),
        '#description' => t('If you have more than one language installed and your content type/bundle is configured to use node translation (one language per node) then this setting would allow you to separate the tags for each language. '),
      );
    }
    else {

      //entity translation is enabled
      $form['tagclouds_language_separation_radios'] = array(
        '#type' => 'radios',
        '#title' => t('Separation of Tags per language'),
        '#options' => array(
          '0' => t('Disable separation by language'),
          '1' => t('Separation of Tags per language for site configured to use node translation (Choose this if you have one language per node , but no entity/field translations for the node fields and your taxonomy/vocabulary is not entity translation enabled)'),
          'entity' => t('Separation of Tags per language per entity. (Choose this if your content type/bundle is configured to use field translation aka entity translation and your tagcloudable taxonomy/vocabulary is translation enabled each term has a translation)'),
        ),
        '#default_value' => variable_get('tagclouds_language_separation_radios', '0'),
        '#description' => t('*Choose tagclouds language separation method.'),
        '#required' => TRUE,
      );
    }
  }
  return system_settings_form($form);
}

/**
 * Menu callback renders a tagclouds page.
 */
function tagclouds_page_chunk($vocs) {
  if ($vocs == NULL) {
    foreach (taxonomy_get_vocabularies(NULL) as $vocabulary) {
      $vocs[] = $vocabulary->vid;
    }
  }
  $tags = tagclouds_get_tags($vocs, variable_get('tagclouds_levels', 6), variable_get('tagclouds_page_amount', '60'));
  $tags = tagclouds_sort_tags($tags);
  $output = theme('tagclouds_weighted', array(
    'terms' => $tags,
  ));
  if (!$output) {
    return drupal_not_found();
  }
  $output = "<div class=\"wrapper tagclouds\">{$output}</div>";
  return $output;
}

/**
 * Menu callback renders a tagclouds page with listed items: each vocabulary.
 */
function tagclouds_page_list($vocs) {
  if ($vocs == NULL) {
    return drupal_not_found();
  }
  $output = '';
  foreach ($vocs as $vid) {
    $vocabulary = taxonomy_vocabulary_load($vid);
    if ($vocabulary == FALSE) {
      return drupal_not_found();
    }

    // Clean out vocabulary, so that we don't have to leave security to our
    // theme layer.
    $vocabulary->description = filter_xss_admin($vocabulary->description);
    $vocabulary->name = filter_xss_admin($vocabulary->name);
    $tags = tagclouds_get_tags(array(
      $vocabulary->vid,
    ), variable_get('tagclouds_levels', 6), variable_get('tagclouds_page_amount', '60'));
    $tags = tagclouds_sort_tags($tags);
    $output .= theme('tagclouds_list_box', array(
      'vocabulary' => $vocabulary,
      'tags' => $tags,
    ));
  }
  if (!$output) {
    return drupal_not_found();
  }
  $output = "<div class=\"wrapper tagclouds\">{$output}</div>";
  return $output;
}

/**
 * Function that gets the information from the database, passes it along to the
 * weight builder and returns these weighted tags. Note that the tags are
 * unordered at this stage, hence they need ordering either by calling our api
 * or by your own ordering data.
 *
 * @param $vids
 *   Vocabulary ids representing the vocabularies where you want the tags from.
 * @param $steps
 *   The amount of tag-sizes you will be using. If you give "12" you still get
 *   six different "weights". Defaults to 6 and is optional.
 * @param $size
 *   The number of tags that will be returned.
 * @param $display OPTIONAL
 *   The type of display "style"=weighted,"count"=numbered display
 *
 * @return
 *   An <em>unordered</em> array with tags-objects, containing the attribute
 *   $tag->weight.
 */
function tagclouds_get_tags($vids, $steps = 6, $size = 60, $display = NULL) {

  // Build the options so we can cache multiple versions.
  global $language;
  $options = implode('_', $vids) . '_' . $language->language . '_' . $steps . '_' . $size . "_" . $display;

  // Check if the cache exists.
  $cache_name = 'tagclouds_cache_' . $options;
  $cache = cache_get($cache_name, 'cache_page');
  $tags = array();

  // Make sure cache has data.
  if (isset($cache->data)) {
    $tags = $cache->data;
  }
  else {
    if (!is_array($vids) || count($vids) == 0) {
      return array();
    }
    $query = db_select('taxonomy_term_data', 'td');
    $query
      ->addExpression('COUNT(*)', 'count');
    $query
      ->fields('td', array(
      'tid',
      'vid',
      'name',
      'description',
    ));
    $query
      ->addExpression('max(n.nid)', 'nid');
    $query
      ->join('taxonomy_index', 'tn', 'td.tid = tn.tid');

    // $query->join('node', 'n', 'tn.nid = n.nid');
    $query
      ->innerJoin('node', 'n', 'tn.nid = n.nid');
    $query
      ->addTag('node_access');
    if (!module_exists('entity_translation')) {
      if (variable_get('tagclouds_language_separation', 0)) {
        $query
          ->condition('n.language', $language->language);

        /* site is using node translation rather than entity_translation
           this will return taxonomy terms for that node translation
           unfortunately this will not work well for bundles/content types that are
           (entity translation enabled /field translated) */
      }
    }
    else {

      // entity translation module is enabled
      if (variable_get('tagclouds_language_separation_radios', 0) == 'entity') {

        /* TODO: would be nice to enhance tagclouds so that a translation enabled field_data_field_example-term-reference (example)
           can be used as a language condition for tid rather than taxonomy_index.  This would work
           much better for entity translation due to the fact that some terms are spelled exactly the
           same in more than one language so a translation enabled term reference will work best for
           this scenario.  This will require adding interface code to allow users to select the
           term-reference fields they want to tagcloud rather than select a taxonomy vocabulary */

        /*if using entity_translation you must add a condition on the term language
          rather than the node language as node language only finds nodes with
          'source' language = $language.  Using term language is more appropriate
          for entity_translation scenarios*/
        $query
          ->condition('td.language', $language->language);
      }
      elseif (variable_get('tagclouds_language_separation_radios', 0) == 1) {
        $query
          ->condition('n.language', $language->language);
      }
    }
    $query
      ->condition('td.vid', $vids);
    $query
      ->condition('n.status', 1);
    $query
      ->groupBy('td.tid')
      ->groupBy('td.vid')
      ->groupBy('td.name');

    //->groupBy('tn.nid');
    $query
      ->groupBy('td.description HAVING COUNT(*) > 0');
    $query
      ->orderBy('COUNT(*)', 'DESC');
    if ($size > 0) {
      $query
        ->range(0, $size);
    }
    drupal_alter('tagclouds_get_tags_query', $query, $size);
    $result = $query
      ->execute();
    foreach ($result as $tag) {
      $tags[$tag->tid] = $tag;
    }
    if ($display == NULL) {
      $display = variable_get('tagclouds_display_type', 'style');
    }
    $tags = tagclouds_build_weighted_tags($tags, $steps);
    cache_set($cache_name, $tags, 'cache_page', CACHE_TEMPORARY);
  }
  return $tags;
}

/**
 * API that returns an array with weighted tags.
 *
 * This is the hard part. People with better ideas are very very welcome to send
 * these to ber@webschuur.com. Distribution is one thing that needs attention.
 *
 * @param $tags
 *   A list of <em>objects</em> with the following attributes: $tag->count,
 *   $tag->tid, $tag->name and $tag->vid. Each Tag will be calculated and
 *   turned into a tag. Refer to tagclouds_get__tags() for an example.
 * @param $steps
 *   The amount of tag-sizes you will be using. If you give "12" you still get
 *   six different "weights". Defaults to 6 and is optional.
 * @return
 *   An <em>unordered</em> array with tags-objects, containing the attribute
 *   $tag->weight.
 */
function tagclouds_build_weighted_tags($tags, $steps = 6) {

  // Find minimum and maximum log-count. By our MatheMagician Steven Wittens aka
  // UnConeD.
  $tags_tmp = array();
  $min = 1000000000.0;
  $max = -1000000000.0;
  foreach ($tags as $id => $tag) {
    $tag->number_of_posts = $tag->count;
    $tag->weightcount = log($tag->count);
    $min = min($min, $tag->weightcount);
    $max = max($max, $tag->weightcount);
    $tags_tmp[$id] = $tag;
  }

  // Note: we need to ensure the range is slightly too large to make sure even
  // the largest element is rounded down.
  $range = max(0.01, $max - $min) * 1.0001;
  foreach ($tags_tmp as $key => $value) {
    $tags[$key]->weight = 1 + floor($steps * ($value->weightcount - $min) / $range);
  }
  return $tags;
}

/**
 * API function to order a set of tags.
 *
 * $Sort = NULL, "title, asc", "title, desc", "count, asc", "count, desc"
 * @todo If you feel like making this more modular, please send me patches.
 */
function tagclouds_sort_tags($tags, $sort = NULL) {
  if ($sort == NULL) {
    $sort = 'title,asc';
    list($sort, $order) = explode(',', variable_get('tagclouds_sort_order', $sort));
  }
  else {
    list($sort, $order) = explode(',', $sort);
  }
  switch ($sort) {
    case 'title':
      usort($tags, "_tagclouds_sort_by_title");
      break;
    case 'count':
      usort($tags, "_tagclouds_sort_by_count");
      break;
    case 'random':
      shuffle($tags);
      break;
  }
  if ($order == 'desc') {
    $tags = array_reverse($tags);
  }
  return $tags;
}

/**
 * Callback for usort, sort by count.
 */
function _tagclouds_sort_by_title($a, $b) {
  return strnatcasecmp($a->name, $b->name);
}
function _tagclouds_sort_by_count($a, $b) {
  return $a->count > $b->count;
}

/**
 * Theme function that renders the HTML for the tags.
 *
 * @ingroup themable
 */
function theme_tagclouds_weighted(array $vars) {
  $terms = $vars['terms'];
  $output = '';
  $display = variable_get('tagclouds_display_type', 'style');
  if (module_exists('i18n_taxonomy')) {
    $language = i18n_language();
  }
  if ($display == 'style') {
    foreach ($terms as $term) {
      if (module_exists('i18n_taxonomy')) {
        $term_name = i18n_taxonomy_term_name($term, $language->language);
        $term_desc = i18n_taxonomy_term_description($term, $language->language);
      }
      else {
        $term_name = $term->name;
        $term_desc = $term->description;
      }
      if ($term->count == 1 && variable_get("tagclouds_display_node_link", false)) {
        $output .= tagclouds_display_node_link_weight($term_name, $term->tid, $term->nid, $term->weight, $term_desc);
      }
      else {
        $output .= tagclouds_display_term_link_weight($term_name, $term->tid, $term->weight, $term_desc);
      }
    }
  }
  else {
    foreach ($terms as $term) {
      if (module_exists('i18n_taxonomy')) {
        $term_name = i18n_taxonomy_term_name($term, $language->language);
        $term_desc = i18n_taxonomy_term_description($term, $language->language);
      }
      else {
        $term_name = $term->name;
        $term_desc = $term->description;
      }
      if ($term->count == 1 && variable_get("tagclouds_display_node_link", false)) {
        $output .= tagclouds_display_node_link_count($term_name, $term->tid, $term->nid, $term->count, $term_desc);
      }
      else {
        $output .= tagclouds_display_term_link_count($term_name, $term->tid, $term->count, $term_desc);
      }
    }
  }
  return $output;
}

/**
 * Display Single Tag with Style
 *
 */
function tagclouds_display_term_link_weight($name, $tid, $weight, $description) {
  if ($term = taxonomy_term_load($tid)) {
    $uri = entity_uri('taxonomy_term', $term);
    $uri['options']['attributes']['class'][] = 'tagclouds';
    $uri['options']['attributes']['class'][] = 'level' . $weight;
    $uri['options']['attributes']['title'] = decode_entities($description);
    return "<span class='tagclouds-term'>" . l($name, $uri['path'], $uri['options']) . "</span>\n";
  }
}
function tagclouds_display_node_link_weight($name, $tid, $nid, $weight, $description) {
  if ($term = taxonomy_term_load($tid) && ($node = node_load($nid))) {
    $uri = entity_uri('node', $node);
    $uri['options']['attributes']['class'][] = 'tagclouds';
    $uri['options']['attributes']['class'][] = 'level' . $weight;
    $uri['options']['attributes']['title'] = decode_entities($description);
    return "<span class='tagclouds-term'>" . l($name, $uri['path'], $uri['options']) . "</span>\n";
  }
}

/**
 * Display Single Tag with Style
 *
 */
function tagclouds_display_node_link_count($name, $tid, $nid, $count, $description) {
  if ($term = taxonomy_term_load($tid) && ($node = node_load($nid))) {
    $uri = entity_uri('node', $node);
    $uri['options']['attributes']['class'][] = 'tagclouds';
    $uri['options']['attributes']['title'] = decode_entities($description);
    return "<span class='tagclouds-term'>" . l($name, $uri['path'], $uri['options']) . " (" . $count . ")" . "</span>\n";
  }
}
function tagclouds_display_term_link_count($name, $tid, $count, $description) {
  if ($term = taxonomy_term_load($tid)) {
    $uri = entity_uri('taxonomy_term', $term);
    $uri['options']['attributes']['class'][] = 'tagclouds';
    $uri['options']['attributes']['title'] = decode_entities($description);
    return "<span class='tagclouds-term'>" . l($name, $uri['path'], $uri['options']) . " (" . $count . ")" . "</span>\n";
  }
}

/**
 * Theme function that renders an entry in tagclouds/list/ views.
 *
 * @param $vocabulary
 *   A full vocabulary object.
 * @param $tags
 *   An array with weighted tag objects.
 * @ingroup themable
 */
function theme_tagclouds_list_box(array $vars) {
  if (module_exists('i18n_taxonomy')) {
    $language = i18n_language();
  }
  $vocabulary = $vars['vocabulary'];
  $tags = $vars['tags'];
  if (module_exists('i18n_taxonomy')) {
    $voc_name = i18n_taxonomy_vocabulary_name($vocabulary, $language->language);
    $voc_desc = i18n_taxonomy_vocabulary_description($vocabulary, $language->language);
  }
  else {
    $voc_name = $vocabulary->name;
    $voc_desc = $vocabulary->description;
  }
  $content = theme('tagclouds_weighted', array(
    'terms' => $tags,
  ));
  $output = '';
  if ($vocabulary->description) {
    $content = '<div>' . $voc_desc . '</div>' . $content;
  }
  $output .= '<h2>' . $voc_name . '</h2><div>' . $content . '</div>';
  return $output;
}

/**
 * Implements hook_block_info().
 */
function tagclouds_block_info() {
  $blocks = array();
  foreach (taxonomy_get_vocabularies() as $voc) {
    $blocks[$voc->vid] = array();
    $blocks[$voc->vid]['info'] = variable_get('tagclouds_block_title_' . $voc->vid, t('Tags in @voc', array(
      '@voc' => $voc->name,
    )));
    $blocks[$voc->vid]['cache'] = DRUPAL_CACHE_GLOBAL;
  }
  return $blocks;
}

/**
 * Implements hook_block_view().
 */
function tagclouds_block_view($delta = '') {
  $blocks = array();
  if ($voc = taxonomy_vocabulary_load($delta)) {
    $blocks['subject'] = variable_get('tagclouds_block_title_' . $delta, t('Tags in @voc', array(
      '@voc' => $voc->name,
    )));
    $tags = tagclouds_get_tags(array(
      $delta,
    ), variable_get('tagclouds_levels', 6), variable_get('tagclouds_block_tags_' . $delta, 12));

    //$tags = tagclouds_sort_tags($tags, "hook_block_view");;
    $tags = tagclouds_sort_tags($tags);
    $blocks['content'] = theme('tagclouds_weighted', array(
      'terms' => $tags,
    ));

    //return a chunk of 12 tags
    if (variable_get('tagclouds_display_more_link_' . $delta, variable_get('tagclouds_display_more_link', TRUE)) && count($tags) >= variable_get('tagclouds_block_tags_' . $delta, 12) && variable_get('tagclouds_block_tags_' . $delta, 12) > 0) {
      $blocks['content'] .= theme('more_link', array(
        'title' => t('more tags'),
        'url' => "tagclouds/chunk/{$voc->vid}",
      ));
    }
  }
  return $blocks;
}

/**
 * Implements hook_block_configure().
 */
function tagclouds_block_configure($delta = '') {
  $form = array();
  $form['tags'] = array(
    '#type' => 'textfield',
    '#title' => t('Tags to show'),
    '#default_value' => variable_get('tagclouds_block_tags_' . $delta, 12),
    '#maxlength' => 3,
    '#description' => t("The number of tags to show in this block. Enter '0' to display all tags."),
  );
  $form['more_link'] = array(
    '#type' => 'checkbox',
    '#title' => t('Display "more" link in the bottom of this block'),
    '#default_value' => variable_get('tagclouds_display_more_link_' . $delta, variable_get('tagclouds_display_more_link', TRUE)),
    '#description' => t('Display "more" link, if number of existing tags bigger than the number of tags to show.'),
  );
  return $form;
}

/**
 * Implements hook_block_save().
 */
function tagclouds_block_save($delta = '', $edit = array()) {
  variable_set('tagclouds_block_tags_' . $delta, $edit['tags']);
  variable_set('tagclouds_display_more_link_' . $delta, $edit['more_link']);
}

/**
 * Implements hook_theme().
 */
function tagclouds_theme() {
  return array(
    'tagclouds_list_box' => array(
      'arguments' => array(
        'vocabulary' => NULL,
        'tags' => NULL,
      ),
    ),
    'tagclouds_weighted' => array(
      'arguments' => array(
        'terms' => array(),
      ),
    ),
  );
}

/**
 * Implements hook_load().
 */
function tagclouds_vocs_load($ids) {
  return explode(',', $ids);
}

Functions

Namesort descending Description
tagcloudss_help Implements hook_help().
tagclouds_block_configure Implements hook_block_configure().
tagclouds_block_info Implements hook_block_info().
tagclouds_block_save Implements hook_block_save().
tagclouds_block_view Implements hook_block_view().
tagclouds_build_weighted_tags API that returns an array with weighted tags.
tagclouds_display_node_link_count Display Single Tag with Style
tagclouds_display_node_link_weight
tagclouds_display_term_link_count
tagclouds_display_term_link_weight Display Single Tag with Style
tagclouds_get_tags Function that gets the information from the database, passes it along to the weight builder and returns these weighted tags. Note that the tags are unordered at this stage, hence they need ordering either by calling our api or by your own ordering data.
tagclouds_init Implements hook_init().
tagclouds_menu Implements hook_menu().
tagclouds_page_chunk Menu callback renders a tagclouds page.
tagclouds_page_list Menu callback renders a tagclouds page with listed items: each vocabulary.
tagclouds_settings Menu callback. Admin setting page for tagclouds.
tagclouds_sort_tags API function to order a set of tags.
tagclouds_theme Implements hook_theme().
tagclouds_vocs_load Implements hook_load().
theme_tagclouds_list_box Theme function that renders an entry in tagclouds/list/ views.
theme_tagclouds_weighted Theme function that renders the HTML for the tags.
_tagclouds_sort_by_count
_tagclouds_sort_by_title Callback for usort, sort by count.