sheetnode.module in Sheetnode 7
Same filename and directory in other branches
Module file for the sheetnode module.
File
sheetnode.moduleView source
<?php
/**
* @file
* Module file for the sheetnode module.
*/
/**
* Implements hook_node_info().
*/
function sheetnode_node_info() {
return array(
'sheetnode' => array(
'name' => t('Sheetnode'),
'module' => 'sheetnode',
'description' => t('A spreadsheet node.'),
'base' => 'sheetnode',
),
);
}
/**
* Implements hook_permission().
*/
function sheetnode_permission() {
return array(
'create sheetnode' => array(
'title' => t('Create new sheetnode'),
'description' => t('Create new sheetnodes.'),
),
'edit own sheetnode' => array(
'title' => t('Edit own sheetnode'),
'description' => t('Edit own sheetnodes.'),
),
'edit any sheetnode' => array(
'title' => t('Edit any sheetnode'),
'description' => t('Edit any sheetnodes.'),
),
'delete own sheetnode' => array(
'title' => t('Delete own sheetnode'),
'description' => t('Delete own sheetnodes.'),
),
'delete any sheetnode' => array(
'title' => t('Delete any sheetnode'),
'description' => t('Delete any sheetnodes.'),
),
'create sheetnode template' => array(
'title' => t('Create sheetnode template'),
'description' => t('Create sheetnode templates.'),
),
'edit sheetnode settings' => array(
'title' => t('Edit sheetnode settings'),
'description' => t('Modify sheetnode settings and formatting.'),
),
);
}
/**
* Implements hook_node_access().
*/
function sheetnode_node_access($op, $node, $account) {
if (is_string($node)) {
if ($node != 'sheetnode') {
return NODE_ACCESS_IGNORE;
}
}
if ($op == 'create') {
return user_access('create sheetnode', $account) ? NODE_ACCESS_ALLOW : NODE_ACCESS_DENY;
}
if ($op == 'update') {
return user_access('edit any sheetnode', $account) || user_access('edit own sheetnode', $account) && $account->uid == $node->uid ? NODE_ACCESS_ALLOW : NODE_ACCESS_DENY;
}
if ($op == 'delete') {
return user_access('delete any sheetnode', $account) || user_access('delete own sheetnode', $account) && $account->uid == $node->uid ? NODE_ACCESS_ALLOW : NODE_ACCESS_DENY;
}
}
/**
* Implements hook_load().
*/
function sheetnode_load($nodes) {
$vids = array();
foreach ($nodes as $node) {
$vids[] = $node->vid;
$nodes[$node->nid]->sheetnode = array(
'nid' => $node->nid,
'vid' => $node->vid,
'value' => NULL,
);
}
if (empty($vids)) {
return;
}
$sheetnodes = db_query("SELECT * FROM {sheetnode} WHERE vid IN (:vids)", array(
':vids' => $vids,
));
foreach ($sheetnodes as $sheetnode) {
$nodes[$sheetnode->nid]->sheetnode = (array) $sheetnode;
}
}
/**
* Implements hook_view().
*/
function sheetnode_view($node, $view_mode) {
// SocialCalc sheet.
if ($view_mode == 'full') {
$output = _sheetnode_inject(drupal_clean_css_identifier('sheetnode-' . $node->nid), 'sheetnode', $node->sheetnode['value'], FALSE, array(
'entity-type' => 'node',
'oid' => $node->nid,
));
$node->content['sheetnode'] = array(
'#markup' => $output,
'#weight' => -1,
);
}
return $node;
}
/**
* Implements hook_delete().
*/
function sheetnode_delete(&$node) {
db_delete('sheetnode')
->condition('nid', $node->nid)
->execute();
}
/**
* Implements hook_node_revision_delete().
*/
function sheetnode_node_revision_delete($node) {
if ($node->type == 'sheetnode') {
db_delete('sheetnode')
->condition('vid', $node->vid)
->execute();
}
}
/**
* Implements hook_form().
*/
function sheetnode_form($node, &$form_state) {
$types = node_type_get_types();
$type = $types['sheetnode'];
// Generate the default title.
$form = node_content_form(!empty($node) ? $node : 'sheetnode', $form_state);
// SocialCalc sheet.
if (!empty($form_state['input']['sheetnode']['value'])) {
$value = $form_state['input']['sheetnode']['value'];
$nid = @$node->nid;
}
elseif (!empty($node->nid)) {
$value = $node->sheetnode['value'];
$nid = $node->nid;
}
elseif (!empty($node->clone_from_original_nid)) {
// support node_clone.module
$original_node = node_load($node->clone_from_original_nid);
$value = $original_node->sheetnode['value'];
$nid = $node->clone_from_original_nid;
}
else {
$value = '';
$nid = NULL;
}
$output = _sheetnode_inject(drupal_clean_css_identifier('sheetnode-' . (empty($node->nid) ? 'new' : $node->nid)), 'sheetnode', $value, drupal_clean_css_identifier('edit-sheetnode-value'), array(
'entity-type' => 'node',
'oid' => $nid,
));
$form['sheetnode'] = array(
'#tree' => TRUE,
'#weight' => -1,
'socialcalc' => array(
'#markup' => $output,
),
'value' => array(
'#type' => 'hidden',
'#attributes' => array(
'id' => 'edit-sheetnode-value',
),
),
);
// Template.
if (user_access('create sheetnode template')) {
$form['sheetnode']['template'] = array(
'#type' => 'textfield',
'#title' => t('Save as template'),
'#description' => t('When saving this sheet, also keep a copy under the name you specify here. Later, this copy can be used as a template for other sheets.'),
'#required' => FALSE,
);
}
return $form;
}
/**
* Implements hook_validate().
*/
function sheetnode_validate($node, $form, &$form_state) {
$errors = form_get_errors();
if (!empty($errors)) {
_sheetnode_inject(drupal_clean_css_identifier('sheetnode-' . (empty($form['nid']['#value']) ? 'new' : $form['nid']['#value'])), 'sheetnode', $form['sheetnode']['value']['#value'], drupal_clean_css_identifier('edit-sheetnode-value'), array(
'entity-type' => 'node',
'oid' => $form['nid']['#value'],
));
}
}
/**
* Helper function to inject Sheetnode JavaScript.
*/
function _sheetnode_inject($sheet_id, $sheet_aliases, $value, $save_element, $context) {
$library_path = drupal_get_path('module', 'sheetnode');
$module_path = drupal_get_path('module', 'sheetnode');
drupal_add_js($library_path . '/socialcalc/formatnumber2.js', array(
'weight' => 1,
));
drupal_add_js($library_path . '/socialcalc/formula1.js', array(
'weight' => 2,
));
drupal_add_js($library_path . '/socialcalc/socialcalcconstants.js', array(
'weight' => 3,
));
drupal_add_js($library_path . '/socialcalc/socialcalc-3.js', array(
'weight' => 4,
));
drupal_add_js($library_path . '/socialcalc/socialcalctableeditor.js', array(
'weight' => 5,
));
drupal_add_js($library_path . '/socialcalc/socialcalcpopup.js', array(
'weight' => 6,
));
drupal_add_js($library_path . '/socialcalc/socialcalcspreadsheetcontrol.js', array(
'weight' => 7,
));
drupal_add_js($library_path . '/socialcalc/socialcalcviewer.js', array(
'weight' => 8,
));
drupal_add_js($module_path . '/js/sheetnode.js', array(
'weight' => 9,
));
drupal_add_css($library_path . '/socialcalc/socialcalc.css');
drupal_add_css($module_path . '/css/sheetnode.css');
// Allow other modules to add their own JS/CSS.
module_invoke_all('sheetnode_plugins', $value, $save_element, $context);
// Lower-case sheet aliases.
$sheet_aliases = array_map('strtolower', (array) $sheet_aliases);
static $sheets = NULL;
if (!isset($sheets[$sheet_id])) {
drupal_add_js(array(
'sheetnode' => array(
$sheet_id => array(
'aliases' => $sheet_aliases,
'value' => $value,
'imagePrefix' => base_path() . $library_path . '/socialcalc/images/sc-',
'containerElement' => $sheet_id,
'saveElement' => $save_element,
'viewMode' => variable_get('sheetnode_view_mode', SHEETNODE_VIEW_FIDDLE),
'showToolbar' => variable_get('sheetnode_view_toolbar', FALSE),
'permissions' => array(
'edit sheetnode settings' => user_access('edit sheetnode settings'),
),
'context' => $context,
),
),
), 'setting');
$sheets[$sheet_id] = TRUE;
}
return '<div class="sheetview" id="' . $sheet_id . '"></div>';
}
/**
* Implements hook_insert().
*/
function sheetnode_insert($node) {
if ($node->sheetnode['value']) {
_sheetnode_save($node->nid, $node->vid, $node->sheetnode['value']);
}
if ($node->sheetnode['template'] && user_access('create sheetnode template')) {
_sheetnode_template_save($node->vid, $node->sheetnode['template'], $node->sheetnode['value']);
}
}
/**
* Implements hook_update().
*/
function sheetnode_update($node) {
if (!empty($node->sheetnode['value'])) {
_sheetnode_save($node->nid, $node->vid, $node->sheetnode['value']);
}
elseif (!empty($node->revision)) {
// Reverting a revision.
$value = db_query("SELECT value FROM {sheetnode} WHERE vid = :old", array(
':old' => $node->old_vid,
))
->fetchField();
db_insert('sheetnode')
->fields(array(
'vid' => $node->vid,
'nid' => $node->nid,
'value' => $value,
))
->execute();
}
if (!empty($node->sheetnode['template']) && user_access('create sheetnode template')) {
_sheetnode_template_save($node->vid, $node->sheetnode['template'], $node->sheetnode['value']);
}
}
/**
* Implements hook_node_presave().
*/
function sheetnode_node_presave($node) {
if ($node->type == 'sheetnode' && isset($node->devel_generate)) {
// Generate random spreadsheets for sheetnodes.
$node->sheetnode = array(
'template' => NULL,
) + _sheetnode_devel_generate($node, NULL, NULL, NULL);
}
}
/**
* Implements hook_node_update_index().
*/
function sheetnode_node_update_index($node) {
$output = '';
if ($node->type == 'sheetnode') {
$output .= _sheetnode_update_index($node->sheetnode['value']);
}
foreach (sheetnode_get_sheetfields($node->type) as $field_name => $field) {
foreach ($node->{$field_name}[LANGUAGE_NONE] as $item) {
$output .= _sheetnode_update_index($item['value']);
}
}
return $output;
}
/**
* Helper function to return an indexable version of the spreadsheet.
*/
function _sheetnode_update_index($value) {
module_load_include('inc', 'sheetnode', 'socialcalc');
$output = '<table>';
$socialcalc = socialcalc_parse($value);
$sc = $socialcalc['sheet'];
$row = -1;
if (!empty($sc['cells'])) {
foreach ($sc['cells'] as $c) {
if ($c['pos'][1] > $row) {
// New row? - this assumes cells are ordered by col/row.
if ($row != -1) {
$output .= '</tr>';
}
$row = $c['pos'][1];
$output .= '<tr>';
}
$output .= '<td>' . (isset($c['datavalue']) ? $c['datavalue'] : ' ') . (isset($c['comment']) ? ' (' . check_plain($c['comment']) . ')' : '') . '</td>';
}
if ($row != -1) {
$output .= '</tr>';
}
$output .= '</table>';
return $output;
}
}
/**
* Implements hook_token_info().
*/
function sheetnode_token_info() {
$info['types']['sheet'] = array(
'name' => t('Sheet'),
'description' => t('Tokens related to sheets.'),
'needs-data' => 'sheet',
);
$info['tokens']['sheet']['cell'] = array(
'name' => t('Cell reference'),
'description' => t('A cell reference such as B52, C79, etc.'),
'dynamic' => TRUE,
);
$info['tokens']['node']['sheetnode'] = array(
'name' => t('Sheetnode'),
'description' => t('The sheet of the node.'),
'type' => 'sheet',
);
return $info;
}
/**
* Implements hook_tokens().
*/
function sheetnode_tokens($type, $tokens, array $data = array(), array $options = array()) {
$replacements = array();
$sanitize = !empty($options['sanitize']);
if ($type == 'sheet' && !empty($data['sheet'])) {
module_load_include('inc', 'sheetnode', 'socialcalc');
$socialcalc = socialcalc_parse($data['sheet']);
$sc = $socialcalc['sheet'];
foreach ($tokens as $name => $original) {
switch ($name) {
default:
list($token, $coord) = explode(':', $name, 2);
if ($token == 'cell' && isset($sc['cells'][$coord])) {
$c = $sc['cells'][$coord];
$replacements[$original] = isset($c['datavalue']) ? $c['datavalue'] : '';
}
break;
}
}
}
if ($type == 'node' && !empty($data['node']) && $data['node']->type == 'sheetnode') {
$node = $data['node'];
foreach ($tokens as $name => $original) {
switch ($name) {
case 'sheetnode':
$replacements[$original] = $node->sheetnode['value'];
break;
}
}
if ($sheet_tokens = token_find_with_prefix($tokens, 'sheetnode')) {
$replacements += token_generate('sheet', $sheet_tokens, array(
'sheet' => $node->sheetnode['value'],
), $options);
}
}
return $replacements;
}
/**
* Implements hook_token_info_alter().
*/
function sheetnode_token_info_alter(&$info) {
$fields = field_info_fields();
foreach ($info['tokens']['node'] as $name => &$token_info) {
if (isset($fields[$name]) && $fields[$name]['type'] == 'sheetfield') {
$token_info['type'] = 'sheet';
}
}
}
/**
* Implements hook_tokens_alter().
*/
function sheetnode_tokens_alter(array &$replacements, array $context) {
if ($context['type'] == 'node' && !empty($context['data']['node'])) {
$node = $context['data']['node'];
$tokens = $context['tokens'];
foreach (sheetnode_get_sheetfields($node->type) as $field_name => $field_info) {
if ($sheet_tokens = token_find_with_prefix($tokens, $field_name)) {
// TODO: how to handle multi-valued fields here?
$replacements += token_generate('sheet', $sheet_tokens, array(
'sheet' => $node->{$field_name}[LANGUAGE_NONE][0]['value'],
), $context['options']);
}
}
}
}
/**
* Implements hook_menu().
*/
function sheetnode_menu() {
$items = array();
$items['sheetnode/load'] = array(
'type' => MENU_CALLBACK,
'page callback' => '_sheetnode_ajax_load',
'access arguments' => array(
'access content',
),
);
$items['sheetnode/field'] = array(
'type' => MENU_CALLBACK,
'page callback' => '_sheetnode_ajax_field',
'access arguments' => array(
'access content',
),
);
$items['sheetnode/token'] = array(
'type' => MENU_CALLBACK,
'page callback' => '_sheetnode_ajax_token',
'access arguments' => array(
'access content',
),
);
$items['node/add/sheetnode_template'] = array(
'title' => 'Sheetnode import from template',
'access arguments' => array(
'create sheetnode',
),
'page callback' => 'drupal_get_form',
'page arguments' => array(
'sheetnode_import_template',
),
'description' => 'Create a new sheetnode from an existing template.',
);
$items['admin/config/content/sheetnode'] = array(
'title' => 'Sheetnode',
'access arguments' => array(
'administer site configuration',
),
'page callback' => 'drupal_get_form',
'page arguments' => array(
'sheetnode_admin_settings',
),
'description' => 'Configure sheetnodes and sheetnode components.',
'type' => MENU_NORMAL_ITEM,
);
$items['admin/config/system/content/general'] = array(
'title' => 'General',
'weight' => -10,
'type' => MENU_DEFAULT_LOCAL_TASK,
);
return $items;
}
define('SHEETNODE_VIEW_READONLY', 0);
define('SHEETNODE_VIEW_FIDDLE', 1);
define('SHEETNODE_VIEW_HTML', 2);
/**
* Form function for admin/settings/sheetnode.
*/
function sheetnode_admin_settings() {
$form['sheetnode_view_mode'] = array(
'#type' => 'radios',
'#title' => t('View mode'),
'#description' => t('Select the way sheetnodes should be displayed in view mode.'),
'#options' => array(
SHEETNODE_VIEW_READONLY => t('Read-only spreadsheet'),
SHEETNODE_VIEW_FIDDLE => t('Fiddle mode (interactive spreadsheet without save functionality)'),
SHEETNODE_VIEW_HTML => t('HTML table'),
),
'#default_value' => variable_get('sheetnode_view_mode', SHEETNODE_VIEW_FIDDLE),
);
$form['sheetnode_view_toolbar'] = array(
'#type' => 'checkbox',
'#title' => t('Show toolbar in view mode'),
'#default_value' => variable_get('sheetnode_view_toolbar', FALSE),
);
return system_settings_form($form);
}
/**
* Form function for node/add/sheetnode_template.
*/
function sheetnode_import_template($form, &$form_state, $tid = NULL) {
if (!empty($tid)) {
$form_state['values']['template'] = $tid;
sheetnode_import_template_submit($form, $form_state);
drupal_goto($form_state['redirect']);
}
$options[0] = t('- Select a template -');
$templates = db_query("SELECT tid, name FROM {sheetnode_template}");
foreach ($templates as $template) {
$options[$template->tid] = $template->name;
}
$form['template'] = array(
'#type' => 'select',
'#title' => t('Template'),
'#description' => t('Please select the template to load into your new sheetnode.'),
'#options' => $options,
'#default_value' => 0,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
/**
* Validate function for sheetnode_import_template form.
*/
function sheetnode_import_template_validate($form, &$form_state) {
if (!$form_state['values']['template']) {
form_set_error('template', t('Please select a template.'));
}
}
/**
* Submit function for sheetnode_import_template form.
*/
function sheetnode_import_template_submit($form, &$form_state) {
global $user;
module_load_include('inc', 'node', 'node.pages');
$template = _sheetnode_template_load($form_state['values']['template']);
$node = new StdClass();
$node->type = 'sheetnode';
node_object_prepare($node);
$node->title = $template->name;
$node->name = $user->name;
$node->language = LANGUAGE_NONE;
$node->sheetnode['value'] = $template->value;
$node->sheetnode['template'] = NULL;
// Let other modules alter the sheetnode or do other work.
$context = array(
'template' => $template,
);
drupal_alter('sheetnode_import', $node, $context);
// Save the sheetnode.
$node = node_submit($node);
node_save($node);
if (!empty($node->nid)) {
$form_state['redirect'] = 'node/' . $node->nid . '/edit';
}
}
/**
* Implements hook_views_api().
*/
function sheetnode_views_api() {
return array(
'api' => 2.0,
);
}
define('SHEETNODE_RANGE_LABELS_ROW', 1);
define('SHEETNODE_RANGE_LABELS_COL', 2);
define('SHEETNODE_RANGE_LABELS_ROW_COL', 3);
/**
* Implements hook_theme().
*/
function sheetnode_theme($existing, $type, $theme, $path) {
return array(
'sheetnode_range' => array(
'variables' => array(
'range' => NULL,
'labels' => SHEETNODE_RANGE_LABELS_ROW_COL,
),
),
);
}
/**
* Theme function for sheetnode_range.
*/
function theme_sheetnode_range($variables) {
$range = $variables['range'];
$labels = $variables['labels'];
if (empty($range)) {
return '';
}
module_load_include('inc', 'sheetnode', 'socialcalc');
list($c, $r) = socialcalc_coord_to_cr(key($range));
$row = array();
if ($labels & SHEETNODE_RANGE_LABELS_ROW) {
$row[] = array(
'data' => $r,
'header' => TRUE,
);
}
$header = array();
if ($labels & SHEETNODE_RANGE_LABELS_ROW) {
$header[] = array();
}
$rows = array();
foreach ($range as $coord => $value) {
$pos = socialcalc_coord_to_cr($coord);
if ($pos[1] > $r) {
$rows[] = $row;
$r = $pos[1];
$row = array();
if ($labels & SHEETNODE_RANGE_LABELS_ROW) {
$row[] = array(
'data' => $r,
'header' => TRUE,
);
}
}
$row[] = $value;
if (empty($rows)) {
$coord = socialcalc_cr_to_coord($pos[0], $pos[1], TRUE);
$header[] = $coord[0];
}
}
$rows[] = $row;
return theme('table', array(
'header' => $labels & SHEETNODE_RANGE_LABELS_COL ? $header : NULL,
'rows' => $rows,
));
}
/**
* Implements hook_field_extra_fields().
*/
function sheetnode_field_extra_fields() {
$extra['node']['sheetnode'] = array(
'form' => array(
'sheetnode' => array(
'label' => t('Spreadsheet'),
'description' => t('Spreadsheet control'),
'weight' => -1,
),
),
'display' => array(
'sheetnode' => array(
'label' => t('Spreadsheet'),
'description' => t('Spreadsheet control'),
'weight' => -1,
),
),
);
return $extra;
}
/**
* Implements hook_field_info().
*/
function sheetnode_field_info() {
return array(
'sheetfield' => array(
'label' => t('Spreadsheet'),
'description' => t('Store a spreadsheet.'),
'default_widget' => 'sheetfield_spreadsheet',
'default_formatter' => 'sheetfield_default',
'translatable' => FALSE,
),
);
}
/**
* Implements hook_node_validate().
*
* Catch form errors to re-initialize sheetfields.
*/
function sheetnode_node_validate($node, $form, &$form_state) {
$errors = form_get_errors();
if (!empty($errors) && !empty($form_state['field'])) {
foreach ($form_state['field'] as $field_name => $field) {
if ($field[LANGUAGE_NONE]['field']['type'] == 'sheetfield' && !empty($form_state['input'][$field_name])) {
foreach ($form_state['input'][$field_name][LANGUAGE_NONE] as $delta => $item) {
$element_id = drupal_clean_css_identifier('edit-' . $field_name . '-' . LANGUAGE_NONE . '-' . $delta);
$output = _sheetnode_inject(drupal_clean_css_identifier('sheetfield-' . $element_id), _sheetnode_sheetfield_aliases($field_name, $field[LANGUAGE_NONE]['instance']['label'], $item, $delta), $item['value'], $element_id, array(
'entity-type' => 'node',
'oid' => @$node->nid,
));
}
}
}
}
}
/**
* Implements hook_field_is_empty().
*/
function sheetnode_field_is_empty($item, $field) {
if (empty($item['value'])) {
return TRUE;
}
module_load_include('inc', 'sheetnode', 'socialcalc');
$sc = socialcalc_parse($item['value']);
return empty($sc['sheet']['cells']);
}
/**
* Implements hook_devel_generate().
*/
function sheetnode_devel_generate($object, $field, $instance, $bundle) {
if (field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_CUSTOM) {
return devel_generate_multiple('_sheetnode_devel_generate', $object, $field, $instance, $bundle);
}
else {
return _sheetnode_devel_generate($object, $field, $instance, $bundle);
}
}
/**
* Helper to generate random spreadsheet.
*/
function _sheetnode_devel_generate($object, $field, $instance, $bundle) {
module_load_include('inc', 'sheetnode', 'socialcalc');
$sc = array(
'edit' => array(),
'audit' => array(),
'sheet' => array(),
);
for ($row = 1; $row < mt_rand(10, 50); $row++) {
$sc['sheet']['cells'][] = array(
'pos' => array(
1,
$row,
),
'datavalue' => devel_generate_word(mt_rand(6, 12)),
'datatype' => 't',
'valuetype' => 't',
);
$sc['sheet']['cells'][] = array(
'pos' => array(
2,
$row,
),
'datavalue' => _sheetnode_lcg_randf(-100, 100),
'datatype' => 'v',
'valuetype' => 'n',
);
}
$sc['edit']['ecell']['coord'] = 'A1';
return array(
'value' => socialcalc_save($sc),
);
}
/**
* Helper function to return random number using combined linear congruential generator.
*/
function _sheetnode_lcg_randf($min, $max) {
return $min + lcg_value() * abs($max - $min);
}
/**
* Implements hook_field_formatter_info().
*/
function sheetnode_field_formatter_info() {
$formatters = array(
'sheetfield_default' => array(
'label' => t('Default'),
'field types' => array(
'sheetfield',
),
),
);
if (module_exists('date')) {
$formatters['socialcalc_date'] = array(
'label' => t('SocialCalc'),
'field types' => array(
'date',
'datetime',
'datestamp',
),
);
}
return $formatters;
}
/**
* Implements hook_field_formatter_view().
*/
function sheetnode_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
$element = array();
switch ($display['type']) {
case 'sheetfield_default':
foreach ($items as $delta => $item) {
$output = '';
if (!empty($item['name'])) {
$output .= '<div class="sheetfield-title">' . $item['name'] . '</div>';
}
$output .= _sheetnode_inject(drupal_clean_css_identifier('sheetfield-' . $field['field_name'] . '-' . $langcode . '-' . $delta), _sheetnode_sheetfield_aliases($field['field_name'], $instance['label'], $item, $delta), $item['value'], FALSE, isset($entity) && $entity == 'node' ? array(
'entity-type' => $entity_type,
'oid' => @$entity->nid,
) : NULL);
$element[$delta] = array(
'#markup' => $output,
);
}
break;
case 'socialcalc_date':
module_load_include('inc', 'sheetnode', 'socialcalc');
foreach ($items as $delta => $item) {
$value = $item['value'];
$timezone = isset($item['timezone']) ? $item['timezone'] : '';
$timezone = date_get_timezone($field['settings']['tz_handling'], $timezone);
$timezone_db = date_get_timezone_db($field['settings']['tz_handling']);
$db_format = date_type_format($field['type']);
$date = new DateObject($value, $timezone_db, $db_format);
$date
->limitGranularity($field['settings']['granularity']);
date_timezone_set($date, timezone_open($timezone));
$element[$delta] = array(
'#markup' => socialcalc_import_date($date),
);
}
break;
}
return $element;
}
/**
* Implements hook_field_widget_info().
*/
function sheetnode_field_widget_info() {
return array(
'sheetfield_spreadsheet' => array(
'label' => t('Spreadsheet'),
'field types' => array(
'sheetfield',
),
),
);
}
/**
* Implements hook_field_widget_form().
*/
function sheetnode_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
$widget = $element;
$widget['#delta'] = $delta;
$defaults = isset($items[$delta]) ? $items[$delta] : array(
'name' => '',
'value' => '',
);
switch ($instance['widget']['type']) {
case 'sheetfield_spreadsheet':
$element_id = drupal_clean_css_identifier('edit-' . $element['#field_name'] . '-' . $langcode . '-' . $delta);
$output = _sheetnode_inject(drupal_clean_css_identifier('sheetfield-' . $element_id), _sheetnode_sheetfield_aliases($element['#field_name'], $instance['label'], $defaults, $delta), $defaults['value'], $element_id, isset($form['#node']) ? array(
'entity-type' => 'node',
'oid' => @$form['#node']->nid,
) : NULL);
$widget['name'] = array(
'#type' => 'textfield',
'#title' => t('Title'),
'#default_value' => @$defaults['name'],
);
$widget['value'] = array(
'#type' => 'hidden',
'#attributes' => array(
'id' => $element_id,
),
'#prefix' => $output,
);
break;
}
return $widget;
}
/**
* Implements hook_form_alter().
*/
function sheetnode_form_alter(&$form, $form_state, $form_id) {
// Sheetfield settings:
// * Insert extra template element for default values.
if ($form_id == 'field_ui_field_edit_form' && $form['#field']['type'] == 'sheetfield') {
$options[0] = t('- Select a template -');
$templates = db_query("SELECT tid, name FROM {sheetnode_template}");
foreach ($templates as $template) {
$options[$template->tid] = $template->name;
}
$form['instance']['default_value_widget']['sheetfield_template'] = array(
'#type' => 'select',
'#title' => t('Template'),
'#description' => t('The template to load into your new sheetfield. This setting will override the sheet above but will be overridden by the PHP code below, if any.'),
'#options' => $options,
'#default_value' => variable_get('sheetfield_template_' . $form['#field']['field_name'], 0),
'#weight' => 1,
);
array_unshift($form['#submit'], '_sheetnode_field_ui_field_edit_form_submit');
}
}
/**
* Submit function for field_ui_field_edit_form form.
*/
function _sheetnode_field_ui_field_edit_form_submit($form, &$form_state) {
if (!empty($form_state['values']['sheetfield_template'])) {
$template = _sheetnode_template_load($form_state['values']['sheetfield_template']);
if (!empty($template)) {
// WARNING: LANGUAGE_NONE is an assumption.
$form_state['values'][$form['#field']['field_name']][LANGUAGE_NONE][0]['value'] = $template->value;
}
}
variable_set('sheetfield_template_' . $form['#field']['field_name'], $form_state['values']['sheetfield_template']);
}
/**
* Implements hook_element_info().
*/
function sheetnode_element_info() {
return array(
'spreadsheet' => array(
'#input' => TRUE,
'#process' => array(
'_sheetnode_spreadsheet_process',
),
),
);
}
/**
* Process function for spreadsheet element.
*/
function _sheetnode_spreadsheet_process($element, &$form_state, $form) {
$value = $element['#value'];
$output = _sheetnode_inject(drupal_clean_css_identifier('sheetfield-' . $element['#id']), array(
$element['#name'],
$element['#title'],
), $value, drupal_clean_css_identifier($element['#id']), NULL);
$element['#tree'] = TRUE;
$element['value'] = array(
'#type' => 'hidden',
'#attributes' => array(
'id' => drupal_clean_css_identifier($element['#id']),
),
'#prefix' => $output,
);
return $element;
}
/**
* API function to return a sheet given a name.
*/
function sheetnode_find_sheet($sheetname) {
module_load_include('inc', 'sheetnode', 'socialcalc');
$args = explode('/', $sheetname);
$sheetname = array_shift($args);
// No value found by default.
$value = NULL;
$title = NULL;
// Try to find a node.
$node = NULL;
if (is_numeric($sheetname)) {
$node = node_load(intval($sheetname));
}
else {
$nid = db_query("SELECT nid FROM {node} WHERE title = :title LIMIT 1", array(
':title' => $sheetname,
))
->fetchField();
if ($nid) {
$node = node_load($nid);
}
}
// Do we have access to this node?
if ($node && !node_access('view', $node)) {
return array(
NULL,
NULL,
);
}
// Is it a sheetnode?
if ($node && $node->type === 'sheetnode' && empty($args[0])) {
$value = $node->sheetnode['value'];
$title = $node->title;
}
// Is it a sheetfield?
if (!$value && $node) {
$target_field = empty($args[0]) ? FALSE : $args[0];
$target_delta = empty($args[1]) ? 0 : intval($args[1]);
foreach (sheetnode_get_sheetfields($node->type) as $field_name => $field_info) {
// Check that it's the field we want:
// * It's accessible to this user.
// * Its label matches the input.
// * Its delta matches the input.
if (field_access('view', $field_info, 'node', $node) && (!$target_field || 0 === strcasecmp($target_field, $field_name) || 0 === strcasecmp($target_field, $field_info['instance']['label']) || 0 === strcasecmp($target_field, @$node->{$field_name}[LANGUAGE_NONE][$target_delta]['name'])) && !empty($node->{$field_name}[LANGUAGE_NONE][$target_delta])) {
$value = $node->{$field_name}[LANGUAGE_NONE][$target_delta]['value'];
$title = trim(sprintf("%s %s %s", $node->title, $field_info['instance']['label'], isset($node->{$field_name}[LANGUAGE_NONE][$target_delta]['name']) ? $node->{$field_name}[LANGUAGE_NONE][$target_delta]['name'] : ($target_delta ? strval($target_delta) : '')));
break;
}
}
}
// Is it a sheetview?
if (!$value && module_exists('views')) {
// Try a view feed with our SocialCalc output plugin style.
$view = views_get_view($sheetname);
if ($view) {
foreach (array_keys($view->display) as $display_id) {
$display = $view->display[$display_id];
if (isset($display->display_options['style_plugin']) && $display->display_options['style_plugin'] === 'sheet_raw' && $view
->access($display_id)) {
if (!empty($args)) {
$view
->set_arguments($args);
}
$value = $view
->render($display_id);
$title = $display->handler
->get_option('title');
// Our style plugin sets this to text/plain, so reset it.
drupal_add_http_header('Content-type', 'text/html; charset=utf-8');
}
}
}
}
return array(
$value,
$title,
);
}
/**
* AJAX function to return a sheet value.
*/
function _sheetnode_ajax_load($sheetname = NULL) {
// Parse the sheetname which might contain extra args separated by forward-slash.
if (empty($sheetname)) {
$sheetname = $_REQUEST['sheetname'];
}
// Look for the sheet.
list($value, $title) = sheetnode_find_sheet($sheetname);
// Found a spreadsheet: return it.
drupal_add_http_header('Content-type', 'text/plain; charset=utf-8');
if ($value) {
$parts = socialcalc_parse_parts($value);
if (isset($parts['sheet'])) {
echo $parts['sheet'];
}
}
drupal_exit();
}
/**
* AJAX function to return a token value.
*/
function _sheetnode_ajax_token($oid = NULL, $entity_type = NULL, $token = NULL) {
if (!$oid) {
$oid = $_REQUEST['oid'];
}
if (!$entity_type) {
$entity_type = $_REQUEST['entity_type'];
}
if (!$token) {
$token = $_REQUEST['token'];
}
$value = NULL;
$entity = _sheetnode_entity_load($entity_type, $oid);
if ($entity) {
// Do the token replacement.
$value = token_replace($token, array(
$entity_type => $entity,
));
// If found, send it back.
if ($value) {
drupal_json_output(array(
'type' => is_numeric($value) ? 'n' : 'th',
'value' => $value,
));
drupal_exit();
}
}
drupal_json_output(array(
'value' => '',
'type' => 'e#NAME?',
));
drupal_exit();
}
/**
* AJAX function to return a field value.
*/
function _sheetnode_ajax_field($oid = NULL, $entity_type = NULL, $field = NULL) {
if (!$oid) {
$oid = $_REQUEST['oid'];
}
if (!$entity_type) {
$entity_type = $_REQUEST['entity_type'];
}
if (!$field) {
$field = $_REQUEST['field'];
}
$value = NULL;
$entity = _sheetnode_entity_load($entity_type, $oid);
if ($entity) {
// Try field.
$field_info = field_info_instance($entity_type, $field, field_extract_bundle($entity_type, $entity));
if ($field_info) {
if (!field_access('view', $field_info, $entity_type, $entity)) {
drupal_json_output(array(
'value' => '',
'type' => 'e#NAME?',
));
drupal_exit();
}
$value = field_view_field($entity_type, $entity, $field, array(
'label' => 'hidden',
));
if ($value) {
$value = drupal_render($value);
}
}
// Try rendered field with raw build functions.
$build_func = $entity_type . '_build_content';
if (!$value && function_exists($build_func)) {
$build_func($entity);
if (isset($entity->content[$field])) {
$value = drupal_render($entity->content[$field]);
}
}
// Try raw object field.
if (!$value && isset($entity->{$field})) {
if (is_object($entity->{$field}) || is_array($entity->{$field})) {
$value = print_r($entity->{$field}, TRUE);
}
else {
$value = $entity->{$field};
}
}
// If found, send it back.
if ($value) {
drupal_json_output(array(
'type' => is_numeric($value) ? 'n' : 'th',
'value' => $value,
));
drupal_exit();
}
}
drupal_json_output(array(
'value' => '',
'type' => 'e#NAME?',
));
drupal_exit();
}
/**
* Helper function to save a sheetnode.
*/
function _sheetnode_save($nid, $vid, $value) {
db_delete('sheetnode')
->condition('vid', $vid)
->execute();
db_insert('sheetnode')
->fields(array(
'nid' => $nid,
'vid' => $vid,
'value' => $value,
))
->execute();
}
/**
* Helper function to save a sheetnode template.
*/
function _sheetnode_template_save($vid, $name, $value) {
db_merge('sheetnode_template')
->key(array(
'name' => $name,
))
->fields(array(
'vid' => $vid,
'value' => $value,
))
->execute();
}
/**
* Helper function to load a sheetnode template.
*/
function _sheetnode_template_load($tid) {
$template = db_select('sheetnode_template', 's')
->fields('s')
->condition('tid', $tid, '=')
->execute()
->fetchAssoc();
return (object) $template;
}
/**
* Helper function to load a Drupal entity.
* Also checks for access right.
*/
function _sheetnode_entity_load($entity_type, $oid, $op = 'view', $account = NULL) {
$entities = entity_load($entity_type, array(
$oid,
));
if (isset($entities[$oid])) {
$entity = $entities[$oid];
// Taken from entity.module/entity_access() - not included to reduce dependencies.
// @see http://drupalcontrib.org/api/drupal/contributions!entity!entity.module/function/entity_access/7
if (($info = entity_get_info()) && isset($info[$entity_type]['access callback'])) {
return $info[$entity_type]['access callback']($op, $entity, $account, $entity_type) ? $entity : FALSE;
}
return $entity;
}
return FALSE;
}
/**
* API function to get all sheetfields of a node type.
*/
function sheetnode_get_sheetfields($type) {
$sheetfields = array();
$instances = field_info_instances('node', $type);
$fields = field_info_fields();
foreach ($instances as $field_name => $instance) {
if ($fields[$field_name]['type'] === 'sheetfield') {
$sheetfields[$field_name] = $fields[$field_name];
$sheetfields[$field_name]['instance'] = $instance;
}
}
return $sheetfields;
}
/**
* Helper function to retrieve possible sheetfield aliases.
*/
function _sheetnode_sheetfield_aliases($field_name, $label, $value, $delta) {
$names = array();
$names[] = $field_name . '/' . $delta;
if (!empty($label)) {
$names[] = $label . '/' . $delta;
}
if (!empty($value['name'])) {
$names[] = $value['name'];
}
return $names;
}