You are here

matrix.module in Matrix field 8.2

Same filename and directory in other branches
  1. 5 matrix.module
  2. 6.2 matrix.module
  3. 6 matrix.module
  4. 7.2 matrix.module

Contains matrix.module.

File

matrix.module
View source
<?php

/**
 * @file
 * Contains matrix.module.
 */
use Drupal\Core\Routing\RouteMatchInterface;
include 'migrate.inc';

/**
 * Implements hook_help().
 */
function matrix_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {

    // Main module help for the matrix module.
    case 'help.page.matrix':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('Defines field type for table with textfields') . '</p>';
      return $output;
    default:
  }
}

/**
 * Implementation of hook_theme()
 */
function matrix_theme() {
  $theme['matrix_preview'] = array(
    'render element' => 'form',
  );
  $theme['matrix_table'] = array(
    'render element' => 'form',
  );
  $theme['matrix_output'] = array(
    'variables' => array(
      'header' => NULL,
      'rows' => NULL,
    ),
  );
  return $theme;
}

/**
 * Implementation of hook_views_api()
 */
function matrix_views_api() {
  return array(
    'api' => 2,
    'path' => drupal_get_path('module', 'matrix') . '/views',
  );
}

/**
 * Implements hook_field_info().
 */
function matrix_field_info() {
  return array(
    'matrix_text' => array(
      'label' => t('Matrix (textfield)'),
      'description' => t('Creates a grid of text fields.'),
      'settings' => array(
        'rows_count' => '',
        'cols_count' => '',
        'size' => '',
        'spreadsheet_style' => '',
      ),
      'default_widget' => 'matrix_grid',
      'default_formatter' => 'matrix_default',
      // Support hook_entity_property_info() from contrib "Entity API".
      'property_type' => 'field_item_matrix',
      'property_callbacks' => array(
        'matrix_field_property_info_callback',
      ),
    ),
    'matrix_custom' => array(
      'label' => t('Matrix (custom)'),
      'description' => t('Creates a grid of any field type.'),
      'settings' => array(
        'rows_count' => '',
        'cols_count' => '',
        'settings' => '',
      ),
      'default_widget' => 'matrix_grid',
      'default_formatter' => 'matrix_default',
      // Support hook_entity_property_info() from contrib "Entity API".
      'property_type' => 'field_item_matrix',
      'property_callbacks' => array(
        'matrix_field_property_info_callback',
      ),
    ),
  );
}

/**
 * List of all matrix field types
 */
function matrix_field_types() {
  return array(
    'full' => array(
      'none' => t('None'),
      'textfield' => t('Textfield'),
      'select' => t('Select list'),
      'radios' => t('Radio buttons'),
      'checkboxes' => t('Check boxes'),
      'calculation' => t('Calculation'),
      'custom' => t('Custom'),
    ),
    'limited' => array(
      'none' => t('None'),
      'calculation' => t('Calculation'),
      'custom' => t('Custom'),
    ),
    'none' => array(
      'none' => t('None'),
    ),
  );
}

/**
 * Implements hook_field_settings_form().
 */
function matrix_field_settings_form($field, $instance, $has_data) {
  module_load_include('inc', 'matrix', 'matrix.admin');
  if ($field['type'] == 'matrix_text') {
    return matrix_field_settings_form_text($field, $instance, $has_data);
  }
  elseif ($field['type'] == 'matrix_custom') {
    return matrix_field_settings_form_custom($field, $instance, $has_data);
  }
}

/**
 * Implementation of hook_content_is_empty().
 */
function matrix_field_is_empty($item, $field) {
  if (is_array($item) && array_key_exists('value', $item)) {
    if (empty($item['value']) && (string) $item['value'] !== '0') {
      return TRUE;
    }
    return FALSE;
  }
  return TRUE;
}

/**
 * Implements hook_field_widget_info().
 */
function matrix_field_widget_info() {
  return array(
    'matrix_text' => array(
      'label' => t('Text Matrix'),
      'description' => t('A grid of textfields'),
      'field types' => array(
        'matrix_text',
      ),
      'settings' => array(),
      'behaviors' => array(
        'multiple values' => FIELD_BEHAVIOR_CUSTOM,
        'default value' => FIELD_BEHAVIOR_NONE,
      ),
    ),
    'matrix_custom' => array(
      'label' => t('Custom Matrix'),
      'description' => t('A grid of form elements'),
      'field types' => array(
        'matrix_custom',
      ),
      'settings' => array(),
      'behaviors' => array(
        'multiple values' => FIELD_BEHAVIOR_CUSTOM,
        'default value' => FIELD_BEHAVIOR_NONE,
      ),
    ),
  );
}

/**
 * Implements hook_field_widget_form().
 */
