highcharttable.module in HighchartTable 7
Uses the Highcharts javascript library and HighchartTable jQuery add-on to decorate any HTML table on your site with a graphical visualisation as a colourful chart. Does NOT require Views.
File
highcharttable.moduleView source
<?php
/**
* @file
* highcharttable.module
*
* Uses the Highcharts javascript library and HighchartTable jQuery add-on to
* decorate any HTML table on your site with a graphical visualisation as a
* colourful chart.
* Does NOT require Views.
*/
define('HIGHCHARTTABLE_DEFAULT_TABLE_SELECTOR', 'table');
define('HIGHCHARTTABLE_CONTEXTUAL_LINKS_EXCLUDE_PAGES', '
admin/*
~admin/reports/access-denied
~admin/reports/page*
~admin/reports/referrers
~admin/reports/search
~admin/reports/visitors');
/**
* Implements hook_preprocess_page().
*
* Many hooks qualify for what we do here...
*/
function highcharttable_preprocess_page($variables) {
$module_path = drupal_get_path('module', 'highcharttable');
// Add some Javascript, with a group and weight such that it will run before
// modules/contextual/contextual.js
// We need this on all pages because we do not know a-priori whether the
// page to be loaded will have HTML tables on it or not -- that is exactly
// what this Javascript figures out!
$js = $module_path . '/js/highcharttable.js';
drupal_add_js($js, array(
'group' => JS_LIBRARY,
'weight' => -1,
));
$global_settings = variable_get('highcharttable_global_settings', array());
$path = current_path();
if (user_access('configure chart decorations')) {
$exclude_pages = isset($global_settings['contextual-links-exclude-pages']) ? $global_settings['contextual-links-exclude-pages'] : HIGHCHARTTABLE_CONTEXTUAL_LINKS_EXCLUDE_PAGES;
if (highcharttable_match_path($path, $exclude_pages)) {
$global_settings['contextual-links'] = 'off';
}
}
else {
$global_settings['contextual-links'] = 'off';
}
if ($global_settings['contextual-links'] != 'off') {
drupal_add_css($module_path . '/css/highcharttable.css');
if ($global_settings['contextual-links'] == '') {
// Default core-style contexutual links behaviour.
// These lines will do nothing, if already included by core Contextual.
$contextual_path = drupal_get_path('module', 'contextual');
drupal_add_css($contextual_path . '/contextual.css');
drupal_add_js($contextual_path . '/contextual.js');
}
}
// First, pass the global settings to the js.
drupal_add_js(array(
'highcharttable' => array(
'global' => $global_settings,
),
), array(
'type' => 'setting',
));
// ... now pass the decorations too.
$path_alias = drupal_get_path_alias($path);
$decoration_settings = variable_get('highcharttable_decorations', array());
foreach ($decoration_settings as $decoration) {
if (!empty($decoration['pages-and-selector'])) {
$include_pages = $decoration['pages-and-selector']['include-pages'];
if (highcharttable_match_path($path, $include_pages) || highcharttable_match_path($path_alias, $include_pages)) {
$settings = highcharttable_get_js_settings($global_settings, $decoration);
drupal_add_js(array(
'highcharttable' => $settings,
), array(
'type' => 'setting',
));
if (empty($is_loaded)) {
$variant = empty($global_settings['use-patched-library']) ? NULL : 'patched';
highcharttable_load_libs($variant);
$is_loaded = TRUE;
}
}
}
}
}
/**
* Set up an array of configurations for the DataTables JS call.
*
* @param array $decoration
* An array of HighchartTable jQuery settings, indexed by the table selector
*/
function highcharttable_get_js_settings($global_settings, $decoration) {
$table_selector = empty($decoration['pages-and-selector']['table-selector']) ? HIGHCHARTTABLE_DEFAULT_TABLE_SELECTOR : $decoration['pages-and-selector']['table-selector'];
$decoration_params = empty($decoration['decoration-params']) ? array() : $decoration['decoration-params'];
$chart_type = isset($decoration_params['chart-type']) ? $decoration_params['chart-type'] : NULL;
$settings = array(
$table_selector => array(
'animation' => empty($global_settings['animation']) ? NULL : $global_settings['animation'],
'color-1' => empty($decoration_params['color-1']) ? NULL : $decoration_params['color-1'],
'color-2' => empty($decoration_params['color-2']) ? NULL : $decoration_params['color-2'],
'color-3' => empty($decoration_params['color-3']) ? NULL : $decoration_params['color-3'],
'container-before' => 1,
'datalabels-enabled' => !empty($decoration_params['labels']),
'height' => empty($decoration_params['height']) ? NULL : (int) $decoration_params['height'],
'hide-table' => !empty($decoration['pages-and-selector']['hide-table']),
'inverted' => !empty($decoration_params['swap-axes']),
'legend-disabled' => empty($decoration_params['legend']),
'legend-layout' => empty($decoration_params['legend']) ? NULL : $decoration_params['legend'],
'pie-show-in-legend' => $chart_type == 'pie' && !empty($decoration_params['legend']),
'subtitle-text' => empty($decoration_params['subtitle']) ? NULL : filter_xss_admin($decoration_params['subtitle']),
'suppress-invalid-series' => isset($decoration_params['suppress-invalid-series']) ? $decoration_params['suppress-invalid-series'] : 2,
'type' => $chart_type,
'xaxis' => empty($decoration_params['xaxis']) ? 0 : max(0, (int) $decoration_params['xaxis'] - 1),
// Only doing formatter for primary yaxis, ignoring opposite yaxes.
'yaxis-1-formatter-callback' => empty($decoration_params['formatter']) ? NULL : $decoration_params['formatter'],
),
);
return $settings;
}
/**
* Include all required external javascript code.
*
* Highcharts JS and HighchartTable jQuery libraries.
*
* @param $variant, string
* Either NULL for the original version or 'patched' for the local version.
*/
function highcharttable_load_libs($variant) {
// Equivalent to core's drupal_add_library('highcharttable', 'highcharts'),
// this loads what is set up in the .libraries.info file, rather than core's
// hook_library(). The format of the array returned is similar.
$highcharts_lib = libraries_load('highcharts');
if (!empty($highcharts_lib['error'])) {
drupal_set_message($highcharts_lib['error message'], 'warning');
}
$highcharttable_lib = libraries_load('highcharttable', $variant);
if (!empty($highcharttable_lib['error'])) {
drupal_set_message($highcharttable_lib['error message'], 'warning');
}
}
/**
* Implements hook_menu().
*/
function highcharttable_menu() {
// Put the administrative settings, on the Configuration page, under Content
$items['admin/config/content/highcharttable'] = array(
'title' => 'HighchartTable',
'description' => 'Configure chart decorations and global settings.',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'highcharttable_admin_configure_form',
),
'access arguments' => array(
'configure chart decorations',
),
'file' => 'highcharttable.admin.inc',
);
$items['highcharttable/contextual-link'] = array(
'title' => 'HighchartTable contextual links',
'description' => 'HighchartTable contextual link',
'page callback' => 'highcharttable_contextual_link',
'access arguments' => array(
'configure chart decorations',
),
);
return $items;
}
/**
* Menu callback for highcharttable/contextual-link
*
* @param string $operation ($arg0)
* The operation to perform, one of 'insert', 'delete'.
* 'configure' is obsolete as this operation can be done via a direct link.
* @param string $chart_type ($arg1)
* The chart type, one of 'column', 'line', 'spline', 'area', 'pie'.
* Defaults to 'column'.
* @param string $arg2
* Optional argument, not used
* @param string $arg3
* Optional argument, not used
*/
function highcharttable_contextual_link($operation = NULL, $chart_type = NULL, $arg2 = NULL, $arg3 = NULL) {
$decorations = variable_get('highcharttable_decorations', array());
$page = filter_input(INPUT_GET, 'destination');
$url_options = array(
'query' => array(
'destination' => $page,
),
);
switch ($operation) {
case 'insert':
highcharttable_add_decoration($page, $chart_type, $decorations);
break;
case 'delete':
highcharttable_remove_page_from_decorations($page, $decorations);
break;
default:
// Not used. As there are no actions required for 'configure', link to
// this page directly, rather than using this function.
drupal_goto('admin/config/content/highcharttable', $url_options);
}
// Make sure to save with keys that are numeric, consecutive and start at 0.
variable_set('highcharttable_decorations', array_values($decorations));
// Having created/deleted the chart decoration we return where we came from.
drupal_goto($page, $url_options);
}
/**
* Add a new char decoration, using the supplied page path and chart type.
*
* @param string $page
* @param string $chart_type, defaults to 'column'
* @param array $decorations
*/
function highcharttable_add_decoration($page, $chart_type = NULL, &$decorations) {
$decoration = array(
'decoration-params' => array(
'chart-type' => empty($chart_type) ? 'column' : $chart_type,
),
'pages-and-selector' => array(
'include-pages' => $page,
'table-selector' => '',
),
);
$decorations[] = $decoration;
}
/**
* Removes the supplied page path from all decorations that use it.
*
* If as a result of this the decoration no longer has any 'include-pages', then
* the decoration is removed altogether.
*
* @param string $page
* @param array $decorations
*/
function highcharttable_remove_page_from_decorations($page, &$decorations) {
$url_options = array(
'query' => array(
'destination' => $page,
),
);
foreach ($decorations as $key => $decoration) {
$include_pages = trim($decoration['pages-and-selector']['include-pages']);
// Remove page path from 'include-pages', if found.
$reduced = str_replace($page, '', $include_pages);
if (strlen($reduced) < strlen($include_pages)) {
$is_deleted = TRUE;
if (empty($reduced)) {
unset($decorations[$key]);
drupal_set_message(t('Chart and chart decoration removed.'));
}
else {
$decorations[$key]['pages-and-selector']['include-pages'] = $reduced;
drupal_set_message(t('Chart removed. However, the underlying chart decoration was retained, as it appears to be used on other pages. Visit the <a href="@url_config">configuration page</a> to learn more.', array(
'@url_config' => url('admin/config/content/highcharttable', $url_options),
)));
}
}
}
if (empty($is_deleted)) {
drupal_set_message(t('The chart was not removed. Most likely this was because this page is part of a wild-card specification shared with other charts. Visit the <a href="@url_config">configuration page</a> to learn more.', array(
'@url_config' => url('admin/config/content/highcharttable', $url_options),
)));
}
}
/**
* Match a path against one or more regex patterns.
*
* A drop-in replacement for drupal_match_path(), this also handles pattern
* negation through the use of the twiddle/squiggle (~) character.
*
* Adapted from function context_condition_path::match() in the Context module.
*
* @param string $path
* The path string to be matched.
* @param string $patterns
* String containing a sequence of patterns separated by \n, \r or \r\n.
* Each pattern will be trimmed of leading spaces.
* Any pattern that begins with ~ is interpreted as an exclusion and overrides
* any match amongst patterns.
* Example:
* With $patterns = "admin/*\n~admin/reports/visitors", this function will
* return TRUE for all "admin/*" paths, except "admin/reports/visitors".
*/
function highcharttable_match_path($path, $patterns) {
$regexps =& drupal_static(__FUNCTION__, array());
$patterns = preg_split('/[\\r\\n]+/', $patterns);
$match = FALSE;
$positives = $negatives = 0;
foreach ($patterns as $pat) {
$pattern = trim($pat);
if (!empty($pattern)) {
if ($negate = drupal_substr($pattern, 0, 1) === '~') {
// Remove the tilde before executing a standard preg_match().
$pattern = drupal_substr($pattern, 1);
$negatives++;
}
else {
$positives++;
}
if (!isset($regexps[$pattern])) {
$regexps[$pattern] = '/^(' . preg_replace(array(
'/(\\r\\n?|\\n)/',
'/\\\\\\*/',
'/(^|\\|)\\\\<front\\\\>($|\\|)/',
), array(
'|',
'.*',
'\\1' . preg_quote(variable_get('site_frontpage', 'node'), '/') . '\\2',
), preg_quote($pattern, '/')) . ')$/';
}
if (preg_match($regexps[$pattern], $path)) {
if ($negate) {
return FALSE;
}
$match = TRUE;
}
}
}
// If there are *only* negative conditions and we got this far, we actually
// have a match.
if ($positives === 0 && $negatives > 0) {
return TRUE;
}
return $match;
}
/**
* Implements hook_permission().
*/
function highcharttable_permission() {
return array(
'configure chart decorations' => array(
'title' => t('Add and configure chart decorations'),
),
);
}
/**
* Implements hook_help().
*/
function highcharttable_help($path, $arg) {
switch ($path) {
case 'admin/help#highcharttable':
$t = t('Configuration instructions and tips are in this <a target="readme" href="@README">README</a> file.<br/>Known issues and solutions may be found on the <a taget="project" href="@highcharttable">HighchartTable</a> project page.', array(
'@README' => url(drupal_get_path('module', 'highcharttable') . '/README.txt'),
'@highcharttable' => url('http://drupal.org/project/highcharttable'),
));
break;
case 'admin/config/content/highcharttable':
$t = t('A <strong>chart decoration</strong> consists of a set of chart options, selected below. Apart from the features you wish to include in each chart decoration, you specify the pages the chart(s) should appear on.');
break;
}
return empty($t) ? '' : '<p>' . $t . '</p>';
}
/**
* Implements hook_libraries_info_file_paths().
*
* Using the .libraries.info files instead of hook_libraries_info().
*/
function highcharttable_libraries_info_file_paths() {
return array(
drupal_get_path('module', 'highcharttable') . '/libraries',
);
}
/**
* Implements hook_libraries_info_alter().
*
* This is a dynamic appendix to the .libraries.info files.
* Through the configuration page, the user may opt for a variant, used here.
*/
function highcharttable_libraries_info_alter(&$libraries) {
if (!isset($libraries['highcharttable'])) {
return;
}
// The d.o. packaging script unfortunately adds to the .libraries.info file
// the version number of the module in the same way as it does for
// highcharttables.info. This is bad news for us, as once set, the Libraries
// module will not attempt to read it from the specified files. So unset.
unset($libraries['highcharts']['version']);
unset($libraries['highcharttable']['version']);
$global_settings = variable_get('highcharttable_global_settings', array());
$variant = empty($global_settings['use-patched-library']) ? NULL : 'patched';
// Based on the variant, the version number is found in a different file.
// Not only is the file where we obtain the version different, so is its path.
// The default path is sites/all/libraries/highcharttable, the variant path
// is the module path.
if ($variant == 'patched') {
$libraries['highcharttable']['library path'] = drupal_get_path('module', 'highcharttable');
}
$libraries['highcharttable']['version arguments']['file'] = $variant == 'patched' ? 'libraries/variants/highchartTable-patched.jquery.json' : 'highchartTable.jquery.json';
}
/**
* Returns whether path matches any of the wildcards in decoration.
*
* @param string $path
* @param array $decoration
*
* @return bool
*/
function _highcharttable_path_matches_decoration($path, $decoration) {
return !empty($decoration['pages-and-selector']) && highcharttable_match_path($path, $decoration['pages-and-selector']['include-pages']);
}
Functions
Name![]() |
Description |
---|---|
highcharttable_add_decoration | Add a new char decoration, using the supplied page path and chart type. |
highcharttable_contextual_link | Menu callback for highcharttable/contextual-link |
highcharttable_get_js_settings | Set up an array of configurations for the DataTables JS call. |
highcharttable_help | Implements hook_help(). |
highcharttable_libraries_info_alter | Implements hook_libraries_info_alter(). |
highcharttable_libraries_info_file_paths | Implements hook_libraries_info_file_paths(). |
highcharttable_load_libs | Include all required external javascript code. |
highcharttable_match_path | Match a path against one or more regex patterns. |
highcharttable_menu | Implements hook_menu(). |
highcharttable_permission | Implements hook_permission(). |
highcharttable_preprocess_page | Implements hook_preprocess_page(). |
highcharttable_remove_page_from_decorations | Removes the supplied page path from all decorations that use it. |
_highcharttable_path_matches_decoration | Returns whether path matches any of the wildcards in decoration. |