<?php

/* @file
 * Show where Beans are used.
 * Currently limited to beans displayed with blockreference.
 *
 */

/**
 * Implements hook_menu()
 *
 * @return array
 */
function bean_usage_menu() {
  $items = array();

  // Todo: beans can be placed via context and block admin - we should account for those as well.
  // usage tab on bean view
  $items['block/%bean_delta/usage'] = array(
    'title' => 'Usage',
    'description' => 'Displays a list of entities where this bean is used.',
    'type' => MENU_LOCAL_TASK,
    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
    'page callback' => 'bean_usage_output',
    'page arguments' => array(1),
    'access arguments' => array('view bean usage'),
  );

  // bean usage report
  $items['admin/reports/bean/usage'] = array(
    'title' => t('Bean usage'),
    'description' => 'Displays a list of Beans (blockreferences) and where they are used.',
    'page callback' => 'bean_usage_output',
    'page arguments' => array(4),
    'access arguments' => array('view bean usage'),
  );

  $items['admin/reports/bean/usage/list'] = array(
    'title' => t('Usage'),
    'description' => 'Displays a list of Beans (blockreferences) and where they are used.',
    'weight' => 0,
    'page callback' => 'bean_usage_output',
    'page arguments' => array(5),
    'access arguments' => array('view bean usage'),
    'type' => MENU_DEFAULT_LOCAL_TASK
  );

  $items['admin/reports/bean/usage/settings'] = array(
    'title' => t('Settings'),
    'description' => t('Displays a list of Beans (blockreferences) and where they are used.'),
    'weight' => 1,
    'page callback' => 'drupal_get_form',
    'page arguments' => array('bean_usage_settings'),
    'access arguments' => array('administer bean usage'),
    'file' => 'includes/bean_usage.forms.inc',
    'type' => MENU_LOCAL_TASK | MENU_NORMAL_ITEM,
  );

  return $items;
}

/**
 * Implements hook_permission()
 *
 * @return array
 */
function bean_usage_permission() {
  $perms = array(
    'view bean usage' => array(
      'title' => t('View bean usage'),
      'description' => t('View a list of Beans (blockreferences) and where they are used.'),
    ),
    'administer bean usage' => array(
      'title' => t('Administer bean usage'),
      'description' => t('Administer settings for the bean usage page.'),
    ),
  );
  return $perms;
}

/**
 * @file
 * Bean Admin Functions and Display
 */

/**
 * Displays a table of Beans and their usage
 *
 * @return string - the HTML for the table
 */