function matrix_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
  $field_name = $field['field_name'];
  $cols_count = $field['settings']['cols_count'];
  $rows_count = $field['settings']['rows_count'];
  $default_values = array();
  foreach ($items as $delta => $item) {
    if ($item && !empty($item['row'])) {
      $default_values[$item['row']][$item['col']][] = $item['value'];
    }
  }
  switch ($field['type']) {
    case 'matrix_text':
      $element['#matrix_rows'] = $rows_count;
      $element['#matrix_cols'] = $cols_count;
      $element['#matrix_more_rows'] = FALSE;
      $element['#matrix_more_cols'] = FALSE;
      $size = $field['settings']['size'];
      $spreadsheet_style = $field['settings']['spreadsheet_style'];
      if ($spreadsheet_style) {
        for ($row = 1; $row <= $rows_count; $row++) {
          $row_labels[$row] = $row;
        }
        for ($col = 1; $col <= $cols_count; $col++) {
          $column_labels[$col] = matrix_make_letter($col);
        }
      }
      else {
        $row_labels = array();
        $column_labels = array();
      }
      for ($row = 1; $row <= $rows_count; $row++) {
        for ($col = 1; $col <= $cols_count; $col++) {
          $element['grid'][$row . '-' . $col] = array(
            '#type' => 'textfield',
            '#default_value' => isset($default_values[$row][$col]) ? $default_values[$row][$col] : '',
            '#size' => $size,
          );
        }
      }
      break;
    case 'matrix_custom':
      $settings = unserialize($field['settings']['settings']);
      $define = $field['settings']['define'];
      if ($rows_count == -1) {
        if (count($default_values) > 3) {

          //check to see how big the grid should be  based on previously submitted data
          $rows_count = count($default_values);
        }
        else {
          $rows_count = 3;
        }
        if (isset($form_state['storage'][$field_name]['rows'])) {
          $rows_count += $form_state['storage'][$field_name]['rows'];
        }
        $element['#matrix_rows'] = $rows_count;
        $element['#matrix_more_rows'] = TRUE;
        $row_labels = array();
      }
      else {
        $element['#matrix_rows'] = $rows_count;
        $element['#matrix_more_rows'] = FALSE;
        for ($row = 1; $row <= $rows_count; $row++) {
          $row_labels[$row] = isset($settings['rows'][$row]['title']) ? $settings['rows'][$row]['title'] : '';
        }
      }
      if ($cols_count == -1) {
        if (isset($default_values[1]) && count($default_values[1]) > 3) {

          //check to see how big the grid should be  based on previously submitted data
          $cols_count = count($default_values[1]);
        }
        else {
          $cols_count = 3;
        }
        if (isset($form_state['storage'][$field_name]['cols'])) {
          $cols_count += $form_state['storage'][$field_name]['cols'];
        }
        $element['#matrix_cols'] = $cols_count;
        $element['#matrix_more_cols'] = TRUE;
        $column_labels = array_fill(0, $cols_count, '&nbsp;');
      }
      else {
        $element['#matrix_cols'] = $cols_count;
        $element['#matrix_more_cols'] = FALSE;
        for ($col = 1; $col <= $cols_count; $col++) {
          $column_labels[$col] = isset($settings['cols'][$col]['title']) ? $settings['cols'][$col]['title'] : '';
        }
      }
      $js = '';
      for ($row = 1; $row <= $rows_count; $row++) {
        for ($col = 1; $col <= $cols_count; $col++) {

          //determine which field type should be shown for this cell

          //if define is cols then if a row is set to calculation or custom other than 'none' then it takes precidence (and visa versa)
          if ($define == 'cols') {
            if (isset($settings['rows'][$row]['field_type']) && in_array($settings['rows'][$row]['field_type'], array(
              'calculation',
              'custom',
            ))) {
              $field_settings = isset($settings['rows'][$row]) ? $settings['rows'][$row] : array(
                'field_type' => 'none',
              );
              $calculation_class = 'matrix-calc-col-' . $col;
            }
            else {
              $field_settings = isset($settings['cols'][$col]) ? $settings['cols'][$col] : array(
                'field_type' => 'none',
              );
              $calculation_class = 'matrix-calc-row-' . $row;
            }
          }
          elseif ($define == 'rows') {
            if (isset($settings['cols'][$col]['field_type']) && in_array($settings['cols'][$col]['field_type'], array(
              'calculation',
              'custom',
            ))) {
              $field_settings = isset($settings['cols'][$col]) ? $settings['cols'][$col] : array(
                'field_type' => 'none',
              );
              $calculation_class = 'matrix-calc-row-' . $row;
            }
            else {
              $field_settings = isset($settings['rows'][$row]) ? $settings['rows'][$row] : array(
                'field_type' => 'none',
              );
              $calculation_class = 'matrix-calc-col-' . $col;
            }
          }

          //now render the element
          $field_type = $field_settings['field_type'];
          if (isset($default_values[$row][$col])) {
            $default_value = $default_values[$row][$col];
          }
          else {
            $default_value[0] = '';
          }
          if (isset($field_settings[$field_type]['required']) && $field_settings[$field_type]['required'] == TRUE) {
            $required = TRUE;
            $required_marker = _theme('form_required_marker', array());
          }
          else {
            $required = FALSE;
            $required_marker = '';
          }
          switch ($field_type) {
            case 'none':
              $element['grid'][$row . '-' . $col] = array(
                '#matrix_row' => $row,
                '#matrix_column' => $col,
                '#field_name' => $element['#title'],
                '#type' => 'markup',
                '#markup' => $default_value[0],
                '#attributes' => array(
                  'class' => array(
                    'matrix-col-' . $col,
                    'matrix-row-' . $row,
                    'matrix-cell-' . $row . '-' . $col,
                  ),
                ),
              );
              break;
            case 'textfield':
              $element['grid'][$row . '-' . $col] = array(
                '#type' => 'textfield',
                '#matrix_row' => $row,
                '#matrix_column' => $col,
                '#field_name' => $element['#title'],
                '#field_prefix' => $field_settings['textfield']['prefix'],
                '#field_suffix' => $field_settings['textfield']['suffix'] . $required_marker,
                '#default_value' => $default_value[0],
                '#size' => $field_settings['textfield']['size'],
                '#matrix_required' => $required,
                '#element_validate' => array(
                  'matrix_required_validate',
                ),
                '#attributes' => array(
                  'class' => array(
                    'matrix-calc-col-' . $col,
                    'matrix-calc-row-' . $row,
                    'matrix-col-' . $col,
                    'matrix-row-' . $row,
                    'matrix-cell-' . $row . '-' . $col,
                  ),
                ),
              );
              break;
            case 'select':
              $element['grid'][$row . '-' . $col] = array(
                '#type' => 'select',
                '#matrix_row' => $row,
                '#matrix_column' => $col,
                '#field_name' => $element['#title'],
                '#default_value' => $default_value[0],
                '#matrix_required' => $required,
                '#element_validate' => array(
                  'matrix_required_validate',
                ),
                '#field_suffix' => $required_marker,
                '#options' => matrix_allowed_values($field, $field_settings, 'select'),
                '#attributes' => array(
                  'class' => array(
                    'matrix-calc-col-' . $col,
                    'matrix-calc-row-' . $row,
                    'matrix-col-' . $col,
                    'matrix-row-' . $row,
                    'matrix-cell-' . $row . '-' . $col,
                  ),
                ),
              );
              break;
            case 'radios':
              $element['grid'][$row . '-' . $col] = array(
                '#type' => 'radios',
                '#matrix_row' => $row,
                '#matrix_column' => $col,
                '#field_name' => $element['#title'],
                '#default_value' => $default_value[0],
                '#matrix_required' => $required,
                '#element_validate' => array(
                  'matrix_required_validate',
                ),
                '#prefix' => $required_marker,
                '#options' => matrix_allowed_values($field, $field_settings, 'radios'),
                '#attributes' => array(
                  'class' => array(
                    'matrix-calc-col-' . $col,
                    'matrix-calc-row-' . $row,
                    'matrix-col-' . $col,
                    'matrix-row-' . $row,
                    'matrix-cell-' . $row . '-' . $col,
                  ),
                ),
              );
              break;
            case 'checkboxes':
              $element['grid'][$row . '-' . $col] = array(
                '#type' => 'checkboxes',
                '#matrix_row' => $row,
                '#matrix_column' => $col,
                '#field_name' => $element['#title'],
                '#default_value' => $default_value,
                '#matrix_required' => $required,
                '#element_validate' => array(
                  'matrix_required_validate',
                ),
                '#prefix' => $required_marker,
                '#options' => matrix_allowed_values($field, $field_settings, 'checkboxes'),
                '#attributes' => array(
                  'class' => array(
                    'matrix-calc-col-' . $col,
                    'matrix-calc-row-' . $row,
                    'matrix-col-' . $col,
                    'matrix-row-' . $row,
                    'matrix-cell-' . $row . '-' . $col,
                  ),
                ),
              );
              break;
            case 'calculation':
              $element['grid'][$row . '-' . $col] = array(
                '#type' => 'hidden',
                '#matrix_row' => $row,
                '#matrix_column' => $col,
                '#field_name' => $element['#title'],
                '#suffix' => '<div id="matrix-result-' . $field_name . '-' . $row . '-' . $col . '">&nbsp;</div>',
                '#attributes' => array(
                  'class' => array(
                    'matrix-col-' . $col,
                    'matrix-row-' . $row,
                    'matrix-cell-' . $row . '-' . $col,
                  ),
                ),
              );
              $opperation = $field_settings['calculation']['calculation'];
              $prefix = $field_settings['calculation']['prefix'];
              $suffix = $field_settings['calculation']['suffix'];
              $rounding = $field_settings['calculation']['rounding'];
              $js .= "jQuery('.{$calculation_class}').change(function(){window.matrix.{$opperation}('{$calculation_class}', '{$row}-{$col}', '{$field_name}', '{$prefix}', '{$suffix}', '{$rounding}')});\n\n                      window.matrix.{$opperation}('{$calculation_class}', '{$row}-{$col}', '{$field_name}', '{$prefix}', '{$suffix}', '{$rounding}');\n";
              break;
            case 'custom':
              $callback = $field_settings['custom']['custom'];
              $element['grid'][$row . '-' . $col] = array(
                '#type' => 'hidden',
                '#matrix_row' => $row,
                '#matrix_column' => $col,
                '#suffix' => '<div id="matrix-result-' . $field_name . '-' . $row . '-' . $col . '">&nbsp;</div>',
                '#attributes' => array(
                  'class' => array(
                    'matrix-col-' . $col,
                    'matrix-row-' . $row,
                    'matrix-cell-' . $row . '-' . $col,
                  ),
                ),
              );
              $js .= "jQuery('.{$calculation_class}').change(function() {window.matrix.custom('{$callback}', '{$calculation_class}', '{$row}-{$col}', '{$field_name}')});\n\n                      window.matrix.custom('{$callback}', '{$calculation_class}', '{$row}-{$col}', '{$field_name}');\n";
              break;
          }
        }
      }
      break;
  }
  if (!empty($js)) {

    // @FIXME
    // The Assets API has totally changed. CSS, JavaScript, and libraries are now
    // attached directly to render arrays using the #attached property.
    //
    //
    // @see https://www.drupal.org/node/2169605
    // @see https://www.drupal.org/node/2408597
    // drupal_add_js(drupal_get_path('module', 'matrix') .'/matrix.js');
    // @FIXME
    // The Assets API has totally changed. CSS, JavaScript, and libraries are now
    // attached directly to render arrays using the #attached property.
    //
    //
    // @see https://www.drupal.org/node/2169605
    // @see https://www.drupal.org/node/2408597
    // drupal_add_js('jQuery(document).ready(function () {'. $js .'});', array('type' => 'inline', 'scope' => 'footer'));
  }
  $element['more_rows'] = array(
    '#type' => 'button',
    '#value' => t('More rows'),
    '#name' => 'matrix-button-' . $field_name,
    '#ajax' => array(
      'callback' => 'matrix_field_ajax_callback',
      'wrapper' => 'matrix-field-' . $field_name,
    ),
    '#limit_validation_errors' => array(),
  );
  $element['more_cols'] = array(
    '#type' => 'button',
    '#value' => t('More columns'),
    '#name' => 'matrix-button-' . $field_name,
    '#ajax' => array(
      'callback' => 'matrix_field_ajax_callback',
      'wrapper' => 'matrix-field-' . $field_name,
    ),
    '#limit_validation_errors' => array(),
  );
  $element['#theme'] = 'matrix_table';
  $element['#row_labels'] = $row_labels;
  $element['#column_labels'] = $column_labels;
  $element['#element_validate'] = array(
    'matrix_field_widget_validate',
  );
  $form_state['matrix'] = $element['#field_name'];

  //used to identify this element in the ajax callback.
  return $element;
}

