Discussion at the Forum: http://www.phpinsider.com/smarty-forum/viewtopic.php?t=4125

History

v0.1.6 (June 23, 2005)

v0.1.5 (June 21, 2005)


Notes


Sample Usage

report.php

<?php
$smarty = new Smarty();

if (!$smarty->is_cached('report.tpl')) {
    $data = array();
    foreach (array(2003, 2004, 2005) as $year) {
        foreach (array(1, 2, 3, 4) as $quarter) {
            foreach (array('ca', 'us') as $region) {
                foreach (array('foo', 'bar', 'baz') as $item) {
                    $sales = rand(2000,20000);
                    $data[] = compact('year', 'quarter', 'region', 'item', 'sales');
                }
            }
        }
    }
    $smarty->assign('data', $data);
}

$smarty->display('report.tpl');
?>

report.tpl

{report recordset=$data record=rec groups="region,year,quarter" resort=true}

{report_header}
    START OF REPORT
    <hr/>
    <table width="400" border=1 cellspacing=1 cellpadding=1>
{/report_header}

{report_header group="region"}
    <tr style="background:blue; color:white;">
        <td>SUMMARY FOR REGION '{$rec.region}'</td>
        <td>{$count.sales} items totalling {$sum.sales} for an average of {$avg.sales} per item</td>
    </tr>
{/report_header}

{report_header group="year"}
    <tr>
        <td align="center" colspan=2>YEAR: {$rec.year}</td>
    </tr>
{/report_header}

{report_header group="quarter"}
    <tr>
        <td align="center" colspan=2>Q{$rec.quarter}</td>
    </tr>
{/report_header}

{report_detail}
    <tr>
        <td>{$rec.item}</td>
        <td>{$rec.sales}</td>
    </tr>
{/report_detail}

{report_footer group="region"}
    <tr>
        <td>TOTALS FOR REGION '{$rec.region}'</td>
        <td>{$count.sales} items totalling {$sum.sales} for an average of {$avg.sales} per item</td>
    </tr>
{/report_footer}

{report_footer}
    <tr>
        <td>GRAND TOTALS</td>
        <td>{$count.sales} items totalling {$sum.sales} for an average of {$avg.sales} per item</td>
    </tr>
   
    </table>
    <hr/>
    END OF REPORT
{/report_footer}

{/report}

Setup

The following plugins should be placed in your plugins directory:

block.report.php

<?php

/**
 * Smarty {report}{/report} block plugin
 *
 * Banded Report Generator Framework
 *
 * This is the main block for this framework and it acts as a container for the
 * rest of the {report_*} block types and handles the looping requirements of
 * the given dataset.
 *
 * @type        block
 * @name        report
 * @version     0.1.6
 * @see         http://www.phpinsider.com/smarty-forum/viewtopic.php?t=4125
 *
 * @author      boots < jayboots @at@ yahoo com >
 * @copyright   brainpower, boots, 2004-2005
 * @license     LGPL
 *
 * @thanks      messju mohr, sophistry
 *
 * @param recordset REQUIRED
 * @param record    REQUIRED
 * @param groups    default: null
 * @param resort    default: false
 */

function smarty_block_report($params, $content, &$smarty, &$repeat)
{
    $_params =& smarty_get_current_plugin_params($smarty);

    if (is_null($content)) {
        $_params['report'] = smarty_block_report__initialize($smarty, $repeat, $params);
        smarty_block_report__process_next($smarty, $repeat, $_params['report']);

    } else {
        smarty_block_report__process_next($smarty, $repeat, $_params['report']);
        if (!$repeat)
            return smarty_block_report__return_content($smarty, $repeat, $_params['report'], $content);
    }

}

