You are here

uc_report.module in Ubercart 8.4

Displays reports on sales, customers, and products to store admin.

The reports module creates a few table-based reports for an Ubercart store. Store admins can view reports for sales between different dates, customers by the products purchased and money spent, and products by their views, number sold, and revenue produced.

File

uc_report/uc_report.module
View source
<?php

/**
 * @file
 * Displays reports on sales, customers, and products to store admin.
 *
 * The reports module creates a few table-based reports for an Ubercart store.
 * Store admins can view reports for sales between different dates, customers
 * by the products purchased and money spent, and products by their views,
 * number sold, and revenue produced.
 */
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Drupal\uc_order\Entity\OrderStatus;

/**
 * Implements hook_help().
 */
function uc_report_help($route_name, RouteMatchInterface $route_match) {
  $output = '';
  $route = \Drupal::routeMatch()
    ->getRouteName();

  //if (substr($route, 0, 10) == 'uc_report.') {
  if (strncmp($route_name, 'uc_report', 9) === 0) {
    $include_statuses = $include_offset = FALSE;

    // Include order status info?
    if (strpos($route_name, '/admin/store/reports/sales/year') === 0) {
      $include_statuses = TRUE;
    }
    else {
      switch ($route_name) {
        case 'uc_report.customers':
        case 'uc_report.products':
        case 'uc_report.sales':
          $include_statuses = TRUE;
          break;
      }
    }

    // Include timezone offset info?
    if (!strncmp($route_name, '/admin/store/reports/sales', 25)) {
      $include_offset = TRUE;
    }

    // Generate message for the path we're at.
    $output .= '<p>';
    switch ($route_name) {
      case 'uc_report.customers':
        $output .= t("The following are total orders, products, sales, and average order totals for each store customer. Clicking on the header links will toggle a descending or ascending order for that column. Clicking on a customer's name will take you to a detailed list of orders that customer has made. Clicking on a customers username will take you to their account page.");
        break;
      case 'uc_report.products':
        $output .= t('The table lists each product listed in the store, its amount sold, how many times it has been viewed, revenue it has produced, and gross profit it has generated. If you do not see the number of views you must enable the Statistics module on the <a href=":url">module administration page</a>.', [
          ':url' => Url::fromRoute('system.modules_list')
            ->toString(),
        ]);
        break;
      case 'uc_report.sales':
        $output .= t('These are the sales for the last two days, average sales for the month, and the projected sales for the rest of the month. Further down the list you will see other sales statistics.');
        break;
      case 'uc_report.sales_custom_report':
        $output .= t('Expand the fieldset below to customize the date range of this report, the statuses of orders displayed, and product display options.');
        break;
    }
    if (strpos($route_name, 'admin/store/reports/sales/year') === 0) {
      $year = $arg[5] ? $arg[5] : \Drupal::service('date.formatter')
        ->format(\Drupal::time()
        ->getRequestTime(), 'custom', "Y");
      $output .= t('This is the monthly break down of sales for the year @year. Clicking on each link will take you to a list of orders during that month.', [
        '@year' => $year,
      ]);
    }
    $output .= '</p>';

    // Include the statuses/offset as needed.
    if ($include_offset) {
      $output .= "<p>" . t('All reports are your local timezone, which is <b>@timezone</b>.', [
        '@timezone' => date_default_timezone_get(),
      ]) . "</p>";
    }
    if ($include_statuses) {
      $statuses = [];
      foreach (uc_report_order_statuses() as $status) {
        $statuses[] = SafeMarkup::checkPlain(OrderStatus::load($status)
          ->label());
      }
      $order_statuses = t('<b>Order statuses used:</b> @statuses', [
        '@statuses' => implode(', ', array_filter($statuses)),
      ]);
      $output .= "<p>{$order_statuses}</p>";
    }
  }
  return $output;
}

/**
 * Implements hook_theme().
 */
function uc_report_theme() {
  return [
    'uc_report_product_table' => [
      'variables' => [
        'header' => NULL,
        'rows' => NULL,
        'attributes' => [],
        'caption' => NULL,
        'colgroups' => [],
        'sticky' => TRUE,
        'empty' => '',
      ],
      'function' => 'theme_uc_report_product_table',
    ],
  ];
}

/**
 * Return a themed table for product reports.
 *
 * Straight duplication of theme_table, but our row handling is different.
 *
 * @see theme_table()
 * @ingroup themeable
 */