/**
 * AJAX callback
 * Returns the field grid after the more rows or more cols button has been pressed
 */
function matrix_field_ajax_callback($form, &$form_state) {
  list($element_name, $value) = matrix_op($form_state);
  if (isset($element_name)) {
    return $form[$element_name];
  }
}

/**
 * Element validate callback; check that the entered values are valid.
 */
function matrix_allowed_values_setting_validate($element, &$form_state) {
  return;
}

/**
 * Submit handler for settings form
 * Clears the cache which is used for this field
 */
function matrix_clear_cache_submit(&$form, &$form_state) {
  $user = \Drupal::currentUser();
  cache_clear_all('matrix-cache-' . $user->uid, 'cache');

  //TODO run this off the form token
}

/**
 * Generate the list of allowed values from a set of field settings
 * This will check first if there is a function for the allowed values and if not will use the allowd_values field
 *
 * @param $field
 *     The field array
 * @param $field_settings
 *     Array of settings for this field
 * @param $element_type
 *     The type of element the settings are for (eg select, radios, checkboxes).
 * @return
 *     Array of values suiteble for use in a #options form element
 */
function matrix_allowed_values($field, $field_settings, $element_type) {
  if (!empty($field_settings[$element_type]['allowed_values_function']) && function_exists($field_settings[$element_type]['allowed_values_function'])) {
    $values = call_user_func($field_settings[$element_type]['allowed_values_function'], $field);
  }
  else {
    $list = explode("\n", $field_settings[$element_type]['allowed_values']);
    $list = array_map('trim', $list);
    $list = array_filter($list, 'strlen');
    foreach ($list as $value) {
      if (strpos($value, '|') !== FALSE) {
        list($key, $value) = explode('|', $value);
        $values[$key] = $value;
      }
      else {
        $values[$value] = $value;
      }
    }
  }
  return $values;
}