function bean_usage_output($bean = NULL) {
  module_load_include('inc', 'bean_usage', 'includes/bean_usage.forms');
  $output = '';
  $bean_delta = NULL;
  $delta_specific = $delta_found = FALSE;
  $bean_usage_form = drupal_get_form('bean_usage_filters');

  if (!empty($bean) && is_object($bean)) {
    $delta_specific = TRUE;
  } else if (is_numeric($bean)) {
    $delta_specific = TRUE;
    $bean = bean_load($bean);
  }

  if (is_object($bean)) {
    drupal_set_title($bean->label . ' Usage');
    $bean_delta = $bean->delta;
  }

  // Get all fields that are of type blockreference and not deleted
  $fields = bean_usage_blockreference_fields();

  // If we have something to work with
  if (!empty($fields)) {
    $filters = drupal_get_query_parameters();

    if ($delta_specific) {
      $delta_found = FALSE;
    }

    $entity_types = bean_usage_blockrefence_entity_types();
    $bundles = bean_usage_blockreference_bundles();

    $query = '';
    foreach($fields as $i => $field) {
      if (!$delta_found) {
        $field_name = $field->name;
        $data_table = 'field_data_' . $field_name;
        if ($i == 0) {
          $query = _bean_usage_field_data($data_table, $i, $field_name, $entity_types, $bundles, $filters, $delta_specific, $bean);
        }
        else {
          $query->union(_bean_usage_field_data($data_table, $i, $field_name, $entity_types, $bundles, $filters, $delta_specific, $bean));
        }
      }
    }

    // Get the data
    $data = $query->execute()->fetchAll(0);

    if (!empty($data)) {
      // Sort the data - default sort is by label
      usort($data, 'bean_usage_sort_by_label');

      if (!empty($_GET['order'])) {
        $sort_by = empty($_GET['order']) ? '' : str_replace('bean ', '', (strtolower($_GET['order'])));
        switch ($sort_by) {
          case 'label':
            usort($data, 'bean_usage_sort_by_label');
            break;
          case 'title':
            usort($data, 'bean_usage_sort_by_title');
            break;
          case 'type':
            usort($data, 'bean_usage_sort_by_type');
            break;
        }
      }

      if (!empty($_GET['sort']) && $_GET['sort'] == 'desc') {
        $data = array_reverse($data);
      }

      $content = array();
      foreach ($data as $bean_data) {
        // We want to link to display the entity type title/name
        $entity_title = '';
        switch ($bean_data->entity_type) {
          // If its a node, get the node title
          case 'node':
            $entity_title = _bean_usage_entity_display('node', $bean_data->entity_id, 'nid', 'title');
            break;

          // If its a user, get the user name
          case 'user':
            $entity_title = _bean_usage_entity_display('user', $bean_data->entity_id, 'uid', 'name');
            break;
        }

        $content[$bean_data->label][$bean_data->entity_type][$bean_data->entity_id]['bean_title'] = $bean_data->title;
        $content[$bean_data->label][$bean_data->entity_type][$bean_data->entity_id]['bean_type'] = $bean_data->type;
        $content[$bean_data->label][$bean_data->entity_type][$bean_data->entity_id]['entity_id'] = $bean_data->entity_id;
        $content[$bean_data->label][$bean_data->entity_type][$bean_data->entity_id]['entity_title'] = $entity_title;

        if ($delta_specific) {
          $delta_found = TRUE;
        }
      }
    }

    // If we have any usage data create and display the table of data
    if (!empty($content)) {
      $rows = count($content);
      $results_per_page = variable_get('bean_usage_results_per_page', 30);
      // set up pager variables
      pager_default_initialize($rows, $results_per_page, $element = 0);

      // Set up output table
      $bean_table_data = array(
        'attributes' => array( // set table attributes
          'width' => '100%' // width
        ),
        'header' => _bean_usage_table_headers($bean_delta),
        'rows' => array(), // table rows
      );

      if ($filters) {
        $bean_table_data['caption'] = t('Filters: ') . $filters;
      }

      // Get the result subset for the current page
      $page = empty($_GET['page']) ? 0 : $_GET['page'];
      $range_start = $page * $results_per_page;
      $usage = array_slice($content, $range_start, $results_per_page);
      // If the user is on a higher page and then filters, the page
      if (!empty($filters) && $page != 0 && count($usage) == 0) {
        while (count($usage) == 0) {
          $page--;
          $range_start = $page * $results_per_page;
          $usage = array_slice($content, $range_start, $results_per_page);
        }
      }

      foreach ($usage as $bean_label => $entity_type) {
        foreach ($entity_type as $type => $delta) {
          $rowspan = count($delta);
          $bean_label_cell = '';
          if (empty($bean_delta)) {
            $bean_label_cell = array(
              'data' => $bean_label,
              'rowspan' => $rowspan,
              'width' => '30%',
            );
          }

          // we create the row with the bean name first and set the rowspan to however many deltas there are
          foreach ($delta as $entity) {
            $bean_title_cell = array(
              'data' => empty($entity['bean_title']) ? '<em>' . t('No title set') . '</em>' : $entity['bean_title'],
              'rowspan' => $rowspan,
              'width' => '30%',
            );

            $bean_type_cell = array(
              'data' => $entity['bean_type'],
              'rowspan' => $rowspan,
              'width' => '10%',
            );

            // We want to prefix the entity title with the entity id
            // We also want the entity title to link back the to the entity page
            $link_prefix = '';
            $text_prefix = '';
            switch ($type) {
              case 'node':
                $text_prefix = '[nid:' . $entity['entity_id'] . '] ';
                $link_prefix = 'node/';
                break;
              case 'user':
                $text_prefix = '[uid:' . $entity['entity_id'] . '] ';
                $link_prefix = 'user/';
                break;
            } // switch ($entity_type)

            // add the usage data to the table
            // We only need to add the Bean label once per delta, Since we set it above we check for its emptiness
            if (!empty($bean_label_cell)) {
              $bean_table_data['rows'][] = array(
                0 => $bean_label_cell,
                1 => $bean_title_cell,
                2 => $bean_type_cell,
                3 => array(
                  'data' => $text_prefix . l($entity['entity_title'], $link_prefix . $entity['entity_id']),
                  'width' => '30%',
                  'no_striping' => TRUE
                ),
              );
            } // if (!empty($bean_label_cell))
            else {
              $bean_table_data['rows'][] = array(
                $text_prefix . l(
                  t($entity['entity_title']),
                  $link_prefix . $entity['entity_id'],
                  array(
                    'attributes' => array(
                      'title'  => $entity['entity_title'],
                      'target' => '_blank'
                    ),
                  )
                ),
              );
            } // else
            // empty out the $bean_row so that it doesn't display more than one if there are multiple deltas for the block
            $bean_label_cell = '';
          } // foreach ($delta)
        } //foreach ($entity_type)
      } // foreach ($usage)
      // Set the output using theme_table with the header and rows created above
      if (!$delta_specific) {
        $output .= drupal_render($bean_usage_form);
      }
      $output .= theme('table', $bean_table_data);
      $output .= theme('pager');
    } // if (!empty($usage))
    else {
      if ($delta_specific) {
        $output = '<p>' . t('This bean is not used anywhere on the site.') . '</p>';
      }
      else {
        $output .= $bean_usage_form;
        $output .= 'There is no bean usage to report for '. $filters;
      }
    } // else
  } // if (!empty($fields))

  // return the output for page rendering
  return $output;
}