function smarty_block_report__initialize(&$smarty, &$repeat, $params)
{
    if (!array_key_exists('recordset', $params)) {
        $smarty->trigger_error("{report}: parameter 'recordset' not given.", E_USER_ERROR);
    }
    if (!array_key_exists('record', $params)) {
        $smarty->trigger_error("{report}: parameter 'record' not given.", E_USER_ERROR);
    }

    $_params = array(
    // setup the basic report data structure
      'recordset'   => (is_array($params['recordset'])) ? $params['recordset'] : array()
    , 'buffer'      => ''
    , 'header'      => array('report'=>array('buffer'=>''), 'group'=>array('buffer'=>array()))
    , 'group'       => array()
    , 'groups'      => array()
    , 'stats'       => array()

    // setup the record data sub-structure
    , 'record'      => array(
        'name'      => $params['record']
      , 'count'     => (is_array($params['recordset'])) ? count($params['recordset']) : 0
      , 'first'     => false
      , 'last'      => false
      , 'iteration' => 0
      , 'prev'      => null
      , 'curr'      => null
      , 'next'      => null
      , 'fields'    => (is_array($params['recordset']) && count($params['recordset'])) ? array_keys($params['recordset'][0]) : array()
      )
    );

    // setup groups, if any
    if (array_key_exists('groups', $params)) {
        $groups = preg_split("/[\s,]+/", $params['groups'], -1, PREG_SPLIT_NO_EMPTY);
        $_params['groups'] = $groups;
        foreach ($_params['groups'] as $group) {
            if (!in_array($group, $_params['record']['fields'], true)) {
                $smarty->trigger_error("{report}: given group '$group' does not have a corresponding record field in given recordset.", E_USER_ERROR);
                return;
            }
        }
        // re-sort array
        if (array_key_exists('resort', $params) && $params['resort']) {
            smarty_block_report__group_sort($groups);
            usort($_params['recordset'], 'smarty_block_report__group_sort');
        }
    }
    return $_params;
}

function smarty_block_report__process_next(&$smarty, &$repeat, &$params)
{
    // iterate the recordset
    $recordset =& $params['recordset'];
    $record =& $params['record'];
    $group =& $params['group'];

    // increase current counts
    ++$record['iteration'];

    if (is_null($record['prev']) && is_null($record['curr']) && count($recordset)) {
        // on the first iteration we load current and next
        $record['curr'] = array_shift($recordset);
        $record['next'] = (count($recordset)) ? array_shift($recordset) : null;
        $record['first'] = true;
        foreach ($record['curr'] as $field=>$value) {
            $params['stats']['sum'][$field] = $value;
            $params['stats']['count'][$field] = 1;
            $params['stats']['avg'][$field] = $value;
        }

    } else if (!is_null($record['next'])) {
        // on a normal iteration we load current from next and then reload next
        $record['prev'] = $record['curr'];
        $record['curr'] = $record['next'];
        $record['next'] = (count($recordset)) ? array_shift($recordset) : null;
        $record['first'] = false;
        foreach ($record['curr'] as $field=>$value) {
            if (is_numeric($value)) {
                $params['stats']['sum'][$field] += $value;
                ++$params['stats']['count'][$field];
                $params['stats']['avg'][$field] = $params['stats']['sum'][$field]/$params['stats']['count'][$field];
            }
        }

    } else {
        // there were no iterations at all
        // decrease current counts to counter-act increase
        --$record['iteration'];    // should = -1
    }

    // is there anything left to do?
    $repeat = !$record['last'];
    $record['last'] = is_null($record['next']) && !is_null($record['curr']);

    // process grouping levels 
    foreach ($params['groups'] as $_group) { 
        if (!is_null($record['curr'])) { 
            if ($record['prev'][$_group] != $record['curr'][$_group] OR ($record['prev'][$prev_group] != $record['curr'][$prev_group])) { 
                $group[$_group]['first'] = true; 
                foreach ($record['curr'] as $field=>$value) { 
                    $group[$_group]['stats']['sum'][$field] = $value; 
                    $group[$_group]['stats']['count'][$field] = 1; 
                    $group[$_group]['stats']['avg'][$field] = $value; 
                } 
            } else { 
                $group[$_group]['first'] = false; 
                foreach ($record['curr'] as $field=>$value) { 
                    if (is_numeric($value)) { 
                        $group[$_group]['stats']['sum'][$field] += $value; 
                        ++$group[$_group]['stats']['count'][$field]; 
                        $group[$_group]['stats']['avg'][$field] = $group[$_group]['stats']['sum'][$field]/$group[$_group]['stats']['count'][$field]; 
                     } 
                } 
            } 


            if ($record['last']) { 
                $group[$_group]['last'] = true; 
            } else if (($record['curr'][$_group] == $record['next'][$_group]) AND ($record['curr'][$prev_group] != $record['next'][$prev_group])) { 
               $group[$_group]['last'] = true; 
            } else if (($record['curr'][$_group] != $record['next'][$_group])) { 
                $group[$_group]['last'] = true; 
            } else { 
                $group[$_group]['last'] = false; 
            } 
        } else { 
            $group[$_group]['first'] = is_null($record['prev']); 
            $group[$_group]['last'] = is_null($record['next']); 
        } 
        
        $prev_group = $_group; 
    } 

    if (isset($record['curr'])) {
        $smarty->assign($record['name'], $record['curr']);
    }
}