/**
 * Theme a set of form elements into a table
 */
function theme_matrix_table($variables) {
  $form = $variables['form'];
  $rows_count = $form['#matrix_rows'];
  $cols_count = $form['#matrix_cols'];
  $more_cols = $form['#matrix_more_cols'];
  $more_rows = $form['#matrix_more_rows'];
  $column_labels = $form['#column_labels'];
  $row_labels = $form['#row_labels'];
  $field_name = $form['#field_name'];
  $table_rows = array();
  for ($row = 1; $row <= $rows_count; $row++) {
    for ($col = 1; $col <= $cols_count; $col++) {
      $table_rows[$row - 1][$col - 1] = drupal_render($form['grid'][$row . '-' . $col]);
    }
  }

  //lables
  if (!empty($row_labels)) {
    array_unshift($column_labels, '');

    //The first column will contain the label
    foreach ($row_labels as $id => $label) {
      array_unshift($table_rows[$id - 1], $label);
    }
  }

  //if there should be a "more rows" button, render a new row with just the button
  if ($more_rows) {
    $table_rows[$rows_count][1] = drupal_render($form['more_rows']);
    for ($col = 2; $col <= $cols_count; $col++) {
      $table_rows[$rows_count][$col] = '&nbsp;';
    }
  }

  //if there should be a "more columns" button, render a new column with just the button
  if ($more_cols) {
    $column_labels[] = drupal_render($form['more_cols']);
    for ($row = 1; $row <= $rows_count; $row++) {
      $table_rows[$row - 1][$cols_count + 1] = '&nbsp;';
    }
  }
  $output = '<div id="matrix-field-' . $field_name . '">';
  $output .= _theme('form_element_label', array(
    'element' => $form,
  ));
  $output .= _theme('table', array(
    'header' => $column_labels,
    'rows' => $table_rows,
  ));
  $output .= filter_xss_admin($form['#description']);
  $output .= '</div>';
  return $output;
}