/**
 * Get all fields that are of type blockreference and not deleted
 *
 * @return mixed
 */
function bean_usage_blockreference_fields() {
  $fields = &drupal_static(__FUNCTION__);

  if (!isset($fields)) {
    $query = db_select('field_config', 'fc');
    $query->addField('fc', 'field_name', 'name');
    $query->leftJoin('field_config_instance', 'fci', 'fc.field_name = fci.field_name');
    $fields = $query->condition('fc.type', 'blockreference')
      ->condition('fc.deleted', 0)
      ->orderBy('fc.field_name', 'asc')
      ->execute()
      ->fetchAll(0);
  }

  return $fields;
}

/**
 * Get all of the entity types used with beans via blockreference
 *
 * @return array
 */
function bean_usage_blockrefence_entity_types() {
  $entity_types = array();

  $et_query = db_select('field_config_instance', 'fci')->distinct();
  $et_query->addField('fci', 'entity_type',  'type');
  $et_query->join('field_config', 'fc', 'fc.field_name = fci.field_name');
  $et_res = $et_query->condition('fc.type', 'blockreference')
    ->execute()
    ->fetchAll(0);

  foreach($et_res as $entity) {
    $entity_types[] = $entity->type;
  }

  return $entity_types;
}

/**
 * Get all of the bundles used with beans via blockreference
 *
 * @return array
 */
function bean_usage_blockreference_bundles() {
  $bundles = array();

  $b_query = db_select('field_config_instance', 'fci')->distinct();
  $b_query->join('field_config', 'fc', 'fc.field_name = fci.field_name');
  $b_res = $b_query->fields('fci', array('bundle'))
    ->condition('fc.type', 'blockreference')
    ->execute()
    ->fetchAll(0);

  foreach($b_res as $bundle) {
    $bundles[] = $bundle->bundle;
  }

  return $bundles;
}