function smarty_block_report__return_content(&$smarty, &$repeat, &$params, $content)
{
    // only return content generated stored in the output buffer by sub {report_*}
    $buffer = str_replace('##SMARTY_BLOCK_REPORT_HEADER##', $params['header']['report']['buffer'], $params['buffer']);
    foreach ($params['header']['group']['buffer'] as $group=>$headers) {
        foreach ($headers as $group_buffer) {
            $buffer = preg_replace("/##SMARTY_BLOCK_GROUP_HEADER_{$group}##/", smarty_block_report__quote_replace($group_buffer), $buffer, 1);
        }
    }
    return $buffer;
}

function smarty_block_report__quote_replace($string) 
{ 
    return strtr($string, array('\\' => '\\\\', '$' => '\\$')); 
} 


/**
 * multi-column recordset sorter callback for use with usort et al.
 *
 * usage:
 *   smarty_block_report__group_sort($sort_keys_array);
 *   usort($recordset, 'smarty_block_report__group_sort');
 *
 * where:
 *  $recordset is an indexed array of records and each record is an associative
 *  array having the same set of fields in each record
 *
 *  $sort_key_array is an array of key names which are present in every record
 *  of the recordset and are given in the required sort order
 */
function smarty_block_report__group_sort($a, $b=null)
{
    static $sort_keys = array();
    if (is_null($b) && !is_null($a)) {
        // when $b is null, the $a is assumed to contain the keys to sort on
        $sort_keys = $a;
    } else {
        $result = 0; // assume equal
        foreach ($sort_keys as $key) {
            if ($a[$key] < $b[$key]) {
                $result = -1;
                break;
            } else if ($a[$key] > $b[$key]) {
                $result = 1;
                break;
            }
        }
        return $result;
    }
}


/**
 * GENERAL PLUGIN HELPERS
 */

function &smarty_get_parent_plugin_params(&$smarty, $parent_plugin_name)
{
    for ($i=count($smarty->_tag_stack)-1; $i>=0; $i--) {
        $tag_name = $smarty->_tag_stack[$i][0];
        if ($tag_name == $parent_plugin_name) break;
    }

    if ($i<0) {
        /* $parent_plugin_name not found */
        list($plugin_name, $plugin_params) = $smarty->_tag_stack[count($smarty->_tag_stack)-1];
        $smarty->trigger_error("\{$plugin_name\}: not inside \{$parent_plugin_name\}-context", E_USER_ERROR);
        return;
    } else {
        return $smarty->_tag_stack[$i][1];
    }
}

function &smarty_get_current_plugin_params(&$smarty)
{
    return $smarty->_tag_stack[count($smarty->_tag_stack)-1][1];
}

/* vim: set expandtab: */

?>

block.report_header.php

<?php

/**
 * Smarty {report_header}{/report_header} block plugin
 *
 * Banded Report Generator Framework
 *
 * The header block is output at the start of the report. If a group name is
 * specified, then the block is output before the start of the group level.
 *
 * @type        block
 * @name        report_header
 * @version     0.1.6
 * @requires    {report}{/report} block plugin.
 * @see         http://www.phpinsider.com/smarty-forum/viewtopic.php?t=4125
 *
 * @author      boots < jayboots @at@ yahoo com >
 * @copyright   brainpower, boots, 2004, 2005
 * @license     LGPL
 *
 * @thanks      messju mohr, sophistry
 *
 * @param group     default: null
 */