/**
 * Theme a set of elements into a table
 */
function theme_matrix_output($variables) {
  return _theme('table', array(
    'header' => $variables['header'],
    'rows' => $variables['rows'],
  ));
}

/**
 * Validate the required attribute of a cell
 * standard #requied cannot be used because:
 * - It should not be invoked during ajax calls
 * - No message is returned to the user (error set without message or cell highlighted)
 */
function matrix_required_validate($element, &$form_state) {
  $error = FALSE;
  list($e, $value) = matrix_op($form_state);
  if (!in_array($value, array(
    t('More rows'),
    t('More columns'),
  ))) {
    if ($element['#matrix_required'] == TRUE) {
      if (is_array($element['#value']) && empty($element['#value'])) {
        $error = TRUE;
      }
      elseif ($element['#value'] == '') {
        $error = TRUE;
      }
      if ($error) {
        form_error($element, t('The cell at row %row, column %column in the %field_name is required', array(
          '%row' => $element['#matrix_row'],
          '%column' => $element['#matrix_column'],
          '%field_name' => $element['#field_name'],
        )));
      }
    }
  }
}

/**
 * Because the name attribute of the add more rows and add more columns is used
 * $form_state['values']['op'] is not available.  This function basically finds the op
 * equivilent in $form_state
 *
 * @param $form_state
 *     The form_state array
 * @return
 *     single array entry ofwith first element the name of the field and second element the value of the button
 */
function matrix_op(&$form_state) {
  foreach ($form_state['values'] as $key => $value) {
    if (is_string($value) && strpos($key, 'matrix-button-') !== FALSE) {
      return array(
        substr($key, 14),
        $value,
      );
    }
  }
  return array(
    '',
    '',
  );

  //fallback case
}

/**
 * Form element validation handler.
 * Respond to ajax requests and
 * Reorder the data so that it is in the correct form for the fields api to save the data
 * Each record gets its own delta.  For multi-valued elements each value gets its own delta.
 */