/**
 * Helper function to display an entity on the report
 *
 * @param $table
 * @param $value
 * @param $key
 * @param $column
 * @return mixed
 */
function _bean_usage_entity_display($table, $value, $key, $column) {
  $query = db_select($table, 't')
    ->fields('t', array($column))
    ->condition($key, $value)
    ->execute()
    ->fetchAssoc();

  return $query[$column];
}

/**
 * Get the data from the field_data tables for bean usage based on bean.bid and bean.delta
 *
 * @param string $data_table
 * @param int $i
 * @param string $field_name
 * @param array $entity_types
 * @param array $bundles
 * @param string $filter
 * @param boolean $delta_specific
 * @param object bean
 *
 * @return SelectQuery
 */
function _bean_usage_field_data( $data_table, $i, $field_name, $entity_types, $bundles, $filters = NULL, $delta_specific = FALSE, $bean = NULL) {
  $query = db_select($data_table, 'fd'. $i);
  $query->leftJoin('block', 'bl', 'fd'. $i . '.' . $field_name . '_bid = bl.bid');
  $query->join('bean', 'b', 'b.delta = bl.delta');
  $query->fields('b', array('bid', 'label', 'title', 'type'));
  $query->fields('fd'. $i, array('entity_id', 'delta', 'entity_type'));
  $query->addField('fd'. $i, $field_name . '_bid', 'bid');
  $query_and = db_and();
  $query_and->condition('bl.module', 'bean');
  $query_and->condition('bl.theme', variable_get('theme_default', NULL));
  $query_and->condition('fd'. $i . '.entity_type', $entity_types, 'IN');
  $query_and->condition('fd'. $i . '.bundle', $bundles, 'IN');

  if (!empty($filters['type'])) {
    $query_and->condition('b.type', array($filters['type']), 'IN');
  }

  if (!empty($filters['title'])) {
    $query_and->condition('b.title', ('%' .$filters['title'] . '%'), 'LIKE');
  }

  if (!empty($filters['label'])) {
    $query_and->condition('b.label', ('%' . $filters['label'] . '%'), 'LIKE');
  }

  if ($delta_specific && !empty($bean)) {
    $query_and->condition('b.bid', intval($bean->bid));
  }

  $query->condition($query_and);
  return $query;
}

/**
 * Set the table headers for the table output based on the page that calls for the data.
 *
 * @param null $bean_delta
 *
 * @return array
 */
function _bean_usage_table_headers($bean_delta = NULL) {
  if (empty($bean_delta)) {
    $table_data_headers = array(
      0 => array(
        'data' => t('Bean label'),
        'field' => 'label',
        'sort' => 'asc'
      ),
      1 => array(
        'data' => t('Bean title'),
        'field' => 'title',
      ),
      2 => array(
        'data' => t('Bean type'),
        'field' => 'type',
      ),
      3 => array(
        'data' => t('Used in'),
      ),
    );
  }
  else {
    $table_data_headers = array(
      0 => array(
        'data' => t('Used in'),
      ),
    );
  }

  return $table_data_headers;
}

/**
 * Sort bean usage by bean.label
 *
 * @param $a
 * @param $b
 *
 * @return int
 */
function bean_usage_sort_by_label($a, $b) {
  return strcmp($a->label, $b->label);
}

/**
 * Sort bean usage by bean.title
 *
 * @param $a
 * @param $b
 *
 * @return int
 */
function bean_usage_sort_by_title($a, $b) {
  return strcmp($a->title, $b->title);
}

/**
 * Sort bean usage by bean.type
 *
 * @param $a
 * @param $b
 *
 * @return int
 */
function bean_usage_sort_by_type($a, $b) {
  return strcmp($a->type, $b->type);
}
