You are here

tagadelic.module in Tagadelic 6

File

tagadelic.module
View source
<?php

/**
 * Implements hook_help().
 */
function tagadelic_help($path, $arg) {
  switch ($path) {
    case 'admin/help#tagadelic':
      return t('Tagadelic offers dynamic urls. <br/>Visit example.com/tagadelic/list/2,1,5 to get the vocabularies 2,1 and 5 listed as tag groups. <br/>Visit example.com/tagadelic/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 tagadelic_init() {
  drupal_add_css(drupal_get_path('module', 'tagadelic') . '/tagadelic.css');
}

/**
 * Implements hook_menu().
 */
function tagadelic_menu() {
  $items = array();
  $items['admin/settings/tagadelic'] = array(
    'title' => 'Tagadelic 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(
      'tagadelic_settings',
    ),
    'access callback' => 'user_access',
    'access arguments' => array(
      'administer taxonomy',
    ),
  );
  $items['tagadelic'] = array(
    'title' => 'Tags',
    'page callback' => 'tagadelic_page_chunk',
    'page arguments' => array(
      NULL,
    ),
    'access callback' => 'user_access',
    'access arguments' => array(
      'access content',
    ),
    'type' => MENU_SUGGESTED_ITEM,
  );
  $items['tagadelic/list/%tagadelic_vocs'] = array(
    'title callback' => 'tagadelic_page_title_callback',
    'title arguments' => array(
      2,
    ),
    'page callback' => 'tagadelic_page_list',
    'page arguments' => array(
      2,
    ),
    'access callback' => 'user_access',
    'access arguments' => array(
      'access content',
    ),
    'type' => MENU_CALLBACK,
  );
  $items['tagadelic/chunk/%tagadelic_vocs'] = array(
    'title callback' => 'tagadelic_page_title_callback',
    'title arguments' => array(
      2,
    ),
    'page callback' => 'tagadelic_page_chunk',
    'page arguments' => array(
      2,
    ),
    'access callback' => 'user_access',
    'access arguments' => array(
      'access content',
    ),
    'type' => MENU_SUGGESTED_ITEM,
  );
  return $items;
}

/**
 * Implements hook_nodeapi().
 *
 * You will have a nice variable in $node available for processing tags!
 */
function tagadelic_nodeapi(&$node, $op, $teaser, $page) {
  if ($op == 'load') {
    $node->tags = tagadelic_node_get_terms($node);
  }
}

/**
 * Menu callback. Admin setting page for tagadelic.
 */
function tagadelic_settings() {
  $options = array(
    'weight,asc' => t('by weight, ascending'),
    'weight,desc' => t('by weight, descending'),
    'title,asc' => t('by title, ascending'),
    'title,desc' => t('by title, descending'),
    'random,none' => t('random'),
  );
  $form['tagadelic_sort_order'] = array(
    '#type' => 'radios',
    '#title' => t('Tagadelic sort order'),
    '#options' => $options,
    '#default_value' => variable_get('tagadelic_sort_order', 'title,asc'),
    '#description' => t('Determines the sort order of the tags on the freetagging page.'),
  );
  $form['tagadelic_page_amount'] = array(
    '#type' => 'textfield',
    '#size' => 5,
    '#title' => t('Amount of tags on the pages'),
    '#default_value' => variable_get('tagadelic_page_amount', '60'),
    '#description' => t('The amount of tags that will show up in a cloud on the pages. Amount of tags in blocks must be configured in the block settings of the various cloud blocks.'),
  );
  $form['tagadelic_levels'] = array(
    '#type' => 'textfield',
    '#size' => 5,
    '#title' => t('Number of levels'),
    '#default_value' => variable_get('tagadelic_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 tagadelic.css'),
  );
  return system_settings_form($form);
}

/**
 * Menu wildcard callback.
 */
function tagadelic_vocs_load($vocs) {
  $parsed_vocs = array();
  if (is_numeric($vocs)) {
    $parsed_vocs = array(
      $vocs,
    );
  }
  elseif (preg_match('/^([0-9]+,){1,5}[0-9]+$/', $vocs)) {
    $parsed_vocs = explode(',', $vocs);
  }
  return $parsed_vocs;
}

/**
 * Menu callback to render the tagadelic title.
 *
 * @param $vocs
 *   An array of taxonomy vocabulary ids.
 */
function tagadelic_page_title_callback($vocs) {
  $title = '';
  if (empty($vocs)) {
    $title = t('Tags');
  }
  else {
    $title = t('Tags in @vocabularies', array(
      '@vocabularies' => theme('tagadelic_list_vocs', $vocs),
    ));
  }
  return $title;
}

/**
 * Theme function that creates a comma separated list of vocabulary terms.
 *
 * @params $vocs
 *   An array of taxonomy vocabulary ids.
 *
 * @ingroup themable
 */
function theme_tagadelic_list_vocs($vocs) {
  $voc_names = array();
  foreach ($vocs as $vid) {
    $vocabulary = taxonomy_vocabulary_load($vid);
    if (!empty($vocabulary)) {
      $voc_names[] = $vocabulary->name;
    }
  }
  return join(', ', $voc_names);
}

/**
 * Menu callback renders a tagadelic page.
 */
function tagadelic_page_chunk($vocs) {
  if ($vocs == NULL) {
    foreach (taxonomy_get_vocabularies(NULL) as $vocabulary) {
      $vocs[] = $vocabulary->vid;
    }
  }
  $tags = tagadelic_get_weighted_tags($vocs, variable_get('tagadelic_levels', 6), variable_get('tagadelic_page_amount', '60'));
  $tags = tagadelic_sort_tags($tags);
  $output = theme('tagadelic_weighted', $tags);
  if (!$output) {
    return drupal_not_found();
  }
  $output = "<div class=\"wrapper tagadelic\">{$output}</div>";
  return $output;
}

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

      // 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 = tagadelic_get_weighted_tags(array(
        $vocabulary->vid,
      ), variable_get('tagadelic_levels', 6), variable_get('tagadelic_page_amount', '60'));
      $tags = tagadelic_sort_tags($tags);
      $output .= theme('tagadelic_list_box', $vocabulary, $tags);
    }
  }
  if (empty($output)) {
    return drupal_not_found();
  }
  $stylesheet = drupal_get_path('module', 'tagadelic') . '/tagadelic.css';
  drupal_add_css($stylesheet, 'all');
  $output = "<div class=\"wrapper tagadelic\">{$output}</div>";
  return $output;
}