function matrix_field_widget_validate($element, &$form_state) {
  list($field_name, $value) = matrix_op($form_state);
  if (isset($form_state['values']['op'])) {
    $rows_count = $element['#matrix_rows'];
    $cols_count = $element['#matrix_cols'];
    $delta = 0;
    for ($row = 1; $row <= $rows_count; $row++) {
      for ($col = 1; $col <= $cols_count; $col++) {
        if (!isset($element['grid'][$row . '-' . $col]['#value'])) {

          //if value is not set
          $value = '';
        }
        elseif (is_array($element['grid'][$row . '-' . $col]['#value']) && empty($element['grid'][$row . '-' . $col]['#value'])) {

          //if array and value not set
          $value = '';
        }
        else {
          $value = $element['grid'][$row . '-' . $col]['#value'];

          //value set
        }
        if (is_array($value)) {
          foreach ($value as $v) {
            $items[$delta] = array(
              'row' => $row,
              'col' => $col,
              'value' => $v,
            );
            $delta++;
          }
        }
        else {
          $items[$delta] = array(
            'row' => $row,
            'col' => $col,
            'value' => $value,
          );
          $delta++;
        }
      }
    }
    $form_state
      ->setValueForElement($element, $items);
  }
  elseif ($field_name == $element['#field_name']) {

    // filter to this matrix (if there is more than 1 marix per form)
    if ($value == t('More columns')) {
      if (isset($form_state['storage'][$field_name]['cols'])) {
        $form_state['storage'][$field_name]['cols']++;
      }
      else {
        $form_state['storage'][$field_name]['cols'] = 1;
      }
    }
    if ($value == t('More rows')) {
      if (isset($form_state['storage'][$field_name]['rows'])) {
        $form_state['storage'][$field_name]['rows']++;
      }
      else {
        $form_state['storage'][$field_name]['rows'] = 1;
      }
    }
  }
}

/**
 * Implements hook_field_formatter_info().
 */
function matrix_field_formatter_info() {
  return array(
    'matrix_default' => array(
      'label' => t('Default'),
      'field types' => array(
        'matrix_text',
        'matrix_custom',
      ),
    ),
  );
}

/**
 * Implements hook_field_formatter_view().
 */
function matrix_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $raw_items, $display) {
  $element = array();
  if (!$raw_items) {

    //If there are no items to show (eg. no content saved for this field on this node) then hide the field
    return;
  }
  switch ($display['type']) {
    case 'matrix_default':
      if (count($raw_items[0]) == 1) {

        //if a single value is passed in from views
        $element[0] = array(
          '#markup' => field_filter_xss($raw_items[0]['value']),
        );
      }

      //transpose the $items array into a [$row][$col] = value arangement
      foreach ($raw_items as $delta => $item) {
        $items[$item['row']][$item['col']][] = matrix_cell_value($item, $field);
      }

      //work out how many rows and columns to show, which is the number of rows with at least some data in the row.

      // this count algorithm assumes the keys are in order, so make sure they are.
      ksort($items);
      foreach ($items as $row_id => $row) {
        foreach ($row as $col_id => $value) {
          if ($value != '') {
            $rows_count = $row_id;

            // if this row has a value, we don't need to keep looking at the rest
            // of the columns.
            break;
          }
        }
      }
      if ($field['settings']['cols_count'] > 0) {
        $cols_count = $field['settings']['cols_count'];
      }
      else {
        foreach ($items as $row_id => $row) {
          foreach ($row as $col_id => $value) {
            if (!empty($value)) {
              $max_cols[$col_id] = $col_id;
            }
          }
        }
        $cols_count = max($max_cols);
      }

      //populate the data part of the table rows
      $rows = array();
      for ($row = 1; $row <= $rows_count; $row++) {
        for ($col = 1; $col <= $cols_count; $col++) {
          $value = array(
            '',
          );
          if (isset($items[$row][$col])) {
            $value = $items[$row][$col];
          }
          $rows[$row][$col] = $value;
        }
      }

      //collapse any multi-valued results into a list
      foreach ($rows as $row_id => $row) {
        foreach ($row as $col_id => $values) {
          if (count($values) > 1) {
            $rows[$row_id][$col_id] = array(
              'data' => _theme('item_list', array(
                'items' => $values,
              )),
              'class' => 'matrix-col-' . $col_id . ' matrix-row-' . $row_id . ' matrix-cell-' . $row_id . '-' . $col_id,
            );
          }
          else {
            $rows[$row_id][$col_id] = array(
              'data' => $values[0],
              'class' => 'matrix-col-' . $col_id . ' matrix-row-' . $row_id . ' matrix-cell-' . $row_id . '-' . $col_id,
            );
          }
        }
      }
      if ($field['type'] == 'matrix_text' && $field['settings']['spreadsheet_style'] == TRUE) {
        for ($col = 1; $col <= $cols_count; $col++) {
          $headers[$col] = matrix_make_letter($col);
        }
        array_unshift($headers, '');

        //cell 0,0
        for ($row = 1; $row <= $rows_count; $row++) {
          $cell = array(
            'data' => $row,
            'class' => 'matrix-col-header',
          );
          array_unshift($rows[$row], $cell);
        }
      }
      elseif ($field['type'] == 'matrix_custom') {
        $headers = array();
        $headers = array_fill(1, $cols_count, '&nbsp;');

        //initialize the headers with spaces (in case headers have not title set)
        $settings = unserialize($field['settings']['settings']);
        if (isset($settings['cols'])) {
          foreach ($settings['cols'] as $col_id => $col) {
            $headers[$col_id] = $col['title'];
          }
        }
        $have_row_labels = FALSE;
        if (!empty($settings['rows'])) {
          foreach ($settings['rows'] as $row_id => $row) {
            if (!empty($row['title'])) {
              $have_row_labels = TRUE;
            }
            $cell = array(
              'data' => $row['title'],
              'class' => 'matrix-col-header',
            );
            if (isset($rows[$row_id])) {
              array_unshift($rows[$row_id], $cell);
            }
          }
        }
        if ($have_row_labels) {

          //had left lables so translate the headers one to the right
          array_unshift($headers, '');

          //cell 0,0
        }
      }
      else {
        $headers = NULL;
      }
      $element[0] = array(
        '#theme' => 'matrix_output',
        '#header' => $headers,
        '#rows' => $rows,
      );
      break;
  }
  return $element;
}