function smarty_block_report_header($params, $content, &$smarty, &$repeat)
{
    $_parent_params =& smarty_get_parent_plugin_params($smarty, 'report');

    if (is_null($content)) {
        /* handle block open tag */
        if (!array_key_exists('group', $params)) {
            // report header
            if ($_parent_params['report']['record']['first'])  {
                $_parent_params['report']['buffer'] .= '##SMARTY_BLOCK_REPORT_HEADER##';
            }
            if ($_parent_params['report']['record']['last']) {
                foreach ($_parent_params['report']['stats'] as $stat_type=>$stat) {
                    $smarty->assign($stat_type, $stat);
                }
            }

        } else {
            // group header
            if (!in_array($params['group'], $_parent_params['report']['record']['fields'], true)) {
                $smarty->trigger_error("{report_header}: given group '{$params['group']}' does not have a corresponding record field in given recordset.", E_USER_ERROR);
            }
            if ($_parent_params['report']['group'][$params['group']]['first']) {
                $_parent_params['report']['buffer'] .= "##SMARTY_BLOCK_GROUP_HEADER_{$params['group']}##";
            }
            if ($_parent_params['report']['group'][$params['group']]['last']) {
                foreach ($_parent_params['report']['group'][$params['group']]['stats'] as $stat_type=>$stat) {
                    $smarty->assign($stat_type, $stat);
                }
            }
        }

    } else {
        /* handle block close tag */
        if (!array_key_exists('group', $params) && $_parent_params['report']['record']['last']) {
           $_parent_params['report']['header']['report']['buffer'] = $content;
        }
        if ($_parent_params['report']['group'][$params['group']]['last']) {
           $_parent_params['report']['header']['group']['buffer'][$params['group']][] = $content;
        }
    }
    return;
}

/* vim: set expandtab: */

?>

block.report_footer.php

<?php

/**
 * Smarty {report_footer}{/report_footer} block plugin
 *
 * Banded Report Generator Framework
 *
 * The footer block is output at the end of the report. If a group name is
 * specified, then the block is output after the end of the group level.
 *
 * @type        block
 * @name        report_footer
 * @version     0.1.6
 * @requires    {report}{/report} block plugin.
 * @see         http://www.phpinsider.com/smarty-forum/viewtopic.php?t=4125
 *
 * @author      boots < jayboots @at@ yahoo com >
 * @copyright   brainpower, boots, 2004, 2005
 * @license     LGPL
 *
 * @thanks      messju mohr, sophistry
 *
 * @param group     default: null
 */
function smarty_block_report_footer($params, $content, &$smarty, &$repeat)
{
    $_parent_params =& smarty_get_parent_plugin_params($smarty, 'report');

    if (is_null($content)) {
        /* handle block open tag */
        if (!array_key_exists('group', $params)) {
           // report footer
            if ($_parent_params['report']['record']['last']) {
                foreach ($_parent_params['report']['stats'] as $stat_type=>$stat) {
                    $smarty->assign($stat_type, $stat);
                }
            }

        } else {
            // group footer
            if (!in_array($params['group'], $_parent_params['report']['record']['fields'], true)) {
                $smarty->trigger_error("{report_footer}: given group '{$params['group']}' does not have a corresponding record field in given recordset.", E_USER_ERROR);
            }
            if ($_parent_params['report']['group'][$params['group']]['last']) {
                foreach ($_parent_params['report']['group'][$params['group']]['stats'] as $stat_type=>$stat) {
                    $smarty->assign($stat_type, $stat);
                }
            }
        }

    } else {
        /* handle block close tag */
        if ($_parent_params['report']['record']['last'] || $_parent_params['report']['group'][$params['group']]['last']) {
            $_parent_params['report']['buffer'] .= $content;
        }
    }
    return;
}

/* vim: set expandtab: */

?>

block.report_detail.php

<?php

/**
 * Smarty {report_detail}{/report_detail} block plugin
 *
 * Banded Report Generator Framework
 *
 * The detail block is output on every iteration of the main {report} block.
 *
 * @type        block
 * @name        report_detail
 * @version     0.1.6
 * @requires    {report}{/report} block plugin.
 * @see         http://www.phpinsider.com/smarty-forum/viewtopic.php?t=4125
 *
 * @author      boots < jayboots @at@ yahoo com >
 * @copyright   brainpower, boots, 2004, 2005
 * @license     LGPL
 *
 * @thanks      messju mohr, sophistry
 */
function smarty_block_report_detail($params, $content, &$smarty, &$repeat)
{
    $_parent_params =& smarty_get_parent_plugin_params($smarty, 'report');

    if (!is_null($content)) {
        /* handle block close tag */
        // add content to {report} tag's current buffer
        $_parent_params['report']['buffer'] .= $content;
    }
    return;
}

/* vim: set expandtab: */

?>

_________________