PEAR::Calendar seems to make you access the classes in a strange way to allow flexibility in displaying "selected" days in a "Smartified" month calendar. Since the PEAR concept (including Calendar) is built around objects, you need to be very careful to use $smarty->assign_by_ref() instead of the normal assign() method.
PEAR calendar lets you store stuff inside its objects (which is nice), but you have to build a so-called "Decorator" class to extend it to support data payloads that can come along with those objects. Calendar only supports isSelected() method out-of-the-box, so there is no way to show what event happened on the day you've selected. Further, in order to extend with a decorator you have to apply it specifically to the day object with the event. You can't use the "wrapper" functionality because it wraps all day objects with the same data regardless.
Finally, the example I made below shows using fetchAll() method rather than the built-in fetch() iterator. I use fetchAll() to be able to handle the weeks. It may be possible to use {$pear_calendar_object->fetch()} inside smarty templates but it didn't occur to me at the time. In any case, this technique, though cumbersome, works. Please feel free to refine it and add to the wiki page.
Here's an example that shows how to make a PEAR_Calendar extension class to implement a simple data repository and render it in a monthly calendar:
// this is a code snippet
// it demonstrates major concepts
// but
// you must provide your own arrays to send to calendar
// $timestamp_array, $timing_array, $index_array
if ( !@include 'Calendar/Calendar.php' ) {
define('CALENDAR_ROOT','/full/path/to/PHP/lib/php/Calendar/');
}
require_once CALENDAR_ROOT.'Month.php';
require_once CALENDAR_ROOT.'Day.php';
require_once CALENDAR_ROOT.'Month/Weekdays.php';
require_once CALENDAR_ROOT.'Decorator.php';
require_once CALENDAR_ROOT.'Decorator/Textual.php';
// CLASSES
//-------------------------------------------
// CREATED: DEC
// MODIFIED: DEC
// DESCRIPTION: CLASS to attach data to PEAR calendar objects
// Decorator to "attach" functionality to selected days
// allows data payload to exist with the date
//
class DataEvent extends Calendar_Decorator {
var $entry;
function DataEvent($calendar) {
Calendar_Decorator::Calendar_Decorator($calendar);
}
// store the data in assoc array
function setData($entry, $key) {
$this->entry[$key][] = $entry;
}
function getData() {
return $this->entry;
}
}
// get the days where events occur
// define a selected day (we need to select
// a day and then get the data from it)
$selection = array();
// MUST sort by timing so we can properly stuff the data into the
// calendar slots
array_multisort($timestamp_array, $timing_array, $index_array);
// also, notice that all of the objects are
// passed around by reference (using =& or &new)
// because we need to make sure we are dealing with the actual data
// and not a copy
foreach ($timestamp_array as $key => $ts) {
// first, check if the selection to be passed to calendar
// has an array entry with this date
if ($previous_timing == $timing_array[$key]) {
// instead of making a new day object just
// stuff more data into the calendar day object
// Attach the payload (the index)
$previous_data_event_object->setData($index_array[$key], 'index');
}else{
// build days that will hold the data
$day = & new Calendar_Day(date("Y",$ts), date("n",$ts), date("j",$ts));
// Create the decorator, passing it the day
$DataEvent = & new DataEvent($day);
// Attach the payload (the index)
$DataEvent->setData($index_array[$key], 'index');
// Add the decorator to the selection
$selection[] =& $DataEvent;
}
// save the timing to compare the next loop
$previous_timing=$timing_array[$key];
$previous_data_event_object =& $DataEvent;
}
// find out the range of dates in months
// to send in as a parameter to the calendar renderer
// sometimes it is 3-month qtrs, but, sometimes it is 6-months
// make sure that we get the whole range by re-sorting the timestamp array
// to go from first to last dates
$timestamp_array_copy=$timestamp_array;
sort($timestamp_array_copy);
$first_month_entry=array_shift($timestamp_array_copy);
$last_month_entry=array_pop($timestamp_array_copy);
// this can only handle 12 month displays
$month_st=date("n",$first_month_entry);
$month_en=date("n",$last_month_entry);
$month_count=($month_en - $month_st)+1;
// build months into separate variables
$x = 0;
while($x<$month_count){
// define variable variables (use them with format $$var)
// for the loop so we get month0, month1, month2
$month_digit="Month".$x;
$month_of_days_by_week_digit="month_of_days_by_week" . $x;
// calendar_month_weekdays gives you access
// to data object that holds a 7 column grid
// zero here means sunday is first day of week
//$Month = & new Calendar_Month_Weekdays($which_year_start, $which_month_start, 0);
// use variable variable here to get three different names
$temp_month=intval($which_month_start)+$x;
$$month_digit = & new Calendar_Month_Weekdays($which_year_start, $temp_month, 0);
// Decorate to get text from the calendar object
$Textual = & new Calendar_Decorator_Textual($$month_digit);
$month_name=$Textual->thisMonthName('long');
// adjusting allows math to be
// performed on dates prior to being sent to the constructor
//$$month_digit->adjust();
// create the month object with the selection of dates
// a little redundancy here since the selection actually spans all three months
$$month_digit->build($selection);
// If month is built with Calendar_Month_Weekdays
// includes non-days (prev and next month's days)
// because it helps you build a nice table block
// get all days in month object into temp array
$month_of_days = & $$month_digit->fetchAll();
// then curdle them down to weeks with array_chunk function
$$month_of_days_by_week_digit = array_chunk($month_of_days, 7);
// put in array
$quarter[]=$$month_of_days_by_week_digit;
$quarter_month_names[]=$month_name;
// increment by 1
$x++;
}
$smarty = new Smarty;
// assign the array of objects so we
// assign by reference avoiding "in memory" copy of object
$smarty->assign_by_ref("quarter", $quarter);
$smarty->assign("quartermonthnames", $quarter_month_names);
$smarty->display('cal.tpl');
Name this cal.tpl
{* monthly layout *}
{* always 7 column headings for the 7 days *}
{* loop over the months in the quarter variable*}
{section name=month loop=$quarter}
{* each month shows its name and the day headings *}
<br><span class="head_2">{$quartermonthnames[month]}:</span><br>
<table border="0" cellpadding="0" cellspacing="3" width="100%">
<tr class="navtd">
<td class="cal_toprow">
<div class="cal_toptext">
S</div>
</td>
<td class="cal_toprow">
<div class="cal_toptext">
M</div>
</td>
<td class="cal_toprow">
<div class="cal_toptext">
T</div>
</td>
<td class="cal_toprow">
<div class="cal_toptext">
W</div>
</td>
<td class="cal_toprow">
<div class="cal_toptext">
T</div>
</td>
<td class="cal_toprow">
<div class="cal_toptext">
F</div>
</td>
<td class="cal_toprow">
<div class="cal_toptext">
S</div>
</td>
</tr>
{* everything with the format $smartyvariable->function() *}
{* represents a method targeting a calendar day object *}
{* start the data section of the calendar *}
{* nested arrays month, week, day, data *}
{section name=week loop=$quarter[month]}
{* each week opens a row *}
<tr class="body">
{* second section to loop in nested array of days *}
{section name=day loop=$quarter[month][week]}
{* each day opens a column *}
<td class="cal_day" width="14%" height="30px" valign="top" {if $quarter[month][week][day]->isSelected() } bgcolor="#D3D3D3" {/if}>
<div class="caption">
{* grab the day number for later use *}
{capture name=daynum}
{$quarter[month][week][day]->thisDay()} <br>
{/capture}
{* check if the day is empty (belongs to another month) *}
{if $quarter[month][week][day]->isEmpty()}
<font color="white">{$smarty.capture.daynum} </font>
{elseif $quarter[month][week][day]->isSelected()}
<font color="red">{$smarty.capture.daynum} </font>
{else}
{$smarty.capture.daynum}
{/if}
{* check if the day is selected *}
{if $quarter[month][week][day]->isSelected()}
{* grab, assign, and loop through the data that has been stuffed into the day *}
<font color="red">
{* grab and assign it *}
{assign var=payload value=$quarter[month][week][day]->getData()}
{* loop through each datatype separately since there might be multiple entries per day *}
{section name=data loop=$payload.index}
<a href="?id={$payload.index[data]}">{$payload.index[data]}</a><br>
{/section}
</font>
{/if}
</div>
</td>
{/section}
</tr>
{/section}
</table>
<br>
{/section}
Here is an updated example that uses a more object oriented approach that is a bit more readable. This example is based on the code above and on that found in the [PEAR Calendar CVS?] and on [toggg.com]. This example displays one month at a time, and is unstyled, and should be fairly accessible.
// ... Initialize directories, load Smarty, connect to database, etc.
// If the PEAR root directory isn't automatically checked in the include_path,
// point this script directly to the location of the PEAR calendar directory
if ( !@include 'Calendar/Calendar.php' ) {
define('CALENDAR_ROOT','/usr/share/pear/Calendar/');
}
// load these components of Pear Calendar
require_once CALENDAR_ROOT.'Month.php';
require_once CALENDAR_ROOT.'Day.php';
require_once CALENDAR_ROOT.'Month/Weekdays.php';
require_once CALENDAR_ROOT.'Decorator.php';
// Select calendar info for any upcoming workshops from the database
// This code is using the ADOdb Database Abstraction Layer
$sql = ("SELECT session_id, session_start, session_end, session_title
FROM et_session_calendar");
// Stuff the returned data into an associative array and cache it for
// 300 seconds (5 minutes)
$dateArray = $db->CacheGetAssoc(300, $sql);
// The events array will contain the dates we will add to the calendar object
$events = array();
// Loop through the dates array, adding each to the event array
foreach ($dateArray as $id => $workshop) {
$events[] = array(
'start' => strtotime($workshop['SESSION_START']),
'end' => strtotime($workshop['SESSION_END']),
'title' => $workshop['SESSION_TITLE'],
'time' => date("g:i A",strtotime($workshop['SESSION_START'])),
'id' => $id );
}
// If the GET variables are not set in the URL, set the now
if (!isset($_GET['y'])) $_GET['y'] = date('Y');
if (!isset($_GET['m'])) $_GET['m'] = date('m');
if (!isset($_GET['d'])) $_GET['d'] = date('d');
class DiaryEvent extends Calendar_Decorator {
var $entries = array();
function addEntry($entry) {
$this->entries[] = $entry;
}
function getEntry() {
$entry = each($this->entries);
if ($entry) {
return $entry['value'];
} else {
reset($this->entries);
return false;
}
}
function entryCount() {
return count($this->entries);
}
}
class MonthPayload_Decorator extends Calendar_Decorator {
function build($selectedDays=array(),$events=array()) {
parent::build($selectedDays);
foreach ($this->calendar->children as $i=> $child) {
// be very careful since we are passing $child by reference to DiaryEvent
$this->calendar->children[$i] = &new DiaryEvent($child);
unset($child); // unset the pointer!
}
if (count($events) > 0) {
$this->setSelection($events);
}
return true;
}
function setSelection($events) {
foreach ($this->calendar->children as $i=> $child) {
$stamp1 = $this->calendar->cE->dateToStamp(
$child->thisYear(), $child->thisMonth(), $child->thisDay());
$stamp2 = $this->calendar->cE->dateToStamp(
$child->thisYear(), $child->thisMonth(), $child->nextDay());
foreach ($events as $event) {
if (($stamp1 >= $event['start'] && $stamp1 < $event['end']) ||
($stamp2 >= $event['start'] && $stamp2 < $event['end']) ||
($stamp1 <= $event['start'] && $stamp2 > $event['end'])
/*if (($stamp1 >= $event['start'] && $stamp1 < $event['end']) ||
($stamp1 <= $event['start'] && $stamp2 > $event['end'])*/ ) {
$this->calendar->children[$i]->addEntry($event);
$this->calendar->children[$i]->setSelected();
}
}
}
}
}
$month = new Calendar_Month_Weekdays($_GET['y'], $_GET['m'], 0);
// Set the current day as a Selected Day and put it in the array
$selectedDays = array (
new Calendar_Day(date('Y'), date('m'), date('d')));
$this->yearOfMonth[] = $month->thisYear();
// Build the days and the workshop events to the decorator
$monthDecorator = new MonthPayload_Decorator($month);
$monthDecorator->build($selectedDays, $events);
// Fetch all days in the month object
$daysInMonth =& $monthDecorator->fetchAll();
// Split the month into weeks
$weeksInMonth = array_chunk($daysInMonth, 7);
// Create links
$prevStamp = $month->prevMonth(true);
$prev = $_SERVER['PHP_SELF'].'?y='.date('Y',$prevStamp).'&m='.date('n',$prevStamp).'&d='.date('j',$prevStamp);
$nextStamp = $month->nextMonth(true);
$next = $_SERVER['PHP_SELF'].'?y='.date('Y',$nextStamp).'&m='.date('n',$nextStamp).'&d='.date('j',$nextStamp);
$smarty->assign_by_ref('month', $weeksInMonth);
$monthName = date('F Y',$month->getTimeStamp());
$smarty->assign('monthName', $monthName);
$smarty->assign('prevMonth', $prev);
$smarty->assign('nextMonth', $next);
$smarty->debugging = true;
$smarty->display('training/calendar/index.tpl');
{* calendar layout *}
<table class="calendar" cellspacing="0" summary="Monthly Calendar" border="1">
{* Display the name of the month and left and right arrows linking to the previous and next months *}
<caption><a href="{$prevMonth}">«</a> {$monthName} <a href="{$nextMonth}">»</a></caption>
{* always 7 column headings for the 7 days *}
<tr>
<th scope="col">Sunday</th>
<th scope="col">Monday</th>
<th scope="col">Tuesday</th>
<th scope="col">Wednesday</th>
<th scope="col">Thursday</th>
<th scope="col">Friday</th>
<th scope="col">Saturday</th>
</tr>
{* nested arrays month, week, day *}
{section name=week loop=$month} {* each week loop opens a new row *}
<tr>
{section name=day loop=$month[week]} {* each day loop creates a new day (will always loop 7 times) *}
{* check if the day is empty (belongs to another month) *}
{* if the day is empty, only display a non-breaking space in the table cell *}
{if $month[week][day]->isEmpty()}<td class="empty"> </td>
{* if the day is marked as selected (contains at least one event), display the date in bold *}
{elseif $month[week][day]->isSelected()}
<td class="selectedday"><strong>{$month[week][day]->thisDay()}</strong></td>
{else}
{* if it is just a regular day, display it *}
<td>{$month[week][day]->thisDay()}</td>
{/if}
{/section}
</tr>
<tr>
{* Create a seperate row where we can display any events *}
{section name=day loop=$month[week]}
{if $month[week][day]->isEmpty()}<td> </td>
{elseif $month[week][day]->isSelected()}
<td class="events">
<ul>
{section name=entry loop=$month[week][day]->entryCount()}
{assign var=payload value=$month[week][day]->getEntry()}
<li><a href="eventinfo.php?session={$payload.id}">{$payload.time} - {$payload.title}</a></li>
{/section}
</ul>
</td>
{else}
<td>
</td>
{/if}
{/section}
</tr>
{/section}
</table>