/**
 * Find a value of a cell for either a string or a select list
 *
 * @param $item
 *    array representing the cell
 * @param $field
 *     array containing the field data
 * @return
 *     value to display
 */
function matrix_cell_value($item, $field) {
  $prefix = '';
  $suffix = '';
  if ($field['type'] == 'matrix_text') {

    //selection lists cannot apply to text fields
    $value = $item['value'];
  }
  elseif ($field['type'] == 'matrix_custom') {

    //see if the cell contains a selection list
    if (isset($field['settings']['settings'])) {
      $settings = unserialize($field['settings']['settings']);
    }
    else {
      $settings = array();
    }
    $define = $field['settings']['define'];

    //work out which settings should be used
    if ($define == 'rows') {
      $field_settings = $settings['rows'][$item['row']];
    }
    else {
      $field_settings = $settings['cols'][$item['col']];
    }

    //determine the value to return
    $field_type = $field_settings['field_type'];
    if (!in_array($field_type, array(
      'select',
      'radios',
      'checkboxes',
    ))) {
      $value = $item['value'];
      $prefix = $field_settings[$field_type]['prefix'];
      $suffix = $field_settings[$field_type]['suffix'];
    }
    else {
      $allowed_values = matrix_allowed_values($field, $field_settings, $field_type);
      $value = $allowed_values[$item['value']];
    }
  }
  return check_plain($prefix . $value . $suffix);
}

/**
 * Make a letter (like with a spreadsheet A, B, C, AA, AB etc)
 *
 * @param $int
 *     The number to convert to a string
 * @return string
 */
function matrix_make_letter($int) {
  $string = '';
  $first = chr((int) ($int / 26) + 64);
  $second = chr($int % 26 + 64);
  if ($first != '@') {

    //@ comes before 'A'
    $string .= $first;
  }
  $string .= $second;
  return $string;
}

/**
 * Menu callback for matrix custom calculations
 * @return string.
 */
function matrix_custom_calculation_callback() {
  $callback = $_POST['callback'];
  $data = explode(",", $_POST['data']);
  $functions = \Drupal::moduleHandler()
    ->invokeAll('matrix_functions');

  //ensure the callback is allowed
  if (!in_array($callback, array_keys($functions['calculation']))) {
    drupal_json_output(array(
      'error' => t('Calcuation callback function not available'),
    ));
    exit;
  }

  //ensure the data is safe
  foreach ($data as $id => $d) {
    $data[$id] = check_plain($d);
  }
  $result = call_user_func($callback, $data);
  drupal_json_output(array(
    'data' => check_plain($result),
  ));
  exit;
}

/**
 * Example Implementation of hook_matrix_functions().
 * Register functions that should be used with the matrix module
 * These are used for the "allowed values functions" and for "custom calculation functions"
 *
 * @return array of allowed functions
 */
