Older Newer
Thu, 23 Jun 2005 18:24:18 . . . . boots [updated to v0.1.6]


Changes by last author:

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

== History ==

v0.1.6 (June 23, 2005)

* fixed problem with replacements in grouped headers (boots, messju, sophistry)

v0.1.5 (June 21, 2005)

* general code cleanup (boots)

* headers now have access to grouping stats (boots)

* fixed grouping bug (sophistry)

----

== Notes ==

* only text/items within the {report_*} blocks are emitted. All other output in the {report}{/report} block is ignored.

* a recordset follows the standard definition and is an indexed array of records where each record is an associative array all of which have identical fields/typing.

* you must specify grouping levels if you wish to use them

* all {report_*} sub blocks are optional -- for example, you can produce a summary report by not including a {report_detail}{/report_detail} block.

----

== Sample Usage ==

=== report.php ===

<code>

<?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');

?>

</code>

=== report.tpl ===

<code>

{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}

</code>

----

== Setup ==

The following plugins should be placed in your plugins directory:

=== block.report.php ===

<code>

<?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: */

?>

</code>

=== block.report_header.php ===

<code>

<?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: */

?>

</code>

=== block.report_footer.php ===

<code>

<?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: */

?>

</code>

=== block.report_detail.php ===

<code>

<?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: */

?>

</code>

_________________