function theme_uc_report_product_table(array $variables) {
  $header = $variables['header'];
  $rows = $variables['rows'];
  $attributes = $variables['attributes'];
  $caption = $variables['caption'];
  $colgroups = $variables['colgroups'];
  $sticky = $variables['sticky'];
  $empty = $variables['empty'];

  // Add sticky headers, if applicable.
  if (count($header) && $sticky) {

    //drupal_add_js('misc/tableheader.js');

    // Add 'sticky-enabled' class to the table to identify it for JS.
    // This is needed to target tables constructed by this function.
    $attributes['class'][] = 'sticky-enabled';
  }
  $output = '<table' . $attributes . ">\n";
  if (isset($caption)) {
    $output .= '<caption>' . $caption . "</caption>\n";
  }

  // Format the table columns:
  if (count($colgroups)) {
    foreach ($colgroups as $colgroup) {
      $attributes = [];

      // Check if we're dealing with a simple or complex column.
      if (isset($colgroup['data'])) {
        foreach ($colgroup as $key => $value) {
          if ($key == 'data') {
            $cols = $value;
          }
          else {
            $attributes[$key] = $value;
          }
        }
      }
      else {
        $cols = $colgroup;
      }

      // Build colgroup.
      if (is_array($cols) && count($cols)) {
        $output .= ' <colgroup' . new Attribute($attributes) . '>';
        foreach ($cols as $col) {
          $output .= ' <col' . new Attribute($col) . ' />';
        }
        $output .= " </colgroup>\n";
      }
      else {
        $output .= ' <colgroup' . new Attribute($attributes) . " />\n";
      }
    }
  }

  // Add the 'empty' row message if available.
  if (!count($rows) && $empty) {
    $header_count = 0;
    foreach ($header as $header_cell) {
      if (is_array($header_cell)) {
        $header_count += isset($header_cell['colspan']) ? $header_cell['colspan'] : 1;
      }
      else {
        $header_count++;
      }
    }
    $rows[] = [
      [
        'data' => $empty,
        'colspan' => $header_count,
        'class' => [
          'empty',
          'message',
        ],
      ],
    ];
  }

  // Format the table header:
  if (count($header)) {
    $ts = tablesort_init($header);

    // HTML requires that the thead tag has tr tags in it follwed by tbody
    // tags. Using ternary operator to check and see if we have any rows.
    $output .= count($rows) ? ' <thead><tr>' : ' <tr>';
    foreach ($header as $cell) {
      $cell = tablesort_header($cell, $header, $ts);
      $output .= _theme_table_cell($cell, TRUE);
    }

    // Using ternary operator to close the tags based on
    // whether or not there are rows.
    $output .= count($rows) ? " </tr></thead>\n" : "</tr>\n";
  }
  else {
    $ts = [];
  }

  // Format the table rows:
  if (count($rows)) {
    $output .= "<tbody>\n";
    $flip = [
      'even' => 'odd',
      'odd' => 'even',
    ];
    $class = 'even';
    foreach ($rows as $row) {
      $attributes = [];

      // Check if we're dealing with a simple or complex row.
      if (isset($row['data'])) {
        foreach ($row as $key => $value) {
          if ($key == 'data') {
            $cells = $value;
          }
          elseif ($key == 'primary') {
            $class = $flip[$class];
          }
          else {
            $attributes[$key] = $value;
          }
        }
      }
      else {
        $cells = $row;
      }
      if (count($cells)) {

        // Add odd/even class.
        // We don't flip here like theme_table(), because we did that above.
        $attributes['class'][] = $class;

        // Build row.
        $output .= ' <tr' . new Attribute($attributes) . '>';
        $i = 0;
        foreach ($cells as $cell) {
          $cell = tablesort_cell($cell, $header, $ts, $i++);
          $output .= _theme_table_cell($cell);
        }
        $output .= " </tr>\n";
      }
    }
    $output .= "</tbody>\n";
  }
  $output .= "</table>\n";
  return $output;
}

/**
 * Implements hook_page_attachments().
 */
function uc_report_page_attachments(&$page) {

  // Load reports CSSs on all reports pages.
  $route = \Drupal::routeMatch()
    ->getRouteName();
  if (substr($route, 0, 10) == 'uc_report.') {
    $page['#attached']['library'][] = 'uc_report/uc_report.styles';
  }
}

/**
 * Implements hook_form_FORM_ID_alter() for uc_store_settings_form().
 */
function uc_report_form_uc_store_settings_form_alter(&$form, &$form_state) {
  $report_config = \Drupal::config('uc_report.settings');
  $form['reports'] = [
    '#type' => 'details',
    '#title' => t('Report settings'),
    '#group' => 'store',
  ];
  $form['reports']['uc_report_table_size'] = [
    '#type' => 'textfield',
    '#title' => t('Number of rows to show in reports'),
    '#description' => t('The maximum number of rows displayed on one page for a report table.'),
    '#default_value' => $report_config
      ->get('table_size'),
    '#size' => 5,
  ];
  $form['reports']['uc_report_reported_statuses'] = [
    '#type' => 'checkboxes',
    '#title' => t('Reported statuses'),
    '#description' => t('Only orders with selected statuses will be included in reports.'),
    '#options' => OrderStatus::getOptionsList(),
    '#default_value' => $report_config
      ->get('reported_statuses'),
    '#multiple' => TRUE,
  ];
}

/**
 * Returns a SQL friendly array of order statuses for orders used in reports.
 */
function uc_report_order_statuses() {
  $statuses = [];
  foreach (OrderStatus::loadMultiple() as $status) {
    $statuses[] = $status
      ->id();
  }
  return $statuses;
}

Functions

Namesort descending Description
theme_uc_report_product_table Return a themed table for product reports.
uc_report_form_uc_store_settings_form_alter Implements hook_form_FORM_ID_alter() for uc_store_settings_form().
uc_report_help Implements hook_help().
uc_report_order_statuses Returns a SQL friendly array of order statuses for orders used in reports.
uc_report_page_attachments Implements hook_page_attachments().
uc_report_theme Implements hook_theme().