/**
 * API that returns a multidimensional array with tags given a node.
 *
 * @param $node
 *   A node object.
 */
function tagadelic_node_get_terms($node) {
  static $vocs;
  if ($terms = taxonomy_node_get_terms($node, 'tid')) {
    if (!isset($vocs[$node->type])) {
      $vocs[$node->type] = taxonomy_get_vocabularies($node->type);
    }
    $tags = array();
    foreach ($terms as $tid => $term) {
      if ($vocs[$node->type][$term->vid]->tags) {
        $tags[$term->vid][$tid] = $term;
      }
    }
    return $tags;
  }
}

/**
 * API function that returns the tags of a node in fancy titled lists.
 *
 * @param $node
 *   A node object.
 */
function tagadelic_tags_lists($node) {
  if (is_array($node->tags)) {
    $output = '';
    foreach ($node->tags as $vid => $terms) {
      $vocabulary = taxonomy_vocabulary_load($vid);
      $title = l($vocabulary->name, "tagadelic/chunk/{$vid}");
      $items = array();
      foreach ($terms as $term) {
        $items[] = l($term->name, taxonomy_term_path($term), array(
          'attributes' => array(
            'title' => t('view all posts tagged with "@tag"', array(
              '@tag' => $term->name,
            )),
          ),
        ));
      }
      $output .= theme('item_list', $items, $title);
    }
    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 sill 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 tagadelic_get_weighted_tags($vids, $steps = 6, $size = 60) {
  $tags = array();

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

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

  // Make sure cache has data.
  if (isset($cache->data)) {
    $tags = $cache->data;
  }
  else {
    if (!is_array($vids) || count($vids) == 0) {
      return array();
    }
    $result = db_query_range(db_rewrite_sql('SELECT COUNT(*) AS count, td.tid, td.vid, td.name, td.description FROM {term_data} td INNER JOIN {term_node} tn ON td.tid = tn.tid INNER JOIN {node} n ON tn.vid = n.vid WHERE td.vid IN (' . db_placeholders($vids) . ') AND n.status = 1 GROUP BY td.tid, td.vid, td.name, td.description HAVING COUNT(*) > 0 ORDER BY count DESC'), $vids, 0, $size);
    $tags = tagadelic_build_weighted_tags($result, $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 $result
 *   A query result, any query result that contains an <em>object</em> with the
 *   following attributes: $tag->count, $tag->tid, $tag->name and $tag->vid.
 *   Refer to tagadelic_get_weighted_tags() for an example.
 * @param $steps
 *   The amount of tag-sizes you will be using. If you give "12" you sill 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 tagadelic_build_weighted_tags($result, $steps = 6) {

  // Find minimum and maximum log-count. By our MatheMagician Steven Wittens aka
  // UnConeD.
  $tags = array();
  $min = 1000000000.0;
  $max = -1000000000.0;
  while ($tag = db_fetch_object($result)) {
    $tag->number_of_posts = $tag->count;
    $tag->count = log($tag->count);
    $min = min($min, $tag->count);
    $max = max($max, $tag->count);
    $tags[$tag->tid] = $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 as $key => $value) {
    $tags[$key]->weight = 1 + floor($steps * ($value->count - $min) / $range);
  }
  return $tags;
}

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

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

/**
 * Callback for usort, sort by weight.
 */
function _tagadelic_sort_by_weight($a, $b) {
  if ($a->weight == $b->weight) {

    // Ensure correct order when same weight
    return $a->count > $b->count;
  }
  return $a->weight > $b->weight;
}

/**
 * Theme function that renders the HTML for the tags.
 *
 * @ingroup themable
 */
function theme_tagadelic_weighted($terms) {
  $output = '';
  foreach ($terms as $term) {
    $output .= l($term->name, taxonomy_term_path($term), array(
      'attributes' => array(
        'class' => "tagadelic level{$term->weight}",
        'rel' => 'tag',
        'title' => $term->description,
      ),
    )) . " \n";
  }
  return $output;
}

/**
 * Theme function that renders an entry in tagadelic/list/ views.
 *
 * @param $vocabulary
 *   A full vocabulary object.
 * @param $tags
 *   An array with weigthed tag objects.
 *
 * @ingroup themable
 */
function theme_tagadelic_list_box($vocabulary, $tags) {
  $output = '';
  $content = theme('tagadelic_weighted', $tags);
  if ($vocabulary->description) {
    $content = theme("box", NULL, $vocabulary->description) . $content;
  }
  $output .= theme('box', $vocabulary->name, $content);
  return $output;
}

/**
 * Theme function to provide a more link.
 *
 * @param $vid
 *   Vocabulary id for which more link is wanted. Optional, if left empty, no
 *   vocabulary is expected, and the more-link will point to the main tagadelic
 *   page at /tagadelic instead.
 *
 * @ingroup themable
 */
function theme_tagadelic_more($vid = NULL) {
  return "<div class='more-link'>" . l(t('more tags'), $vid ? "tagadelic/chunk/{$vid}" : "tagadelic") . "</div>";
}

/**
 * Implements hook_block().
 */
function tagadelic_block($op = 'list', $delta = 0, $edit = array()) {
  $blocks = array();
  if ($op == 'view') {
    if ($voc = taxonomy_vocabulary_load($delta)) {
      $blocks['subject'] = variable_get('tagadelic_block_title_' . $delta, t('Tags in @voc', array(
        '@voc' => $voc->name,
      )));
      $tags = tagadelic_get_weighted_tags(array(
        $voc->vid,
      ), variable_get('tagadelic_levels', 6), variable_get('tagadelic_block_tags_' . $delta, 12));
      $tags = tagadelic_sort_tags($tags);

      // Return a chunk of 12 tags
      $blocks['content'] = theme('tagadelic_weighted', $tags);
      if (count($tags) >= variable_get('tagadelic_block_tags_' . $delta, 12)) {

        // Add more link
        $blocks['content'] .= theme('tagadelic_more', $voc->vid);
      }
    }
    elseif ($delta == 'all') {
      $blocks['subject'] = t('Tags');
      $tags = tagadelic_get_weighted_tags(array_keys(taxonomy_get_vocabularies()), variable_get('tagadelic_levels', 6), variable_get('tagadelic_block_tags_' . $delta, 12));
      $tags = tagadelic_sort_tags($tags);
      $blocks['content'] = theme('tagadelic_weighted', $tags);
      if (count($tags) >= variable_get('tagadelic_block_tags_' . $delta, 12)) {

        // Add more link
        $blocks['content'] .= theme('tagadelic_more');
      }
    }
    elseif (arg(0) == 'node' && is_numeric(arg(1)) && ($node = node_load(arg(1)))) {
      $blocks['subject'] = t('Tags for @title', array(
        '@title' => $node->title,
      ));
      $blocks['content'] = tagadelic_tags_lists($node);
    }
    return $blocks;
  }
  elseif ($op == 'list') {
    foreach (taxonomy_get_vocabularies() as $voc) {
      $blocks[$voc->vid]['info'] = t('Tags in @voc', array(
        '@voc' => $voc->name,
      ));
      $blocks[$voc->vid]['cache'] = BLOCK_CACHE_GLOBAL;
    }
    $blocks[0]['info'] = t('Tags for the current post');
    $blocks[0]['cache'] = BLOCK_CACHE_PER_PAGE;
    $blocks['all']['info'] = t('Tags for all vocabularies');
    $blocks['all']['cache'] = BLOCK_CACHE_GLOBAL;
    return $blocks;
  }
  elseif ($op == 'configure') {
    $voc = taxonomy_vocabulary_load($delta);
    $form = array();
    $form['tags'] = array(
      '#type' => 'textfield',
      '#title' => t('Tags to show'),
      '#default_value' => variable_get('tagadelic_block_tags_' . $delta, 12),
      '#maxlength' => 3,
      '#description' => t('The number of tags to show in this block.'),
    );
    return $form;
  }
  elseif ($op == 'save') {
    variable_set('tagadelic_block_tags_' . $delta, $edit['tags']);
    return;
  }
}

/**
 * Implements hook_theme().
 */
function tagadelic_theme() {
  return array(
    'tagadelic_list_box' => array(
      'arguments' => array(
        'vocabulary' => NULL,
        'tags' => NULL,
      ),
    ),
    'tagadelic_more' => array(
      'arguments' => array(
        'vid' => NULL,
      ),
    ),
    'tagadelic_weighted' => array(
      'arguments' => array(
        'terms' => NULL,
      ),
    ),
    'tagadelic_list_vocs' => array(
      'arguments' => array(
        'vid' => NULL,
      ),
    ),
  );
}

Functions

Namesort descending Description
tagadelic_block Implements hook_block().
tagadelic_build_weighted_tags API that returns an array with weighted tags.
tagadelic_get_weighted_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.
tagadelic_help Implements hook_help().
tagadelic_init Implements hook_init().
tagadelic_menu Implements hook_menu().
tagadelic_nodeapi Implements hook_nodeapi().
tagadelic_node_get_terms API that returns a multidimensional array with tags given a node.
tagadelic_page_chunk Menu callback renders a tagadelic page.
tagadelic_page_list Menu callback renders a tagadelic page with listed items: each vocabulary.
tagadelic_page_title_callback Menu callback to render the tagadelic title.
tagadelic_settings Menu callback. Admin setting page for tagadelic.
tagadelic_sort_tags API function to order a set of tags.
tagadelic_tags_lists API function that returns the tags of a node in fancy titled lists.
tagadelic_theme Implements hook_theme().
tagadelic_vocs_load Menu wildcard callback.
theme_tagadelic_list_box Theme function that renders an entry in tagadelic/list/ views.
theme_tagadelic_list_vocs Theme function that creates a comma separated list of vocabulary terms.
theme_tagadelic_more Theme function to provide a more link.
theme_tagadelic_weighted Theme function that renders the HTML for the tags.
_tagadelic_sort_by_title Callback for usort, sort by count.
_tagadelic_sort_by_weight Callback for usort, sort by weight.