function matrix_matrix_functions() {
  return array(
    'allowed_values' => array(
      'matrix_allowed_values_example' => t('Custom example (10\'s)'),
    ),
    'calculation' => array(
      'matrix_custom_processing_example' => t('Custom example (sum of squares)'),
    ),
  );
}

/**
 * Example function for providing a list of allowed values for a select list
 *
 * @param $field
 *     The field array
 * @return 
 *     An array of key-value pairs
 */
function matrix_allowed_values_example($field) {
  return array(
    10 => t('Ten'),
    20 => t('Twenty'),
    30 => t('Thirty'),
    40 => t('Forty'),
    50 => t('Fifty'),
  );
}

/**
 * Example function for performing a custom calculation on some data
 *
 * @param $data
 *     Array of data to process
 * @return 
 *     A string to store
 */
function matrix_custom_processing_example($data) {
  return implode(',', $data);
}

/**
 * Additional callback to adapt the property info of matrix fields.
 *
 * @see entity_metadata_field_entity_property_info()
 */
function matrix_field_property_info_callback(&$info, $entity_type, $field, $instance, $field_type) {
  $property =& $info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']];

  // Define a data structure so it's possible to deal with the row, column and
  // value.
  $property['getter callback'] = 'entity_metadata_field_verbatim_get';
  $property['setter callback'] = 'entity_metadata_field_verbatim_set';

  // Auto-create the field item as soon as a property is set.
  $property['auto creation'] = 'matrix_field_item_create';
  $property['property info'] = matrix_field_item_property_info();
  unset($property['query callback']);
}

/**
 * Callback for creating a new, empty matrix field item.
 *
 * @see matrix_field_property_info_callback()
 */
function matrix_field_item_create() {
  return array(
    'row' => NULL,
    'col' => NULL,
    'value' => NULL,
  );
}

/**
 * Defines info for the properties of the matrix-field item data structure.
 */
function matrix_field_item_property_info() {
  $properties['row'] = array(
    'type' => 'integer',
    'label' => t('The row of the item'),
    'setter callback' => 'entity_property_verbatim_set',
  );
  $properties['col'] = array(
    'type' => 'integer',
    'label' => t('The column of the item.'),
    'setter callback' => 'entity_property_verbatim_set',
  );
  $properties['value'] = array(
    'type' => 'text',
    'label' => t('The value of the item.'),
    'setter callback' => 'entity_property_verbatim_set',
  );
  return $properties;
}

Functions

Namesort descending Description
matrix_allowed_values Generate the list of allowed values from a set of field settings This will check first if there is a function for the allowed values and if not will use the allowd_values field
matrix_allowed_values_example Example function for providing a list of allowed values for a select list
matrix_allowed_values_setting_validate Element validate callback; check that the entered values are valid.
matrix_cell_value Find a value of a cell for either a string or a select list
matrix_clear_cache_submit Submit handler for settings form Clears the cache which is used for this field
matrix_custom_calculation_callback Menu callback for matrix custom calculations
matrix_custom_processing_example Example function for performing a custom calculation on some data
matrix_field_ajax_callback AJAX callback Returns the field grid after the more rows or more cols button has been pressed
matrix_field_formatter_info Implements hook_field_formatter_info().
matrix_field_formatter_view Implements hook_field_formatter_view().
matrix_field_info Implements hook_field_info().
matrix_field_is_empty Implementation of hook_content_is_empty().
matrix_field_item_create Callback for creating a new, empty matrix field item.
matrix_field_item_property_info Defines info for the properties of the matrix-field item data structure.
matrix_field_property_info_callback Additional callback to adapt the property info of matrix fields.
matrix_field_settings_form Implements hook_field_settings_form().
matrix_field_types List of all matrix field types
matrix_field_widget_form Implements hook_field_widget_form().
matrix_field_widget_info Implements hook_field_widget_info().
matrix_field_widget_validate Form element validation handler. Respond to ajax requests and Reorder the data so that it is in the correct form for the fields api to save the data Each record gets its own delta. For multi-valued elements each value gets its own delta.
matrix_help Implements hook_help().
matrix_make_letter Make a letter (like with a spreadsheet A, B, C, AA, AB etc)
matrix_matrix_functions Example Implementation of hook_matrix_functions(). Register functions that should be used with the matrix module These are used for the "allowed values functions" and for "custom calculation functions"
matrix_op Because the name attribute of the add more rows and add more columns is used $form_state['values']['op'] is not available. This function basically finds the op equivilent in $form_state
matrix_required_validate Validate the required attribute of a cell standard #requied cannot be used because:
matrix_theme Implementation of hook_theme()
matrix_views_api Implementation of hook_views_api()
theme_matrix_output Theme a set of elements into a table
theme_matrix_table Theme a set of form elements into a table